From 13e2b0887389fbd26e411c0cbf8c96db282134ab Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 25 Sep 2025 22:21:09 +0800 Subject: [PATCH] editor: Improve line number width by max lines. (#1291) --- crates/story/examples/markdown.rs | 22 ++------ crates/ui/src/input/element.rs | 85 +++++++++++++++++++------------ 2 files changed, 57 insertions(+), 50 deletions(-) diff --git a/crates/story/examples/markdown.rs b/crates/story/examples/markdown.rs index b048fe3c..85d44c86 100644 --- a/crates/story/examples/markdown.rs +++ b/crates/story/examples/markdown.rs @@ -1,10 +1,9 @@ use gpui::*; use gpui_component::{ - highlighter::{HighlightTheme, Language}, + highlighter::Language, input::{InputEvent, InputState, TabSize, TextInput}, resizable::{h_resizable, resizable_panel, ResizableState}, - text::{TextView, TextViewStyle}, - ActiveTheme as _, + text::TextView, }; use story::Assets; @@ -48,14 +47,6 @@ impl Example { impl Render for Example { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - let theme = if cx.theme().mode.is_dark() { - HighlightTheme::default_dark() - } else { - HighlightTheme::default_light() - }; - - let is_dark = cx.theme().mode.is_dark(); - h_resizable("container", self.resizable_state.clone()) .child( resizable_panel().child( @@ -68,7 +59,7 @@ impl Render for Example { TextInput::new(&self.input_state) .h_full() .p_0() - .appearance(false) + .border_0() .focus_bordered(false), ), ), @@ -87,12 +78,7 @@ impl Render for Example { window, cx, ) - .selectable() - .style(TextViewStyle { - highlight_theme: theme.clone(), - is_dark, - ..Default::default() - }), + .selectable(), ), ), ) diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 435eb905..4f3e9233 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -4,7 +4,7 @@ use gpui::{ fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler, Entity, GlobalElementId, Half, HighlightStyle, Hitbox, IntoElement, LayoutId, MouseButton, MouseMoveEvent, Path, Pixels, Point, ShapedLine, SharedString, Size, Style, TextAlign, TextRun, - UnderlineStyle, Window, + TextStyle, UnderlineStyle, Window, }; use ropey::Rope; use smallvec::SmallVec; @@ -483,6 +483,45 @@ impl TextElement { (visible_range, visible_top) } + /// Return (line_number_width, line_number_len) + fn layout_line_numbers( + state: &InputState, + text: &Rope, + font_size: Pixels, + style: &TextStyle, + window: &mut Window, + ) -> (Pixels, usize) { + let total_lines = text.lines_len(); + let line_number_len = match total_lines { + 0..=9999 => 5, + 10000..=99999 => 6, + 100000..=999999 => 7, + _ => 8, + }; + + let line_number_width = if state.mode.line_number() { + let empty_line_number = window.text_system().shape_line( + "+".repeat(line_number_len).into(), + font_size, + &[TextRun { + len: line_number_len, + font: style.font(), + color: gpui::black(), + background_color: None, + underline: None, + strikethrough: None, + }], + None, + ); + + empty_line_number.width + px(6.) + LINE_NUMBER_RIGHT_MARGIN + } else { + px(0.) + }; + + (line_number_width, line_number_len) + } + /// First usize is the offset of skipped. fn highlight_lines( &mut self, @@ -670,39 +709,23 @@ impl Element for TextElement { let (display_text, text_color) = if is_empty { ( - Rope::from(placeholder.as_str()), + &Rope::from(placeholder.as_str()), cx.theme().muted_foreground, ) } else if state.masked { ( - Rope::from("*".repeat(text.chars().count())), + &Rope::from("*".repeat(text.chars().count())), cx.theme().foreground, ) } else { - (text.clone(), cx.theme().foreground) + (&text, cx.theme().foreground) }; let text_style = window.text_style(); // Calculate the width of the line numbers - let empty_line_number = window.text_system().shape_line( - "++++++".into(), - font_size, - &[TextRun { - len: 6, - font: style.font(), - color: gpui::black(), - background_color: None, - underline: None, - strikethrough: None, - }], - None, - ); - let line_number_width = if state.mode.line_number() { - empty_line_number.width + px(6.) + LINE_NUMBER_RIGHT_MARGIN - } else { - px(0.) - }; + let (line_number_width, line_number_len) = + Self::layout_line_numbers(&state, &text, font_size, &text_style, window); let run = TextRun { len: display_text.len(), @@ -881,9 +904,8 @@ impl Element for TextElement { let state = self.state.read(cx); let line_numbers = if state.mode.line_number() { let mut line_numbers = vec![]; - let run_len = 6; let other_line_runs = vec![TextRun { - len: run_len, + len: line_number_len, font: style.font(), color: cx.theme().muted_foreground, background_color: None, @@ -891,7 +913,7 @@ impl Element for TextElement { strikethrough: None, }]; let current_line_runs = vec![TextRun { - len: run_len, + len: line_number_len, font: style.font(), color: cx.theme().foreground, background_color: None, @@ -902,7 +924,7 @@ impl Element for TextElement { // build line numbers for (ix, line) in last_layout.lines.iter().enumerate() { let ix = last_layout.visible_range.start + ix; - let line_no_text = format!("{:>6}", ix + 1); + let line_no = format!("{:>width$}", ix + 1, width = line_number_len).into(); let runs = if current_row == Some(ix) { ¤t_line_runs @@ -911,12 +933,11 @@ impl Element for TextElement { }; let mut sub_lines: SmallVec<[ShapedLine; 1]> = SmallVec::new(); - sub_lines.push(window.text_system().shape_line( - line_no_text.into(), - font_size, - &runs, - None, - )); + sub_lines.push( + window + .text_system() + .shape_line(line_no, font_size, &runs, None), + ); for _ in 0..line.wrap_boundaries.len() { sub_lines.push(ShapedLine::default()); }