-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
vim: Update anyquotes and anybrackets to behave like mini.ai plugin #24167
base: main
Are you sure you want to change the base?
vim: Update anyquotes and anybrackets to behave like mini.ai plugin #24167
Conversation
hi @ConradIrwin I’ve been working on a new version of the New Tests and Behaviors
would improperly merge both quoted sections into something like:
Now, just like the real mini.ai plugin, the “false” string in the middle is correctly ignored, resulting in:
This behavior aligns with what the original mini.ai does, avoiding accidental merging of unrelated quotes.
pressing
which could be quite frustrating. Now, consistent with the original mini.ai, it only targets the parentheses around "hello":
This is much more intuitive when working with nested brackets on multiple lines. SummaryI’ve introduced new helper functions to achieve these behaviors and added test cases illustrating scenarios where the old implementation fell short. Please take a look at the new tests to see how each scenario is handled. I believe this new logic provides a much more seamless and predictable experience, matching the original mini.ai plugin’s design. Let me know what you think or if you have any questions! Thanks! |
I just want to take a moment to apologize for the multiple iterations regarding this feature. As a big fan of the mini.ai plugin in Neovim, I truly believe this feature will be loved by many developers, just as much as I do. That’s why I’m putting so much effort into making it happen, I’m really excited about its potential! Thank you for your patience and understanding :) I highly recommend everyone give this feature a try, I’m confident it will significantly enhance your coding experience. You won’t regret it! :) |
@oca159 nice! Thanks for this, and I like the new behavior. That said, we should not be doing anything O(n) in the size of the buffer if we can help it; and so I think we should use tree-sitter to do this. Luckily, Zed treats quotes like brackets for the purposes of this, so I think we can use the same structure of method for both:
Happy to pair with you on this: https://cal.com/conradirwin/pairing |
Hi @ConradIrwin, I’ve scheduled a meeting with you to discuss the Tree-sitter implementation, specifically focusing on the use of bracket_ranges and innermost_enclosing_bracket_ranges. I’ve been experimenting with these functions but haven’t had success yet. In the meantime, I’ll continue refactoring my code to better utilize them. Looking forward to our discussion, and thanks for your help! |
6c32b76
to
a208dd9
Compare
Hi @ConradIrwin, I’ve updated the PR. Could you please review it when you have a moment? Additionally, I wanted to highlight that the tests for quotes are currently failing due to the bug I mentioned earlier in today’s meeting. It appears that brackets_range is not functioning correctly with single and back quotes. Let me know if you need further details or if there’s anything specific I should address. Thanks! |
It seems that modifying the Tree-sitter queries in brackets.scm for TypeScript to the following resolves the issue: ("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
("'" @open "'" @close)
("`" @open "`" @close) With this change, it now works correctly for all types of quotes (double, single, and backticks). Additionally, it was necessary to update the VimTestContext initialization to:
To ensure the tests pass, the implementation of new_typescript should also be adjusted accordingly. pub async fn new_typescript(
capabilities: lsp::ServerCapabilities,
cx: &mut gpui::TestAppContext,
) -> EditorLspTestContext {
let mut word_characters: HashSet<char> = Default::default();
word_characters.insert('$');
word_characters.insert('#');
let language = Language::new(
LanguageConfig {
name: "Typescript".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
brackets: language::BracketPairConfig {
pairs: vec![
language::BracketPair {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
],
disabled_scopes_by_bracket_ix: Default::default(),
},
word_characters,
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
)
.with_queries(LanguageQueries {
brackets: Some(Cow::from(indoc! {r#"
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("'" @open "'" @close)
("`" @open "`" @close)
("\"" @open "\"" @close)"#})),
indents: Some(Cow::from(indoc! {r#"
[
(call_expression)
(assignment_expression)
(member_expression)
(lexical_declaration)
(variable_declaration)
(assignment_expression)
(if_statement)
(for_statement)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent
"#})),
..Default::default()
})
.expect("Could not parse queries");
Self::new(language, capabilities, cx).await
} After applying all the changes, I confirmed that the implementation now works correctly for all types of quotes. This means that the new Tree-sitter-based approach requires updating the brackets.scm queries for every language that needs to support this feature. I’m not sure if there’s a more efficient way to handle this, so I’d appreciate your thoughts or suggestions. Additionally, I noticed an issue with bracket_ranges when dealing with nested strings. For example: Input: Expected result after ciq: Actual result: It seems the current implementation doesn’t handle nested strings properly. Let me know if you’d like me to investigate this further or if you have any ideas on how to address it. That's all my concerns about the new implementation using Tree-Sitter |
fe6c640
to
f71f116
Compare
@@ -1972,9 +2063,36 @@ mod test { | |||
|
|||
#[gpui::test] | |||
async fn test_anyquotes_object(cx: &mut gpui::TestAppContext) { | |||
let mut cx = VimTestContext::new(cx, true).await; | |||
let mut cx = VimTestContext::new_typescript(cx).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated to use new tree sitter queries that includes simple and back quotes
@@ -3,3 +3,5 @@ | |||
("{" @open "}" @close) | |||
("<" @open ">" @close) | |||
("\"" @open "\"" @close) | |||
("'" @open "'" @close) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a better way to include any type of quotes as brackets for all languages?
Otherwise this code change should be included in all the brackets.scm file of all the languages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly not that I know of.
191ae5a
to
b2ee789
Compare
@@ -2359,13 +2102,13 @@ mod test { | |||
( | |||
"c i q", | |||
"This is a \"simple 'qˇuote'\" example.", | |||
"This is a \"simple 'ˇ'\" example.", | |||
"This is a \"ˇ\" example.", // Not supported by tree sitter queries for now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nested quotes are not supported by tree sitter queries for now, i need help to see if it's possible to support it using tree sitter queries
Mode::Insert, | ||
), | ||
( | ||
"c a q", | ||
"This is a \"simple 'qˇuote'\" example.", | ||
"This is a \"simple ˇ\" example.", // same mini.ai plugin behavior | ||
"This is a ˇ example.", // Not supported by tree sitter queries for now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nested quotes are not supported by tree sitter queries for now, i need help to see if it's possible to support it using tree sitter queries
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is supportable with tree-sitter unfortunately :/.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(well, it is; but it would require changing the grammars for each language)
b2ee789
to
0e83b78
Compare
0e83b78
to
a1af5ca
Compare
ff0df2b
to
38c0586
Compare
// To support f-strings in python | ||
if snapshot.chars_at(start_off).next() == Some('f') { | ||
start_off += 1; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the mini.ai behavior is wrong in this case, and we should stick with start_off
. Given an example like print(f"a|aa")
, daq
should give you print(|)
not print(f|)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've noticed this is consistent with the behavior in the original mini.ai implementation. The system appears to ignore any characters that precede quotation marks. I understand your point about this limitation, and I'll investigate ways to improve this behavior in the code
around: bool, | ||
) -> Option<Range<DisplayPoint>> { | ||
find_any_delimiters(map, display_point, around, |buffer, start| { | ||
matches!(buffer.chars_at(start).next(), Some('\'' | '"' | '`' | 'f')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use the last character of the query for now (so it works for f"
in python, r"
in rust, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense!
return Some(pair.start..pair.end); | ||
} else { | ||
let new_start = pair.start.column() + 1; | ||
let new_end = pair.end.column() - 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bracket queries should give you a range for the start and end delimiter; so around
becomes start_range.start..end_range.end
and !around
is start_range.end..end_range.start
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(it's also worth knowing that it's generally unsafe to + 1 on an offset into a string in rust, because string indexes must always align to utf8 bytes or string range queries panic; If you do need that in Zed, you can, but you need to clip the Point against the display map).
Thanks for this! I've left a few comments inline. I also noticed that python docstrings weren't handled correctly by
|
Overview
This PR improves the existing mini.ai‐like text-object logic for both “AnyQuotes” (quotes) and “AnyBrackets” (brackets) by adding a multi‐line fallback. The first pass searches only the current line for a best match (cover or next); if none are found, we do a multi‐line pass. This preserves mini.ai's usual “line priority” while ensuring we can detect pairs that start on one line and end on another.
What Changed
gather_line_brackets(map, caret.row())
to find bracket pairs((), [], {}, <>)
on the caret’s line.gather_brackets_multiline(map)
to single‐pass scan the entire buffer, collecting bracket pairs that might span multiple lines.pick_best_range
) to choose the best.gather_line_quotes(map, caret.row())
.gather_quotes_multiline(map)
, building a big string for the whole buffer and using naive regex for "...", '...', and...
.ciq
), we skip bounding quotes or brackets if the range is at least 2 characters wide.caq
), we return the entire range.finalize
” helpersfinalize_bracket_range
andfinalize_quote_range
handle the “inner” skip‐chars vs. “outer” logic.Why This Matters
Example Use Cases
Tests
Limitations / Future Enhancements
Important Notes
References:
Thank you for reviewing these changes!
Release Notes: