Cargo.lock 🔗
@@ -4268,6 +4268,7 @@ dependencies = [
name = "debugger_ui"
version = "0.1.0"
dependencies = [
+ "alacritty_terminal",
"anyhow",
"client",
"collections",
Cole Miller created
Relanding #32817 with an improved approach, bugs fixed, and a test.
Release Notes:
- N/A
Cargo.lock | 1
crates/debugger_ui/Cargo.toml | 1
crates/debugger_ui/src/session/running/console.rs | 263 +++++++++++++++
crates/debugger_ui/src/tests/console.rs | 203 ++++++++++++
crates/editor/src/display_map.rs | 36 +
crates/editor/src/display_map/custom_highlights.rs | 17
crates/editor/src/display_map/inlay_map.rs | 4
crates/editor/src/editor.rs | 108 +++++-
crates/editor/src/editor_tests.rs | 8
crates/editor/src/element.rs | 16
crates/editor/src/highlight_matching_bracket.rs | 2
crates/editor/src/hover_popover.rs | 2
crates/editor/src/items.rs | 9
crates/editor/src/test/editor_test_context.rs | 4
crates/language_tools/src/syntax_tree_view.rs | 2
crates/project/src/debugger/dap_command.rs | 2
crates/search/src/project_search.rs | 2
crates/vim/src/normal/yank.rs | 2
crates/vim/src/replace.rs | 2
19 files changed, 605 insertions(+), 79 deletions(-)
@@ -4268,6 +4268,7 @@ dependencies = [
name = "debugger_ui"
version = "0.1.0"
dependencies = [
+ "alacritty_terminal",
"anyhow",
"client",
"collections",
@@ -26,6 +26,7 @@ test-support = [
]
[dependencies]
+alacritty_terminal.workspace = true
anyhow.workspace = true
client.workspace = true
collections.workspace = true
@@ -2,13 +2,15 @@ use super::{
stack_frame_list::{StackFrameList, StackFrameListEvent},
variable_list::VariableList,
};
+use alacritty_terminal::vte::ansi;
use anyhow::Result;
use collections::HashMap;
use dap::OutputEvent;
use editor::{Bias, CompletionProvider, Editor, EditorElement, EditorStyle, ExcerptId};
use fuzzy::StringMatchCandidate;
use gpui::{
- Context, Entity, FocusHandle, Focusable, Render, Subscription, Task, TextStyle, WeakEntity,
+ Context, Entity, FocusHandle, Focusable, HighlightStyle, Hsla, Render, Subscription, Task,
+ TextStyle, WeakEntity,
};
use language::{Buffer, CodeLabel, ToOffset};
use menu::Confirm;
@@ -17,8 +19,8 @@ use project::{
debugger::session::{CompletionsQuery, OutputToken, Session, SessionEvent},
};
use settings::Settings;
-use std::{cell::RefCell, rc::Rc, usize};
-use theme::ThemeSettings;
+use std::{cell::RefCell, ops::Range, rc::Rc, usize};
+use theme::{Theme, ThemeSettings};
use ui::{Divider, prelude::*};
pub struct Console {
@@ -136,18 +138,193 @@ impl Console {
cx: &mut App,
) {
self.console.update(cx, |console, cx| {
- let mut to_insert = String::default();
+ console.set_read_only(false);
+
for event in events {
- use std::fmt::Write;
+ let to_insert = format!("{}\n", event.output.trim_end());
+
+ let mut ansi_handler = ConsoleHandler::default();
+ let mut ansi_processor = ansi::Processor::<ansi::StdSyncHandler>::default();
+
+ let len = console.buffer().read(cx).len(cx);
+ ansi_processor.advance(&mut ansi_handler, to_insert.as_bytes());
+ let output = std::mem::take(&mut ansi_handler.output);
+ let mut spans = std::mem::take(&mut ansi_handler.spans);
+ let mut background_spans = std::mem::take(&mut ansi_handler.background_spans);
+ if ansi_handler.current_range_start < output.len() {
+ spans.push((
+ ansi_handler.current_range_start..output.len(),
+ ansi_handler.current_color,
+ ));
+ }
+ if ansi_handler.current_background_range_start < output.len() {
+ background_spans.push((
+ ansi_handler.current_background_range_start..output.len(),
+ ansi_handler.current_background_color,
+ ));
+ }
+ console.move_to_end(&editor::actions::MoveToEnd, window, cx);
+ console.insert(&output, window, cx);
+ let buffer = console.buffer().read(cx).snapshot(cx);
+
+ struct ConsoleAnsiHighlight;
+
+ for (range, color) in spans {
+ let Some(color) = color else { continue };
+ let start_offset = len + range.start;
+ let range = start_offset..len + range.end;
+ let range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
+ let style = HighlightStyle {
+ color: Some(terminal_view::terminal_element::convert_color(
+ &color,
+ cx.theme(),
+ )),
+ ..Default::default()
+ };
+ console.highlight_text_key::<ConsoleAnsiHighlight>(
+ start_offset,
+ vec![range],
+ style,
+ cx,
+ );
+ }
- _ = write!(to_insert, "{}\n", event.output.trim_end());
+ for (range, color) in background_spans {
+ let Some(color) = color else { continue };
+ let start_offset = len + range.start;
+ let range = start_offset..len + range.end;
+ let range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
+
+ let color_fetcher: fn(&Theme) -> Hsla = match color {
+ // Named and theme defined colors
+ ansi::Color::Named(n) => match n {
+ ansi::NamedColor::Black => |theme| theme.colors().terminal_ansi_black,
+ ansi::NamedColor::Red => |theme| theme.colors().terminal_ansi_red,
+ ansi::NamedColor::Green => |theme| theme.colors().terminal_ansi_green,
+ ansi::NamedColor::Yellow => |theme| theme.colors().terminal_ansi_yellow,
+ ansi::NamedColor::Blue => |theme| theme.colors().terminal_ansi_blue,
+ ansi::NamedColor::Magenta => {
+ |theme| theme.colors().terminal_ansi_magenta
+ }
+ ansi::NamedColor::Cyan => |theme| theme.colors().terminal_ansi_cyan,
+ ansi::NamedColor::White => |theme| theme.colors().terminal_ansi_white,
+ ansi::NamedColor::BrightBlack => {
+ |theme| theme.colors().terminal_ansi_bright_black
+ }
+ ansi::NamedColor::BrightRed => {
+ |theme| theme.colors().terminal_ansi_bright_red
+ }
+ ansi::NamedColor::BrightGreen => {
+ |theme| theme.colors().terminal_ansi_bright_green
+ }
+ ansi::NamedColor::BrightYellow => {
+ |theme| theme.colors().terminal_ansi_bright_yellow
+ }
+ ansi::NamedColor::BrightBlue => {
+ |theme| theme.colors().terminal_ansi_bright_blue
+ }
+ ansi::NamedColor::BrightMagenta => {
+ |theme| theme.colors().terminal_ansi_bright_magenta
+ }
+ ansi::NamedColor::BrightCyan => {
+ |theme| theme.colors().terminal_ansi_bright_cyan
+ }
+ ansi::NamedColor::BrightWhite => {
+ |theme| theme.colors().terminal_ansi_bright_white
+ }
+ ansi::NamedColor::Foreground => {
+ |theme| theme.colors().terminal_foreground
+ }
+ ansi::NamedColor::Background => {
+ |theme| theme.colors().terminal_background
+ }
+ ansi::NamedColor::Cursor => |theme| theme.players().local().cursor,
+ ansi::NamedColor::DimBlack => {
+ |theme| theme.colors().terminal_ansi_dim_black
+ }
+ ansi::NamedColor::DimRed => {
+ |theme| theme.colors().terminal_ansi_dim_red
+ }
+ ansi::NamedColor::DimGreen => {
+ |theme| theme.colors().terminal_ansi_dim_green
+ }
+ ansi::NamedColor::DimYellow => {
+ |theme| theme.colors().terminal_ansi_dim_yellow
+ }
+ ansi::NamedColor::DimBlue => {
+ |theme| theme.colors().terminal_ansi_dim_blue
+ }
+ ansi::NamedColor::DimMagenta => {
+ |theme| theme.colors().terminal_ansi_dim_magenta
+ }
+ ansi::NamedColor::DimCyan => {
+ |theme| theme.colors().terminal_ansi_dim_cyan
+ }
+ ansi::NamedColor::DimWhite => {
+ |theme| theme.colors().terminal_ansi_dim_white
+ }
+ ansi::NamedColor::BrightForeground => {
+ |theme| theme.colors().terminal_bright_foreground
+ }
+ ansi::NamedColor::DimForeground => {
+ |theme| theme.colors().terminal_dim_foreground
+ }
+ },
+ // 'True' colors
+ ansi::Color::Spec(_) => |theme| theme.colors().editor_background,
+ // 8 bit, indexed colors
+ ansi::Color::Indexed(i) => {
+ match i {
+ // 0-15 are the same as the named colors above
+ 0 => |theme| theme.colors().terminal_ansi_black,
+ 1 => |theme| theme.colors().terminal_ansi_red,
+ 2 => |theme| theme.colors().terminal_ansi_green,
+ 3 => |theme| theme.colors().terminal_ansi_yellow,
+ 4 => |theme| theme.colors().terminal_ansi_blue,
+ 5 => |theme| theme.colors().terminal_ansi_magenta,
+ 6 => |theme| theme.colors().terminal_ansi_cyan,
+ 7 => |theme| theme.colors().terminal_ansi_white,
+ 8 => |theme| theme.colors().terminal_ansi_bright_black,
+ 9 => |theme| theme.colors().terminal_ansi_bright_red,
+ 10 => |theme| theme.colors().terminal_ansi_bright_green,
+ 11 => |theme| theme.colors().terminal_ansi_bright_yellow,
+ 12 => |theme| theme.colors().terminal_ansi_bright_blue,
+ 13 => |theme| theme.colors().terminal_ansi_bright_magenta,
+ 14 => |theme| theme.colors().terminal_ansi_bright_cyan,
+ 15 => |theme| theme.colors().terminal_ansi_bright_white,
+ // 16-231 are a 6x6x6 RGB color cube, mapped to 0-255 using steps defined by XTerm.
+ // See: https://github.com/xterm-x11/xterm-snapshots/blob/master/256colres.pl
+ // 16..=231 => {
+ // let (r, g, b) = rgb_for_index(index as u8);
+ // rgba_color(
+ // if r == 0 { 0 } else { r * 40 + 55 },
+ // if g == 0 { 0 } else { g * 40 + 55 },
+ // if b == 0 { 0 } else { b * 40 + 55 },
+ // )
+ // }
+ // 232-255 are a 24-step grayscale ramp from (8, 8, 8) to (238, 238, 238).
+ // 232..=255 => {
+ // let i = index as u8 - 232; // Align index to 0..24
+ // let value = i * 10 + 8;
+ // rgba_color(value, value, value)
+ // }
+ // For compatibility with the alacritty::Colors interface
+ // See: https://github.com/alacritty/alacritty/blob/master/alacritty_terminal/src/term/color.rs
+ _ => |_| gpui::black(),
+ }
+ }
+ };
+
+ console.highlight_background_key::<ConsoleAnsiHighlight>(
+ start_offset,
+ &[range],
+ color_fetcher,
+ cx,
+ );
+ }
}
- console.set_read_only(false);
- console.move_to_end(&editor::actions::MoveToEnd, window, cx);
- console.insert(&to_insert, window, cx);
console.set_read_only(true);
-
cx.notify();
});
}
@@ -459,3 +636,69 @@ impl ConsoleQueryBarCompletionProvider {
})
}
}
+
+#[derive(Default)]
+struct ConsoleHandler {
+ output: String,
+ spans: Vec<(Range<usize>, Option<ansi::Color>)>,
+ background_spans: Vec<(Range<usize>, Option<ansi::Color>)>,
+ current_range_start: usize,
+ current_background_range_start: usize,
+ current_color: Option<ansi::Color>,
+ current_background_color: Option<ansi::Color>,
+ pos: usize,
+}
+
+impl ConsoleHandler {
+ fn break_span(&mut self, color: Option<ansi::Color>) {
+ self.spans.push((
+ self.current_range_start..self.output.len(),
+ self.current_color,
+ ));
+ self.current_color = color;
+ self.current_range_start = self.pos;
+ }
+
+ fn break_background_span(&mut self, color: Option<ansi::Color>) {
+ self.background_spans.push((
+ self.current_background_range_start..self.output.len(),
+ self.current_background_color,
+ ));
+ self.current_background_color = color;
+ self.current_background_range_start = self.pos;
+ }
+}
+
+impl ansi::Handler for ConsoleHandler {
+ fn input(&mut self, c: char) {
+ self.output.push(c);
+ self.pos += c.len_utf8();
+ }
+
+ fn linefeed(&mut self) {
+ self.output.push('\n');
+ self.pos += 1;
+ }
+
+ fn put_tab(&mut self, count: u16) {
+ self.output
+ .extend(std::iter::repeat('\t').take(count as usize));
+ self.pos += count as usize;
+ }
+
+ fn terminal_attribute(&mut self, attr: ansi::Attr) {
+ match attr {
+ ansi::Attr::Foreground(color) => {
+ self.break_span(Some(color));
+ }
+ ansi::Attr::Background(color) => {
+ self.break_background_span(Some(color));
+ }
+ ansi::Attr::Reset => {
+ self.break_span(None);
+ self.break_background_span(None);
+ }
+ _ => {}
+ }
+ }
+}
@@ -3,6 +3,7 @@ use crate::{
*,
};
use dap::requests::StackTrace;
+use editor::{DisplayPoint, display_map::DisplayRow};
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use project::{FakeFs, Project};
use serde_json::json;
@@ -110,7 +111,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: Some(dap::OutputEventCategory::Stdout),
- output: "Second output line after thread stopped!".to_string(),
+ output: "\tSecond output line after thread stopped!".to_string(),
data: None,
variables_reference: None,
source: None,
@@ -124,7 +125,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: Some(dap::OutputEventCategory::Console),
- output: "Second console output line after thread stopped!".to_string(),
+ output: "\tSecond console output line after thread stopped!".to_string(),
data: None,
variables_reference: None,
source: None,
@@ -150,13 +151,209 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
.unwrap();
assert_eq!(
- "First console output line before thread stopped!\nFirst output line before thread stopped!\nSecond output line after thread stopped!\nSecond console output line after thread stopped!\n",
+ "First console output line before thread stopped!\nFirst output line before thread stopped!\n\tSecond output line after thread stopped!\n\tSecond console output line after thread stopped!\n",
active_session_panel.read(cx).running_state().read(cx).console().read(cx).editor().read(cx).text(cx).as_str()
);
})
.unwrap();
}
+#[gpui::test]
+async fn test_escape_code_processing(executor: BackgroundExecutor, cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(executor.clone());
+
+ fs.insert_tree(
+ path!("/project"),
+ json!({
+ "main.rs": "First line\nSecond line\nThird line\nFourth line",
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
+ let workspace = init_test_workspace(&project, cx).await;
+ let cx = &mut VisualTestContext::from_window(*workspace, cx);
+ workspace
+ .update(cx, |workspace, window, cx| {
+ workspace.focus_panel::<DebugPanel>(window, cx);
+ })
+ .unwrap();
+
+ let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
+ let client = session.read_with(cx, |session, _| session.adapter_client().unwrap());
+
+ client.on_request::<StackTrace, _>(move |_, _| {
+ Ok(dap::StackTraceResponse {
+ stack_frames: Vec::default(),
+ total_frames: None,
+ })
+ });
+
+ client
+ .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+ category: None,
+ output: "Checking latest version of JavaScript...".to_string(),
+ data: None,
+ variables_reference: None,
+ source: None,
+ line: None,
+ column: None,
+ group: None,
+ location_reference: None,
+ }))
+ .await;
+ client
+ .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+ category: None,
+ output: " \u{1b}[1m\u{1b}[38;2;173;127;168m▲ Next.js 15.1.5\u{1b}[39m\u{1b}[22m"
+ .to_string(),
+ data: None,
+ variables_reference: None,
+ source: None,
+ line: None,
+ column: None,
+ group: None,
+ location_reference: None,
+ }))
+ .await;
+ client
+ .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+ category: None,
+ output: " - Local: http://localhost:3000\n - Network: http://192.168.1.144:3000\n\n \u{1b}[32m\u{1b}[1m✓\u{1b}[22m\u{1b}[39m Starting..."
+ .to_string(),
+ data: None,
+ variables_reference: None,
+ source: None,
+ line: None,
+ column: None,
+ group: None,
+ location_reference: None,
+ }))
+ .await;
+ // [crates/debugger_ui/src/session/running/console.rs:147:9] &to_insert = "Could not read source map for file:///Users/cole/roles-at/node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/typescript.js: ENOENT: no such file or directory, open '/Users/cole/roles-at/node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/typescript.js.map'\n"
+ client
+ .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+ category: None,
+ output: "Something else...".to_string(),
+ data: None,
+ variables_reference: None,
+ source: None,
+ line: None,
+ column: None,
+ group: None,
+ location_reference: None,
+ }))
+ .await;
+ client
+ .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+ category: None,
+ output: " \u{1b}[32m\u{1b}[1m✓\u{1b}[22m\u{1b}[39m Ready in 1009ms\n".to_string(),
+ data: None,
+ variables_reference: None,
+ source: None,
+ line: None,
+ column: None,
+ group: None,
+ location_reference: None,
+ }))
+ .await;
+
+ // introduce some background highlight
+ client
+ .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+ category: None,
+ output: "\u{1b}[41m\u{1b}[37mBoth background and foreground!".to_string(),
+ data: None,
+ variables_reference: None,
+ source: None,
+ line: None,
+ column: None,
+ group: None,
+ location_reference: None,
+ }))
+ .await;
+ // another random line
+ client
+ .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+ category: None,
+ output: "Even more...".to_string(),
+ data: None,
+ variables_reference: None,
+ source: None,
+ line: None,
+ column: None,
+ group: None,
+ location_reference: None,
+ }))
+ .await;
+
+ cx.run_until_parked();
+
+ let _running_state =
+ active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
+ cx.focus_self(window);
+ item.running_state().clone()
+ });
+
+ cx.run_until_parked();
+
+ workspace
+ .update(cx, |workspace, window, cx| {
+ let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
+ let active_debug_session_panel = debug_panel
+ .update(cx, |this, _| this.active_session())
+ .unwrap();
+
+ let editor =
+ active_debug_session_panel
+ .read(cx)
+ .running_state()
+ .read(cx)
+ .console()
+ .read(cx)
+ .editor().clone();
+
+ assert_eq!(
+ "Checking latest version of JavaScript...\n ▲ Next.js 15.1.5\n - Local: http://localhost:3000\n - Network: http://192.168.1.144:3000\n\n ✓ Starting...\nSomething else...\n ✓ Ready in 1009ms\nBoth background and foreground!\nEven more...\n",
+ editor
+ .read(cx)
+ .text(cx)
+ .as_str()
+ );
+
+ let text_highlights = editor.update(cx, |editor, cx| {
+ let mut text_highlights = editor.all_text_highlights(window, cx).into_iter().flat_map(|(_, ranges)| ranges).collect::<Vec<_>>();
+ text_highlights.sort_by(|a, b| a.start.cmp(&b.start));
+ text_highlights
+ });
+ pretty_assertions::assert_eq!(
+ text_highlights,
+ [
+ DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 21),
+ DisplayPoint::new(DisplayRow(1), 21)..DisplayPoint::new(DisplayRow(2), 0),
+ DisplayPoint::new(DisplayRow(5), 1)..DisplayPoint::new(DisplayRow(5), 4),
+ DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(6), 0),
+ DisplayPoint::new(DisplayRow(7), 1)..DisplayPoint::new(DisplayRow(7), 4),
+ DisplayPoint::new(DisplayRow(7), 4)..DisplayPoint::new(DisplayRow(8), 0),
+ DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(9), 0),
+ ]
+ );
+
+ let background_highlights = editor.update(cx, |editor, cx| {
+ editor.all_text_background_highlights(window, cx).into_iter().map(|(range, _)| range).collect::<Vec<_>>()
+ });
+ pretty_assertions::assert_eq!(
+ background_highlights,
+ [
+ DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(9), 0),
+ ]
+ )
+ })
+ .unwrap();
+}
+
// #[gpui::test]
// async fn test_grouped_output(executor: BackgroundExecutor, cx: &mut TestAppContext) {
// init_test(cx);
@@ -76,11 +76,17 @@ pub enum FoldStatus {
Foldable,
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum HighlightKey {
+ Type(TypeId),
+ TypePlus(TypeId, usize),
+}
+
pub trait ToDisplayPoint {
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
}
-type TextHighlights = TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
+type TextHighlights = TreeMap<HighlightKey, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
@@ -473,12 +479,11 @@ impl DisplayMap {
pub fn highlight_text(
&mut self,
- type_id: TypeId,
+ key: HighlightKey,
ranges: Vec<Range<Anchor>>,
style: HighlightStyle,
) {
- self.text_highlights
- .insert(type_id, Arc::new((style, ranges)));
+ self.text_highlights.insert(key, Arc::new((style, ranges)));
}
pub(crate) fn highlight_inlays(
@@ -501,11 +506,22 @@ impl DisplayMap {
}
pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
- let highlights = self.text_highlights.get(&type_id)?;
+ let highlights = self.text_highlights.get(&HighlightKey::Type(type_id))?;
Some((highlights.0, &highlights.1))
}
+
+ #[cfg(feature = "test-support")]
+ pub fn all_text_highlights(
+ &self,
+ ) -> impl Iterator<Item = &Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
+ self.text_highlights.values()
+ }
+
pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
- let mut cleared = self.text_highlights.remove(&type_id).is_some();
+ let mut cleared = self
+ .text_highlights
+ .remove(&HighlightKey::Type(type_id))
+ .is_some();
cleared |= self.inlay_highlights.remove(&type_id).is_some();
cleared
}
@@ -1333,7 +1349,9 @@ impl DisplaySnapshot {
&self,
) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
let type_id = TypeId::of::<Tag>();
- self.text_highlights.get(&type_id).cloned()
+ self.text_highlights
+ .get(&HighlightKey::Type(type_id))
+ .cloned()
}
#[allow(unused)]
@@ -2294,7 +2312,7 @@ pub mod tests {
// Insert a block in the middle of a multi-line diagnostic.
map.update(cx, |map, cx| {
map.highlight_text(
- TypeId::of::<usize>(),
+ HighlightKey::Type(TypeId::of::<usize>()),
vec![
buffer_snapshot.anchor_before(Point::new(3, 9))
..buffer_snapshot.anchor_after(Point::new(3, 14)),
@@ -2616,7 +2634,7 @@ pub mod tests {
map.update(cx, |map, _cx| {
map.highlight_text(
- TypeId::of::<MyType>(),
+ HighlightKey::Type(TypeId::of::<MyType>()),
highlighted_ranges
.into_iter()
.map(|range| {
@@ -1,16 +1,15 @@
use collections::BTreeMap;
use gpui::HighlightStyle;
use language::Chunk;
-use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
+use multi_buffer::{MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
use std::{
- any::TypeId,
cmp,
iter::{self, Peekable},
ops::Range,
- sync::Arc,
vec,
};
-use sum_tree::TreeMap;
+
+use crate::display_map::{HighlightKey, TextHighlights};
pub struct CustomHighlightsChunks<'a> {
buffer_chunks: MultiBufferChunks<'a>,
@@ -19,15 +18,15 @@ pub struct CustomHighlightsChunks<'a> {
multibuffer_snapshot: &'a MultiBufferSnapshot,
highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
- active_highlights: BTreeMap<TypeId, HighlightStyle>,
- text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
+ active_highlights: BTreeMap<HighlightKey, HighlightStyle>,
+ text_highlights: Option<&'a TextHighlights>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
struct HighlightEndpoint {
offset: usize,
is_start: bool,
- tag: TypeId,
+ tag: HighlightKey,
style: HighlightStyle,
}
@@ -35,7 +34,7 @@ impl<'a> CustomHighlightsChunks<'a> {
pub fn new(
range: Range<usize>,
language_aware: bool,
- text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
+ text_highlights: Option<&'a TextHighlights>,
multibuffer_snapshot: &'a MultiBufferSnapshot,
) -> Self {
Self {
@@ -66,7 +65,7 @@ impl<'a> CustomHighlightsChunks<'a> {
fn create_highlight_endpoints(
range: &Range<usize>,
- text_highlights: Option<&TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
+ text_highlights: Option<&TextHighlights>,
buffer: &MultiBufferSnapshot,
) -> iter::Peekable<vec::IntoIter<HighlightEndpoint>> {
let mut highlight_endpoints = Vec::new();
@@ -1115,7 +1115,7 @@ mod tests {
use super::*;
use crate::{
InlayId, MultiBuffer,
- display_map::{InlayHighlights, TextHighlights},
+ display_map::{HighlightKey, InlayHighlights, TextHighlights},
hover_links::InlayHighlight,
};
use gpui::{App, HighlightStyle};
@@ -1629,7 +1629,7 @@ mod tests {
text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
log::info!("highlighting text ranges {text_highlight_ranges:?}");
text_highlights.insert(
- TypeId::of::<()>(),
+ HighlightKey::Type(TypeId::of::<()>()),
Arc::new((
HighlightStyle::default(),
text_highlight_ranges
@@ -197,7 +197,7 @@ pub use sum_tree::Bias;
use sum_tree::TreeMap;
use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
use theme::{
- ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
+ ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
observe_buffer_font_size_adjustment,
};
use ui::{
@@ -708,7 +708,7 @@ impl EditorActionId {
// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
-type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
+type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
#[derive(Default)]
@@ -1017,7 +1017,7 @@ pub struct Editor {
placeholder_text: Option<Arc<str>>,
highlight_order: usize,
highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
- background_highlights: TreeMap<TypeId, BackgroundHighlight>,
+ background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
gutter_highlights: TreeMap<TypeId, GutterHighlight>,
scrollbar_marker_state: ScrollbarMarkerState,
active_indent_guides_state: ActiveIndentGuidesState,
@@ -6180,7 +6180,7 @@ impl Editor {
editor.update(cx, |editor, cx| {
editor.highlight_background::<Self>(
&ranges_to_highlight,
- |theme| theme.editor_highlighted_line_background,
+ |theme| theme.colors().editor_highlighted_line_background,
cx,
);
});
@@ -6535,12 +6535,12 @@ impl Editor {
this.highlight_background::<DocumentHighlightRead>(
&read_ranges,
- |theme| theme.editor_document_highlight_read_background,
+ |theme| theme.colors().editor_document_highlight_read_background,
cx,
);
this.highlight_background::<DocumentHighlightWrite>(
&write_ranges,
- |theme| theme.editor_document_highlight_write_background,
+ |theme| theme.colors().editor_document_highlight_write_background,
cx,
);
cx.notify();
@@ -6642,7 +6642,7 @@ impl Editor {
if !match_ranges.is_empty() {
editor.highlight_background::<SelectedTextHighlight>(
&match_ranges,
- |theme| theme.editor_document_highlight_bracket_background,
+ |theme| theme.colors().editor_document_highlight_bracket_background,
cx,
)
}
@@ -15397,7 +15397,7 @@ impl Editor {
}
editor.highlight_background::<Self>(
&ranges,
- |theme| theme.editor_highlighted_line_background,
+ |theme| theme.colors().editor_highlighted_line_background,
cx,
);
}
@@ -18552,7 +18552,7 @@ impl Editor {
pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
self.highlight_background::<SearchWithinRange>(
ranges,
- |colors| colors.editor_document_highlight_read_background,
+ |colors| colors.colors().editor_document_highlight_read_background,
cx,
)
}
@@ -18568,11 +18568,28 @@ impl Editor {
pub fn highlight_background<T: 'static>(
&mut self,
ranges: &[Range<Anchor>],
- color_fetcher: fn(&ThemeColors) -> Hsla,
+ color_fetcher: fn(&Theme) -> Hsla,
cx: &mut Context<Self>,
) {
- self.background_highlights
- .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
+ self.background_highlights.insert(
+ HighlightKey::Type(TypeId::of::<T>()),
+ (color_fetcher, Arc::from(ranges)),
+ );
+ self.scrollbar_marker_state.dirty = true;
+ cx.notify();
+ }
+
+ pub fn highlight_background_key<T: 'static>(
+ &mut self,
+ key: usize,
+ ranges: &[Range<Anchor>],
+ color_fetcher: fn(&Theme) -> Hsla,
+ cx: &mut Context<Self>,
+ ) {
+ self.background_highlights.insert(
+ HighlightKey::TypePlus(TypeId::of::<T>(), key),
+ (color_fetcher, Arc::from(ranges)),
+ );
self.scrollbar_marker_state.dirty = true;
cx.notify();
}
@@ -18581,7 +18598,9 @@ impl Editor {
&mut self,
cx: &mut Context<Self>,
) -> Option<BackgroundHighlight> {
- let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
+ let text_highlights = self
+ .background_highlights
+ .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
if !text_highlights.1.is_empty() {
self.scrollbar_marker_state.dirty = true;
cx.notify();
@@ -18667,6 +18686,30 @@ impl Editor {
.insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
}
+ #[cfg(feature = "test-support")]
+ pub fn all_text_highlights(
+ &self,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
+ let snapshot = self.snapshot(window, cx);
+ self.display_map.update(cx, |display_map, _| {
+ display_map
+ .all_text_highlights()
+ .map(|highlight| {
+ let (style, ranges) = highlight.as_ref();
+ (
+ *style,
+ ranges
+ .iter()
+ .map(|range| range.clone().to_display_points(&snapshot))
+ .collect(),
+ )
+ })
+ .collect()
+ })
+ }
+
#[cfg(feature = "test-support")]
pub fn all_text_background_highlights(
&self,
@@ -18677,8 +18720,7 @@ impl Editor {
let buffer = &snapshot.buffer_snapshot;
let start = buffer.anchor_before(0);
let end = buffer.anchor_after(buffer.len());
- let theme = cx.theme().colors();
- self.background_highlights_in_range(start..end, &snapshot, theme)
+ self.background_highlights_in_range(start..end, &snapshot, cx.theme())
}
#[cfg(feature = "test-support")]
@@ -18687,7 +18729,9 @@ impl Editor {
let highlights = self
.background_highlights
- .get(&TypeId::of::<items::BufferSearchHighlights>());
+ .get(&HighlightKey::Type(TypeId::of::<
+ items::BufferSearchHighlights,
+ >()));
if let Some((_color, ranges)) = highlights {
ranges
@@ -18706,11 +18750,11 @@ impl Editor {
) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
let read_highlights = self
.background_highlights
- .get(&TypeId::of::<DocumentHighlightRead>())
+ .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
.map(|h| &h.1);
let write_highlights = self
.background_highlights
- .get(&TypeId::of::<DocumentHighlightWrite>())
+ .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
.map(|h| &h.1);
let left_position = position.bias_left(buffer);
let right_position = position.bias_right(buffer);
@@ -18737,7 +18781,7 @@ impl Editor {
pub fn has_background_highlights<T: 'static>(&self) -> bool {
self.background_highlights
- .get(&TypeId::of::<T>())
+ .get(&HighlightKey::Type(TypeId::of::<T>()))
.map_or(false, |(_, highlights)| !highlights.is_empty())
}
@@ -18745,7 +18789,7 @@ impl Editor {
&self,
search_range: Range<Anchor>,
display_snapshot: &DisplaySnapshot,
- theme: &ThemeColors,
+ theme: &Theme,
) -> Vec<(Range<DisplayPoint>, Hsla)> {
let mut results = Vec::new();
for (color_fetcher, ranges) in self.background_highlights.values() {
@@ -18786,7 +18830,10 @@ impl Editor {
count: usize,
) -> Vec<RangeInclusive<DisplayPoint>> {
let mut results = Vec::new();
- let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
+ let Some((_, ranges)) = self
+ .background_highlights
+ .get(&HighlightKey::Type(TypeId::of::<T>()))
+ else {
return vec![];
};
@@ -18923,6 +18970,23 @@ impl Editor {
.collect()
}
+ pub fn highlight_text_key<T: 'static>(
+ &mut self,
+ key: usize,
+ ranges: Vec<Range<Anchor>>,
+ style: HighlightStyle,
+ cx: &mut Context<Self>,
+ ) {
+ self.display_map.update(cx, |map, _| {
+ map.highlight_text(
+ HighlightKey::TypePlus(TypeId::of::<T>(), key),
+ ranges,
+ style,
+ );
+ });
+ cx.notify();
+ }
+
pub fn highlight_text<T: 'static>(
&mut self,
ranges: Vec<Range<Anchor>>,
@@ -18930,7 +18994,7 @@ impl Editor {
cx: &mut Context<Self>,
) {
self.display_map.update(cx, |map, _| {
- map.highlight_text(TypeId::of::<T>(), ranges, style)
+ map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
});
cx.notify();
}
@@ -13697,7 +13697,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
let mut highlighted_ranges = editor.background_highlights_in_range(
anchor_range(Point::new(3, 4)..Point::new(7, 4)),
&snapshot,
- cx.theme().colors(),
+ cx.theme(),
);
// Enforce a consistent ordering based on color without relying on the ordering of the
// highlight's `TypeId` which is non-executor.
@@ -13727,7 +13727,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
editor.background_highlights_in_range(
anchor_range(Point::new(5, 6)..Point::new(6, 4)),
&snapshot,
- cx.theme().colors(),
+ cx.theme(),
),
&[(
DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
@@ -20334,7 +20334,7 @@ async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
editor.highlight_background::<DocumentHighlightRead>(
&[highlight_range],
- |c| c.editor_document_highlight_read_background,
+ |theme| theme.colors().editor_document_highlight_read_background,
cx,
);
});
@@ -20412,7 +20412,7 @@ async fn test_rename_without_prepare(cx: &mut TestAppContext) {
let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
editor.highlight_background::<DocumentHighlightRead>(
&[highlight_range],
- |c| c.editor_document_highlight_read_background,
+ |theme| theme.colors().editor_document_highlight_read_background,
cx,
);
});
@@ -12,8 +12,8 @@ use crate::{
ToggleFold,
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
display_map::{
- Block, BlockContext, BlockStyle, DisplaySnapshot, EditorMargins, FoldId, HighlightedChunk,
- ToDisplayPoint,
+ Block, BlockContext, BlockStyle, DisplaySnapshot, EditorMargins, FoldId, HighlightKey,
+ HighlightedChunk, ToDisplayPoint,
},
editor_settings::{
CurrentLineHighlight, DocumentColorsRenderMode, DoubleClickInMultibuffer, MinimapThumb,
@@ -6166,13 +6166,15 @@ impl EditorElement {
background_highlights.iter()
{
let is_search_highlights = *background_highlight_id
- == TypeId::of::<BufferSearchHighlights>();
+ == HighlightKey::Type(TypeId::of::<BufferSearchHighlights>());
let is_text_highlights = *background_highlight_id
- == TypeId::of::<SelectedTextHighlight>();
+ == HighlightKey::Type(TypeId::of::<SelectedTextHighlight>());
let is_symbol_occurrences = *background_highlight_id
- == TypeId::of::<DocumentHighlightRead>()
+ == HighlightKey::Type(TypeId::of::<DocumentHighlightRead>())
|| *background_highlight_id
- == TypeId::of::<DocumentHighlightWrite>();
+ == HighlightKey::Type(
+ TypeId::of::<DocumentHighlightWrite>(),
+ );
if (is_search_highlights && scrollbar_settings.search_results)
|| (is_text_highlights && scrollbar_settings.selected_text)
|| (is_symbol_occurrences && scrollbar_settings.selected_symbol)
@@ -8091,7 +8093,7 @@ impl Element for EditorElement {
editor.read(cx).background_highlights_in_range(
start_anchor..end_anchor,
&snapshot.display_snapshot,
- cx.theme().colors(),
+ cx.theme(),
)
})
.unwrap_or_default();
@@ -40,7 +40,7 @@ pub fn refresh_matching_bracket_highlights(
opening_range.to_anchors(&snapshot.buffer_snapshot),
closing_range.to_anchors(&snapshot.buffer_snapshot),
],
- |theme| theme.editor_document_highlight_bracket_background,
+ |theme| theme.colors().editor_document_highlight_bracket_background,
cx,
)
}
@@ -520,7 +520,7 @@ fn show_hover(
// Highlight the selected symbol using a background highlight
editor.highlight_background::<HoverState>(
&hover_highlights,
- |theme| theme.element_hover, // todo update theme
+ |theme| theme.colors().element_hover, // todo update theme
cx,
);
}
@@ -2,6 +2,7 @@ use crate::{
Anchor, Autoscroll, Editor, EditorEvent, EditorSettings, ExcerptId, ExcerptRange, FormatTarget,
MultiBuffer, MultiBufferSnapshot, NavigationData, SearchWithinRange, SelectionEffects,
ToPoint as _,
+ display_map::HighlightKey,
editor_settings::SeedQuerySetting,
persistence::{DB, SerializedEditor},
scroll::ScrollAnchor,
@@ -1431,7 +1432,7 @@ impl SearchableItem for Editor {
fn get_matches(&self, _window: &mut Window, _: &mut App) -> Vec<Range<Anchor>> {
self.background_highlights
- .get(&TypeId::of::<BufferSearchHighlights>())
+ .get(&HighlightKey::Type(TypeId::of::<BufferSearchHighlights>()))
.map_or(Vec::new(), |(_color, ranges)| {
ranges.iter().cloned().collect()
})
@@ -1454,12 +1455,12 @@ impl SearchableItem for Editor {
) {
let existing_range = self
.background_highlights
- .get(&TypeId::of::<BufferSearchHighlights>())
+ .get(&HighlightKey::Type(TypeId::of::<BufferSearchHighlights>()))
.map(|(_, range)| range.as_ref());
let updated = existing_range != Some(matches);
self.highlight_background::<BufferSearchHighlights>(
matches,
- |theme| theme.search_match_background,
+ |theme| theme.colors().search_match_background,
cx,
);
if updated {
@@ -1701,7 +1702,7 @@ impl SearchableItem for Editor {
let buffer = self.buffer().read(cx).snapshot(cx);
let search_within_ranges = self
.background_highlights
- .get(&TypeId::of::<SearchWithinRange>())
+ .get(&HighlightKey::Type(TypeId::of::<SearchWithinRange>()))
.map_or(vec![], |(_color, ranges)| {
ranges.iter().cloned().collect::<Vec<_>>()
});
@@ -1,6 +1,6 @@
use crate::{
AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, RowExt,
- display_map::ToDisplayPoint,
+ display_map::{HighlightKey, ToDisplayPoint},
};
use buffer_diff::DiffHunkStatusKind;
use collections::BTreeMap;
@@ -509,7 +509,7 @@ impl EditorTestContext {
let snapshot = editor.snapshot(window, cx);
editor
.background_highlights
- .get(&TypeId::of::<Tag>())
+ .get(&HighlightKey::Type(TypeId::of::<Tag>()))
.map(|h| h.1.clone())
.unwrap_or_default()
.iter()
@@ -358,7 +358,7 @@ impl Render for SyntaxTreeView {
editor.clear_background_highlights::<Self>( cx);
editor.highlight_background::<Self>(
&[range],
- |theme| theme.editor_document_highlight_write_background,
+ |theme| theme.colors().editor_document_highlight_write_background,
cx,
);
});
@@ -1547,7 +1547,7 @@ fn dap_client_capabilities(adapter_id: String) -> InitializeRequestArguments {
supports_memory_event: Some(false),
supports_args_can_be_interpreted_by_shell: Some(false),
supports_start_debugging_request: Some(true),
- supports_ansistyling: Some(false),
+ supports_ansistyling: Some(true),
}
}
@@ -1377,7 +1377,7 @@ impl ProjectSearchView {
}
editor.highlight_background::<Self>(
&match_ranges,
- |theme| theme.search_match_background,
+ |theme| theme.colors().search_match_background,
cx,
);
});
@@ -222,7 +222,7 @@ impl Vim {
editor.highlight_background::<HighlightOnYank>(
&ranges_to_highlight,
- |colors| colors.editor_document_highlight_read_background,
+ |colors| colors.colors().editor_document_highlight_read_background,
cx,
);
cx.spawn(async move |this, cx| {
@@ -261,7 +261,7 @@ impl Vim {
let ranges = [new_range];
editor.highlight_background::<VimExchange>(
&ranges,
- |theme| theme.editor_document_highlight_read_background,
+ |theme| theme.colors().editor_document_highlight_read_background,
cx,
);
}