Add support for auto-closing of JSX tags (#25681)

Closes #4271

Implemented by kicking of a task on the main thread at the end of
`Editor::handle_input` which waits for the buffer to be re-parsed before
checking if JSX tag completion possible based on the recent edits, and
if it is then it spawns a task on the background thread to generate the
edits to be auto-applied to the buffer

Release Notes:

- Added support for auto-closing of JSX tags

---------

Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Max Brunsfeld <max@zed.dev>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Peter Tripp <peter@zed.dev>
This commit is contained in:
Ben Kunkle 2025-03-06 08:36:10 -06:00 committed by GitHub
parent 05df3d1bd6
commit ff25fa24e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1207 additions and 149 deletions

View file

@ -591,6 +591,7 @@ impl<'a> Cursor<'a> {
}
}
#[derive(Clone)]
pub struct Chunks<'a> {
chunks: sum_tree::Cursor<'a, Chunk, usize>,
range: Range<usize>,
@ -780,6 +781,40 @@ impl<'a> Chunks<'a> {
reversed,
}
}
pub fn equals_str(&self, other: &str) -> bool {
let chunk = self.clone();
if chunk.reversed {
let mut offset = other.len();
for chunk in chunk {
if other[0..offset].ends_with(chunk) {
offset -= chunk.len();
} else {
return false;
}
}
if offset != 0 {
return false;
}
} else {
let mut offset = 0;
for chunk in chunk {
if offset >= other.len() {
return false;
}
if other[offset..].starts_with(chunk) {
offset += chunk.len();
} else {
return false;
}
}
if offset != other.len() {
return false;
}
}
return true;
}
}
impl<'a> Iterator for Chunks<'a> {
@ -1855,6 +1890,53 @@ mod tests {
}
}
#[test]
fn test_chunks_equals_str() {
let text = "This is a multi-chunk\n& multi-line test string!";
let rope = Rope::from(text);
for start in 0..text.len() {
for end in start..text.len() {
let range = start..end;
let correct_substring = &text[start..end];
// Test that correct range returns true
assert!(rope
.chunks_in_range(range.clone())
.equals_str(correct_substring));
assert!(rope
.reversed_chunks_in_range(range.clone())
.equals_str(correct_substring));
// Test that all other ranges return false (unless they happen to match)
for other_start in 0..text.len() {
for other_end in other_start..text.len() {
if other_start == start && other_end == end {
continue;
}
let other_substring = &text[other_start..other_end];
// Only assert false if the substrings are actually different
if other_substring == correct_substring {
continue;
}
assert!(!rope
.chunks_in_range(range.clone())
.equals_str(other_substring));
assert!(!rope
.reversed_chunks_in_range(range.clone())
.equals_str(other_substring));
}
}
}
}
let rope = Rope::from("");
assert!(rope.chunks_in_range(0..0).equals_str(""));
assert!(rope.reversed_chunks_in_range(0..0).equals_str(""));
assert!(!rope.chunks_in_range(0..0).equals_str("foo"));
assert!(!rope.reversed_chunks_in_range(0..0).equals_str("foo"));
}
fn clip_offset(text: &str, mut offset: usize, bias: Bias) -> usize {
while !text.is_char_boundary(offset) {
match bias {