Make word completions less intrusive (#36745)
Introduce `min_words_query_len` threshold for automatic word completion display, and set it to 3 by default. Re-enable word completions in Markdown and Plaintext. Release Notes: - Introduced `min_words_query_len` threshold for automatic word completion display, and set it to 3 by default to make them less intrusive
This commit is contained in:
parent
92bbcdeb7d
commit
3d2fa72d1f
6 changed files with 109 additions and 21 deletions
|
@ -1503,6 +1503,11 @@
|
||||||
//
|
//
|
||||||
// Default: fallback
|
// Default: fallback
|
||||||
"words": "fallback",
|
"words": "fallback",
|
||||||
|
// Minimum number of characters required to automatically trigger word-based completions.
|
||||||
|
// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
|
||||||
|
//
|
||||||
|
// Default: 3
|
||||||
|
"words_min_length": 3,
|
||||||
// Whether to fetch LSP completions or not.
|
// Whether to fetch LSP completions or not.
|
||||||
//
|
//
|
||||||
// Default: true
|
// Default: true
|
||||||
|
@ -1642,9 +1647,6 @@
|
||||||
"use_on_type_format": false,
|
"use_on_type_format": false,
|
||||||
"allow_rewrap": "anywhere",
|
"allow_rewrap": "anywhere",
|
||||||
"soft_wrap": "editor_width",
|
"soft_wrap": "editor_width",
|
||||||
"completions": {
|
|
||||||
"words": "disabled"
|
|
||||||
},
|
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"allowed": true
|
"allowed": true
|
||||||
}
|
}
|
||||||
|
@ -1658,9 +1660,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Plain Text": {
|
"Plain Text": {
|
||||||
"completions": {
|
|
||||||
"words": "disabled"
|
|
||||||
},
|
|
||||||
"allow_rewrap": "anywhere"
|
"allow_rewrap": "anywhere"
|
||||||
},
|
},
|
||||||
"Python": {
|
"Python": {
|
||||||
|
|
|
@ -301,6 +301,7 @@ mod tests {
|
||||||
init_test(cx, |settings| {
|
init_test(cx, |settings| {
|
||||||
settings.defaults.completions = Some(CompletionSettings {
|
settings.defaults.completions = Some(CompletionSettings {
|
||||||
words: WordsCompletionMode::Disabled,
|
words: WordsCompletionMode::Disabled,
|
||||||
|
words_min_length: 0,
|
||||||
lsp: true,
|
lsp: true,
|
||||||
lsp_fetch_timeout_ms: 0,
|
lsp_fetch_timeout_ms: 0,
|
||||||
lsp_insert_mode: LspInsertMode::Insert,
|
lsp_insert_mode: LspInsertMode::Insert,
|
||||||
|
@ -533,6 +534,7 @@ mod tests {
|
||||||
init_test(cx, |settings| {
|
init_test(cx, |settings| {
|
||||||
settings.defaults.completions = Some(CompletionSettings {
|
settings.defaults.completions = Some(CompletionSettings {
|
||||||
words: WordsCompletionMode::Disabled,
|
words: WordsCompletionMode::Disabled,
|
||||||
|
words_min_length: 0,
|
||||||
lsp: true,
|
lsp: true,
|
||||||
lsp_fetch_timeout_ms: 0,
|
lsp_fetch_timeout_ms: 0,
|
||||||
lsp_insert_mode: LspInsertMode::Insert,
|
lsp_insert_mode: LspInsertMode::Insert,
|
||||||
|
|
|
@ -5576,6 +5576,11 @@ impl Editor {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
|
.is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
|
||||||
|
|
||||||
|
let omit_word_completions = match &query {
|
||||||
|
Some(query) => query.chars().count() < completion_settings.words_min_length,
|
||||||
|
None => completion_settings.words_min_length != 0,
|
||||||
|
};
|
||||||
|
|
||||||
let (mut words, provider_responses) = match &provider {
|
let (mut words, provider_responses) = match &provider {
|
||||||
Some(provider) => {
|
Some(provider) => {
|
||||||
let provider_responses = provider.completions(
|
let provider_responses = provider.completions(
|
||||||
|
@ -5587,9 +5592,11 @@ impl Editor {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
let words = match completion_settings.words {
|
let words = match (omit_word_completions, completion_settings.words) {
|
||||||
WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
|
(true, _) | (_, WordsCompletionMode::Disabled) => {
|
||||||
WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
|
Task::ready(BTreeMap::default())
|
||||||
|
}
|
||||||
|
(false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
|
||||||
.background_spawn(async move {
|
.background_spawn(async move {
|
||||||
buffer_snapshot.words_in_range(WordsQuery {
|
buffer_snapshot.words_in_range(WordsQuery {
|
||||||
fuzzy_contents: None,
|
fuzzy_contents: None,
|
||||||
|
@ -5601,16 +5608,20 @@ impl Editor {
|
||||||
|
|
||||||
(words, provider_responses)
|
(words, provider_responses)
|
||||||
}
|
}
|
||||||
None => (
|
None => {
|
||||||
cx.background_spawn(async move {
|
let words = if omit_word_completions {
|
||||||
buffer_snapshot.words_in_range(WordsQuery {
|
Task::ready(BTreeMap::default())
|
||||||
fuzzy_contents: None,
|
} else {
|
||||||
range: word_search_range,
|
cx.background_spawn(async move {
|
||||||
skip_digits,
|
buffer_snapshot.words_in_range(WordsQuery {
|
||||||
|
fuzzy_contents: None,
|
||||||
|
range: word_search_range,
|
||||||
|
skip_digits,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}),
|
};
|
||||||
Task::ready(Ok(Vec::new())),
|
(words, Task::ready(Ok(Vec::new())))
|
||||||
),
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
|
let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
|
||||||
|
|
|
@ -12237,6 +12237,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
settings.defaults.completions = Some(CompletionSettings {
|
settings.defaults.completions = Some(CompletionSettings {
|
||||||
lsp_insert_mode,
|
lsp_insert_mode,
|
||||||
words: WordsCompletionMode::Disabled,
|
words: WordsCompletionMode::Disabled,
|
||||||
|
words_min_length: 0,
|
||||||
lsp: true,
|
lsp: true,
|
||||||
lsp_fetch_timeout_ms: 0,
|
lsp_fetch_timeout_ms: 0,
|
||||||
});
|
});
|
||||||
|
@ -12295,6 +12296,7 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext)
|
||||||
update_test_language_settings(&mut cx, |settings| {
|
update_test_language_settings(&mut cx, |settings| {
|
||||||
settings.defaults.completions = Some(CompletionSettings {
|
settings.defaults.completions = Some(CompletionSettings {
|
||||||
words: WordsCompletionMode::Disabled,
|
words: WordsCompletionMode::Disabled,
|
||||||
|
words_min_length: 0,
|
||||||
// set the opposite here to ensure that the action is overriding the default behavior
|
// set the opposite here to ensure that the action is overriding the default behavior
|
||||||
lsp_insert_mode: LspInsertMode::Insert,
|
lsp_insert_mode: LspInsertMode::Insert,
|
||||||
lsp: true,
|
lsp: true,
|
||||||
|
@ -12331,6 +12333,7 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext)
|
||||||
update_test_language_settings(&mut cx, |settings| {
|
update_test_language_settings(&mut cx, |settings| {
|
||||||
settings.defaults.completions = Some(CompletionSettings {
|
settings.defaults.completions = Some(CompletionSettings {
|
||||||
words: WordsCompletionMode::Disabled,
|
words: WordsCompletionMode::Disabled,
|
||||||
|
words_min_length: 0,
|
||||||
// set the opposite here to ensure that the action is overriding the default behavior
|
// set the opposite here to ensure that the action is overriding the default behavior
|
||||||
lsp_insert_mode: LspInsertMode::Replace,
|
lsp_insert_mode: LspInsertMode::Replace,
|
||||||
lsp: true,
|
lsp: true,
|
||||||
|
@ -13072,6 +13075,7 @@ async fn test_word_completion(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |language_settings| {
|
init_test(cx, |language_settings| {
|
||||||
language_settings.defaults.completions = Some(CompletionSettings {
|
language_settings.defaults.completions = Some(CompletionSettings {
|
||||||
words: WordsCompletionMode::Fallback,
|
words: WordsCompletionMode::Fallback,
|
||||||
|
words_min_length: 0,
|
||||||
lsp: true,
|
lsp: true,
|
||||||
lsp_fetch_timeout_ms: 10,
|
lsp_fetch_timeout_ms: 10,
|
||||||
lsp_insert_mode: LspInsertMode::Insert,
|
lsp_insert_mode: LspInsertMode::Insert,
|
||||||
|
@ -13168,6 +13172,7 @@ async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext
|
||||||
init_test(cx, |language_settings| {
|
init_test(cx, |language_settings| {
|
||||||
language_settings.defaults.completions = Some(CompletionSettings {
|
language_settings.defaults.completions = Some(CompletionSettings {
|
||||||
words: WordsCompletionMode::Enabled,
|
words: WordsCompletionMode::Enabled,
|
||||||
|
words_min_length: 0,
|
||||||
lsp: true,
|
lsp: true,
|
||||||
lsp_fetch_timeout_ms: 0,
|
lsp_fetch_timeout_ms: 0,
|
||||||
lsp_insert_mode: LspInsertMode::Insert,
|
lsp_insert_mode: LspInsertMode::Insert,
|
||||||
|
@ -13231,6 +13236,7 @@ async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |language_settings| {
|
init_test(cx, |language_settings| {
|
||||||
language_settings.defaults.completions = Some(CompletionSettings {
|
language_settings.defaults.completions = Some(CompletionSettings {
|
||||||
words: WordsCompletionMode::Disabled,
|
words: WordsCompletionMode::Disabled,
|
||||||
|
words_min_length: 0,
|
||||||
lsp: true,
|
lsp: true,
|
||||||
lsp_fetch_timeout_ms: 0,
|
lsp_fetch_timeout_ms: 0,
|
||||||
lsp_insert_mode: LspInsertMode::Insert,
|
lsp_insert_mode: LspInsertMode::Insert,
|
||||||
|
@ -13304,6 +13310,7 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |language_settings| {
|
init_test(cx, |language_settings| {
|
||||||
language_settings.defaults.completions = Some(CompletionSettings {
|
language_settings.defaults.completions = Some(CompletionSettings {
|
||||||
words: WordsCompletionMode::Fallback,
|
words: WordsCompletionMode::Fallback,
|
||||||
|
words_min_length: 0,
|
||||||
lsp: false,
|
lsp: false,
|
||||||
lsp_fetch_timeout_ms: 0,
|
lsp_fetch_timeout_ms: 0,
|
||||||
lsp_insert_mode: LspInsertMode::Insert,
|
lsp_insert_mode: LspInsertMode::Insert,
|
||||||
|
@ -13361,6 +13368,56 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |language_settings| {
|
||||||
|
language_settings.defaults.completions = Some(CompletionSettings {
|
||||||
|
words: WordsCompletionMode::Enabled,
|
||||||
|
words_min_length: 3,
|
||||||
|
lsp: true,
|
||||||
|
lsp_fetch_timeout_ms: 0,
|
||||||
|
lsp_insert_mode: LspInsertMode::Insert,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||||
|
cx.set_state(indoc! {"ˇ
|
||||||
|
wow
|
||||||
|
wowen
|
||||||
|
wowser
|
||||||
|
"});
|
||||||
|
cx.simulate_keystroke("w");
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
cx.update_editor(|editor, _, _| {
|
||||||
|
if editor.context_menu.borrow_mut().is_some() {
|
||||||
|
panic!(
|
||||||
|
"expected completion menu to be hidden, as words completion threshold is not met"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.simulate_keystroke("o");
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
cx.update_editor(|editor, _, _| {
|
||||||
|
if editor.context_menu.borrow_mut().is_some() {
|
||||||
|
panic!(
|
||||||
|
"expected completion menu to be hidden, as words completion threshold is not met still"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.simulate_keystroke("w");
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
cx.update_editor(|editor, _, _| {
|
||||||
|
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||||
|
{
|
||||||
|
assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
|
||||||
|
} else {
|
||||||
|
panic!("expected completion menu to be open after the word completions threshold is met");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
|
fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
|
||||||
let position = || lsp::Position {
|
let position = || lsp::Position {
|
||||||
line: params.text_document_position.position.line,
|
line: params.text_document_position.position.line,
|
||||||
|
|
|
@ -350,6 +350,12 @@ pub struct CompletionSettings {
|
||||||
/// Default: `fallback`
|
/// Default: `fallback`
|
||||||
#[serde(default = "default_words_completion_mode")]
|
#[serde(default = "default_words_completion_mode")]
|
||||||
pub words: WordsCompletionMode,
|
pub words: WordsCompletionMode,
|
||||||
|
/// How many characters has to be in the completions query to automatically show the words-based completions.
|
||||||
|
/// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
|
||||||
|
///
|
||||||
|
/// Default: 3
|
||||||
|
#[serde(default = "default_3")]
|
||||||
|
pub words_min_length: usize,
|
||||||
/// Whether to fetch LSP completions or not.
|
/// Whether to fetch LSP completions or not.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
|
@ -359,7 +365,7 @@ pub struct CompletionSettings {
|
||||||
/// When set to 0, waits indefinitely.
|
/// When set to 0, waits indefinitely.
|
||||||
///
|
///
|
||||||
/// Default: 0
|
/// Default: 0
|
||||||
#[serde(default = "default_lsp_fetch_timeout_ms")]
|
#[serde(default)]
|
||||||
pub lsp_fetch_timeout_ms: u64,
|
pub lsp_fetch_timeout_ms: u64,
|
||||||
/// Controls how LSP completions are inserted.
|
/// Controls how LSP completions are inserted.
|
||||||
///
|
///
|
||||||
|
@ -405,8 +411,8 @@ fn default_lsp_insert_mode() -> LspInsertMode {
|
||||||
LspInsertMode::ReplaceSuffix
|
LspInsertMode::ReplaceSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_lsp_fetch_timeout_ms() -> u64 {
|
fn default_3() -> usize {
|
||||||
0
|
3
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The settings for a particular language.
|
/// The settings for a particular language.
|
||||||
|
@ -1468,6 +1474,7 @@ impl settings::Settings for AllLanguageSettings {
|
||||||
} else {
|
} else {
|
||||||
d.completions = Some(CompletionSettings {
|
d.completions = Some(CompletionSettings {
|
||||||
words: mode,
|
words: mode,
|
||||||
|
words_min_length: 3,
|
||||||
lsp: true,
|
lsp: true,
|
||||||
lsp_fetch_timeout_ms: 0,
|
lsp_fetch_timeout_ms: 0,
|
||||||
lsp_insert_mode: LspInsertMode::ReplaceSuffix,
|
lsp_insert_mode: LspInsertMode::ReplaceSuffix,
|
||||||
|
|
|
@ -2425,6 +2425,7 @@ Examples:
|
||||||
{
|
{
|
||||||
"completions": {
|
"completions": {
|
||||||
"words": "fallback",
|
"words": "fallback",
|
||||||
|
"words_min_length": 3,
|
||||||
"lsp": true,
|
"lsp": true,
|
||||||
"lsp_fetch_timeout_ms": 0,
|
"lsp_fetch_timeout_ms": 0,
|
||||||
"lsp_insert_mode": "replace_suffix"
|
"lsp_insert_mode": "replace_suffix"
|
||||||
|
@ -2444,6 +2445,17 @@ Examples:
|
||||||
2. `fallback` - Only if LSP response errors or times out, use document's words to show completions
|
2. `fallback` - Only if LSP response errors or times out, use document's words to show completions
|
||||||
3. `disabled` - Never fetch or complete document's words for completions (word-based completions can still be queried via a separate action)
|
3. `disabled` - Never fetch or complete document's words for completions (word-based completions can still be queried via a separate action)
|
||||||
|
|
||||||
|
### Min Words Query Length
|
||||||
|
|
||||||
|
- Description: Minimum number of characters required to automatically trigger word-based completions.
|
||||||
|
Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
|
||||||
|
- Setting: `words_min_length`
|
||||||
|
- Default: `3`
|
||||||
|
|
||||||
|
**Options**
|
||||||
|
|
||||||
|
Positive integer values
|
||||||
|
|
||||||
### LSP
|
### LSP
|
||||||
|
|
||||||
- Description: Whether to fetch LSP completions or not.
|
- Description: Whether to fetch LSP completions or not.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue