code-editor: Add current line background. (#887)
https://github.com/user-attachments/assets/3ab6a1b5-648c-4431-91d2-7ebefee2f511
This commit is contained in:
parent
2d6284694d
commit
2c4a28a6da
4 changed files with 64 additions and 9 deletions
|
|
@ -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.,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ impl LineHighlightStyle {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct CodeHighlighter {
|
||||
highlighter: Rc<Highlighter<'static>>,
|
||||
pub(super) highlighter: Rc<Highlighter<'static>>,
|
||||
pub(super) text: SharedString,
|
||||
/// The lines by split \n
|
||||
pub(super) lines: Vec<LineHighlightStyle>,
|
||||
|
|
|
|||
|
|
@ -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<PaintQuad>, Point<Pixels>) {
|
||||
) -> (Option<PaintQuad>, Point<Pixels>, 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<Rc<crate::highlighter::Highlighter<'static>>> {
|
||||
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<PaintQuad>,
|
||||
cursor_scroll_offset: Point<Pixels>,
|
||||
current_line_index: usize,
|
||||
selection_path: Option<Path<Pixels>>,
|
||||
bounds: Bounds<Pixels>,
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<Range<usize>>,
|
||||
pub(super) selection_reversed: bool,
|
||||
/// The index of the current line, zero-based.
|
||||
pub(super) current_line_index: Option<usize>,
|
||||
pub(super) marked_range: Option<Range<usize>>,
|
||||
pub(super) last_layout: Option<SmallVec<[WrappedLine; 1]>>,
|
||||
pub(super) last_cursor_offset: Option<usize>,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue