Detailed changes
@@ -1503,6 +1503,11 @@
//
// Default: 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.
//
// Default: true
@@ -1642,9 +1647,6 @@
"use_on_type_format": false,
"allow_rewrap": "anywhere",
"soft_wrap": "editor_width",
- "completions": {
- "words": "disabled"
- },
"prettier": {
"allowed": true
}
@@ -1658,9 +1660,6 @@
}
},
"Plain Text": {
- "completions": {
- "words": "disabled"
- },
"allow_rewrap": "anywhere"
},
"Python": {
@@ -301,6 +301,7 @@ mod tests {
init_test(cx, |settings| {
settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled,
+ words_min_length: 0,
lsp: true,
lsp_fetch_timeout_ms: 0,
lsp_insert_mode: LspInsertMode::Insert,
@@ -533,6 +534,7 @@ mod tests {
init_test(cx, |settings| {
settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled,
+ words_min_length: 0,
lsp: true,
lsp_fetch_timeout_ms: 0,
lsp_insert_mode: LspInsertMode::Insert,
@@ -5576,6 +5576,11 @@ impl Editor {
.as_ref()
.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 {
Some(provider) => {
let provider_responses = provider.completions(
@@ -5587,9 +5592,11 @@ impl Editor {
cx,
);
- let words = match completion_settings.words {
- WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
- WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
+ let words = match (omit_word_completions, completion_settings.words) {
+ (true, _) | (_, WordsCompletionMode::Disabled) => {
+ Task::ready(BTreeMap::default())
+ }
+ (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
.background_spawn(async move {
buffer_snapshot.words_in_range(WordsQuery {
fuzzy_contents: None,
@@ -5601,16 +5608,20 @@ impl Editor {
(words, provider_responses)
}
- None => (
- cx.background_spawn(async move {
- buffer_snapshot.words_in_range(WordsQuery {
- fuzzy_contents: None,
- range: word_search_range,
- skip_digits,
+ None => {
+ let words = if omit_word_completions {
+ Task::ready(BTreeMap::default())
+ } else {
+ cx.background_spawn(async move {
+ 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;
@@ -12237,6 +12237,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
settings.defaults.completions = Some(CompletionSettings {
lsp_insert_mode,
words: WordsCompletionMode::Disabled,
+ words_min_length: 0,
lsp: true,
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| {
settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled,
+ words_min_length: 0,
// set the opposite here to ensure that the action is overriding the default behavior
lsp_insert_mode: LspInsertMode::Insert,
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| {
settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled,
+ words_min_length: 0,
// set the opposite here to ensure that the action is overriding the default behavior
lsp_insert_mode: LspInsertMode::Replace,
lsp: true,
@@ -13072,6 +13075,7 @@ async fn test_word_completion(cx: &mut TestAppContext) {
init_test(cx, |language_settings| {
language_settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Fallback,
+ words_min_length: 0,
lsp: true,
lsp_fetch_timeout_ms: 10,
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| {
language_settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Enabled,
+ words_min_length: 0,
lsp: true,
lsp_fetch_timeout_ms: 0,
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| {
language_settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled,
+ words_min_length: 0,
lsp: true,
lsp_fetch_timeout_ms: 0,
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| {
language_settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Fallback,
+ words_min_length: 0,
lsp: false,
lsp_fetch_timeout_ms: 0,
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> {
let position = || lsp::Position {
line: params.text_document_position.position.line,
@@ -350,6 +350,12 @@ pub struct CompletionSettings {
/// Default: `fallback`
#[serde(default = "default_words_completion_mode")]
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.
///
/// Default: true
@@ -359,7 +365,7 @@ pub struct CompletionSettings {
/// When set to 0, waits indefinitely.
///
/// Default: 0
- #[serde(default = "default_lsp_fetch_timeout_ms")]
+ #[serde(default)]
pub lsp_fetch_timeout_ms: u64,
/// Controls how LSP completions are inserted.
///
@@ -405,8 +411,8 @@ fn default_lsp_insert_mode() -> LspInsertMode {
LspInsertMode::ReplaceSuffix
}
-fn default_lsp_fetch_timeout_ms() -> u64 {
- 0
+fn default_3() -> usize {
+ 3
}
/// The settings for a particular language.
@@ -1468,6 +1474,7 @@ impl settings::Settings for AllLanguageSettings {
} else {
d.completions = Some(CompletionSettings {
words: mode,
+ words_min_length: 3,
lsp: true,
lsp_fetch_timeout_ms: 0,
lsp_insert_mode: LspInsertMode::ReplaceSuffix,
@@ -2425,6 +2425,7 @@ Examples:
{
"completions": {
"words": "fallback",
+ "words_min_length": 3,
"lsp": true,
"lsp_fetch_timeout_ms": 0,
"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
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
- Description: Whether to fetch LSP completions or not.