@@ -288,7 +288,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
let custom_highlights = string_match
.positions
.iter()
- .map(|pos| (*pos..pos + 1, highlight_style));
+ .map(|pos| (*pos..label.ceil_char_boundary(pos + 1), highlight_style));
let highlights = gpui::combine_highlights(custom_highlights, syntax_runs);
@@ -299,9 +299,12 @@ impl PickerDelegate for ProjectSymbolsDelegate {
.toggle_state(selected)
.child(
v_flex()
- .child(LabelLike::new().child(
- StyledText::new(label).with_default_highlights(&text_style, highlights),
- ))
+ .child(
+ LabelLike::new().child(
+ StyledText::new(&label)
+ .with_default_highlights(&text_style, highlights),
+ ),
+ )
.child(
h_flex()
.child(Label::new(path).size(LabelSize::Small).color(Color::Muted))
@@ -483,6 +486,106 @@ mod tests {
});
}
+ #[gpui::test]
+ async fn test_project_symbols_renders_utf8_match(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(path!("/dir"), json!({ "test.rs": "" }))
+ .await;
+
+ let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
+
+ let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+ language_registry.add(Arc::new(Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["rs".to_string()],
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ None,
+ )));
+ let mut fake_servers = language_registry.register_fake_lsp(
+ "Rust",
+ FakeLspAdapter {
+ capabilities: lsp::ServerCapabilities {
+ workspace_symbol_provider: Some(OneOf::Left(true)),
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ );
+
+ let _buffer = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer_with_lsp(path!("/dir/test.rs"), cx)
+ })
+ .await
+ .unwrap();
+
+ let fake_symbols = [symbol("안녕", path!("/dir/test.rs"))];
+ let fake_server = fake_servers.next().await.unwrap();
+ fake_server.set_request_handler::<lsp::WorkspaceSymbolRequest, _, _>(
+ move |params: lsp::WorkspaceSymbolParams, cx| {
+ let executor = cx.background_executor().clone();
+ let fake_symbols = fake_symbols.clone();
+ async move {
+ let candidates = fake_symbols
+ .iter()
+ .enumerate()
+ .map(|(id, symbol)| StringMatchCandidate::new(id, &symbol.name))
+ .collect::<Vec<_>>();
+ let matches = fuzzy::match_strings(
+ &candidates,
+ ¶ms.query,
+ true,
+ true,
+ 100,
+ &Default::default(),
+ executor,
+ )
+ .await;
+
+ Ok(Some(lsp::WorkspaceSymbolResponse::Flat(
+ matches
+ .into_iter()
+ .map(|mat| fake_symbols[mat.candidate_id].clone())
+ .collect(),
+ )))
+ }
+ },
+ );
+
+ let (multi_workspace, cx) =
+ cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
+ let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
+
+ let symbols = cx.new_window_entity(|window, cx| {
+ Picker::uniform_list(
+ ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
+ window,
+ cx,
+ )
+ });
+
+ symbols.update_in(cx, |p, window, cx| {
+ p.update_matches("안".to_string(), window, cx);
+ });
+
+ cx.run_until_parked();
+ symbols.read_with(cx, |symbols, _| {
+ assert_eq!(symbols.delegate.matches.len(), 1);
+ assert_eq!(symbols.delegate.matches[0].string, "안녕");
+ });
+
+ symbols.update_in(cx, |p, window, cx| {
+ assert!(p.delegate.render_match(0, false, window, cx).is_some());
+ });
+ }
+
fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
let store = SettingsStore::test(cx);