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
name: macos-aarch64
bin: ashell
- os: macos-13 # Intel
- os: macos-14 # Intel (cross-compiled from Apple Silicon to avoid queue delays)
target: x86_64-apple-darwin
name: macos-x86_64
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).
## 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
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 版本的最新预编译程序。
## Mac 安装指南
下载并解压后,请先将 `ashell.app` 拖入或移动到 **应用程序 (Applications)** 目录。
由于应用采用本地签名,初次启动时如果系统提示“App 已损坏,无法打开”,请打开终端(Terminal)并执行以下命令:
```bash
sudo xattr -cr /Applications/ashell.app
```
## 功能特性
当前版本提供了一个功能完备的 GPUI 原生工作区:
+22 -2
View File
@@ -460,6 +460,26 @@ impl Ashell {
}
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) => {
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);
self.tabs.push(tab);
self.active_tab = Some(id);
@@ -1138,7 +1158,7 @@ impl Ashell {
self.events_tx.clone(),
);
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(
self.runtime.handle(),
id.clone(),
+9 -1
View File
@@ -144,7 +144,15 @@ async fn run_sftp(
match command {
SftpCommand::Close => break,
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 {
tab_id: tab_id.clone(),
text: format!("list failed: {err:#}"),
+18 -8
View File
@@ -41,6 +41,7 @@ pub enum BackendEvent {
RemoteSystem { tab_id: String, snapshot: SystemSnapshot },
RemoteSystemUnavailable { tab_id: String, reason: String },
Closed { tab_id: String, reason: String },
TerminalTitleChanged { tab_id: String, title: String },
}
#[derive(Clone)]
@@ -121,17 +122,18 @@ pub struct SftpUiState {
}
impl TerminalTab {
pub fn new_local(id: String, title: String, backend: BackendTx) -> Self {
Self::new(id, title, TabKind::Local, "local shell".into(), backend)
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) -> 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),
backend,
events,
);
tab.session = Some(session.clone());
tab.connected = false;
@@ -145,9 +147,9 @@ impl TerminalTab {
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 {
id,
id: id.clone(),
title,
kind,
status,
@@ -155,7 +157,7 @@ impl TerminalTab {
session: None,
sftp: None,
processor: Processor::new(),
term: new_term(100, 30, backend.clone()),
term: new_term(100, 30, backend.clone(), id, events),
cols: 100,
rows: 30,
backend,
@@ -324,7 +326,9 @@ fn viewport_selection_from_range(
#[derive(Clone)]
struct TerminalListener {
tab_id: String,
backend: BackendTx,
events: std::sync::mpsc::Sender<BackendEvent>,
}
impl EventListener for TerminalListener {
@@ -341,19 +345,25 @@ impl EventListener for TerminalListener {
self.backend
.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(
Config {
scrolling_history: 2000,
..Config::default()
},
&TerminalSize::new(cols, rows),
TerminalListener { backend },
TerminalListener { tab_id, backend, events },
)
}