mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Support bracket colorization (rainbow brackets) (#43172)
Deals with https://github.com/zed-industries/zed/issues/5259 Highlights brackets with different colors based on their depth. Uses existing tree-sitter queries from brackets.scm to find brackets, uses theme's accents to color them. https://github.com/user-attachments/assets/cc5f3aba-22fa-446d-9af7-ba6e772029da 1. Adds `colorize_brackets` language setting that allows, per language or globally for all languages, to configure whether Zed should color the brackets for a particular language. Disabled for all languages by default. 2. Any given language can opt-out a certain bracket pair by amending the brackets.scm like `("\"" @open "\"" @close) ` -> `(("\"" @open "\"" @close) (#set! rainbow.exclude))` 3. Brackets are using colors from theme accents, which can be overridden as ```jsonc "theme_overrides": { "One Dark": { "accents": ["#ff69b4", "#7fff00", "#ff1493", "#00ffff", "#ff8c00", "#9400d3"] } }, ``` Release Notes: - Added bracket colorization (rainbow brackets) support. Use `colorize_brackets` language setting to enable. --------- Co-authored-by: MrSubidubi <dev@bahn.sh> Co-authored-by: Lukas Wirth <lukas@zed.dev> Co-authored-by: MrSubidubi <finn@zed.dev> Co-authored-by: Lukas Wirth <me@lukaswirth.dev> Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
parent
e6e5ccbf10
commit
7e341bcf94
46 changed files with 1988 additions and 252 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -14082,6 +14082,7 @@ dependencies = [
|
|||
"smol",
|
||||
"sysinfo 0.37.2",
|
||||
"task",
|
||||
"theme",
|
||||
"thiserror 2.0.17",
|
||||
"toml 0.8.23",
|
||||
"unindent",
|
||||
|
|
@ -15135,6 +15136,7 @@ dependencies = [
|
|||
"language",
|
||||
"lsp",
|
||||
"menu",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -255,6 +255,12 @@
|
|||
// Whether to display inline and alongside documentation for items in the
|
||||
// completions menu
|
||||
"show_completion_documentation": true,
|
||||
// Whether to colorize brackets in the editor.
|
||||
// (also known as "rainbow brackets")
|
||||
//
|
||||
// The colors that are used for different indentation levels are defined in the theme (theme key: `accents`).
|
||||
// They can be customized by using theme overrides.
|
||||
"colorize_brackets": false,
|
||||
// When to show the scrollbar in the completion menu.
|
||||
// This setting can take four values:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ impl Console {
|
|||
start_offset,
|
||||
vec![range],
|
||||
style,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
1287
crates/editor/src/bracket_colorization.rs
Normal file
1287
crates/editor/src/bracket_colorization.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -483,8 +483,26 @@ impl DisplayMap {
|
|||
key: HighlightKey,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
style: HighlightStyle,
|
||||
merge: bool,
|
||||
cx: &App,
|
||||
) {
|
||||
self.text_highlights.insert(key, Arc::new((style, ranges)));
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let to_insert = match self.text_highlights.remove(&key).filter(|_| merge) {
|
||||
Some(previous) => {
|
||||
let mut merged_ranges = previous.1.clone();
|
||||
for new_range in ranges {
|
||||
let i = merged_ranges
|
||||
.binary_search_by(|probe| {
|
||||
probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
|
||||
})
|
||||
.unwrap_or_else(|i| i);
|
||||
merged_ranges.insert(i, new_range);
|
||||
}
|
||||
Arc::new((style, merged_ranges))
|
||||
}
|
||||
None => Arc::new((style, ranges)),
|
||||
};
|
||||
self.text_highlights.insert(key, to_insert);
|
||||
}
|
||||
|
||||
pub(crate) fn highlight_inlays(
|
||||
|
|
@ -523,6 +541,15 @@ impl DisplayMap {
|
|||
.text_highlights
|
||||
.remove(&HighlightKey::Type(type_id))
|
||||
.is_some();
|
||||
self.text_highlights.retain(|key, _| {
|
||||
let retain = if let HighlightKey::TypePlus(key_type_id, _) = key {
|
||||
key_type_id != &type_id
|
||||
} else {
|
||||
true
|
||||
};
|
||||
cleared |= !retain;
|
||||
retain
|
||||
});
|
||||
cleared |= self.inlay_highlights.remove(&type_id).is_some();
|
||||
cleared
|
||||
}
|
||||
|
|
@ -1382,6 +1409,33 @@ impl DisplaySnapshot {
|
|||
.cloned()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn all_text_highlight_ranges<Tag: ?Sized + 'static>(
|
||||
&self,
|
||||
) -> Vec<(gpui::Hsla, Range<Point>)> {
|
||||
use itertools::Itertools;
|
||||
|
||||
let required_type_id = TypeId::of::<Tag>();
|
||||
self.text_highlights
|
||||
.iter()
|
||||
.filter(|(key, _)| match key {
|
||||
HighlightKey::Type(type_id) => type_id == &required_type_id,
|
||||
HighlightKey::TypePlus(type_id, _) => type_id == &required_type_id,
|
||||
})
|
||||
.map(|(_, value)| value.clone())
|
||||
.flat_map(|ranges| {
|
||||
ranges
|
||||
.1
|
||||
.iter()
|
||||
.flat_map(|range| {
|
||||
Some((ranges.0.color?, range.to_point(self.buffer_snapshot())))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.sorted_by_key(|(_, range)| range.start)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
|
||||
|
|
@ -2387,6 +2441,8 @@ pub mod tests {
|
|||
..buffer_snapshot.anchor_after(Point::new(3, 18)),
|
||||
],
|
||||
red.into(),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
map.insert_blocks(
|
||||
[BlockProperties {
|
||||
|
|
@ -2698,7 +2754,7 @@ pub mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
map.update(cx, |map, _cx| {
|
||||
map.update(cx, |map, cx| {
|
||||
map.highlight_text(
|
||||
HighlightKey::Type(TypeId::of::<MyType>()),
|
||||
highlighted_ranges
|
||||
|
|
@ -2710,6 +2766,8 @@ pub mod tests {
|
|||
})
|
||||
.collect(),
|
||||
style,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ impl<'a> CustomHighlightsChunks<'a> {
|
|||
buffer_chunks: multibuffer_snapshot.chunks(range.clone(), language_aware),
|
||||
buffer_chunk: None,
|
||||
offset: range.start,
|
||||
|
||||
text_highlights,
|
||||
highlight_endpoints: create_highlight_endpoints(
|
||||
&range,
|
||||
|
|
@ -75,16 +74,9 @@ fn create_highlight_endpoints(
|
|||
let style = text_highlights.0;
|
||||
let ranges = &text_highlights.1;
|
||||
|
||||
let start_ix = match ranges.binary_search_by(|probe| {
|
||||
let cmp = probe.end.cmp(&start, buffer);
|
||||
if cmp.is_gt() {
|
||||
cmp::Ordering::Greater
|
||||
} else {
|
||||
cmp::Ordering::Less
|
||||
}
|
||||
}) {
|
||||
Ok(i) | Err(i) => i,
|
||||
};
|
||||
let start_ix = ranges
|
||||
.binary_search_by(|probe| probe.end.cmp(&start, buffer).then(cmp::Ordering::Less))
|
||||
.unwrap_or_else(|i| i);
|
||||
|
||||
for range in &ranges[start_ix..] {
|
||||
if range.start.cmp(&end, buffer).is_ge() {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
|
||||
pub mod actions;
|
||||
mod blink_manager;
|
||||
mod bracket_colorization;
|
||||
mod clangd_ext;
|
||||
pub mod code_context_menus;
|
||||
pub mod display_map;
|
||||
|
|
@ -118,11 +119,11 @@ use language::{
|
|||
AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
|
||||
BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
|
||||
DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
|
||||
IndentSize, Language, LanguageRegistry, OffsetRangeExt, OutlineItem, Point, Runnable,
|
||||
Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
|
||||
IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point,
|
||||
Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
|
||||
language_settings::{
|
||||
self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
|
||||
language_settings,
|
||||
self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
|
||||
all_language_settings, language_settings,
|
||||
},
|
||||
point_from_lsp, point_to_lsp, text_diff_with_options,
|
||||
};
|
||||
|
|
@ -175,6 +176,7 @@ use std::{
|
|||
borrow::Cow,
|
||||
cell::{OnceCell, RefCell},
|
||||
cmp::{self, Ordering, Reverse},
|
||||
collections::hash_map,
|
||||
iter::{self, Peekable},
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
|
|
@ -1193,6 +1195,9 @@ pub struct Editor {
|
|||
folding_newlines: Task<()>,
|
||||
select_next_is_case_sensitive: Option<bool>,
|
||||
pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
|
||||
applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
|
||||
accent_overrides: Vec<SharedString>,
|
||||
fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
|
||||
}
|
||||
|
||||
fn debounce_value(debounce_ms: u64) -> Option<Duration> {
|
||||
|
|
@ -2333,12 +2338,18 @@ impl Editor {
|
|||
folding_newlines: Task::ready(()),
|
||||
lookup_key: None,
|
||||
select_next_is_case_sensitive: None,
|
||||
applicable_language_settings: HashMap::default(),
|
||||
accent_overrides: Vec::new(),
|
||||
fetched_tree_sitter_chunks: HashMap::default(),
|
||||
};
|
||||
|
||||
if is_minimap {
|
||||
return editor;
|
||||
}
|
||||
|
||||
editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
|
||||
editor.accent_overrides = editor.fetch_accent_overrides(cx);
|
||||
|
||||
if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
|
||||
editor
|
||||
._subscriptions
|
||||
|
|
@ -2378,6 +2389,7 @@ impl Editor {
|
|||
InlayHintRefreshReason::NewLinesShown,
|
||||
cx,
|
||||
);
|
||||
editor.colorize_brackets(false, cx);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
|
|
@ -21141,13 +21153,16 @@ impl Editor {
|
|||
key: usize,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
style: HighlightStyle,
|
||||
merge: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.display_map.update(cx, |map, _| {
|
||||
self.display_map.update(cx, |map, cx| {
|
||||
map.highlight_text(
|
||||
HighlightKey::TypePlus(TypeId::of::<T>(), key),
|
||||
ranges,
|
||||
style,
|
||||
merge,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.notify();
|
||||
|
|
@ -21159,8 +21174,14 @@ impl Editor {
|
|||
style: HighlightStyle,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.display_map.update(cx, |map, _| {
|
||||
map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
|
||||
self.display_map.update(cx, |map, cx| {
|
||||
map.highlight_text(
|
||||
HighlightKey::Type(TypeId::of::<T>()),
|
||||
ranges,
|
||||
style,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
|
@ -21308,7 +21329,6 @@ impl Editor {
|
|||
self.active_indent_guides_state.dirty = true;
|
||||
self.refresh_active_diagnostics(cx);
|
||||
self.refresh_code_actions(window, cx);
|
||||
self.refresh_selected_text_highlights(true, window, cx);
|
||||
self.refresh_single_line_folds(window, cx);
|
||||
self.refresh_matching_bracket_highlights(window, cx);
|
||||
if self.has_active_edit_prediction() {
|
||||
|
|
@ -21364,6 +21384,7 @@ impl Editor {
|
|||
}
|
||||
self.update_lsp_data(Some(buffer_id), window, cx);
|
||||
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
self.colorize_brackets(false, cx);
|
||||
cx.emit(EditorEvent::ExcerptsAdded {
|
||||
buffer: buffer.clone(),
|
||||
predecessor: *predecessor,
|
||||
|
|
@ -21401,10 +21422,16 @@ impl Editor {
|
|||
multi_buffer::Event::ExcerptsExpanded { ids } => {
|
||||
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
self.refresh_document_highlights(cx);
|
||||
for id in ids {
|
||||
self.fetched_tree_sitter_chunks.remove(id);
|
||||
}
|
||||
self.colorize_brackets(false, cx);
|
||||
cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
|
||||
}
|
||||
multi_buffer::Event::Reparsed(buffer_id) => {
|
||||
self.tasks_update_task = Some(self.refresh_runnables(window, cx));
|
||||
self.refresh_selected_text_highlights(true, window, cx);
|
||||
self.colorize_brackets(true, cx);
|
||||
jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
|
||||
|
||||
cx.emit(EditorEvent::Reparsed(*buffer_id));
|
||||
|
|
@ -21475,7 +21502,52 @@ impl Editor {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
|
||||
if !self.mode.is_full() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
theme::ThemeSettings::get_global(cx)
|
||||
.theme_overrides
|
||||
.get(cx.theme().name.as_ref())
|
||||
.map(|theme_style| &theme_style.accents)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flat_map(|accent| accent.0.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn fetch_applicable_language_settings(
|
||||
&self,
|
||||
cx: &App,
|
||||
) -> HashMap<Option<LanguageName>, LanguageSettings> {
|
||||
if !self.mode.is_full() {
|
||||
return HashMap::default();
|
||||
}
|
||||
|
||||
self.buffer().read(cx).all_buffers().into_iter().fold(
|
||||
HashMap::default(),
|
||||
|mut acc, buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let language = buffer.language().map(|language| language.name());
|
||||
if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
|
||||
let file = buffer.file();
|
||||
v.insert(language_settings(language, file, cx).into_owned());
|
||||
}
|
||||
acc
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let new_language_settings = self.fetch_applicable_language_settings(cx);
|
||||
let language_settings_changed = new_language_settings != self.applicable_language_settings;
|
||||
self.applicable_language_settings = new_language_settings;
|
||||
|
||||
let new_accent_overrides = self.fetch_accent_overrides(cx);
|
||||
let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
|
||||
self.accent_overrides = new_accent_overrides;
|
||||
|
||||
if self.diagnostics_enabled() {
|
||||
let new_severity = EditorSettings::get_global(cx)
|
||||
.diagnostics_max_severity
|
||||
|
|
@ -21547,15 +21619,19 @@ impl Editor {
|
|||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
|
||||
colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
|
||||
}) {
|
||||
if !inlay_splice.is_empty() {
|
||||
self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
|
||||
if language_settings_changed || accent_overrides_changed {
|
||||
self.colorize_brackets(true, cx);
|
||||
}
|
||||
|
||||
if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
|
||||
colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
|
||||
}) {
|
||||
if !inlay_splice.is_empty() {
|
||||
self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
|
||||
}
|
||||
self.refresh_colors_for_visible_range(None, window, cx);
|
||||
}
|
||||
self.refresh_colors_for_visible_range(None, window, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
|
|
@ -22668,7 +22744,7 @@ fn insert_extra_newline_tree_sitter(
|
|||
_ => return false,
|
||||
};
|
||||
let pair = {
|
||||
let mut result: Option<BracketMatch> = None;
|
||||
let mut result: Option<BracketMatch<usize>> = None;
|
||||
|
||||
for pair in buffer
|
||||
.all_bracket_ranges(range.start.0..range.end.0)
|
||||
|
|
|
|||
|
|
@ -17565,7 +17565,9 @@ async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
|
|||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
|
||||
let mut assert = |before, after| {
|
||||
|
||||
#[track_caller]
|
||||
fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
|
||||
let _state_context = cx.set_state(before);
|
||||
cx.run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
|
|
@ -17573,30 +17575,33 @@ async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
|
|||
});
|
||||
cx.run_until_parked();
|
||||
cx.assert_editor_state(after);
|
||||
};
|
||||
}
|
||||
|
||||
// Outside bracket jumps to outside of matching bracket
|
||||
assert("console.logˇ(var);", "console.log(var)ˇ;");
|
||||
assert("console.log(var)ˇ;", "console.logˇ(var);");
|
||||
assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
|
||||
assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
|
||||
|
||||
// Inside bracket jumps to inside of matching bracket
|
||||
assert("console.log(ˇvar);", "console.log(varˇ);");
|
||||
assert("console.log(varˇ);", "console.log(ˇvar);");
|
||||
assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
|
||||
assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
|
||||
|
||||
// When outside a bracket and inside, favor jumping to the inside bracket
|
||||
assert(
|
||||
"console.log('foo', [1, 2, 3]ˇ);",
|
||||
"console.log(ˇ'foo', [1, 2, 3]);",
|
||||
"console.log('foo', ˇ[1, 2, 3]);",
|
||||
&mut cx,
|
||||
);
|
||||
assert(
|
||||
"console.log(ˇ'foo', [1, 2, 3]);",
|
||||
"console.log('foo', [1, 2, 3]ˇ);",
|
||||
"console.log('foo'ˇ, [1, 2, 3]);",
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// Bias forward if two options are equally likely
|
||||
assert(
|
||||
"let result = curried_fun()ˇ();",
|
||||
"let result = curried_fun()()ˇ;",
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
|
||||
|
|
@ -17609,6 +17614,7 @@ async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
|
|||
function test() {
|
||||
console.logˇ('test')
|
||||
}"},
|
||||
&mut cx,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -500,6 +500,7 @@ impl Editor {
|
|||
editor.register_visible_buffers(cx);
|
||||
editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
editor.update_lsp_data(None, window, cx);
|
||||
editor.colorize_brackets(false, cx);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
pub mod row_chunk;
|
||||
|
||||
use crate::{
|
||||
DebuggerTextObject, LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag,
|
||||
TextObject, TreeSitterOptions,
|
||||
diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup},
|
||||
language_settings::{LanguageSettings, language_settings},
|
||||
outline::OutlineItem,
|
||||
row_chunk::RowChunks,
|
||||
syntax_map::{
|
||||
SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatch,
|
||||
SyntaxMapMatches, SyntaxSnapshot, ToTreeSitterPoint,
|
||||
|
|
@ -18,9 +21,9 @@ pub use crate::{
|
|||
proto,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use clock::Lamport;
|
||||
pub use clock::ReplicaId;
|
||||
use collections::HashMap;
|
||||
use clock::{Global, Lamport};
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::MTime;
|
||||
use futures::channel::oneshot;
|
||||
use gpui::{
|
||||
|
|
@ -28,8 +31,9 @@ use gpui::{
|
|||
Task, TaskLabel, TextStyle,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use lsp::{LanguageServerId, NumberOrString};
|
||||
use parking_lot::Mutex;
|
||||
use parking_lot::{Mutex, RawMutex, lock_api::MutexGuard};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use settings::WorktreeId;
|
||||
|
|
@ -45,7 +49,7 @@ use std::{
|
|||
iter::{self, Iterator, Peekable},
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
ops::{Deref, Range},
|
||||
ops::{Deref, Not, Range},
|
||||
path::PathBuf,
|
||||
rc,
|
||||
sync::{Arc, LazyLock},
|
||||
|
|
@ -126,6 +130,29 @@ pub struct Buffer {
|
|||
has_unsaved_edits: Cell<(clock::Global, bool)>,
|
||||
change_bits: Vec<rc::Weak<Cell<bool>>>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
tree_sitter_data: Arc<Mutex<TreeSitterData>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TreeSitterData {
|
||||
chunks: RowChunks,
|
||||
brackets_by_chunks: Vec<Option<Vec<BracketMatch<usize>>>>,
|
||||
}
|
||||
|
||||
const MAX_ROWS_IN_A_CHUNK: u32 = 50;
|
||||
|
||||
impl TreeSitterData {
|
||||
fn clear(&mut self) {
|
||||
self.brackets_by_chunks = vec![None; self.chunks.len()];
|
||||
}
|
||||
|
||||
fn new(snapshot: text::BufferSnapshot) -> Self {
|
||||
let chunks = RowChunks::new(snapshot, MAX_ROWS_IN_A_CHUNK);
|
||||
Self {
|
||||
brackets_by_chunks: vec![None; chunks.len()],
|
||||
chunks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -149,6 +176,7 @@ pub struct BufferSnapshot {
|
|||
remote_selections: TreeMap<ReplicaId, SelectionSet>,
|
||||
language: Option<Arc<Language>>,
|
||||
non_text_state_update_count: usize,
|
||||
tree_sitter_data: Arc<Mutex<TreeSitterData>>,
|
||||
}
|
||||
|
||||
/// The kind and amount of indentation in a particular line. For now,
|
||||
|
|
@ -819,11 +847,18 @@ impl EditPreview {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BracketMatch {
|
||||
pub open_range: Range<usize>,
|
||||
pub close_range: Range<usize>,
|
||||
pub struct BracketMatch<T> {
|
||||
pub open_range: Range<T>,
|
||||
pub close_range: Range<T>,
|
||||
pub newline_only: bool,
|
||||
pub depth: usize,
|
||||
pub syntax_layer_depth: usize,
|
||||
pub color_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl<T> BracketMatch<T> {
|
||||
pub fn bracket_ranges(self) -> (Range<T>, Range<T>) {
|
||||
(self.open_range, self.close_range)
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
|
|
@ -974,8 +1009,10 @@ impl Buffer {
|
|||
let saved_mtime = file.as_ref().and_then(|file| file.disk_state().mtime());
|
||||
let snapshot = buffer.snapshot();
|
||||
let syntax_map = Mutex::new(SyntaxMap::new(&snapshot));
|
||||
let tree_sitter_data = TreeSitterData::new(snapshot);
|
||||
Self {
|
||||
saved_mtime,
|
||||
tree_sitter_data: Arc::new(Mutex::new(tree_sitter_data)),
|
||||
saved_version: buffer.version(),
|
||||
preview_version: buffer.version(),
|
||||
reload_task: None,
|
||||
|
|
@ -1025,12 +1062,14 @@ impl Buffer {
|
|||
let language_registry = language_registry.clone();
|
||||
syntax.reparse(&text, language_registry, language);
|
||||
}
|
||||
let tree_sitter_data = TreeSitterData::new(text.clone());
|
||||
BufferSnapshot {
|
||||
text,
|
||||
syntax,
|
||||
file: None,
|
||||
diagnostics: Default::default(),
|
||||
remote_selections: Default::default(),
|
||||
tree_sitter_data: Arc::new(Mutex::new(tree_sitter_data)),
|
||||
language,
|
||||
non_text_state_update_count: 0,
|
||||
}
|
||||
|
|
@ -1048,9 +1087,11 @@ impl Buffer {
|
|||
)
|
||||
.snapshot();
|
||||
let syntax = SyntaxMap::new(&text).snapshot();
|
||||
let tree_sitter_data = TreeSitterData::new(text.clone());
|
||||
BufferSnapshot {
|
||||
text,
|
||||
syntax,
|
||||
tree_sitter_data: Arc::new(Mutex::new(tree_sitter_data)),
|
||||
file: None,
|
||||
diagnostics: Default::default(),
|
||||
remote_selections: Default::default(),
|
||||
|
|
@ -1075,9 +1116,11 @@ impl Buffer {
|
|||
if let Some(language) = language.clone() {
|
||||
syntax.reparse(&text, language_registry, language);
|
||||
}
|
||||
let tree_sitter_data = TreeSitterData::new(text.clone());
|
||||
BufferSnapshot {
|
||||
text,
|
||||
syntax,
|
||||
tree_sitter_data: Arc::new(Mutex::new(tree_sitter_data)),
|
||||
file: None,
|
||||
diagnostics: Default::default(),
|
||||
remote_selections: Default::default(),
|
||||
|
|
@ -1097,6 +1140,7 @@ impl Buffer {
|
|||
BufferSnapshot {
|
||||
text,
|
||||
syntax,
|
||||
tree_sitter_data: self.tree_sitter_data.clone(),
|
||||
file: self.file.clone(),
|
||||
remote_selections: self.remote_selections.clone(),
|
||||
diagnostics: self.diagnostics.clone(),
|
||||
|
|
@ -1611,6 +1655,7 @@ impl Buffer {
|
|||
self.syntax_map.lock().did_parse(syntax_snapshot);
|
||||
self.request_autoindent(cx);
|
||||
self.parse_status.0.send(ParseStatus::Idle).unwrap();
|
||||
self.tree_sitter_data.lock().clear();
|
||||
cx.emit(BufferEvent::Reparsed);
|
||||
cx.notify();
|
||||
}
|
||||
|
|
@ -4120,61 +4165,166 @@ impl BufferSnapshot {
|
|||
self.syntax.matches(range, self, query)
|
||||
}
|
||||
|
||||
/// Finds all [`RowChunks`] applicable to the given range, then returns all bracket pairs that intersect with those chunks.
|
||||
/// Hence, may return more bracket pairs than the range contains.
|
||||
///
|
||||
/// Will omit known chunks.
|
||||
/// The resulting bracket match collections are not ordered.
|
||||
pub fn fetch_bracket_ranges(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
known_chunks: Option<(&Global, &HashSet<Range<BufferRow>>)>,
|
||||
) -> HashMap<Range<BufferRow>, Vec<BracketMatch<usize>>> {
|
||||
let mut tree_sitter_data = self.latest_tree_sitter_data().clone();
|
||||
|
||||
let known_chunks = match known_chunks {
|
||||
Some((known_version, known_chunks)) => {
|
||||
if !tree_sitter_data
|
||||
.chunks
|
||||
.version()
|
||||
.changed_since(known_version)
|
||||
{
|
||||
known_chunks.clone()
|
||||
} else {
|
||||
HashSet::default()
|
||||
}
|
||||
}
|
||||
None => HashSet::default(),
|
||||
};
|
||||
|
||||
let mut new_bracket_matches = HashMap::default();
|
||||
let mut all_bracket_matches = HashMap::default();
|
||||
|
||||
for chunk in tree_sitter_data
|
||||
.chunks
|
||||
.applicable_chunks(&[self.anchor_before(range.start)..self.anchor_after(range.end)])
|
||||
{
|
||||
if known_chunks.contains(&chunk.row_range()) {
|
||||
continue;
|
||||
}
|
||||
let Some(chunk_range) = tree_sitter_data.chunks.chunk_range(chunk) else {
|
||||
continue;
|
||||
};
|
||||
let chunk_range = chunk_range.to_offset(&tree_sitter_data.chunks.snapshot);
|
||||
|
||||
let bracket_matches = match tree_sitter_data.brackets_by_chunks[chunk.id].take() {
|
||||
Some(cached_brackets) => cached_brackets,
|
||||
None => {
|
||||
let mut bracket_pairs_ends = Vec::new();
|
||||
let mut matches =
|
||||
self.syntax
|
||||
.matches(chunk_range.clone(), &self.text, |grammar| {
|
||||
grammar.brackets_config.as_ref().map(|c| &c.query)
|
||||
});
|
||||
let configs = matches
|
||||
.grammars()
|
||||
.iter()
|
||||
.map(|grammar| grammar.brackets_config.as_ref().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let chunk_range = chunk_range.clone();
|
||||
let new_matches = iter::from_fn(move || {
|
||||
while let Some(mat) = matches.peek() {
|
||||
let mut open = None;
|
||||
let mut close = None;
|
||||
let depth = mat.depth;
|
||||
let config = configs[mat.grammar_index];
|
||||
let pattern = &config.patterns[mat.pattern_index];
|
||||
for capture in mat.captures {
|
||||
if capture.index == config.open_capture_ix {
|
||||
open = Some(capture.node.byte_range());
|
||||
} else if capture.index == config.close_capture_ix {
|
||||
close = Some(capture.node.byte_range());
|
||||
}
|
||||
}
|
||||
|
||||
matches.advance();
|
||||
|
||||
let Some((open_range, close_range)) = open.zip(close) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let bracket_range = open_range.start..=close_range.end;
|
||||
if !bracket_range.overlaps(&chunk_range) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Some((open_range, close_range, pattern, depth));
|
||||
}
|
||||
None
|
||||
})
|
||||
.sorted_by_key(|(open_range, _, _, _)| open_range.start)
|
||||
.map(|(open_range, close_range, pattern, syntax_layer_depth)| {
|
||||
while let Some(&last_bracket_end) = bracket_pairs_ends.last() {
|
||||
if last_bracket_end <= open_range.start {
|
||||
bracket_pairs_ends.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let bracket_depth = bracket_pairs_ends.len();
|
||||
bracket_pairs_ends.push(close_range.end);
|
||||
|
||||
BracketMatch {
|
||||
open_range,
|
||||
close_range,
|
||||
syntax_layer_depth,
|
||||
newline_only: pattern.newline_only,
|
||||
color_index: pattern.rainbow_exclude.not().then_some(bracket_depth),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
new_bracket_matches.insert(chunk.id, new_matches.clone());
|
||||
new_matches
|
||||
}
|
||||
};
|
||||
all_bracket_matches.insert(chunk.row_range(), bracket_matches);
|
||||
}
|
||||
|
||||
let mut latest_tree_sitter_data = self.latest_tree_sitter_data();
|
||||
if latest_tree_sitter_data.chunks.version() == &self.version {
|
||||
for (chunk_id, new_matches) in new_bracket_matches {
|
||||
let old_chunks = &mut latest_tree_sitter_data.brackets_by_chunks[chunk_id];
|
||||
if old_chunks.is_none() {
|
||||
*old_chunks = Some(new_matches);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
all_bracket_matches
|
||||
}
|
||||
|
||||
fn latest_tree_sitter_data(&self) -> MutexGuard<'_, RawMutex, TreeSitterData> {
|
||||
let mut tree_sitter_data = self.tree_sitter_data.lock();
|
||||
if self
|
||||
.version
|
||||
.changed_since(tree_sitter_data.chunks.version())
|
||||
{
|
||||
*tree_sitter_data = TreeSitterData::new(self.text.clone());
|
||||
}
|
||||
tree_sitter_data
|
||||
}
|
||||
|
||||
pub fn all_bracket_ranges(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
) -> impl Iterator<Item = BracketMatch> + '_ {
|
||||
let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
|
||||
grammar.brackets_config.as_ref().map(|c| &c.query)
|
||||
});
|
||||
let configs = matches
|
||||
.grammars()
|
||||
.iter()
|
||||
.map(|grammar| grammar.brackets_config.as_ref().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
iter::from_fn(move || {
|
||||
while let Some(mat) = matches.peek() {
|
||||
let mut open = None;
|
||||
let mut close = None;
|
||||
let depth = mat.depth;
|
||||
let config = &configs[mat.grammar_index];
|
||||
let pattern = &config.patterns[mat.pattern_index];
|
||||
for capture in mat.captures {
|
||||
if capture.index == config.open_capture_ix {
|
||||
open = Some(capture.node.byte_range());
|
||||
} else if capture.index == config.close_capture_ix {
|
||||
close = Some(capture.node.byte_range());
|
||||
}
|
||||
}
|
||||
|
||||
matches.advance();
|
||||
|
||||
let Some((open_range, close_range)) = open.zip(close) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let bracket_range = open_range.start..=close_range.end;
|
||||
if !bracket_range.overlaps(&range) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Some(BracketMatch {
|
||||
open_range,
|
||||
close_range,
|
||||
newline_only: pattern.newline_only,
|
||||
depth,
|
||||
});
|
||||
}
|
||||
None
|
||||
})
|
||||
) -> impl Iterator<Item = BracketMatch<usize>> {
|
||||
self.fetch_bracket_ranges(range.clone(), None)
|
||||
.into_values()
|
||||
.flatten()
|
||||
.filter(move |bracket_match| {
|
||||
let bracket_range = bracket_match.open_range.start..bracket_match.close_range.end;
|
||||
bracket_range.overlaps(&range)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns bracket range pairs overlapping or adjacent to `range`
|
||||
pub fn bracket_ranges<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = BracketMatch> + '_ {
|
||||
) -> impl Iterator<Item = BracketMatch<usize>> + '_ {
|
||||
// Find bracket pairs that *inclusively* contain the given range.
|
||||
let range = range.start.to_previous_offset(self)..range.end.to_next_offset(self);
|
||||
self.all_bracket_ranges(range)
|
||||
|
|
@ -4320,15 +4470,19 @@ impl BufferSnapshot {
|
|||
pub fn enclosing_bracket_ranges<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = BracketMatch> + '_ {
|
||||
) -> impl Iterator<Item = BracketMatch<usize>> + '_ {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
|
||||
let result: Vec<_> = self.bracket_ranges(range.clone()).collect();
|
||||
let max_depth = result.iter().map(|mat| mat.depth).max().unwrap_or(0);
|
||||
let max_depth = result
|
||||
.iter()
|
||||
.map(|mat| mat.syntax_layer_depth)
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
result.into_iter().filter(move |pair| {
|
||||
pair.open_range.start <= range.start
|
||||
&& pair.close_range.end >= range.end
|
||||
&& pair.depth == max_depth
|
||||
&& pair.syntax_layer_depth == max_depth
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -4815,6 +4969,7 @@ impl Clone for BufferSnapshot {
|
|||
remote_selections: self.remote_selections.clone(),
|
||||
diagnostics: self.diagnostics.clone(),
|
||||
language: self.language.clone(),
|
||||
tree_sitter_data: self.tree_sitter_data.clone(),
|
||||
non_text_state_update_count: self.non_text_state_update_count,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
121
crates/language/src/buffer/row_chunk.rs
Normal file
121
crates/language/src/buffer/row_chunk.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
//! A row chunk is an exclusive range of rows, [`BufferRow`] within a buffer of a certain version, [`Global`].
|
||||
//! All but the last chunk are of a constant, given size.
|
||||
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
use clock::Global;
|
||||
use text::{Anchor, OffsetRangeExt as _, Point};
|
||||
use util::RangeExt;
|
||||
|
||||
use crate::BufferRow;
|
||||
|
||||
/// An range of rows, exclusive as [`lsp::Range`] and
|
||||
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#range>
|
||||
/// denote.
|
||||
///
|
||||
/// Represents an area in a text editor, adjacent to other ones.
|
||||
/// Together, chunks form entire document at a particular version [`Global`].
|
||||
/// Each chunk is queried for inlays as `(start_row, 0)..(end_exclusive, 0)` via
|
||||
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHintParams>
|
||||
#[derive(Clone)]
|
||||
pub struct RowChunks {
|
||||
pub(crate) snapshot: text::BufferSnapshot,
|
||||
chunks: Arc<[RowChunk]>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RowChunks {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RowChunks")
|
||||
.field("version", self.snapshot.version())
|
||||
.field("chunks", &self.chunks)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl RowChunks {
|
||||
pub fn new(snapshot: text::BufferSnapshot, max_rows_per_chunk: u32) -> Self {
|
||||
let buffer_point_range = (0..snapshot.len()).to_point(&snapshot);
|
||||
let last_row = buffer_point_range.end.row;
|
||||
let chunks = (buffer_point_range.start.row..=last_row)
|
||||
.step_by(max_rows_per_chunk as usize)
|
||||
.enumerate()
|
||||
.map(|(id, chunk_start)| RowChunk {
|
||||
id,
|
||||
start: chunk_start,
|
||||
end_exclusive: (chunk_start + max_rows_per_chunk).min(last_row),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Self {
|
||||
snapshot,
|
||||
chunks: Arc::from(chunks),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &Global {
|
||||
self.snapshot.version()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.chunks.len()
|
||||
}
|
||||
|
||||
pub fn applicable_chunks(
|
||||
&self,
|
||||
ranges: &[Range<text::Anchor>],
|
||||
) -> impl Iterator<Item = RowChunk> {
|
||||
let row_ranges = ranges
|
||||
.iter()
|
||||
.map(|range| range.to_point(&self.snapshot))
|
||||
// Be lenient and yield multiple chunks if they "touch" the exclusive part of the range.
|
||||
// This will result in LSP hints [re-]queried for more ranges, but also more hints already visible when scrolling around.
|
||||
.map(|point_range| point_range.start.row..point_range.end.row + 1)
|
||||
.collect::<Vec<_>>();
|
||||
self.chunks
|
||||
.iter()
|
||||
.filter(move |chunk| -> bool {
|
||||
let chunk_range = chunk.row_range().to_inclusive();
|
||||
row_ranges
|
||||
.iter()
|
||||
.any(|row_range| chunk_range.overlaps(&row_range))
|
||||
})
|
||||
.copied()
|
||||
}
|
||||
|
||||
pub fn chunk_range(&self, chunk: RowChunk) -> Option<Range<Anchor>> {
|
||||
if !self.chunks.contains(&chunk) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = Point::new(chunk.start, 0);
|
||||
let end = if self.chunks.last() == Some(&chunk) {
|
||||
Point::new(
|
||||
chunk.end_exclusive,
|
||||
self.snapshot.line_len(chunk.end_exclusive),
|
||||
)
|
||||
} else {
|
||||
Point::new(chunk.end_exclusive, 0)
|
||||
};
|
||||
Some(self.snapshot.anchor_before(start)..self.snapshot.anchor_after(end))
|
||||
}
|
||||
|
||||
pub fn previous_chunk(&self, chunk: RowChunk) -> Option<RowChunk> {
|
||||
if chunk.id == 0 {
|
||||
None
|
||||
} else {
|
||||
self.chunks.get(chunk.id - 1).copied()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct RowChunk {
|
||||
pub id: usize,
|
||||
pub start: BufferRow,
|
||||
pub end_exclusive: BufferRow,
|
||||
}
|
||||
|
||||
impl RowChunk {
|
||||
pub fn row_range(&self) -> Range<BufferRow> {
|
||||
self.start..self.end_exclusive
|
||||
}
|
||||
}
|
||||
|
|
@ -1111,9 +1111,10 @@ fn test_text_objects(cx: &mut App) {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_enclosing_bracket_ranges(cx: &mut App) {
|
||||
let mut assert = |selection_text, range_markers| {
|
||||
#[track_caller]
|
||||
fn assert(selection_text: &'static str, range_markers: Vec<&'static str>, cx: &mut App) {
|
||||
assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
|
||||
};
|
||||
}
|
||||
|
||||
assert(
|
||||
indoc! {"
|
||||
|
|
@ -1130,6 +1131,7 @@ fn test_enclosing_bracket_ranges(cx: &mut App) {
|
|||
}
|
||||
«}»
|
||||
let foo = 1;"}],
|
||||
cx,
|
||||
);
|
||||
|
||||
assert(
|
||||
|
|
@ -1156,6 +1158,7 @@ fn test_enclosing_bracket_ranges(cx: &mut App) {
|
|||
}
|
||||
let foo = 1;"},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
|
||||
assert(
|
||||
|
|
@ -1182,6 +1185,7 @@ fn test_enclosing_bracket_ranges(cx: &mut App) {
|
|||
}
|
||||
let foo = 1;"},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
|
||||
assert(
|
||||
|
|
@ -1199,6 +1203,7 @@ fn test_enclosing_bracket_ranges(cx: &mut App) {
|
|||
}
|
||||
«}»
|
||||
let foo = 1;"}],
|
||||
cx,
|
||||
);
|
||||
|
||||
assert(
|
||||
|
|
@ -1209,7 +1214,8 @@ fn test_enclosing_bracket_ranges(cx: &mut App) {
|
|||
}
|
||||
}
|
||||
let fˇoo = 1;"},
|
||||
vec![],
|
||||
Vec::new(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// Regression test: avoid crash when querying at the end of the buffer.
|
||||
|
|
@ -1221,7 +1227,8 @@ fn test_enclosing_bracket_ranges(cx: &mut App) {
|
|||
}
|
||||
}
|
||||
let foo = 1;ˇ"},
|
||||
vec![],
|
||||
Vec::new(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1323,6 +1323,7 @@ struct BracketsConfig {
|
|||
#[derive(Clone, Debug, Default)]
|
||||
struct BracketsPatternConfig {
|
||||
newline_only: bool,
|
||||
rainbow_exclude: bool,
|
||||
}
|
||||
|
||||
pub struct DebugVariablesConfig {
|
||||
|
|
@ -1685,9 +1686,13 @@ impl Language {
|
|||
.map(|ix| {
|
||||
let mut config = BracketsPatternConfig::default();
|
||||
for setting in query.property_settings(ix) {
|
||||
if setting.key.as_ref() == "newline.only" {
|
||||
let setting_key = setting.key.as_ref();
|
||||
if setting_key == "newline.only" {
|
||||
config.newline_only = true
|
||||
}
|
||||
if setting_key == "rainbow.exclude" {
|
||||
config.rainbow_exclude = true
|
||||
}
|
||||
}
|
||||
config
|
||||
})
|
||||
|
|
@ -2640,8 +2645,9 @@ pub fn rust_lang() -> Arc<Language> {
|
|||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
(closure_parameters "|" @open "|" @close)"#,
|
||||
(closure_parameters "|" @open "|" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))"#,
|
||||
)),
|
||||
text_objects: Some(Cow::from(
|
||||
r#"
|
||||
|
|
|
|||
|
|
@ -54,14 +54,14 @@ pub struct AllLanguageSettings {
|
|||
pub(crate) file_types: FxHashMap<Arc<str>, GlobSet>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WhitespaceMap {
|
||||
pub space: SharedString,
|
||||
pub tab: SharedString,
|
||||
}
|
||||
|
||||
/// The settings for a particular language.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LanguageSettings {
|
||||
/// How many columns a tab should occupy.
|
||||
pub tab_size: NonZeroU32,
|
||||
|
|
@ -153,9 +153,11 @@ pub struct LanguageSettings {
|
|||
pub completions: CompletionSettings,
|
||||
/// Preferred debuggers for this language.
|
||||
pub debuggers: Vec<String>,
|
||||
/// Whether to use tree-sitter bracket queries to detect and colorize the brackets in the editor.
|
||||
pub colorize_brackets: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CompletionSettings {
|
||||
/// Controls how words are completed.
|
||||
/// For large documents, not all words may be fetched for completion.
|
||||
|
|
@ -207,7 +209,7 @@ pub struct IndentGuideSettings {
|
|||
pub background_coloring: settings::IndentGuideBackgroundColoring,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LanguageTaskSettings {
|
||||
/// Extra task variables to set for a particular language.
|
||||
pub variables: HashMap<String, String>,
|
||||
|
|
@ -225,7 +227,7 @@ pub struct LanguageTaskSettings {
|
|||
/// Allows to enable/disable formatting with Prettier
|
||||
/// and configure default Prettier, used when no project-level Prettier installation is found.
|
||||
/// Prettier formatting is disabled by default.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PrettierSettings {
|
||||
/// Enables or disables formatting with Prettier for a given language.
|
||||
pub allowed: bool,
|
||||
|
|
@ -584,6 +586,7 @@ impl settings::Settings for AllLanguageSettings {
|
|||
},
|
||||
show_completions_on_input: settings.show_completions_on_input.unwrap(),
|
||||
show_completion_documentation: settings.show_completion_documentation.unwrap(),
|
||||
colorize_brackets: settings.colorize_brackets.unwrap(),
|
||||
completions: CompletionSettings {
|
||||
words: completions.words.unwrap(),
|
||||
words_min_length: completions.words_min_length.unwrap() as usize,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("`" @open "`" @close)
|
||||
(("do" @open "done" @close) (#set! newline.only))
|
||||
((case_statement ("in" @open "esac" @close)) (#set! newline.only))
|
||||
((if_statement (elif_clause ("then" @open)) (else_clause ("else" @close))) (#set! newline.only))
|
||||
((if_statement (else_clause ("else" @open)) "fi" @close) (#set! newline.only))
|
||||
((if_statement ("then" @open) (elif_clause ("elif" @close))) (#set! newline.only))
|
||||
((if_statement ("then" @open) (else_clause ("else" @close))) (#set! newline.only))
|
||||
((if_statement ("then" @open "fi" @close)) (#set! newline.only))
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("`" @open "`" @close) (#set! rainbow.exclude))
|
||||
(("do" @open "done" @close) (#set! newline.only) (#set! rainbow.exclude))
|
||||
((case_statement ("in" @open "esac" @close)) (#set! newline.only) (#set! rainbow.exclude))
|
||||
((if_statement (elif_clause ("then" @open)) (else_clause ("else" @close))) (#set! newline.only) (#set! rainbow.exclude))
|
||||
((if_statement (else_clause ("else" @open)) "fi" @close) (#set! newline.only) (#set! rainbow.exclude))
|
||||
((if_statement ("then" @open) (elif_clause ("elif" @close))) (#set! newline.only) (#set! rainbow.exclude))
|
||||
((if_statement ("then" @open) (else_clause ("else" @close))) (#set! newline.only) (#set! rainbow.exclude))
|
||||
((if_statement ("then" @open "fi" @close)) (#set! newline.only) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("`" @open "`" @close)
|
||||
((rune_literal) @open @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("`" @open "`" @close) (#set! rainbow.exclude))
|
||||
((rune_literal) @open @close (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
("<" @open ">" @close)
|
||||
("<" @open "/>" @close)
|
||||
("</" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
("`" @open "`" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))
|
||||
(("`" @open "`" @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("`" @open "`" @close)
|
||||
("'" @open "'" @close)
|
||||
((fenced_code_block_delimiter) @open (fenced_code_block_delimiter) @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("`" @open "`" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))
|
||||
(((fenced_code_block_delimiter) @open (fenced_code_block_delimiter) @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
((string_start) @open (string_end) @close)
|
||||
(((string_start) @open (string_end) @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
(closure_parameters "|" @open "|" @close)
|
||||
("'" @open "'" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
("<" @open ">" @close)
|
||||
("<" @open "/>" @close)
|
||||
("</" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
("`" @open "`" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))
|
||||
(("`" @open "`" @close) (#set! rainbow.exclude))
|
||||
|
||||
((jsx_element (jsx_opening_element) @open (jsx_closing_element) @close) (#set! newline.only))
|
||||
((jsx_element (jsx_opening_element) @open (jsx_closing_element) @close) (#set! newline.only) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
("`" @open "`" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))
|
||||
(("`" @open "`" @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
(("'" @open "'" @close) (#set! rainbow.exclude))
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ use collections::{BTreeMap, Bound, HashMap, HashSet};
|
|||
use gpui::{App, Context, Entity, EntityId, EventEmitter};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharClassifier,
|
||||
CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntryRef, DiskState, File,
|
||||
IndentGuideSettings, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline,
|
||||
OutlineItem, Point, PointUtf16, Selection, TextDimension, TextObject, ToOffset as _,
|
||||
AutoindentMode, BracketMatch, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability,
|
||||
CharClassifier, CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntryRef, DiskState,
|
||||
File, IndentGuideSettings, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
|
||||
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, TextObject, ToOffset as _,
|
||||
ToPoint as _, TransactionId, TreeSitterOptions, Unclipped,
|
||||
language_settings::{LanguageSettings, language_settings},
|
||||
};
|
||||
|
|
@ -5400,7 +5400,6 @@ impl MultiBufferSnapshot {
|
|||
{
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let mut excerpt = self.excerpt_containing(range.clone())?;
|
||||
|
||||
Some(
|
||||
excerpt
|
||||
.buffer()
|
||||
|
|
@ -5410,15 +5409,17 @@ impl MultiBufferSnapshot {
|
|||
BufferOffset(pair.open_range.start)..BufferOffset(pair.open_range.end);
|
||||
let close_range =
|
||||
BufferOffset(pair.close_range.start)..BufferOffset(pair.close_range.end);
|
||||
if excerpt.contains_buffer_range(open_range.start..close_range.end) {
|
||||
Some((
|
||||
excerpt.map_range_from_buffer(open_range),
|
||||
excerpt.map_range_from_buffer(close_range),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
excerpt
|
||||
.contains_buffer_range(open_range.start..close_range.end)
|
||||
.then(|| BracketMatch {
|
||||
open_range: excerpt.map_range_from_buffer(open_range),
|
||||
close_range: excerpt.map_range_from_buffer(close_range),
|
||||
color_index: pair.color_index,
|
||||
newline_only: pair.newline_only,
|
||||
syntax_layer_depth: pair.syntax_layer_depth,
|
||||
})
|
||||
})
|
||||
.map(BracketMatch::bracket_ranges),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ use crate::{
|
|||
lsp_command::{self, *},
|
||||
lsp_store::{
|
||||
self,
|
||||
inlay_hint_cache::BufferChunk,
|
||||
log_store::{GlobalLogStore, LanguageServerKind},
|
||||
},
|
||||
manifest_tree::{
|
||||
|
|
@ -73,6 +72,7 @@ use language::{
|
|||
serialize_lsp_edit, serialize_version,
|
||||
},
|
||||
range_from_lsp, range_to_lsp,
|
||||
row_chunk::RowChunk,
|
||||
};
|
||||
use lsp::{
|
||||
AdapterServerCapabilities, CodeActionKind, CompletionContext, CompletionOptions,
|
||||
|
|
@ -117,7 +117,7 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
use sum_tree::Dimensions;
|
||||
use text::{Anchor, BufferId, LineEnding, OffsetRangeExt, Point, ToPoint as _};
|
||||
use text::{Anchor, BufferId, LineEnding, OffsetRangeExt, ToPoint as _};
|
||||
|
||||
use util::{
|
||||
ConnectionResult, ResultExt as _, debug_panic, defer, maybe, merge_json_value_into,
|
||||
|
|
@ -3590,7 +3590,7 @@ pub struct BufferLspData {
|
|||
code_lens: Option<CodeLensData>,
|
||||
inlay_hints: BufferInlayHints,
|
||||
lsp_requests: HashMap<LspKey, HashMap<LspRequestId, Task<()>>>,
|
||||
chunk_lsp_requests: HashMap<LspKey, HashMap<BufferChunk, LspRequestId>>,
|
||||
chunk_lsp_requests: HashMap<LspKey, HashMap<RowChunk, LspRequestId>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
@ -6706,7 +6706,7 @@ impl LspStore {
|
|||
self.latest_lsp_data(buffer, cx)
|
||||
.inlay_hints
|
||||
.applicable_chunks(ranges)
|
||||
.map(|chunk| chunk.start..chunk.end)
|
||||
.map(|chunk| chunk.row_range())
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
|
@ -6729,7 +6729,6 @@ impl LspStore {
|
|||
known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>> {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let next_hint_id = self.next_hint_id.clone();
|
||||
let lsp_data = self.latest_lsp_data(&buffer, cx);
|
||||
let query_version = lsp_data.buffer_version.clone();
|
||||
|
|
@ -6758,14 +6757,12 @@ impl LspStore {
|
|||
let mut ranges_to_query = None;
|
||||
let applicable_chunks = existing_inlay_hints
|
||||
.applicable_chunks(ranges.as_slice())
|
||||
.filter(|chunk| !known_chunks.contains(&(chunk.start..chunk.end)))
|
||||
.filter(|chunk| !known_chunks.contains(&chunk.row_range()))
|
||||
.collect::<Vec<_>>();
|
||||
if applicable_chunks.is_empty() {
|
||||
return HashMap::default();
|
||||
}
|
||||
|
||||
let last_chunk_number = existing_inlay_hints.buffer_chunks_len() - 1;
|
||||
|
||||
for row_chunk in applicable_chunks {
|
||||
match (
|
||||
existing_inlay_hints
|
||||
|
|
@ -6779,16 +6776,12 @@ impl LspStore {
|
|||
.cloned(),
|
||||
) {
|
||||
(None, None) => {
|
||||
let end = if last_chunk_number == row_chunk.id {
|
||||
Point::new(row_chunk.end, buffer_snapshot.line_len(row_chunk.end))
|
||||
} else {
|
||||
Point::new(row_chunk.end, 0)
|
||||
let Some(chunk_range) = existing_inlay_hints.chunk_range(row_chunk) else {
|
||||
continue;
|
||||
};
|
||||
ranges_to_query.get_or_insert_with(Vec::new).push((
|
||||
row_chunk,
|
||||
buffer_snapshot.anchor_before(Point::new(row_chunk.start, 0))
|
||||
..buffer_snapshot.anchor_after(end),
|
||||
));
|
||||
ranges_to_query
|
||||
.get_or_insert_with(Vec::new)
|
||||
.push((row_chunk, chunk_range));
|
||||
}
|
||||
(None, Some(fetched_hints)) => hint_fetch_tasks.push((row_chunk, fetched_hints)),
|
||||
(Some(cached_hints), None) => {
|
||||
|
|
@ -6796,7 +6789,7 @@ impl LspStore {
|
|||
if for_server.is_none_or(|for_server| for_server == server_id) {
|
||||
cached_inlay_hints
|
||||
.get_or_insert_with(HashMap::default)
|
||||
.entry(row_chunk.start..row_chunk.end)
|
||||
.entry(row_chunk.row_range())
|
||||
.or_insert_with(HashMap::default)
|
||||
.entry(server_id)
|
||||
.or_insert_with(Vec::new)
|
||||
|
|
@ -6810,7 +6803,7 @@ impl LspStore {
|
|||
if for_server.is_none_or(|for_server| for_server == server_id) {
|
||||
cached_inlay_hints
|
||||
.get_or_insert_with(HashMap::default)
|
||||
.entry(row_chunk.start..row_chunk.end)
|
||||
.entry(row_chunk.row_range())
|
||||
.or_insert_with(HashMap::default)
|
||||
.entry(server_id)
|
||||
.or_insert_with(Vec::new)
|
||||
|
|
@ -6896,7 +6889,7 @@ impl LspStore {
|
|||
.map(|(row_chunk, hints)| (row_chunk, Task::ready(Ok(hints))))
|
||||
.chain(hint_fetch_tasks.into_iter().map(|(chunk, hints_fetch)| {
|
||||
(
|
||||
chunk.start..chunk.end,
|
||||
chunk.row_range(),
|
||||
cx.spawn(async move |_, _| {
|
||||
hints_fetch.await.map_err(|e| {
|
||||
if e.error_code() != ErrorCode::Internal {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ use std::{collections::hash_map, ops::Range, sync::Arc};
|
|||
use collections::HashMap;
|
||||
use futures::future::Shared;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::{Buffer, BufferRow, BufferSnapshot};
|
||||
use language::{
|
||||
Buffer,
|
||||
row_chunk::{RowChunk, RowChunks},
|
||||
};
|
||||
use lsp::LanguageServerId;
|
||||
use text::OffsetRangeExt;
|
||||
use util::RangeExt as _;
|
||||
use text::Anchor;
|
||||
|
||||
use crate::{InlayHint, InlayId};
|
||||
|
||||
|
|
@ -46,8 +48,7 @@ impl InvalidationStrategy {
|
|||
}
|
||||
|
||||
pub struct BufferInlayHints {
|
||||
snapshot: BufferSnapshot,
|
||||
buffer_chunks: Vec<BufferChunk>,
|
||||
chunks: RowChunks,
|
||||
hints_by_chunks: Vec<Option<CacheInlayHints>>,
|
||||
fetches_by_chunks: Vec<Option<CacheInlayHintsTask>>,
|
||||
hints_by_id: HashMap<InlayId, HintForId>,
|
||||
|
|
@ -62,25 +63,10 @@ struct HintForId {
|
|||
position: usize,
|
||||
}
|
||||
|
||||
/// An range of rows, exclusive as [`lsp::Range`] and
|
||||
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#range>
|
||||
/// denote.
|
||||
///
|
||||
/// Represents an area in a text editor, adjacent to other ones.
|
||||
/// Together, chunks form entire document at a particular version [`clock::Global`].
|
||||
/// Each chunk is queried for inlays as `(start_row, 0)..(end_exclusive, 0)` via
|
||||
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHintParams>
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct BufferChunk {
|
||||
pub id: usize,
|
||||
pub start: BufferRow,
|
||||
pub end: BufferRow,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BufferInlayHints {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BufferInlayHints")
|
||||
.field("buffer_chunks", &self.buffer_chunks)
|
||||
.field("buffer_chunks", &self.chunks)
|
||||
.field("hints_by_chunks", &self.hints_by_chunks)
|
||||
.field("fetches_by_chunks", &self.fetches_by_chunks)
|
||||
.field("hints_by_id", &self.hints_by_id)
|
||||
|
|
@ -92,58 +78,30 @@ const MAX_ROWS_IN_A_CHUNK: u32 = 50;
|
|||
|
||||
impl BufferInlayHints {
|
||||
pub fn new(buffer: &Entity<Buffer>, cx: &mut App) -> Self {
|
||||
let buffer = buffer.read(cx);
|
||||
let snapshot = buffer.snapshot();
|
||||
let buffer_point_range = (0..buffer.len()).to_point(&snapshot);
|
||||
let last_row = buffer_point_range.end.row;
|
||||
let buffer_chunks = (buffer_point_range.start.row..=last_row)
|
||||
.step_by(MAX_ROWS_IN_A_CHUNK as usize)
|
||||
.enumerate()
|
||||
.map(|(id, chunk_start)| BufferChunk {
|
||||
id,
|
||||
start: chunk_start,
|
||||
end: (chunk_start + MAX_ROWS_IN_A_CHUNK).min(last_row),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let chunks = RowChunks::new(buffer.read(cx).text_snapshot(), MAX_ROWS_IN_A_CHUNK);
|
||||
|
||||
Self {
|
||||
hints_by_chunks: vec![None; buffer_chunks.len()],
|
||||
fetches_by_chunks: vec![None; buffer_chunks.len()],
|
||||
hints_by_chunks: vec![None; chunks.len()],
|
||||
fetches_by_chunks: vec![None; chunks.len()],
|
||||
latest_invalidation_requests: HashMap::default(),
|
||||
hints_by_id: HashMap::default(),
|
||||
hint_resolves: HashMap::default(),
|
||||
snapshot,
|
||||
buffer_chunks,
|
||||
chunks,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn applicable_chunks(
|
||||
&self,
|
||||
ranges: &[Range<text::Anchor>],
|
||||
) -> impl Iterator<Item = BufferChunk> {
|
||||
let row_ranges = ranges
|
||||
.iter()
|
||||
.map(|range| range.to_point(&self.snapshot))
|
||||
// Be lenient and yield multiple chunks if they "touch" the exclusive part of the range.
|
||||
// This will result in LSP hints [re-]queried for more ranges, but also more hints already visible when scrolling around.
|
||||
.map(|point_range| point_range.start.row..point_range.end.row + 1)
|
||||
.collect::<Vec<_>>();
|
||||
self.buffer_chunks
|
||||
.iter()
|
||||
.filter(move |chunk| {
|
||||
let chunk_range = chunk.start..=chunk.end;
|
||||
row_ranges
|
||||
.iter()
|
||||
.any(|row_range| chunk_range.overlaps(&row_range))
|
||||
})
|
||||
.copied()
|
||||
) -> impl Iterator<Item = RowChunk> {
|
||||
self.chunks.applicable_chunks(ranges)
|
||||
}
|
||||
|
||||
pub fn cached_hints(&mut self, chunk: &BufferChunk) -> Option<&CacheInlayHints> {
|
||||
pub fn cached_hints(&mut self, chunk: &RowChunk) -> Option<&CacheInlayHints> {
|
||||
self.hints_by_chunks[chunk.id].as_ref()
|
||||
}
|
||||
|
||||
pub fn fetched_hints(&mut self, chunk: &BufferChunk) -> &mut Option<CacheInlayHintsTask> {
|
||||
pub fn fetched_hints(&mut self, chunk: &RowChunk) -> &mut Option<CacheInlayHintsTask> {
|
||||
&mut self.fetches_by_chunks[chunk.id]
|
||||
}
|
||||
|
||||
|
|
@ -177,8 +135,8 @@ impl BufferInlayHints {
|
|||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.hints_by_chunks = vec![None; self.buffer_chunks.len()];
|
||||
self.fetches_by_chunks = vec![None; self.buffer_chunks.len()];
|
||||
self.hints_by_chunks = vec![None; self.chunks.len()];
|
||||
self.fetches_by_chunks = vec![None; self.chunks.len()];
|
||||
self.hints_by_id.clear();
|
||||
self.hint_resolves.clear();
|
||||
self.latest_invalidation_requests.clear();
|
||||
|
|
@ -186,7 +144,7 @@ impl BufferInlayHints {
|
|||
|
||||
pub fn insert_new_hints(
|
||||
&mut self,
|
||||
chunk: BufferChunk,
|
||||
chunk: RowChunk,
|
||||
server_id: LanguageServerId,
|
||||
new_hints: Vec<(InlayId, InlayHint)>,
|
||||
) {
|
||||
|
|
@ -225,10 +183,6 @@ impl BufferInlayHints {
|
|||
Some(hint)
|
||||
}
|
||||
|
||||
pub fn buffer_chunks_len(&self) -> usize {
|
||||
self.buffer_chunks.len()
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_for_server_refresh(
|
||||
&mut self,
|
||||
for_server: LanguageServerId,
|
||||
|
|
@ -263,7 +217,7 @@ impl BufferInlayHints {
|
|||
true
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_for_chunk(&mut self, chunk: BufferChunk) {
|
||||
pub(crate) fn invalidate_for_chunk(&mut self, chunk: RowChunk) {
|
||||
self.fetches_by_chunks[chunk.id] = None;
|
||||
if let Some(hints_by_server) = self.hints_by_chunks[chunk.id].take() {
|
||||
for (hint_id, _) in hints_by_server.into_values().flatten() {
|
||||
|
|
@ -272,4 +226,8 @@ impl BufferInlayHints {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunk_range(&self, chunk: RowChunk) -> Option<Range<Anchor>> {
|
||||
self.chunks.chunk_range(chunk)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ node_runtime = { workspace = true, features = ["test-support"] }
|
|||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
remote = { workspace = true, features = ["test-support"] }
|
||||
theme = { workspace = true, features = ["test-support"] }
|
||||
language_model = { workspace = true, features = ["test-support"] }
|
||||
lsp = { workspace = true, features = ["test-support"] }
|
||||
prompt_store.workspace = true
|
||||
|
|
|
|||
|
|
@ -1499,6 +1499,14 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
|
|||
cx: &mut TestAppContext,
|
||||
server_cx: &mut TestAppContext,
|
||||
) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
editor::init(cx);
|
||||
});
|
||||
|
||||
use editor::Editor;
|
||||
use gpui::VisualContext;
|
||||
let text_2 = "
|
||||
|
|
|
|||
|
|
@ -49,5 +49,6 @@ editor = { workspace = true, features = ["test-support"] }
|
|||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
lsp.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
unindent.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
|||
|
|
@ -2449,6 +2449,7 @@ pub mod tests {
|
|||
use editor::{DisplayPoint, display_map::DisplayRow};
|
||||
use gpui::{Action, TestAppContext, VisualTestContext, WindowHandle};
|
||||
use language::{FakeLspAdapter, rust_lang};
|
||||
use pretty_assertions::assert_eq;
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
use settings::{InlayHintSettingsContent, SettingsStore};
|
||||
|
|
@ -2507,10 +2508,6 @@ pub mod tests {
|
|||
DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40),
|
||||
match_background_color
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9),
|
||||
selection_background_color
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9),
|
||||
match_background_color
|
||||
|
|
|
|||
|
|
@ -412,6 +412,10 @@ pub struct LanguageSettingsContent {
|
|||
///
|
||||
/// Default: []
|
||||
pub debuggers: Option<Vec<String>>,
|
||||
/// Whether to use tree-sitter bracket queries to detect and colorize the brackets in the editor.
|
||||
///
|
||||
/// Default: false
|
||||
pub colorize_brackets: Option<bool>,
|
||||
}
|
||||
|
||||
/// Controls how whitespace should be displayedin the editor.
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ pub struct ThemeStyleContent {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
|
||||
pub struct AccentContent(pub Option<String>);
|
||||
pub struct AccentContent(pub Option<SharedString>);
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
|
||||
pub struct PlayerColorContent {
|
||||
|
|
|
|||
|
|
@ -450,6 +450,7 @@ impl VsCodeSettings {
|
|||
prettier: None,
|
||||
remove_trailing_whitespace_on_save: self.read_bool("editor.trimAutoWhitespace"),
|
||||
show_completion_documentation: None,
|
||||
colorize_brackets: self.read_bool("editor.bracketPairColorization.enabled"),
|
||||
show_completions_on_input: self.read_bool("editor.suggestOnTriggerCharacters"),
|
||||
show_edit_predictions: self.read_bool("editor.inlineSuggest.enabled"),
|
||||
show_whitespaces: self.read_enum("editor.renderWhitespace", |s| {
|
||||
|
|
|
|||
|
|
@ -6991,6 +6991,25 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
|
|||
metadata: None,
|
||||
files: USER | PROJECT,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Colorize brackets",
|
||||
description: "Whether to colorize brackets in the editor.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("languages.$(language).colorize_brackets"),
|
||||
pick: |settings_content| {
|
||||
language_settings_field(settings_content, |language| {
|
||||
language.colorize_brackets.as_ref()
|
||||
})
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
language_settings_field_mut(settings_content, value, |language, value| {
|
||||
language.colorize_brackets = value;
|
||||
})
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER | PROJECT,
|
||||
}),
|
||||
]);
|
||||
|
||||
if current_language().is_none() {
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ You can also add agents through your `settings.json`, by specifying certain fiel
|
|||
{
|
||||
"agent_servers": {
|
||||
"My Custom Agent": {
|
||||
"type": "custom",
|
||||
"command": "node",
|
||||
"args": ["~/projects/agent/index.js", "--acp"],
|
||||
"env": {}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ You can customize a wide range of settings for each language, including:
|
|||
- [`soft_wrap`](./configuring-zed.md#soft-wrap): How to wrap long lines of code
|
||||
- [`show_completions_on_input`](./configuring-zed.md#show-completions-on-input): Whether or not to show completions as you type
|
||||
- [`show_completion_documentation`](./configuring-zed.md#show-completion-documentation): Whether to display inline and alongside documentation for items in the completions menu
|
||||
- [`colorize_brackets`](./configuring-zed.md#colorize-brackets): Whether to use tree-sitter bracket queries to detect and colorize the brackets in the editor (also known as "rainbow brackets")
|
||||
|
||||
These settings allow you to maintain specific coding styles across different languages and projects.
|
||||
|
||||
|
|
|
|||
|
|
@ -4687,6 +4687,18 @@ See the [debugger page](./debugger.md) for more information about debugging supp
|
|||
},
|
||||
```
|
||||
|
||||
## Colorize Brackets
|
||||
|
||||
- Description: Whether to use tree-sitter bracket queries to detect and colorize the brackets in the editor (also known as "rainbow brackets").
|
||||
- Setting: `colorize_brackets`
|
||||
- Default: `false`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
The colors that are used for different indentation levels are defined in the theme (theme key: `accents`). They can be customized by using theme overrides.
|
||||
|
||||
## Unnecessary Code Fade
|
||||
|
||||
- Description: How much to fade out unused code.
|
||||
|
|
|
|||
|
|
@ -154,6 +154,14 @@ This query identifies opening and closing brackets, braces, and quotation marks.
|
|||
| @open | Captures opening brackets, braces, and quotes |
|
||||
| @close | Captures closing brackets, braces, and quotes |
|
||||
|
||||
Zed uses these to highlight matching brackets: painting each bracket pair with a different color ("rainbow brackets") and highlighting the brackets if the cursor is inside the bracket pair.
|
||||
|
||||
To opt out of rainbow brackets colorization, add the following to the corresponding `brackets.scm` entry:
|
||||
|
||||
```scheme
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
```
|
||||
|
||||
### Code outline/structure
|
||||
|
||||
The `outline.scm` file defines the structure for the code outline.
|
||||
|
|
|
|||
|
|
@ -51,7 +51,15 @@ For example, add the following to your `settings.json` if you wish to override t
|
|||
"comment.doc": {
|
||||
"font_style": "italic"
|
||||
}
|
||||
}
|
||||
},
|
||||
"accents": [
|
||||
"#ff0000",
|
||||
"#ff7f00",
|
||||
"#ffff00",
|
||||
"#00ff00",
|
||||
"#0000ff",
|
||||
"#8b00ff"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@ TBD: Centered layout related settings
|
|||
"lsp_document_colors": "inlay", // none, inlay, border, background
|
||||
// When to show the scrollbar in the completion menu.
|
||||
"completion_menu_scrollbar": "never", // auto, system, always, never
|
||||
// Turn on colorization of brackets in editors (configurable per language)
|
||||
"colorize_brackets": true,
|
||||
```
|
||||
|
||||
### Edit Predictions {#editor-ai}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
("<" @open "/>" @close)
|
||||
("</" @open ">" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
((element (start_tag) @open (end_tag) @close) (#set! newline.only))
|
||||
(("\"" @open "\"" @close) (#set! rainbow.exclude))
|
||||
((element (start_tag) @open (end_tag) @close) (#set! newline.only) (#set! rainbow.exclude))
|
||||
|
|
|
|||
Loading…
Reference in a new issue