languages: Fix completion item highlighting for Python (#59758)
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions

# Objective

Consider a simple Python file with the following content, where both a
method and a function are defined:
```python
class TestCase:
    def a_test_method(self):    # a method
        print("This is a method test")

def a_test_function():    # a function
    print("This is a function test")
```

Below this, if we type `a_test_func` and trigger language server
completion, we would expect to see `a_test_function` in the completion
list. Similarly, if we have:
```python
test = TestCase()
test.a_testˇ
```
and trigger language server completion, we should see `a_test_method` in
the completion list.

In both cases, we expect the items in the completion list to have the
same color highlighting as they do in the editor buffer, but currently,
they do not. This is because Zed uses the wrong queries to retrieve the
highlight. Taking the `ty` LSP adapter as an example:

d753a31db5/crates/languages/src/python.rs (L323-L332)
the key `"function.method"` and `"function"` are used to look up the
method and function highlight IDs, but these keys do not exist in
`crates/grammars/src/python/highlights.scm`, which defines the syntax
highlighting. The correct keys here should be `"function.method.call"`
and `"function.call"`.

## Solution

- As stated above, change the keys from `"function.method"` and
`"function"` to `"function.method.call"` and `"function.call"`,
respectively.
- Additionally, since we have several built-in language servers for
Python, a minor refactoring has been done to reduce duplicate code and
simplify future changes.

## Testing

- I have tested this change locally; the before and after comparison is
provided below.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

## Showcase


<details>
  <summary>Click to view showcase</summary>

| Before | After |
| :---: | :---: |
| <img width="767" height="308" alt="function_before"
src="https://github.com/user-attachments/assets/943852d6-f54e-411d-916f-add7aac57956"
/> | <img width="767" height="308" alt="function_after"
src="https://github.com/user-attachments/assets/3a817229-ce32-4d13-88b8-9e9151869338"
/> |
| <img width="767" height="308" alt="method_before"
src="https://github.com/user-attachments/assets/cbc0f4f4-be6e-44d3-9798-df29df5731a3"
/> | <img width="767" height="308" alt="method_after"
src="https://github.com/user-attachments/assets/6e8f7e84-c5c6-4b37-a2c5-10820f12a09d"
/> |


</details>

---

Release Notes:

- Improved Python completion item highlighting for methods and
functions.
This commit is contained in:
Xin Zhao 2026-06-24 16:04:40 +08:00 committed by GitHub
parent daf4656c87
commit f5c19126fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -14,7 +14,7 @@ use language::{
use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};
use language::{Toolchain, ToolchainList, ToolchainLister, ToolchainMetadata};
use lsp::{LanguageServerBinary, Uri};
use lsp::{CompletionItemKind, LanguageServerBinary, Uri};
use lsp::{LanguageServerBinaryOptions, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use pet_core::Configuration;
@ -193,16 +193,8 @@ fn label_for_pyright_completion(
let label = &item.label;
let label_len = label.len();
let grammar = language.grammar()?;
let highlight_id = match item.kind? {
lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method"),
lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function"),
lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type"),
lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant"),
lsp::CompletionItemKind::VARIABLE => grammar.highlight_id_for_name("variable"),
_ => {
return None;
}
};
let highlight_id = highlight_id_for_completion(item.kind?, grammar)?;
let mut text = label.clone();
if let Some(completion_details) = item
.label_details
@ -255,6 +247,24 @@ fn label_for_python_symbol(
))
}
/// Returns the highlight ID for the given completion item kind, if it is supported.
///
/// The outer `Option` is `None` if the item kind returned by the language server is not covered.
/// The inner `Option` is `None` if the item kind is covered, but the highlight name is not present in the grammar.
fn highlight_id_for_completion(
item_kind: CompletionItemKind,
grammar: &Arc<language::Grammar>,
) -> Option<Option<language::HighlightId>> {
match item_kind {
CompletionItemKind::METHOD => Some(grammar.highlight_id_for_name("function.method.call")),
CompletionItemKind::FUNCTION => Some(grammar.highlight_id_for_name("function.call")),
CompletionItemKind::CLASS => Some(grammar.highlight_id_for_name("type")),
CompletionItemKind::CONSTANT => Some(grammar.highlight_id_for_name("constant")),
CompletionItemKind::VARIABLE => Some(grammar.highlight_id_for_name("variable")),
_ => None,
}
}
pub struct TyLspAdapter {
fs: Arc<dyn Fs>,
}
@ -320,16 +330,7 @@ impl LspAdapter for TyLspAdapter {
let label = &item.label;
let label_len = label.len();
let grammar = language.grammar()?;
let highlight_id = match item.kind? {
lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method"),
lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function"),
lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type"),
lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant"),
lsp::CompletionItemKind::VARIABLE => grammar.highlight_id_for_name("variable"),
_ => {
return None;
}
};
let highlight_id = highlight_id_for_completion(item.kind?, grammar)?;
let mut text = label.clone();
if let Some(completion_details) = item
@ -1814,13 +1815,7 @@ impl LspAdapter for PyLspAdapter {
let label = &item.label;
let label_len = label.len();
let grammar = language.grammar()?;
let highlight_id = match item.kind? {
lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?,
lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?,
lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?,
lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
_ => return None,
};
let highlight_id = highlight_id_for_completion(item.kind?, grammar)??;
Some(language::CodeLabel::filtered(
label.clone(),
label_len,