mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-10-05 16:00:26 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2276eef191 | ||
|
|
bb39626173 | ||
|
|
2cb8ab50aa | ||
|
|
785670c428 | ||
|
|
d33babe552 | ||
|
|
985f88fb1a |
@@ -34,7 +34,7 @@ jobs:
|
|||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
name: macos-aarch64
|
name: macos-aarch64
|
||||||
bin: ashell
|
bin: ashell
|
||||||
- os: macos-13 # Intel
|
- os: macos-14 # Intel (cross-compiled from Apple Silicon to avoid queue delays)
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
name: macos-x86_64
|
name: macos-x86_64
|
||||||
bin: ashell
|
bin: ashell
|
||||||
|
|||||||
Generated
+75
-19
@@ -1,6 +1,6 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "accesskit"
|
name = "accesskit"
|
||||||
@@ -302,9 +302,11 @@ dependencies = [
|
|||||||
"russh",
|
"russh",
|
||||||
"russh-keys",
|
"russh-keys",
|
||||||
"russh-sftp",
|
"russh-sftp",
|
||||||
|
"rust-i18n 3.1.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"ssh-key",
|
"ssh-key",
|
||||||
|
"sys-locale",
|
||||||
"sysinfo 0.33.1",
|
"sysinfo 0.33.1",
|
||||||
"tar",
|
"tar",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
@@ -753,7 +755,7 @@ dependencies = [
|
|||||||
"bitflags 2.12.1",
|
"bitflags 2.12.1",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools 0.11.0",
|
"itertools 0.13.0",
|
||||||
"log",
|
"log",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -1682,7 +1684,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users 0.5.2",
|
"redox_users 0.5.2",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1942,7 +1944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2563,7 +2565,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"presser",
|
"presser",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"windows 0.58.0",
|
"windows 0.62.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2697,7 +2699,7 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
"regex",
|
"regex",
|
||||||
"ropey",
|
"ropey",
|
||||||
"rust-i18n",
|
"rust-i18n 4.1.0",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -3258,7 +3260,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core 0.58.0",
|
"windows-core 0.62.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4122,7 +4124,7 @@ version = "0.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08"
|
checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4308,7 +4310,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5984,6 +5986,20 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-i18n"
|
||||||
|
version = "3.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fda2551fdfaf6cc5ee283adc15e157047b92ae6535cf80f6d4962d05717dc332"
|
||||||
|
dependencies = [
|
||||||
|
"globwalk",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"rust-i18n-macro 3.1.5",
|
||||||
|
"rust-i18n-support 3.1.5",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-i18n"
|
name = "rust-i18n"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
@@ -5992,11 +6008,28 @@ checksum = "55691a65892c33ee2de49c15ea5600c6f4a70e8eeb8e6c3cd96d2a231d230c40"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"globwalk",
|
"globwalk",
|
||||||
"regex",
|
"regex",
|
||||||
"rust-i18n-macro",
|
"rust-i18n-macro 4.1.0",
|
||||||
"rust-i18n-support",
|
"rust-i18n-support 4.1.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-i18n-macro"
|
||||||
|
version = "3.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22baf7d7f56656d23ebe24f6bb57a5d40d2bce2a5f1c503e692b5b2fa450f965"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rust-i18n-support 3.1.5",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-i18n-macro"
|
name = "rust-i18n-macro"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
@@ -6006,13 +6039,36 @@ dependencies = [
|
|||||||
"glob",
|
"glob",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rust-i18n-support",
|
"rust-i18n-support 4.1.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-i18n-support"
|
||||||
|
version = "3.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940ed4f52bba4c0152056d771e563b7133ad9607d4384af016a134b58d758f19"
|
||||||
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
|
"base62",
|
||||||
|
"globwalk",
|
||||||
|
"itertools 0.11.0",
|
||||||
|
"lazy_static",
|
||||||
|
"normpath",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
"siphasher",
|
||||||
|
"toml 0.8.23",
|
||||||
|
"triomphe",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-i18n-support"
|
name = "rust-i18n-support"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
@@ -6069,7 +6125,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.4.15",
|
"linux-raw-sys 0.4.15",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6082,7 +6138,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.12.1",
|
"linux-raw-sys 0.12.1",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6737,7 +6793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
|
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6845,7 +6901,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"psm",
|
"psm",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7205,7 +7261,7 @@ dependencies = [
|
|||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7682,7 +7738,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"memoffset 0.9.1",
|
"memoffset 0.9.1",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8442,7 +8498,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ rfd = "0.15"
|
|||||||
russh = "0.49"
|
russh = "0.49"
|
||||||
russh-keys = "0.49"
|
russh-keys = "0.49"
|
||||||
russh-sftp = "2"
|
russh-sftp = "2"
|
||||||
|
rust-i18n = "3"
|
||||||
|
sys-locale = "0.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
ssh-key = "0.6"
|
ssh-key = "0.6"
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ This project focuses on providing a high-performance and visually appealing shel
|
|||||||
|
|
||||||
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 and Windows from the [GitHub Releases page](https://github.com/rust-kotlin/ashell/releases/latest).
|
||||||
|
|
||||||
|
## Mac Installation Guide
|
||||||
|
|
||||||
|
After downloading, please unzip the file and move `ashell.app` to your **Applications** folder.
|
||||||
|
Since the app uses ad-hoc signing, macOS may warn that the app is "damaged" upon first launch. If this happens, open Terminal and run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo xattr -cr /Applications/ashell.app
|
||||||
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
The current version provides a fully-featured GPUI-native workspace:
|
The current version provides a fully-featured GPUI-native workspace:
|
||||||
|
|||||||
@@ -12,6 +12,15 @@
|
|||||||
|
|
||||||
您可以从 [GitHub Releases 页面](https://github.com/rust-kotlin/ashell/releases/latest) 下载 macOS 和 Windows 版本的最新预编译程序。
|
您可以从 [GitHub Releases 页面](https://github.com/rust-kotlin/ashell/releases/latest) 下载 macOS 和 Windows 版本的最新预编译程序。
|
||||||
|
|
||||||
|
## Mac 安装指南
|
||||||
|
|
||||||
|
下载并解压后,请先将 `ashell.app` 拖入或移动到 **应用程序 (Applications)** 目录。
|
||||||
|
由于应用采用本地签名,初次启动时如果系统提示“App 已损坏,无法打开”,请打开终端(Terminal)并执行以下命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo xattr -cr /Applications/ashell.app
|
||||||
|
```
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
当前版本提供了一个功能完备的 GPUI 原生工作区:
|
当前版本提供了一个功能完备的 GPUI 原生工作区:
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
remote_files: "REMOTE FILES"
|
||||||
|
open_ssh_tab_sftp: "Open an SSH tab to browse SFTP files."
|
||||||
|
sftp_connecting: "sftp connecting..."
|
||||||
|
refresh: "Refresh"
|
||||||
|
upload_file: "Upload File"
|
||||||
|
upload_folder: "Upload Folder"
|
||||||
|
hidden: "Hidden"
|
||||||
|
download: "Download"
|
||||||
|
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"
|
||||||
|
local_terminal: "Local Terminal"
|
||||||
|
open_local_shell_tab: "Open a new local shell tab"
|
||||||
|
connect: "Connect"
|
||||||
|
edit: "Edit"
|
||||||
|
delete: "Delete"
|
||||||
|
host: "Host"
|
||||||
|
port: "Port"
|
||||||
|
user: "User"
|
||||||
|
password: "Password"
|
||||||
|
key: "Key"
|
||||||
|
private_key_path: "Private Key Path"
|
||||||
|
private_key_data: "Private Key Data"
|
||||||
|
save_and_connect: "Save & Connect"
|
||||||
|
save: "Save"
|
||||||
|
cancel: "Cancel"
|
||||||
|
host_and_user_required: "Host and user are required"
|
||||||
|
language: "Language"
|
||||||
|
follow_system: "Follow System"
|
||||||
|
light_theme: "Light Theme"
|
||||||
|
dark_theme: "Dark Theme"
|
||||||
|
use_light_mode: "Use Light Mode"
|
||||||
|
use_dark_mode: "Use Dark Mode"
|
||||||
|
english: "English"
|
||||||
|
chinese: "简体中文"
|
||||||
|
idle_no_session: "idle / No session"
|
||||||
|
system: "SYSTEM"
|
||||||
|
cpu: "CPU"
|
||||||
|
mem: "MEM"
|
||||||
|
swap: "SWAP"
|
||||||
|
net: "NET"
|
||||||
|
disk: "DISK"
|
||||||
|
global_load: "global load"
|
||||||
|
live: "Live"
|
||||||
|
idle: "idle"
|
||||||
|
saved: "SAVED"
|
||||||
|
add_ssh: "+ ssh"
|
||||||
|
open_local_or_ssh: "Open a local terminal or pick an SSH session."
|
||||||
|
open_session: "Open Session"
|
||||||
|
settings: "Settings"
|
||||||
|
terminal_font_size: "Terminal Font Size"
|
||||||
|
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"
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
remote_files: "远程文件"
|
||||||
|
open_ssh_tab_sftp: "打开一个 SSH 会话面板以浏览 SFTP 文件。"
|
||||||
|
sftp_connecting: "正在连接 SFTP..."
|
||||||
|
refresh: "刷新"
|
||||||
|
upload_file: "上传文件"
|
||||||
|
upload_folder: "上传文件夹"
|
||||||
|
hidden: "显示隐藏文件"
|
||||||
|
download: "下载"
|
||||||
|
download_count: "下载 (%{count})"
|
||||||
|
name: "名称"
|
||||||
|
size: "大小"
|
||||||
|
modified: "修改时间"
|
||||||
|
new_ssh_connection: "新建 SSH 连接"
|
||||||
|
create_or_edit_ssh_session: "创建或编辑一个 SSH 会话"
|
||||||
|
local_terminal: "本地终端"
|
||||||
|
open_local_shell_tab: "打开一个新的本地终端页面"
|
||||||
|
connect: "连接"
|
||||||
|
edit: "编辑"
|
||||||
|
delete: "删除"
|
||||||
|
host: "主机 (Host)"
|
||||||
|
port: "端口 (Port)"
|
||||||
|
user: "用户 (User)"
|
||||||
|
password: "密码 (Password)"
|
||||||
|
key: "密钥 (Key)"
|
||||||
|
private_key_path: "私钥路径"
|
||||||
|
private_key_data: "私钥内容"
|
||||||
|
save_and_connect: "保存并连接"
|
||||||
|
save: "保存"
|
||||||
|
cancel: "取消"
|
||||||
|
host_and_user_required: "主机名和用户名不能为空"
|
||||||
|
language: "显示语言"
|
||||||
|
follow_system: "跟随系统"
|
||||||
|
light_theme: "浅色主题"
|
||||||
|
dark_theme: "深色主题"
|
||||||
|
use_light_mode: "使用浅色模式"
|
||||||
|
use_dark_mode: "使用深色模式"
|
||||||
|
english: "English"
|
||||||
|
chinese: "简体中文"
|
||||||
|
idle_no_session: "空闲 / 未连接会话"
|
||||||
|
system: "系统"
|
||||||
|
cpu: "CPU"
|
||||||
|
mem: "内存"
|
||||||
|
swap: "虚拟内存"
|
||||||
|
net: "网络"
|
||||||
|
disk: "磁盘"
|
||||||
|
global_load: "全局负载"
|
||||||
|
live: "实时"
|
||||||
|
idle: "空闲"
|
||||||
|
saved: "已保存会话"
|
||||||
|
add_ssh: "+ SSH"
|
||||||
|
open_local_or_ssh: "打开本地终端或选择一个 SSH 会话。"
|
||||||
|
open_session: "打开会话"
|
||||||
|
settings: "设置"
|
||||||
|
terminal_font_size: "终端字体大小"
|
||||||
|
theme_management_hint: "主题模式和亮/暗主题系列可通过顶栏的主题按钮管理。"
|
||||||
|
local_host: "本地主机"
|
||||||
|
local_terminal_opened: "已打开本地终端"
|
||||||
|
connecting: "正在连接"
|
||||||
|
starting_connection: "开始连接..."
|
||||||
|
connection_failed: "连接失败"
|
||||||
@@ -78,12 +78,18 @@ pub struct ConfigFile {
|
|||||||
pub light_theme_name: String,
|
pub light_theme_name: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub dark_theme_name: String,
|
pub dark_theme_name: String,
|
||||||
|
#[serde(default = "default_locale")]
|
||||||
|
pub locale: String,
|
||||||
#[serde(default = "default_terminal_font_size")]
|
#[serde(default = "default_terminal_font_size")]
|
||||||
pub terminal_font_size: f32,
|
pub terminal_font_size: f32,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub sessions: Vec<Session>,
|
pub sessions: Vec<Session>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_locale() -> String {
|
||||||
|
"system".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn default_terminal_font_size() -> f32 {
|
fn default_terminal_font_size() -> f32 {
|
||||||
13.0
|
13.0
|
||||||
}
|
}
|
||||||
@@ -144,6 +150,18 @@ impl ConfigStore {
|
|||||||
&self.cache.dark_theme_name
|
&self.cache.dark_theme_name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn locale(&self) -> &str {
|
||||||
|
if self.cache.locale.is_empty() {
|
||||||
|
"system"
|
||||||
|
} else {
|
||||||
|
&self.cache.locale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_locale(&mut self, locale: &str) {
|
||||||
|
self.cache.locale = locale.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn terminal_font_size(&self) -> f32 {
|
pub fn terminal_font_size(&self) -> f32 {
|
||||||
if self.cache.terminal_font_size <= 0.0 {
|
if self.cache.terminal_font_size <= 0.0 {
|
||||||
default_terminal_font_size()
|
default_terminal_font_size()
|
||||||
|
|||||||
+368
-63
@@ -1,7 +1,6 @@
|
|||||||
#![windows_subsystem = "windows"]
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
@@ -15,7 +14,7 @@ use alacritty_terminal::index::Side;
|
|||||||
use alacritty_terminal::selection::SelectionType;
|
use alacritty_terminal::selection::SelectionType;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Anchor, App, AppContext as _, Bounds, ClipboardItem, Context, Entity, FocusHandle,
|
Anchor, App, AppContext as _, Bounds, ClipboardItem, Context, ElementId, Entity, FocusHandle,
|
||||||
Focusable as _, FontWeight, Hsla, InteractiveElement as _, IntoElement, KeyBinding,
|
Focusable as _, FontWeight, Hsla, InteractiveElement as _, IntoElement, KeyBinding,
|
||||||
KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _,
|
KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _,
|
||||||
PathPromptOptions, Pixels, Point, QuitMode, Render, ScrollDelta, ScrollWheelEvent,
|
PathPromptOptions, Pixels, Point, QuitMode, Render, ScrollDelta, ScrollWheelEvent,
|
||||||
@@ -23,8 +22,8 @@ use gpui::{
|
|||||||
prelude::FluentBuilder as _, px, size, uniform_list,
|
prelude::FluentBuilder as _, px, size, uniform_list,
|
||||||
};
|
};
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
ActiveTheme as _, ElementExt, IconName, Root, Sizable as _, Theme, ThemeMode, ThemeRegistry,
|
ActiveTheme as _, Disableable, ElementExt, IconName, Root, Sizable as _, Theme, ThemeMode,
|
||||||
WindowExt as _,
|
ThemeRegistry, WindowExt as _,
|
||||||
button::{Button, ButtonVariants as _},
|
button::{Button, ButtonVariants as _},
|
||||||
checkbox::Checkbox,
|
checkbox::Checkbox,
|
||||||
dialog::Dialog,
|
dialog::Dialog,
|
||||||
@@ -55,6 +54,8 @@ use system::{DiskSample, SystemSampler, SystemSnapshot, format_bytes};
|
|||||||
use terminal::{BackendCommand, BackendEvent, TabKind, TerminalTab, encode_key};
|
use terminal::{BackendCommand, BackendEvent, TabKind, TerminalTab, encode_key};
|
||||||
use terminal_element::TerminalElement;
|
use terminal_element::TerminalElement;
|
||||||
|
|
||||||
|
rust_i18n::i18n!("locales", fallback = "en");
|
||||||
|
|
||||||
const DEFAULT_COLS: u16 = 100;
|
const DEFAULT_COLS: u16 = 100;
|
||||||
const DEFAULT_ROWS: u16 = 30;
|
const DEFAULT_ROWS: u16 = 30;
|
||||||
const APP_FONT_FAMILY: &str = "Maple Mono NF CN";
|
const APP_FONT_FAMILY: &str = "Maple Mono NF CN";
|
||||||
@@ -201,14 +202,15 @@ struct SftpContextMenuState {
|
|||||||
|
|
||||||
impl Ashell {
|
impl Ashell {
|
||||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let host_input = cx.new(|cx| InputState::new(window, cx).placeholder("host"));
|
let host_input =
|
||||||
|
cx.new(|cx| InputState::new(window, cx).placeholder(rust_i18n::t!("host")));
|
||||||
let session_name_input =
|
let session_name_input =
|
||||||
cx.new(|cx| InputState::new(window, cx).placeholder("name (optional)"));
|
cx.new(|cx| InputState::new(window, cx).placeholder("name (optional)"));
|
||||||
let port_input = cx.new(|cx| InputState::new(window, cx).default_value("22"));
|
let port_input = cx.new(|cx| InputState::new(window, cx).default_value("22"));
|
||||||
let user_input = cx.new(|cx| InputState::new(window, cx).default_value("root"));
|
let user_input = cx.new(|cx| InputState::new(window, cx).default_value("root"));
|
||||||
let password_input = cx.new(|cx| {
|
let password_input = cx.new(|cx| {
|
||||||
InputState::new(window, cx)
|
InputState::new(window, cx)
|
||||||
.placeholder("password")
|
.placeholder(rust_i18n::t!("password"))
|
||||||
.masked(true)
|
.masked(true)
|
||||||
});
|
});
|
||||||
let key_path_input =
|
let key_path_input =
|
||||||
@@ -259,6 +261,19 @@ impl Ashell {
|
|||||||
} else {
|
} else {
|
||||||
config.dark_theme_name().into()
|
config.dark_theme_name().into()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let configured_locale = config.locale();
|
||||||
|
let mut active_locale = configured_locale.to_string();
|
||||||
|
if active_locale == "system" {
|
||||||
|
active_locale = sys_locale::get_locale().unwrap_or_else(|| "en".to_string());
|
||||||
|
if active_locale.starts_with("zh") {
|
||||||
|
active_locale = "zh-CN".to_string();
|
||||||
|
} else {
|
||||||
|
active_locale = "en".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rust_i18n::set_locale(&active_locale);
|
||||||
|
gpui_component::set_locale(&active_locale);
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
selector_focus_handle: cx.focus_handle(),
|
selector_focus_handle: cx.focus_handle(),
|
||||||
@@ -454,12 +469,32 @@ impl Ashell {
|
|||||||
progress.lines.push(reason.clone().into());
|
progress.lines.push(reason.clone().into());
|
||||||
let _ = session_label;
|
let _ = session_label;
|
||||||
let _ = tab_title;
|
let _ = tab_title;
|
||||||
progress.title = "Connection Failed".into();
|
progress.title = rust_i18n::t!("connection_failed").into();
|
||||||
progress.failed = true;
|
progress.failed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.status = reason.into();
|
self.status = reason.into();
|
||||||
}
|
}
|
||||||
|
BackendEvent::TerminalTitleChanged { tab_id, title } => {
|
||||||
|
if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == tab_id) {
|
||||||
|
if let Some(sftp) = &tab.sftp {
|
||||||
|
let potential_path = if let Some((_, p)) = title.rsplit_once(':') {
|
||||||
|
p.trim()
|
||||||
|
} else {
|
||||||
|
title.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
if potential_path.starts_with('/') || potential_path.starts_with('~') {
|
||||||
|
let path_to_sync = potential_path.to_string();
|
||||||
|
if path_to_sync != sftp.current_path {
|
||||||
|
if let Some(handle) = self.sftp_handles.get(&tab_id) {
|
||||||
|
handle.list_dir(path_to_sync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,7 +550,8 @@ impl Ashell {
|
|||||||
) {
|
) {
|
||||||
Ok(backend) => {
|
Ok(backend) => {
|
||||||
let title = if cfg!(windows) { "PowerShell" } else { "Local" }.to_string();
|
let title = if cfg!(windows) { "PowerShell" } else { "Local" }.to_string();
|
||||||
let mut tab = TerminalTab::new_local(id.clone(), title, backend);
|
let mut tab =
|
||||||
|
TerminalTab::new_local(id.clone(), title, backend, self.events_tx.clone());
|
||||||
tab.resize(DEFAULT_COLS, DEFAULT_ROWS);
|
tab.resize(DEFAULT_COLS, DEFAULT_ROWS);
|
||||||
self.tabs.push(tab);
|
self.tabs.push(tab);
|
||||||
self.active_tab = Some(id);
|
self.active_tab = Some(id);
|
||||||
@@ -544,7 +580,7 @@ impl Ashell {
|
|||||||
let key_inline = self.key_inline_input.read(cx).value().to_string();
|
let key_inline = self.key_inline_input.read(cx).value().to_string();
|
||||||
|
|
||||||
if host.is_empty() || user.is_empty() {
|
if host.is_empty() || user.is_empty() {
|
||||||
self.status = "host and user are required".into();
|
self.status = rust_i18n::t!("host_and_user_required").into();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -685,7 +721,7 @@ impl Ashell {
|
|||||||
|
|
||||||
window.open_dialog(cx, move |dialog: Dialog, _window, _cx| {
|
window.open_dialog(cx, move |dialog: Dialog, _window, _cx| {
|
||||||
dialog
|
dialog
|
||||||
.title("SSH Session")
|
.title(rust_i18n::t!("new_ssh_connection"))
|
||||||
.w(px(520.))
|
.w(px(520.))
|
||||||
.overlay_closable(true)
|
.overlay_closable(true)
|
||||||
.content({
|
.content({
|
||||||
@@ -716,7 +752,7 @@ impl Ashell {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
Button::new("ssh-auth-password")
|
Button::new("ssh-auth-password")
|
||||||
.label("password")
|
.label(rust_i18n::t!("password").to_string())
|
||||||
.when(is_password, |button| button.primary())
|
.when(is_password, |button| button.primary())
|
||||||
.on_click(window.listener_for(
|
.on_click(window.listener_for(
|
||||||
&view,
|
&view,
|
||||||
@@ -730,7 +766,7 @@ impl Ashell {
|
|||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("ssh-auth-key")
|
Button::new("ssh-auth-key")
|
||||||
.label("key")
|
.label(rust_i18n::t!("key").to_string())
|
||||||
.when(!is_password, |button| button.primary())
|
.when(!is_password, |button| button.primary())
|
||||||
.on_click(window.listener_for(
|
.on_click(window.listener_for(
|
||||||
&view,
|
&view,
|
||||||
@@ -759,13 +795,17 @@ impl Ashell {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
Button::new("connect-ssh-cancel")
|
Button::new("connect-ssh-cancel")
|
||||||
.label("cancel")
|
.label(rust_i18n::t!("cancel").to_string())
|
||||||
.on_click(|_, window, cx| window.close_dialog(cx)),
|
.on_click(|_, window, cx| window.close_dialog(cx)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("connect-ssh-confirm")
|
Button::new("connect-ssh-confirm")
|
||||||
.primary()
|
.primary()
|
||||||
.label(if is_editing { "save" } else { "connect" })
|
.label(if is_editing {
|
||||||
|
rust_i18n::t!("save")
|
||||||
|
} else {
|
||||||
|
rust_i18n::t!("connect")
|
||||||
|
})
|
||||||
.on_click(window.listener_for(
|
.on_click(window.listener_for(
|
||||||
&view,
|
&view,
|
||||||
|this, _, window, cx| {
|
|this, _, window, cx| {
|
||||||
@@ -852,13 +892,15 @@ impl Ashell {
|
|||||||
div()
|
div()
|
||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.font_weight(FontWeight::SEMIBOLD)
|
.font_weight(FontWeight::SEMIBOLD)
|
||||||
.child("Local Terminal"),
|
.child(rust_i18n::t!("local_terminal")),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_size(px(11.))
|
.text_size(px(11.))
|
||||||
.text_color(_cx.theme().muted_foreground)
|
.text_color(_cx.theme().muted_foreground)
|
||||||
.child("Open a new local shell tab"),
|
.child(rust_i18n::t!(
|
||||||
|
"open_local_shell_tab"
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -894,13 +936,15 @@ impl Ashell {
|
|||||||
div()
|
div()
|
||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.font_weight(FontWeight::SEMIBOLD)
|
.font_weight(FontWeight::SEMIBOLD)
|
||||||
.child("New SSH Connection"),
|
.child(rust_i18n::t!("new_ssh_connection")),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_size(px(11.))
|
.text_size(px(11.))
|
||||||
.text_color(_cx.theme().muted_foreground)
|
.text_color(_cx.theme().muted_foreground)
|
||||||
.child("Create or edit an SSH session"),
|
.child(rust_i18n::t!(
|
||||||
|
"create_or_edit_ssh_session"
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -986,7 +1030,7 @@ impl Ashell {
|
|||||||
let view = cx.entity();
|
let view = cx.entity();
|
||||||
window.open_dialog(cx, move |dialog: Dialog, _window, _| {
|
window.open_dialog(cx, move |dialog: Dialog, _window, _| {
|
||||||
dialog
|
dialog
|
||||||
.title("Settings")
|
.title(rust_i18n::t!("settings").to_string())
|
||||||
.w(px(480.))
|
.w(px(480.))
|
||||||
.content({
|
.content({
|
||||||
let view = view.clone();
|
let view = view.clone();
|
||||||
@@ -998,7 +1042,7 @@ impl Ashell {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(div().w(px(180.)).child("Terminal Font Size"))
|
.child(div().w(px(180.)).child(rust_i18n::t!("terminal_font_size").to_string()))
|
||||||
.child(
|
.child(
|
||||||
Button::new("font-size-down")
|
Button::new("font-size-down")
|
||||||
.label("-")
|
.label("-")
|
||||||
@@ -1020,11 +1064,63 @@ impl Ashell {
|
|||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_3()
|
||||||
|
.child(div().w(px(180.)).child(rust_i18n::t!("language").to_string()))
|
||||||
|
.child(
|
||||||
|
Button::new("language-dropdown")
|
||||||
|
.small()
|
||||||
|
.icon(IconName::Globe)
|
||||||
|
.label({
|
||||||
|
let current_locale = view.read(cx).config.locale().to_string();
|
||||||
|
if current_locale == "en" {
|
||||||
|
rust_i18n::t!("english").to_string()
|
||||||
|
} else if current_locale == "zh-CN" {
|
||||||
|
rust_i18n::t!("chinese").to_string()
|
||||||
|
} else {
|
||||||
|
rust_i18n::t!("follow_system").to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||||
|
let view = view.clone();
|
||||||
|
move |mut menu, window, cx| {
|
||||||
|
let current_locale = view.read(cx).config.locale().to_string();
|
||||||
|
menu = menu
|
||||||
|
.min_w(160.)
|
||||||
|
.item(
|
||||||
|
PopupMenuItem::new(rust_i18n::t!("follow_system").to_string())
|
||||||
|
.checked(current_locale == "system")
|
||||||
|
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||||
|
this.set_display_language("system", window, cx)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.separator()
|
||||||
|
.item(
|
||||||
|
PopupMenuItem::new(rust_i18n::t!("english").to_string())
|
||||||
|
.checked(current_locale == "en")
|
||||||
|
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||||
|
this.set_display_language("en", window, cx)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.item(
|
||||||
|
PopupMenuItem::new(rust_i18n::t!("chinese").to_string())
|
||||||
|
.checked(current_locale == "zh-CN")
|
||||||
|
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||||
|
this.set_display_language("zh-CN", window, cx)
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
menu
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child("Theme mode and light/dark theme families are managed from the top-bar theme button."),
|
.child(rust_i18n::t!("theme_management_hint")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1137,8 +1233,12 @@ impl Ashell {
|
|||||||
DEFAULT_ROWS,
|
DEFAULT_ROWS,
|
||||||
self.events_tx.clone(),
|
self.events_tx.clone(),
|
||||||
);
|
);
|
||||||
self.tabs
|
self.tabs.push(TerminalTab::new_ssh(
|
||||||
.push(TerminalTab::new_ssh(id.clone(), &session, backend));
|
id.clone(),
|
||||||
|
&session,
|
||||||
|
backend,
|
||||||
|
self.events_tx.clone(),
|
||||||
|
));
|
||||||
let sftp_handle = sftp::spawn_sftp(
|
let sftp_handle = sftp::spawn_sftp(
|
||||||
self.runtime.handle(),
|
self.runtime.handle(),
|
||||||
id.clone(),
|
id.clone(),
|
||||||
@@ -1150,8 +1250,8 @@ impl Ashell {
|
|||||||
self.pending_sftp_path_sync = Some("/".into());
|
self.pending_sftp_path_sync = Some("/".into());
|
||||||
self.connection_progress = Some(ConnectionProgress {
|
self.connection_progress = Some(ConnectionProgress {
|
||||||
tab_id: id,
|
tab_id: id,
|
||||||
title: "Connecting".into(),
|
title: rust_i18n::t!("connecting").into(),
|
||||||
lines: vec!["starting connection...".into()],
|
lines: vec![rust_i18n::t!("starting_connection").into()],
|
||||||
failed: false,
|
failed: false,
|
||||||
});
|
});
|
||||||
self.status = "ssh tab opened".into();
|
self.status = "ssh tab opened".into();
|
||||||
@@ -1504,6 +1604,26 @@ impl Ashell {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_display_language(&mut self, locale: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.config.set_locale(locale);
|
||||||
|
let mut active_locale = locale.to_string();
|
||||||
|
if active_locale == "system" {
|
||||||
|
active_locale = sys_locale::get_locale().unwrap_or_else(|| "en".to_string());
|
||||||
|
if active_locale.starts_with("zh") {
|
||||||
|
active_locale = "zh-CN".to_string();
|
||||||
|
} else {
|
||||||
|
active_locale = "en".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rust_i18n::set_locale(&active_locale);
|
||||||
|
gpui_component::set_locale(&active_locale);
|
||||||
|
if let Err(err) = self.config.save() {
|
||||||
|
tracing::warn!("failed to save language preferences: {err:#}");
|
||||||
|
}
|
||||||
|
window.refresh();
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_theme_preferences(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn apply_theme_preferences(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let light_theme = ThemeRegistry::global(cx)
|
let light_theme = ThemeRegistry::global(cx)
|
||||||
.themes()
|
.themes()
|
||||||
@@ -1889,7 +2009,7 @@ impl Ashell {
|
|||||||
div()
|
div()
|
||||||
.text_size(px(13.))
|
.text_size(px(13.))
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child("Open a local terminal or pick an SSH session."),
|
.child(rust_i18n::t!("open_local_or_ssh")),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
@@ -1897,13 +2017,13 @@ impl Ashell {
|
|||||||
.child(
|
.child(
|
||||||
Button::new("home-open-local")
|
Button::new("home-open-local")
|
||||||
.primary()
|
.primary()
|
||||||
.label("Local Terminal")
|
.label(rust_i18n::t!("local_terminal").to_string())
|
||||||
.on_click(cx.listener(|this, _, _, cx| this.open_local(cx))),
|
.on_click(cx.listener(|this, _, _, cx| this.open_local(cx))),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("home-open-session")
|
Button::new("home-open-session")
|
||||||
.ghost()
|
.ghost()
|
||||||
.label("Open Session")
|
.label(rust_i18n::t!("open_session").to_string())
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
this.show_selector_dialog(window, cx)
|
this.show_selector_dialog(window, cx)
|
||||||
})),
|
})),
|
||||||
@@ -1947,7 +2067,7 @@ impl Ashell {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|id| self.tabs.iter().find(|t| &t.id == id))
|
.and_then(|id| self.tabs.iter().find(|t| &t.id == id))
|
||||||
.map(|t| t.title.clone())
|
.map(|t| t.title.clone())
|
||||||
.unwrap_or_else(|| "No session".into())
|
.unwrap_or_else(|| rust_i18n::t!("idle_no_session").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_ssh_session(&self) -> Option<(String, Session)> {
|
fn active_ssh_session(&self) -> Option<(String, Session)> {
|
||||||
@@ -2008,7 +2128,7 @@ impl Ashell {
|
|||||||
|
|
||||||
fn system_card(
|
fn system_card(
|
||||||
&self,
|
&self,
|
||||||
label: &'static str,
|
label: String,
|
||||||
percent: f32,
|
percent: f32,
|
||||||
detail: String,
|
detail: String,
|
||||||
fill: Hsla,
|
fill: Hsla,
|
||||||
@@ -2031,7 +2151,7 @@ impl Ashell {
|
|||||||
.text_size(px(11.))
|
.text_size(px(11.))
|
||||||
.font_weight(FontWeight::SEMIBOLD)
|
.font_weight(FontWeight::SEMIBOLD)
|
||||||
.text_color(fill)
|
.text_color(fill)
|
||||||
.child(label),
|
.child(label.clone()),
|
||||||
)
|
)
|
||||||
.child(div().flex_1())
|
.child(div().flex_1())
|
||||||
.child(
|
.child(
|
||||||
@@ -2077,14 +2197,14 @@ impl Ashell {
|
|||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.font_weight(FontWeight::SEMIBOLD)
|
.font_weight(FontWeight::SEMIBOLD)
|
||||||
.text_color(cx.theme().chart_4)
|
.text_color(cx.theme().chart_4)
|
||||||
.child("NET"),
|
.child(rust_i18n::t!("net")),
|
||||||
)
|
)
|
||||||
.child(div().flex_1())
|
.child(div().flex_1())
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_size(px(11.))
|
.text_size(px(11.))
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child("live"),
|
.child(rust_i18n::t!("live")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -2175,6 +2295,89 @@ impl Ashell {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_sftp_entry(&mut self, path: String, checked: bool, cx: &mut Context<Self>) {
|
||||||
|
if let Some(sftp) = self.active_sftp_mut() {
|
||||||
|
if checked {
|
||||||
|
sftp.selected_entries.insert(path);
|
||||||
|
} else {
|
||||||
|
sftp.selected_entries.remove(&path);
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_all_sftp_entries(&mut self, checked: bool, cx: &mut Context<Self>) {
|
||||||
|
if let Some(sftp) = self.active_sftp_mut() {
|
||||||
|
if checked {
|
||||||
|
let paths: Vec<String> = sftp.entries.iter().map(|e| e.full_path.clone()).collect();
|
||||||
|
for path in paths {
|
||||||
|
sftp.selected_entries.insert(path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sftp.selected_entries.clear();
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_selected_sftp_entries(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let Some(sftp) = self.active_sftp() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let selected: Vec<String> = sftp.selected_entries.iter().cloned().collect();
|
||||||
|
if selected.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(handle) = self.active_sftp_handle().cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let path_prompt = cx.prompt_for_paths(PathPromptOptions {
|
||||||
|
files: false,
|
||||||
|
directories: true,
|
||||||
|
multiple: false,
|
||||||
|
prompt: Some("Select Download Folder".into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
if let Ok(Ok(Some(mut paths))) = path_prompt.await {
|
||||||
|
if let Some(folder) = paths.pop() {
|
||||||
|
let local_dir = folder.to_string_lossy().to_string();
|
||||||
|
for remote in selected {
|
||||||
|
let _ = handle.commands.send(crate::sftp::SftpCommand::Download {
|
||||||
|
remote,
|
||||||
|
local_dir: local_dir.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = this.update(cx, |this, cx| {
|
||||||
|
if let Some(sftp_mut) = this.active_sftp_mut() {
|
||||||
|
sftp_mut.selected_entries.clear();
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok::<(), anyhow::Error>(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_sftp_files_batch(&mut self, paths: Vec<String>, _cx: &mut Context<Self>) {
|
||||||
|
if paths.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(sftp) = self.active_sftp() {
|
||||||
|
if let Some(handle) = self.active_sftp_handle() {
|
||||||
|
let _ = handle.commands.send(crate::sftp::SftpCommand::UploadPaths {
|
||||||
|
locals: paths,
|
||||||
|
remote_dir: sftp.current_path.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn theme_dropdown(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn theme_dropdown(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let view = cx.entity();
|
let view = cx.entity();
|
||||||
let themes = ThemeRegistry::global(cx)
|
let themes = ThemeRegistry::global(cx)
|
||||||
@@ -2212,28 +2415,28 @@ impl Ashell {
|
|||||||
menu = menu
|
menu = menu
|
||||||
.min_w(220.)
|
.min_w(220.)
|
||||||
.item(
|
.item(
|
||||||
PopupMenuItem::new("Follow System")
|
PopupMenuItem::new(rust_i18n::t!("follow_system"))
|
||||||
.checked(follow_system)
|
.checked(follow_system)
|
||||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||||
this.set_follow_system_theme(true, window, cx)
|
this.set_follow_system_theme(true, window, cx)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.item(
|
.item(
|
||||||
PopupMenuItem::new("Use Light Mode")
|
PopupMenuItem::new(rust_i18n::t!("use_light_mode"))
|
||||||
.checked(!follow_system && !is_dark_mode)
|
.checked(!follow_system && !is_dark_mode)
|
||||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||||
this.switch_theme_mode(ThemeMode::Light, window, cx)
|
this.switch_theme_mode(ThemeMode::Light, window, cx)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.item(
|
.item(
|
||||||
PopupMenuItem::new("Use Dark Mode")
|
PopupMenuItem::new(rust_i18n::t!("use_dark_mode"))
|
||||||
.checked(!follow_system && is_dark_mode)
|
.checked(!follow_system && is_dark_mode)
|
||||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||||
this.switch_theme_mode(ThemeMode::Dark, window, cx)
|
this.switch_theme_mode(ThemeMode::Dark, window, cx)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.separator()
|
.separator()
|
||||||
.label("Light Theme");
|
.label(rust_i18n::t!("light_theme").to_string());
|
||||||
|
|
||||||
for theme_name in light_themes.clone() {
|
for theme_name in light_themes.clone() {
|
||||||
let checked = theme_name == light_theme_name;
|
let checked = theme_name == light_theme_name;
|
||||||
@@ -2247,7 +2450,7 @@ impl Ashell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
menu = menu.separator();
|
menu = menu.separator();
|
||||||
menu = menu.label("Dark Theme");
|
menu = menu.label(rust_i18n::t!("dark_theme").to_string());
|
||||||
|
|
||||||
for theme_name in dark_themes.clone() {
|
for theme_name in dark_themes.clone() {
|
||||||
let checked = theme_name == dark_theme_name;
|
let checked = theme_name == dark_theme_name;
|
||||||
@@ -2281,13 +2484,13 @@ impl Ashell {
|
|||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.font_weight(FontWeight::SEMIBOLD)
|
.font_weight(FontWeight::SEMIBOLD)
|
||||||
.text_color(cx.theme().primary)
|
.text_color(cx.theme().primary)
|
||||||
.child("REMOTE FILES"),
|
.child(rust_i18n::t!("remote_files")),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child("Open an SSH tab to browse SFTP files."),
|
.child(rust_i18n::t!("open_ssh_tab_sftp")),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2299,6 +2502,11 @@ impl Ashell {
|
|||||||
.filter(|entry| self.show_hidden_files || !entry.name.starts_with('.'))
|
.filter(|entry| self.show_hidden_files || !entry.name.starts_with('.'))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let status = sftp.status.clone();
|
let status = sftp.status.clone();
|
||||||
|
let selected_entries = sftp.selected_entries.clone();
|
||||||
|
let all_selected = !entries.is_empty()
|
||||||
|
&& entries
|
||||||
|
.iter()
|
||||||
|
.all(|e| selected_entries.contains(&e.full_path));
|
||||||
let parent_path = Self::sftp_parent_path(&sftp.current_path);
|
let parent_path = Self::sftp_parent_path(&sftp.current_path);
|
||||||
let view = cx.entity();
|
let view = cx.entity();
|
||||||
let icon_col_width = px(14.);
|
let icon_col_width = px(14.);
|
||||||
@@ -2310,6 +2518,16 @@ impl Ashell {
|
|||||||
.gap_0()
|
.gap_0()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.bg(cx.theme().background)
|
.bg(cx.theme().background)
|
||||||
|
.on_drop(
|
||||||
|
cx.listener(|this, paths: &gpui::ExternalPaths, window, cx| {
|
||||||
|
let paths_to_upload: Vec<String> = paths
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.collect();
|
||||||
|
this.upload_sftp_files_batch(paths_to_upload, cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.h(px(34.))
|
.h(px(34.))
|
||||||
@@ -2324,7 +2542,7 @@ impl Ashell {
|
|||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.font_weight(FontWeight::SEMIBOLD)
|
.font_weight(FontWeight::SEMIBOLD)
|
||||||
.text_color(cx.theme().primary)
|
.text_color(cx.theme().primary)
|
||||||
.child("REMOTE FILES"),
|
.child(rust_i18n::t!("remote_files")),
|
||||||
)
|
)
|
||||||
.child(div().flex_1())
|
.child(div().flex_1())
|
||||||
.child(
|
.child(
|
||||||
@@ -2332,7 +2550,7 @@ impl Ashell {
|
|||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.icon(IconName::ArrowRight)
|
.icon(IconName::ArrowRight)
|
||||||
.label("refresh")
|
.label(rust_i18n::t!("refresh").to_string())
|
||||||
.on_click(cx.listener(|this, _, _, cx| this.refresh_sftp(cx))),
|
.on_click(cx.listener(|this, _, _, cx| this.refresh_sftp(cx))),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -2340,7 +2558,7 @@ impl Ashell {
|
|||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.icon(IconName::Plus)
|
.icon(IconName::Plus)
|
||||||
.label("upload file")
|
.label(rust_i18n::t!("upload_file").to_string())
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
this.upload_sftp_files(window, cx)
|
this.upload_sftp_files(window, cx)
|
||||||
})),
|
})),
|
||||||
@@ -2350,14 +2568,30 @@ impl Ashell {
|
|||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.icon(IconName::Folder)
|
.icon(IconName::Folder)
|
||||||
.label("upload folder")
|
.label(rust_i18n::t!("upload_folder").to_string())
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
this.upload_sftp_folder(window, cx)
|
this.upload_sftp_folder(window, cx)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("sftp-download-selected")
|
||||||
|
.ghost()
|
||||||
|
.small()
|
||||||
|
.icon(IconName::ArrowDown)
|
||||||
|
.label(if selected_entries.is_empty() {
|
||||||
|
rust_i18n::t!("download").to_string()
|
||||||
|
} else {
|
||||||
|
rust_i18n::t!("download_count", count = selected_entries.len()).to_string()
|
||||||
|
})
|
||||||
|
.disabled(selected_entries.is_empty())
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.download_selected_sftp_entries(window, cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
Checkbox::new("sftp-show-hidden")
|
Checkbox::new("sftp-show-hidden")
|
||||||
.label("hidden")
|
.small()
|
||||||
|
.label(rust_i18n::t!("hidden").to_string())
|
||||||
.checked(self.show_hidden_files)
|
.checked(self.show_hidden_files)
|
||||||
.tab_stop(false)
|
.tab_stop(false)
|
||||||
.on_click(cx.listener(|this, checked, _, cx| {
|
.on_click(cx.listener(|this, checked, _, cx| {
|
||||||
@@ -2396,6 +2630,20 @@ impl Ashell {
|
|||||||
.border_b_1()
|
.border_b_1()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.bg(cx.theme().muted.opacity(0.8))
|
.bg(cx.theme().muted.opacity(0.8))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.w(px(24.))
|
||||||
|
.flex_none()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.child(
|
||||||
|
Checkbox::new("sftp-select-all")
|
||||||
|
.checked(all_selected)
|
||||||
|
.on_click(cx.listener(move |this, checked, _, cx| {
|
||||||
|
this.toggle_all_sftp_entries(*checked, cx);
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
@@ -2408,7 +2656,7 @@ impl Ashell {
|
|||||||
.flex_1()
|
.flex_1()
|
||||||
.text_size(px(11.))
|
.text_size(px(11.))
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child("NAME"),
|
.child(rust_i18n::t!("name")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -2417,7 +2665,7 @@ impl Ashell {
|
|||||||
.flex_none()
|
.flex_none()
|
||||||
.text_size(px(11.))
|
.text_size(px(11.))
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child("SIZE"),
|
.child(rust_i18n::t!("size")),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
@@ -2425,7 +2673,7 @@ impl Ashell {
|
|||||||
.flex_none()
|
.flex_none()
|
||||||
.text_size(px(11.))
|
.text_size(px(11.))
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child("MODIFIED"),
|
.child(rust_i18n::t!("modified")),
|
||||||
)
|
)
|
||||||
.child(div().w(px(12.)).flex_none()),
|
.child(div().w(px(12.)).flex_none()),
|
||||||
)
|
)
|
||||||
@@ -2447,6 +2695,8 @@ impl Ashell {
|
|||||||
let left_row = entry.clone();
|
let left_row = entry.clone();
|
||||||
let right_row = entry.clone();
|
let right_row = entry.clone();
|
||||||
let remote_path = entry.full_path.clone();
|
let remote_path = entry.full_path.clone();
|
||||||
|
let is_checked =
|
||||||
|
selected_entries.contains(&entry.full_path);
|
||||||
let is_selected = selected_path
|
let is_selected = selected_path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|path| path == entry.full_path)
|
.map(|path| path == entry.full_path)
|
||||||
@@ -2504,6 +2754,37 @@ impl Ashell {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.w(px(24.))
|
||||||
|
.flex_none()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.on_mouse_down(MouseButton::Left, |_, _, cx| {
|
||||||
|
cx.stop_propagation()
|
||||||
|
})
|
||||||
|
.on_mouse_down(
|
||||||
|
MouseButton::Right,
|
||||||
|
|_, _, cx| cx.stop_propagation(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Checkbox::new(ElementId::Name(
|
||||||
|
format!("check-{}", entry.full_path)
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
.checked(is_checked)
|
||||||
|
.on_click(window.listener_for(&view, {
|
||||||
|
let path = entry.full_path.clone();
|
||||||
|
move |this, checked, _, cx| {
|
||||||
|
this.toggle_sftp_entry(
|
||||||
|
path.clone(),
|
||||||
|
*checked,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
@@ -2596,11 +2877,6 @@ impl Ashell {
|
|||||||
fn sidebar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn sidebar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let sessions = self.config.sessions().to_vec();
|
let sessions = self.config.sessions().to_vec();
|
||||||
let active_session_id = self.active_session_id().map(ToOwned::to_owned);
|
let active_session_id = self.active_session_id().map(ToOwned::to_owned);
|
||||||
let active_kind = match self.active_kind() {
|
|
||||||
Some(TabKind::Local) => "local",
|
|
||||||
Some(TabKind::Ssh) => "ssh",
|
|
||||||
None => "idle",
|
|
||||||
};
|
|
||||||
let connection_color = self.active_connection_color(cx);
|
let connection_color = self.active_connection_color(cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -2638,7 +2914,17 @@ impl Ashell {
|
|||||||
div()
|
div()
|
||||||
.text_size(px(11.))
|
.text_size(px(11.))
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child(format!("{} / {}", active_kind, self.active_title())),
|
.child({
|
||||||
|
if let Some(kind) = self.active_kind() {
|
||||||
|
let kind_str = match kind {
|
||||||
|
TabKind::Local => rust_i18n::t!("local_terminal").to_string(),
|
||||||
|
TabKind::Ssh => "ssh".to_string(),
|
||||||
|
};
|
||||||
|
format!("{} / {}", kind_str, self.active_title())
|
||||||
|
} else {
|
||||||
|
self.active_title()
|
||||||
|
}
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -2653,7 +2939,7 @@ impl Ashell {
|
|||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.font_weight(FontWeight::SEMIBOLD)
|
.font_weight(FontWeight::SEMIBOLD)
|
||||||
.text_color(cx.theme().primary)
|
.text_color(cx.theme().primary)
|
||||||
.child("SYSTEM"),
|
.child(rust_i18n::t!("system")),
|
||||||
)
|
)
|
||||||
.child(div().flex_1())
|
.child(div().flex_1())
|
||||||
.child(
|
.child(
|
||||||
@@ -2672,21 +2958,21 @@ impl Ashell {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(self.system_card(
|
.child(self.system_card(
|
||||||
"CPU",
|
rust_i18n::t!("cpu").to_string(),
|
||||||
self.system.cpu_percent,
|
self.system.cpu_percent,
|
||||||
"global load".into(),
|
rust_i18n::t!("global_load").into(),
|
||||||
cx.theme().chart_1,
|
cx.theme().chart_1,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.child(self.system_card(
|
.child(self.system_card(
|
||||||
"MEM",
|
rust_i18n::t!("mem").to_string(),
|
||||||
self.system.mem_percent,
|
self.system.mem_percent,
|
||||||
self.system.mem_detail.clone(),
|
self.system.mem_detail.clone(),
|
||||||
cx.theme().chart_2,
|
cx.theme().chart_2,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.child(self.system_card(
|
.child(self.system_card(
|
||||||
"SWAP",
|
rust_i18n::t!("swap").to_string(),
|
||||||
self.system.swap_percent,
|
self.system.swap_percent,
|
||||||
self.system.swap_detail.clone(),
|
self.system.swap_detail.clone(),
|
||||||
cx.theme().chart_3,
|
cx.theme().chart_3,
|
||||||
@@ -2705,7 +2991,7 @@ impl Ashell {
|
|||||||
div()
|
div()
|
||||||
.text_size(px(11.))
|
.text_size(px(11.))
|
||||||
.text_color(cx.theme().chart_5)
|
.text_color(cx.theme().chart_5)
|
||||||
.child("DISK"),
|
.child(rust_i18n::t!("disk")),
|
||||||
)
|
)
|
||||||
.children({
|
.children({
|
||||||
let mut disk_elements = Vec::new();
|
let mut disk_elements = Vec::new();
|
||||||
@@ -2719,7 +3005,7 @@ impl Ashell {
|
|||||||
.child(
|
.child(
|
||||||
Button::new("open-ssh-panel")
|
Button::new("open-ssh-panel")
|
||||||
.primary()
|
.primary()
|
||||||
.label("+ ssh")
|
.label(rust_i18n::t!("add_ssh").to_string())
|
||||||
.on_click(
|
.on_click(
|
||||||
cx.listener(|this, _, window, cx| this.open_new_ssh_dialog(window, cx)),
|
cx.listener(|this, _, window, cx| this.open_new_ssh_dialog(window, cx)),
|
||||||
),
|
),
|
||||||
@@ -2732,7 +3018,7 @@ impl Ashell {
|
|||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.font_weight(FontWeight::SEMIBOLD)
|
.font_weight(FontWeight::SEMIBOLD)
|
||||||
.text_color(cx.theme().primary)
|
.text_color(cx.theme().primary)
|
||||||
.child("SAVED"),
|
.child(rust_i18n::t!("saved")),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -3250,7 +3536,26 @@ fn sync_macos_launch_environment() {
|
|||||||
fn sync_macos_launch_environment() {}
|
fn sync_macos_launch_environment() {}
|
||||||
|
|
||||||
fn open_main_window(cx: &mut App) {
|
fn open_main_window(cx: &mut App) {
|
||||||
let window_options = WindowOptions::default();
|
let mut window_options = WindowOptions::default();
|
||||||
|
|
||||||
|
if let Some(display) = cx.displays().first().cloned() {
|
||||||
|
let display_bounds = display.bounds();
|
||||||
|
let width = display_bounds.size.width * 0.8;
|
||||||
|
let height = display_bounds.size.height * 0.9;
|
||||||
|
|
||||||
|
let x = display_bounds.origin.x + (display_bounds.size.width - width) / 2.0;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let y = display_bounds.origin.y;
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
let y = display_bounds.origin.y + (display_bounds.size.height - height) / 2.0;
|
||||||
|
|
||||||
|
window_options.window_bounds = Some(gpui::WindowBounds::Windowed(Bounds::new(
|
||||||
|
point(x, y),
|
||||||
|
size(width, height),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
cx.open_window(window_options, |window, cx| {
|
cx.open_window(window_options, |window, cx| {
|
||||||
window.activate_window();
|
window.activate_window();
|
||||||
window.set_window_title("ashell");
|
window.set_window_title("ashell");
|
||||||
|
|||||||
+11
-3
@@ -47,7 +47,7 @@ pub struct PreviewData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum SftpCommand {
|
pub enum SftpCommand {
|
||||||
ListDir(String),
|
ListDir(String),
|
||||||
Preview(String),
|
Preview(String),
|
||||||
Download { remote: String, local_dir: String },
|
Download { remote: String, local_dir: String },
|
||||||
@@ -56,7 +56,7 @@ enum SftpCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct SftpHandle {
|
pub struct SftpHandle {
|
||||||
commands: UnboundedSender<SftpCommand>,
|
pub commands: UnboundedSender<SftpCommand>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
join: Option<JoinHandle<()>>,
|
join: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,15 @@ async fn run_sftp(
|
|||||||
match command {
|
match command {
|
||||||
SftpCommand::Close => break,
|
SftpCommand::Close => break,
|
||||||
SftpCommand::ListDir(path) => {
|
SftpCommand::ListDir(path) => {
|
||||||
if let Err(err) = emit_entries(&events, &tab_id, &sftp, &path).await {
|
let actual_path = if path == "~" {
|
||||||
|
home.clone()
|
||||||
|
} else if let Some(rest) = path.strip_prefix("~/") {
|
||||||
|
crate::sftp::join_remote(&home, rest)
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = emit_entries(&events, &tab_id, &sftp, &actual_path).await {
|
||||||
let _ = events.send(BackendEvent::SftpStatus {
|
let _ = events.send(BackendEvent::SftpStatus {
|
||||||
tab_id: tab_id.clone(),
|
tab_id: tab_id.clone(),
|
||||||
text: format!("list failed: {err:#}"),
|
text: format!("list failed: {err:#}"),
|
||||||
|
|||||||
+20
-8
@@ -41,6 +41,7 @@ pub enum BackendEvent {
|
|||||||
RemoteSystem { tab_id: String, snapshot: SystemSnapshot },
|
RemoteSystem { tab_id: String, snapshot: SystemSnapshot },
|
||||||
RemoteSystemUnavailable { tab_id: String, reason: String },
|
RemoteSystemUnavailable { tab_id: String, reason: String },
|
||||||
Closed { tab_id: String, reason: String },
|
Closed { tab_id: String, reason: String },
|
||||||
|
TerminalTitleChanged { tab_id: String, title: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -118,20 +119,22 @@ pub struct SftpUiState {
|
|||||||
pub entries: Vec<RemoteEntry>,
|
pub entries: Vec<RemoteEntry>,
|
||||||
pub selected_path: Option<String>,
|
pub selected_path: Option<String>,
|
||||||
pub preview: Option<PreviewData>,
|
pub preview: Option<PreviewData>,
|
||||||
|
pub selected_entries: std::collections::HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalTab {
|
impl TerminalTab {
|
||||||
pub fn new_local(id: String, title: String, backend: BackendTx) -> Self {
|
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)
|
Self::new(id, title, TabKind::Local, "local shell".into(), backend, events)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_ssh(id: String, session: &Session, backend: BackendTx) -> Self {
|
pub fn new_ssh(id: String, session: &Session, backend: BackendTx, events: std::sync::mpsc::Sender<BackendEvent>) -> Self {
|
||||||
let mut tab = Self::new(
|
let mut tab = Self::new(
|
||||||
id,
|
id,
|
||||||
session.name.clone(),
|
session.name.clone(),
|
||||||
TabKind::Ssh,
|
TabKind::Ssh,
|
||||||
format!("connecting {}@{}:{}", session.user, session.host, session.port),
|
format!("connecting {}@{}:{}", session.user, session.host, session.port),
|
||||||
backend,
|
backend,
|
||||||
|
events,
|
||||||
);
|
);
|
||||||
tab.session = Some(session.clone());
|
tab.session = Some(session.clone());
|
||||||
tab.connected = false;
|
tab.connected = false;
|
||||||
@@ -141,13 +144,14 @@ impl TerminalTab {
|
|||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
selected_path: None,
|
selected_path: None,
|
||||||
preview: None,
|
preview: None,
|
||||||
|
selected_entries: std::collections::HashSet::new(),
|
||||||
});
|
});
|
||||||
tab
|
tab
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(id: String, title: String, kind: TabKind, status: String, backend: BackendTx) -> Self {
|
fn new(id: String, title: String, kind: TabKind, status: String, backend: BackendTx, events: std::sync::mpsc::Sender<BackendEvent>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id: id.clone(),
|
||||||
title,
|
title,
|
||||||
kind,
|
kind,
|
||||||
status,
|
status,
|
||||||
@@ -155,7 +159,7 @@ impl TerminalTab {
|
|||||||
session: None,
|
session: None,
|
||||||
sftp: None,
|
sftp: None,
|
||||||
processor: Processor::new(),
|
processor: Processor::new(),
|
||||||
term: new_term(100, 30, backend.clone()),
|
term: new_term(100, 30, backend.clone(), id, events),
|
||||||
cols: 100,
|
cols: 100,
|
||||||
rows: 30,
|
rows: 30,
|
||||||
backend,
|
backend,
|
||||||
@@ -324,7 +328,9 @@ fn viewport_selection_from_range(
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TerminalListener {
|
struct TerminalListener {
|
||||||
|
tab_id: String,
|
||||||
backend: BackendTx,
|
backend: BackendTx,
|
||||||
|
events: std::sync::mpsc::Sender<BackendEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventListener for TerminalListener {
|
impl EventListener for TerminalListener {
|
||||||
@@ -341,19 +347,25 @@ impl EventListener for TerminalListener {
|
|||||||
self.backend
|
self.backend
|
||||||
.send(BackendCommand::Input(format(size).into_bytes()));
|
.send(BackendCommand::Input(format(size).into_bytes()));
|
||||||
}
|
}
|
||||||
|
Event::Title(title) => {
|
||||||
|
let _ = self.events.send(BackendEvent::TerminalTitleChanged {
|
||||||
|
tab_id: self.tab_id.clone(),
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_term(cols: u16, rows: u16, backend: BackendTx) -> Term<TerminalListener> {
|
fn new_term(cols: u16, rows: u16, backend: BackendTx, tab_id: String, events: std::sync::mpsc::Sender<BackendEvent>) -> Term<TerminalListener> {
|
||||||
Term::new(
|
Term::new(
|
||||||
Config {
|
Config {
|
||||||
scrolling_history: 2000,
|
scrolling_history: 2000,
|
||||||
..Config::default()
|
..Config::default()
|
||||||
},
|
},
|
||||||
&TerminalSize::new(cols, rows),
|
&TerminalSize::new(cols, rows),
|
||||||
TerminalListener { backend },
|
TerminalListener { tab_id, backend, events },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user