Skip to content

Commit

Permalink
Add commit characters to protocol (#59339)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabritto authored Jul 23, 2024
1 parent 97ed8fc commit 8a36e26
Show file tree
Hide file tree
Showing 145 changed files with 1,483 additions and 76 deletions.
1 change: 1 addition & 0 deletions src/harness/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ export class SessionClient implements LanguageService {

return entry as { name: string; kind: ScriptElementKind; kindModifiers: string; sortText: string; }; // TODO: GH#18217
}),
defaultCommitCharacters: response.body!.defaultCommitCharacters,
};
}

Expand Down
17 changes: 16 additions & 1 deletion src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,7 @@ export class TestState {
}

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

if (ts.hasProperty(options, "optionalReplacementSpan")) {
Expand All @@ -1035,6 +1035,14 @@ export class TestState {
);
}

if (ts.hasProperty(options, "defaultCommitCharacters")) {
assert.deepEqual(
actualCompletions.defaultCommitCharacters?.sort(),
options.defaultCommitCharacters?.sort(),
"Expected 'defaultCommitCharacters' properties to match",
);
}

const nameToEntries = new Map<string, ts.CompletionEntry[]>();
const nameAndSourceToData = new Map<string, ts.CompletionEntryData | false>();
for (const entry of actualCompletions.entries) {
Expand Down Expand Up @@ -1181,6 +1189,13 @@ export class TestState {
assert.equal(actual.isSnippet, expected.isSnippet, `At entry ${actual.name}: Expected 'isSnippet' properties to match`);
assert.equal(actual.source, expected.source, `At entry ${actual.name}: Expected 'source' values to match`);
assert.equal(actual.sortText, expected.sortText || ts.Completions.SortText.LocationPriority, `At entry ${actual.name}: Expected 'sortText' properties to match`);
if (ts.hasProperty(expected, "commitCharacters")) {
assert.deepEqual(
actual.commitCharacters?.sort(),
expected.commitCharacters?.sort(),
`At entry ${actual.name}: Expected 'commitCharacters' values to match`,
);
}
if (expected.sourceDisplay && actual.sourceDisplay) {
assert.equal(ts.displayPartsToString(actual.sourceDisplay), expected.sourceDisplay, `At entry ${actual.name}: Expected 'sourceDisplay' properties to match`);
}
Expand Down
2 changes: 2 additions & 0 deletions src/harness/fourslashInterfaceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,7 @@ export interface ExpectedCompletionEntryObject {
readonly labelDetails?: ExpectedCompletionEntryLabelDetails;
readonly tags?: readonly ts.JSDocTagInfo[];
readonly sortText?: ts.Completions.SortText;
readonly commitCharacters?: string[]; // If not specified, won't assert about this
}

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

export interface VerifySignatureHelpOptions {
Expand Down
58 changes: 52 additions & 6 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,14 @@ function resolvingModuleSpecifiers<TReturn>(
}
}

/** @internal */
export function getDefaultCommitCharacters(isNewIdentifierLocation: boolean): string[] {
if (isNewIdentifierLocation) {
return [];
}
return [".", ",", ";"];
}

/** @internal */
export function getCompletionsAtPosition(
host: LanguageServiceHost,
Expand All @@ -704,7 +712,14 @@ export function getCompletionsAtPosition(
if (triggerCharacter === " ") {
// `isValidTrigger` ensures we are at `import |`
if (preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) {
return { isGlobalCompletion: true, isMemberCompletion: false, isNewIdentifierLocation: true, isIncomplete: true, entries: [] };
return {
isGlobalCompletion: true,
isMemberCompletion: false,
isNewIdentifierLocation: true,
isIncomplete: true,
entries: [],
defaultCommitCharacters: getDefaultCommitCharacters(/*isNewIdentifierLocation*/ true),
};
}
return undefined;
}
Expand Down Expand Up @@ -887,7 +902,13 @@ function continuePreviousIncompleteResponse(
}

function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo {
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
return {
isGlobalCompletion: false,
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries,
defaultCommitCharacters: getDefaultCommitCharacters(/*isNewIdentifierLocation*/ false),
};
}

function getJSDocParameterCompletions(
Expand Down Expand Up @@ -1212,6 +1233,7 @@ function specificKeywordCompletionInfo(entries: readonly CompletionEntry[], isNe
isMemberCompletion: false,
isNewIdentifierLocation,
entries: entries.slice(),
defaultCommitCharacters: getDefaultCommitCharacters(isNewIdentifierLocation),
};
}

Expand Down Expand Up @@ -1387,6 +1409,7 @@ function completionInfoFromData(
isNewIdentifierLocation,
optionalReplacementSpan: getOptionalReplacementSpan(location),
entries,
defaultCommitCharacters: getDefaultCommitCharacters(isNewIdentifierLocation),
};
}

Expand Down Expand Up @@ -1596,7 +1619,14 @@ function getJsxClosingTagCompletion(location: Node | undefined, sourceFile: Sour
kindModifiers: undefined,
sortText: SortText.LocationPriority,
};
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: replacementSpan, entries: [entry] };
return {
isGlobalCompletion: false,
isMemberCompletion: true,
isNewIdentifierLocation: false,
optionalReplacementSpan: replacementSpan,
entries: [entry],
defaultCommitCharacters: getDefaultCommitCharacters(/*isNewIdentifierLocation*/ false),
};
}
return;
}
Expand All @@ -1622,6 +1652,7 @@ function getJSCompletionEntries(
kindModifiers: "",
sortText: SortText.JavascriptIdentifiers,
isFromUncheckedFile: true,
commitCharacters: [],
}, compareCompletionEntries);
}
});
Expand All @@ -1633,7 +1664,13 @@ function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPrefe
}

function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry {
return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority };
return {
name: completionNameForLiteral(sourceFile, preferences, literal),
kind: ScriptElementKind.string,
kindModifiers: ScriptElementKindModifier.none,
sortText: SortText.LocationPriority,
commitCharacters: [],
};
}

function createCompletionEntry(
Expand Down Expand Up @@ -1863,9 +1900,11 @@ function createCompletionEntry(

// Use a 'sortText' of 0' so that all symbol completion entries come before any other
// entries (like JavaScript identifier entries).
const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location);
const commitCharacters = (kind === ScriptElementKind.warning || kind === ScriptElementKind.string) ? [] : undefined;
return {
name,
kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location),
kind,
kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol),
sortText,
source,
Expand All @@ -1880,6 +1919,7 @@ function createCompletionEntry(
isPackageJsonImport: originIsPackageJsonImport(origin) || undefined,
isImportStatementCompletion: !!importStatementCompletion || undefined,
data,
commitCharacters,
...includeSymbol ? { symbol } : undefined,
};
}
Expand Down Expand Up @@ -2754,7 +2794,13 @@ export function getCompletionEntriesFromSymbols(
function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined {
const entries = getLabelStatementCompletions(node);
if (entries.length) {
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
return {
isGlobalCompletion: false,
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries,
defaultCommitCharacters: getDefaultCommitCharacters(/*isNewIdentifierLocation*/ false),
};
}
}

Expand Down
28 changes: 25 additions & 3 deletions src/services/stringCompletions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createCompletionDetails,
createCompletionDetailsForSymbol,
getCompletionEntriesFromSymbols,
getDefaultCommitCharacters,
getPropertiesForObjectExpression,
Log,
SortText,
Expand Down Expand Up @@ -260,7 +261,14 @@ function convertStringLiteralCompletions(
/*isRightOfOpenTag*/ undefined,
includeSymbol,
); // Target will not be used, so arbitrary
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries };
return {
isGlobalCompletion: false,
isMemberCompletion: true,
isNewIdentifierLocation: completion.hasIndexSignature,
optionalReplacementSpan,
entries,
defaultCommitCharacters: getDefaultCommitCharacters(completion.hasIndexSignature),
};
}
case StringLiteralCompletionKind.Types: {
const quoteChar = contextToken.kind === SyntaxKind.NoSubstitutionTemplateLiteral
Expand All @@ -274,8 +282,16 @@ function convertStringLiteralCompletions(
kind: ScriptElementKind.string,
sortText: SortText.LocationPriority,
replacementSpan: getReplacementSpanForContextToken(contextToken, position),
commitCharacters: [],
}));
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, optionalReplacementSpan, entries };
return {
isGlobalCompletion: false,
isMemberCompletion: false,
isNewIdentifierLocation: completion.isNewIdentifier,
optionalReplacementSpan,
entries,
defaultCommitCharacters: getDefaultCommitCharacters(completion.isNewIdentifier),
};
}
default:
return Debug.assertNever(completion);
Expand Down Expand Up @@ -310,7 +326,13 @@ function convertPathCompletions(pathCompletions: readonly PathCompletion[]): Com
const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment.
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.
const entries = pathCompletions.map(({ name, kind, span, extension }): CompletionEntry => ({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: SortText.LocationPriority, replacementSpan: span }));
return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries };
return {
isGlobalCompletion,
isMemberCompletion: false,
isNewIdentifierLocation,
entries,
defaultCommitCharacters: getDefaultCommitCharacters(isNewIdentifierLocation),
};
}
function kindModifiersFromExtension(extension: Extension | undefined): ScriptElementKindModifier {
switch (extension) {
Expand Down
8 changes: 8 additions & 0 deletions src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,10 @@ export interface CompletionInfo {
*/
isIncomplete?: true;
entries: CompletionEntry[];
/**
* Default commit characters for the completion entries.
*/
defaultCommitCharacters?: string[];
}

export interface CompletionEntryDataAutoImport {
Expand Down Expand Up @@ -1551,6 +1555,10 @@ export interface CompletionEntry {
* is an auto-import.
*/
data?: CompletionEntryData;
/**
* If this completion entry is selected, typing a commit character will cause the entry to be accepted.
*/
commitCharacters?: string[];
}

export interface CompletionEntryLabelDetails {
Expand Down
8 changes: 8 additions & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10764,6 +10764,10 @@ declare namespace ts {
*/
isIncomplete?: true;
entries: CompletionEntry[];
/**
* Default commit characters for the completion entries.
*/
defaultCommitCharacters?: string[];
}
interface CompletionEntryDataAutoImport {
/**
Expand Down Expand Up @@ -10870,6 +10874,10 @@ declare namespace ts {
* is an auto-import.
*/
data?: CompletionEntryData;
/**
* If this completion entry is selected, typing a commit character will cause the entry to be accepted.
*/
commitCharacters?: string[];
}
interface CompletionEntryLabelDetails {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4689,6 +4689,11 @@
}
]
}
],
"defaultCommitCharacters": [
".",
",",
";"
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"isGlobalCompletion": false,
"isMemberCompletion": false,
"isNewIdentifierLocation": true,
"entries": []
"entries": [],
"defaultCommitCharacters": []
}
}
]
10 changes: 10 additions & 0 deletions tests/baselines/reference/completionImportAttributes.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@
}
]
}
],
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
Expand Down Expand Up @@ -267,6 +272,11 @@
}
]
}
],
"defaultCommitCharacters": [
".",
",",
";"
]
}
}
Expand Down
10 changes: 10 additions & 0 deletions tests/baselines/reference/completionImportCallAssertion.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@
}
]
}
],
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
Expand Down Expand Up @@ -267,6 +272,11 @@
}
]
}
],
"defaultCommitCharacters": [
".",
",",
";"
]
}
}
Expand Down
Loading

0 comments on commit 8a36e26

Please sign in to comment.