diff --git a/packages/apidom-ls/src/apidom-language-types.ts b/packages/apidom-ls/src/apidom-language-types.ts index fd2b4cc54..957d117c6 100644 --- a/packages/apidom-ls/src/apidom-language-types.ts +++ b/packages/apidom-ls/src/apidom-language-types.ts @@ -277,6 +277,7 @@ export interface ValidationContext { export interface CompletionContext { maxNumberOfItems?: number; + enableLSPFilter?: boolean; } export interface DerefContext { diff --git a/packages/apidom-ls/src/services/completion/completion-service.ts b/packages/apidom-ls/src/services/completion/completion-service.ts index b6b279d65..71f6b8b7e 100644 --- a/packages/apidom-ls/src/services/completion/completion-service.ts +++ b/packages/apidom-ls/src/services/completion/completion-service.ts @@ -169,7 +169,7 @@ export class DefaultCompletionService implements CompletionService { } // eslint-disable-next-line class-methods-use-this - private resolveCaretContext(node: Element, offset: number): CaretContext { + private resolveCaretContext(node: Element, offset: number, textModified: boolean): CaretContext { let caretContext: CaretContext = CaretContext.UNDEFINED; if (node) { const sm = getSourceMap(node); @@ -180,7 +180,11 @@ export class DefaultCompletionService implements CompletionService { if (offset > sm.offset && offset < sm.endOffset!) { caretContext = CaretContext.KEY_INNER; } else if (offset === sm.offset) { - caretContext = CaretContext.KEY_START; + if (sm.length === 0 && textModified) { + caretContext = CaretContext.KEY_END; + } else { + caretContext = CaretContext.KEY_START; + } } else { caretContext = CaretContext.KEY_END; } @@ -251,6 +255,7 @@ export class DefaultCompletionService implements CompletionService { ): Promise { perfStart(PerfLabels.START); const context = !completionContext ? this.settings?.completionContext : completionContext; + const enableFiltering = context?.enableLSPFilter; const completionList: CompletionList = { items: [], isIncomplete: false, @@ -543,7 +548,7 @@ export class DefaultCompletionService implements CompletionService { // only if we have a node let completionNode: Element | undefined; if (node) { - const caretContext = this.resolveCaretContext(node, targetOffset); + const caretContext = this.resolveCaretContext(node, targetOffset, textModified); completionNode = this.resolveCompletionNode(node, caretContext); const completionNodeContext = this.resolveCompletionNodeContext(caretContext); @@ -707,7 +712,15 @@ export class DefaultCompletionService implements CompletionService { } else if (contentLanguage.format === 'YAML') { // item.insertText = `${item.insertText}\n`; } - collector.add(item); + if (word && word.length > 0) { + if (enableFiltering && item.insertText?.includes(word)) { + collector.add(item); + } else if (!enableFiltering) { + collector.add(item); + } + } else if (!word) { + collector.add(item); + } } } else if ( // in a primitive value node @@ -781,8 +794,12 @@ export class DefaultCompletionService implements CompletionService { */ item.filterText = text.substring(nodeSourceMap.offset, nodeSourceMap.endOffset!); - if (word && word.length > 0 && unquotedOriginalInsertText?.includes(word)) { - collector.add(item); + if (word && word.length > 0) { + if (enableFiltering && unquotedOriginalInsertText?.includes(word)) { + collector.add(item); + } else if (!enableFiltering) { + collector.add(item); + } } else if (!word) { collector.add(item); } diff --git a/packages/apidom-ls/test/complete.ts b/packages/apidom-ls/test/complete.ts index 80b7d1d94..dc9a096de 100644 --- a/packages/apidom-ls/test/complete.ts +++ b/packages/apidom-ls/test/complete.ts @@ -1243,4 +1243,84 @@ describe('apidom-ls-complete', function () { }, ] as ApidomCompletionItem[]); }); + + it('openapi / yaml - test LSP provided filter', async function () { + const completionContext: CompletionContext = { + maxNumberOfItems: 100, + enableLSPFilter: true, + }; + + const spec = fs + .readFileSync(path.join(__dirname, 'fixtures', 'openapi-complete-filter.yaml')) + .toString(); + + const doc: TextDocument = TextDocument.create( + 'foo://bar/penapi-complete-filter.yaml', + 'yaml', + 0, + spec, + ); + + const pos = Position.create(4, 8); + const result = await languageService.doCompletion( + doc, + { textDocument: doc, position: pos }, + completionContext, + ); + assert.deepEqual(result!.items, [ + { + label: 'responses', + insertText: 'responses: \n $1', + kind: 14, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + '[Responses Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#responsesObject)\n\\\n\\\nThe list of possible responses as they are returned from executing this operation.', + }, + targetSpecs: [{ namespace: 'openapi', version: '3.1.0' }], + filterText: 'se', + textEdit: { + range: { start: { line: 4, character: 6 }, end: { line: 4, character: 8 } }, + newText: 'responses: \n $1', + }, + }, + { + label: 'security', + insertText: 'security: \n - $1', + kind: 14, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + '[[Security Requirement Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#serverObject)]\n\\\n\\\nA declaration of which security mechanisms can be used for this operation. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. To make security optional, an empty security requirement (`{}`) can be included in the array. This definition overrides any declared top-level [`security`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oasSecurity). To remove a top-level security declaration, an empty array can be used.', + }, + targetSpecs: [{ namespace: 'openapi', version: '3.1.0' }], + preselect: true, + filterText: 'se', + textEdit: { + range: { start: { line: 4, character: 6 }, end: { line: 4, character: 8 } }, + newText: 'security: \n - $1', + }, + }, + { + label: 'servers', + insertText: 'servers: \n - $1', + kind: 14, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + '[[Server Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#serverObject)]\n\\\n\\\nAn alternative `server` array to service this operation. If an alternative `server` object is specified at the Path Item Object or Root level, it will be overridden by this value.', + }, + targetSpecs: [{ namespace: 'openapi', version: '3.1.0' }], + preselect: true, + filterText: 'se', + textEdit: { + range: { start: { line: 4, character: 6 }, end: { line: 4, character: 8 } }, + newText: 'servers: \n - $1', + }, + }, + ] as ApidomCompletionItem[]); + }); }); diff --git a/packages/apidom-ls/test/fixtures/openapi-complete-filter.yaml b/packages/apidom-ls/test/fixtures/openapi-complete-filter.yaml new file mode 100644 index 000000000..5fe71f98a --- /dev/null +++ b/packages/apidom-ls/test/fixtures/openapi-complete-filter.yaml @@ -0,0 +1,6 @@ +openapi: 3.1.0 +paths: + /pet: + put: + se + summary: Update an existing pet