diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 931e332d93d869bc31909643190d5b35f32409dc..8edcd9a80d1759d965dc38ecb1c88f0ea76056ad 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -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::( + 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::>(); + 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);