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 Nov 25, 2023
1 parent 592cbc7 commit 953b7f4
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 53 deletions.
10 changes: 7 additions & 3 deletions lapce-app/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1601,14 +1601,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
16 changes: 10 additions & 6 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 @@ -710,15 +711,15 @@ impl Syntax {
pub fn find_tag(
&self,
offset: usize,
previous: bool,
find_previous: bool,
tag: &str,
) -> Option<usize> {
let tree = self.layers.as_ref()?.try_tree()?;
let node = tree
.root_node()
.descendant_for_byte_range(offset, offset + 1)?;

if let Some(offset) = self.find_tag_in_siblings(node, previous, tag) {
if let Some(offset) = self.find_tag_in_siblings(node, find_previous, tag) {
return Some(offset);
}

Expand All @@ -728,7 +729,9 @@ impl Syntax {

let mut node = node;
while let Some(parent) = node.parent() {
if let Some(offset) = self.find_tag_in_siblings(parent, previous, tag) {
if let Some(offset) =
self.find_tag_in_siblings(parent, find_previous, tag)
{
return Some(offset);
}
node = parent;
Expand All @@ -739,17 +742,18 @@ impl Syntax {
fn find_tag_in_siblings(
&self,
node: Node,
previous: bool,
find_previous: bool,
tag: &str,
) -> Option<usize> {
let mut node = node;
while let Some(sibling) = if previous {
while let Some(sibling) = if find_previous {
node.prev_sibling()
} else {
node.next_sibling()
} {
let offset = sibling.start_byte();

if sibling.kind() == tag {
let offset = sibling.start_byte();
return Some(offset);
}
node = sibling;
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
99 changes: 56 additions & 43 deletions lapce-core/src/word.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,10 @@ impl<'a> WordCursor<'a> {
let other = matching_char(c)?;
let mut n = 0;
while let Some(current) = self.inner.next_codepoint() {
let offset = self.inner.pos();

if current == c && n == 0 {
return Some(self.inner.pos());
return Some(offset);
}
if current == other {
n += 1;
Expand Down Expand Up @@ -375,8 +377,10 @@ impl<'a> WordCursor<'a> {
let other = matching_char(c)?;
let mut n = 0;
while let Some(current) = self.inner.prev_codepoint() {
let offset = self.inner.pos();

if current == c && n == 0 {
return Some(self.inner.pos());
return Some(offset);
}
if current == other {
n += 1;
Expand Down Expand Up @@ -408,40 +412,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 +697,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 +706,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 953b7f4

Please sign in to comment.