style: run cargo fmt

This commit is contained in:
TomZz
2026-07-15 02:26:16 +08:00
parent 4c292c060c
commit a32fd9437d
17 changed files with 722 additions and 344 deletions
+19 -3
View File
@@ -128,12 +128,20 @@ pub(crate) const WORKSPACE_ACTIONS: &[WorkspaceAction] = &[
WorkspaceAction {
id: "Copy",
label_key: "settings_copy",
default_suffix: if cfg!(target_os = "macos") { "c" } else { "shift-c" },
default_suffix: if cfg!(target_os = "macos") {
"c"
} else {
"shift-c"
},
},
WorkspaceAction {
id: "Paste",
label_key: "settings_paste",
default_suffix: if cfg!(target_os = "macos") { "v" } else { "shift-v" },
default_suffix: if cfg!(target_os = "macos") {
"v"
} else {
"shift-v"
},
},
];
@@ -305,7 +313,15 @@ impl KeybindingsPage {
let groups = [
(
"settings_group_keybind_general",
vec!["OpenSettings", "OpenSession", "OpenTransfers", "NewSsh", "OpenSearch", "Copy", "Paste"],
vec![
"OpenSettings",
"OpenSession",
"OpenTransfers",
"NewSsh",
"OpenSearch",
"Copy",
"Paste",
],
),
(
"settings_group_keybind_zoom",
+13 -18
View File
@@ -4,17 +4,15 @@ use std::{
};
use gpui::{
Along, AnyElement, App, Axis, Bounds, Context, Element, ElementId, Empty, Entity,
EventEmitter, InteractiveElement as _, IntoElement, MouseMoveEvent, MouseUpEvent,
ParentElement, Pixels, Render, RenderOnce, Style, StyleRefinement, Styled, Window, div,
prelude::FluentBuilder, AppContext as _, IsZero as _,
Along, AnyElement, App, AppContext as _, Axis, Bounds, Context, Element, ElementId, Empty,
Entity, EventEmitter, InteractiveElement as _, IntoElement, IsZero as _, MouseMoveEvent,
MouseUpEvent, ParentElement, Pixels, Render, RenderOnce, Style, StyleRefinement, Styled,
Window, div, prelude::FluentBuilder,
};
use gpui_component::{
AxisExt, ElementExt, h_flex, v_flex, StyledExt,
};
use gpui_component::{AxisExt, ElementExt, StyledExt, h_flex, v_flex};
use super::{ResizableState, resizable_panel, resize_handle, PANEL_MIN_SIZE, ResizablePanelEvent};
use super::{PANEL_MIN_SIZE, ResizablePanelEvent, ResizableState, resizable_panel, resize_handle};
#[derive(Clone)]
pub(crate) struct DragPanel;
@@ -305,16 +303,13 @@ impl RenderOnce for ResizablePanel {
let handle = if self.locked {
handle
} else {
handle.on_drag(
DragPanel,
move |drag_panel, _, _, cx| {
cx.stop_propagation();
state.update(cx, |state, _| {
state.resizing_panel_ix = Some(ix);
});
cx.new(|_| drag_panel.deref().clone())
},
)
handle.on_drag(DragPanel, move |drag_panel, _, _, cx| {
cx.stop_propagation();
state.update(cx, |state, _| {
state.resizing_panel_ix = Some(ix);
});
cx.new(|_| drag_panel.deref().clone())
})
};
this.child(handle)
})
+10 -11
View File
@@ -1,9 +1,9 @@
use std::{cell::Cell, rc::Rc};
use gpui::{
div, prelude::FluentBuilder as _, px, AnyElement, App, Axis, Element, ElementId, Entity,
GlobalElementId, InteractiveElement, IntoElement, MouseDownEvent, MouseUpEvent,
ParentElement as _, Pixels, Point, Render, StatefulInteractiveElement, Styled as _, Window,
AnyElement, App, Axis, Element, ElementId, Entity, GlobalElementId, InteractiveElement,
IntoElement, MouseDownEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render,
StatefulInteractiveElement, Styled as _, Window, div, prelude::FluentBuilder as _, px,
};
use gpui_component::{ActiveTheme as _, AxisExt as _, dock::DockPlacement};
@@ -126,14 +126,13 @@ impl<T: 'static, E: 'static + Render> Element for ResizeHandle<T, E> {
)
})
.map(|this| match self.placement {
Some(DockPlacement::Left) => {
this.when(has_drag, |this| this.cursor_col_resize())
.top_0()
.right(px(1.))
.h_full()
.w(HANDLE_SIZE)
.pl(HANDLE_PADDING)
}
Some(DockPlacement::Left) => this
.when(has_drag, |this| this.cursor_col_resize())
.top_0()
.right(px(1.))
.h_full()
.w(HANDLE_SIZE)
.pl(HANDLE_PADDING),
_ => this
.when(axis.is_horizontal(), |this| {
this.when(has_drag, |this| this.cursor_col_resize())
+31 -29
View File
@@ -1,13 +1,14 @@
use std::collections::HashMap;
use gpui::{
Context, Focusable as _, Hsla, IntoElement, InteractiveElement as _, MouseButton,
Context, Focusable as _, Hsla, InteractiveElement as _, IntoElement, MouseButton,
ParentElement as _, Styled as _, Window, div, prelude::FluentBuilder as _, px, rems,
};
use gpui_component::{
ActiveTheme as _, Disableable as _, ElementExt as _, IconName, Sizable as _,
button::{Button, ButtonVariants as _},
h_flex, input::Input,
h_flex,
input::Input,
};
use rust_i18n::t;
@@ -323,23 +324,19 @@ impl Ashell {
}
/// Render the search button (used in the tab bar).
pub(crate) fn render_search_button(
&self,
cx: &mut Context<Self>,
) -> impl gpui::IntoElement {
pub(crate) fn render_search_button(&self, cx: &mut Context<Self>) -> impl gpui::IntoElement {
// Wrap in a div so .hover() doesn't conflict with Button's internal hover.
div()
.child(
Button::new("search-btn")
.ghost()
.small()
.rounded(px(999.))
.icon(IconName::Search)
.tooltip(t!("search").to_string())
.on_click(cx.listener(|this, _, window, cx| {
this.toggle_search(window, cx);
})),
)
div().child(
Button::new("search-btn")
.ghost()
.small()
.rounded(px(999.))
.icon(IconName::Search)
.tooltip(t!("search").to_string())
.on_click(cx.listener(|this, _, window, cx| {
this.toggle_search(window, cx);
})),
)
}
/// Render the expanded search bar overlay (when search is active).
@@ -369,10 +366,13 @@ impl Ashell {
this.search_bar_bounds = Some(bounds);
});
})
.on_mouse_down(MouseButton::Left, cx.listener(|this, _, window, cx| {
this.refocus_search_input(window, cx);
cx.stop_propagation();
}))
.on_mouse_down(
MouseButton::Left,
cx.listener(|this, _, window, cx| {
this.refocus_search_input(window, cx);
cx.stop_propagation();
}),
)
.child(
h_flex()
.gap_1()
@@ -385,13 +385,15 @@ impl Ashell {
.child(
div()
.w(px(200.))
.on_key_down(cx.listener(|this, event: &gpui::KeyDownEvent, window, cx| {
if event.keystroke.key.as_str() == "escape" {
this.close_search(window, cx);
window.prevent_default();
cx.stop_propagation();
}
}))
.on_key_down(cx.listener(
|this, event: &gpui::KeyDownEvent, window, cx| {
if event.keystroke.key.as_str() == "escape" {
this.close_search(window, cx);
window.prevent_default();
cx.stop_propagation();
}
},
))
.child(Input::new(&self.search_input).small()),
)
.when(!current_display.is_empty(), |this| {
+11 -4
View File
@@ -179,7 +179,14 @@ pub(crate) fn sync_macos_launch_environment() {
}
fn read_proxy_from_env() -> Option<(String, String, Option<u16>, String, String)> {
let vars = ["ALL_PROXY", "all_proxy", "HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
let vars = [
"ALL_PROXY",
"all_proxy",
"HTTPS_PROXY",
"https_proxy",
"HTTP_PROXY",
"http_proxy",
];
for var in vars {
if let Ok(val) = std::env::var(var) {
if val.is_empty() {
@@ -301,13 +308,13 @@ pub(crate) fn open_main_window(cx: &mut App) {
let focus_handle = view.read(cx).focus_handle.clone();
window.focus(&focus_handle, cx);
let view_clone = view.clone();
window.on_window_should_close(cx, move |window: &mut gpui::Window, cx: &mut gpui::App| {
let handle = window.window_handle();
if !cx.windows().contains(&handle) {
tracing::warn!("[ui] window not found in app during close, skipping save layout state.");
tracing::warn!(
"[ui] window not found in app during close, skipping save layout state."
);
return true;
}
view_clone.read(cx).save_layout_state(window, cx);
+4 -1
View File
@@ -17,7 +17,10 @@ use std::sync::atomic::{AtomicBool, Ordering};
pub(crate) static USING_SYSTEM_MAPLE: AtomicBool = AtomicBool::new(false);
pub(crate) fn load_fonts(cx: &mut App) -> Result<()> {
let has_system_maple = cx.text_system().all_font_names().contains(&"Maple Mono NF CN".to_string());
let has_system_maple = cx
.text_system()
.all_font_names()
.contains(&"Maple Mono NF CN".to_string());
if has_system_maple {
USING_SYSTEM_MAPLE.store(true, Ordering::Relaxed);
} else {
+70 -49
View File
@@ -1,3 +1,4 @@
use crate::app::resizable::{h_resizable, resizable_panel, v_resizable};
use gpui::{
Context, ElementId, Focusable as _, FontWeight, Hsla, InteractiveElement as _, IntoElement,
MouseButton, MouseDownEvent, ParentElement as _, PathBuilder, Pixels, Render,
@@ -17,7 +18,6 @@ use gpui_component::{
tab::{Tab, TabBar},
v_flex,
};
use crate::app::resizable::{h_resizable, resizable_panel, v_resizable};
use rust_i18n::t;
use crate::{
@@ -1984,7 +1984,7 @@ impl Ashell {
) -> impl IntoElement {
let is_macos = cfg!(target_os = "macos");
let is_fullscreen = window.is_fullscreen();
let is_active = cx.active_window() == Some(window.window_handle());
h_flex()
@@ -1999,9 +1999,17 @@ impl Ashell {
.id("window-close")
.size(px(12.))
.rounded_full()
.bg(if is_active { hsla(3.0 / 360.0, 1.0, 0.67, 1.0) } else { hsla(0.0, 0.0, 0.8, 1.0) }) // Red or Inactive Grey
.group_hover("window-controls", |s| s.bg(hsla(3.0 / 360.0, 1.0, 0.67, 1.0)))
.when(!is_macos, |this| this.window_control_area(gpui::WindowControlArea::Close))
.bg(if is_active {
hsla(3.0 / 360.0, 1.0, 0.67, 1.0)
} else {
hsla(0.0, 0.0, 0.8, 1.0)
}) // Red or Inactive Grey
.group_hover("window-controls", |s| {
s.bg(hsla(3.0 / 360.0, 1.0, 0.67, 1.0))
})
.when(!is_macos, |this| {
this.window_control_area(gpui::WindowControlArea::Close)
})
.on_click(cx.listener(|this, _, window, cx| {
this.save_layout_state(window, cx);
window.remove_window();
@@ -2020,7 +2028,7 @@ impl Ashell {
.text_color(hsla(3.0 / 360.0, 1.0, 0.15, 0.7))
.opacity(0.0)
.group_hover("window-controls", |s| s.opacity(1.0))
.child("")
.child(""),
),
)
.child(
@@ -2028,9 +2036,17 @@ impl Ashell {
.id("window-minimize")
.size(px(12.))
.rounded_full()
.bg(if is_active { hsla(39.0 / 360.0, 1.0, 0.59, 1.0) } else { hsla(0.0, 0.0, 0.8, 1.0) }) // Yellow or Inactive Grey
.group_hover("window-controls", |s| s.bg(hsla(39.0 / 360.0, 1.0, 0.59, 1.0)))
.when(!is_macos, |this| this.window_control_area(gpui::WindowControlArea::Min))
.bg(if is_active {
hsla(39.0 / 360.0, 1.0, 0.59, 1.0)
} else {
hsla(0.0, 0.0, 0.8, 1.0)
}) // Yellow or Inactive Grey
.group_hover("window-controls", |s| {
s.bg(hsla(39.0 / 360.0, 1.0, 0.59, 1.0))
})
.when(!is_macos, |this| {
this.window_control_area(gpui::WindowControlArea::Min)
})
.on_click(|_, window, _| window.minimize_window())
.hover(|s| s.bg(hsla(39.0 / 360.0, 1.0, 0.49, 1.0)))
.active(|s| s.bg(hsla(39.0 / 360.0, 1.0, 0.39, 1.0)))
@@ -2046,7 +2062,7 @@ impl Ashell {
.text_color(hsla(39.0 / 360.0, 1.0, 0.15, 0.8))
.opacity(0.0)
.group_hover("window-controls", |s| s.opacity(1.0))
.child("")
.child(""),
),
)
.child(
@@ -2054,9 +2070,17 @@ impl Ashell {
.id("window-maximize")
.size(px(12.))
.rounded_full()
.bg(if is_active { hsla(127.0 / 360.0, 0.68, 0.47, 1.0) } else { hsla(0.0, 0.0, 0.8, 1.0) }) // Green or Inactive Grey
.group_hover("window-controls", |s| s.bg(hsla(127.0 / 360.0, 0.68, 0.47, 1.0)))
.when(!is_macos, |this| this.window_control_area(gpui::WindowControlArea::Max))
.bg(if is_active {
hsla(127.0 / 360.0, 0.68, 0.47, 1.0)
} else {
hsla(0.0, 0.0, 0.8, 1.0)
}) // Green or Inactive Grey
.group_hover("window-controls", |s| {
s.bg(hsla(127.0 / 360.0, 0.68, 0.47, 1.0))
})
.when(!is_macos, |this| {
this.window_control_area(gpui::WindowControlArea::Max)
})
.on_click(|_, window, _| {
if window.is_fullscreen() {
window.toggle_fullscreen();
@@ -2081,7 +2105,7 @@ impl Ashell {
.text_color(hsla(127.0 / 360.0, 1.0, 0.15, 0.8))
.opacity(0.0)
.group_hover("window-controls", |s| s.opacity(1.0))
.child("+")
.child("+"),
),
)
})
@@ -2334,7 +2358,10 @@ impl Ashell {
let font_size = px(this.terminal_font_size);
let line_height = px(this.terminal_line_height());
let cell_width = px(this.terminal_cell_width());
let is_url_hovered = this.hovered_url.as_ref().map_or(false, |hu| hu.tab_id == *tab_id);
let is_url_hovered = this
.hovered_url
.as_ref()
.map_or(false, |hu| hu.tab_id == *tab_id);
let mut el = div()
.size_full()
.overflow_hidden()
@@ -2375,44 +2402,38 @@ impl Ashell {
.and_then(|tab| tab.disconnected_reason.clone());
if let Some(reason) = disconnected_reason {
let tab_id_for_reconnect = tab_id.clone();
el = div()
.size_full()
.relative()
.child(el)
.child(
div()
.absolute()
.bottom_0()
.left_0()
.right_0()
el = div().size_full().relative().child(el).child(
div().absolute().bottom_0().left_0().right_0().child(
h_flex()
.w_full()
.items_center()
.gap_2()
.px_3()
.py_1()
.bg(cx.theme().danger.opacity(0.15))
.child(
h_flex()
.w_full()
.items_center()
.gap_2()
.px_3()
.py_1()
.bg(cx.theme().danger.opacity(0.15))
div()
.text_size(rems(0.85))
.text_color(cx.theme().danger)
.child(
div()
.text_size(rems(0.85))
.text_color(cx.theme().danger)
.child(t!("session_disconnected", "reason" = reason).to_string()),
)
.child(
div()
.text_size(rems(0.85))
.text_color(cx.theme().muted_foreground)
.child(format!("{}", t!("press_enter_to_reconnect"))),
)
.on_mouse_down(
MouseButton::Left,
cx.listener(move |this, _, _, cx| {
this.retry_disconnected_tab(&tab_id_for_reconnect, cx);
}),
t!("session_disconnected", "reason" = reason)
.to_string(),
),
)
.child(
div()
.text_size(rems(0.85))
.text_color(cx.theme().muted_foreground)
.child(format!("{}", t!("press_enter_to_reconnect"))),
)
.on_mouse_down(
MouseButton::Left,
cx.listener(move |this, _, _, cx| {
this.retry_disconnected_tab(&tab_id_for_reconnect, cx);
}),
),
);
),
);
}
let indicator_color = this
.tabs
+5 -5
View File
@@ -322,7 +322,9 @@ async fn connect_and_authenticate(
break;
}
Ok(false) => {
tracing::debug!("[ssh] public key auth failed with algorithm, trying next");
tracing::debug!(
"[ssh] public key auth failed with algorithm, trying next"
);
continue;
}
Err(e) => {
@@ -344,8 +346,7 @@ async fn connect_and_authenticate(
success
} else {
let success =
authenticate_with_default_keys(&mut handle, &session.user, passphrase)
.await?;
authenticate_with_default_keys(&mut handle, &session.user, passphrase).await?;
if !success {
return Err(anyhow::anyhow!(
"public key authentication failed for {}@{}:{} - no valid default key found in ~/.ssh/",
@@ -417,8 +418,7 @@ async fn connect_and_authenticate(
),
});
let success =
authenticate_with_default_keys(&mut handle, &session.user, passphrase)
.await?;
authenticate_with_default_keys(&mut handle, &session.user, passphrase).await?;
if !success {
return Err(anyhow::anyhow!(
"ssh-config authentication failed for {}@{}:{} - no valid default key found",
+4 -6
View File
@@ -16,14 +16,12 @@ rust_i18n::i18n!("locales", fallback = "en");
gpui::actions!(ashell_terminal, [TerminalTabKey, TerminalBacktabKey]);
pub(crate) use app::keybinding_recorder::{
ClosePane, FocusPaneDown, FocusPaneLeft, FocusPaneRight, FocusPaneUp, NewSsh, OpenSearch,
OpenSession, OpenSettings, OpenTransfers, SplitPaneDown, SplitPaneLeft, SplitPaneRight,
SplitPaneUp, ToggleSftpZoom, ToggleSidebar, Copy, Paste,
ClosePane, Copy, FocusPaneDown, FocusPaneLeft, FocusPaneRight, FocusPaneUp, NewSsh, OpenSearch,
OpenSession, OpenSettings, OpenTransfers, Paste, SplitPaneDown, SplitPaneLeft, SplitPaneRight,
SplitPaneUp, ToggleSftpZoom, ToggleSidebar,
};
pub(crate) use app::{
Ashell, PaneLayout, SelectorEntry, SftpContextMenuState, TabGroup,
};
pub(crate) use app::{Ashell, PaneLayout, SelectorEntry, SftpContextMenuState, TabGroup};
fn main() {
app::startup::sync_macos_launch_environment();
+23 -15
View File
@@ -351,8 +351,8 @@ impl ConfigStore {
}
let mut cache = if path.exists() {
let raw_bytes = fs::read(&path)
.with_context(|| format!("failed to read {}", path.display()))?;
let raw_bytes =
fs::read(&path).with_context(|| format!("failed to read {}", path.display()))?;
let hardware_uuid = get_hardware_uuid();
match decrypt_config(&raw_bytes, &hardware_uuid) {
Ok(cache) => cache,
@@ -1031,7 +1031,12 @@ fn get_hardware_uuid() -> String {
#[cfg(target_os = "windows")]
{
if let Ok(output) = std::process::Command::new("reg")
.args(&["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"])
.args(&[
"query",
"HKLM\\SOFTWARE\\Microsoft\\Cryptography",
"/v",
"MachineGuid",
])
.output()
{
let stdout = String::from_utf8_lossy(&output.stdout);
@@ -1069,17 +1074,17 @@ fn encrypt_config(config: &ConfigFile, password: &str) -> Result<Vec<u8>> {
let mut nonce = [0u8; 24];
OsRng.fill_bytes(&mut salt);
OsRng.fill_bytes(&mut nonce);
let mut key = [0u8; 32];
Argon2::default()
.hash_password_into(password.as_bytes(), &salt, &mut key)
.map_err(|err| anyhow::anyhow!("derive encryption key: {err}"))?;
let plaintext = serde_json::to_vec(config).context("serialize config")?;
let ciphertext = XChaCha20Poly1305::new((&key).into())
.encrypt(XNonce::from_slice(&nonce), plaintext.as_ref())
.map_err(|_| anyhow::anyhow!("encrypt config payload"))?;
serde_json::to_vec_pretty(&EncryptedConfigEnvelope {
format_version: 1,
kdf: "argon2id".to_string(),
@@ -1100,7 +1105,9 @@ fn decrypt_config(raw: &[u8], password: &str) -> Result<ConfigFile> {
{
return Err(anyhow::anyhow!("unsupported encrypted config format"));
}
let salt = STANDARD.decode(envelope.salt).context("decode config salt")?;
let salt = STANDARD
.decode(envelope.salt)
.context("decode config salt")?;
let nonce = STANDARD
.decode(envelope.nonce)
.context("decode config nonce")?;
@@ -1110,16 +1117,18 @@ fn decrypt_config(raw: &[u8], password: &str) -> Result<ConfigFile> {
let ciphertext = STANDARD
.decode(envelope.payload)
.context("decode encrypted config payload")?;
let mut key = [0u8; 32];
Argon2::default()
.hash_password_into(password.as_bytes(), &salt, &mut key)
.map_err(|err| anyhow::anyhow!("derive encryption key: {err}"))?;
let plaintext = XChaCha20Poly1305::new((&key).into())
.decrypt(XNonce::from_slice(&nonce), ciphertext.as_ref())
.map_err(|_| anyhow::anyhow!("cannot decrypt config; hardware UUID mismatch or corrupted data"))?;
.map_err(|_| {
anyhow::anyhow!("cannot decrypt config; hardware UUID mismatch or corrupted data")
})?;
serde_json::from_slice(&plaintext).context("parse decrypted config")
}
@@ -1138,17 +1147,16 @@ mod tests {
let config = ConfigFile::default();
let password = "test-password-123";
let encrypted = encrypt_config(&config, password).unwrap();
// Ensure it doesn't contain plain text fields of default config
let encrypted_str = String::from_utf8_lossy(&encrypted);
assert!(!encrypted_str.contains("Maple Mono NF CN"));
assert!(encrypted_str.contains("argon2id"));
let decrypted = decrypt_config(&encrypted, password).unwrap();
assert_eq!(decrypted.terminal_font_family, config.terminal_font_family);
// Decrypt with wrong password should fail
assert!(decrypt_config(&encrypted, "wrong-password").is_err());
}
}
+6 -4
View File
@@ -102,9 +102,7 @@ impl Ashell {
let mut session = match self.ssh_auth_method {
AuthMethod::Password => Session::password(host, port, user, password),
AuthMethod::Key => {
Session::key(host, port, user, key_path, key_inline, passphrase)
}
AuthMethod::Key => Session::key(host, port, user, key_path, key_inline, passphrase),
AuthMethod::Config => {
// Force key_inline to empty — config mode never uses inline key content.
// The backend will try default keys from ~/.ssh/ if no explicit key path is set.
@@ -785,7 +783,11 @@ impl Ashell {
}
pub(crate) fn handle_tab_close(&mut self, id: String) {
if self.connection_progress.as_ref().map_or(false, |p| p.tab_id == id) {
if self
.connection_progress
.as_ref()
.map_or(false, |p| p.tab_id == id)
{
self.connection_progress = None;
}
let group_ix = self
-1
View File
@@ -177,4 +177,3 @@ mod tests {
assert_eq!(entries[1].hostname, "5.6.7.8");
}
}
+2 -4
View File
@@ -886,8 +886,7 @@ async fn connect_and_authenticate(
let passphrase = session.passphrase.trim();
let passphrase = (!passphrase.is_empty()).then_some(passphrase);
let success =
authenticate_with_default_keys(&mut handle, &session.user, passphrase)
.await?;
authenticate_with_default_keys(&mut handle, &session.user, passphrase).await?;
if !success {
return Err(anyhow!(
"public key authentication failed for {}@{}:{} - no valid default key found in ~/.ssh/",
@@ -940,8 +939,7 @@ async fn connect_and_authenticate(
let passphrase = session.passphrase.trim();
let passphrase = (!passphrase.is_empty()).then_some(passphrase);
let success =
authenticate_with_default_keys(&mut handle, &session.user, passphrase)
.await?;
authenticate_with_default_keys(&mut handle, &session.user, passphrase).await?;
if !success {
return Err(anyhow!(
"ssh-config authentication failed for {}@{}:{} - no valid default key found",
+32 -17
View File
@@ -375,7 +375,12 @@ impl TerminalElement {
fn layout_grid(
&self,
cx: &App,
) -> (Vec<LayoutRect>, Vec<BatchedTextRun>, Vec<LayoutCustomBlock>, Vec<LayoutUnderline>) {
) -> (
Vec<LayoutRect>,
Vec<BatchedTextRun>,
Vec<LayoutCustomBlock>,
Vec<LayoutUnderline>,
) {
let view_read = self.view.read(cx);
let hovered_url = view_read.hovered_url.clone();
@@ -439,12 +444,18 @@ impl TerminalElement {
// Apply hover underline if mouse is hovering over this URL
if let Some(hu) = &hovered_url {
if hu.tab_id == self.tab_id
&& hu.cells.contains(&(render_cell.row as usize, render_cell.col as usize))
&& hu
.cells
.contains(&(render_cell.row as usize, render_cell.col as usize))
{
underlines.push(LayoutUnderline {
row: render_cell.row,
col: render_cell.col,
cells: if cell.flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 },
cells: if cell.flags.contains(Flags::WIDE_CHAR) {
2
} else {
1
},
color: style.color,
});
}
@@ -515,7 +526,9 @@ impl TerminalElement {
let cursor_style = self.view.read(cx).cursor_style;
let show_cursor = match cursor_style {
CursorStyle::Blink | CursorStyle::BeamBlink => {
if let Ok(duration) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
if let Ok(duration) =
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
{
(duration.as_millis() / 600) % 2 == 0
} else {
true
@@ -595,7 +608,7 @@ impl Element for TerminalElement {
let tab_id = self.tab_id.clone();
let _ = view.update(cx, |this, cx| {
let old_bounds = this.terminal_bounds.insert(tab_id.clone(), bounds);
// Sync PTY size unconditionally on every prepaint layout pass to ensure
// absolute synchronization with GPUI layout regardless of intermediate events.
let line_height = this.terminal_line_height();
@@ -604,11 +617,11 @@ impl Element for TerminalElement {
let h = bounds.size.height.as_f32();
let cols = (w / cell_width).floor().max(1.0) as u16;
let rows = (h / line_height).floor().max(1.0) as u16;
if let Some(tab) = this.tabs.iter_mut().find(|t| t.id == tab_id) {
tab.resize(cols, rows);
}
if old_bounds != Some(bounds) {
cx.notify();
}
@@ -640,9 +653,14 @@ impl Element for TerminalElement {
) {
// Compute a vertical offset to center the text grid vertically,
// distributing the leftover pixel remainder evenly to the top and bottom.
let grid_height = prepaint.metrics.line_height * (prepaint.bounds.size.height.as_f32() / prepaint.metrics.line_height.as_f32()).floor();
let grid_height = prepaint.metrics.line_height
* (prepaint.bounds.size.height.as_f32() / prepaint.metrics.line_height.as_f32())
.floor();
let y_offset = ((prepaint.bounds.size.height - grid_height) / 2.0).max(px(0.0));
let draw_origin = point(prepaint.bounds.origin.x, prepaint.bounds.origin.y + y_offset);
let draw_origin = point(
prepaint.bounds.origin.x,
prepaint.bounds.origin.y + y_offset,
);
for rect in &prepaint.rects {
rect.paint(draw_origin, prepaint.metrics, window);
@@ -657,10 +675,10 @@ impl Element for TerminalElement {
}
for block in &prepaint.custom_blocks {
let x = draw_origin.x.as_f32()
+ block.col as f32 * prepaint.metrics.cell_width.as_f32();
let y = draw_origin.y.as_f32()
+ block.row as f32 * prepaint.metrics.line_height.as_f32();
let x =
draw_origin.x.as_f32() + block.col as f32 * prepaint.metrics.cell_width.as_f32();
let y =
draw_origin.y.as_f32() + block.row as f32 * prepaint.metrics.line_height.as_f32();
paint_custom_block(
window,
block.c,
@@ -801,10 +819,7 @@ fn merge_underlines(mut underlines: Vec<LayoutUnderline>) -> Vec<LayoutUnderline
for u in underlines {
if let Some(last) = merged.last_mut() {
if last.row == u.row
&& last.color == u.color
&& last.col + last.cells as i32 == u.col
{
if last.row == u.row && last.color == u.color && last.col + last.cells as i32 == u.col {
last.cells += u.cells;
continue;
}
+467 -168
View File
@@ -1,6 +1,6 @@
use std::collections::HashMap;
use gpui::Hsla;
use crate::terminal::RenderCell;
use gpui::Hsla;
use std::collections::HashMap;
trait HslaExt {
fn into_rgba_like(self, r: u8, g: u8, b: u8) -> Self;
@@ -15,10 +15,19 @@ impl HslaExt for Hsla {
let min = rf.min(gf).min(bf);
let l = (max + min) / 2.0;
if max == min {
return Hsla { h: 0.0, s: 0.0, l, a: 1.0 };
return Hsla {
h: 0.0,
s: 0.0,
l,
a: 1.0,
};
}
let d = max - min;
let s = if l > 0.5 { d / (2.0 - max - min) } else { d / (max + min) };
let s = if l > 0.5 {
d / (2.0 - max - min)
} else {
d / (max + min)
};
let h = if max == rf {
((gf - bf) / d + if gf < bf { 6.0 } else { 0.0 }) / 6.0
} else if max == gf {
@@ -33,58 +42,58 @@ impl HslaExt for Hsla {
#[derive(Debug, Clone)]
struct HighlightColors {
// Log levels
error: Hsla, // ERROR, ERR
critical: Hsla, // PANIC, FATAL, EMERGENCY, CRITICAL
warning: Hsla, // WARNING, WARN
info: Hsla, // INFO, NOTICE
debug: Hsla, // DEBUG, TRACE, DBG
alert: Hsla, // ALERT
error: Hsla, // ERROR, ERR
critical: Hsla, // PANIC, FATAL, EMERGENCY, CRITICAL
warning: Hsla, // WARNING, WARN
info: Hsla, // INFO, NOTICE
debug: Hsla, // DEBUG, TRACE, DBG
alert: Hsla, // ALERT
// Status indicators
success: Hsla, // SUCCESS, OK, PASS, DONE, COMPLETED
failure: Hsla, // FAILED, FAIL, FAILURE
pending: Hsla, // PENDING, WAITING, PROCESSING
running: Hsla, // RUNNING, ACTIVE, EXECUTING
stopped: Hsla, // STOPPED, INACTIVE, HALTED, IDLE
skipped: Hsla, // SKIPPED, SKIP
success: Hsla, // SUCCESS, OK, PASS, DONE, COMPLETED
failure: Hsla, // FAILED, FAIL, FAILURE
pending: Hsla, // PENDING, WAITING, PROCESSING
running: Hsla, // RUNNING, ACTIVE, EXECUTING
stopped: Hsla, // STOPPED, INACTIVE, HALTED, IDLE
skipped: Hsla, // SKIPPED, SKIP
// Network
network_up: Hsla, // UP, ONLINE, CONNECTED
network_down: Hsla, // DOWN, OFFLINE, UNREACHABLE
timeout: Hsla, // TIMEOUT, TIMED OUT
refused: Hsla, // REFUSED, REJECTED, DENIED
network_up: Hsla, // UP, ONLINE, CONNECTED
network_down: Hsla, // DOWN, OFFLINE, UNREACHABLE
timeout: Hsla, // TIMEOUT, TIMED OUT
refused: Hsla, // REFUSED, REJECTED, DENIED
// Security & Auth
security: Hsla, // SSH, SSL, TLS, CERTIFICATE
auth: Hsla, // AUTHENTICATED, AUTHORIZED, LOGIN
danger: Hsla, // ROOT, SUDO, PASSWORD, SECRET
security: Hsla, // SSH, SSL, TLS, CERTIFICATE
auth: Hsla, // AUTHENTICATED, AUTHORIZED, LOGIN
danger: Hsla, // ROOT, SUDO, PASSWORD, SECRET
// Operations
started: Hsla, // START, BOOT, STARTING
stopped_op: Hsla, // STOP, SHUTDOWN, STOPPING
restart: Hsla, // RESTART, RESTARTING
deploy: Hsla, // DEPLOY, DEPLOYED, DEPLOYMENT
crashed: Hsla, // CRASH, CRASHED, SIGSEGV
started: Hsla, // START, BOOT, STARTING
stopped_op: Hsla, // STOP, SHUTDOWN, STOPPING
restart: Hsla, // RESTART, RESTARTING
deploy: Hsla, // DEPLOY, DEPLOYED, DEPLOYMENT
crashed: Hsla, // CRASH, CRASHED, SIGSEGV
// Resources
memory: Hsla, // MEMORY, RAM, SWAP, HEAP
cpu: Hsla, // CPU, PROCESSOR, CORE
disk: Hsla, // DISK, STORAGE, PARTITION, MOUNT
memory: Hsla, // MEMORY, RAM, SWAP, HEAP
cpu: Hsla, // CPU, PROCESSOR, CORE
disk: Hsla, // DISK, STORAGE, PARTITION, MOUNT
// HTTP codes
http_2xx: Hsla, // 200-299 Success
http_3xx: Hsla, // 300-399 Redirect
http_4xx: Hsla, // 400-499 Client Error
http_5xx: Hsla, // 500-599 Server Error
http_2xx: Hsla, // 200-299 Success
http_3xx: Hsla, // 300-399 Redirect
http_4xx: Hsla, // 400-499 Client Error
http_5xx: Hsla, // 500-599 Server Error
// Dev / Exceptions
exception: Hsla, // Exception, Traceback, Error type
deprecated: Hsla, // DEPRECATED, TODO, FIXME
exception: Hsla, // Exception, Traceback, Error type
deprecated: Hsla, // DEPRECATED, TODO, FIXME
// Existing
network: Hsla, // IP addresses
url: Hsla, // http://, https://
port: Hsla, // :22, :443, etc.
network: Hsla, // IP addresses
url: Hsla, // http://, https://
port: Hsla, // :22, :443, etc.
}
fn hsla(r: u8, g: u8, b: u8) -> Hsla {
@@ -100,58 +109,58 @@ fn hsla(r: u8, g: u8, b: u8) -> Hsla {
fn highlight_colors() -> HighlightColors {
HighlightColors {
// Log levels
error: hsla(224, 96, 96), // #E06060 red
critical: hsla(255, 50, 50), // #FF3232 bright red
warning: hsla(232, 201, 122), // #E8C97A yellow
info: hsla(108, 180, 238), // #6CB4EE blue
debug: hsla(130, 140, 155), // #828C9B gray
alert: hsla(213, 126, 234), // #D57EEA bright magenta
error: hsla(224, 96, 96), // #E06060 red
critical: hsla(255, 50, 50), // #FF3232 bright red
warning: hsla(232, 201, 122), // #E8C97A yellow
info: hsla(108, 180, 238), // #6CB4EE blue
debug: hsla(130, 140, 155), // #828C9B gray
alert: hsla(213, 126, 234), // #D57EEA bright magenta
// Status
success: hsla(126, 198, 153), // #7EC699 green
failure: hsla(232, 168, 124), // #E8A87C orange
pending: hsla(232, 201, 122), // #E8C97A yellow
running: hsla( 86, 206, 234), // #56CEEA cyan
stopped: hsla(160, 165, 175), // #A0A5AF gray
skipped: hsla(199, 146, 234), // #C792EA purple
success: hsla(126, 198, 153), // #7EC699 green
failure: hsla(232, 168, 124), // #E8A87C orange
pending: hsla(232, 201, 122), // #E8C97A yellow
running: hsla(86, 206, 234), // #56CEEA cyan
stopped: hsla(160, 165, 175), // #A0A5AF gray
skipped: hsla(199, 146, 234), // #C792EA purple
// Network
network_up: hsla(126, 198, 153), // #7EC699 green
network_down: hsla(224, 96, 96), // #E06060 red
timeout: hsla(213, 126, 234), // #D57EEA magenta
refused: hsla(245, 160, 80), // #F5A050 orange
network_up: hsla(126, 198, 153), // #7EC699 green
network_down: hsla(224, 96, 96), // #E06060 red
timeout: hsla(213, 126, 234), // #D57EEA magenta
refused: hsla(245, 160, 80), // #F5A050 orange
// Security
security: hsla( 86, 206, 234), // #56CEEA cyan
auth: hsla(126, 198, 153), // #7EC699 green
danger: hsla(180, 50, 50), // #B43232 dark red
security: hsla(86, 206, 234), // #56CEEA cyan
auth: hsla(126, 198, 153), // #7EC699 green
danger: hsla(180, 50, 50), // #B43232 dark red
// Operations
started: hsla(126, 198, 153), // #7EC699 green
stopped_op: hsla(224, 96, 96), // #E06060 red
restart: hsla(232, 201, 122), // #E8C97A yellow
deploy: hsla(100, 210, 140), // #64D28C bright green
crashed: hsla(255, 50, 50), // #FF3232 bright red
started: hsla(126, 198, 153), // #7EC699 green
stopped_op: hsla(224, 96, 96), // #E06060 red
restart: hsla(232, 201, 122), // #E8C97A yellow
deploy: hsla(100, 210, 140), // #64D28C bright green
crashed: hsla(255, 50, 50), // #FF3232 bright red
// Resources
memory: hsla(199, 146, 234), // #C792EA purple
cpu: hsla( 86, 206, 234), // #56CEEA cyan
disk: hsla(108, 180, 238), // #6CB4EE blue
memory: hsla(199, 146, 234), // #C792EA purple
cpu: hsla(86, 206, 234), // #56CEEA cyan
disk: hsla(108, 180, 238), // #6CB4EE blue
// HTTP codes
http_2xx: hsla(126, 198, 153), // #7EC699 green
http_3xx: hsla( 86, 206, 234), // #56CEEA cyan
http_4xx: hsla(232, 201, 122), // #E8C97A yellow
http_5xx: hsla(224, 96, 96), // #E06060 red
http_2xx: hsla(126, 198, 153), // #7EC699 green
http_3xx: hsla(86, 206, 234), // #56CEEA cyan
http_4xx: hsla(232, 201, 122), // #E8C97A yellow
http_5xx: hsla(224, 96, 96), // #E06060 red
// Dev
exception: hsla(224, 96, 96), // #E06060 red
deprecated: hsla(245, 160, 80), // #F5A050 orange
exception: hsla(224, 96, 96), // #E06060 red
deprecated: hsla(245, 160, 80), // #F5A050 orange
// Existing
network: hsla(199, 146, 234), // #C792EA purple
url: hsla( 86, 212, 199), // #56D4C7 teal
port: hsla(130, 170, 200), // #82AAC8 muted teal
network: hsla(199, 146, 234), // #C792EA purple
url: hsla(86, 212, 199), // #56D4C7 teal
port: hsla(130, 170, 200), // #82AAC8 muted teal
}
}
@@ -202,18 +211,40 @@ fn highlight_http_codes(
// Specific HTTP codes to highlight
const HTTP_CODES: &[(u16, bool)] = &[
// 2xx
(200, true), (201, true), (202, true), (204, true), (206, true),
(200, true),
(201, true),
(202, true),
(204, true),
(206, true),
// 3xx
(301, true), (302, true), (304, true), (307, true), (308, true),
(301, true),
(302, true),
(304, true),
(307, true),
(308, true),
// 4xx
(400, true), (401, true), (403, true), (404, true), (405, true),
(408, true), (409, true), (410, true), (422, true), (429, true),
(400, true),
(401, true),
(403, true),
(404, true),
(405, true),
(408, true),
(409, true),
(410, true),
(422, true),
(429, true),
// 5xx
(500, true), (502, true), (503, true), (504, true),
(500, true),
(502, true),
(503, true),
(504, true),
];
for i in 0..len.saturating_sub(2) {
if !bytes[i].is_ascii_digit() || !bytes[i + 1].is_ascii_digit() || !bytes[i + 2].is_ascii_digit() {
if !bytes[i].is_ascii_digit()
|| !bytes[i + 1].is_ascii_digit()
|| !bytes[i + 2].is_ascii_digit()
{
continue;
}
@@ -249,10 +280,7 @@ fn highlight_http_codes(
}
}
pub fn highlight_cells(
cells: &[RenderCell],
rows: usize,
) -> HashMap<(i32, i32), Hsla> {
pub fn highlight_cells(cells: &[RenderCell], rows: usize) -> HashMap<(i32, i32), Hsla> {
let colors = highlight_colors();
let mut row_chars: Vec<Vec<(i32, char)>> = vec![Vec::with_capacity(128); rows];
@@ -289,178 +317,458 @@ pub fn highlight_cells(
let text = chars_buf.as_str();
// ── 1. Critical errors (highest priority) ──────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["PANIC", "EMERGENCY", "FATAL", "SEGFAULT", "CRITICAL",
"OOM", "OUT OF MEMORY", "KERNEL PANIC", "CORE DUMPED", "BUS ERROR"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"PANIC",
"EMERGENCY",
"FATAL",
"SEGFAULT",
"CRITICAL",
"OOM",
"OUT OF MEMORY",
"KERNEL PANIC",
"CORE DUMPED",
"BUS ERROR",
],
colors.critical,
);
// ── 2. Error keywords ──────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["ERROR", "ERR"], colors.error);
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["ERROR", "ERR"],
colors.error,
);
// ── 3. Alert ───────────────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["ALERT"], colors.alert);
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["ALERT"],
colors.alert,
);
// ── 4. Warning keywords ────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["WARNING", "WARN"], colors.warning);
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["WARNING", "WARN"],
colors.warning,
);
// ── 5. Info keywords ───────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["INFO", "INFORMATION", "NOTICE"], colors.info);
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["INFO", "INFORMATION", "NOTICE"],
colors.info,
);
// ── 6. Debug keywords ──────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["DEBUG", "DBG", "TRACE"], colors.debug);
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["DEBUG", "DBG", "TRACE"],
colors.debug,
);
// ── 7. Success status ──────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["SUCCESS", "SUCCEEDED", "SUCCESSFUL", "PASSED", "PASS",
"OK", "DONE", "COMPLETED", "FINISHED", "COMPLETE"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"SUCCESS",
"SUCCEEDED",
"SUCCESSFUL",
"PASSED",
"PASS",
"OK",
"DONE",
"COMPLETED",
"FINISHED",
"COMPLETE",
],
colors.success,
);
// ── 8. Failure status ──────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["FAILED", "FAILURE", "FAIL", "NOT OK"],
colors.failure,
);
// ── 9. Pending / Waiting ───────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["PENDING", "WAITING", "PROCESSING", "IN PROGRESS", "QUEUED"],
colors.pending,
);
// ── 10. Running / Active ───────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["RUNNING", "ACTIVE", "EXECUTING", "IN_PROGRESS", "LIVE"],
colors.running,
);
// ── 11. Stopped / Inactive ─────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["STOPPED", "INACTIVE", "HALTED", "IDLE", "PAUSED"],
colors.stopped,
);
// ── 12. Skipped ────────────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["SKIPPED", "SKIP", "SKIPPING"], colors.skipped);
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["SKIPPED", "SKIP", "SKIPPING"],
colors.skipped,
);
// ── 13. Network UP ─────────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["UP", "ONLINE", "CONNECTED", "REACHABLE", "LISTENING",
"ESTABLISHED", "LINK UP"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"UP",
"ONLINE",
"CONNECTED",
"REACHABLE",
"LISTENING",
"ESTABLISHED",
"LINK UP",
],
colors.network_up,
);
// ── 14. Network DOWN ───────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["DOWN", "OFFLINE", "UNREACHABLE", "DISCONNECTED",
"NOT LISTENING", "LINK DOWN", "NO CARRIER"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"DOWN",
"OFFLINE",
"UNREACHABLE",
"DISCONNECTED",
"NOT LISTENING",
"LINK DOWN",
"NO CARRIER",
],
colors.network_down,
);
// ── 15. Timeout ────────────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["TIMEOUT", "TIMED OUT", "TIMEOUTS", "ETIMEDOUT", "SLOW", "LATENCY"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"TIMEOUT",
"TIMED OUT",
"TIMEOUTS",
"ETIMEDOUT",
"SLOW",
"LATENCY",
],
colors.timeout,
);
// ── 16. Refused / Denied ───────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["REFUSED", "REJECTED", "DENIED", "PERMISSION DENIED",
"ACCESS DENIED", "FORBIDDEN", "BLOCKED", "DROP"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"REFUSED",
"REJECTED",
"DENIED",
"PERMISSION DENIED",
"ACCESS DENIED",
"FORBIDDEN",
"BLOCKED",
"DROP",
],
colors.refused,
);
// ── 17. Security / Protocol ────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["SSH", "SSHD", "SSL", "TLS", "HTTPS", "CERTIFICATE", "CERT",
"FIREWALL", "IPTABLES", "ACL", "WAF"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"SSH",
"SSHD",
"SSL",
"TLS",
"HTTPS",
"CERTIFICATE",
"CERT",
"FIREWALL",
"IPTABLES",
"ACL",
"WAF",
],
colors.security,
);
// ── 18. Authentication ─────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["AUTHENTICATED", "ACCEPTED", "AUTHORIZED", "LOGIN", "LOGOUT",
"LOGGED IN", "LOGGED OUT", "SESSION"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"AUTHENTICATED",
"ACCEPTED",
"AUTHORIZED",
"LOGIN",
"LOGOUT",
"LOGGED IN",
"LOGGED OUT",
"SESSION",
],
colors.auth,
);
// ── 19. Danger (root/sudo/secrets) ─────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["ROOT", "SUDO", "UID=0", "PASSWORD", "SECRET", "TOKEN",
"API_KEY", "APIKEY", "PRIVATE KEY", "CREDENTIALS"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"ROOT",
"SUDO",
"UID=0",
"PASSWORD",
"SECRET",
"TOKEN",
"API_KEY",
"APIKEY",
"PRIVATE KEY",
"CREDENTIALS",
],
colors.danger,
);
// ── 20. Operations: Start ──────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["STARTED", "START", "STARTING", "BOOT", "BOOTING", "LAUNCHED", "LAUNCH"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"STARTED", "START", "STARTING", "BOOT", "BOOTING", "LAUNCHED", "LAUNCH",
],
colors.started,
);
// ── 21. Operations: Stop ───────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["STOPPED", "STOP", "STOPPING", "SHUTDOWN", "SHUTTING DOWN", "TERMINATED"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"STOPPED",
"STOP",
"STOPPING",
"SHUTDOWN",
"SHUTTING DOWN",
"TERMINATED",
],
colors.stopped_op,
);
// ── 22. Operations: Restart ────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["RESTARTED", "RESTART", "RESTARTING", "RELOAD", "RELOADED", "RELOADING"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"RESTARTED",
"RESTART",
"RESTARTING",
"RELOAD",
"RELOADED",
"RELOADING",
],
colors.restart,
);
// ── 23. Operations: Deploy ─────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["DEPLOYED", "DEPLOYMENT", "DEPLOYING", "DEPLOY",
"ROLLBACK", "ROLLED BACK", "ROLLING BACK", "UPGRADE", "UPGRADED"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"DEPLOYED",
"DEPLOYMENT",
"DEPLOYING",
"DEPLOY",
"ROLLBACK",
"ROLLED BACK",
"ROLLING BACK",
"UPGRADE",
"UPGRADED",
],
colors.deploy,
);
// ── 24. Operations: Crash ──────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["CRASH", "CRASHED", "CRASHING", "SIGSEGV", "SIGABRT", "SIGKILL",
"DIED", "EXITED", "EXIT CODE", "CORE DUMP"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"CRASH",
"CRASHED",
"CRASHING",
"SIGSEGV",
"SIGABRT",
"SIGKILL",
"DIED",
"EXITED",
"EXIT CODE",
"CORE DUMP",
],
colors.crashed,
);
// ── 25. Resources: Memory ──────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&["MEMORY", "RAM", "HEAP", "STACK", "SWAP", "MEM"],
colors.memory,
);
// ── 26. Resources: CPU ─────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["CPU", "PROCESSOR", "CORE", "CORES", "THREAD", "THREADS", "LOAD"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"CPU",
"PROCESSOR",
"CORE",
"CORES",
"THREAD",
"THREADS",
"LOAD",
],
colors.cpu,
);
// ── 27. Resources: Disk ────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["DISK", "STORAGE", "PARTITION", "MOUNT", "FILESYSTEM",
"INODE", "IOPS", "READ", "WRITE"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"DISK",
"STORAGE",
"PARTITION",
"MOUNT",
"FILESYSTEM",
"INODE",
"IOPS",
"READ",
"WRITE",
],
colors.disk,
);
// ── 28. Dev: Exceptions ────────────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["EXCEPTION", "TRACEBACK", "THROW", "THROWN", "STACKTRACE",
"TYPEERROR", "VALUEERROR", "KEYERROR", "ATTRIBUTEERROR",
"INDEXERROR", "RUNTIMEERROR", "IOERROR", "OSERROR",
"NULLPOINTER", "NPE", "SEGFAULT"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"EXCEPTION",
"TRACEBACK",
"THROW",
"THROWN",
"STACKTRACE",
"TYPEERROR",
"VALUEERROR",
"KEYERROR",
"ATTRIBUTEERROR",
"INDEXERROR",
"RUNTIMEERROR",
"IOERROR",
"OSERROR",
"NULLPOINTER",
"NPE",
"SEGFAULT",
],
colors.exception,
);
// ── 29. Dev: Deprecated / TODO ─────────────────────────
highlight_keywords(&mut map, text, &byte_to_col, row_i32,
&["DEPRECATED", "TODO", "FIXME", "HACK", "XXX", "WORKAROUND", "TEMPORARY"],
highlight_keywords(
&mut map,
text,
&byte_to_col,
row_i32,
&[
"DEPRECATED",
"TODO",
"FIXME",
"HACK",
"XXX",
"WORKAROUND",
"TEMPORARY",
],
colors.deprecated,
);
@@ -477,8 +785,6 @@ pub fn highlight_cells(
}
}
// ── 33. Port numbers ───────────────────────────────────
for m in find_ports(text) {
let port_len = find_port_len(&text[m..]);
@@ -537,11 +843,7 @@ fn find_ip_len(text: &str) -> usize {
len += 1;
}
if dots == 3 && digits > 0 {
len
} else {
0
}
if dots == 3 && digits > 0 { len } else { 0 }
}
fn find_ip_addresses(text: &str) -> Vec<usize> {
@@ -550,16 +852,12 @@ fn find_ip_addresses(text: &str) -> Vec<usize> {
let len = bytes.len();
for i in 0..len {
if bytes[i].is_ascii_digit()
&& (i == 0 || is_boundary(bytes[i - 1] as char))
{
if bytes[i].is_ascii_digit() && (i == 0 || is_boundary(bytes[i - 1] as char)) {
let remaining = &text[i..];
let ip_len = find_ip_len(remaining);
if ip_len > 0 {
let ip_str = &remaining[..ip_len];
let valid = ip_str
.split('.')
.all(|octet| octet.parse::<u8>().is_ok());
let valid = ip_str.split('.').all(|octet| octet.parse::<u8>().is_ok());
if valid {
positions.push(i);
}
@@ -671,7 +969,9 @@ pub fn build_logical_lines<'a>(cells: &'a [RenderCell], rows: usize) -> Vec<Logi
let wraps_from_prev = row_idx > 0 && {
current_line.as_ref().map_or(false, |line| {
line.row_cells.last().map_or(false, |rc| {
rc.cell.flags.contains(alacritty_terminal::term::cell::Flags::WRAPLINE)
rc.cell
.flags
.contains(alacritty_terminal::term::cell::Flags::WRAPLINE)
})
})
};
@@ -738,4 +1038,3 @@ pub fn find_url_at_cell(
}
None
}
+7 -2
View File
@@ -387,7 +387,12 @@ impl Ashell {
if let Some((row, col, _side)) = self.terminal_grid_point_and_side(event.position) {
if let Some(snapshot) = self.active_snapshot() {
if let Some(active_id) = &self.active_tab {
if let Some((url, url_cells)) = crate::terminal::highlight::find_url_at_cell(&snapshot.cells, snapshot.rows, row, col) {
if let Some((url, url_cells)) = crate::terminal::highlight::find_url_at_cell(
&snapshot.cells,
snapshot.rows,
row,
col,
) {
hovered_url = Some(crate::app::HoveredUrl {
url,
tab_id: active_id.clone(),
@@ -397,7 +402,7 @@ impl Ashell {
}
}
}
if self.hovered_url != hovered_url || self.cmd_ctrl_pressed != cmd_ctrl_pressed {
self.hovered_url = hovered_url;
self.cmd_ctrl_pressed = cmd_ctrl_pressed;
+18 -7
View File
@@ -1,7 +1,7 @@
pub mod custom_blocks;
pub mod element;
pub mod input;
pub mod highlight;
pub mod input;
use std::sync::mpsc::Sender;
@@ -134,7 +134,12 @@ pub struct TerminalTab {
pub rows: u16,
pub backend: std::sync::Arc<std::sync::Mutex<BackendTx>>,
pub scroll_pixel_y: f32,
pub(crate) highlight_cache: std::cell::RefCell<Option<(Vec<RenderCell>, std::collections::HashMap<(i32, i32), gpui::Hsla>)>>,
pub(crate) highlight_cache: std::cell::RefCell<
Option<(
Vec<RenderCell>,
std::collections::HashMap<(i32, i32), gpui::Hsla>,
)>,
>,
}
#[derive(Clone, Copy)]
@@ -348,9 +353,9 @@ impl TerminalTab {
let highlights = if is_enabled {
let mut cache = self.highlight_cache.borrow_mut();
let cache_valid = cache.as_ref().is_some_and(|(cached_cells, _)| {
cached_cells == &cells
});
let cache_valid = cache
.as_ref()
.is_some_and(|(cached_cells, _)| cached_cells == &cells);
if cache_valid {
cache.as_ref().unwrap().1.clone()
} else {
@@ -503,7 +508,10 @@ fn viewport_selection_from_range(
} = selection.as_ref().copied()?;
let top_point = viewport_to_point(display_offset, Point::new(0, Column(0)));
let bottom_point = viewport_to_point(display_offset, Point::new(rows.saturating_sub(1), Column(0)));
let bottom_point = viewport_to_point(
display_offset,
Point::new(rows.saturating_sub(1), Column(0)),
);
let top_line = top_point.line;
let bottom_line = bottom_point.line;
@@ -521,7 +529,10 @@ fn viewport_selection_from_range(
} else if end.line > bottom_line {
Point::new(rows.saturating_sub(1), Column(cols.saturating_sub(1)))
} else {
point_to_viewport(display_offset, end).unwrap_or(Point::new(rows.saturating_sub(1), Column(cols.saturating_sub(1))))
point_to_viewport(display_offset, end).unwrap_or(Point::new(
rows.saturating_sub(1),
Column(cols.saturating_sub(1)),
))
};
Some(ViewportSelection {