Skip to content

Commit

Permalink
Improve the performance of bracket search in no language mode
Browse files Browse the repository at this point in the history
Basically turns this part of the WordCursor stateless.
I think the other parts can also benefit from this sort of optimization.

Signed-off-by: Hanif Bin Ariffin <[email protected]>
  • Loading branch information
hbina committed Dec 3, 2023
1 parent ede1159 commit 28cc1f6
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 45 deletions.
10 changes: 7 additions & 3 deletions lapce-app/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1596,14 +1596,18 @@ impl Document {
pub fn find_enclosing_brackets(&self, offset: usize) -> Option<(usize, usize)> {
self.syntax
.with_untracked(|syntax| {
(!syntax.text.is_empty()).then(|| syntax.find_enclosing_pair(offset))
if !syntax.text.is_empty() {
syntax.find_enclosing_pair(offset)
} else {
None
}
})
// If syntax.text is empty, either the buffer is empty or we don't have syntax support
// for the current language.
// Try a language unaware search for enclosing brackets in case it is the latter.
.unwrap_or_else(|| {
.or_else(|| {
self.buffer.with_untracked(|buffer| {
WordCursor::new(buffer.text(), offset).find_enclosing_pair()
WordCursor::find_enclosing_pair(buffer.text(), offset)
})
})
}
Expand Down
2 changes: 2 additions & 0 deletions lapce-core/src/syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ impl Syntax {
builder.build()
}

/// Returns the matching bracket of the character at the given `offset`.
pub fn find_matching_pair(&self, offset: usize) -> Option<usize> {
let tree = self.layers.as_ref()?.try_tree()?;
let node = tree
Expand Down Expand Up @@ -748,6 +749,7 @@ impl Syntax {
} else {
node.next_sibling()
} {

if sibling.kind() == tag {
let offset = sibling.start_byte();
return Some(offset);
Expand Down
4 changes: 3 additions & 1 deletion lapce-core/src/syntax/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ impl<'a> TextProvider<'a> for RopeProvider<'a> {
}
}

/// If the character is an opening bracket return Some(true), if closing, return Some(false)
/// If the character `c` is an opening bracket return `Some(true)`.
/// If the character `c` is an closing bracket return `Some(false)`.
/// Otherwise return `None`.
pub fn matching_pair_direction(c: char) -> Option<bool> {
Some(match c {
'{' => true,
Expand Down
91 changes: 50 additions & 41 deletions lapce-core/src/word.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,40 +408,54 @@ impl<'a> WordCursor<'a> {
(start, end)
}

/// Return the enclosing brackets of the current position
///
/// **Example**:
///
///```rust
/// # use lapce_core::word::WordCursor;
/// # use lapce_xi_rope::Rope;
/// let text = "outer {{inner} world";
/// let rope = Rope::from(text);
/// let mut cursor = WordCursor::new(&rope, 10);
/// let (start, end) = cursor.find_enclosing_pair().unwrap();
/// assert_eq!(start, 7);
/// assert_eq!(end, 13)
///```
pub fn find_enclosing_pair(&mut self) -> Option<(usize, usize)> {
let old_offset = self.inner.pos();
while let Some(c) = self.inner.prev_codepoint() {
if matching_pair_direction(c) == Some(true) {
let opening_bracket_offset = self.inner.pos();
if let Some(closing_bracket_offset) = self.match_pairs() {
if (opening_bracket_offset..=closing_bracket_offset)
.contains(&old_offset)
{
return Some((
opening_bracket_offset,
closing_bracket_offset,
));
} else {
self.inner.set(opening_bracket_offset);
/// Return the enclosing brackets of the current position.
pub fn find_enclosing_pair<'b>(
text: &'a Rope,
offset: usize,
) -> Option<(usize, usize)> {
let mut left_cursor = Cursor::new(text, offset);
let mut right_cursor = Cursor::new(text, offset);
let mut right_cache: Vec<(usize, char)> = Vec::new();

// `backward_cursor` only goes backward while `forward_cursor` moves while storing the list of closing brackets.
loop {
let left_char = left_cursor.prev_codepoint()?;
let left_pos = left_cursor.pos();
let left_matching_char = matching_char(left_char);
let is_open_bracket = matching_pair_direction(left_char) == Some(true);

match (left_matching_char, is_open_bracket) {
(Some(left_matching_char), true) => {
// Find in the right cache first
let right_cache_exists =
right_cache.iter().find_map(|(idx, u)| {
if *u == left_matching_char {
return Some(*idx);
}
None
});

if let Some(idx) = right_cache_exists {
return Some((left_pos, idx));
}

// Otherwise walk the right cursor forward
loop {
let right_pos = right_cursor.pos();
let right_char = right_cursor.next_codepoint()?;
let is_closing_bracket =
matching_pair_direction(right_char) == Some(false);

if is_closing_bracket && left_matching_char == right_char {
return Some((left_pos, right_pos));
}

right_cache.push((left_cursor.pos(), right_char));
}
}
}
_ => continue,
};
}
None
}
}

Expand Down Expand Up @@ -679,8 +693,7 @@ mod test {
fn find_pair_should_return_positions() {
let text = "violet (are) blue";
let rope = Rope::from(text);
let mut cursor = WordCursor::new(&rope, 9);
let positions = cursor.find_enclosing_pair();
let positions = WordCursor::find_enclosing_pair(&rope, 9);
assert_eq!(positions, Some((7, 11)));
}

Expand All @@ -689,25 +702,21 @@ mod test {
let text = "violets {are (blue) }";
let rope = Rope::from(text);

let mut cursor = WordCursor::new(&rope, 11);
let positions = cursor.find_enclosing_pair();
let positions = WordCursor::find_enclosing_pair(&rope, 11);
assert_eq!(positions, Some((8, 23)));

let mut cursor = WordCursor::new(&rope, 20);
let positions = cursor.find_enclosing_pair();
let positions = WordCursor::find_enclosing_pair(&rope, 20);
assert_eq!(positions, Some((8, 23)));

let mut cursor = WordCursor::new(&rope, 18);
let positions = cursor.find_enclosing_pair();
let positions = WordCursor::find_enclosing_pair(&rope, 18);
assert_eq!(positions, Some((13, 18)));
}

#[test]
fn find_pair_should_return_none() {
let text = "violet (are) blue";
let rope = Rope::from(text);
let mut cursor = WordCursor::new(&rope, 1);
let positions = cursor.find_enclosing_pair();
let positions = WordCursor::find_enclosing_pair(&rope, 1);
assert_eq!(positions, None);
}
}

0 comments on commit 28cc1f6

Please sign in to comment.