Implement grapheme support for supermaven completions (#18279)

Closes [#18278](https://github.com/zed-industries/zed/issues/18278)

Release Notes:

- Fixed a panic when graphemes are included in supermaven completions
This commit is contained in:
Sebastijan Kelnerič 2024-09-24 16:49:07 +02:00 committed by GitHub
parent 437bcc0ce6
commit e87d6da2a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 14 additions and 10 deletions

1
Cargo.lock generated
View file

@ -11006,6 +11006,7 @@ dependencies = [
"text", "text",
"theme", "theme",
"ui", "ui",
"unicode-segmentation",
"util", "util",
"windows 0.58.0", "windows 0.58.0",
] ]

View file

@ -29,6 +29,7 @@ supermaven_api.workspace = true
smol.workspace = true smol.workspace = true
text.workspace = true text.workspace = true
ui.workspace = true ui.workspace = true
unicode-segmentation.workspace = true
util.workspace = true util.workspace = true
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]

View file

@ -12,6 +12,7 @@ use std::{
time::Duration, time::Duration,
}; };
use text::{ToOffset, ToPoint}; use text::{ToOffset, ToPoint};
use unicode_segmentation::UnicodeSegmentation;
pub const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75); pub const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
@ -54,33 +55,34 @@ fn completion_state_from_diff(
) -> CompletionProposal { ) -> CompletionProposal {
let buffer_text = snapshot let buffer_text = snapshot
.text_for_range(delete_range.clone()) .text_for_range(delete_range.clone())
.collect::<String>() .collect::<String>();
.chars()
.collect::<Vec<char>>();
let mut inlays: Vec<InlayProposal> = Vec::new(); let mut inlays: Vec<InlayProposal> = Vec::new();
let completion = completion_text.chars().collect::<Vec<char>>(); let completion_graphemes: Vec<&str> = completion_text.graphemes(true).collect();
let buffer_graphemes: Vec<&str> = buffer_text.graphemes(true).collect();
let mut offset = position.to_offset(&snapshot); let mut offset = position.to_offset(&snapshot);
let mut i = 0; let mut i = 0;
let mut j = 0; let mut j = 0;
while i < completion.len() && j < buffer_text.len() { while i < completion_graphemes.len() && j < buffer_graphemes.len() {
// find the next instance of the buffer text in the completion text. // find the next instance of the buffer text in the completion text.
let k = completion[i..].iter().position(|c| *c == buffer_text[j]); let k = completion_graphemes[i..]
.iter()
.position(|c| *c == buffer_graphemes[j]);
match k { match k {
Some(k) => { Some(k) => {
if k != 0 { if k != 0 {
// the range from the current position to item is an inlay. // the range from the current position to item is an inlay.
inlays.push(InlayProposal::Suggestion( inlays.push(InlayProposal::Suggestion(
snapshot.anchor_after(offset), snapshot.anchor_after(offset),
completion_text[i..i + k].into(), completion_graphemes[i..i + k].join("").into(),
)); ));
} }
i += k + 1; i += k + 1;
j += 1; j += 1;
offset.add_assign(1); offset.add_assign(buffer_graphemes[j - 1].len());
} }
None => { None => {
// there are no more matching completions, so drop the remaining // there are no more matching completions, so drop the remaining
@ -90,11 +92,11 @@ fn completion_state_from_diff(
} }
} }
if j == buffer_text.len() && i < completion.len() { if j == buffer_graphemes.len() && i < completion_graphemes.len() {
// there is leftover completion text, so drop it as an inlay. // there is leftover completion text, so drop it as an inlay.
inlays.push(InlayProposal::Suggestion( inlays.push(InlayProposal::Suggestion(
snapshot.anchor_after(offset), snapshot.anchor_after(offset),
completion_text[i..completion_text.len()].into(), completion_graphemes[i..].join("").into(),
)); ));
} }