snippets: Fix tabstop completion choices (#31955)

I'm not sure when snippet tabstop choices broke, I checked the parent of
#31872 and they also don't work before that.

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-06-05 13:17:41 -06:00 committed by GitHub
parent ccc173ebb1
commit d15d85830a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 56 additions and 33 deletions

View file

@ -2712,7 +2712,9 @@ impl Editor {
.invalidate(&self.selections.disjoint_anchors(), buffer); .invalidate(&self.selections.disjoint_anchors(), buffer);
self.take_rename(false, window, cx); self.take_rename(false, window, cx);
let new_cursor_position = self.selections.newest_anchor().head(); let newest_selection = self.selections.newest_anchor();
let new_cursor_position = newest_selection.head();
let selection_start = newest_selection.start;
self.push_to_nav_history( self.push_to_nav_history(
*old_cursor_position, *old_cursor_position,
@ -2722,8 +2724,6 @@ impl Editor {
); );
if local { if local {
let new_cursor_position = self.selections.newest_anchor().head();
if let Some(buffer_id) = new_cursor_position.buffer_id { if let Some(buffer_id) = new_cursor_position.buffer_id {
if !self.registered_buffers.contains_key(&buffer_id) { if !self.registered_buffers.contains_key(&buffer_id) {
if let Some(project) = self.project.as_ref() { if let Some(project) = self.project.as_ref() {
@ -2754,15 +2754,15 @@ impl Editor {
if should_update_completions { if should_update_completions {
if let Some(completion_position) = completion_position { if let Some(completion_position) = completion_position {
let new_cursor_offset = new_cursor_position.to_offset(buffer); let start_offset = selection_start.to_offset(buffer);
let position_matches = let position_matches = start_offset == completion_position.to_offset(buffer);
new_cursor_offset == completion_position.to_offset(buffer);
let continue_showing = if position_matches { let continue_showing = if position_matches {
let (word_range, kind) = buffer.surrounding_word(new_cursor_offset, true); if self.snippet_stack.is_empty() {
if let Some(CharKind::Word) = kind { buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
word_range.start < new_cursor_offset
} else { } else {
false // Snippet choices can be shown even when the cursor is in whitespace.
// Dismissing the menu when actions like backspace
true
} }
} else { } else {
false false
@ -5046,7 +5046,10 @@ impl Editor {
return; return;
} }
let position = self.selections.newest_anchor().head(); // Typically `start` == `end`, but with snippet tabstop choices the default choice is
// inserted and selected. To handle that case, the start of the selection is used so that
// the menu starts with all choices.
let position = self.selections.newest_anchor().start;
if position.diff_base_anchor.is_some() { if position.diff_base_anchor.is_some() {
return; return;
} }
@ -8914,26 +8917,30 @@ impl Editor {
selection: Range<Anchor>, selection: Range<Anchor>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
if selection.start.buffer_id.is_none() { let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
(Some(a), Some(b)) if a == b => a,
_ => {
log::error!("expected anchor range to have matching buffer IDs");
return;
}
};
let multi_buffer = self.buffer().read(cx);
let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
return; return;
} };
let buffer_id = selection.start.buffer_id.unwrap();
let buffer = self.buffer().read(cx).buffer(buffer_id);
let id = post_inc(&mut self.next_completion_id); let id = post_inc(&mut self.next_completion_id);
let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order; let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
*self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
if let Some(buffer) = buffer { CompletionsMenu::new_snippet_choices(
*self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions( id,
CompletionsMenu::new_snippet_choices( true,
id, choices,
true, selection,
choices, buffer,
selection, snippet_sort_order,
buffer, ),
snippet_sort_order, ));
),
));
}
} }
pub fn insert_snippet( pub fn insert_snippet(
@ -8987,9 +8994,7 @@ impl Editor {
}) })
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Sort in reverse order so that the first range is the newest created tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
// selection. Completions will use it and autoscroll will prioritize it.
tabstop_ranges.sort_unstable_by(|a, b| b.start.cmp(&a.start, snapshot));
Tabstop { Tabstop {
is_end_tabstop, is_end_tabstop,
@ -9001,7 +9006,9 @@ impl Editor {
}); });
if let Some(tabstop) = tabstops.first() { if let Some(tabstop) = tabstops.first() {
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select_ranges(tabstop.ranges.iter().cloned()); // Reverse order so that the first range is the newest created selection.
// Completions will use it and autoscroll will prioritize it.
s.select_ranges(tabstop.ranges.iter().rev().cloned());
}); });
if let Some(choices) = &tabstop.choices { if let Some(choices) = &tabstop.choices {
@ -9117,7 +9124,9 @@ impl Editor {
} }
if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) { if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select_ranges(current_ranges.iter().cloned()) // Reverse order so that the first range is the newest created selection.
// Completions will use it and autoscroll will prioritize it.
s.select_ranges(current_ranges.iter().rev().cloned())
}); });
if let Some(choices) = &snippet.choices[snippet.active_index] { if let Some(choices) = &snippet.choices[snippet.active_index] {

View file

@ -4182,6 +4182,20 @@ impl MultiBufferSnapshot {
(start..end, word_kind) (start..end, word_kind)
} }
pub fn char_kind_before<T: ToOffset>(
&self,
start: T,
for_completion: bool,
) -> Option<CharKind> {
let start = start.to_offset(self);
let classifier = self
.char_classifier_at(start)
.for_completion(for_completion);
self.reversed_chars_at(start)
.next()
.map(|ch| classifier.kind(ch))
}
pub fn is_singleton(&self) -> bool { pub fn is_singleton(&self) -> bool {
self.singleton self.singleton
} }