feat(input): support custom text highlights

This commit is contained in:
Daniel Bulant 2026-08-19 00:48:55 +02:00
parent 420d333eb5
commit 4d0dde1f23
No known key found for this signature in database
2 changed files with 45 additions and 22 deletions

View file

@ -752,30 +752,36 @@ impl TextElement {
} => (highlighter.borrow(), diagnostics), } => (highlighter.borrow(), diagnostics),
_ => return None, _ => return None,
}; };
let highlighter = highlighter.as_ref()?; let highlighter = highlighter.as_ref();
let mut offset = visible_byte_range.start;
let mut styles = vec![]; let mut styles = vec![];
for line in text if let Some(highlighter) = highlighter {
.iter_lines() let mut offset = visible_byte_range.start;
.skip(visible_range.start) for line in text
.take(visible_range.len()) .iter_lines()
{ .skip(visible_range.start)
let line_len = if is_multi_line { .take(visible_range.len())
// +1 for `\n` {
line.len() + 1 let line_len = if is_multi_line {
} else { // +1 for `\n`
line.len() line.len() + 1
}; } else {
line.len()
};
let range = offset..offset + line_len; let range = offset..offset + line_len;
let line_styles = highlighter.styles(&range, &cx.theme().highlight_theme); let line_styles = highlighter.styles(&range, &cx.theme().highlight_theme);
styles = gpui::combine_highlights(styles, line_styles).collect(); styles = gpui::combine_highlights(styles, line_styles).collect();
offset = range.end; offset = range.end;
}
} }
let custom_styles = state.custom_highlights.iter().filter(|(range, _)| {
range.start < visible_byte_range.end && range.end > visible_byte_range.start
});
styles = gpui::combine_highlights(styles, custom_styles.cloned()).collect();
let diagnostic_styles = diagnostics.styles_for_range(&visible_byte_range, cx); let diagnostic_styles = diagnostics.styles_for_range(&visible_byte_range, cx);
// hover definition style // hover definition style

View file

@ -5,10 +5,11 @@
use anyhow::Result; use anyhow::Result;
use gpui::{ use gpui::{
Action, App, AppContext, Bounds, ClipboardItem, Context, Entity, EntityInputHandler, Action, App, AppContext, Bounds, ClipboardItem, Context, Entity, EntityInputHandler,
EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding, EventEmitter, FocusHandle, Focusable, HighlightStyle, InteractiveElement as _, IntoElement,
KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription, ParentElement as _, Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, SharedString,
Task, UTF16Selection, Window, actions, div, point, prelude::FluentBuilder as _, px, Styled as _, Subscription, Task, UTF16Selection, Window, actions, div, point,
prelude::FluentBuilder as _, px,
}; };
use ropey::{Rope, RopeSlice}; use ropey::{Rope, RopeSlice};
use serde::Deserialize; use serde::Deserialize;
@ -298,6 +299,8 @@ pub struct InputState {
pub(crate) deferred_scroll_offset: Option<Point<Pixels>>, pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
/// The size of the scrollable content. /// The size of the scrollable content.
pub(crate) scroll_size: gpui::Size<Pixels>, pub(crate) scroll_size: gpui::Size<Pixels>,
/// Additional presentation-only styles keyed by UTF-8 byte range.
pub(super) custom_highlights: Vec<(Range<usize>, HighlightStyle)>,
/// The mask pattern for formatting the input text /// The mask pattern for formatting the input text
pub(crate) mask_pattern: MaskPattern, pub(crate) mask_pattern: MaskPattern,
@ -395,6 +398,7 @@ impl InputState {
scroll_handle: ScrollHandle::new(), scroll_handle: ScrollHandle::new(),
scroll_size: gpui::size(px(0.), px(0.)), scroll_size: gpui::size(px(0.), px(0.)),
deferred_scroll_offset: None, deferred_scroll_offset: None,
custom_highlights: Vec::new(),
preferred_column: None, preferred_column: None,
placeholder: SharedString::default(), placeholder: SharedString::default(),
mask_pattern: MaskPattern::default(), mask_pattern: MaskPattern::default(),
@ -529,6 +533,19 @@ impl InputState {
cx.notify(); cx.notify();
} }
/// Replace presentation-only text styles keyed by UTF-8 byte range.
///
/// Custom highlights are combined with syntax and diagnostic highlights and do not alter
/// the editor text, selection, or history.
pub fn set_custom_highlights(
&mut self,
highlights: Vec<(Range<usize>, HighlightStyle)>,
cx: &mut Context<Self>,
) {
self.custom_highlights = highlights;
cx.notify();
}
fn reset_highlighter(&mut self, cx: &mut Context<Self>) { fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
match &mut self.mode { match &mut self.mode {
InputMode::CodeEditor { highlighter, .. } => { InputMode::CodeEditor { highlighter, .. } => {