From 1945f20ddfe247e351b66629b329ae4dc42fe391 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Mon, 5 Aug 2024 10:27:58 +0200 Subject: [PATCH] Add triggerCharacters to completion source Completions are only triggered automatically, if the the trigger character matches one of the given trigger characters. --- README.md | 2 ++ src/completion.ts | 27 +++++++++++++++++----- test/completion.test.ts | 50 ++++++++++++++++++++++++++++++++--------- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 117a56c..93548c4 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,8 @@ Create an LSP based completion source. - `fromCompletionItemKind` (`Function`, optional) — Convert an LSP completion item kind to a CodeMirror completion type. - `section` (`string`, optional) — The section to use for completions. +- `triggerCharacters` (`string`) — Only trigger completions automatically when one of these + characters is typed. #### Returns diff --git a/src/completion.ts b/src/completion.ts index 2c2cde6..c44c83e 100644 --- a/src/completion.ts +++ b/src/completion.ts @@ -108,6 +108,11 @@ export declare namespace createCompletionSource { * The section to use for completions. */ section?: string + + /** + * Only trigger completions automatically when one of these characters is typed. + */ + triggerCharacters?: string } } @@ -125,12 +130,22 @@ export function createCompletionSource(options: createCompletionSource.Options): return async (context) => { const textDocument = getTextDocument(context.state) - const completionContext: CompletionContext = context.explicit - ? { triggerKind: 1 satisfies typeof CompletionTriggerKind.Invoked } - : { - triggerCharacter: context.state.sliceDoc(context.pos - 1, context.pos), - triggerKind: 2 satisfies typeof CompletionTriggerKind.TriggerCharacter - } + let completionContext: CompletionContext + if (context.explicit) { + completionContext = { + triggerKind: 1 satisfies typeof CompletionTriggerKind.Invoked + } + } else { + const triggerCharacter = context.state.sliceDoc(context.pos - 1, context.pos) + if (!options.triggerCharacters?.includes(triggerCharacter)) { + return null + } + + completionContext = { + triggerCharacter, + triggerKind: 2 satisfies typeof CompletionTriggerKind.TriggerCharacter + } + } const completions = await options.doComplete( textDocument, diff --git a/test/completion.test.ts b/test/completion.test.ts index 1a62c5b..05ed471 100644 --- a/test/completion.test.ts +++ b/test/completion.test.ts @@ -43,7 +43,7 @@ test('completion args explicit', async () => { expect(context).toStrictEqual({ triggerKind: CompletionTriggerKind.Invoked }) }) -test('completion args implicit', async () => { +test('completion args implicit trigger character match', async () => { let document: TextDocument | undefined let position: Position | undefined let context: LspCompletionContext | undefined @@ -55,6 +55,7 @@ test('completion args implicit', async () => { const completionSource = createCompletionSource({ markdownToDom, + triggerCharacters: 'x', doComplete(doc, pos, ctx) { document = doc position = pos @@ -72,6 +73,33 @@ test('completion args implicit', async () => { }) }) +test('completion args implicit trigger character no match', async () => { + let document: TextDocument | undefined + let position: Position | undefined + let context: LspCompletionContext | undefined + + const view = new EditorView({ + doc: 'Text\n', + extensions: [textDocument()] + }) + + const completionSource = createCompletionSource({ + markdownToDom, + triggerCharacters: 'Tet', + doComplete(doc, pos, ctx) { + document = doc + position = pos + context = ctx + } + }) + + await completionSource(new CompletionContext(view.state, 3, false)) + + expect(document).toBeUndefined() + expect(position).toBeUndefined() + expect(context).toBeUndefined() +}) + test('ignore null', async () => { const view = new EditorView({ doc: 'Text\n', @@ -85,7 +113,7 @@ test('ignore null', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 3, false)) + const completions = await completionSource(new CompletionContext(view.state, 3, true)) expect(completions).toBeNull() }) @@ -103,7 +131,7 @@ test('ignore undefined', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 3, false)) + const completions = await completionSource(new CompletionContext(view.state, 3, true)) expect(completions).toBeNull() }) @@ -122,7 +150,7 @@ test('ignore outdated', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 3, false, view)) + const completions = await completionSource(new CompletionContext(view.state, 3, true, view)) expect(completions).toBeNull() }) @@ -142,7 +170,7 @@ test('minimal meta', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 4, false)) + const completions = await completionSource(new CompletionContext(view.state, 4, true)) expect(completions).toStrictEqual({ commitCharacters: undefined, @@ -178,7 +206,7 @@ test('full meta', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 3, false)) + const completions = await completionSource(new CompletionContext(view.state, 3, true)) expect(completions).toStrictEqual({ commitCharacters: undefined, @@ -318,7 +346,7 @@ test('completion item kinds', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 3, false)) + const completions = await completionSource(new CompletionContext(view.state, 3, true)) expect(completions).toStrictEqual({ commitCharacters: undefined, @@ -544,7 +572,7 @@ test('textEditText', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 3, false)) + const completions = await completionSource(new CompletionContext(view.state, 3, true)) expect(completions).toStrictEqual({ commitCharacters: undefined, @@ -585,7 +613,7 @@ test('textEdit plain text', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 3, false)) + const completions = await completionSource(new CompletionContext(view.state, 3, true)) const apply = completions!.options[0]!.apply as (v: EditorView) => unknown apply(view) @@ -620,7 +648,7 @@ test('textEdit snippet', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 3, false)) + const completions = await completionSource(new CompletionContext(view.state, 3, true)) const apply = completions!.options[0]!.apply as (v: EditorView) => unknown apply(view) @@ -659,7 +687,7 @@ test('itemDefaults', async () => { } }) - const completions = await completionSource(new CompletionContext(view.state, 3, false)) + const completions = await completionSource(new CompletionContext(view.state, 3, true)) const apply = completions!.options[0]!.apply as (v: EditorView) => unknown apply(view)