Add textobjects queries (#20924)
Co-Authored-By: Max <max@zed.dev> Release Notes: - vim: Added motions `[[`, `[]`, `]]`, `][` for navigating by section, `[m`, `]m`, `[M`, `]M` for navigating by method, and `[*`, `]*`, `[/`, `]/` for comments. These currently only work for languages built in to Zed, as they are powered by new tree-sitter queries. - vim: Added new text objects: `ic`, `ac` for inside/around classes, `if`,`af` for functions/methods, and `g c` for comments. These currently only work for languages built in to Zed, as they are powered by new tree-sitter queries. --------- Co-authored-by: Max <max@zed.dev>
This commit is contained in:
parent
c443307c19
commit
75c9dc179b
28 changed files with 1205 additions and 26 deletions
|
@ -1,6 +1,10 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use crate::{motion::right, state::Mode, Vim};
|
||||
use crate::{
|
||||
motion::right,
|
||||
state::{Mode, Operator},
|
||||
Vim,
|
||||
};
|
||||
use editor::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
movement::{self, FindRange},
|
||||
|
@ -10,7 +14,7 @@ use editor::{
|
|||
use itertools::Itertools;
|
||||
|
||||
use gpui::{actions, impl_actions, ViewContext};
|
||||
use language::{BufferSnapshot, CharKind, Point, Selection};
|
||||
use language::{BufferSnapshot, CharKind, Point, Selection, TextObject, TreeSitterOptions};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use serde::Deserialize;
|
||||
|
||||
|
@ -30,6 +34,9 @@ pub enum Object {
|
|||
Argument,
|
||||
IndentObj { include_below: bool },
|
||||
Tag,
|
||||
Method,
|
||||
Class,
|
||||
Comment,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
|
@ -61,7 +68,10 @@ actions!(
|
|||
CurlyBrackets,
|
||||
AngleBrackets,
|
||||
Argument,
|
||||
Tag
|
||||
Tag,
|
||||
Method,
|
||||
Class,
|
||||
Comment
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -107,6 +117,18 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
|||
Vim::action(editor, cx, |vim, _: &Argument, cx| {
|
||||
vim.object(Object::Argument, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, _: &Method, cx| {
|
||||
vim.object(Object::Method, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, _: &Class, cx| {
|
||||
vim.object(Object::Class, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, _: &Comment, cx| {
|
||||
if !matches!(vim.active_operator(), Some(Operator::Object { .. })) {
|
||||
vim.push_operator(Operator::Object { around: true }, cx);
|
||||
}
|
||||
vim.object(Object::Comment, cx)
|
||||
});
|
||||
Vim::action(
|
||||
editor,
|
||||
cx,
|
||||
|
@ -144,6 +166,9 @@ impl Object {
|
|||
| Object::CurlyBrackets
|
||||
| Object::SquareBrackets
|
||||
| Object::Argument
|
||||
| Object::Method
|
||||
| Object::Class
|
||||
| Object::Comment
|
||||
| Object::IndentObj { .. } => true,
|
||||
}
|
||||
}
|
||||
|
@ -162,12 +187,15 @@ impl Object {
|
|||
| Object::Parentheses
|
||||
| Object::SquareBrackets
|
||||
| Object::Tag
|
||||
| Object::Method
|
||||
| Object::Class
|
||||
| Object::Comment
|
||||
| Object::CurlyBrackets
|
||||
| Object::AngleBrackets => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn target_visual_mode(self, current_mode: Mode) -> Mode {
|
||||
pub fn target_visual_mode(self, current_mode: Mode, around: bool) -> Mode {
|
||||
match self {
|
||||
Object::Word { .. }
|
||||
| Object::Sentence
|
||||
|
@ -186,8 +214,16 @@ impl Object {
|
|||
| Object::AngleBrackets
|
||||
| Object::VerticalBars
|
||||
| Object::Tag
|
||||
| Object::Comment
|
||||
| Object::Argument
|
||||
| Object::IndentObj { .. } => Mode::Visual,
|
||||
Object::Method | Object::Class => {
|
||||
if around {
|
||||
Mode::VisualLine
|
||||
} else {
|
||||
Mode::Visual
|
||||
}
|
||||
}
|
||||
Object::Paragraph => Mode::VisualLine,
|
||||
}
|
||||
}
|
||||
|
@ -238,6 +274,33 @@ impl Object {
|
|||
Object::AngleBrackets => {
|
||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')
|
||||
}
|
||||
Object::Method => text_object(
|
||||
map,
|
||||
relative_to,
|
||||
if around {
|
||||
TextObject::AroundFunction
|
||||
} else {
|
||||
TextObject::InsideFunction
|
||||
},
|
||||
),
|
||||
Object::Comment => text_object(
|
||||
map,
|
||||
relative_to,
|
||||
if around {
|
||||
TextObject::AroundComment
|
||||
} else {
|
||||
TextObject::InsideComment
|
||||
},
|
||||
),
|
||||
Object::Class => text_object(
|
||||
map,
|
||||
relative_to,
|
||||
if around {
|
||||
TextObject::AroundClass
|
||||
} else {
|
||||
TextObject::InsideClass
|
||||
},
|
||||
),
|
||||
Object::Argument => argument(map, relative_to, around),
|
||||
Object::IndentObj { include_below } => indent(map, relative_to, around, include_below),
|
||||
}
|
||||
|
@ -441,6 +504,47 @@ fn around_next_word(
|
|||
Some(start..end)
|
||||
}
|
||||
|
||||
fn text_object(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
target: TextObject,
|
||||
) -> Option<Range<DisplayPoint>> {
|
||||
let snapshot = &map.buffer_snapshot;
|
||||
let offset = relative_to.to_offset(map, Bias::Left);
|
||||
|
||||
let excerpt = snapshot.excerpt_containing(offset..offset)?;
|
||||
let buffer = excerpt.buffer();
|
||||
|
||||
let mut matches: Vec<Range<usize>> = buffer
|
||||
.text_object_ranges(offset..offset, TreeSitterOptions::default())
|
||||
.filter_map(|(r, m)| if m == target { Some(r) } else { None })
|
||||
.collect();
|
||||
matches.sort_by_key(|r| (r.end - r.start));
|
||||
if let Some(range) = matches.first() {
|
||||
return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
|
||||
}
|
||||
|
||||
let around = target.around()?;
|
||||
let mut matches: Vec<Range<usize>> = buffer
|
||||
.text_object_ranges(offset..offset, TreeSitterOptions::default())
|
||||
.filter_map(|(r, m)| if m == around { Some(r) } else { None })
|
||||
.collect();
|
||||
matches.sort_by_key(|r| (r.end - r.start));
|
||||
let around_range = matches.first()?;
|
||||
|
||||
let mut matches: Vec<Range<usize>> = buffer
|
||||
.text_object_ranges(around_range.clone(), TreeSitterOptions::default())
|
||||
.filter_map(|(r, m)| if m == target { Some(r) } else { None })
|
||||
.collect();
|
||||
matches.sort_by_key(|r| r.start);
|
||||
if let Some(range) = matches.first() {
|
||||
if !range.is_empty() {
|
||||
return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
|
||||
}
|
||||
}
|
||||
return Some(around_range.start.to_display_point(map)..around_range.end.to_display_point(map));
|
||||
}
|
||||
|
||||
fn argument(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue