mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-10-04 16:00:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||||
|
|||||||
@@ -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 原生工作区:
|
||||||
|
|||||||
+22
-2
@@ -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
@@ -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
@@ -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 },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user