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:
co-authored by
MrSubidubi
Lukas Wirth
MrSubidubi
Lukas Wirth
Smit Barmase
parent
e6e5ccbf10
commit
7e341bcf94
+212
-57
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user