editor: Utilize filter_text from language server for filter_range (#33155)

Closes #33106

Instead of directly using `filter_text` in `StringMatchCandidate`, which
yields better results for fuzzy matching but messes up with highlighting
letters in bold, as `positions` list generated by fuzzy crate are now of
`filter_text` and not `label` shown to the user.

This PR fixes it by keeping use of `filter_range` in
`StringMatchCandidate`, which is range w.r.t to `label` shown to user.
And actually generating this `filter_range` at source by using
`filter_range` if exists.

- [x] Tests

Release Notes:

- Fixed issue where incorrect letters are marked as bold in completions.
This commit is contained in:
Smit Barmase 2025-06-21 19:47:16 +05:30 committed by GitHub
parent 834cdc1271
commit 76e3136369
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 206 additions and 61 deletions

View file

@ -131,8 +131,16 @@ impl super::LspAdapter for CLspAdapter {
let text = format!("{} {}", detail, label);
let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
let runs = language.highlight_text(&source, 11..11 + text.len());
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter_text| {
text.find(filter_text)
.map(|start| start..start + filter_text.len())
})
.unwrap_or(detail.len() + 1..text.len());
return Some(CodeLabel {
filter_range: detail.len() + 1..text.len(),
filter_range,
text,
runs,
});
@ -143,8 +151,16 @@ impl super::LspAdapter for CLspAdapter {
let detail = completion.detail.as_ref().unwrap();
let text = format!("{} {}", detail, label);
let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter_text| {
text.find(filter_text)
.map(|start| start..start + filter_text.len())
})
.unwrap_or(detail.len() + 1..text.len());
return Some(CodeLabel {
filter_range: detail.len() + 1..text.len(),
filter_range,
text,
runs,
});
@ -155,16 +171,24 @@ impl super::LspAdapter for CLspAdapter {
let detail = completion.detail.as_ref().unwrap();
let text = format!("{} {}", detail, label);
let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
let filter_start = detail.len() + 1;
let filter_end =
if let Some(end) = text.rfind('(').filter(|end| *end > filter_start) {
end
} else {
text.len()
};
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter_text| {
text.find(filter_text)
.map(|start| start..start + filter_text.len())
})
.unwrap_or_else(|| {
let filter_start = detail.len() + 1;
let filter_end = text
.rfind('(')
.filter(|end| *end > filter_start)
.unwrap_or(text.len());
filter_start..filter_end
});
return Some(CodeLabel {
filter_range: filter_start..filter_end,
filter_range,
text,
runs,
});
@ -186,7 +210,8 @@ impl super::LspAdapter for CLspAdapter {
.grammar()
.and_then(|g| g.highlight_id_for_name(highlight_name?))
{
let mut label = CodeLabel::plain(label.to_string(), None);
let mut label =
CodeLabel::plain(label.to_string(), completion.filter_text.as_deref());
label.runs.push((
0..label.text.rfind('(').unwrap_or(label.text.len()),
highlight_id,
@ -196,7 +221,10 @@ impl super::LspAdapter for CLspAdapter {
}
_ => {}
}
Some(CodeLabel::plain(label.to_string(), None))
Some(CodeLabel::plain(
label.to_string(),
completion.filter_text.as_deref(),
))
}
async fn label_for_symbol(

View file

@ -233,10 +233,18 @@ impl super::LspAdapter for GoLspAdapter {
let text = format!("{label} {detail}");
let source = Rope::from(format!("import {text}").as_str());
let runs = language.highlight_text(&source, 7..7 + text.len());
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter_text| {
text.find(filter_text)
.map(|start| start..start + filter_text.len())
})
.unwrap_or(0..label.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
filter_range,
});
}
Some((
@ -250,10 +258,18 @@ impl super::LspAdapter for GoLspAdapter {
name_offset,
language.highlight_text(&source, 4..4 + text.len()),
);
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter_text| {
text.find(filter_text)
.map(|start| start..start + filter_text.len())
})
.unwrap_or(0..label.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
filter_range,
});
}
Some((lsp::CompletionItemKind::STRUCT, _)) => {
@ -263,10 +279,18 @@ impl super::LspAdapter for GoLspAdapter {
name_offset,
language.highlight_text(&source, 5..5 + text.len()),
);
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter_text| {
text.find(filter_text)
.map(|start| start..start + filter_text.len())
})
.unwrap_or(0..label.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
filter_range,
});
}
Some((lsp::CompletionItemKind::INTERFACE, _)) => {
@ -276,10 +300,18 @@ impl super::LspAdapter for GoLspAdapter {
name_offset,
language.highlight_text(&source, 5..5 + text.len()),
);
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter_text| {
text.find(filter_text)
.map(|start| start..start + filter_text.len())
})
.unwrap_or(0..label.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
filter_range,
});
}
Some((lsp::CompletionItemKind::FIELD, detail)) => {
@ -290,10 +322,18 @@ impl super::LspAdapter for GoLspAdapter {
name_offset,
language.highlight_text(&source, 16..16 + text.len()),
);
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter_text| {
text.find(filter_text)
.map(|start| start..start + filter_text.len())
})
.unwrap_or(0..label.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
filter_range,
});
}
Some((lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD, detail)) => {
@ -304,8 +344,16 @@ impl super::LspAdapter for GoLspAdapter {
name_offset,
language.highlight_text(&source, 5..5 + text.len()),
);
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter_text| {
text.find(filter_text)
.map(|start| start..start + filter_text.len())
})
.unwrap_or(0..label.len());
return Some(CodeLabel {
filter_range: 0..label.len(),
filter_range,
text,
runs,
});

View file

@ -268,10 +268,15 @@ impl LspAdapter for PythonLspAdapter {
lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
_ => return None,
};
let filter_range = item
.filter_text
.as_deref()
.and_then(|filter| label.find(filter).map(|ix| ix..ix + filter.len()))
.unwrap_or(0..label.len());
Some(language::CodeLabel {
text: label.clone(),
runs: vec![(0..label.len(), highlight_id)],
filter_range: 0..label.len(),
filter_range,
})
}
@ -1152,10 +1157,15 @@ impl LspAdapter for PyLspAdapter {
lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
_ => return None,
};
let filter_range = item
.filter_text
.as_deref()
.and_then(|filter| label.find(filter).map(|ix| ix..ix + filter.len()))
.unwrap_or(0..label.len());
Some(language::CodeLabel {
text: label.clone(),
runs: vec![(0..label.len(), highlight_id)],
filter_range: 0..label.len(),
filter_range,
})
}

View file

@ -310,10 +310,15 @@ impl LspAdapter for RustLspAdapter {
let source = Rope::from(format!("{prefix}{text} }}"));
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
.unwrap_or(0..name.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..name.len(),
filter_range,
});
}
(
@ -330,10 +335,15 @@ impl LspAdapter for RustLspAdapter {
let source = Rope::from(format!("{prefix}{text} = ();"));
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
.unwrap_or(0..name.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..name.len(),
filter_range,
});
}
(
@ -367,9 +377,13 @@ impl LspAdapter for RustLspAdapter {
text.push(' ');
text.push_str(&detail);
}
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
.unwrap_or(0..completion.label.find('(').unwrap_or(text.len()));
return Some(CodeLabel {
filter_range: 0..completion.label.find('(').unwrap_or(text.len()),
filter_range,
text,
runs,
});
@ -378,12 +392,18 @@ impl LspAdapter for RustLspAdapter {
.as_ref()
.map_or(false, |detail| detail.starts_with("macro_rules! "))
{
let source = Rope::from(completion.label.as_str());
let runs = language.highlight_text(&source, 0..completion.label.len());
let text = completion.label.clone();
let len = text.len();
let source = Rope::from(text.as_str());
let runs = language.highlight_text(&source, 0..len);
let filter_range = completion
.filter_text
.as_deref()
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
.unwrap_or(0..len);
return Some(CodeLabel {
filter_range: 0..completion.label.len(),
text: completion.label.clone(),
filter_range,
text,
runs,
});
}
@ -406,7 +426,7 @@ impl LspAdapter for RustLspAdapter {
label.push(' ');
label.push_str(detail);
}
let mut label = CodeLabel::plain(label, None);
let mut label = CodeLabel::plain(label, completion.filter_text.as_deref());
if let Some(highlight_name) = highlight_name {
let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?;
label.runs.push((
@ -1181,6 +1201,49 @@ mod tests {
],
})
);
assert_eq!(
adapter
.label_for_completion(
&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::METHOD),
label: "await.as_deref_mut()".to_string(),
filter_text: Some("as_deref_mut".to_string()),
label_details: Some(CompletionItemLabelDetails {
detail: None,
description: Some("fn(&mut self) -> IterMut<'_, T>".to_string()),
}),
..Default::default()
},
&language
)
.await,
Some(CodeLabel {
text: "await.as_deref_mut()".to_string(),
filter_range: 6..18,
runs: vec![],
})
);
assert_eq!(
adapter
.label_for_completion(
&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FIELD),
label: "inner_value".to_string(),
filter_text: Some("value".to_string()),
detail: Some("String".to_string()),
..Default::default()
},
&language,
)
.await,
Some(CodeLabel {
text: "inner_value: String".to_string(),
filter_range: 6..11,
runs: vec![(0..11, HighlightId(3)), (13..19, HighlightId(0))],
})
);
}
#[gpui::test]

View file

@ -668,11 +668,15 @@ impl LspAdapter for TypeScriptLspAdapter {
} else {
item.label.clone()
};
let filter_range = item
.filter_text
.as_deref()
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
.unwrap_or(0..len);
Some(language::CodeLabel {
text,
runs: vec![(0..len, highlight_id)],
filter_range: 0..len,
filter_range,
})
}

View file

@ -195,11 +195,15 @@ impl LspAdapter for VtslsLspAdapter {
} else {
item.label.clone()
};
let filter_range = item
.filter_text
.as_deref()
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
.unwrap_or(0..len);
Some(language::CodeLabel {
text,
runs: vec![(0..len, highlight_id)],
filter_range: 0..len,
filter_range,
})
}