mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-06 02:37:21 +00:00
Fix common_prefix_at panic when needle contains multibyte chars
Also, make the prefix matching case-insensitive, since this is the typical behavior with autocomplete.
This commit is contained in:
parent
6f28033efe
commit
5090e6f146
2 changed files with 57 additions and 27 deletions
|
@ -7,7 +7,6 @@ use std::{
|
||||||
iter::Iterator,
|
iter::Iterator,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use util::test::marked_text_ranges;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
|
@ -167,14 +166,51 @@ fn test_line_len() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_common_prefix_at_positionn() {
|
fn test_common_prefix_at_positionn() {
|
||||||
let (text, ranges) = marked_text_ranges("a = [bcd]");
|
let text = "a = str; b = δα";
|
||||||
let buffer = Buffer::new(0, 0, History::new(text.into()));
|
let buffer = Buffer::new(0, 0, History::new(text.into()));
|
||||||
let snapshot = &buffer.snapshot();
|
|
||||||
let expected_range = ranges[0].to_offset(&snapshot);
|
let offset1 = offset_after(text, "str");
|
||||||
|
let offset2 = offset_after(text, "δα");
|
||||||
|
|
||||||
|
// the preceding word is a prefix of the suggestion
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer.common_prefix_at(expected_range.end, "bcdef"),
|
buffer.common_prefix_at(offset1, "string"),
|
||||||
expected_range
|
range_of(text, "str"),
|
||||||
)
|
);
|
||||||
|
// a suffix of the preceding word is a prefix of the suggestion
|
||||||
|
assert_eq!(
|
||||||
|
buffer.common_prefix_at(offset1, "tree"),
|
||||||
|
range_of(text, "tr"),
|
||||||
|
);
|
||||||
|
// the preceding word is a substring of the suggestion, but not a prefix
|
||||||
|
assert_eq!(
|
||||||
|
buffer.common_prefix_at(offset1, "astro"),
|
||||||
|
empty_range_after(text, "str"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// prefix matching is case insenstive.
|
||||||
|
assert_eq!(
|
||||||
|
buffer.common_prefix_at(offset1, "Strαngε"),
|
||||||
|
range_of(text, "str"),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
buffer.common_prefix_at(offset2, "ΔΑΜΝ"),
|
||||||
|
range_of(text, "δα"),
|
||||||
|
);
|
||||||
|
|
||||||
|
fn offset_after(text: &str, part: &str) -> usize {
|
||||||
|
text.find(part).unwrap() + part.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_range_after(text: &str, part: &str) -> Range<usize> {
|
||||||
|
let offset = offset_after(text, part);
|
||||||
|
offset..offset
|
||||||
|
}
|
||||||
|
|
||||||
|
fn range_of(text: &str, part: &str) -> Range<usize> {
|
||||||
|
let start = text.find(part).unwrap();
|
||||||
|
start..start + part.len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1510,31 +1510,25 @@ impl BufferSnapshot {
|
||||||
|
|
||||||
pub fn common_prefix_at<T>(&self, position: T, needle: &str) -> Range<T>
|
pub fn common_prefix_at<T>(&self, position: T, needle: &str) -> Range<T>
|
||||||
where
|
where
|
||||||
T: Clone + ToOffset + FromAnchor,
|
T: ToOffset + TextDimension,
|
||||||
{
|
{
|
||||||
let position_offset = position.to_offset(self);
|
let offset = position.to_offset(self);
|
||||||
// Get byte indices and char counts for every character in needle in reverse order
|
let common_prefix_len = needle
|
||||||
let char_indices = needle
|
|
||||||
.char_indices()
|
.char_indices()
|
||||||
.map(|(index, _)| index)
|
.map(|(index, _)| index)
|
||||||
.chain(std::iter::once(needle.len()))
|
.chain([needle.len()])
|
||||||
.enumerate()
|
.take_while(|&len| len <= offset)
|
||||||
// Don't test any prefixes that are bigger than the requested position
|
.filter(|&len| {
|
||||||
.take_while(|(_, prefix_length)| *prefix_length <= position_offset);
|
let left = self
|
||||||
|
.chars_for_range(offset - len..offset)
|
||||||
let start = char_indices
|
.flat_map(|c| char::to_lowercase(c));
|
||||||
// Compute the prefix string and prefix start location
|
let right = needle[..len].chars().flat_map(|c| char::to_lowercase(c));
|
||||||
.map(move |(byte_position, char_length)| {
|
left.eq(right)
|
||||||
(position_offset - char_length, &needle[..byte_position])
|
|
||||||
})
|
})
|
||||||
// Only take strings when the prefix is contained at the expected prefix position
|
|
||||||
.filter(|(prefix_offset, prefix)| self.contains_str_at(prefix_offset, prefix))
|
|
||||||
// Convert offset to T
|
|
||||||
.map(|(prefix_offset, _)| T::from_anchor(&self.anchor_before(prefix_offset), self))
|
|
||||||
.last()
|
.last()
|
||||||
// If no prefix matches, return the passed in position to create an empty range
|
.unwrap_or(0);
|
||||||
.unwrap_or(position.clone());
|
let start_offset = offset - common_prefix_len;
|
||||||
|
let start = self.text_summary_for_range(0..start_offset);
|
||||||
start..position
|
start..position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue