Implement bracket matching using queries
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
e9bd872b06
commit
8b7a314474
8 changed files with 50 additions and 43 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2714,7 +2714,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tree-sitter"
|
name = "tree-sitter"
|
||||||
version = "0.19.5"
|
version = "0.19.5"
|
||||||
source = "git+https://github.com/tree-sitter/tree-sitter?rev=036aceed574c2c23eee8f0ff90be5a2409e524c1#036aceed574c2c23eee8f0ff90be5a2409e524c1"
|
source = "git+https://github.com/tree-sitter/tree-sitter?rev=97dfee63257b5e92197399b381aa993514640adf#97dfee63257b5e92197399b381aa993514640adf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -2,14 +2,14 @@
|
||||||
members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"]
|
members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"}
|
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
|
||||||
tree-sitter = {git = "https://github.com/tree-sitter/tree-sitter", rev = "036aceed574c2c23eee8f0ff90be5a2409e524c1"}
|
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "97dfee63257b5e92197399b381aa993514640adf" }
|
||||||
|
|
||||||
# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457
|
# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457
|
||||||
cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
|
cocoa = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" }
|
||||||
cocoa-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
|
cocoa-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" }
|
||||||
core-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
|
core-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" }
|
||||||
core-graphics = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
|
core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
split-debuginfo = "unpacked"
|
split-debuginfo = "unpacked"
|
||||||
|
|
6
zed/languages/rust/brackets.scm
Normal file
6
zed/languages/rust/brackets.scm
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
("(" @open ")" @close)
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
||||||
|
("<" @open ">" @close)
|
||||||
|
("\"" @open "\"" @close)
|
||||||
|
(closure_parameters "|" @open "|" @close)
|
|
@ -727,41 +727,27 @@ impl Buffer {
|
||||||
&self,
|
&self,
|
||||||
range: Range<T>,
|
range: Range<T>,
|
||||||
) -> Option<(Range<usize>, Range<usize>)> {
|
) -> Option<(Range<usize>, Range<usize>)> {
|
||||||
let mut bracket_ranges = None;
|
let (lang, tree) = self.language.as_ref().zip(self.syntax_tree())?;
|
||||||
if let Some((lang, tree)) = self.language.as_ref().zip(self.syntax_tree()) {
|
let open_capture_ix = lang.brackets_query.capture_index_for_name("open")?;
|
||||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
let close_capture_ix = lang.brackets_query.capture_index_for_name("close")?;
|
||||||
let mut cursor = tree.root_node().walk();
|
|
||||||
'outer: loop {
|
|
||||||
let node = cursor.node();
|
|
||||||
if node.child_count() >= 2 {
|
|
||||||
if let Some((first_child, last_child)) =
|
|
||||||
node.child(0).zip(node.child(node.child_count() - 1))
|
|
||||||
{
|
|
||||||
for pair in &lang.config.bracket_pairs {
|
|
||||||
if pair.start == first_child.kind() && pair.end == last_child.kind() {
|
|
||||||
bracket_ranges =
|
|
||||||
Some((first_child.byte_range(), last_child.byte_range()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cursor.goto_first_child() {
|
// Find bracket pairs that *inclusively* contain the given range.
|
||||||
break;
|
let range = range.start.to_offset(self).saturating_sub(1)..range.end.to_offset(self) + 1;
|
||||||
}
|
let mut cursor = QueryCursorHandle::new();
|
||||||
|
let matches = cursor.set_byte_range(range.start, range.end).matches(
|
||||||
|
&lang.brackets_query,
|
||||||
|
tree.root_node(),
|
||||||
|
TextProvider(&self.visible_text),
|
||||||
|
);
|
||||||
|
|
||||||
while cursor.node().end_byte() < range.end {
|
// Get the ranges of the innermost pair of brackets.
|
||||||
if !cursor.goto_next_sibling() {
|
matches
|
||||||
break 'outer;
|
.filter_map(|mat| {
|
||||||
}
|
let open = mat.nodes_for_capture_index(open_capture_ix).next()?;
|
||||||
}
|
let close = mat.nodes_for_capture_index(close_capture_ix).next()?;
|
||||||
|
Some((open.byte_range(), close.byte_range()))
|
||||||
if cursor.node().start_byte() > range.start {
|
})
|
||||||
break;
|
.min_by_key(|(open_range, close_range)| close_range.end - open_range.start)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bracket_ranges
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&self, new_text: Arc<str>, ctx: &AppContext) -> Task<Diff> {
|
fn diff(&self, new_text: Arc<str>, ctx: &AppContext) -> Task<Diff> {
|
||||||
|
|
|
@ -1826,6 +1826,8 @@ impl BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_to_enclosing_bracket(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
pub fn move_to_enclosing_bracket(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
|
use super::RangeExt as _;
|
||||||
|
|
||||||
let buffer = self.buffer.read(ctx.as_ref());
|
let buffer = self.buffer.read(ctx.as_ref());
|
||||||
let mut selections = self.selections(ctx.as_ref()).to_vec();
|
let mut selections = self.selections(ctx.as_ref()).to_vec();
|
||||||
for selection in &mut selections {
|
for selection in &mut selections {
|
||||||
|
@ -1833,12 +1835,13 @@ impl BufferView {
|
||||||
if let Some((open_range, close_range)) =
|
if let Some((open_range, close_range)) =
|
||||||
buffer.enclosing_bracket_ranges(selection_range.clone())
|
buffer.enclosing_bracket_ranges(selection_range.clone())
|
||||||
{
|
{
|
||||||
|
let close_range = close_range.to_inclusive();
|
||||||
let destination = if close_range.contains(&selection_range.start)
|
let destination = if close_range.contains(&selection_range.start)
|
||||||
&& close_range.contains(&selection_range.end)
|
&& close_range.contains(&selection_range.end)
|
||||||
{
|
{
|
||||||
open_range.end
|
open_range.end
|
||||||
} else {
|
} else {
|
||||||
close_range.start
|
*close_range.start()
|
||||||
};
|
};
|
||||||
selection.start = buffer.anchor_before(destination);
|
selection.start = buffer.anchor_before(destination);
|
||||||
selection.end = selection.start.clone();
|
selection.end = selection.start.clone();
|
||||||
|
|
|
@ -535,6 +535,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
grammar: grammar.clone(),
|
grammar: grammar.clone(),
|
||||||
highlight_query,
|
highlight_query,
|
||||||
|
brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
|
||||||
theme_mapping: Default::default(),
|
theme_mapping: Default::default(),
|
||||||
});
|
});
|
||||||
lang.set_theme(&theme);
|
lang.set_theme(&theme);
|
||||||
|
|
|
@ -9,7 +9,10 @@ pub use buffer_element::*;
|
||||||
pub use buffer_view::*;
|
pub use buffer_view::*;
|
||||||
pub use display_map::DisplayPoint;
|
pub use display_map::DisplayPoint;
|
||||||
use display_map::*;
|
use display_map::*;
|
||||||
use std::{cmp, ops::Range};
|
use std::{
|
||||||
|
cmp,
|
||||||
|
ops::{Range, RangeInclusive},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum Bias {
|
pub enum Bias {
|
||||||
|
@ -19,10 +22,15 @@ pub enum Bias {
|
||||||
|
|
||||||
trait RangeExt<T> {
|
trait RangeExt<T> {
|
||||||
fn sorted(&self) -> Range<T>;
|
fn sorted(&self) -> Range<T>;
|
||||||
|
fn to_inclusive(&self) -> RangeInclusive<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Ord + Clone> RangeExt<T> for Range<T> {
|
impl<T: Ord + Clone> RangeExt<T> for Range<T> {
|
||||||
fn sorted(&self) -> Self {
|
fn sorted(&self) -> Self {
|
||||||
cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
|
cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_inclusive(&self) -> RangeInclusive<T> {
|
||||||
|
self.start.clone()..=self.end.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ pub struct LanguageDir;
|
||||||
pub struct LanguageConfig {
|
pub struct LanguageConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path_suffixes: Vec<String>,
|
pub path_suffixes: Vec<String>,
|
||||||
pub bracket_pairs: Vec<BracketPair>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -27,6 +26,7 @@ pub struct Language {
|
||||||
pub config: LanguageConfig,
|
pub config: LanguageConfig,
|
||||||
pub grammar: Grammar,
|
pub grammar: Grammar,
|
||||||
pub highlight_query: Query,
|
pub highlight_query: Query,
|
||||||
|
pub brackets_query: Query,
|
||||||
pub theme_mapping: Mutex<ThemeMap>,
|
pub theme_mapping: Mutex<ThemeMap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ impl LanguageRegistry {
|
||||||
config: rust_config,
|
config: rust_config,
|
||||||
grammar,
|
grammar,
|
||||||
highlight_query: Self::load_query(grammar, "rust/highlights.scm"),
|
highlight_query: Self::load_query(grammar, "rust/highlights.scm"),
|
||||||
|
brackets_query: Self::load_query(grammar, "rust/brackets.scm"),
|
||||||
theme_mapping: Mutex::new(ThemeMap::default()),
|
theme_mapping: Mutex::new(ThemeMap::default()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -106,6 +107,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
grammar,
|
grammar,
|
||||||
highlight_query: Query::new(grammar, "").unwrap(),
|
highlight_query: Query::new(grammar, "").unwrap(),
|
||||||
|
brackets_query: Query::new(grammar, "").unwrap(),
|
||||||
theme_mapping: Default::default(),
|
theme_mapping: Default::default(),
|
||||||
}),
|
}),
|
||||||
Arc::new(Language {
|
Arc::new(Language {
|
||||||
|
@ -116,6 +118,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
grammar,
|
grammar,
|
||||||
highlight_query: Query::new(grammar, "").unwrap(),
|
highlight_query: Query::new(grammar, "").unwrap(),
|
||||||
|
brackets_query: Query::new(grammar, "").unwrap(),
|
||||||
theme_mapping: Default::default(),
|
theme_mapping: Default::default(),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue