Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snippet edit feature #1343

Merged
merged 2 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions client-node-tests/src/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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');
Expand Down
1 change: 1 addition & 0 deletions client/src/common/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1836,6 +1836,7 @@ export abstract class BaseLanguageClient implements FeatureClient<Middleware, La
workspaceEdit.changeAnnotationSupport = {
groupsOnLabel: true
};
workspaceEdit.snippetEditSupport = true;

const diagnostics = ensure(ensure(result, 'textDocument')!, 'publishDiagnostics')!;
diagnostics.relatedInformation = true;
Expand Down
2 changes: 2 additions & 0 deletions client/src/common/protocolConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
MariaSolOs marked this conversation as resolved.
Show resolved Hide resolved
} else {
result.replace(uri, asRange(edit.range), edit.newText);
}
Expand Down
10 changes: 9 additions & 1 deletion protocol/src/common/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
MariaSolOs marked this conversation as resolved.
Show resolved Hide resolved
* @proposed
*/
snippetEditSupport?: boolean;
MariaSolOs marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -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
Expand Down
66 changes: 58 additions & 8 deletions types/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)[];
}

/**
Expand All @@ -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 };
}

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand All @@ -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));
MariaSolOs marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* A helper class
*/
Expand Down Expand Up @@ -4291,6 +4333,7 @@ export interface StringValue {
* The kind of string value.
*/
kind: 'snippet';

/**
* The snippet string.
*/
Expand All @@ -4301,6 +4344,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);
}
}

/**
Expand Down