Skip to content

Commit 8a36e26

Browse files
authored
Add commit characters to protocol (#59339)
1 parent 97ed8fc commit 8a36e26

File tree

145 files changed

+1483
-76
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

145 files changed

+1483
-76
lines changed

src/harness/client.ts

+1
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ export class SessionClient implements LanguageService {
300300

301301
return entry as { name: string; kind: ScriptElementKind; kindModifiers: string; sortText: string; }; // TODO: GH#18217
302302
}),
303+
defaultCommitCharacters: response.body!.defaultCommitCharacters,
303304
};
304305
}
305306

src/harness/fourslashImpl.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,7 @@ export class TestState {
10241024
}
10251025

10261026
if (ts.hasProperty(options, "isGlobalCompletion") && actualCompletions.isGlobalCompletion !== options.isGlobalCompletion) {
1027-
this.raiseError(`Expected 'isGlobalCompletion to be ${options.isGlobalCompletion}, got ${actualCompletions.isGlobalCompletion}`);
1027+
this.raiseError(`Expected 'isGlobalCompletion' to be ${options.isGlobalCompletion}, got ${actualCompletions.isGlobalCompletion}`);
10281028
}
10291029

10301030
if (ts.hasProperty(options, "optionalReplacementSpan")) {
@@ -1035,6 +1035,14 @@ export class TestState {
10351035
);
10361036
}
10371037

1038+
if (ts.hasProperty(options, "defaultCommitCharacters")) {
1039+
assert.deepEqual(
1040+
actualCompletions.defaultCommitCharacters?.sort(),
1041+
options.defaultCommitCharacters?.sort(),
1042+
"Expected 'defaultCommitCharacters' properties to match",
1043+
);
1044+
}
1045+
10381046
const nameToEntries = new Map<string, ts.CompletionEntry[]>();
10391047
const nameAndSourceToData = new Map<string, ts.CompletionEntryData | false>();
10401048
for (const entry of actualCompletions.entries) {
@@ -1181,6 +1189,13 @@ export class TestState {
11811189
assert.equal(actual.isSnippet, expected.isSnippet, `At entry ${actual.name}: Expected 'isSnippet' properties to match`);
11821190
assert.equal(actual.source, expected.source, `At entry ${actual.name}: Expected 'source' values to match`);
11831191
assert.equal(actual.sortText, expected.sortText || ts.Completions.SortText.LocationPriority, `At entry ${actual.name}: Expected 'sortText' properties to match`);
1192+
if (ts.hasProperty(expected, "commitCharacters")) {
1193+
assert.deepEqual(
1194+
actual.commitCharacters?.sort(),
1195+
expected.commitCharacters?.sort(),
1196+
`At entry ${actual.name}: Expected 'commitCharacters' values to match`,
1197+
);
1198+
}
11841199
if (expected.sourceDisplay && actual.sourceDisplay) {
11851200
assert.equal(ts.displayPartsToString(actual.sourceDisplay), expected.sourceDisplay, `At entry ${actual.name}: Expected 'sourceDisplay' properties to match`);
11861201
}

src/harness/fourslashInterfaceImpl.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1797,6 +1797,7 @@ export interface ExpectedCompletionEntryObject {
17971797
readonly labelDetails?: ExpectedCompletionEntryLabelDetails;
17981798
readonly tags?: readonly ts.JSDocTagInfo[];
17991799
readonly sortText?: ts.Completions.SortText;
1800+
readonly commitCharacters?: string[]; // If not specified, won't assert about this
18001801
}
18011802

18021803
export interface ExpectedCompletionEntryLabelDetails {
@@ -1820,6 +1821,7 @@ export interface VerifyCompletionsOptions {
18201821
readonly excludes?: ArrayOrSingle<string>;
18211822
readonly preferences?: ts.UserPreferences;
18221823
readonly triggerCharacter?: ts.CompletionsTriggerCharacter;
1824+
readonly defaultCommitCharacters?: string[]; // Only tested if set
18231825
}
18241826

18251827
export interface VerifySignatureHelpOptions {

src/services/completions.ts

+52-6
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,14 @@ function resolvingModuleSpecifiers<TReturn>(
682682
}
683683
}
684684

685+
/** @internal */
686+
export function getDefaultCommitCharacters(isNewIdentifierLocation: boolean): string[] {
687+
if (isNewIdentifierLocation) {
688+
return [];
689+
}
690+
return [".", ",", ";"];
691+
}
692+
685693
/** @internal */
686694
export function getCompletionsAtPosition(
687695
host: LanguageServiceHost,
@@ -704,7 +712,14 @@ export function getCompletionsAtPosition(
704712
if (triggerCharacter === " ") {
705713
// `isValidTrigger` ensures we are at `import |`
706714
if (preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) {
707-
return { isGlobalCompletion: true, isMemberCompletion: false, isNewIdentifierLocation: true, isIncomplete: true, entries: [] };
715+
return {
716+
isGlobalCompletion: true,
717+
isMemberCompletion: false,
718+
isNewIdentifierLocation: true,
719+
isIncomplete: true,
720+
entries: [],
721+
defaultCommitCharacters: getDefaultCommitCharacters(/*isNewIdentifierLocation*/ true),
722+
};
708723
}
709724
return undefined;
710725
}
@@ -887,7 +902,13 @@ function continuePreviousIncompleteResponse(
887902
}
888903

889904
function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo {
890-
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
905+
return {
906+
isGlobalCompletion: false,
907+
isMemberCompletion: false,
908+
isNewIdentifierLocation: false,
909+
entries,
910+
defaultCommitCharacters: getDefaultCommitCharacters(/*isNewIdentifierLocation*/ false),
911+
};
891912
}
892913

893914
function getJSDocParameterCompletions(
@@ -1212,6 +1233,7 @@ function specificKeywordCompletionInfo(entries: readonly CompletionEntry[], isNe
12121233
isMemberCompletion: false,
12131234
isNewIdentifierLocation,
12141235
entries: entries.slice(),
1236+
defaultCommitCharacters: getDefaultCommitCharacters(isNewIdentifierLocation),
12151237
};
12161238
}
12171239

@@ -1387,6 +1409,7 @@ function completionInfoFromData(
13871409
isNewIdentifierLocation,
13881410
optionalReplacementSpan: getOptionalReplacementSpan(location),
13891411
entries,
1412+
defaultCommitCharacters: getDefaultCommitCharacters(isNewIdentifierLocation),
13901413
};
13911414
}
13921415

@@ -1596,7 +1619,14 @@ function getJsxClosingTagCompletion(location: Node | undefined, sourceFile: Sour
15961619
kindModifiers: undefined,
15971620
sortText: SortText.LocationPriority,
15981621
};
1599-
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: replacementSpan, entries: [entry] };
1622+
return {
1623+
isGlobalCompletion: false,
1624+
isMemberCompletion: true,
1625+
isNewIdentifierLocation: false,
1626+
optionalReplacementSpan: replacementSpan,
1627+
entries: [entry],
1628+
defaultCommitCharacters: getDefaultCommitCharacters(/*isNewIdentifierLocation*/ false),
1629+
};
16001630
}
16011631
return;
16021632
}
@@ -1622,6 +1652,7 @@ function getJSCompletionEntries(
16221652
kindModifiers: "",
16231653
sortText: SortText.JavascriptIdentifiers,
16241654
isFromUncheckedFile: true,
1655+
commitCharacters: [],
16251656
}, compareCompletionEntries);
16261657
}
16271658
});
@@ -1633,7 +1664,13 @@ function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPrefe
16331664
}
16341665

16351666
function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry {
1636-
return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority };
1667+
return {
1668+
name: completionNameForLiteral(sourceFile, preferences, literal),
1669+
kind: ScriptElementKind.string,
1670+
kindModifiers: ScriptElementKindModifier.none,
1671+
sortText: SortText.LocationPriority,
1672+
commitCharacters: [],
1673+
};
16371674
}
16381675

16391676
function createCompletionEntry(
@@ -1863,9 +1900,11 @@ function createCompletionEntry(
18631900

18641901
// Use a 'sortText' of 0' so that all symbol completion entries come before any other
18651902
// entries (like JavaScript identifier entries).
1903+
const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location);
1904+
const commitCharacters = (kind === ScriptElementKind.warning || kind === ScriptElementKind.string) ? [] : undefined;
18661905
return {
18671906
name,
1868-
kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location),
1907+
kind,
18691908
kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol),
18701909
sortText,
18711910
source,
@@ -1880,6 +1919,7 @@ function createCompletionEntry(
18801919
isPackageJsonImport: originIsPackageJsonImport(origin) || undefined,
18811920
isImportStatementCompletion: !!importStatementCompletion || undefined,
18821921
data,
1922+
commitCharacters,
18831923
...includeSymbol ? { symbol } : undefined,
18841924
};
18851925
}
@@ -2754,7 +2794,13 @@ export function getCompletionEntriesFromSymbols(
27542794
function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined {
27552795
const entries = getLabelStatementCompletions(node);
27562796
if (entries.length) {
2757-
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
2797+
return {
2798+
isGlobalCompletion: false,
2799+
isMemberCompletion: false,
2800+
isNewIdentifierLocation: false,
2801+
entries,
2802+
defaultCommitCharacters: getDefaultCommitCharacters(/*isNewIdentifierLocation*/ false),
2803+
};
27582804
}
27592805
}
27602806

src/services/stringCompletions.ts

+25-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
createCompletionDetails,
55
createCompletionDetailsForSymbol,
66
getCompletionEntriesFromSymbols,
7+
getDefaultCommitCharacters,
78
getPropertiesForObjectExpression,
89
Log,
910
SortText,
@@ -260,7 +261,14 @@ function convertStringLiteralCompletions(
260261
/*isRightOfOpenTag*/ undefined,
261262
includeSymbol,
262263
); // Target will not be used, so arbitrary
263-
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries };
264+
return {
265+
isGlobalCompletion: false,
266+
isMemberCompletion: true,
267+
isNewIdentifierLocation: completion.hasIndexSignature,
268+
optionalReplacementSpan,
269+
entries,
270+
defaultCommitCharacters: getDefaultCommitCharacters(completion.hasIndexSignature),
271+
};
264272
}
265273
case StringLiteralCompletionKind.Types: {
266274
const quoteChar = contextToken.kind === SyntaxKind.NoSubstitutionTemplateLiteral
@@ -274,8 +282,16 @@ function convertStringLiteralCompletions(
274282
kind: ScriptElementKind.string,
275283
sortText: SortText.LocationPriority,
276284
replacementSpan: getReplacementSpanForContextToken(contextToken, position),
285+
commitCharacters: [],
277286
}));
278-
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, optionalReplacementSpan, entries };
287+
return {
288+
isGlobalCompletion: false,
289+
isMemberCompletion: false,
290+
isNewIdentifierLocation: completion.isNewIdentifier,
291+
optionalReplacementSpan,
292+
entries,
293+
defaultCommitCharacters: getDefaultCommitCharacters(completion.isNewIdentifier),
294+
};
279295
}
280296
default:
281297
return Debug.assertNever(completion);
@@ -310,7 +326,13 @@ function convertPathCompletions(pathCompletions: readonly PathCompletion[]): Com
310326
const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment.
311327
const isNewIdentifierLocation = true; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of.
312328
const entries = pathCompletions.map(({ name, kind, span, extension }): CompletionEntry => ({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: SortText.LocationPriority, replacementSpan: span }));
313-
return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries };
329+
return {
330+
isGlobalCompletion,
331+
isMemberCompletion: false,
332+
isNewIdentifierLocation,
333+
entries,
334+
defaultCommitCharacters: getDefaultCommitCharacters(isNewIdentifierLocation),
335+
};
314336
}
315337
function kindModifiersFromExtension(extension: Extension | undefined): ScriptElementKindModifier {
316338
switch (extension) {

src/services/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,10 @@ export interface CompletionInfo {
14391439
*/
14401440
isIncomplete?: true;
14411441
entries: CompletionEntry[];
1442+
/**
1443+
* Default commit characters for the completion entries.
1444+
*/
1445+
defaultCommitCharacters?: string[];
14421446
}
14431447

14441448
export interface CompletionEntryDataAutoImport {
@@ -1551,6 +1555,10 @@ export interface CompletionEntry {
15511555
* is an auto-import.
15521556
*/
15531557
data?: CompletionEntryData;
1558+
/**
1559+
* If this completion entry is selected, typing a commit character will cause the entry to be accepted.
1560+
*/
1561+
commitCharacters?: string[];
15541562
}
15551563

15561564
export interface CompletionEntryLabelDetails {

tests/baselines/reference/api/typescript.d.ts

+8
Original file line numberDiff line numberDiff line change
@@ -10764,6 +10764,10 @@ declare namespace ts {
1076410764
*/
1076510765
isIncomplete?: true;
1076610766
entries: CompletionEntry[];
10767+
/**
10768+
* Default commit characters for the completion entries.
10769+
*/
10770+
defaultCommitCharacters?: string[];
1076710771
}
1076810772
interface CompletionEntryDataAutoImport {
1076910773
/**
@@ -10870,6 +10874,10 @@ declare namespace ts {
1087010874
* is an auto-import.
1087110875
*/
1087210876
data?: CompletionEntryData;
10877+
/**
10878+
* If this completion entry is selected, typing a commit character will cause the entry to be accepted.
10879+
*/
10880+
commitCharacters?: string[];
1087310881
}
1087410882
interface CompletionEntryLabelDetails {
1087510883
/**

tests/baselines/reference/completionEntryForUnionMethod.baseline

+5
Original file line numberDiff line numberDiff line change
@@ -4689,6 +4689,11 @@
46894689
}
46904690
]
46914691
}
4692+
],
4693+
"defaultCommitCharacters": [
4694+
".",
4695+
",",
4696+
";"
46924697
]
46934698
}
46944699
}

tests/baselines/reference/completionForStringLiteralImport3.baseline

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"isGlobalCompletion": false,
1717
"isMemberCompletion": false,
1818
"isNewIdentifierLocation": true,
19-
"entries": []
19+
"entries": [],
20+
"defaultCommitCharacters": []
2021
}
2122
}
2223
]

tests/baselines/reference/completionImportAttributes.baseline

+10
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@
137137
}
138138
]
139139
}
140+
],
141+
"defaultCommitCharacters": [
142+
".",
143+
",",
144+
";"
140145
]
141146
}
142147
},
@@ -267,6 +272,11 @@
267272
}
268273
]
269274
}
275+
],
276+
"defaultCommitCharacters": [
277+
".",
278+
",",
279+
";"
270280
]
271281
}
272282
}

tests/baselines/reference/completionImportCallAssertion.baseline

+10
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@
137137
}
138138
]
139139
}
140+
],
141+
"defaultCommitCharacters": [
142+
".",
143+
",",
144+
";"
140145
]
141146
}
142147
},
@@ -267,6 +272,11 @@
267272
}
268273
]
269274
}
275+
],
276+
"defaultCommitCharacters": [
277+
".",
278+
",",
279+
";"
270280
]
271281
}
272282
}

0 commit comments

Comments
 (0)