Don't require strict prefix matches for private field completions
Fixxes #89556
This commit is contained in:
parent
b4a835f5b9
commit
58fe34bb77
|
@ -77,26 +77,8 @@ class MyCompletionItem extends vscode.CompletionItem {
|
|||
};
|
||||
}
|
||||
|
||||
if (tsEntry.insertText) {
|
||||
this.insertText = tsEntry.insertText;
|
||||
|
||||
// Set filterText for intelliCode and bracket accessors , but not for `this.` completions since it results in
|
||||
// them being overly prioritized. #74164
|
||||
this.filterText = !(/^this\./).test(tsEntry.insertText) ? tsEntry.insertText : undefined;
|
||||
|
||||
// Handle the case:
|
||||
//
|
||||
// ```
|
||||
// const xyz = { 'ab c': 1 };
|
||||
// xyz.ab|
|
||||
// ```
|
||||
//
|
||||
// In which case we want to insert a bracket accessor but should use `.abc` as the filter text instead of
|
||||
// the bracketed insert text.
|
||||
if (tsEntry.insertText[0] === '[') {
|
||||
this.filterText = tsEntry.insertText.replace(/^\[['"](.+)[['"]\]$/, '.$1');
|
||||
}
|
||||
}
|
||||
this.insertText = tsEntry.insertText;
|
||||
this.filterText = this.getFilterText(line, tsEntry.insertText);
|
||||
|
||||
if (completionContext.isMemberCompletion && completionContext.dotAccessorContext) {
|
||||
this.filterText = completionContext.dotAccessorContext.text + (this.insertText || this.label);
|
||||
|
@ -139,6 +121,42 @@ class MyCompletionItem extends vscode.CompletionItem {
|
|||
this.resolveRange(line);
|
||||
}
|
||||
|
||||
private getFilterText(line: string, insertText: string | undefined): string | undefined {
|
||||
// Handle private field completions
|
||||
if (this.tsEntry.name.startsWith('#')) {
|
||||
const wordRange = this.document.getWordRangeAtPosition(this.position);
|
||||
const wordStart = wordRange ? line.charAt(wordRange.start.character) : undefined;
|
||||
if (insertText) {
|
||||
if (insertText.startsWith('this.#')) {
|
||||
return wordStart === '#' ? insertText : insertText.replace(/^this\.#/, '');
|
||||
} else {
|
||||
return insertText;
|
||||
}
|
||||
} else {
|
||||
return wordStart === '#' ? undefined : this.tsEntry.name.replace(/^#/, '');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// For `this.` completions, generally don't set the filter text since we don't want them to be overly prioritized. #74164
|
||||
if (insertText?.startsWith('this.')) {
|
||||
return undefined;
|
||||
}
|
||||
// Handle the case:
|
||||
// ```
|
||||
// const xyz = { 'ab c': 1 };
|
||||
// xyz.ab|
|
||||
// ```
|
||||
// In which case we want to insert a bracket accessor but should use `.abc` as the filter text instead of
|
||||
// the bracketed insert text.
|
||||
else if (insertText?.startsWith('[')) {
|
||||
return insertText.replace(/^\[['"](.+)[['"]\]$/, '.$1');
|
||||
}
|
||||
|
||||
// In all other cases, fallback to using the insertText
|
||||
return insertText;
|
||||
}
|
||||
|
||||
private resolveRange(line: string): void {
|
||||
if (this.range) {
|
||||
return;
|
||||
|
|
|
@ -17,7 +17,7 @@ const jsTsLanguageConfiguration: vscode.LanguageConfiguration = {
|
|||
decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]].*$/,
|
||||
increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/
|
||||
},
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
|
||||
onEnterRules: [
|
||||
{
|
||||
// e.g. /** | */
|
||||
|
|
|
@ -27,20 +27,22 @@ async function updateConfig(newConfig: VsCodeConfiguration): Promise<VsCodeConfi
|
|||
}
|
||||
|
||||
namespace Config {
|
||||
export const suggestSelection = 'editor.suggestSelection';
|
||||
export const completeFunctionCalls = 'typescript.suggest.completeFunctionCalls';
|
||||
export const autoClosingBrackets = 'editor.autoClosingBrackets';
|
||||
export const completeFunctionCalls = 'typescript.suggest.completeFunctionCalls';
|
||||
export const insertMode = 'editor.suggest.insertMode';
|
||||
export const snippetSuggestions = 'editor.snippetSuggestions';
|
||||
export const suggestSelection = 'editor.suggestSelection';
|
||||
}
|
||||
|
||||
const insertModes = Object.freeze(['insert', 'replace']);
|
||||
|
||||
suite('TypeScript Completions', () => {
|
||||
const configDefaults: VsCodeConfiguration = Object.freeze({
|
||||
[Config.suggestSelection]: 'first',
|
||||
[Config.completeFunctionCalls]: false,
|
||||
[Config.autoClosingBrackets]: 'always',
|
||||
[Config.completeFunctionCalls]: false,
|
||||
[Config.insertMode]: 'insert',
|
||||
[Config.snippetSuggestions]: 'none',
|
||||
[Config.suggestSelection]: 'first',
|
||||
});
|
||||
|
||||
const _disposables: vscode.Disposable[] = [];
|
||||
|
@ -463,6 +465,110 @@ suite('TypeScript Completions', () => {
|
|||
`abc["xy w"]`
|
||||
));
|
||||
});
|
||||
|
||||
test('Private field completions on `this.#` should work', async () => {
|
||||
await enumerateConfig(Config.insertMode, insertModes, async config => {
|
||||
await createTestEditor(testDocumentUri,
|
||||
`class A {`,
|
||||
` #xyz = 1;`,
|
||||
` foo() {`,
|
||||
` this.#$0`,
|
||||
` }`,
|
||||
`}`,
|
||||
);
|
||||
|
||||
const document = await acceptFirstSuggestion(testDocumentUri, _disposables);
|
||||
assert.strictEqual(
|
||||
document.getText(),
|
||||
joinLines(
|
||||
`class A {`,
|
||||
` #xyz = 1;`,
|
||||
` foo() {`,
|
||||
` this.#xyz`,
|
||||
` }`,
|
||||
`}`,
|
||||
),
|
||||
`Config: ${config}`);
|
||||
});
|
||||
});
|
||||
|
||||
test('Private field completions on `#` should insert `this.`', async () => {
|
||||
await enumerateConfig(Config.insertMode, insertModes, async config => {
|
||||
await createTestEditor(testDocumentUri,
|
||||
`class A {`,
|
||||
` #xyz = 1;`,
|
||||
` foo() {`,
|
||||
` #$0`,
|
||||
` }`,
|
||||
`}`,
|
||||
);
|
||||
|
||||
const document = await acceptFirstSuggestion(testDocumentUri, _disposables);
|
||||
assert.strictEqual(
|
||||
document.getText(),
|
||||
joinLines(
|
||||
`class A {`,
|
||||
` #xyz = 1;`,
|
||||
` foo() {`,
|
||||
` this.#xyz`,
|
||||
` }`,
|
||||
`}`,
|
||||
),
|
||||
`Config: ${config}`);
|
||||
});
|
||||
});
|
||||
|
||||
test('Private field completions should not require strict prefix match (#89556)', async () => {
|
||||
await enumerateConfig(Config.insertMode, insertModes, async config => {
|
||||
await createTestEditor(testDocumentUri,
|
||||
`class A {`,
|
||||
` #xyz = 1;`,
|
||||
` foo() {`,
|
||||
` this.xyz$0`,
|
||||
` }`,
|
||||
`}`,
|
||||
);
|
||||
|
||||
const document = await acceptFirstSuggestion(testDocumentUri, _disposables);
|
||||
assert.strictEqual(
|
||||
document.getText(),
|
||||
joinLines(
|
||||
`class A {`,
|
||||
` #xyz = 1;`,
|
||||
` foo() {`,
|
||||
` this.#xyz`,
|
||||
` }`,
|
||||
`}`,
|
||||
),
|
||||
`Config: ${config}`);
|
||||
});
|
||||
});
|
||||
|
||||
test('Private field completions without `this.` should not require strict prefix match (#89556)', async () => {
|
||||
await enumerateConfig(Config.insertMode, insertModes, async config => {
|
||||
await createTestEditor(testDocumentUri,
|
||||
`class A {`,
|
||||
` #xyz = 1;`,
|
||||
` foo() {`,
|
||||
` xyz$0`,
|
||||
` }`,
|
||||
`}`,
|
||||
);
|
||||
|
||||
const document = await acceptFirstSuggestion(testDocumentUri, _disposables);
|
||||
assert.strictEqual(
|
||||
document.getText(),
|
||||
joinLines(
|
||||
`class A {`,
|
||||
` #xyz = 1;`,
|
||||
` foo() {`,
|
||||
` this.#xyz`,
|
||||
` }`,
|
||||
`}`,
|
||||
),
|
||||
`Config: ${config}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function enumerateConfig(configKey: string, values: readonly string[], f: (message: string) => Promise<void>): Promise<void> {
|
||||
|
|
Loading…
Reference in a new issue