Compare commits

...
3 Commits
6 changed files with 68 additions and 12 deletions
+1 -1
View File
@@ -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
+9
View File
@@ -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:
+9
View File
@@ -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 原生工作区:
+22 -2
View File
@@ -460,6 +460,26 @@ impl Ashell {
} }
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 +535,7 @@ 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);
@@ -1138,7 +1158,7 @@ impl Ashell {
self.events_tx.clone(), self.events_tx.clone(),
); );
self.tabs self.tabs
.push(TerminalTab::new_ssh(id.clone(), &session, backend)); .push(TerminalTab::new_ssh(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(),
+9 -1
View File
@@ -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:#}"),
+18 -8
View File
@@ -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)]
@@ -121,17 +122,18 @@ pub struct SftpUiState {
} }
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;
@@ -145,9 +147,9 @@ impl TerminalTab {
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 +157,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 +326,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 +345,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 },
) )
} }