diff --git a/Cargo.lock b/Cargo.lock index b72773ae98..1669bf0ddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4268,6 +4268,7 @@ dependencies = [ name = "debugger_ui" version = "0.1.0" dependencies = [ + "alacritty_terminal", "anyhow", "client", "collections", diff --git a/crates/debugger_ui/Cargo.toml b/crates/debugger_ui/Cargo.toml index c837d16dd1..17570bd3d9 100644 --- a/crates/debugger_ui/Cargo.toml +++ b/crates/debugger_ui/Cargo.toml @@ -26,6 +26,7 @@ test-support = [ ] [dependencies] +alacritty_terminal.workspace = true anyhow.workspace = true client.workspace = true collections.workspace = true diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index d872f0f636..06cf408316 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -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(); - for event in events { - use std::fmt::Write; + console.set_read_only(false); - _ = write!(to_insert, "{}\n", event.output.trim_end()); + for event in events { + let to_insert = format!("{}\n", event.output.trim_end()); + + let mut ansi_handler = ConsoleHandler::default(); + let mut ansi_processor = ansi::Processor::::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::( + start_offset, + vec![range], + style, + cx, + ); + } + + 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::( + 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, Option)>, + background_spans: Vec<(Range, Option)>, + current_range_start: usize, + current_background_range_start: usize, + current_color: Option, + current_background_color: Option, + pos: usize, +} + +impl ConsoleHandler { + fn break_span(&mut self, color: Option) { + 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) { + 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); + } + _ => {} + } + } +} diff --git a/crates/debugger_ui/src/tests/console.rs b/crates/debugger_ui/src/tests/console.rs index 2c31c87f54..cae2ff3501 100644 --- a/crates/debugger_ui/src/tests/console.rs +++ b/crates/debugger_ui/src/tests/console.rs @@ -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::(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::(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::(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::>(); + 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::>() + }); + 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); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 7417fec81a..fd371e20cb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -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>)>>; +type TextHighlights = TreeMap>)>>; type InlayHighlights = TreeMap>; /// 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>, 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])> { - 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>)>> { + 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>)>> { let type_id = TypeId::of::(); - 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::(), + HighlightKey::Type(TypeId::of::()), 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::(), + HighlightKey::Type(TypeId::of::()), highlighted_ranges .into_iter() .map(|range| { diff --git a/crates/editor/src/display_map/custom_highlights.rs b/crates/editor/src/display_map/custom_highlights.rs index 11356586eb..ae69e9cf8c 100644 --- a/crates/editor/src/display_map/custom_highlights.rs +++ b/crates/editor/src/display_map/custom_highlights.rs @@ -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>, - active_highlights: BTreeMap, - text_highlights: Option<&'a TreeMap>)>>>, + active_highlights: BTreeMap, + 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, language_aware: bool, - text_highlights: Option<&'a TreeMap>)>>>, + 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, - text_highlights: Option<&TreeMap>)>>>, + text_highlights: Option<&TextHighlights>, buffer: &MultiBufferSnapshot, ) -> iter::Peekable> { let mut highlight_endpoints = Vec::new(); diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index d97c141f0c..33fc5540d6 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -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 diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ad8c3fbe33..d5032d4485 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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; -type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range]>); +type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range]>); type GutterHighlight = (fn(&App) -> Hsla, Vec>); #[derive(Default)] @@ -1017,7 +1017,7 @@ pub struct Editor { placeholder_text: Option>, highlight_order: usize, highlighted_rows: HashMap>, - background_highlights: TreeMap, + background_highlights: TreeMap, gutter_highlights: TreeMap, scrollbar_marker_state: ScrollbarMarkerState, active_indent_guides_state: ActiveIndentGuidesState, @@ -6180,7 +6180,7 @@ impl Editor { editor.update(cx, |editor, cx| { editor.highlight_background::( &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::( &read_ranges, - |theme| theme.editor_document_highlight_read_background, + |theme| theme.colors().editor_document_highlight_read_background, cx, ); this.highlight_background::( &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::( &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::( &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], cx: &mut Context) { self.highlight_background::( 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( &mut self, ranges: &[Range], - color_fetcher: fn(&ThemeColors) -> Hsla, + color_fetcher: fn(&Theme) -> Hsla, cx: &mut Context, ) { - self.background_highlights - .insert(TypeId::of::(), (color_fetcher, Arc::from(ranges))); + self.background_highlights.insert( + HighlightKey::Type(TypeId::of::()), + (color_fetcher, Arc::from(ranges)), + ); + self.scrollbar_marker_state.dirty = true; + cx.notify(); + } + + pub fn highlight_background_key( + &mut self, + key: usize, + ranges: &[Range], + color_fetcher: fn(&Theme) -> Hsla, + cx: &mut Context, + ) { + self.background_highlights.insert( + HighlightKey::TypePlus(TypeId::of::(), 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, ) -> Option { - let text_highlights = self.background_highlights.remove(&TypeId::of::())?; + let text_highlights = self + .background_highlights + .remove(&HighlightKey::Type(TypeId::of::()))?; if !text_highlights.1.is_empty() { self.scrollbar_marker_state.dirty = true; cx.notify(); @@ -18667,6 +18686,30 @@ impl Editor { .insert(TypeId::of::(), (color_fetcher, gutter_highlights)); } + #[cfg(feature = "test-support")] + pub fn all_text_highlights( + &self, + window: &mut Window, + cx: &mut Context, + ) -> Vec<(HighlightStyle, Vec>)> { + 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::()); + .get(&HighlightKey::Type(TypeId::of::< + items::BufferSearchHighlights, + >())); if let Some((_color, ranges)) = highlights { ranges @@ -18706,11 +18750,11 @@ impl Editor { ) -> impl 'a + Iterator> { let read_highlights = self .background_highlights - .get(&TypeId::of::()) + .get(&HighlightKey::Type(TypeId::of::())) .map(|h| &h.1); let write_highlights = self .background_highlights - .get(&TypeId::of::()) + .get(&HighlightKey::Type(TypeId::of::())) .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(&self) -> bool { self.background_highlights - .get(&TypeId::of::()) + .get(&HighlightKey::Type(TypeId::of::())) .map_or(false, |(_, highlights)| !highlights.is_empty()) } @@ -18745,7 +18789,7 @@ impl Editor { &self, search_range: Range, display_snapshot: &DisplaySnapshot, - theme: &ThemeColors, + theme: &Theme, ) -> Vec<(Range, Hsla)> { let mut results = Vec::new(); for (color_fetcher, ranges) in self.background_highlights.values() { @@ -18786,7 +18830,10 @@ impl Editor { count: usize, ) -> Vec> { let mut results = Vec::new(); - let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::()) else { + let Some((_, ranges)) = self + .background_highlights + .get(&HighlightKey::Type(TypeId::of::())) + else { return vec![]; }; @@ -18923,6 +18970,23 @@ impl Editor { .collect() } + pub fn highlight_text_key( + &mut self, + key: usize, + ranges: Vec>, + style: HighlightStyle, + cx: &mut Context, + ) { + self.display_map.update(cx, |map, _| { + map.highlight_text( + HighlightKey::TypePlus(TypeId::of::(), key), + ranges, + style, + ); + }); + cx.notify(); + } + pub fn highlight_text( &mut self, ranges: Vec>, @@ -18930,7 +18994,7 @@ impl Editor { cx: &mut Context, ) { self.display_map.update(cx, |map, _| { - map.highlight_text(TypeId::of::(), ranges, style) + map.highlight_text(HighlightKey::Type(TypeId::of::()), ranges, style) }); cx.notify(); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 88d18930a0..d53b75a21a 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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::( &[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::( &[highlight_range], - |c| c.editor_document_highlight_read_background, + |theme| theme.colors().editor_document_highlight_read_background, cx, ); }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 77d040ec76..4d3e571cce 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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::(); + == HighlightKey::Type(TypeId::of::()); let is_text_highlights = *background_highlight_id - == TypeId::of::(); + == HighlightKey::Type(TypeId::of::()); let is_symbol_occurrences = *background_highlight_id - == TypeId::of::() + == HighlightKey::Type(TypeId::of::()) || *background_highlight_id - == TypeId::of::(); + == HighlightKey::Type( + TypeId::of::(), + ); 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(); diff --git a/crates/editor/src/highlight_matching_bracket.rs b/crates/editor/src/highlight_matching_bracket.rs index 1e4477eea8..e38197283d 100644 --- a/crates/editor/src/highlight_matching_bracket.rs +++ b/crates/editor/src/highlight_matching_bracket.rs @@ -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, ) } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 2411b26ff1..b174a3ba62 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -520,7 +520,7 @@ fn show_hover( // Highlight the selected symbol using a background highlight editor.highlight_background::( &hover_highlights, - |theme| theme.element_hover, // todo update theme + |theme| theme.colors().element_hover, // todo update theme cx, ); } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f5b8b0b64d..93a80d7764 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -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> { self.background_highlights - .get(&TypeId::of::()) + .get(&HighlightKey::Type(TypeId::of::())) .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::()) + .get(&HighlightKey::Type(TypeId::of::())) .map(|(_, range)| range.as_ref()); let updated = existing_range != Some(matches); self.highlight_background::( 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::()) + .get(&HighlightKey::Type(TypeId::of::())) .map_or(vec![], |(_color, ranges)| { ranges.iter().cloned().collect::>() }); diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index dfb41096cd..195abbe6d9 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -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::()) + .get(&HighlightKey::Type(TypeId::of::())) .map(|h| h.1.clone()) .unwrap_or_default() .iter() diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index e88b3a825b..99132ce452 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -358,7 +358,7 @@ impl Render for SyntaxTreeView { editor.clear_background_highlights::( cx); editor.highlight_background::( &[range], - |theme| theme.editor_document_highlight_write_background, + |theme| theme.colors().editor_document_highlight_write_background, cx, ); }); diff --git a/crates/project/src/debugger/dap_command.rs b/crates/project/src/debugger/dap_command.rs index 6bd5746733..735444b3f3 100644 --- a/crates/project/src/debugger/dap_command.rs +++ b/crates/project/src/debugger/dap_command.rs @@ -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), } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index b70c212d5a..79ae18fcfe 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1377,7 +1377,7 @@ impl ProjectSearchView { } editor.highlight_background::( &match_ranges, - |theme| theme.search_match_background, + |theme| theme.colors().search_match_background, cx, ); }); diff --git a/crates/vim/src/normal/yank.rs b/crates/vim/src/normal/yank.rs index 6f83b954b2..3525b0d43f 100644 --- a/crates/vim/src/normal/yank.rs +++ b/crates/vim/src/normal/yank.rs @@ -222,7 +222,7 @@ impl Vim { editor.highlight_background::( &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| { diff --git a/crates/vim/src/replace.rs b/crates/vim/src/replace.rs index f975aefa33..5f407db5cb 100644 --- a/crates/vim/src/replace.rs +++ b/crates/vim/src/replace.rs @@ -261,7 +261,7 @@ impl Vim { let ranges = [new_range]; editor.highlight_background::( &ranges, - |theme| theme.editor_document_highlight_read_background, + |theme| theme.colors().editor_document_highlight_read_background, cx, ); }