languages: Improve completion sorting for Python-based LSPs (#47160)

Closes #47086

This PR detects completion items ending with `=` (which typically
represent keyword arguments in function calls provided by
`Pyright`/`BasedPyright`/`pylsp`) and assigns them the highest sorting
priority.

This ensures that when a user is filling out function arguments, the
named parameters appear at the top of the list, rather than being buried
mixed with other symbols.

After fix:
<img width="786" height="460" alt="image"
src="https://github.com/user-attachments/assets/75e94b0f-a2e7-4876-b9bd-02ad98cc8c50"
/>


> **Note on Sorting:** Currently, these named arguments will be sorted
alphabetically by label. Preserving the original order of the function
definition would be ideal, but it requires information not currently
available in this logical block. Insights on how to retrieve the
definition order would be appreciated.

> **Note on other LSPs:** 
> * **`ty`**: Already provides well-sorted completions natively, so no
intervention is required.

Release Notes:

- Improved completion order for Python-based LSPs
This commit is contained in:
Xin Zhao authored and GitHub committed 2026-02-25 19:07:54 +05:30
1 parent 823f9dda40
commit 6b7c97eb8d
1 file changed
+34 -13
+34 -13
View File
@@ -112,6 +112,8 @@ impl FromStr for TestRunner {
/// Decided to ignore Pyright's sortText() completely and to manually sort all entries
fn process_pyright_completions(items: &mut [lsp::CompletionItem]) {
for item in items {
let is_named_argument = item.label.ends_with('=');
let is_dunder = item.label.starts_with("__") && item.label.ends_with("__");
let visibility_priority = if is_dunder {
@@ -124,23 +126,35 @@ fn process_pyright_completions(items: &mut [lsp::CompletionItem]) {
'0' // public
};
let is_external = item
.detail
.as_ref()
.is_some_and(|detail| detail == "Auto-import");
let source_priority = if is_external { '1' } else { '0' };
// Kind priority within same visibility level
let kind_priority = match item.kind {
Some(lsp::CompletionItemKind::ENUM_MEMBER) => '0',
Some(lsp::CompletionItemKind::FIELD) => '1',
Some(lsp::CompletionItemKind::PROPERTY) => '2',
Some(lsp::CompletionItemKind::VARIABLE) => '3',
Some(lsp::CompletionItemKind::CONSTANT) => '4',
Some(lsp::CompletionItemKind::METHOD) => '5',
Some(lsp::CompletionItemKind::FUNCTION) => '5',
Some(lsp::CompletionItemKind::CLASS) => '6',
Some(lsp::CompletionItemKind::MODULE) => '7',
_ => '8',
Some(lsp::CompletionItemKind::KEYWORD) => '0',
Some(lsp::CompletionItemKind::ENUM_MEMBER) => '1',
Some(lsp::CompletionItemKind::FIELD) => '2',
Some(lsp::CompletionItemKind::PROPERTY) => '3',
Some(lsp::CompletionItemKind::VARIABLE) => '4',
Some(lsp::CompletionItemKind::CONSTANT) => '5',
Some(lsp::CompletionItemKind::METHOD) => '6',
Some(lsp::CompletionItemKind::FUNCTION) => '6',
Some(lsp::CompletionItemKind::CLASS) => '7',
Some(lsp::CompletionItemKind::MODULE) => '8',
_ => 'z',
};
// Named arguments get higher priority
let argument_priority = if is_named_argument { '0' } else { '1' };
item.sort_text = Some(format!(
"{}{}{}",
visibility_priority, kind_priority, item.label
"{}{}{}{}{}",
argument_priority, source_priority, visibility_priority, kind_priority, item.label
));
}
}
@@ -1689,7 +1703,14 @@ impl LspAdapter for PyLspAdapter {
Self::SERVER_NAME
}
async fn process_completions(&self, _items: &mut [lsp::CompletionItem]) {}
async fn process_completions(&self, items: &mut [lsp::CompletionItem]) {
for item in items {
let is_named_argument = item.label.ends_with('=');
let priority = if is_named_argument { '0' } else { '1' };
let sort_text = item.sort_text.take().unwrap_or_else(|| item.label.clone());
item.sort_text = Some(format!("{}{}", priority, sort_text));
}
}
async fn label_for_completion(
&self,