diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index d4eff1e0ac..6a20f49f19 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1942,6 +1942,11 @@ namespace ts { case SnippetKind.TabStop: emitTabStop(snippet); break; + case SnippetKind.Sequence: + emitSequence(snippet); + break; + default: + return Debug.fail("Unsupported SnippetKind."); } } @@ -1956,6 +1961,12 @@ namespace ts { nonEscapingWrite(`\$${snippet.order}`); } + function emitSequence(snippet: SnippetSequence) { + for (const s of snippet.snippets) { + emitTabStop(s); + } + } + // // Identifiers // diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b1690b6f78..0887675cb8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6828,7 +6828,7 @@ namespace ts { } /* @internal */ - export type SnippetElement = TabStop | Placeholder; + export type SnippetElement = TabStop | Placeholder | SnippetSequence; /* @internal */ export interface TabStop { @@ -6842,6 +6842,12 @@ namespace ts { order: number; } + /* @internal */ + export interface SnippetSequence { + kind: SnippetKind.Sequence; + snippets: TabStop[]; + } + // Reference: https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax /* @internal */ export const enum SnippetKind { @@ -6849,6 +6855,7 @@ namespace ts { Placeholder, // `${1:foo}` Choice, // `${1|one,two,three|}` Variable, // `$name`, `${name:default}` + Sequence, // A sequence of tabstops, e.g. `$1$0` } export const enum EmitFlags { diff --git a/src/services/completions.ts b/src/services/completions.ts index c70efda3f3..c8d28a2a01 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -852,12 +852,19 @@ namespace ts.Completions { let tabstopStart = 1; if (preferences.includeCompletionsWithSnippetText) { isSnippet = true; - // We are adding a final tabstop (i.e. $0) in the body of the suggested member, if it has one. + // We are adding an initial and final tabstop (i.e. `$1$0`) in the body of the suggested member, + // if it has one. // Note: this assumes we won't have more than one body in the completion nodes, which should be the case. - const emptyStatement1 = factory.createExpressionStatement(factory.createIdentifier("")); - setSnippetElement(emptyStatement1, { kind: SnippetKind.TabStop, order: 1 }); + const emptyStatement = factory.createExpressionStatement(factory.createIdentifier("")); + setSnippetElement(emptyStatement, { + kind: SnippetKind.Sequence, + snippets: [ + { kind: SnippetKind.TabStop, order: 1 }, + { kind: SnippetKind.TabStop, order: 0 }, + ], + }); tabstopStart = 2; - body = factory.createBlock([emptyStatement1], /* multiline */ true); + body = factory.createBlock([emptyStatement], /* multiline */ true); } else { body = factory.createBlock([], /* multiline */ true); diff --git a/tests/cases/fourslash/completionsOverridingMethod2.ts b/tests/cases/fourslash/completionsOverridingMethod2.ts index 724e958c55..7438651b05 100644 --- a/tests/cases/fourslash/completionsOverridingMethod2.ts +++ b/tests/cases/fourslash/completionsOverridingMethod2.ts @@ -30,7 +30,7 @@ verify.completions({ }, isSnippet: true, insertText: -"\"\\$usd\"(${2:a}: ${3:number}): ${4:number} {\n $1\n}\n", +"\"\\$usd\"(${2:a}: ${3:number}): ${4:number} {\n $1$0\n}\n", } ], }); \ No newline at end of file