@@ -33,21 +33,21 @@ pub enum AssistantEditKind {
Update {
old_text: String,
new_text: String,
- description: String,
+ description: Option<String>,
},
Create {
new_text: String,
- description: String,
+ description: Option<String>,
},
InsertBefore {
old_text: String,
new_text: String,
- description: String,
+ description: Option<String>,
},
InsertAfter {
old_text: String,
new_text: String,
- description: String,
+ description: Option<String>,
},
Delete {
old_text: String,
@@ -86,19 +86,37 @@ enum SearchDirection {
Diagonal,
}
-// A measure of the currently quality of an in-progress fuzzy search.
-//
-// Uses 60 bits to store a numeric cost, and 4 bits to store the preceding
-// operation in the search.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct SearchState {
- score: u32,
+ cost: u32,
direction: SearchDirection,
}
impl SearchState {
- fn new(score: u32, direction: SearchDirection) -> Self {
- Self { score, direction }
+ fn new(cost: u32, direction: SearchDirection) -> Self {
+ Self { cost, direction }
+ }
+}
+
+struct SearchMatrix {
+ cols: usize,
+ data: Vec<SearchState>,
+}
+
+impl SearchMatrix {
+ fn new(rows: usize, cols: usize) -> Self {
+ SearchMatrix {
+ cols,
+ data: vec![SearchState::new(0, SearchDirection::Diagonal); rows * cols],
+ }
+ }
+
+ fn get(&self, row: usize, col: usize) -> SearchState {
+ self.data[row * self.cols + col]
+ }
+
+ fn set(&mut self, row: usize, col: usize, cost: SearchState) {
+ self.data[row * self.cols + col] = cost;
}
}
@@ -187,23 +205,23 @@ impl AssistantEdit {
"update" => AssistantEditKind::Update {
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
- description: description.ok_or_else(|| anyhow!("missing description"))?,
+ description,
},
"insert_before" => AssistantEditKind::InsertBefore {
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
- description: description.ok_or_else(|| anyhow!("missing description"))?,
+ description,
},
"insert_after" => AssistantEditKind::InsertAfter {
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
- description: description.ok_or_else(|| anyhow!("missing description"))?,
+ description,
},
"delete" => AssistantEditKind::Delete {
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
},
"create" => AssistantEditKind::Create {
- description: description.ok_or_else(|| anyhow!("missing description"))?,
+ description,
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
},
_ => Err(anyhow!("unknown operation {operation:?}"))?,
@@ -264,7 +282,7 @@ impl AssistantEditKind {
ResolvedEdit {
range,
new_text,
- description: Some(description),
+ description,
}
}
Self::Create {
@@ -272,7 +290,7 @@ impl AssistantEditKind {
description,
} => ResolvedEdit {
range: text::Anchor::MIN..text::Anchor::MAX,
- description: Some(description),
+ description,
new_text,
},
Self::InsertBefore {
@@ -285,7 +303,7 @@ impl AssistantEditKind {
ResolvedEdit {
range: range.start..range.start,
new_text,
- description: Some(description),
+ description,
}
}
Self::InsertAfter {
@@ -298,7 +316,7 @@ impl AssistantEditKind {
ResolvedEdit {
range: range.end..range.end,
new_text,
- description: Some(description),
+ description,
}
}
Self::Delete { old_text } => {
@@ -314,44 +332,29 @@ impl AssistantEditKind {
fn resolve_location(buffer: &text::BufferSnapshot, search_query: &str) -> Range<text::Anchor> {
const INSERTION_COST: u32 = 3;
+ const DELETION_COST: u32 = 10;
const WHITESPACE_INSERTION_COST: u32 = 1;
- const DELETION_COST: u32 = 3;
const WHITESPACE_DELETION_COST: u32 = 1;
- const EQUALITY_BONUS: u32 = 5;
-
- struct Matrix {
- cols: usize,
- data: Vec<SearchState>,
- }
-
- impl Matrix {
- fn new(rows: usize, cols: usize) -> Self {
- Matrix {
- cols,
- data: vec![SearchState::new(0, SearchDirection::Diagonal); rows * cols],
- }
- }
-
- fn get(&self, row: usize, col: usize) -> SearchState {
- self.data[row * self.cols + col]
- }
-
- fn set(&mut self, row: usize, col: usize, cost: SearchState) {
- self.data[row * self.cols + col] = cost;
- }
- }
let buffer_len = buffer.len();
let query_len = search_query.len();
- let mut matrix = Matrix::new(query_len + 1, buffer_len + 1);
-
+ let mut matrix = SearchMatrix::new(query_len + 1, buffer_len + 1);
+ let mut leading_deletion_cost = 0_u32;
for (row, query_byte) in search_query.bytes().enumerate() {
+ let deletion_cost = if query_byte.is_ascii_whitespace() {
+ WHITESPACE_DELETION_COST
+ } else {
+ DELETION_COST
+ };
+
+ leading_deletion_cost = leading_deletion_cost.saturating_add(deletion_cost);
+ matrix.set(
+ row + 1,
+ 0,
+ SearchState::new(leading_deletion_cost, SearchDirection::Diagonal),
+ );
+
for (col, buffer_byte) in buffer.bytes_in_range(0..buffer.len()).flatten().enumerate() {
- let deletion_cost = if query_byte.is_ascii_whitespace() {
- WHITESPACE_DELETION_COST
- } else {
- DELETION_COST
- };
let insertion_cost = if buffer_byte.is_ascii_whitespace() {
WHITESPACE_INSERTION_COST
} else {
@@ -359,38 +362,35 @@ impl AssistantEditKind {
};
let up = SearchState::new(
- matrix.get(row, col + 1).score.saturating_sub(deletion_cost),
+ matrix.get(row, col + 1).cost.saturating_add(deletion_cost),
SearchDirection::Up,
);
let left = SearchState::new(
- matrix
- .get(row + 1, col)
- .score
- .saturating_sub(insertion_cost),
+ matrix.get(row + 1, col).cost.saturating_add(insertion_cost),
SearchDirection::Left,
);
let diagonal = SearchState::new(
if query_byte == *buffer_byte {
- matrix.get(row, col).score.saturating_add(EQUALITY_BONUS)
+ matrix.get(row, col).cost
} else {
matrix
.get(row, col)
- .score
- .saturating_sub(deletion_cost + insertion_cost)
+ .cost
+ .saturating_add(deletion_cost + insertion_cost)
},
SearchDirection::Diagonal,
);
- matrix.set(row + 1, col + 1, up.max(left).max(diagonal));
+ matrix.set(row + 1, col + 1, up.min(left).min(diagonal));
}
}
// Traceback to find the best match
let mut best_buffer_end = buffer_len;
- let mut best_score = 0;
+ let mut best_cost = u32::MAX;
for col in 1..=buffer_len {
- let score = matrix.get(query_len, col).score;
- if score > best_score {
- best_score = score;
+ let cost = matrix.get(query_len, col).cost;
+ if cost < best_cost {
+ best_cost = cost;
best_buffer_end = col;
}
}
@@ -560,89 +560,84 @@ mod tests {
language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher,
};
use settings::SettingsStore;
- use text::{OffsetRangeExt, Point};
use ui::BorrowAppContext;
use unindent::Unindent as _;
+ use util::test::{generate_marked_text, marked_text_ranges};
#[gpui::test]
fn test_resolve_location(cx: &mut AppContext) {
- {
- let buffer = cx.new_model(|cx| {
- Buffer::local(
- concat!(
- " Lorem\n",
- " ipsum\n",
- " dolor sit amet\n",
- " consecteur",
- ),
- cx,
- )
- });
- let snapshot = buffer.read(cx).snapshot();
- assert_eq!(
- AssistantEditKind::resolve_location(&snapshot, "ipsum\ndolor").to_point(&snapshot),
- Point::new(1, 0)..Point::new(2, 18)
- );
- }
+ assert_location_resolution(
+ concat!(
+ " Lorem\n",
+ "« ipsum\n",
+ " dolor sit amet»\n",
+ " consecteur",
+ ),
+ "ipsum\ndolor",
+ cx,
+ );
- {
- let buffer = cx.new_model(|cx| {
- Buffer::local(
- concat!(
- "fn foo1(a: usize) -> usize {\n",
- " 40\n",
- "}\n",
- "\n",
- "fn foo2(b: usize) -> usize {\n",
- " 42\n",
- "}\n",
- ),
- cx,
- )
- });
- let snapshot = buffer.read(cx).snapshot();
- assert_eq!(
- AssistantEditKind::resolve_location(&snapshot, "fn foo1(b: usize) {\n40\n}")
- .to_point(&snapshot),
- Point::new(0, 0)..Point::new(2, 1)
- );
- }
+ assert_location_resolution(
+ &"
+ «fn foo1(a: usize) -> usize {
+ 40
+ }»
- {
- let buffer = cx.new_model(|cx| {
- Buffer::local(
- concat!(
- "fn main() {\n",
- " Foo\n",
- " .bar()\n",
- " .baz()\n",
- " .qux()\n",
- "}\n",
- "\n",
- "fn foo2(b: usize) -> usize {\n",
- " 42\n",
- "}\n",
- ),
- cx,
- )
- });
- let snapshot = buffer.read(cx).snapshot();
- assert_eq!(
- AssistantEditKind::resolve_location(&snapshot, "Foo.bar.baz.qux()")
- .to_point(&snapshot),
- Point::new(1, 0)..Point::new(4, 14)
- );
- }
+ fn foo2(b: usize) -> usize {
+ 42
+ }
+ "
+ .unindent(),
+ "fn foo1(b: usize) {\n40\n}",
+ cx,
+ );
+
+ assert_location_resolution(
+ &"
+ fn main() {
+ « Foo
+ .bar()
+ .baz()
+ .qux()»
+ }
+
+ fn foo2(b: usize) -> usize {
+ 42
+ }
+ "
+ .unindent(),
+ "Foo.bar.baz.qux()",
+ cx,
+ );
+
+ assert_location_resolution(
+ &"
+ class Something {
+ one() { return 1; }
+ « two() { return 2222; }
+ three() { return 333; }
+ four() { return 4444; }
+ five() { return 5555; }
+ six() { return 6666; }
+ » seven() { return 7; }
+ eight() { return 8; }
+ }
+ "
+ .unindent(),
+ &"
+ two() { return 2222; }
+ four() { return 4444; }
+ five() { return 5555; }
+ six() { return 6666; }
+ "
+ .unindent(),
+ cx,
+ );
}
#[gpui::test]
fn test_resolve_edits(cx: &mut AppContext) {
- let settings_store = SettingsStore::test(cx);
- cx.set_global(settings_store);
- language::init(cx);
- cx.update_global::<SettingsStore, _>(|settings, cx| {
- settings.update_user_settings::<AllLanguageSettings>(cx, |_| {});
- });
+ init_test(cx);
assert_edits(
"
@@ -675,7 +670,7 @@ mod tests {
last_name: String,
"
.unindent(),
- description: "".into(),
+ description: None,
},
AssistantEditKind::Update {
old_text: "
@@ -690,7 +685,7 @@ mod tests {
}
"
.unindent(),
- description: "".into(),
+ description: None,
},
],
"
@@ -734,7 +729,7 @@ mod tests {
qux();
}"
.unindent(),
- description: "implement bar".into(),
+ description: Some("implement bar".into()),
},
AssistantEditKind::Update {
old_text: "
@@ -747,7 +742,7 @@ mod tests {
bar();
}"
.unindent(),
- description: "call bar in foo".into(),
+ description: Some("call bar in foo".into()),
},
AssistantEditKind::InsertAfter {
old_text: "
@@ -762,7 +757,7 @@ mod tests {
}
"
.unindent(),
- description: "implement qux".into(),
+ description: Some("implement qux".into()),
},
],
"
@@ -814,7 +809,7 @@ mod tests {
}
"
.unindent(),
- description: "pick better number".into(),
+ description: None,
},
AssistantEditKind::Update {
old_text: "
@@ -829,7 +824,7 @@ mod tests {
}
"
.unindent(),
- description: "pick better number".into(),
+ description: None,
},
AssistantEditKind::Update {
old_text: "
@@ -844,7 +839,7 @@ mod tests {
}
"
.unindent(),
- description: "pick better number".into(),
+ description: None,
},
],
"
@@ -865,6 +860,69 @@ mod tests {
.unindent(),
cx,
);
+
+ assert_edits(
+ "
+ impl Person {
+ fn set_name(&mut self, name: String) {
+ self.name = name;
+ }
+
+ fn name(&self) -> String {
+ return self.name;
+ }
+ }
+ "
+ .unindent(),
+ vec![
+ AssistantEditKind::Update {
+ old_text: "self.name = name;".unindent(),
+ new_text: "self._name = name;".unindent(),
+ description: None,
+ },
+ AssistantEditKind::Update {
+ old_text: "return self.name;\n".unindent(),
+ new_text: "return self._name;\n".unindent(),
+ description: None,
+ },
+ ],
+ "
+ impl Person {
+ fn set_name(&mut self, name: String) {
+ self._name = name;
+ }
+
+ fn name(&self) -> String {
+ return self._name;
+ }
+ }
+ "
+ .unindent(),
+ cx,
+ );
+ }
+
+ fn init_test(cx: &mut AppContext) {
+ let settings_store = SettingsStore::test(cx);
+ cx.set_global(settings_store);
+ language::init(cx);
+ cx.update_global::<SettingsStore, _>(|settings, cx| {
+ settings.update_user_settings::<AllLanguageSettings>(cx, |_| {});
+ });
+ }
+
+ #[track_caller]
+ fn assert_location_resolution(
+ text_with_expected_range: &str,
+ query: &str,
+ cx: &mut AppContext,
+ ) {
+ let (text, _) = marked_text_ranges(text_with_expected_range, false);
+ let buffer = cx.new_model(|cx| Buffer::local(text.clone(), cx));
+ let snapshot = buffer.read(cx).snapshot();
+ let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
+ let text_with_actual_range = generate_marked_text(&text, &[range], false);
+ pretty_assertions::assert_eq!(text_with_actual_range, text_with_expected_range);
}
#[track_caller]