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),
|
inner: Arc::new(theme),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn settings(&self) -> &highlighting::ThemeSettings {
|
||||||
|
&self.inner.settings
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inspired by the `iced` crate's `Highlighter` struct.
|
/// 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
|
/// https://github.com/iced-rs/iced/blob/master/highlighter/src/lib.rs#L24
|
||||||
pub struct Highlighter<'a> {
|
pub struct Highlighter<'a> {
|
||||||
syntax: &'static parsing::SyntaxReference,
|
syntax: &'static parsing::SyntaxReference,
|
||||||
|
pub(crate) theme: &'a HighlightTheme,
|
||||||
highlighter: highlighting::Highlighter<'a>,
|
highlighter: highlighting::Highlighter<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,6 +76,7 @@ impl<'a> Highlighter<'a> {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
syntax,
|
syntax,
|
||||||
|
theme,
|
||||||
highlighter,
|
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 {
|
gpui::Rgba {
|
||||||
r: color.r as f32 / 255.,
|
r: color.r as f32 / 255.,
|
||||||
g: color.g as f32 / 255.,
|
g: color.g as f32 / 255.,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ impl LineHighlightStyle {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(super) struct CodeHighlighter {
|
pub(super) struct CodeHighlighter {
|
||||||
highlighter: Rc<Highlighter<'static>>,
|
pub(super) highlighter: Rc<Highlighter<'static>>,
|
||||||
pub(super) text: SharedString,
|
pub(super) text: SharedString,
|
||||||
/// The lines by split \n
|
/// The lines by split \n
|
||||||
pub(super) lines: Vec<LineHighlightStyle>,
|
pub(super) lines: Vec<LineHighlightStyle>,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
|
fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
|
||||||
Entity, GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path,
|
Entity, GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path,
|
||||||
|
|
@ -54,10 +56,11 @@ impl TextElement {
|
||||||
line_number_width: Pixels,
|
line_number_width: Pixels,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> (Option<PaintQuad>, Point<Pixels>) {
|
) -> (Option<PaintQuad>, Point<Pixels>, usize) {
|
||||||
let input = self.input.read(cx);
|
let input = self.input.read(cx);
|
||||||
let selected_range = &input.selected_range;
|
let selected_range = &input.selected_range;
|
||||||
let cursor_offset = input.cursor_offset();
|
let cursor_offset = input.cursor_offset();
|
||||||
|
let mut current_line_index = 0;
|
||||||
let mut scroll_offset = input.scroll_handle.offset();
|
let mut scroll_offset = input.scroll_handle.offset();
|
||||||
let mut cursor = None;
|
let mut cursor = None;
|
||||||
|
|
||||||
|
|
@ -172,9 +175,12 @@ impl TextElement {
|
||||||
cx.theme().caret,
|
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(
|
fn layout_selections(
|
||||||
|
|
@ -316,6 +322,13 @@ impl TextElement {
|
||||||
_ => None,
|
_ => 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 {
|
pub(super) struct PrepaintState {
|
||||||
|
|
@ -324,6 +337,7 @@ pub(super) struct PrepaintState {
|
||||||
line_number_width: Pixels,
|
line_number_width: Pixels,
|
||||||
cursor: Option<PaintQuad>,
|
cursor: Option<PaintQuad>,
|
||||||
cursor_scroll_offset: Point<Pixels>,
|
cursor_scroll_offset: Point<Pixels>,
|
||||||
|
current_line_index: usize,
|
||||||
selection_path: Option<Path<Pixels>>,
|
selection_path: Option<Path<Pixels>>,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
@ -438,7 +452,8 @@ impl Element for TextElement {
|
||||||
let mut line_numbers = SmallVec::new();
|
let mut line_numbers = SmallVec::new();
|
||||||
let total_lines = input.text_wrapper.lines.len();
|
let total_lines = input.text_wrapper.lines.len();
|
||||||
let run_len = if total_lines > 999 { 4 } else { 3 };
|
let run_len = if total_lines > 999 { 4 } else { 3 };
|
||||||
let runs = vec![TextRun {
|
|
||||||
|
let other_line_runs = vec![TextRun {
|
||||||
len: run_len,
|
len: run_len,
|
||||||
font: style.font(),
|
font: style.font(),
|
||||||
color: cx.theme().muted_foreground,
|
color: cx.theme().muted_foreground,
|
||||||
|
|
@ -446,6 +461,14 @@ impl Element for TextElement {
|
||||||
underline: None,
|
underline: None,
|
||||||
strikethrough: 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() {
|
for (i, line_wrap) in input.text_wrapper.lines.iter().enumerate() {
|
||||||
let line_no = if run_len == 4 {
|
let line_no = if run_len == 4 {
|
||||||
|
|
@ -454,6 +477,12 @@ impl Element for TextElement {
|
||||||
format!("{:>3}", i + 1).into()
|
format!("{:>3}", i + 1).into()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let runs = if input.current_line_index == Some(i) {
|
||||||
|
¤t_line_runs
|
||||||
|
} else {
|
||||||
|
&other_line_runs
|
||||||
|
};
|
||||||
|
|
||||||
let line = window
|
let line = window
|
||||||
.text_system()
|
.text_system()
|
||||||
.shape_text(line_no, font_size, &runs, None, None)
|
.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
|
// 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,
|
&lines,
|
||||||
line_height,
|
line_height,
|
||||||
&mut bounds,
|
&mut bounds,
|
||||||
|
|
@ -589,6 +618,7 @@ impl Element for TextElement {
|
||||||
line_number_width,
|
line_number_width,
|
||||||
cursor,
|
cursor,
|
||||||
cursor_scroll_offset,
|
cursor_scroll_offset,
|
||||||
|
current_line_index,
|
||||||
selection_path,
|
selection_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -657,10 +687,24 @@ impl Element for TextElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(line_numbers) = prepaint.line_numbers.as_ref() {
|
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);
|
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);
|
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;
|
offset_y += line_size.height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -668,8 +712,9 @@ impl Element for TextElement {
|
||||||
let mut offset_y = px(0.);
|
let mut offset_y = px(0.);
|
||||||
for line in prepaint.lines.iter() {
|
for line in prepaint.lines.iter() {
|
||||||
let p = point(origin.x + prepaint.line_number_width, origin.y + offset_y);
|
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);
|
_ = line.paint(p, line_height, TextAlign::Left, None, window, cx);
|
||||||
offset_y += line.size(line_height).height;
|
offset_y += line_size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if focused {
|
if focused {
|
||||||
|
|
@ -696,6 +741,7 @@ impl Element for TextElement {
|
||||||
input.last_selected_range = Some(selected_range);
|
input.last_selected_range = Some(selected_range);
|
||||||
input.scroll_size = scroll_size;
|
input.scroll_size = scroll_size;
|
||||||
input.line_number_width = prepaint.line_number_width;
|
input.line_number_width = prepaint.line_number_width;
|
||||||
|
input.current_line_index = Some(prepaint.current_line_index);
|
||||||
input
|
input
|
||||||
.scroll_handle
|
.scroll_handle
|
||||||
.set_offset(prepaint.cursor_scroll_offset);
|
.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.
|
/// Range for save the selected word, use to keep word range when drag move.
|
||||||
pub(super) selected_word_range: Option<Range<usize>>,
|
pub(super) selected_word_range: Option<Range<usize>>,
|
||||||
pub(super) selection_reversed: bool,
|
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) marked_range: Option<Range<usize>>,
|
||||||
pub(super) last_layout: Option<SmallVec<[WrappedLine; 1]>>,
|
pub(super) last_layout: Option<SmallVec<[WrappedLine; 1]>>,
|
||||||
pub(super) last_cursor_offset: Option<usize>,
|
pub(super) last_cursor_offset: Option<usize>,
|
||||||
|
|
@ -298,6 +300,7 @@ impl InputState {
|
||||||
selected_range: 0..0,
|
selected_range: 0..0,
|
||||||
selected_word_range: None,
|
selected_word_range: None,
|
||||||
selection_reversed: false,
|
selection_reversed: false,
|
||||||
|
current_line_index: None,
|
||||||
marked_range: None,
|
marked_range: None,
|
||||||
input_bounds: Bounds::default(),
|
input_bounds: Bounds::default(),
|
||||||
selecting: false,
|
selecting: false,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue