From 4d0dde1f23c5ebb0bfbfa2229b105e5a424c64df Mon Sep 17 00:00:00 2001 From: Daniel Bulant Date: Wed, 19 Aug 2026 00:48:55 +0200 Subject: [PATCH] feat(input): support custom text highlights --- crates/ui/src/input/element.rs | 42 +++++++++++++++++++--------------- crates/ui/src/input/state.rs | 25 ++++++++++++++++---- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 4e7941cc..3b491923 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -752,30 +752,36 @@ impl TextElement { } => (highlighter.borrow(), diagnostics), _ => return None, }; - let highlighter = highlighter.as_ref()?; - - let mut offset = visible_byte_range.start; + let highlighter = highlighter.as_ref(); let mut styles = vec![]; - for line in text - .iter_lines() - .skip(visible_range.start) - .take(visible_range.len()) - { - let line_len = if is_multi_line { - // +1 for `\n` - line.len() + 1 - } else { - line.len() - }; + if let Some(highlighter) = highlighter { + let mut offset = visible_byte_range.start; + for line in text + .iter_lines() + .skip(visible_range.start) + .take(visible_range.len()) + { + let line_len = if is_multi_line { + // +1 for `\n` + line.len() + 1 + } else { + line.len() + }; - let range = offset..offset + line_len; - let line_styles = highlighter.styles(&range, &cx.theme().highlight_theme); - styles = gpui::combine_highlights(styles, line_styles).collect(); + let range = offset..offset + line_len; + let line_styles = highlighter.styles(&range, &cx.theme().highlight_theme); + 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); // hover definition style diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index 89b3ffa9..494f97da 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -5,10 +5,11 @@ use anyhow::Result; use gpui::{ Action, App, AppContext, Bounds, ClipboardItem, Context, Entity, EntityInputHandler, - EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding, - KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, - Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription, - Task, UTF16Selection, Window, actions, div, point, prelude::FluentBuilder as _, px, + EventEmitter, FocusHandle, Focusable, HighlightStyle, InteractiveElement as _, IntoElement, + KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + ParentElement as _, Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, SharedString, + Styled as _, Subscription, Task, UTF16Selection, Window, actions, div, point, + prelude::FluentBuilder as _, px, }; use ropey::{Rope, RopeSlice}; use serde::Deserialize; @@ -298,6 +299,8 @@ pub struct InputState { pub(crate) deferred_scroll_offset: Option>, /// The size of the scrollable content. pub(crate) scroll_size: gpui::Size, + /// Additional presentation-only styles keyed by UTF-8 byte range. + pub(super) custom_highlights: Vec<(Range, HighlightStyle)>, /// The mask pattern for formatting the input text pub(crate) mask_pattern: MaskPattern, @@ -395,6 +398,7 @@ impl InputState { scroll_handle: ScrollHandle::new(), scroll_size: gpui::size(px(0.), px(0.)), deferred_scroll_offset: None, + custom_highlights: Vec::new(), preferred_column: None, placeholder: SharedString::default(), mask_pattern: MaskPattern::default(), @@ -529,6 +533,19 @@ impl InputState { 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, HighlightStyle)>, + cx: &mut Context, + ) { + self.custom_highlights = highlights; + cx.notify(); + } + fn reset_highlighter(&mut self, cx: &mut Context) { match &mut self.mode { InputMode::CodeEditor { highlighter, .. } => {