From 3882323f7978e17a836830454149cbdd11975c63 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 15 Oct 2025 12:16:56 +0200 Subject: [PATCH] language: Assert `CodeLabel` text ranges are correct (#40242) Release Notes: - N/A *or* Added/Fixed/Improved ... --- .../agent_ui/src/acp/completion_provider.rs | 8 +- .../src/context_picker/completion_provider.rs | 13 +- .../src/assistant_slash_command.rs | 7 +- .../src/file_command.rs | 8 +- .../src/tab_command.rs | 8 +- .../src/session/running/console.rs | 12 +- crates/editor/src/code_context_menus.rs | 6 +- crates/editor/src/editor.rs | 6 +- crates/editor/src/editor_tests.rs | 7 +- crates/file_finder/src/open_path_prompt.rs | 4 +- crates/gpui/src/elements/text.rs | 20 ++- crates/gpui/src/text_system/line_wrapper.rs | 6 +- crates/language/src/language.rs | 72 +++++++++-- .../src/extension_lsp_adapter.rs | 12 +- crates/languages/src/c.rs | 26 ++-- crates/languages/src/go.rs | 76 ++++-------- crates/languages/src/python.rs | 71 +++++------ crates/languages/src/rust.rs | 114 +++++++++--------- crates/languages/src/typescript.rs | 13 +- crates/languages/src/vtsls.rs | 13 +- crates/project/src/lsp_store.rs | 32 ++--- 21 files changed, 245 insertions(+), 289 deletions(-) diff --git a/crates/agent_ui/src/acp/completion_provider.rs b/crates/agent_ui/src/acp/completion_provider.rs index 684b8f2d0d764fe257e61124e217242ff6df29f3..7588e9f53b32302b3a078f44b3cf85be56ca1b4b 100644 --- a/crates/agent_ui/src/acp/completion_provider.rs +++ b/crates/agent_ui/src/acp/completion_provider.rs @@ -12,7 +12,7 @@ use anyhow::Result; use editor::{CompletionProvider, Editor, ExcerptId}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{App, Entity, Task, WeakEntity}; -use language::{Buffer, CodeLabel, HighlightId}; +use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId}; use lsp::CompletionContext; use project::lsp_store::{CompletionDocumentation, SymbolLocation}; use project::{ @@ -673,7 +673,7 @@ impl ContextPickerCompletionProvider { fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel { let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); - let mut label = CodeLabel::default(); + let mut label = CodeLabelBuilder::default(); label.push_str(file_name, None); label.push_str(" ", None); @@ -682,9 +682,7 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: label.push_str(directory, comment_id); } - label.filter_range = 0..label.text().len(); - - label + label.build() } impl CompletionProvider for ContextPickerCompletionProvider { diff --git a/crates/agent_ui/src/context_picker/completion_provider.rs b/crates/agent_ui/src/context_picker/completion_provider.rs index 33a5a621a1d1ea23ccdb49fd97010fea1856ce80..e030779eb8c37347410507a74d27299dbcdfbf7d 100644 --- a/crates/agent_ui/src/context_picker/completion_provider.rs +++ b/crates/agent_ui/src/context_picker/completion_provider.rs @@ -11,7 +11,7 @@ use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{App, Entity, Task, WeakEntity}; use http_client::HttpClientWithUrl; use itertools::Itertools; -use language::{Buffer, CodeLabel, HighlightId}; +use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId}; use lsp::CompletionContext; use project::lsp_store::SymbolLocation; use project::{ @@ -686,7 +686,8 @@ impl ContextPickerCompletionProvider { }; let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); - let mut label = CodeLabel::plain(symbol.name.clone(), None); + let mut label = CodeLabelBuilder::default(); + label.push_str(&symbol.name, None); label.push_str(" ", None); label.push_str(&file_name, comment_id); label.push_str(&format!(" L{}", symbol.range.start.0.row + 1), comment_id); @@ -696,7 +697,7 @@ impl ContextPickerCompletionProvider { Some(Completion { replace_range: source_range.clone(), new_text, - label, + label: label.build(), documentation: None, source: project::CompletionSource::Custom, icon_path: Some(IconName::Code.path().into()), @@ -729,7 +730,7 @@ impl ContextPickerCompletionProvider { fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel { let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); - let mut label = CodeLabel::default(); + let mut label = CodeLabelBuilder::default(); label.push_str(file_name, None); label.push_str(" ", None); @@ -738,9 +739,7 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: label.push_str(directory, comment_id); } - label.filter_range = 0..label.text().len(); - - label + label.build() } impl CompletionProvider for ContextPickerCompletionProvider { diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index 4b85fa2edf2afd6b3ea7df154b5e14ab492a8013..2e6bb7325e14ac109d77854e1d848c541a685458 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -9,6 +9,7 @@ use anyhow::Result; use futures::StreamExt; use futures::stream::{self, BoxStream}; use gpui::{App, SharedString, Task, WeakEntity, Window}; +use language::CodeLabelBuilder; use language::HighlightId; use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt}; pub use language_model::Role; @@ -328,15 +329,15 @@ impl SlashCommandLine { } pub fn create_label_for_command(command_name: &str, arguments: &[&str], cx: &App) -> CodeLabel { - let mut label = CodeLabel::default(); + let mut label = CodeLabelBuilder::default(); label.push_str(command_name, None); + label.respan_filter_range(None); label.push_str(" ", None); label.push_str( &arguments.join(" "), cx.theme().syntax().highlight_id("comment").map(HighlightId), ); - label.filter_range = 0..command_name.len(); - label + label.build() } #[cfg(test)] diff --git a/crates/assistant_slash_commands/src/file_command.rs b/crates/assistant_slash_commands/src/file_command.rs index 0968a297b82bb0da783ec18fb1cd0301acf50f4c..6fe1a410d3551fe72737500ad8b143a392645d1b 100644 --- a/crates/assistant_slash_commands/src/file_command.rs +++ b/crates/assistant_slash_commands/src/file_command.rs @@ -7,7 +7,7 @@ use futures::Stream; use futures::channel::mpsc; use fuzzy::PathMatch; use gpui::{App, Entity, Task, WeakEntity}; -use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate}; +use language::{BufferSnapshot, CodeLabelBuilder, HighlightId, LineEnding, LspAdapterDelegate}; use project::{PathMatchCandidateSet, Project}; use serde::{Deserialize, Serialize}; use smol::stream::StreamExt; @@ -168,7 +168,7 @@ impl SlashCommand for FileSlashCommand { .display(path_style) .to_string(); - let mut label = CodeLabel::default(); + let mut label = CodeLabelBuilder::default(); let file_name = path_match.path.file_name()?; let label_text = if path_match.is_dir { format!("{}/ ", file_name) @@ -178,10 +178,10 @@ impl SlashCommand for FileSlashCommand { label.push_str(label_text.as_str(), None); label.push_str(&text, comment_id); - label.filter_range = 0..file_name.len(); + label.respan_filter_range(Some(file_name)); Some(ArgumentCompletion { - label, + label: label.build(), new_text: text, after_completion: AfterCompletion::Compose, replace_previous_arguments: false, diff --git a/crates/assistant_slash_commands/src/tab_command.rs b/crates/assistant_slash_commands/src/tab_command.rs index 9fd38128cacd51db5bd48fc801d1238ae3f674c4..a4c0ad412cca3eaf7d03d684cc3fb828be60a93d 100644 --- a/crates/assistant_slash_commands/src/tab_command.rs +++ b/crates/assistant_slash_commands/src/tab_command.rs @@ -7,7 +7,7 @@ use collections::{HashMap, HashSet}; use editor::Editor; use futures::future::join_all; use gpui::{Task, WeakEntity}; -use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate}; +use language::{BufferSnapshot, CodeLabel, CodeLabelBuilder, HighlightId, LspAdapterDelegate}; use std::sync::{Arc, atomic::AtomicBool}; use ui::{ActiveTheme, App, Window, prelude::*}; use util::{ResultExt, paths::PathStyle}; @@ -308,10 +308,10 @@ fn create_tab_completion_label( comment_id: Option, ) -> CodeLabel { let (parent_path, file_name) = path_style.split(path); - let mut label = CodeLabel::default(); + let mut label = CodeLabelBuilder::default(); label.push_str(file_name, None); label.push_str(" ", None); label.push_str(parent_path.unwrap_or_default(), comment_id); - label.filter_range = 0..file_name.len(); - label + label.respan_filter_range(Some(file_name)); + label.build() } diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index cf7b59f2fe96bb031fc1ed1a5d7ae4005dd37eb9..635955dda9c1c26d7e501c7991d2afc9fa1c9bb1 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -669,11 +669,7 @@ impl ConsoleQueryBarCompletionProvider { &snapshot, ), new_text: string_match.string.clone(), - label: CodeLabel { - filter_range: 0..string_match.string.len(), - text: string_match.string.clone(), - runs: Vec::new(), - }, + label: CodeLabel::plain(string_match.string.clone(), None), icon_path: None, documentation: Some(CompletionDocumentation::MultiLineMarkdown( variable_value.into(), @@ -782,11 +778,7 @@ impl ConsoleQueryBarCompletionProvider { &snapshot, ), new_text, - label: CodeLabel { - filter_range: 0..completion.label.len(), - text: completion.label, - runs: Vec::new(), - }, + label: CodeLabel::plain(completion.label, None), icon_path: None, documentation: completion.detail.map(|detail| { CompletionDocumentation::MultiLineMarkdown(detail.into()) diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 9f8de1ac452260bb8d3250fd0d30beeecd1d7c9e..359c985ee9208a1a83e3458635df883c2cf991a8 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -328,11 +328,7 @@ impl CompletionsMenu { .map(|choice| Completion { replace_range: selection.start.text_anchor..selection.end.text_anchor, new_text: choice.to_string(), - label: CodeLabel { - text: choice.to_string(), - runs: Default::default(), - filter_range: Default::default(), - }, + label: CodeLabel::plain(choice.to_string(), None), icon_path: None, documentation: None, confirm: None, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3767c863b151641a3e9bce1277c01aff83dc99f2..43f2e2ba5d2d1cb1b0a808eee388a75ccbfb02ab 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -23077,11 +23077,7 @@ fn snippet_completions( }), lsp_defaults: None, }, - label: CodeLabel { - text: matching_prefix.clone(), - runs: Vec::new(), - filter_range: 0..matching_prefix.len(), - }, + label: CodeLabel::plain(matching_prefix.clone(), None), icon_path: None, documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText { single_line: snippet.name.clone().into(), diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ede8d1c20d664ecaa5f26955de85a4dbc5313a5e..dab89557cf137b2c75eb5690011d8914d29a358f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -14878,12 +14878,7 @@ async fn test_multiline_completion(cx: &mut TestAppContext) { } else { item.label.clone() }; - let len = text.len(); - Some(language::CodeLabel { - text, - runs: Vec::new(), - filter_range: 0..len, - }) + Some(language::CodeLabel::plain(text, None)) })), ..FakeLspAdapter::default() }, diff --git a/crates/file_finder/src/open_path_prompt.rs b/crates/file_finder/src/open_path_prompt.rs index b0417b1d13fc4f82c8b16b0ac87249405b6f4129..b199c61a5e3b78ab656add6b0b1cfcc80a3ada74 100644 --- a/crates/file_finder/src/open_path_prompt.rs +++ b/crates/file_finder/src/open_path_prompt.rs @@ -755,7 +755,7 @@ impl PickerDelegate for OpenPathDelegate { .with_default_highlights( &window.text_style(), vec![( - delta..delta + label_len, + delta..label_len, HighlightStyle::color(Color::Conflict.color(cx)), )], ) @@ -765,7 +765,7 @@ impl PickerDelegate for OpenPathDelegate { .with_default_highlights( &window.text_style(), vec![( - delta..delta + label_len, + delta..label_len, HighlightStyle::color(Color::Created.color(cx)), )], ) diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index b5e071279623611685ea744e38b072284e764e2a..5d34ccfa5d78e09d47b36a2e061fdaa0fbbca45f 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -180,8 +180,7 @@ impl StyledText { "Can't use `with_default_highlights` and `with_highlights`" ); let runs = Self::compute_runs(&self.text, default_style, highlights); - self.runs = Some(runs); - self + self.with_runs(runs) } /// Set the styling attributes for the given text, as well as @@ -194,7 +193,15 @@ impl StyledText { self.runs.is_none(), "Can't use `with_highlights` and `with_default_highlights`" ); - self.delayed_highlights = Some(highlights.into_iter().collect::>()); + self.delayed_highlights = Some( + highlights + .into_iter() + .inspect(|(run, _)| { + debug_assert!(self.text.is_char_boundary(run.start)); + debug_assert!(self.text.is_char_boundary(run.end)); + }) + .collect::>(), + ); self } @@ -207,8 +214,10 @@ impl StyledText { let mut ix = 0; for (range, highlight) in highlights { if ix < range.start { + debug_assert!(text.is_char_boundary(range.start)); runs.push(default_style.clone().to_run(range.start - ix)); } + debug_assert!(text.is_char_boundary(range.end)); runs.push( default_style .clone() @@ -225,6 +234,11 @@ impl StyledText { /// Set the text runs for this piece of text. pub fn with_runs(mut self, runs: Vec) -> Self { + let mut text = &**self.text; + for run in &runs { + text = text.get(run.len..).expect("invalid text run"); + } + assert!(text.is_empty(), "invalid text run"); self.runs = Some(runs); self } diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index d499d78551a5e0e268b575496bbdac5ddf59369c..ea97cbf0d36a393976809d9aee5cb0d4f2dfab63 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -225,19 +225,15 @@ impl LineWrapper { fn update_runs_after_truncation(result: &str, ellipsis: &str, runs: &mut Vec) { let mut truncate_at = result.len() - ellipsis.len(); - let mut run_end = None; for (run_index, run) in runs.iter_mut().enumerate() { if run.len <= truncate_at { truncate_at -= run.len; } else { run.len = truncate_at + ellipsis.len(); - run_end = Some(run_index + 1); + runs.truncate(run_index + 1); break; } } - if let Some(run_end) = run_end { - runs.truncate(run_end); - } } /// A fragment of a line that can be wrapped. diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c16e90bd0f6c02fe49e2845ab24f8d767b32d82b..fb523aeb9e84e49f4d4c9afb0583eacc1e844030 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -670,6 +670,16 @@ pub struct CodeLabel { pub filter_range: Range, } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CodeLabelBuilder { + /// The text to display. + text: String, + /// Syntax highlighting runs. + runs: Vec<(Range, HighlightId)>, + /// The portion of the text that should be used in fuzzy filtering. + filter_range: Range, +} + #[derive(Clone, Deserialize, JsonSchema)] pub struct LanguageConfig { /// Human-readable name of the language. @@ -2223,6 +2233,34 @@ impl Grammar { } } +impl CodeLabelBuilder { + pub fn respan_filter_range(&mut self, filter_text: Option<&str>) { + self.filter_range = filter_text + .and_then(|filter| self.text.find(filter).map(|ix| ix..ix + filter.len())) + .unwrap_or(0..self.text.len()); + } + + pub fn push_str(&mut self, text: &str, highlight: Option) { + let start_ix = self.text.len(); + self.text.push_str(text); + if let Some(highlight) = highlight { + let end_ix = self.text.len(); + self.runs.push((start_ix..end_ix, highlight)); + } + } + + pub fn build(mut self) -> CodeLabel { + if self.filter_range.end == 0 { + self.respan_filter_range(None); + } + CodeLabel { + text: self.text, + runs: self.runs, + filter_range: self.filter_range, + } + } +} + impl CodeLabel { pub fn fallback_for_completion( item: &lsp::CompletionItem, @@ -2286,25 +2324,39 @@ impl CodeLabel { } pub fn plain(text: String, filter_text: Option<&str>) -> Self { + Self::filtered(text, filter_text, Vec::new()) + } + + pub fn filtered( + text: String, + filter_text: Option<&str>, + runs: Vec<(Range, HighlightId)>, + ) -> Self { let filter_range = filter_text .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .unwrap_or(0..text.len()); + Self::new(text, filter_range, runs) + } + + pub fn new( + text: String, + filter_range: Range, + runs: Vec<(Range, HighlightId)>, + ) -> Self { + assert!( + text.get(filter_range.clone()).is_some(), + "invalid filter range" + ); + runs.iter().for_each(|(range, _)| { + assert!(text.get(range.clone()).is_some(), "invalid run range"); + }); Self { - runs: Vec::new(), + runs, filter_range, text, } } - pub fn push_str(&mut self, text: &str, highlight: Option) { - let start_ix = self.text.len(); - self.text.push_str(text); - let end_ix = self.text.len(); - if let Some(highlight) = highlight { - self.runs.push((start_ix..end_ix, highlight)); - } - } - pub fn text(&self) -> &str { self.text.as_str() } diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs index 407b18314f025b7d3b3f43b0735fd0265f6eb9c4..01b726748649e29b4fe69ce26df5564819894985 100644 --- a/crates/language_extension/src/extension_lsp_adapter.rs +++ b/crates/language_extension/src/extension_lsp_adapter.rs @@ -463,11 +463,7 @@ fn build_code_label( let filter_range = label.filter_range.clone(); text.get(filter_range.clone())?; - Some(CodeLabel { - text, - runs, - filter_range, - }) + Some(CodeLabel::new(text, filter_range, runs)) } fn lsp_completion_to_extension(value: lsp::CompletionItem) -> extension::Completion { @@ -615,11 +611,7 @@ fn test_build_code_label() { assert_eq!( label, - CodeLabel { - text: label_text, - runs: label_runs, - filter_range: label.filter_range.clone() - } + CodeLabel::new(label_text, label.filter_range.clone(), label_runs) ) } diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index f30120a635655af6c11889d3af110e6c2dca81fc..3463f4505044c83c9ba8a0e602cf5bfa82e93e3f 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -188,11 +188,7 @@ impl super::LspAdapter for CLspAdapter { .map(|start| start..start + filter_text.len()) }) .unwrap_or(detail.len() + 1..text.len()); - return Some(CodeLabel { - filter_range, - text, - runs, - }); + return Some(CodeLabel::new(text, filter_range, runs)); } Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE) if completion.detail.is_some() => @@ -208,11 +204,7 @@ impl super::LspAdapter for CLspAdapter { .map(|start| start..start + filter_text.len()) }) .unwrap_or(detail.len() + 1..text.len()); - return Some(CodeLabel { - filter_range, - text, - runs, - }); + return Some(CodeLabel::new(text, filter_range, runs)); } Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD) if completion.detail.is_some() => @@ -236,11 +228,7 @@ impl super::LspAdapter for CLspAdapter { filter_start..filter_end }); - return Some(CodeLabel { - filter_range, - text, - runs, - }); + return Some(CodeLabel::new(text, filter_range, runs)); } Some(kind) => { let highlight_name = match kind { @@ -324,11 +312,11 @@ impl super::LspAdapter for CLspAdapter { _ => return None, }; - Some(CodeLabel { - runs: language.highlight_text(&text.as_str().into(), display_range.clone()), - text: text[display_range].to_string(), + Some(CodeLabel::new( + text[display_range.clone()].to_string(), filter_range, - }) + language.highlight_text(&text.as_str().into(), display_range), + )) } fn prepare_initialize_params( diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 13a4cec85ff8554cd14cb835a4320662f79a41d4..fb5b24c5098422d2dfe0b80674ce74398b245cb2 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -231,11 +231,7 @@ impl LspAdapter for GoLspAdapter { .map(|start| start..start + filter_text.len()) }) .unwrap_or(0..label.len()); - return Some(CodeLabel { - text, - runs, - filter_range, - }); + return Some(CodeLabel::new(text, filter_range, runs)); } Some(( lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE, @@ -256,11 +252,7 @@ impl LspAdapter for GoLspAdapter { .map(|start| start..start + filter_text.len()) }) .unwrap_or(0..label.len()); - return Some(CodeLabel { - text, - runs, - filter_range, - }); + return Some(CodeLabel::new(text, filter_range, runs)); } Some((lsp::CompletionItemKind::STRUCT, _)) => { let text = format!("{label} struct {{}}"); @@ -277,11 +269,7 @@ impl LspAdapter for GoLspAdapter { .map(|start| start..start + filter_text.len()) }) .unwrap_or(0..label.len()); - return Some(CodeLabel { - text, - runs, - filter_range, - }); + return Some(CodeLabel::new(text, filter_range, runs)); } Some((lsp::CompletionItemKind::INTERFACE, _)) => { let text = format!("{label} interface {{}}"); @@ -298,11 +286,7 @@ impl LspAdapter for GoLspAdapter { .map(|start| start..start + filter_text.len()) }) .unwrap_or(0..label.len()); - return Some(CodeLabel { - text, - runs, - filter_range, - }); + return Some(CodeLabel::new(text, filter_range, runs)); } Some((lsp::CompletionItemKind::FIELD, detail)) => { let text = format!("{label} {detail}"); @@ -320,11 +304,7 @@ impl LspAdapter for GoLspAdapter { .map(|start| start..start + filter_text.len()) }) .unwrap_or(0..label.len()); - return Some(CodeLabel { - text, - runs, - filter_range, - }); + return Some(CodeLabel::new(text, filter_range, runs)); } Some((lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD, detail)) => { if let Some(signature) = detail.strip_prefix("func") { @@ -342,11 +322,7 @@ impl LspAdapter for GoLspAdapter { .map(|start| start..start + filter_text.len()) }) .unwrap_or(0..label.len()); - return Some(CodeLabel { - filter_range, - text, - runs, - }); + return Some(CodeLabel::new(text, filter_range, runs)); } } _ => {} @@ -406,11 +382,11 @@ impl LspAdapter for GoLspAdapter { _ => return None, }; - Some(CodeLabel { - runs: language.highlight_text(&text.as_str().into(), display_range.clone()), - text: text[display_range].to_string(), + Some(CodeLabel::new( + text[display_range.clone()].to_string(), filter_range, - }) + language.highlight_text(&text.as_str().into(), display_range), + )) } fn diagnostic_message_to_markdown(&self, message: &str) -> Option { @@ -810,15 +786,15 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "Hello(a B) c.D".to_string(), - filter_range: 0..5, - runs: vec![ + Some(CodeLabel::new( + "Hello(a B) c.D".to_string(), + 0..5, + vec![ (0..5, highlight_function), (8..9, highlight_type), (13..14, highlight_type), - ], - }) + ] + )) ); // Nested methods @@ -834,15 +810,15 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "one.two.Three() [3]interface{}".to_string(), - filter_range: 0..13, - runs: vec![ + Some(CodeLabel::new( + "one.two.Three() [3]interface{}".to_string(), + 0..13, + vec![ (8..13, highlight_function), (17..18, highlight_number), (19..28, highlight_keyword), ], - }) + )) ); // Nested fields @@ -858,11 +834,11 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "two.Three a.Bcd".to_string(), - filter_range: 0..9, - runs: vec![(4..9, highlight_field), (12..15, highlight_type)], - }) + Some(CodeLabel::new( + "two.Three a.Bcd".to_string(), + 0..9, + vec![(4..9, highlight_field), (12..15, highlight_type)], + )) ); } diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index ecd3dd3ec94711758a26a96d19ebd4ebad3bf5a4..d0bdff37676c0a7fc6e4ba4930ad461497dd790d 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -407,11 +407,6 @@ impl LspAdapter for PyrightLspAdapter { return None; } }; - let filter_range = item - .filter_text - .as_deref() - .and_then(|filter| label.find(filter).map(|ix| ix..ix + filter.len())) - .unwrap_or(0..label.len()); let mut text = label.clone(); if let Some(completion_details) = item .label_details @@ -420,14 +415,14 @@ impl LspAdapter for PyrightLspAdapter { { write!(&mut text, " {}", completion_details).ok(); } - Some(language::CodeLabel { - runs: highlight_id + Some(language::CodeLabel::filtered( + text, + item.filter_text.as_deref(), + highlight_id .map(|id| (0..label.len(), id)) .into_iter() .collect(), - text, - filter_range, - }) + )) } async fn label_for_symbol( @@ -458,11 +453,11 @@ impl LspAdapter for PyrightLspAdapter { _ => return None, }; - Some(language::CodeLabel { - runs: language.highlight_text(&text.as_str().into(), display_range.clone()), - text: text[display_range].to_string(), + Some(language::CodeLabel::new( + text[display_range.clone()].to_string(), filter_range, - }) + language.highlight_text(&text.as_str().into(), display_range), + )) } async fn workspace_configuration( @@ -1424,16 +1419,11 @@ impl LspAdapter for PyLspAdapter { lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?, _ => return None, }; - let filter_range = item - .filter_text - .as_deref() - .and_then(|filter| label.find(filter).map(|ix| ix..ix + filter.len())) - .unwrap_or(0..label.len()); - Some(language::CodeLabel { - text: label.clone(), - runs: vec![(0..label.len(), highlight_id)], - filter_range, - }) + Some(language::CodeLabel::filtered( + label.clone(), + item.filter_text.as_deref(), + vec![(0..label.len(), highlight_id)], + )) } async fn label_for_symbol( @@ -1463,12 +1453,11 @@ impl LspAdapter for PyLspAdapter { } _ => return None, }; - - Some(language::CodeLabel { - runs: language.highlight_text(&text.as_str().into(), display_range.clone()), - text: text[display_range].to_string(), + Some(language::CodeLabel::new( + text[display_range.clone()].to_string(), filter_range, - }) + language.highlight_text(&text.as_str().into(), display_range), + )) } async fn workspace_configuration( @@ -1708,11 +1697,6 @@ impl LspAdapter for BasedPyrightLspAdapter { return None; } }; - let filter_range = item - .filter_text - .as_deref() - .and_then(|filter| label.find(filter).map(|ix| ix..ix + filter.len())) - .unwrap_or(0..label.len()); let mut text = label.clone(); if let Some(completion_details) = item .label_details @@ -1721,14 +1705,14 @@ impl LspAdapter for BasedPyrightLspAdapter { { write!(&mut text, " {}", completion_details).ok(); } - Some(language::CodeLabel { - runs: highlight_id + Some(language::CodeLabel::filtered( + text, + item.filter_text.as_deref(), + highlight_id .map(|id| (0..label.len(), id)) .into_iter() .collect(), - text, - filter_range, - }) + )) } async fn label_for_symbol( @@ -1758,12 +1742,11 @@ impl LspAdapter for BasedPyrightLspAdapter { } _ => return None, }; - - Some(language::CodeLabel { - runs: language.highlight_text(&text.as_str().into(), display_range.clone()), - text: text[display_range].to_string(), + Some(language::CodeLabel::new( + text[display_range.clone()].to_string(), filter_range, - }) + language.highlight_text(&text.as_str().into(), display_range), + )) } async fn workspace_configuration( diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 83edb7ca30b77e78a842af41e50719879756cdbd..83b8c7c67ada5f7850867e2d8b089e765a1a3e44 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -209,11 +209,7 @@ impl LspAdapter for RustLspAdapter { }) .unwrap_or_else(filter_range); - CodeLabel { - text, - runs, - filter_range, - } + CodeLabel::new(text, filter_range, runs) }; let mut label = match (detail_right, completion.kind) { (Some(signature), Some(lsp::CompletionItemKind::FIELD)) => { @@ -364,11 +360,11 @@ impl LspAdapter for RustLspAdapter { let filter_range = prefix.len()..prefix.len() + name.len(); let display_range = 0..filter_range.end; - Some(CodeLabel { - runs: language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range), - text: format!("{prefix}{name}"), + Some(CodeLabel::new( + format!("{prefix}{name}"), filter_range, - }) + language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range), + )) } fn prepare_initialize_params( @@ -1166,10 +1162,10 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "hello(&mut Option) -> Vec (use crate::foo)".to_string(), - filter_range: 0..5, - runs: vec![ + Some(CodeLabel::new( + "hello(&mut Option) -> Vec (use crate::foo)".to_string(), + 0..5, + vec![ (0..5, highlight_function), (7..10, highlight_keyword), (11..17, highlight_type), @@ -1177,7 +1173,7 @@ mod tests { (25..28, highlight_type), (29..30, highlight_type), ], - }) + )) ); assert_eq!( adapter @@ -1194,10 +1190,10 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "hello(&mut Option) -> Vec (use crate::foo)".to_string(), - filter_range: 0..5, - runs: vec![ + Some(CodeLabel::new( + "hello(&mut Option) -> Vec (use crate::foo)".to_string(), + 0..5, + vec![ (0..5, highlight_function), (7..10, highlight_keyword), (11..17, highlight_type), @@ -1205,7 +1201,7 @@ mod tests { (25..28, highlight_type), (29..30, highlight_type), ], - }) + )) ); assert_eq!( adapter @@ -1219,11 +1215,11 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "len: usize".to_string(), - filter_range: 0..3, - runs: vec![(0..3, highlight_field), (5..10, highlight_type),], - }) + Some(CodeLabel::new( + "len: usize".to_string(), + 0..3, + vec![(0..3, highlight_field), (5..10, highlight_type),], + )) ); assert_eq!( @@ -1242,10 +1238,10 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "hello(&mut Option) -> Vec (use crate::foo)".to_string(), - filter_range: 0..5, - runs: vec![ + Some(CodeLabel::new( + "hello(&mut Option) -> Vec (use crate::foo)".to_string(), + 0..5, + vec![ (0..5, highlight_function), (7..10, highlight_keyword), (11..17, highlight_type), @@ -1253,7 +1249,7 @@ mod tests { (25..28, highlight_type), (29..30, highlight_type), ], - }) + )) ); assert_eq!( @@ -1271,10 +1267,10 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "hello(&mut Option) -> Vec (use crate::foo)".to_string(), - filter_range: 0..5, - runs: vec![ + Some(CodeLabel::new( + "hello(&mut Option) -> Vec (use crate::foo)".to_string(), + 0..5, + vec![ (0..5, highlight_function), (7..10, highlight_keyword), (11..17, highlight_type), @@ -1282,7 +1278,7 @@ mod tests { (25..28, highlight_type), (29..30, highlight_type), ], - }) + )) ); assert_eq!( @@ -1301,16 +1297,16 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "await.as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(), - filter_range: 6..18, - runs: vec![ + Some(CodeLabel::new( + "await.as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(), + 6..18, + vec![ (6..18, HighlightId(2)), (20..23, HighlightId(1)), (33..40, HighlightId(0)), (45..46, HighlightId(0)) ], - }) + )) ); assert_eq!( @@ -1331,10 +1327,10 @@ mod tests { &language ) .await, - Some(CodeLabel { - text: "pub fn as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(), - filter_range: 7..19, - runs: vec![ + Some(CodeLabel::new( + "pub fn as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(), + 7..19, + vec![ (0..3, HighlightId(1)), (4..6, HighlightId(1)), (7..19, HighlightId(2)), @@ -1342,7 +1338,7 @@ mod tests { (34..41, HighlightId(0)), (46..47, HighlightId(0)) ], - }) + )) ); assert_eq!( @@ -1358,11 +1354,11 @@ mod tests { &language, ) .await, - Some(CodeLabel { - text: "inner_value: String".to_string(), - filter_range: 6..11, - runs: vec![(0..11, HighlightId(3)), (13..19, HighlightId(0))], - }) + Some(CodeLabel::new( + "inner_value: String".to_string(), + 6..11, + vec![(0..11, HighlightId(3)), (13..19, HighlightId(0))], + )) ); } @@ -1388,22 +1384,22 @@ mod tests { adapter .label_for_symbol("hello", lsp::SymbolKind::FUNCTION, &language) .await, - Some(CodeLabel { - text: "fn hello".to_string(), - filter_range: 3..8, - runs: vec![(0..2, highlight_keyword), (3..8, highlight_function)], - }) + Some(CodeLabel::new( + "fn hello".to_string(), + 3..8, + vec![(0..2, highlight_keyword), (3..8, highlight_function)], + )) ); assert_eq!( adapter .label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER, &language) .await, - Some(CodeLabel { - text: "type World".to_string(), - filter_range: 5..10, - runs: vec![(0..4, highlight_keyword), (5..10, highlight_type)], - }) + Some(CodeLabel::new( + "type World".to_string(), + 5..10, + vec![(0..4, highlight_keyword), (5..10, highlight_type)], + )) ); } diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 3a4eb259261c5a26f55da388b2a62504cdeced1a..a9a1104c8c6cfa2b6eaa7083d18316cee4978fc8 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -777,16 +777,11 @@ impl LspAdapter for TypeScriptLspAdapter { } else { item.label.clone() }; - let filter_range = item - .filter_text - .as_deref() - .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) - .unwrap_or(0..len); - Some(language::CodeLabel { + Some(language::CodeLabel::filtered( text, - runs: vec![(0..len, highlight_id)], - filter_range, - }) + item.filter_text.as_deref(), + vec![(0..len, highlight_id)], + )) } async fn initialization_options( diff --git a/crates/languages/src/vtsls.rs b/crates/languages/src/vtsls.rs index 9124a64227f91aa256063f012c960e92afbd8b9e..fbaab1341c3b5332887698f0e28397a15b9f158b 100644 --- a/crates/languages/src/vtsls.rs +++ b/crates/languages/src/vtsls.rs @@ -201,16 +201,11 @@ impl LspAdapter for VtslsLspAdapter { } else { item.label.clone() }; - let filter_range = item - .filter_text - .as_deref() - .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) - .unwrap_or(0..len); - Some(language::CodeLabel { + Some(language::CodeLabel::filtered( text, - runs: vec![(0..len, highlight_id)], - filter_range, - }) + item.filter_text.as_deref(), + vec![(0..len, highlight_id)], + )) } async fn workspace_configuration( diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index f899c3a88645d45f0b302669e4a54b9134c911b4..68db3ac2b436e77281b4c61296b78ff5e4cf52d1 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -9365,11 +9365,7 @@ impl LspStore { name: symbol.name, kind: symbol.kind, range: symbol.range, - label: CodeLabel { - text: Default::default(), - runs: Default::default(), - filter_range: Default::default(), - }, + label: CodeLabel::default(), }, cx, ) @@ -9559,11 +9555,7 @@ impl LspStore { new_text: completion.new_text, source: completion.source, documentation: None, - label: CodeLabel { - text: Default::default(), - runs: Default::default(), - filter_range: Default::default(), - }, + label: CodeLabel::default(), insert_text_mode: None, icon_path: None, confirm: None, @@ -13060,19 +13052,19 @@ mod tests { #[test] fn test_multi_len_chars_normalization() { - let mut label = CodeLabel { - text: "myElˇ (parameter) myElˇ: {\n foo: string;\n}".to_string(), - runs: vec![(0..6, HighlightId(1))], - filter_range: 0..6, - }; + let mut label = CodeLabel::new( + "myElˇ (parameter) myElˇ: {\n foo: string;\n}".to_string(), + 0..6, + vec![(0..6, HighlightId(1))], + ); ensure_uniform_list_compatible_label(&mut label); assert_eq!( label, - CodeLabel { - text: "myElˇ (parameter) myElˇ: { foo: string; }".to_string(), - runs: vec![(0..6, HighlightId(1))], - filter_range: 0..6, - } + CodeLabel::new( + "myElˇ (parameter) myElˇ: { foo: string; }".to_string(), + 0..6, + vec![(0..6, HighlightId(1))], + ) ); } }