mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-10-03 16:00:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd866d1400 | ||
|
|
68439bc599 | ||
|
|
f1186e4c84 | ||
|
|
2595cc4853 | ||
|
|
2ae28df8f8 | ||
|
|
58fd03386a | ||
|
|
890ce85cfc | ||
|
|
d5fb956161 | ||
|
|
4bad75e522 | ||
|
|
9e28e78319 | ||
|
|
d561232d19 | ||
|
|
107fe8942c | ||
|
|
06796031e6 | ||
|
|
0cfd3a2fc2 | ||
|
|
7504bf6448 | ||
|
|
09ecd67ca6 | ||
|
|
6dfa199dcb | ||
|
|
4c5148fccf | ||
|
|
108a9cf95b | ||
|
|
9885b2573a | ||
|
|
cdf4cc3d53 | ||
|
|
5b381045f9 | ||
|
|
f246c952a2 |
@@ -86,6 +86,7 @@ jobs:
|
||||
mkdir -p "$CONTENTS/MacOS" "$CONTENTS/Resources"
|
||||
|
||||
cp "target/${{ matrix.target }}/release/ashell" "$CONTENTS/MacOS/"
|
||||
cp "assets/icons/ashell.icns" "$CONTENTS/Resources/ashell.icns"
|
||||
|
||||
VERSION_NUM="${VERSION#v}"
|
||||
cat > "$CONTENTS/Info.plist" <<EOF
|
||||
@@ -98,6 +99,8 @@ jobs:
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>ashell</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>ashell.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>dev.ashell.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
@@ -148,9 +151,20 @@ jobs:
|
||||
name: ${{ matrix.name }}
|
||||
path: ${{ env.ASSET }}
|
||||
|
||||
publish:
|
||||
name: Publish Release
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all workflow artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: Attach to GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ${{ env.ASSET }}
|
||||
files: dist/**/*
|
||||
generate_release_notes: true
|
||||
fail_on_unmatched_files: true
|
||||
|
||||
Generated
+114
-4
@@ -292,11 +292,15 @@ dependencies = [
|
||||
"chrono",
|
||||
"directories",
|
||||
"flate2",
|
||||
"futures",
|
||||
"gpui",
|
||||
"gpui-component",
|
||||
"gpui-component-assets",
|
||||
"gpui_platform",
|
||||
"image",
|
||||
"menu",
|
||||
"notify 6.1.1",
|
||||
"open",
|
||||
"portable-pty",
|
||||
"rfd",
|
||||
"russh",
|
||||
@@ -315,6 +319,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
"winres",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -1411,6 +1416,15 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
@@ -2693,7 +2707,7 @@ dependencies = [
|
||||
"lsp-types",
|
||||
"markdown",
|
||||
"markup5ever_rcdom",
|
||||
"notify",
|
||||
"notify 7.0.0",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"paste",
|
||||
@@ -3455,6 +3469,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.10.2"
|
||||
@@ -3544,6 +3569,25 @@ version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||
|
||||
[[package]]
|
||||
name = "is-docker"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-wsl"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
|
||||
dependencies = [
|
||||
"is-docker",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
@@ -4106,6 +4150,18 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.1"
|
||||
@@ -4267,6 +4323,25 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.12.1",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify 0.9.6",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio 0.8.11",
|
||||
"walkdir",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "7.0.0"
|
||||
@@ -4276,11 +4351,11 @@ dependencies = [
|
||||
"bitflags 2.12.1",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"inotify 0.10.2",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"mio 1.2.1",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -4704,6 +4779,17 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c"
|
||||
dependencies = [
|
||||
"is-wsl",
|
||||
"libc",
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
@@ -4829,6 +4915,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||
|
||||
[[package]]
|
||||
name = "pathfinder_geometry"
|
||||
version = "0.5.1"
|
||||
@@ -7415,7 +7507,7 @@ checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.2.1",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
@@ -7481,6 +7573,15 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
@@ -9135,6 +9236,15 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
|
||||
dependencies = [
|
||||
"toml 0.5.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winsafe"
|
||||
version = "0.0.19"
|
||||
|
||||
@@ -38,9 +38,16 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
uuid = { version = "1", features = ["v4", "serde"] }
|
||||
walkdir = "2"
|
||||
zip = { version = "2", default-features = false, features = ["deflate"] }
|
||||
image = { version = "0.25.9", default-features = false, features = ["png"] }
|
||||
futures = "0.3.32"
|
||||
notify = "6.1.1"
|
||||
open = "5.1"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
|
||||
[build-dependencies]
|
||||
winres = "0.1.12"
|
||||
|
||||
+11
-1
@@ -8,9 +8,19 @@
|
||||
|
||||
This project focuses on providing a high-performance and visually appealing shell workspace by combining local and remote environments with a rich set of built-in features.
|
||||
|
||||
## 🚀 v0.3 Massive Update!
|
||||
|
||||
The brand-new v0.3 introduces comprehensive upgrades to both architecture and user experience:
|
||||
- ✨ **Global Real-Time Font Settings** (Thanks to [@ancion](https://github.com/ancion) for [PR #6](https://github.com/rust-kotlin/ashell/pull/6)): Customize your global UI fonts and terminal rendering fonts independently. Adjust text size in real-time with automatic persistence.
|
||||
- ✨ **Concurrent SFTP Transfers**: A dedicated Transfers dialog now supports highly optimized, concurrent file uploads and downloads with precise progress display.
|
||||
- ✨ **Persistent Workspace State**: Your sidebar width adjustments, terminal themes, font settings, and file visibility preferences are completely restored upon your next launch!
|
||||
- ✨ **Intelligent SSH Disconnect/Reconnect**: Greatly enhanced SSH connection resilience. Provides a beautiful and localized overlay for one-click re-connection on unexpected disconnections.
|
||||
- ✨ **Hot-Swappable i18n System**: Deeply optimized internationalization. Switch instantly between English and Simplified Chinese without restarting the application.
|
||||
- ✨ **Smart Right-Click Actions**: Added intelligent right-click copy and paste for the terminal, featuring a dedicated settings toggle and OS-native keyboard shortcut hints.
|
||||
|
||||
## Download
|
||||
|
||||
You can download the latest pre-compiled releases for macOS and Windows from the [GitHub Releases page](https://github.com/rust-kotlin/ashell/releases/latest).
|
||||
You can download the latest pre-compiled releases for macOS, Windows, and Linux from the [GitHub Releases page](https://github.com/rust-kotlin/ashell/releases/latest).
|
||||
|
||||
## Mac Installation Guide
|
||||
|
||||
|
||||
@@ -8,9 +8,19 @@
|
||||
|
||||
该项目旨在通过结合本地和远程环境,并内置丰富的核心功能,提供一个高性能且美观的 Shell 工作区。
|
||||
|
||||
## 🚀 v0.3 版本重磅更新!
|
||||
|
||||
全新发布的 v0.3 迎来了架构和体验的全面升级:
|
||||
- ✨ **全局实时字体设置与字号调节**(感谢 [@ancion](https://github.com/ancion) 提供的 [PR #6](https://github.com/rust-kotlin/ashell/pull/6)):支持自定义全局 UI 字体和终端渲染字体,支持实时字号调节并自动记忆。
|
||||
- ✨ **SFTP 多任务并发传输队列**:引入全新的独立传输对话框,支持多文件并发上传下载,提供精准的进度展示。
|
||||
- ✨ **持久化工作区记忆**:您对侧边栏的尺寸拖拽、终端主题、字体、隐藏文件开关等一切设置都会在下次启动时原样恢复!
|
||||
- ✨ **智能断线重连与状态感知**:全面增强的 SSH 网络异常处理机制,断线后直接提供美观的覆盖层用于一键重连或关闭。
|
||||
- ✨ **多语言热切换增强**:深度优化的 i18n 系统,包含中文与英文在内的所有界面文案现在均支持热切换。
|
||||
- ✨ **智能右键快捷操作**:新增终端右键智能复制与粘贴功能,提供专属设置项自由控制开关,并支持根据不同操作系统自适应显示原生快捷键提示。
|
||||
|
||||
## 下载
|
||||
|
||||
您可以从 [GitHub Releases 页面](https://github.com/rust-kotlin/ashell/releases/latest) 下载 macOS 和 Windows 版本的最新预编译程序。
|
||||
您可以从 [GitHub Releases 页面](https://github.com/rust-kotlin/ashell/releases/latest) 下载 macOS、Windows 和 Linux 版本的最新预编译程序。
|
||||
|
||||
## Mac 安装指南
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 437 KiB |
@@ -0,0 +1,8 @@
|
||||
fn main() {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("assets/icons/ashell.ico");
|
||||
res.compile().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,23 @@ upload_file: "Upload File"
|
||||
upload_folder: "Upload Folder"
|
||||
hidden: "Hidden"
|
||||
download: "Download"
|
||||
download_folder: "Download Folder"
|
||||
download_count: "Download (%{count})"
|
||||
name: "NAME"
|
||||
size: "SIZE"
|
||||
modified: "MODIFIED"
|
||||
new_ssh_connection: "New SSH Connection"
|
||||
create_or_edit_ssh_session: "Create or edit an SSH session"
|
||||
new_folder: "New Folder"
|
||||
create_folder_success: "Created folder: %{name}"
|
||||
create_folder_failed: "Failed to create folder: %{err}"
|
||||
delete_selected: "Delete"
|
||||
confirm_delete: "Confirm Delete"
|
||||
confirm_delete_desc: "Are you sure you want to delete the selected %{count} items? This action cannot be undone."
|
||||
system_path_warning: "WARNING: You are attempting to delete critical system paths! This may cause system corruption!"
|
||||
delete_success: "Successfully deleted %{count} items"
|
||||
delete_failed: "Failed to delete: %{err}"
|
||||
deleting_paths: "Deleting %{count} items..."
|
||||
local_terminal: "Local Terminal"
|
||||
open_local_shell_tab: "Open a new local shell tab"
|
||||
connect: "Connect"
|
||||
@@ -51,10 +62,50 @@ add_ssh: "+ ssh"
|
||||
open_local_or_ssh: "Open a local terminal or pick an SSH session."
|
||||
open_session: "Open Session"
|
||||
settings: "Settings"
|
||||
ui_font_size: "UI Font Size"
|
||||
terminal_font_size: "Terminal Font Size"
|
||||
system_default: "System Default"
|
||||
ui_font_family: "UI Font"
|
||||
terminal_font_family: "Terminal Font"
|
||||
theme_management_hint: "Theme mode and light/dark theme families are managed from the top-bar theme button."
|
||||
local_host: "local host"
|
||||
local_terminal_opened: "local terminal opened"
|
||||
connecting: "Connecting"
|
||||
starting_connection: "starting connection..."
|
||||
connection_failed: "Connection Failed"
|
||||
|
||||
preview_failed: "Preview failed: %{err}"
|
||||
downloading_file: "Downloading %{base}..."
|
||||
download_failed: "Download failed: %{err}"
|
||||
uploading: "Uploading..."
|
||||
upload_failed: "Upload failed: %{err}"
|
||||
downloaded_folder: "Downloaded folder to %{path}"
|
||||
downloaded_file: "Downloaded file to %{path}"
|
||||
uploaded_file: "Uploaded file"
|
||||
uploaded_folder: "Uploaded folder"
|
||||
uploaded_n_files: "Uploaded %{files} files"
|
||||
uploaded_n_folders: "Uploaded %{folders} folders"
|
||||
uploaded_files_and_folders: "Uploaded %{files} files and %{folders} folders"
|
||||
n_files: "%{files} files"
|
||||
n_folders: "%{folders} folders"
|
||||
n_files_and_folders: "%{files} files and %{folders} folders"
|
||||
reset: "Reset"
|
||||
reset_layout: "Reset Default Layout"
|
||||
reset_layout_success: "Layout reset. Window size takes effect on restart."
|
||||
|
||||
transfers: "Transfers"
|
||||
no_transfers_yet: "No transfers yet"
|
||||
downloading: "Downloading"
|
||||
paused: "Paused"
|
||||
completed: "Completed"
|
||||
failed: "Failed"
|
||||
cancelled: "Cancelled"
|
||||
session: "Session"
|
||||
|
||||
software_builtin: "Built-in"
|
||||
retry: "Retry"
|
||||
right_click_copy_paste: "Right-click Copy/Paste"
|
||||
copy_paste_hint: "Tip: Regardless of this switch, you can always use %{key} + C/V to copy/paste."
|
||||
edit_file: "Edit File"
|
||||
edit_file_tooltip: "Download to temporary directory, open in default editor, and auto upload on save."
|
||||
auto_saved_and_uploaded: "Auto-saved & uploaded: %{base}"
|
||||
|
||||
@@ -6,12 +6,25 @@ upload_file: "上传文件"
|
||||
upload_folder: "上传文件夹"
|
||||
hidden: "显示隐藏文件"
|
||||
download: "下载"
|
||||
download_folder: "下载文件夹"
|
||||
download_count: "下载 (%{count})"
|
||||
name: "名称"
|
||||
size: "大小"
|
||||
modified: "修改时间"
|
||||
new_ssh_connection: "新建 SSH 连接"
|
||||
create_or_edit_ssh_session: "创建或编辑一个 SSH 会话"
|
||||
new_folder: "新建文件夹"
|
||||
create_folder_success: "成功创建文件夹: %{name}"
|
||||
create_folder_failed: "创建文件夹失败: %{err}"
|
||||
delete_selected: "删除"
|
||||
confirm_delete: "确认删除"
|
||||
confirm_delete_desc: "确定要删除以下选中的 %{count} 个项吗?此操作无法撤销。"
|
||||
system_path_warning: "警告:您即将删除系统关键路径!这可能会导致系统损坏!"
|
||||
delete_success: "成功删除 %{count} 个项"
|
||||
delete_failed: "删除失败: %{err}"
|
||||
deleting_paths: "正在删除 %{count} 个项..."
|
||||
cancel: "取消"
|
||||
confirm: "确认"
|
||||
local_terminal: "本地终端"
|
||||
open_local_shell_tab: "打开一个新的本地终端页面"
|
||||
connect: "连接"
|
||||
@@ -51,10 +64,50 @@ add_ssh: "+ SSH"
|
||||
open_local_or_ssh: "打开本地终端或选择一个 SSH 会话。"
|
||||
open_session: "打开会话"
|
||||
settings: "设置"
|
||||
ui_font_size: "界面字体大小"
|
||||
terminal_font_size: "终端字体大小"
|
||||
system_default: "系统默认"
|
||||
ui_font_family: "界面字体"
|
||||
terminal_font_family: "终端字体"
|
||||
theme_management_hint: "主题模式和亮/暗主题系列可通过顶栏的主题按钮管理。"
|
||||
local_host: "本地主机"
|
||||
local_terminal_opened: "已打开本地终端"
|
||||
connecting: "正在连接"
|
||||
starting_connection: "开始连接..."
|
||||
connection_failed: "连接失败"
|
||||
|
||||
preview_failed: "预览失败: %{err}"
|
||||
downloading_file: "正在下载 %{base}..."
|
||||
download_failed: "下载失败: %{err}"
|
||||
uploading: "正在上传..."
|
||||
upload_failed: "上传失败: %{err}"
|
||||
downloaded_folder: "已下载文件夹至 %{path}"
|
||||
downloaded_file: "已下载文件至 %{path}"
|
||||
uploaded_file: "已上传文件"
|
||||
uploaded_folder: "已上传文件夹"
|
||||
uploaded_n_files: "已上传 %{files} 个文件"
|
||||
uploaded_n_folders: "已上传 %{folders} 个文件夹"
|
||||
uploaded_files_and_folders: "已上传 %{files} 个文件及 %{folders} 个文件夹"
|
||||
n_files: "%{files} 个文件"
|
||||
n_folders: "%{folders} 个文件夹"
|
||||
n_files_and_folders: "%{files} 个文件及 %{folders} 个文件夹"
|
||||
reset: "重置"
|
||||
reset_layout: "恢复默认布局"
|
||||
reset_layout_success: "布局占比已恢复默认。窗口大小将在下次启动时生效。"
|
||||
|
||||
transfers: "传输记录"
|
||||
no_transfers_yet: "暂无传输记录"
|
||||
downloading: "正在下载"
|
||||
paused: "已暂停"
|
||||
completed: "已完成"
|
||||
failed: "失败"
|
||||
cancelled: "已取消"
|
||||
session: "会话"
|
||||
|
||||
software_builtin: "软件内置"
|
||||
retry: "重试"
|
||||
right_click_copy_paste: "右键复制/粘贴"
|
||||
copy_paste_hint: "提示:不管是否开启此开关,都可以使用 %{key} + C/V 快捷键进行复制和粘贴。"
|
||||
edit_file: "编辑文件"
|
||||
edit_file_tooltip: "下载到临时目录,使用默认编辑器打开,并在保存后自动上传。"
|
||||
auto_saved_and_uploaded: "已自动保存并上传: %{base}"
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 279 KiB |
@@ -17,6 +17,8 @@ rm -rf "$APP_DIR"
|
||||
mkdir -p "$MACOS_DIR" "$RESOURCES_DIR"
|
||||
cp "$ROOT_DIR/target/release/$APP_NAME" "$MACOS_DIR/$APP_NAME"
|
||||
|
||||
cp "$ROOT_DIR/assets/icons/ashell.icns" "$RESOURCES_DIR/ashell.icns"
|
||||
|
||||
cat > "$CONTENTS_DIR/Info.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||
@@ -27,6 +29,8 @@ cat > "$CONTENTS_DIR/Info.plist" <<EOF
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$APP_NAME</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>ashell.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$BUNDLE_ID</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
|
||||
+148
-1
@@ -70,11 +70,36 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum SavedWindowBounds {
|
||||
Fullscreen {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
},
|
||||
Maximized {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
},
|
||||
Windowed {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct ConfigFile {
|
||||
#[serde(default)]
|
||||
pub follow_system_theme: bool,
|
||||
#[serde(default)]
|
||||
pub theme_mode: String,
|
||||
#[serde(default)]
|
||||
pub light_theme_name: String,
|
||||
#[serde(default)]
|
||||
pub dark_theme_name: String,
|
||||
@@ -82,8 +107,24 @@ pub struct ConfigFile {
|
||||
pub locale: String,
|
||||
#[serde(default = "default_terminal_font_size")]
|
||||
pub terminal_font_size: f32,
|
||||
#[serde(default = "default_ui_font_size")]
|
||||
pub ui_font_size: f32,
|
||||
#[serde(default)]
|
||||
pub right_click_copy_paste: bool,
|
||||
#[serde(default = "default_ui_font_family")]
|
||||
pub ui_font_family: String,
|
||||
#[serde(default = "default_terminal_font_family")]
|
||||
pub terminal_font_family: String,
|
||||
#[serde(default)]
|
||||
pub sessions: Vec<Session>,
|
||||
#[serde(default)]
|
||||
pub window_bounds: Option<SavedWindowBounds>,
|
||||
#[serde(default)]
|
||||
pub workspace_panels: Option<Vec<f32>>,
|
||||
#[serde(default)]
|
||||
pub body_panels: Option<Vec<f32>>,
|
||||
#[serde(default)]
|
||||
pub transfers: Vec<crate::terminal::Transfer>,
|
||||
}
|
||||
|
||||
fn default_locale() -> String {
|
||||
@@ -94,6 +135,20 @@ fn default_terminal_font_size() -> f32 {
|
||||
13.0
|
||||
}
|
||||
|
||||
fn default_ui_font_size() -> f32 {
|
||||
12.0
|
||||
}
|
||||
|
||||
pub fn default_ui_font_family() -> String {
|
||||
// ".SystemUIFont" is a GPUI sentinel that resolves to the platform system UI font.
|
||||
// This matches gpui-component's own Theme default.
|
||||
".SystemUIFont".to_string()
|
||||
}
|
||||
|
||||
fn default_terminal_font_family() -> String {
|
||||
"Maple Mono NF CN".to_string()
|
||||
}
|
||||
|
||||
pub struct ConfigStore {
|
||||
path: PathBuf,
|
||||
cache: ConfigFile,
|
||||
@@ -105,6 +160,9 @@ impl ConfigStore {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.with_context(|| format!("failed to create config dir {}", parent.display()))?;
|
||||
let tmp_dir = parent.join("tmp");
|
||||
let _ = fs::remove_dir_all(&tmp_dir);
|
||||
let _ = fs::create_dir_all(&tmp_dir);
|
||||
}
|
||||
|
||||
let cache = if path.exists() {
|
||||
@@ -138,10 +196,18 @@ impl ConfigStore {
|
||||
&self.cache.sessions
|
||||
}
|
||||
|
||||
pub fn tmp_dir(&self) -> Option<PathBuf> {
|
||||
self.path.parent().map(|p| p.join("tmp"))
|
||||
}
|
||||
|
||||
pub fn follow_system_theme(&self) -> bool {
|
||||
self.cache.follow_system_theme
|
||||
}
|
||||
|
||||
pub fn theme_mode(&self) -> &str {
|
||||
&self.cache.theme_mode
|
||||
}
|
||||
|
||||
pub fn light_theme_name(&self) -> &str {
|
||||
&self.cache.light_theme_name
|
||||
}
|
||||
@@ -173,18 +239,98 @@ impl ConfigStore {
|
||||
pub fn set_theme_preferences(
|
||||
&mut self,
|
||||
follow_system_theme: bool,
|
||||
theme_mode: impl Into<String>,
|
||||
light_theme_name: impl Into<String>,
|
||||
dark_theme_name: impl Into<String>,
|
||||
) {
|
||||
self.cache.follow_system_theme = follow_system_theme;
|
||||
self.cache.theme_mode = theme_mode.into();
|
||||
self.cache.light_theme_name = light_theme_name.into();
|
||||
self.cache.dark_theme_name = dark_theme_name.into();
|
||||
}
|
||||
|
||||
pub fn window_bounds(&self) -> Option<&SavedWindowBounds> {
|
||||
self.cache.window_bounds.as_ref()
|
||||
}
|
||||
|
||||
pub fn workspace_panels(&self) -> Option<&Vec<f32>> {
|
||||
self.cache.workspace_panels.as_ref()
|
||||
}
|
||||
|
||||
pub fn body_panels(&self) -> Option<&Vec<f32>> {
|
||||
self.cache.body_panels.as_ref()
|
||||
}
|
||||
|
||||
pub fn transfers(&self) -> Vec<crate::terminal::Transfer> {
|
||||
self.cache.transfers.clone()
|
||||
}
|
||||
|
||||
pub fn set_transfers(&mut self, transfers: Vec<crate::terminal::Transfer>) {
|
||||
self.cache.transfers = transfers;
|
||||
if let Err(err) = self.save() {
|
||||
tracing::error!("failed to save config: {err:#}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_layout_state(
|
||||
&mut self,
|
||||
window_bounds: Option<SavedWindowBounds>,
|
||||
workspace_panels: Option<Vec<f32>>,
|
||||
body_panels: Option<Vec<f32>>,
|
||||
) {
|
||||
self.cache.window_bounds = window_bounds;
|
||||
self.cache.workspace_panels = workspace_panels;
|
||||
self.cache.body_panels = body_panels;
|
||||
}
|
||||
|
||||
pub fn set_terminal_font_size(&mut self, terminal_font_size: f32) {
|
||||
self.cache.terminal_font_size = terminal_font_size.max(10.0);
|
||||
}
|
||||
|
||||
pub fn ui_font_size(&self) -> f32 {
|
||||
if self.cache.ui_font_size <= 0.0 {
|
||||
default_ui_font_size()
|
||||
} else {
|
||||
self.cache.ui_font_size
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ui_font_size(&mut self, ui_font_size: f32) {
|
||||
self.cache.ui_font_size = ui_font_size.max(8.0);
|
||||
}
|
||||
|
||||
pub fn ui_font_family(&self) -> &str {
|
||||
if self.cache.ui_font_family.is_empty() {
|
||||
".SystemUIFont"
|
||||
} else {
|
||||
&self.cache.ui_font_family
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ui_font_family(&mut self, family: &str) {
|
||||
self.cache.ui_font_family = family.to_string();
|
||||
}
|
||||
|
||||
pub fn right_click_copy_paste(&self) -> bool {
|
||||
self.cache.right_click_copy_paste
|
||||
}
|
||||
|
||||
pub fn set_right_click_copy_paste(&mut self, val: bool) {
|
||||
self.cache.right_click_copy_paste = val;
|
||||
}
|
||||
|
||||
pub fn terminal_font_family(&self) -> &str {
|
||||
if self.cache.terminal_font_family.is_empty() {
|
||||
"Maple Mono NF CN"
|
||||
} else {
|
||||
&self.cache.terminal_font_family
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_terminal_font_family(&mut self, family: &str) {
|
||||
self.cache.terminal_font_family = family.to_string();
|
||||
}
|
||||
|
||||
pub fn get(&self, id: &str) -> Option<&Session> {
|
||||
self.cache.sessions.iter().find(|s| s.id == id)
|
||||
}
|
||||
@@ -206,6 +352,7 @@ impl ConfigStore {
|
||||
return Ok(());
|
||||
}
|
||||
let raw = serde_json::to_string_pretty(&self.cache)?;
|
||||
fs::write(&self.path, raw).with_context(|| format!("failed to write {}", self.path.display()))
|
||||
fs::write(&self.path, raw)
|
||||
.with_context(|| format!("failed to write {}", self.path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
|
||||
use portable_pty::{CommandBuilder, PtySize, native_pty_system};
|
||||
|
||||
use crate::terminal::{BackendCommand, BackendEvent, BackendTx};
|
||||
|
||||
@@ -34,7 +34,10 @@ pub fn spawn_local_terminal(
|
||||
});
|
||||
|
||||
let mut cmd = CommandBuilder::new(&shell);
|
||||
cmd.env("TERM", std::env::var("TERM").unwrap_or_else(|_| "xterm-256color".into()));
|
||||
cmd.env(
|
||||
"TERM",
|
||||
std::env::var("TERM").unwrap_or_else(|_| "xterm-256color".into()),
|
||||
);
|
||||
cmd.env(
|
||||
"COLORTERM",
|
||||
std::env::var("COLORTERM").unwrap_or_else(|_| "truecolor".into()),
|
||||
|
||||
+1545
-348
File diff suppressed because it is too large
Load Diff
+873
-120
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -3,19 +3,19 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use async_trait::async_trait;
|
||||
use directories::BaseDirs;
|
||||
use russh::{
|
||||
client::{self, Handler},
|
||||
keys::{decode_secret_key, key::PrivateKeyWithHashAlg, load_secret_key, HashAlg, PrivateKey},
|
||||
ChannelMsg, Disconnect,
|
||||
client::{self, Handler},
|
||||
keys::{HashAlg, PrivateKey, decode_secret_key, key::PrivateKeyWithHashAlg, load_secret_key},
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
config::{AuthMethod, Session},
|
||||
system::{remote_snapshot_from_kv, SystemSnapshot},
|
||||
system::{SystemSnapshot, remote_snapshot_from_kv},
|
||||
terminal::{BackendCommand, BackendEvent, BackendTx},
|
||||
};
|
||||
|
||||
|
||||
+11
-13
@@ -1,6 +1,9 @@
|
||||
use std::{collections::BTreeMap, time::{Duration, Instant}};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{Result, anyhow};
|
||||
use sysinfo::{Disks, Networks, System};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -69,7 +72,10 @@ impl SystemSampler {
|
||||
let rx_total: u64 = self.nets.iter().map(|(_, d)| d.total_received()).sum();
|
||||
let tx_total: u64 = self.nets.iter().map(|(_, d)| d.total_transmitted()).sum();
|
||||
let now = Instant::now();
|
||||
let elapsed = now.duration_since(self.last_instant).as_secs_f64().max(0.001);
|
||||
let elapsed = now
|
||||
.duration_since(self.last_instant)
|
||||
.as_secs_f64()
|
||||
.max(0.001);
|
||||
let rx_rate = (rx_total.saturating_sub(self.last_rx_total) as f64 / elapsed) as u64;
|
||||
let tx_rate = (tx_total.saturating_sub(self.last_tx_total) as f64 / elapsed) as u64;
|
||||
self.last_rx_total = rx_total;
|
||||
@@ -92,16 +98,8 @@ impl SystemSampler {
|
||||
cpu_percent,
|
||||
mem_percent: ratio(mem_used, mem_total),
|
||||
swap_percent: ratio(swap_used, swap_total),
|
||||
mem_detail: format!(
|
||||
"{}/{}",
|
||||
format_bytes(mem_used),
|
||||
format_bytes(mem_total)
|
||||
),
|
||||
swap_detail: format!(
|
||||
"{}/{}",
|
||||
format_bytes(swap_used),
|
||||
format_bytes(swap_total)
|
||||
),
|
||||
mem_detail: format!("{}/{}", format_bytes(mem_used), format_bytes(mem_total)),
|
||||
swap_detail: format!("{}/{}", format_bytes(swap_used), format_bytes(swap_total)),
|
||||
net_rx: format!("{}/s", format_bytes(rx_rate)),
|
||||
net_tx: format!("{}/s", format_bytes(tx_rate)),
|
||||
disks,
|
||||
|
||||
+164
-29
@@ -5,10 +5,7 @@ use alacritty_terminal::{
|
||||
grid::{Dimensions, Scroll},
|
||||
index::{Column, Point, Side},
|
||||
selection::{Selection, SelectionRange, SelectionType},
|
||||
term::{
|
||||
cell::Cell,
|
||||
point_to_viewport, viewport_to_point, Config, Term, TermMode,
|
||||
},
|
||||
term::{Config, Term, TermMode, cell::Cell, point_to_viewport, viewport_to_point},
|
||||
vte::ansi::{CursorShape, Processor},
|
||||
};
|
||||
use gpui::Keystroke;
|
||||
@@ -32,16 +29,62 @@ pub enum BackendCommand {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BackendEvent {
|
||||
Output { tab_id: String, bytes: Vec<u8> },
|
||||
Status { tab_id: String, text: String },
|
||||
Connected { tab_id: String },
|
||||
SftpEntries { tab_id: String, path: String, entries: Vec<RemoteEntry> },
|
||||
SftpPreview { tab_id: String, preview: PreviewData },
|
||||
SftpStatus { tab_id: String, text: String },
|
||||
RemoteSystem { tab_id: String, snapshot: SystemSnapshot },
|
||||
RemoteSystemUnavailable { tab_id: String, reason: String },
|
||||
Closed { tab_id: String, reason: String },
|
||||
TerminalTitleChanged { tab_id: String, title: String },
|
||||
Output {
|
||||
tab_id: String,
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
Status {
|
||||
tab_id: String,
|
||||
text: String,
|
||||
},
|
||||
Connected {
|
||||
tab_id: String,
|
||||
},
|
||||
SftpEntries {
|
||||
tab_id: String,
|
||||
path: String,
|
||||
entries: Vec<RemoteEntry>,
|
||||
},
|
||||
SftpPreview {
|
||||
tab_id: String,
|
||||
preview: PreviewData,
|
||||
},
|
||||
SftpStatus {
|
||||
tab_id: String,
|
||||
text: String,
|
||||
},
|
||||
RemoteSystem {
|
||||
tab_id: String,
|
||||
snapshot: SystemSnapshot,
|
||||
},
|
||||
RemoteSystemUnavailable {
|
||||
tab_id: String,
|
||||
reason: String,
|
||||
},
|
||||
SftpHome {
|
||||
tab_id: String,
|
||||
home: String,
|
||||
},
|
||||
TransferProgress {
|
||||
#[allow(dead_code)]
|
||||
tab_id: String,
|
||||
id: String,
|
||||
transferred: u64,
|
||||
total: Option<u64>,
|
||||
state: TransferState,
|
||||
},
|
||||
TransferStarted {
|
||||
tab_id: String,
|
||||
info: TransferInfo,
|
||||
},
|
||||
Closed {
|
||||
tab_id: String,
|
||||
reason: String,
|
||||
},
|
||||
TerminalTitleChanged {
|
||||
tab_id: String,
|
||||
title: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -120,19 +163,40 @@ pub struct SftpUiState {
|
||||
pub selected_path: Option<String>,
|
||||
pub preview: Option<PreviewData>,
|
||||
pub selected_entries: std::collections::HashSet<String>,
|
||||
pub home_dir: String,
|
||||
}
|
||||
|
||||
impl TerminalTab {
|
||||
pub fn new_local(id: String, title: String, backend: BackendTx, events: std::sync::mpsc::Sender<BackendEvent>) -> Self {
|
||||
Self::new(id, title, TabKind::Local, "local shell".into(), backend, events)
|
||||
pub fn new_local(
|
||||
id: String,
|
||||
title: String,
|
||||
backend: BackendTx,
|
||||
events: std::sync::mpsc::Sender<BackendEvent>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id,
|
||||
title,
|
||||
TabKind::Local,
|
||||
"local shell".into(),
|
||||
backend,
|
||||
events,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_ssh(id: String, session: &Session, backend: BackendTx, events: std::sync::mpsc::Sender<BackendEvent>) -> Self {
|
||||
pub fn new_ssh(
|
||||
id: String,
|
||||
session: &Session,
|
||||
backend: BackendTx,
|
||||
events: std::sync::mpsc::Sender<BackendEvent>,
|
||||
) -> Self {
|
||||
let mut tab = Self::new(
|
||||
id,
|
||||
session.name.clone(),
|
||||
TabKind::Ssh,
|
||||
format!("connecting {}@{}:{}", session.user, session.host, session.port),
|
||||
format!(
|
||||
"connecting {}@{}:{}",
|
||||
session.user, session.host, session.port
|
||||
),
|
||||
backend,
|
||||
events,
|
||||
);
|
||||
@@ -140,16 +204,24 @@ impl TerminalTab {
|
||||
tab.connected = false;
|
||||
tab.sftp = Some(SftpUiState {
|
||||
current_path: "/".into(),
|
||||
status: "sftp connecting...".into(),
|
||||
status: t!("sftp_connecting").to_string(),
|
||||
entries: Vec::new(),
|
||||
selected_path: None,
|
||||
preview: None,
|
||||
selected_entries: std::collections::HashSet::new(),
|
||||
home_dir: "/".into(),
|
||||
});
|
||||
tab
|
||||
}
|
||||
|
||||
fn new(id: String, title: String, kind: TabKind, status: String, backend: BackendTx, events: std::sync::mpsc::Sender<BackendEvent>) -> Self {
|
||||
fn new(
|
||||
id: String,
|
||||
title: String,
|
||||
kind: TabKind,
|
||||
status: String,
|
||||
backend: BackendTx,
|
||||
events: std::sync::mpsc::Sender<BackendEvent>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.clone(),
|
||||
title,
|
||||
@@ -264,8 +336,11 @@ impl TerminalTab {
|
||||
self.term.scroll_display(Scroll::Bottom);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn has_selection(&self) -> bool {
|
||||
self.term.selection_to_string().is_some_and(|text| !text.is_empty())
|
||||
self.term
|
||||
.selection_to_string()
|
||||
.is_some_and(|text| !text.is_empty())
|
||||
}
|
||||
|
||||
pub fn clear_selection(&mut self) {
|
||||
@@ -273,7 +348,9 @@ impl TerminalTab {
|
||||
}
|
||||
|
||||
pub fn selection_text(&self) -> Option<String> {
|
||||
self.term.selection_to_string().filter(|text| !text.is_empty())
|
||||
self.term
|
||||
.selection_to_string()
|
||||
.filter(|text| !text.is_empty())
|
||||
}
|
||||
|
||||
pub fn begin_selection(
|
||||
@@ -301,19 +378,25 @@ impl TerminalTab {
|
||||
}
|
||||
|
||||
pub fn paste_text(&mut self, text: &str) {
|
||||
let paste_text = text.replace('\x1b', "").replace("\r\n", "\r").replace('\n', "\r");
|
||||
let paste_text = text
|
||||
.replace('\x1b', "")
|
||||
.replace("\r\n", "\r")
|
||||
.replace('\n', "\r");
|
||||
|
||||
self.backend
|
||||
.send(BackendCommand::Input(paste_text.into_bytes()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn viewport_selection_from_range(
|
||||
display_offset: usize,
|
||||
selection: &Option<SelectionRange>,
|
||||
) -> Option<ViewportSelection> {
|
||||
let SelectionRange { start, end, is_block } = selection.as_ref().copied()?;
|
||||
let SelectionRange {
|
||||
start,
|
||||
end,
|
||||
is_block,
|
||||
} = selection.as_ref().copied()?;
|
||||
let start = point_to_viewport(display_offset, start)?;
|
||||
let end = point_to_viewport(display_offset, end)?;
|
||||
|
||||
@@ -336,7 +419,9 @@ struct TerminalListener {
|
||||
impl EventListener for TerminalListener {
|
||||
fn send_event(&self, event: Event) {
|
||||
match event {
|
||||
Event::PtyWrite(output) => self.backend.send(BackendCommand::Input(output.into_bytes())),
|
||||
Event::PtyWrite(output) => self
|
||||
.backend
|
||||
.send(BackendCommand::Input(output.into_bytes())),
|
||||
Event::TextAreaSizeRequest(format) => {
|
||||
let size = alacritty_terminal::event::WindowSize {
|
||||
num_lines: 30,
|
||||
@@ -358,14 +443,25 @@ impl EventListener for TerminalListener {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_term(cols: u16, rows: u16, backend: BackendTx, tab_id: String, events: std::sync::mpsc::Sender<BackendEvent>) -> Term<TerminalListener> {
|
||||
use rust_i18n::t;
|
||||
fn new_term(
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
backend: BackendTx,
|
||||
tab_id: String,
|
||||
events: std::sync::mpsc::Sender<BackendEvent>,
|
||||
) -> Term<TerminalListener> {
|
||||
Term::new(
|
||||
Config {
|
||||
scrolling_history: 2000,
|
||||
..Config::default()
|
||||
},
|
||||
&TerminalSize::new(cols, rows),
|
||||
TerminalListener { tab_id, backend, events },
|
||||
TerminalListener {
|
||||
tab_id,
|
||||
backend,
|
||||
events,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -397,7 +493,11 @@ impl Dimensions for TerminalSize {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode_key(keystroke: &Keystroke, app_cursor_mode: bool, option_as_meta: bool) -> Option<Vec<u8>> {
|
||||
pub fn encode_key(
|
||||
keystroke: &Keystroke,
|
||||
app_cursor_mode: bool,
|
||||
option_as_meta: bool,
|
||||
) -> Option<Vec<u8>> {
|
||||
zed_like_to_esc_str(keystroke, app_cursor_mode, option_as_meta)
|
||||
.map(|text| text.into_owned().into_bytes())
|
||||
}
|
||||
@@ -567,3 +667,38 @@ fn modifier_code(keystroke: &Keystroke) -> u32 {
|
||||
}
|
||||
modifier_code + 1
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum TransferType {
|
||||
Upload,
|
||||
Download,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum TransferState {
|
||||
Running,
|
||||
Paused,
|
||||
Completed,
|
||||
Failed(String),
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TransferInfo {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub source: String,
|
||||
pub target: String,
|
||||
pub kind: TransferType,
|
||||
pub total_bytes: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Transfer {
|
||||
pub tab_id: String,
|
||||
pub tab_title: String,
|
||||
pub info: TransferInfo,
|
||||
pub transferred: u64,
|
||||
pub total: Option<u64>,
|
||||
pub state: TransferState,
|
||||
}
|
||||
|
||||
+61
-32
@@ -3,9 +3,10 @@ use alacritty_terminal::{
|
||||
vte::ansi::{Color as AnsiColor, CursorShape, NamedColor},
|
||||
};
|
||||
use gpui::{
|
||||
App, Bounds, Element, ElementId, Entity, FocusHandle, Font, FontStyle, FontWeight, GlobalElementId, Hsla, InputHandler, IntoElement,
|
||||
LayoutId, Pixels, Point, Rgba, StrikethroughStyle, TextRun, TextStyle, UTF16Selection, UnderlineStyle, Window, fill, point, px,
|
||||
relative, rgb,
|
||||
App, Bounds, Element, ElementId, Entity, FocusHandle, Font, FontStyle, FontWeight,
|
||||
GlobalElementId, Hsla, InputHandler, IntoElement, LayoutId, Pixels, Point, Rgba, SharedString,
|
||||
StrikethroughStyle, TextRun, TextStyle, UTF16Selection, UnderlineStyle, Window, fill, point,
|
||||
px, relative, rgb,
|
||||
};
|
||||
use gpui_component::ActiveTheme as _;
|
||||
|
||||
@@ -81,7 +82,13 @@ impl BatchedTextRun {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&self, origin: Point<Pixels>, metrics: TerminalMetrics, window: &mut Window, cx: &mut App) {
|
||||
fn paint(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
metrics: TerminalMetrics,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let pos = point(
|
||||
origin.x + metrics.cell_width * self.col as f32,
|
||||
origin.y + metrics.line_height * self.row as f32,
|
||||
@@ -120,7 +127,7 @@ pub struct TerminalElement {
|
||||
focus_handle: FocusHandle,
|
||||
snapshot: RenderSnapshot,
|
||||
marked_text: Option<String>,
|
||||
font_family: &'static str,
|
||||
font_family: SharedString,
|
||||
font_size: Pixels,
|
||||
line_height: Pixels,
|
||||
cell_width: Pixels,
|
||||
@@ -146,10 +153,13 @@ impl InputHandler for TerminalInputHandler {
|
||||
_window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<UTF16Selection> {
|
||||
self.view.read(cx).terminal_accepts_text_input().then_some(UTF16Selection {
|
||||
range: 0..0,
|
||||
reversed: false,
|
||||
})
|
||||
self.view
|
||||
.read(cx)
|
||||
.terminal_accepts_text_input()
|
||||
.then_some(UTF16Selection {
|
||||
range: 0..0,
|
||||
reversed: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn marked_text_range(
|
||||
@@ -240,7 +250,7 @@ impl TerminalElement {
|
||||
focus_handle: FocusHandle,
|
||||
snapshot: RenderSnapshot,
|
||||
marked_text: Option<String>,
|
||||
font_family: &'static str,
|
||||
font_family: SharedString,
|
||||
font_size: Pixels,
|
||||
line_height: Pixels,
|
||||
cell_width: Pixels,
|
||||
@@ -260,7 +270,7 @@ impl TerminalElement {
|
||||
fn base_text_style(&self, cx: &App) -> TextStyle {
|
||||
TextStyle {
|
||||
color: cx.theme().foreground,
|
||||
font_family: self.font_family.into(),
|
||||
font_family: self.font_family.clone(),
|
||||
font_size: self.font_size.into(),
|
||||
line_height: self.line_height.into(),
|
||||
..Default::default()
|
||||
@@ -277,15 +287,21 @@ impl TerminalElement {
|
||||
fg.a *= 0.7;
|
||||
}
|
||||
|
||||
let underline = cell.flags.intersects(Flags::ALL_UNDERLINES).then(|| UnderlineStyle {
|
||||
color: Some(fg),
|
||||
thickness: px(1.0),
|
||||
wavy: cell.flags.contains(Flags::UNDERCURL),
|
||||
});
|
||||
let strikethrough = cell.flags.contains(Flags::STRIKEOUT).then(|| StrikethroughStyle {
|
||||
color: Some(fg),
|
||||
thickness: px(1.0),
|
||||
});
|
||||
let underline = cell
|
||||
.flags
|
||||
.intersects(Flags::ALL_UNDERLINES)
|
||||
.then(|| UnderlineStyle {
|
||||
color: Some(fg),
|
||||
thickness: px(1.0),
|
||||
wavy: cell.flags.contains(Flags::UNDERCURL),
|
||||
});
|
||||
let strikethrough = cell
|
||||
.flags
|
||||
.contains(Flags::STRIKEOUT)
|
||||
.then(|| StrikethroughStyle {
|
||||
color: Some(fg),
|
||||
thickness: px(1.0),
|
||||
});
|
||||
|
||||
let weight = if cell.flags.intersects(Flags::BOLD | Flags::DIM_BOLD) {
|
||||
FontWeight::BOLD
|
||||
@@ -303,7 +319,7 @@ impl TerminalElement {
|
||||
color: fg,
|
||||
background_color: None,
|
||||
font: Font {
|
||||
family: self.font_family.into(),
|
||||
family: self.font_family.clone(),
|
||||
weight,
|
||||
style,
|
||||
..Font::default()
|
||||
@@ -326,10 +342,9 @@ impl TerminalElement {
|
||||
continue;
|
||||
}
|
||||
|
||||
let selected = self
|
||||
.snapshot
|
||||
.selection
|
||||
.is_some_and(|selection| selection_contains(selection, render_cell.row, render_cell.col));
|
||||
let selected = self.snapshot.selection.is_some_and(|selection| {
|
||||
selection_contains(selection, render_cell.row, render_cell.col)
|
||||
});
|
||||
let bg = color_to_hsla(cell.bg, false, cx);
|
||||
if selected || !is_default_bg(cell.bg) {
|
||||
rects.push(LayoutRect {
|
||||
@@ -499,7 +514,7 @@ impl Element for TerminalElement {
|
||||
&[TextRun {
|
||||
len: marked_text.len(),
|
||||
font: Font {
|
||||
family: self.font_family.into(),
|
||||
family: self.font_family.clone(),
|
||||
..Font::default()
|
||||
},
|
||||
color: base_style.color,
|
||||
@@ -525,7 +540,11 @@ impl Element for TerminalElement {
|
||||
}
|
||||
|
||||
if let Some(cursor) = prepaint.cursor {
|
||||
if self.marked_text.as_ref().is_some_and(|text| !text.is_empty()) {
|
||||
if self
|
||||
.marked_text
|
||||
.as_ref()
|
||||
.is_some_and(|text| !text.is_empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
let x = prepaint.bounds.origin.x + prepaint.metrics.cell_width * cursor.col as f32;
|
||||
@@ -534,7 +553,10 @@ impl Element for TerminalElement {
|
||||
CursorShape::Hidden => {}
|
||||
CursorShape::Beam => {
|
||||
window.paint_quad(fill(
|
||||
Bounds::new(point(x, y), gpui::size(px(2.), prepaint.metrics.line_height)),
|
||||
Bounds::new(
|
||||
point(x, y),
|
||||
gpui::size(px(2.), prepaint.metrics.line_height),
|
||||
),
|
||||
cursor.color,
|
||||
));
|
||||
}
|
||||
@@ -572,7 +594,10 @@ fn merge_rects(mut rects: Vec<LayoutRect>) -> Vec<LayoutRect> {
|
||||
|
||||
for rect in rects {
|
||||
if let Some(last) = merged.last_mut() {
|
||||
if last.row == rect.row && last.color == rect.color && last.col + last.cells as i32 == rect.col {
|
||||
if last.row == rect.row
|
||||
&& last.color == rect.color
|
||||
&& last.col + last.cells as i32 == rect.col
|
||||
{
|
||||
last.cells += rect.cells;
|
||||
continue;
|
||||
}
|
||||
@@ -601,7 +626,11 @@ fn selection_contains(selection: ViewportSelection, row: i32, col: i32) -> bool
|
||||
}
|
||||
|
||||
fn is_blank(cell: &alacritty_terminal::term::cell::Cell) -> bool {
|
||||
cell.c == ' ' && cell.zerowidth().is_none() && !cell.flags.intersects(Flags::ALL_UNDERLINES | Flags::STRIKEOUT)
|
||||
cell.c == ' '
|
||||
&& cell.zerowidth().is_none()
|
||||
&& !cell
|
||||
.flags
|
||||
.intersects(Flags::ALL_UNDERLINES | Flags::STRIKEOUT)
|
||||
}
|
||||
|
||||
fn is_default_bg(color: AnsiColor) -> bool {
|
||||
@@ -623,8 +652,8 @@ fn color_to_hsla(color: AnsiColor, foreground: bool, cx: &App) -> Hsla {
|
||||
|
||||
fn ansi_index_color(index: u8, _cx: &App) -> Hsla {
|
||||
const ANSI_16: [u32; 16] = [
|
||||
0x1f2430, 0xff5c57, 0x5af78e, 0xf3f99d, 0x57c7ff, 0xff6ac1, 0x9aedfe, 0xf1f1f0,
|
||||
0x686868, 0xff5c57, 0x5af78e, 0xf3f99d, 0x57c7ff, 0xff6ac1, 0x9aedfe, 0xffffff,
|
||||
0x1f2430, 0xff5c57, 0x5af78e, 0xf3f99d, 0x57c7ff, 0xff6ac1, 0x9aedfe, 0xf1f1f0, 0x686868,
|
||||
0xff5c57, 0x5af78e, 0xf3f99d, 0x57c7ff, 0xff6ac1, 0x9aedfe, 0xffffff,
|
||||
];
|
||||
|
||||
if (index as usize) < ANSI_16.len() {
|
||||
|
||||
Reference in New Issue
Block a user