fix(app): resolve connection export modal and terminal history glitches (#115)

- Fix CSV connection export confirmation dialog not opening within settings page
- Remove redundant connect button on connection rows in sidebar edit mode
- Enhance terminal syntax highlighting with background color, inverse, dim, and CJK wide char support
- Improve command history tracking accuracy using buffer-relative coordinates and residue filtering
This commit is contained in:
Realm
2026-08-28 03:12:54 +08:00
committed by GitHub
parent b76f9c7375
commit 7b410afea7
6 changed files with 312 additions and 163 deletions
+4 -3
View File
@@ -632,7 +632,8 @@ impl Ashell {
}
pub(crate) fn export_connections(&mut self, window: &mut gpui::Window, cx: &mut Context<Self>) {
if self.active_dialog.is_some() {
let parent_dialog = self.active_dialog;
if parent_dialog.is_some() && parent_dialog != Some(DialogKind::Settings) {
return;
}
@@ -695,7 +696,7 @@ impl Ashell {
let sessions = sessions_for_export.clone();
view_for_ok.update(cx, |this, cx| {
if this.active_dialog == Some(DialogKind::ConnectionExport) {
this.active_dialog = None;
this.active_dialog = parent_dialog;
}
this.start_connection_csv_export(sessions, window, cx);
cx.notify();
@@ -707,7 +708,7 @@ impl Ashell {
move |_, _, cx| {
view.update(cx, |this, cx| {
if this.active_dialog == Some(DialogKind::ConnectionExport) {
this.active_dialog = None;
this.active_dialog = parent_dialog;
}
cx.notify();
});
+1 -30
View File
@@ -3336,7 +3336,6 @@ impl Ashell {
active_session_id: Option<&str>,
cx: &mut Context<Self>,
) -> gpui::AnyElement {
let connect_id = session.id.clone();
let edit_id = session.id.clone();
let delete_id = session.id.clone();
let management_mode = self.connection_management_mode;
@@ -3347,8 +3346,6 @@ impl Ashell {
let selection_id = session.id.clone();
let row_selection_id = selection_id.clone();
let row_id = ElementId::Name(format!("saved-connect-{}", session.id).into());
let connect_button_id =
ElementId::Name(format!("connect-saved-session-{}", session.id).into());
div()
.id(row_id)
@@ -3477,33 +3474,7 @@ impl Ashell {
.text_size(ui_rems(0.75))
.text_color(cx.theme().muted_foreground)
.child(detail),
)
.when(management_mode, |this| {
this.child(
h_flex()
.w(px(24.))
.ml_1()
.flex_none()
.items_center()
.justify_center()
.on_mouse_down(MouseButton::Left, |_, _, cx| {
cx.stop_propagation();
})
.on_mouse_down(MouseButton::Right, |_, _, cx| {
cx.stop_propagation();
})
.child(
pointer_button(connect_button_id)
.ghost()
.small()
.icon(IconName::ExternalLink)
.tooltip(t!("connect").to_string())
.on_click(cx.listener(move |this, _, window, cx| {
this.connect_saved_session(connect_id.clone(), window, cx);
})),
),
)
}),
),
)
.into_any_element()
}
+129 -40
View File
@@ -12,6 +12,7 @@ use gpui_component::ActiveTheme as _;
use crate::Ashell;
use crate::terminal::custom_blocks::{is_custom_block_supported, paint_custom_block};
use crate::terminal::highlight::HighlightStyle;
use crate::terminal::{RenderSnapshot, ViewportSelection};
#[derive(Clone, Copy)]
@@ -338,21 +339,16 @@ impl TerminalElement {
}
}
fn cell_run_style(&self, cell: &alacritty_terminal::term::cell::Cell, cx: &App) -> TextRun {
let mut fg = color_to_hsla(cell.fg, true, cx);
let mut bg = color_to_hsla(cell.bg, false, cx);
if cell.flags.contains(Flags::INVERSE) {
std::mem::swap(&mut fg, &mut bg);
}
if cell.flags.contains(Flags::DIM) {
fg.a *= 0.7;
}
fn cell_run_style(
&self,
cell: &alacritty_terminal::term::cell::Cell,
foreground: Hsla,
) -> TextRun {
let underline = cell
.flags
.intersects(Flags::ALL_UNDERLINES)
.then(|| UnderlineStyle {
color: Some(fg),
color: Some(foreground),
thickness: px(1.0),
wavy: cell.flags.contains(Flags::UNDERCURL),
});
@@ -360,7 +356,7 @@ impl TerminalElement {
.flags
.contains(Flags::STRIKEOUT)
.then(|| StrikethroughStyle {
color: Some(fg),
color: Some(foreground),
thickness: px(1.0),
});
@@ -377,7 +373,7 @@ impl TerminalElement {
TextRun {
len: cell.c.len_utf8(),
color: fg,
color: foreground,
background_color: None,
font: Font {
family: self.font_family.clone(),
@@ -408,12 +404,6 @@ impl TerminalElement {
let mut underlines = Vec::new();
let mut current_run: Option<BatchedTextRun> = None;
// Retrieve cached keyword highlights and merge with search highlights
let mut highlights = self.snapshot.highlights.clone();
if let Some(sm) = self.search_highlights.as_ref() {
highlights.extend(sm.iter().map(|(k, v)| (*k, *v)));
}
for render_cell in &self.snapshot.cells {
let cell = &render_cell.cell;
if cell.flags.intersects(
@@ -425,24 +415,54 @@ impl TerminalElement {
let selected = self.snapshot.selection.is_some_and(|selection| {
selection_contains(selection, render_cell.row, render_cell.col)
});
let bg = color_to_hsla(cell.bg, false, cx);
if selected || !is_default_bg(cell.bg) || cell.flags.contains(Flags::INVERSE) {
let cell_position = (render_cell.row, render_cell.col);
let keyword_highlight = self.snapshot.highlights.get(&cell_position).copied();
let search_highlight = self
.search_highlights
.as_ref()
.and_then(|highlights| highlights.get(&cell_position))
.copied();
let (foreground, background) = resolve_cell_colors(
color_to_hsla(cell.fg, true, cx),
color_to_hsla(cell.bg, false, cx),
keyword_highlight,
cell.flags,
);
let cell_span = if cell.flags.contains(Flags::WIDE_CHAR) {
2
} else {
1
};
if selected {
rects.push(LayoutRect {
row: render_cell.row,
col: render_cell.col,
cells: if cell.flags.contains(Flags::WIDE_CHAR) {
2
} else {
1
},
color: if selected {
cx.theme().selection
} else if cell.flags.contains(Flags::INVERSE) {
color_to_hsla(cell.fg, true, cx)
} else {
bg
},
cells: cell_span,
color: cx.theme().selection,
});
} else {
let has_highlight_background =
keyword_highlight.is_some_and(|style| style.background.is_some());
if !is_default_bg(cell.bg)
|| cell.flags.contains(Flags::INVERSE)
|| has_highlight_background
{
rects.push(LayoutRect {
row: render_cell.row,
col: render_cell.col,
cells: cell_span,
color: background,
});
}
if let Some(search_background) = search_highlight {
rects.push(LayoutRect {
row: render_cell.row,
col: render_cell.col,
cells: cell_span,
color: search_background,
});
}
}
if is_blank(cell) {
@@ -452,12 +472,7 @@ impl TerminalElement {
continue;
}
let mut style = self.cell_run_style(cell, cx);
// Apply keyword highlight color if this cell was matched.
if let Some(&hl_color) = highlights.get(&(render_cell.row, render_cell.col)) {
style.color = hl_color;
}
let style = self.cell_run_style(cell, foreground);
// Apply hover underline if mouse is hovering over this URL
if let Some(hu) = &hovered_url {
@@ -871,6 +886,31 @@ fn selection_contains(selection: ViewportSelection, row: i32, col: i32) -> bool
after_start && before_end
}
fn resolve_cell_colors(
mut foreground: Hsla,
mut background: Hsla,
highlight: Option<HighlightStyle>,
flags: Flags,
) -> (Hsla, Hsla) {
if let Some(highlight) = highlight {
if let Some(highlight_foreground) = highlight.foreground {
foreground = highlight_foreground;
}
if let Some(highlight_background) = highlight.background {
background = highlight_background;
}
}
if flags.contains(Flags::INVERSE) {
std::mem::swap(&mut foreground, &mut background);
}
if flags.contains(Flags::DIM) {
foreground.a *= 0.7;
}
(foreground, background)
}
fn is_blank(cell: &alacritty_terminal::term::cell::Cell) -> bool {
cell.c == ' '
&& cell.zerowidth().is_none()
@@ -965,7 +1005,14 @@ fn named_color(named: NamedColor, _foreground: bool, cx: &App) -> Hsla {
#[cfg(test)]
mod tests {
use super::should_render_cursor;
use super::{resolve_cell_colors, should_render_cursor};
use crate::terminal::highlight::HighlightStyle;
use alacritty_terminal::term::cell::Flags;
use gpui::Hsla;
fn color(h: f32, s: f32, l: f32) -> Hsla {
Hsla { h, s, l, a: 1.0 }
}
#[test]
fn renders_cursor_only_for_focused_pane_in_focused_terminal_window() {
@@ -974,4 +1021,46 @@ mod tests {
assert!(!should_render_cursor(true, false, true));
assert!(!should_render_cursor(true, true, false));
}
#[test]
fn applies_highlight_channels_before_inverse_terminal_style() {
let original_foreground = color(0.0, 0.0, 0.8);
let original_background = color(0.0, 0.0, 0.1);
let highlight_foreground = color(0.1, 0.8, 0.5);
let highlight_background = color(0.6, 0.8, 0.4);
let (foreground, background) = resolve_cell_colors(
original_foreground,
original_background,
Some(HighlightStyle {
foreground: Some(highlight_foreground),
background: Some(highlight_background),
}),
Flags::INVERSE,
);
assert_eq!(foreground, highlight_background);
assert_eq!(background, highlight_foreground);
}
#[test]
fn foreground_highlight_preserves_background_and_dim_style() {
let original_foreground = color(0.0, 0.0, 0.8);
let original_background = color(0.0, 0.0, 0.1);
let highlight_foreground = color(0.1, 0.8, 0.5);
let (foreground, background) = resolve_cell_colors(
original_foreground,
original_background,
Some(HighlightStyle {
foreground: Some(highlight_foreground),
background: None,
}),
Flags::DIM,
);
assert_eq!(foreground.alpha(1.0), highlight_foreground);
assert!((foreground.a - 0.7).abs() < f32::EPSILON);
assert_eq!(background, original_background);
}
}
+76 -63
View File
@@ -1,41 +1,19 @@
use crate::terminal::RenderCell;
use gpui::Hsla;
use gpui::{Hsla, Rgba};
use std::collections::HashMap;
trait HslaExt {
fn into_rgba_like(self, r: u8, g: u8, b: u8) -> Self;
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct HighlightStyle {
pub foreground: Option<Hsla>,
pub background: Option<Hsla>,
}
impl HslaExt for Hsla {
fn into_rgba_like(self, r: u8, g: u8, b: u8) -> Self {
let rf = r as f32 / 255.0;
let gf = g as f32 / 255.0;
let bf = b as f32 / 255.0;
let max = rf.max(gf).max(bf);
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,
};
impl HighlightStyle {
fn from_foreground(color: Hsla) -> Self {
Self {
foreground: Some(color),
background: None,
}
let 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 {
((bf - rf) / d + 2.0) / 6.0
} else {
((rf - gf) / d + 4.0) / 6.0
};
Hsla { h, s, l, a: 1.0 }
}
}
@@ -97,13 +75,12 @@ struct HighlightColors {
}
fn hsla(r: u8, g: u8, b: u8) -> Hsla {
Hsla {
h: 0.0,
s: 0.0,
l: 0.0,
Hsla::from(Rgba {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: 1.0,
}
.into_rgba_like(r, g, b)
})
}
fn highlight_colors() -> HighlightColors {
@@ -168,7 +145,7 @@ fn highlight_colors() -> HighlightColors {
struct KeywordMatch {
start_col: i32,
end_col: i32,
color: Hsla,
style: HighlightStyle,
priority: usize,
}
@@ -184,7 +161,7 @@ impl KeywordMatch {
#[derive(Default)]
struct KeywordHighlights {
colors: HashMap<(i32, i32), Hsla>,
styles: HashMap<(i32, i32), HighlightStyle>,
pending: Vec<KeywordMatch>,
next_priority: usize,
}
@@ -214,7 +191,7 @@ impl KeywordHighlights {
for candidate in selected {
for col in candidate.start_col..=candidate.end_col {
self.colors.insert((row_i32, col), candidate.color);
self.styles.insert((row_i32, col), candidate.style);
}
}
}
@@ -262,7 +239,7 @@ fn highlight_keywords(
map.pending.push(KeywordMatch {
start_col,
end_col,
color,
style: HighlightStyle::from_foreground(color),
priority,
});
start = abs + kw.len();
@@ -276,7 +253,7 @@ fn highlight_keywords(
/// Highlight HTTP status codes (200, 301, 404, 500, etc.)
/// Only matches specific common HTTP codes, not all 3-digit numbers.
fn highlight_http_codes(
map: &mut HashMap<(i32, i32), Hsla>,
map: &mut HashMap<(i32, i32), HighlightStyle>,
text: &str,
byte_to_col: &[i32],
row_i32: i32,
@@ -352,12 +329,13 @@ fn highlight_http_codes(
let start_col = byte_to_col[i];
let end_col = byte_to_col[(i + 2).min(byte_to_col.len() - 1)];
for c in start_col..=end_col {
map.entry((row_i32, c)).or_insert(color);
map.entry((row_i32, c))
.or_insert_with(|| HighlightStyle::from_foreground(color));
}
}
}
pub fn highlight_cells(cells: &[RenderCell], rows: usize) -> HashMap<(i32, i32), Hsla> {
pub fn highlight_cells(cells: &[RenderCell], rows: usize) -> HashMap<(i32, i32), HighlightStyle> {
let colors = highlight_colors();
let mut row_chars: Vec<Vec<(i32, char)>> = vec![Vec::with_capacity(128); rows];
@@ -365,7 +343,9 @@ pub fn highlight_cells(cells: &[RenderCell], rows: usize) -> HashMap<(i32, i32),
if rc.row < 0 || (rc.row as usize) >= rows {
continue;
}
row_chars[rc.row as usize].push((rc.col, rc.cell.c));
if let Some(character) = rendered_text_character(&rc.cell) {
row_chars[rc.row as usize].push((rc.col, character));
}
}
for row in row_chars.iter_mut() {
row.sort_by_key(|&(col, _)| col);
@@ -852,7 +832,7 @@ pub fn highlight_cells(cells: &[RenderCell], rows: usize) -> HashMap<(i32, i32),
map.apply_row(row_i32);
// ── 30. HTTP status codes ──────────────────────────────
highlight_http_codes(&mut map.colors, text, &byte_to_col, row_i32, &colors);
highlight_http_codes(&mut map.styles, text, &byte_to_col, row_i32, &colors);
// ── 31. IP addresses ───────────────────────────────────
for m in find_ip_addresses(text) {
@@ -860,7 +840,9 @@ pub fn highlight_cells(cells: &[RenderCell], rows: usize) -> HashMap<(i32, i32),
let start_col = byte_to_col[m];
let end_col = byte_to_col[(m + ip_len - 1).min(byte_to_col.len() - 1)];
for c in start_col..=end_col {
map.colors.entry((row_i32, c)).or_insert(colors.network);
map.styles
.entry((row_i32, c))
.or_insert_with(|| HighlightStyle::from_foreground(colors.network));
}
}
@@ -870,7 +852,9 @@ pub fn highlight_cells(cells: &[RenderCell], rows: usize) -> HashMap<(i32, i32),
let start_col = byte_to_col[m];
let end_col = byte_to_col[(m + port_len - 1).min(byte_to_col.len() - 1)];
for c in start_col..=end_col {
map.colors.entry((row_i32, c)).or_insert(colors.port);
map.styles
.entry((row_i32, c))
.or_insert_with(|| HighlightStyle::from_foreground(colors.port));
}
}
}
@@ -884,13 +868,15 @@ pub fn highlight_cells(cells: &[RenderCell], rows: usize) -> HashMap<(i32, i32),
let idx = m + i;
if idx < line.byte_to_cell.len() {
let (r, c) = line.byte_to_cell[idx];
map.colors.entry((r as i32, c as i32)).or_insert(colors.url);
map.styles
.entry((r as i32, c as i32))
.or_insert_with(|| HighlightStyle::from_foreground(colors.url));
}
}
}
}
map.colors
map.styles
}
fn find_ip_len(text: &str) -> usize {
@@ -1090,10 +1076,12 @@ pub fn build_logical_lines<'a>(cells: &'a [RenderCell], rows: usize) -> Vec<Logi
});
for rc in row_cells {
line.text.push(rc.cell.c);
let end_len = line.text.len();
while line.byte_to_cell.len() < end_len {
line.byte_to_cell.push((rc.row as usize, rc.col as usize));
if let Some(character) = rendered_text_character(&rc.cell) {
line.text.push(character);
let end_len = line.text.len();
while line.byte_to_cell.len() < end_len {
line.byte_to_cell.push((rc.row as usize, rc.col as usize));
}
}
line.row_cells.push(rc);
}
@@ -1108,6 +1096,21 @@ pub fn build_logical_lines<'a>(cells: &'a [RenderCell], rows: usize) -> Vec<Logi
logical_lines
}
fn rendered_text_character(cell: &alacritty_terminal::term::cell::Cell) -> Option<char> {
use alacritty_terminal::term::cell::Flags;
if cell
.flags
.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER)
{
None
} else if cell.flags.contains(Flags::HIDDEN) {
Some(' ')
} else {
Some(cell.c)
}
}
pub fn find_url_at_cell(
cells: &[RenderCell],
rows: usize,
@@ -1142,7 +1145,7 @@ pub fn find_url_at_cell(
#[cfg(test)]
mod tests {
use super::{KeywordHighlights, KeywordMatch, find_url_len, hsla};
use super::{HighlightStyle, KeywordHighlights, KeywordMatch, find_url_len, hsla};
fn detected_url(text: &str) -> &str {
&text[..find_url_len(text)]
@@ -1181,21 +1184,26 @@ mod tests {
KeywordMatch {
start_col: 3,
end_col: 11,
color: short_color,
style: HighlightStyle::from_foreground(short_color),
priority: 0,
},
KeywordMatch {
start_col: 0,
end_col: 11,
color: long_color,
style: HighlightStyle::from_foreground(long_color),
priority: 1,
},
];
highlights.apply_row(0);
assert_eq!(highlights.colors.len(), 12);
assert!(highlights.colors.values().all(|color| *color == long_color));
assert_eq!(highlights.styles.len(), 12);
assert!(
highlights
.styles
.values()
.all(|style| style.foreground == Some(long_color) && style.background.is_none())
);
}
#[test]
@@ -1208,8 +1216,13 @@ mod tests {
super::highlight_keywords(&mut highlights, text, &byte_to_col, 0, &["BOOT"], color);
highlights.apply_row(0);
assert_eq!(highlights.colors.len(), 4);
assert!((0..9).all(|col| !highlights.colors.contains_key(&(0, col))));
assert!((10..14).all(|col| highlights.colors.get(&(0, col)) == Some(&color)));
assert_eq!(highlights.styles.len(), 4);
assert!((0..9).all(|col| !highlights.styles.contains_key(&(0, col))));
assert!((10..14).all(|col| {
highlights
.styles
.get(&(0, col))
.is_some_and(|style| style.foreground == Some(color) && style.background.is_none())
}));
}
}
+99 -24
View File
@@ -345,7 +345,7 @@ impl Ashell {
};
(
session.id.clone(),
tab.cursor_state().map(|cursor| (cursor.row, cursor.col)),
tab.buffer_cursor_position(),
tab.is_alternate_screen_active(),
)
};
@@ -1284,6 +1284,11 @@ fn terminal_command_text(
start: (usize, usize),
end: Option<(usize, usize)>,
) -> Option<String> {
let start = buffer_position_in_viewport(snapshot, start)?;
let end = match end {
Some(position) => Some(buffer_position_in_viewport(snapshot, position)?),
None => None,
};
let logical_lines =
crate::terminal::highlight::build_logical_lines(&snapshot.cells, snapshot.rows);
for line in logical_lines {
@@ -1315,42 +1320,38 @@ fn terminal_command_text(
None
}
fn buffer_position_in_viewport(
snapshot: &crate::terminal::RenderSnapshot,
position: (usize, usize),
) -> Option<(usize, usize)> {
let viewport_start = snapshot
.history_size
.saturating_sub(snapshot.display_offset);
let row = position.0.checked_sub(viewport_start)?;
(row < snapshot.rows && position.1 < snapshot.cols).then_some((row, position.1))
}
fn command_history_text(rendered: Option<&str>, buffered: &str, input_uncertain: bool) -> String {
let rendered = rendered.unwrap_or_default().trim();
let buffered = buffered.trim();
if !input_uncertain && !buffered.is_empty() {
return buffered.to_string();
}
merge_command_text(rendered, buffered)
}
fn merge_command_text(rendered: Option<&str>, buffered: &str) -> String {
let rendered = rendered.unwrap_or_default().trim();
let buffered = buffered.trim();
if rendered.is_empty() {
return buffered.to_string();
}
if buffered.is_empty() {
return rendered.to_string();
}
if rendered.starts_with(buffered) || rendered.ends_with(buffered) {
return rendered.to_string();
}
if buffered.starts_with(rendered) || buffered.ends_with(rendered) {
// Completion extends the current token; a new argument after exact raw input is stale content.
if rendered
.strip_prefix(buffered)
.and_then(|suffix| suffix.chars().next())
.is_some_and(char::is_whitespace)
{
return buffered.to_string();
}
let overlap = buffered
.char_indices()
.map(|(index, _)| index)
.chain(std::iter::once(buffered.len()))
.filter(|index| *index > 0 && rendered.ends_with(&buffered[..*index]))
.max()
.unwrap_or(0);
if overlap > 0 {
format!("{rendered}{}", &buffered[overlap..])
} else {
rendered.to_string()
}
rendered.to_string()
}
#[cfg(test)]
@@ -1581,6 +1582,58 @@ mod tests {
);
}
#[test]
fn rejects_whitespace_delimited_screen_suffix_when_input_is_uncertain() {
let command = "sh /site/jimureport/jimureport-restart.sh";
let rendered = format!("{command} /sijiji-r");
assert_eq!(
command_history_text(Some(rendered.as_str()), command, true),
command
);
}
#[test]
fn does_not_append_tab_completion_keystroke_fragments() {
let command = "sh /site/jimureport/jimureport-restart.sh";
let buffered = "sh /sijiji-r";
assert_eq!(command_history_text(Some(command), buffered, true), command);
}
#[test]
fn falls_back_to_raw_input_when_uncertain_screen_text_is_unavailable() {
let command = "sh /site/jimureport/jimureport-restart.sh";
assert_eq!(command_history_text(None, command, true), command);
}
#[test]
fn keeps_screen_completion_that_extends_the_current_argument() {
let buffered = "sh /site/jimu";
let rendered = "sh /site/jimureport/jimureport-restart.sh";
assert_eq!(
command_history_text(Some(rendered), buffered, true),
rendered
);
}
#[test]
fn excludes_hidden_screen_residue_from_rendered_command() {
let command = "sh /site/jimureport/jimureport-restart.sh";
let rendered = format!("$ {command} /sijiji-r");
let mut snapshot = snapshot(&[rendered.as_str()], rendered.len());
for cell in snapshot.cells.iter_mut().skip(2 + command.chars().count()) {
cell.cell.flags.insert(Flags::HIDDEN);
}
assert_eq!(
terminal_command_text(&snapshot, (0, 2), None),
Some(command.to_string())
);
}
#[test]
fn truncates_rendered_command_at_the_submission_cursor() {
let command = "sh /site/vocano/vocano-restart.sh";
@@ -1592,4 +1645,26 @@ mod tests {
Some(command.to_string())
);
}
#[test]
fn keeps_command_coordinates_stable_when_wrapping_scrolls_the_viewport() {
let first_row = "$ sh /site/jimureport/jimureport-";
let second_row = "restart.sh /sijiji-r";
let mut snapshot = snapshot(&[first_row, second_row], first_row.len());
snapshot.history_size = 3;
snapshot.display_offset = 2;
snapshot
.cells
.iter_mut()
.find(|cell| cell.row == 0 && cell.col == first_row.len() as i32 - 1)
.unwrap()
.cell
.flags
.insert(Flags::WRAPLINE);
assert_eq!(
terminal_command_text(&snapshot, (1, 2), Some((2, "restart.sh".len()))),
Some("sh /site/jimureport/jimureport-restart.sh".to_string())
);
}
}
+3 -3
View File
@@ -887,7 +887,7 @@ pub struct TerminalTab {
type HighlightCache = std::cell::RefCell<
Option<(
Vec<RenderCell>,
std::collections::HashMap<(i32, i32), gpui::Hsla>,
std::collections::HashMap<(i32, i32), self::highlight::HighlightStyle>,
)>,
>;
@@ -940,7 +940,7 @@ pub struct RenderSnapshot {
pub history_size: usize,
pub rows: usize,
pub cols: usize,
pub highlights: std::collections::HashMap<(i32, i32), gpui::Hsla>,
pub highlights: std::collections::HashMap<(i32, i32), self::highlight::HighlightStyle>,
}
#[derive(Clone, Copy)]
@@ -2136,7 +2136,7 @@ impl TerminalTab {
let _ = self.cursor_state_for_click_at(now);
}
fn buffer_cursor_position(&self) -> Option<(usize, usize)> {
pub(crate) fn buffer_cursor_position(&self) -> Option<(usize, usize)> {
let grid = self.term.grid();
let row = usize::try_from(grid.cursor.point.line.0).ok()?;
Some((