mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-10-03 16:00:18 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c65354e8d6 |
@@ -2,7 +2,12 @@ pub(crate) const DEFAULT_COLS: u16 = 100;
|
||||
pub(crate) const DEFAULT_ROWS: u16 = 30;
|
||||
pub(crate) const SIDEBAR_WIDTH: f32 = 306.0;
|
||||
pub(crate) const COLLAPSED_SIDEBAR_WIDTH: f32 = 52.0;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const TAB_BAR_HEIGHT: f32 = 52.0;
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const TERMINAL_PADDING_X: f32 = 32.0;
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const TERMINAL_PADDING_Y: f32 = 32.0;
|
||||
|
||||
pub(crate) const TERMINAL_KEY_CONTEXT: &str = "AshellTerminal";
|
||||
|
||||
+13
-14
@@ -2270,8 +2270,11 @@ impl Ashell {
|
||||
div()
|
||||
.size_full()
|
||||
.on_prepaint(move |bounds, _window, cx| {
|
||||
let _ = view.update(cx, |this, _| {
|
||||
this.terminal_panel_bounds = Some(bounds);
|
||||
let _ = view.update(cx, |this, cx| {
|
||||
if this.terminal_panel_bounds != Some(bounds) {
|
||||
this.terminal_panel_bounds = Some(bounds);
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
})
|
||||
.overflow_hidden()
|
||||
@@ -2320,8 +2323,6 @@ impl Ashell {
|
||||
let Some(snapshot) = snapshot else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
let view = cx.entity();
|
||||
let tab_id_clone = tab_id.clone();
|
||||
let tab_id_clone2 = tab_id.clone();
|
||||
let focus_handle = this.focus_handle.clone();
|
||||
let marked_text = if is_focused {
|
||||
@@ -2336,11 +2337,6 @@ impl Ashell {
|
||||
let mut el = div()
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
.on_prepaint(move |bounds, _window, cx| {
|
||||
let _ = view.update(cx, |this, _| {
|
||||
this.terminal_bounds.insert(tab_id_clone.clone(), bounds);
|
||||
});
|
||||
})
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, _, _, cx| {
|
||||
@@ -2357,6 +2353,7 @@ impl Ashell {
|
||||
font_size,
|
||||
line_height,
|
||||
cell_width,
|
||||
tab_id.to_string(),
|
||||
this.search_highlight_map(
|
||||
tab_id,
|
||||
cx.theme().danger.opacity(0.35),
|
||||
@@ -2433,10 +2430,10 @@ impl Ashell {
|
||||
el = el.opacity(0.85);
|
||||
}
|
||||
|
||||
let mut wrapper = div().size_full();
|
||||
if has_multiple_panes {
|
||||
if is_focused {
|
||||
el = div()
|
||||
.size_full()
|
||||
wrapper = wrapper
|
||||
.relative()
|
||||
.child(
|
||||
div()
|
||||
@@ -2477,11 +2474,13 @@ impl Ashell {
|
||||
.p(px(4.))
|
||||
.child(el);
|
||||
} else {
|
||||
el = div().size_full().p(px(4.)).child(el);
|
||||
wrapper = wrapper.p(px(4.)).child(el);
|
||||
}
|
||||
} else {
|
||||
wrapper = wrapper.child(el);
|
||||
}
|
||||
|
||||
el.into_any_element()
|
||||
wrapper.into_any_element()
|
||||
}
|
||||
PaneLayout::Horizontal(children, ratio) => {
|
||||
v_flex()
|
||||
@@ -2596,7 +2595,7 @@ impl Render for Ashell {
|
||||
self.active_tab = self.tabs.first().map(|tab| tab.id.clone());
|
||||
}
|
||||
self.sync_sftp_path_input(window, cx);
|
||||
self.sync_terminal_size(window, cx);
|
||||
|
||||
if self.show_transfers_dialog {
|
||||
self.show_transfers_dialog = false;
|
||||
self.show_transfers_dialog(window, cx);
|
||||
|
||||
+2
-58
@@ -1,7 +1,7 @@
|
||||
pub mod config;
|
||||
|
||||
use gpui::{
|
||||
App, AppContext as _, Context, Entity, KeyDownEvent, MouseButton, MouseDownEvent,
|
||||
AppContext as _, Context, Entity, KeyDownEvent, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, SharedString, Window, px,
|
||||
};
|
||||
use gpui_component::{Theme, WindowExt as _, input::InputState};
|
||||
@@ -13,8 +13,7 @@ use self::config::{AuthMethod, Session};
|
||||
use crate::{
|
||||
Ashell, PaneLayout, SelectorEntry, TabGroup,
|
||||
app::constants::{
|
||||
DEFAULT_COLS, DEFAULT_ROWS, SIDEBAR_WIDTH, TAB_BAR_HEIGHT, TERMINAL_PADDING_X,
|
||||
TERMINAL_PADDING_Y,
|
||||
DEFAULT_COLS, DEFAULT_ROWS,
|
||||
},
|
||||
backend::{local, ssh},
|
||||
terminal::{BackendCommand, RenderSnapshot, TabKind, TerminalTab},
|
||||
@@ -959,64 +958,9 @@ impl Ashell {
|
||||
format!("{}@{}:{}", session.user, session.host, session.port)
|
||||
}
|
||||
|
||||
pub(crate) fn sync_terminal_size(&mut self, window: &Window, cx: &App) {
|
||||
let viewport = window.viewport_size();
|
||||
let sidebar_width = self
|
||||
.workspace_panels
|
||||
.read(cx)
|
||||
.sizes()
|
||||
.first()
|
||||
.map(|size| size.as_f32())
|
||||
.unwrap_or(SIDEBAR_WIDTH);
|
||||
|
||||
// Use the whole terminal panel bounds for PTY sizing.
|
||||
// Individual pane bounds are smaller when split, so using them here
|
||||
// would shrink the root PTY dimensions a second time.
|
||||
let (width, height) = if let Some(bounds) = self.terminal_panel_bounds {
|
||||
(bounds.size.width.as_f32(), bounds.size.height.as_f32())
|
||||
} else {
|
||||
let terminal_height = self
|
||||
.body_panels
|
||||
.read(cx)
|
||||
.sizes()
|
||||
.first()
|
||||
.map(|size| size.as_f32())
|
||||
.unwrap_or(viewport.height.as_f32() - TAB_BAR_HEIGHT - 248.0);
|
||||
(
|
||||
(viewport.width.as_f32() - sidebar_width - TERMINAL_PADDING_X - 8.0)
|
||||
.max(self.terminal_cell_width()),
|
||||
(terminal_height - TERMINAL_PADDING_Y).max(self.terminal_line_height()),
|
||||
)
|
||||
};
|
||||
let total_cols = (width / self.terminal_cell_width()).floor().max(1.0) as u16;
|
||||
let total_rows = (height / self.terminal_line_height()).floor().max(1.0) as u16;
|
||||
|
||||
Self::resize_pane_tree(&mut self.tabs, &self.pane_root, total_cols, total_rows);
|
||||
}
|
||||
|
||||
fn resize_pane_tree(tabs: &mut [TerminalTab], layout: &PaneLayout, cols: u16, rows: u16) {
|
||||
match layout {
|
||||
PaneLayout::Single(id) => {
|
||||
if let Some(tab) = tabs.iter_mut().find(|t| t.id == *id) {
|
||||
tab.resize(cols.max(1), rows.max(1));
|
||||
}
|
||||
}
|
||||
PaneLayout::Horizontal(children, _) => {
|
||||
let n = children.len() as u16;
|
||||
let each_rows = (rows / n).max(1);
|
||||
for child in children {
|
||||
Self::resize_pane_tree(tabs, child, cols, each_rows);
|
||||
}
|
||||
}
|
||||
PaneLayout::Vertical(children, _) => {
|
||||
let n = children.len() as u16;
|
||||
let each_cols = (cols / n).max(1);
|
||||
for child in children {
|
||||
Self::resize_pane_tree(tabs, child, each_cols, rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn split_current_pane(&mut self, direction: &str, cx: &mut Context<Self>) {
|
||||
tracing::info!(
|
||||
|
||||
+44
-9
@@ -132,6 +132,7 @@ pub struct TerminalElement {
|
||||
font_size: Pixels,
|
||||
line_height: Pixels,
|
||||
cell_width: Pixels,
|
||||
tab_id: String,
|
||||
search_highlights: Option<std::collections::HashMap<(i32, i32), Hsla>>,
|
||||
}
|
||||
|
||||
@@ -271,6 +272,7 @@ impl TerminalElement {
|
||||
font_size: Pixels,
|
||||
line_height: Pixels,
|
||||
cell_width: Pixels,
|
||||
tab_id: String,
|
||||
search_highlights: Option<std::collections::HashMap<(i32, i32), Hsla>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -282,6 +284,7 @@ impl TerminalElement {
|
||||
font_size,
|
||||
line_height,
|
||||
cell_width,
|
||||
tab_id,
|
||||
search_highlights,
|
||||
}
|
||||
}
|
||||
@@ -541,6 +544,32 @@ impl Element for TerminalElement {
|
||||
) -> Self::PrepaintState {
|
||||
let _ = self.base_text_style(cx);
|
||||
let (rects, runs, custom_blocks) = self.layout_grid(cx);
|
||||
|
||||
// Save the precise GPUI-rendered bounds of this terminal element.
|
||||
// This is 100% accurate because it is recorded during layout prepaint.
|
||||
let view = self.view.clone();
|
||||
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();
|
||||
let cell_width = this.terminal_cell_width();
|
||||
let w = bounds.size.width.as_f32();
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
PrepaintState {
|
||||
bounds,
|
||||
metrics: TerminalMetrics {
|
||||
@@ -564,18 +593,24 @@ impl Element for TerminalElement {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
// 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 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);
|
||||
|
||||
for rect in &prepaint.rects {
|
||||
rect.paint(prepaint.bounds.origin, prepaint.metrics, window);
|
||||
rect.paint(draw_origin, prepaint.metrics, window);
|
||||
}
|
||||
|
||||
for run in &prepaint.runs {
|
||||
run.paint(prepaint.bounds.origin, prepaint.metrics, window, cx);
|
||||
run.paint(draw_origin, prepaint.metrics, window, cx);
|
||||
}
|
||||
|
||||
for block in &prepaint.custom_blocks {
|
||||
let x = prepaint.bounds.origin.x.as_f32()
|
||||
let x = draw_origin.x.as_f32()
|
||||
+ block.col as f32 * prepaint.metrics.cell_width.as_f32();
|
||||
let y = prepaint.bounds.origin.y.as_f32()
|
||||
let y = draw_origin.y.as_f32()
|
||||
+ block.row as f32 * prepaint.metrics.line_height.as_f32();
|
||||
paint_custom_block(
|
||||
window,
|
||||
@@ -592,7 +627,7 @@ impl Element for TerminalElement {
|
||||
&self.focus_handle,
|
||||
TerminalInputHandler {
|
||||
view: self.view.clone(),
|
||||
element_bounds: prepaint.bounds,
|
||||
element_bounds: Bounds::new(draw_origin, prepaint.bounds.size),
|
||||
cell_width: prepaint.metrics.cell_width.as_f32(),
|
||||
line_height: prepaint.metrics.line_height.as_f32(),
|
||||
},
|
||||
@@ -602,8 +637,8 @@ impl Element for TerminalElement {
|
||||
if let Some(marked_text) = self.marked_text.as_ref().filter(|text| !text.is_empty()) {
|
||||
if let Some(cursor) = prepaint.cursor {
|
||||
let pos = point(
|
||||
prepaint.bounds.origin.x + prepaint.metrics.cell_width * cursor.col as f32,
|
||||
prepaint.bounds.origin.y + prepaint.metrics.line_height * cursor.row as f32,
|
||||
draw_origin.x + prepaint.metrics.cell_width * cursor.col as f32,
|
||||
draw_origin.y + prepaint.metrics.line_height * cursor.row as f32,
|
||||
);
|
||||
let mut base_style = self.base_text_style(cx);
|
||||
base_style.underline = Some(UnderlineStyle {
|
||||
@@ -650,8 +685,8 @@ impl Element for TerminalElement {
|
||||
{
|
||||
return;
|
||||
}
|
||||
let x = prepaint.bounds.origin.x + prepaint.metrics.cell_width * cursor.col as f32;
|
||||
let y = prepaint.bounds.origin.y + prepaint.metrics.line_height * cursor.row as f32;
|
||||
let x = draw_origin.x + prepaint.metrics.cell_width * cursor.col as f32;
|
||||
let y = draw_origin.y + prepaint.metrics.line_height * cursor.row as f32;
|
||||
match cursor.shape {
|
||||
CursorShape::Hidden => {}
|
||||
CursorShape::Beam => {
|
||||
|
||||
Reference in New Issue
Block a user