Files
oak-gpui/crates/jj_ui/src/bookmark_picker.rs
T
Smit Barmase 131f2857a5 editor: Improve code completion filtering to provide fewer and more accurate suggestions (#32928)
Closes #32756

- Uses `filter_text` from LSP source to filter items in completion list.
This fixes noisy lists like on typing `await` in Rust, it would suggest
`await.or`, `await.and`, etc., which are bad suggestions. Fallbacks to
label.
- Add `penalize_length` flag to fuzzy matcher, which was the default
behavior across. Now, this flag is set to `false` just for code
completion fuzzy matching. This fixes the case where if the query is
`unreac` and the completion items are `unreachable` and
`unreachable!()`, the item with a shorter length would have a larger
score than the other one, which is not right in the case of
auto-complete context. Now these two items will have the same fuzzy
score, and LSP `sort_text` will take over in finalizing its ranking.
- Updated test to be more utility based rather than example based. This
will help to iterate/verify logic faster on what's going on.

Before/After:

await: 
<img width="600" alt="before-await"
src="https://github.com/user-attachments/assets/384138dd-a90d-4942-a430-6ae15df37268"
/>
<img width="600" alt="after-await"
src="https://github.com/user-attachments/assets/d05a10fa-bae5-49bd-9fe7-9933ff215f29"
/>

iter:
<img width="600" alt="before-iter"
src="https://github.com/user-attachments/assets/6e57ffe9-007d-4b17-9cc2-d48fc0176c8e"
/>
<img width="600" alt="after-iter"
src="https://github.com/user-attachments/assets/a8577a9f-dcc8-4fd6-9ba0-b7590584ec31"
/>

opt:
<img width="600" alt="opt-before"
src="https://github.com/user-attachments/assets/d45b6c52-c9ee-4bf3-8552-d5e3fdbecbff"
/>
<img width="600" alt="opt-after"
src="https://github.com/user-attachments/assets/daac11a8-9699-48f8-b441-19fe9803848d"
/>

Release Notes:

- Improved code completion filtering to provide fewer and more accurate
suggestions.
2025-06-18 16:01:28 +05:30

199 lines
5.2 KiB
Rust

use std::sync::Arc;
use fuzzy::{StringMatchCandidate, match_strings};
use gpui::{
App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity, Window,
prelude::*,
};
use jj::{Bookmark, JujutsuStore};
use picker::{Picker, PickerDelegate};
use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
use util::ResultExt as _;
use workspace::{ModalView, Workspace};
pub fn register(workspace: &mut Workspace) {
workspace.register_action(open);
}
fn open(
workspace: &mut Workspace,
_: &zed_actions::jj::BookmarkList,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let Some(jj_store) = JujutsuStore::try_global(cx) else {
return;
};
workspace.toggle_modal(window, cx, |window, cx| {
let delegate = BookmarkPickerDelegate::new(cx.entity().downgrade(), jj_store, cx);
BookmarkPicker::new(delegate, window, cx)
});
}
pub struct BookmarkPicker {
picker: Entity<Picker<BookmarkPickerDelegate>>,
}
impl BookmarkPicker {
pub fn new(
delegate: BookmarkPickerDelegate,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}
impl ModalView for BookmarkPicker {}
impl EventEmitter<DismissEvent> for BookmarkPicker {}
impl Focusable for BookmarkPicker {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for BookmarkPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
v_flex().w(rems(34.)).child(self.picker.clone())
}
}
#[derive(Debug, Clone)]
struct BookmarkEntry {
bookmark: Bookmark,
positions: Vec<usize>,
}
pub struct BookmarkPickerDelegate {
picker: WeakEntity<BookmarkPicker>,
matches: Vec<BookmarkEntry>,
all_bookmarks: Vec<Bookmark>,
selected_index: usize,
}
impl BookmarkPickerDelegate {
fn new(
picker: WeakEntity<BookmarkPicker>,
jj_store: Entity<JujutsuStore>,
cx: &mut Context<BookmarkPicker>,
) -> Self {
let bookmarks = jj_store.read(cx).repository().list_bookmarks();
Self {
picker,
matches: Vec::new(),
all_bookmarks: bookmarks,
selected_index: 0,
}
}
}
impl PickerDelegate for BookmarkPickerDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Select Bookmark…".into()
}
fn match_count(&self) -> usize {
self.matches.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
self.selected_index = ix;
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let background = cx.background_executor().clone();
let all_bookmarks = self.all_bookmarks.clone();
cx.spawn_in(window, async move |this, cx| {
let matches = if query.is_empty() {
all_bookmarks
.into_iter()
.map(|bookmark| BookmarkEntry {
bookmark,
positions: Vec::new(),
})
.collect()
} else {
let candidates = all_bookmarks
.iter()
.enumerate()
.map(|(ix, bookmark)| StringMatchCandidate::new(ix, &bookmark.ref_name))
.collect::<Vec<_>>();
match_strings(
&candidates,
&query,
false,
true,
100,
&Default::default(),
background,
)
.await
.into_iter()
.map(|mat| BookmarkEntry {
bookmark: all_bookmarks[mat.candidate_id].clone(),
positions: mat.positions,
})
.collect()
};
this.update(cx, |this, _cx| {
this.delegate.matches = matches;
})
.log_err();
})
}
fn confirm(&mut self, _secondary: bool, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {
//
}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
self.picker
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
}
fn render_match(
&self,
ix: usize,
selected: bool,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let entry = &self.matches[ix];
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(HighlightedLabel::new(
entry.bookmark.ref_name.clone(),
entry.positions.clone(),
)),
)
}
}