mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-10-03 16:00:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
785670c428 | ||
|
|
d33babe552 |
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user