diff --git a/client-node-tests/src/helpers.test.ts b/client-node-tests/src/helpers.test.ts index 90dffd73e..bf38631a7 100644 --- a/client-node-tests/src/helpers.test.ts +++ b/client-node-tests/src/helpers.test.ts @@ -8,7 +8,8 @@ import { strictEqual, ok } from 'assert'; import { Position, Range, TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier, Command, CodeLens, CodeActionContext, Diagnostic, DiagnosticSeverity, WorkspaceChange, TextDocumentEdit, CreateFile, RenameFile, DeleteFile, ChangeAnnotation, - AnnotatedTextEdit + AnnotatedTextEdit, + TextEdit } from 'vscode-languageclient'; suite('Protocol Helper Tests', () => { @@ -124,17 +125,21 @@ suite('Protocol Helper Tests', () => { strictEqual(workspaceEdit.documentChanges!.length, 2); let edits = (workspaceEdit.documentChanges![0] as TextDocumentEdit).edits; strictEqual(edits.length, 3); - rangeEqual(edits[0].range, Range.create(0,1,0,1)); - strictEqual(edits[0].newText, 'insert'); - rangeEqual(edits[1].range, Range.create(0,1,2,3)); - strictEqual(edits[1].newText, 'replace'); - rangeEqual(edits[2].range, Range.create(0,1,2,3)); - strictEqual(edits[2].newText, ''); + let edit = edits[0] as TextEdit; + rangeEqual(edit.range, Range.create(0,1,0,1)); + strictEqual(edit.newText, 'insert'); + edit = edits[1] as TextEdit; + rangeEqual(edit.range, Range.create(0,1,2,3)); + strictEqual(edit.newText, 'replace'); + edit = edits[2] as TextEdit; + rangeEqual(edit.range, Range.create(0,1,2,3)); + strictEqual(edit.newText, ''); edits = (workspaceEdit.documentChanges![1] as TextDocumentEdit).edits; strictEqual(edits.length, 1); - rangeEqual(edits[0].range, Range.create(2,3,2,3)); - strictEqual(edits[0].newText, 'insert'); + edit = edits[0] as TextEdit; + rangeEqual(edit.range, Range.create(2,3,2,3)); + strictEqual(edit.newText, 'insert'); workspaceChange.createFile('file:///create.txt'); workspaceChange.renameFile('file:///old.txt', 'file:///new.txt'); diff --git a/client/src/common/protocolConverter.ts b/client/src/common/protocolConverter.ts index 30c132ef7..3a9c0fc92 100644 --- a/client/src/common/protocolConverter.ts +++ b/client/src/common/protocolConverter.ts @@ -1088,6 +1088,8 @@ export function createConverter(uriConverter: URIConverter | undefined, trustMar for (const edit of change.edits) { if (ls.AnnotatedTextEdit.is(edit)) { result.replace(uri, asRange(edit.range), edit.newText, asMetadata(edit.annotationId)); + } else if (ls.SnippetTextEdit.is(edit)) { + result.replace(uri, asRange(edit.range), edit.snippet.value, asMetadata(edit.annotationId)); } else { result.replace(uri, asRange(edit.range), edit.newText); } diff --git a/protocol/src/common/protocol.ts b/protocol/src/common/protocol.ts index 0df3e84bb..7e23dd367 100644 --- a/protocol/src/common/protocol.ts +++ b/protocol/src/common/protocol.ts @@ -4094,6 +4094,14 @@ export interface WorkspaceEditClientCapabilities { * @since 3.16.0 */ changeAnnotationSupport?: ChangeAnnotationsSupportOptions; + + /** + * Whether the client supports snippets as text edits. + * + * @since 3.18.0 + * @proposed + */ + snippetEditSupport?: boolean; } /** @@ -4207,7 +4215,7 @@ export { DidChangeNotebookDocumentNotification, DidSaveNotebookDocumentParams, DidSaveNotebookDocumentNotification, DidCloseNotebookDocumentParams, DidCloseNotebookDocumentNotification, // Inline Completions - InlineCompletionClientCapabilities, InlineCompletionOptions, InlineCompletionParams, InlineCompletionRegistrationOptions, InlineCompletionRequest + InlineCompletionClientCapabilities, InlineCompletionOptions, InlineCompletionParams, InlineCompletionRegistrationOptions, InlineCompletionRequest, }; // To be backwards compatible diff --git a/types/src/main.ts b/types/src/main.ts index 916edd8a2..880843e9a 100644 --- a/types/src/main.ts +++ b/types/src/main.ts @@ -1026,8 +1026,11 @@ export interface TextDocumentEdit { * * @since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a * client capability. + * + * @since 3.18.0 - support for SnippetTextEdit. This is guarded using a + * client capability. */ - edits: (TextEdit | AnnotatedTextEdit)[]; + edits: (TextEdit | AnnotatedTextEdit | SnippetTextEdit)[]; } /** @@ -1038,7 +1041,7 @@ export namespace TextDocumentEdit { /** * Creates a new `TextDocumentEdit` */ - export function create(textDocument: OptionalVersionedTextDocumentIdentifier, edits: (TextEdit | AnnotatedTextEdit)[]): TextDocumentEdit { + export function create(textDocument: OptionalVersionedTextDocumentIdentifier, edits: (TextEdit | AnnotatedTextEdit | SnippetTextEdit)[]): TextDocumentEdit { return { textDocument, edits }; } @@ -1330,8 +1333,11 @@ export interface TextEditChange { * * @since 3.16.0 - support for annotated text edits. This is usually * guarded using a client capability. + * + * @since 3.18.0 - support for snippet text edits. This is usually + * guarded using a client capability. */ - all(): (TextEdit | AnnotatedTextEdit)[]; + all(): (TextEdit | AnnotatedTextEdit | SnippetTextEdit)[]; /** * Clears the edits for this change. @@ -1345,8 +1351,11 @@ export interface TextEditChange { * * @since 3.16.0 - support for annotated text edits. This is usually * guarded using a client capability. + * + * @since 3.18.0 - support for snippet text edits. This is usually + * guarded using a client capability. */ - add(edit: TextEdit | AnnotatedTextEdit): void; + add(edit: TextEdit | AnnotatedTextEdit | SnippetTextEdit): void; /** * Insert the given text at the given position. @@ -1380,10 +1389,10 @@ export interface TextEditChange { class TextEditChangeImpl implements TextEditChange { - private edits: (TextEdit | AnnotatedTextEdit)[]; + private edits: (TextEdit | AnnotatedTextEdit | SnippetTextEdit)[]; private changeAnnotations: ChangeAnnotations | undefined; - public constructor(edits: (TextEdit | AnnotatedTextEdit)[], changeAnnotations?: ChangeAnnotations) { + public constructor(edits: (TextEdit | AnnotatedTextEdit | SnippetTextEdit)[], changeAnnotations?: ChangeAnnotations) { this.edits = edits; this.changeAnnotations = changeAnnotations; } @@ -1451,11 +1460,11 @@ class TextEditChangeImpl implements TextEditChange { } } - public add(edit: TextEdit | AnnotatedTextEdit): void { + public add(edit: TextEdit | AnnotatedTextEdit | SnippetTextEdit): void { this.edits.push(edit); } - public all(): (TextEdit | AnnotatedTextEdit)[] { + public all(): (TextEdit | AnnotatedTextEdit | SnippetTextEdit)[] { return this.edits; } @@ -1470,6 +1479,39 @@ class TextEditChangeImpl implements TextEditChange { } } +/** + * An interactive text edit. + * + * @since 3.18.0 + * @proposed + */ +export interface SnippetTextEdit { + /** + * The range of the text document to be manipulated. + */ + range: Range; + + /** + * The snippet to be inserted. + */ + snippet: StringValue; + + /** + * The actual identifier of the snippet edit. + */ + annotationId?: ChangeAnnotationIdentifier; +} + +export namespace SnippetTextEdit { + export function is(value: any): value is SnippetTextEdit { + const candidate = value as SnippetTextEdit; + return Is.objectLiteral(candidate) + && Range.is(candidate.range) + && StringValue.isSnippet(candidate.snippet) + && (ChangeAnnotation.is(candidate.annotationId) || ChangeAnnotationIdentifier.is(candidate.annotationId)); + } +} + /** * A helper class */ @@ -4283,6 +4325,7 @@ export interface StringValue { * The kind of string value. */ kind: 'snippet'; + /** * The snippet string. */ @@ -4293,6 +4336,13 @@ export namespace StringValue { export function createSnippet(value: string): StringValue { return { kind: 'snippet', value }; } + + export function isSnippet(value: any): value is StringValue { + const candidate = value as StringValue; + return Is.objectLiteral(candidate) + && candidate.kind === 'snippet' + && Is.string(candidate.value); + } } /**