diff --git a/crates/ui/src/highlighter/highlight.rs b/crates/ui/src/highlighter/highlight.rs index fe82e4bd..d978fd4e 100644 --- a/crates/ui/src/highlighter/highlight.rs +++ b/crates/ui/src/highlighter/highlight.rs @@ -52,6 +52,10 @@ impl HighlightTheme { inner: Arc::new(theme), }) } + + pub fn settings(&self) -> &highlighting::ThemeSettings { + &self.inner.settings + } } /// Inspired by the `iced` crate's `Highlighter` struct. @@ -59,6 +63,7 @@ impl HighlightTheme { /// https://github.com/iced-rs/iced/blob/master/highlighter/src/lib.rs#L24 pub struct Highlighter<'a> { syntax: &'static parsing::SyntaxReference, + pub(crate) theme: &'a HighlightTheme, highlighter: highlighting::Highlighter<'a>, } @@ -71,6 +76,7 @@ impl<'a> Highlighter<'a> { Self { syntax, + theme, highlighter, } } @@ -104,7 +110,7 @@ impl<'a> Highlighter<'a> { } } -fn color_to_hsla(color: highlighting::Color) -> Hsla { +pub fn color_to_hsla(color: highlighting::Color) -> Hsla { gpui::Rgba { r: color.r as f32 / 255., g: color.g as f32 / 255., diff --git a/crates/ui/src/input/code_highlighter.rs b/crates/ui/src/input/code_highlighter.rs index 4d01f208..4e783d69 100644 --- a/crates/ui/src/input/code_highlighter.rs +++ b/crates/ui/src/input/code_highlighter.rs @@ -22,7 +22,7 @@ impl LineHighlightStyle { #[derive(Clone)] pub(super) struct CodeHighlighter { - highlighter: Rc>, + pub(super) highlighter: Rc>, pub(super) text: SharedString, /// The lines by split \n pub(super) lines: Vec, diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 939aaa7a..84e560ec 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use gpui::{ fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler, Entity, GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path, @@ -54,10 +56,11 @@ impl TextElement { line_number_width: Pixels, window: &mut Window, cx: &mut App, - ) -> (Option, Point) { + ) -> (Option, Point, usize) { let input = self.input.read(cx); let selected_range = &input.selected_range; let cursor_offset = input.cursor_offset(); + let mut current_line_index = 0; let mut scroll_offset = input.scroll_handle.offset(); let mut cursor = None; @@ -172,9 +175,12 @@ impl TextElement { cx.theme().caret, )) }; + + // Calculate the current line index + current_line_index = (cursor_pos.y.0 / line_height.0) as usize; } - (cursor, scroll_offset) + (cursor, scroll_offset, current_line_index) } fn layout_selections( @@ -316,6 +322,13 @@ impl TextElement { _ => None, }) } + + fn highlighter(&self, cx: &App) -> Option>> { + match &self.input.read(cx).mode { + InputMode::CodeEditor { highlighter, .. } => Some(highlighter.highlighter.clone()), + _ => None, + } + } } pub(super) struct PrepaintState { @@ -324,6 +337,7 @@ pub(super) struct PrepaintState { line_number_width: Pixels, cursor: Option, cursor_scroll_offset: Point, + current_line_index: usize, selection_path: Option>, bounds: Bounds, } @@ -438,7 +452,8 @@ impl Element for TextElement { let mut line_numbers = SmallVec::new(); let total_lines = input.text_wrapper.lines.len(); let run_len = if total_lines > 999 { 4 } else { 3 }; - let runs = vec![TextRun { + + let other_line_runs = vec![TextRun { len: run_len, font: style.font(), color: cx.theme().muted_foreground, @@ -446,6 +461,14 @@ impl Element for TextElement { underline: None, strikethrough: None, }]; + let current_line_runs = vec![TextRun { + len: run_len, + font: style.font(), + color: cx.theme().foreground, + background_color: None, + underline: None, + strikethrough: None, + }]; for (i, line_wrap) in input.text_wrapper.lines.iter().enumerate() { let line_no = if run_len == 4 { @@ -454,6 +477,12 @@ impl Element for TextElement { format!("{:>3}", i + 1).into() }; + let runs = if input.current_line_index == Some(i) { + ¤t_line_runs + } else { + &other_line_runs + }; + let line = window .text_system() .shape_text(line_no, font_size, &runs, None, None) @@ -564,7 +593,7 @@ impl Element for TextElement { // Calculate the scroll offset to keep the cursor in view - let (cursor, cursor_scroll_offset) = self.layout_cursor( + let (cursor, cursor_scroll_offset, current_line_index) = self.layout_cursor( &lines, line_height, &mut bounds, @@ -589,6 +618,7 @@ impl Element for TextElement { line_number_width, cursor, cursor_scroll_offset, + current_line_index, selection_path, } } @@ -657,10 +687,24 @@ impl Element for TextElement { } if let Some(line_numbers) = prepaint.line_numbers.as_ref() { - for line in line_numbers.iter() { + for (ix, line) in line_numbers.iter().enumerate() { let p = point(origin.x, origin.y + offset_y); - _ = line.paint(p, line_height, TextAlign::Left, None, window, cx); let line_size = line.size(line_height); + // Paint the current line background + if prepaint.current_line_index == ix { + if let Some(bg_color) = self + .highlighter(cx) + .and_then(|h| h.theme.settings().line_highlight) + .map(crate::highlighter::color_to_hsla) + { + window.paint_quad(fill( + Bounds::new(p, size(bounds.size.width, line_height)), + bg_color, + )); + } + } + + _ = line.paint(p, line_height, TextAlign::Left, None, window, cx); offset_y += line_size.height; } } @@ -668,8 +712,9 @@ impl Element for TextElement { let mut offset_y = px(0.); for line in prepaint.lines.iter() { let p = point(origin.x + prepaint.line_number_width, origin.y + offset_y); + let line_size = line.size(line_height); _ = line.paint(p, line_height, TextAlign::Left, None, window, cx); - offset_y += line.size(line_height).height; + offset_y += line_size.height; } if focused { @@ -696,6 +741,7 @@ impl Element for TextElement { input.last_selected_range = Some(selected_range); input.scroll_size = scroll_size; input.line_number_width = prepaint.line_number_width; + input.current_line_index = Some(prepaint.current_line_index); input .scroll_handle .set_offset(prepaint.cursor_scroll_offset); diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index 6b2126ea..ab90be3d 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -223,6 +223,8 @@ pub struct InputState { /// Range for save the selected word, use to keep word range when drag move. pub(super) selected_word_range: Option>, pub(super) selection_reversed: bool, + /// The index of the current line, zero-based. + pub(super) current_line_index: Option, pub(super) marked_range: Option>, pub(super) last_layout: Option>, pub(super) last_cursor_offset: Option, @@ -298,6 +300,7 @@ impl InputState { selected_range: 0..0, selected_word_range: None, selection_reversed: false, + current_line_index: None, marked_range: None, input_bounds: Bounds::default(), selecting: false,