Skip to content

Commit 20c53f4

Browse files
committed
fix: rewrite method-snippets so they are faster and compatible with Windows on latest vscode release. Now they are resolved when showing documentation (much earlier), instead of when completion accepted.
1 parent af72f55 commit 20c53f4

File tree

6 files changed

+99
-70
lines changed

6 files changed

+99
-70
lines changed

src/onCompletionAccepted.ts

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default (tsApi: { onCompletionAccepted }) => {
1313
let onCompletionAcceptedOverride: ((item: any) => void) | undefined
1414

1515
// eslint-disable-next-line complexity
16-
tsApi.onCompletionAccepted(async (item: vscode.CompletionItem & { document: vscode.TextDocument }) => {
16+
tsApi.onCompletionAccepted(async (item: vscode.CompletionItem & { document: vscode.TextDocument; tsEntry }) => {
1717
if (onCompletionAcceptedOverride) {
1818
onCompletionAcceptedOverride(item)
1919
return
@@ -36,57 +36,56 @@ export default (tsApi: { onCompletionAccepted }) => {
3636
return
3737
}
3838

39-
const enableMethodSnippets = vscode.workspace.getConfiguration(process.env.IDS_PREFIX, item.document).get('enableMethodSnippets')
40-
41-
if (enableMethodSnippets && /* snippet by vscode or by us to ignore pos */ typeof insertText !== 'object') {
39+
if (/* snippet is by vscode or by us to ignore pos */ typeof insertText !== 'object') {
4240
const editor = getActiveRegularEditor()!
43-
const startPos = editor.selection.start
44-
const nextSymbol = editor.document.getText(new vscode.Range(startPos, startPos.translate(0, 1)))
45-
if (!['(', '.', '`'].includes(nextSymbol)) {
46-
// all-in handling
47-
const controller = new AbortController()
48-
inFlightMethodSnippetOperation = controller
49-
const params: RequestResponseTypes['getFullMethodSnippet'] | undefined = await sendCommand('getFullMethodSnippet', {
50-
inputOptions: {
51-
acceptAmbiguous: lastAcceptedAmbiguousMethodSnippetSuggestion === suggestionName,
52-
} satisfies RequestOptionsTypes['getFullMethodSnippet'],
41+
if (item.tsEntry.source) {
42+
await new Promise<void>(resolve => {
43+
vscode.workspace.onDidChangeTextDocument(({ document }) => {
44+
if (editor.document !== document) return
45+
resolve()
46+
})
5347
})
48+
await new Promise(resolve => {
49+
setTimeout(resolve, 0)
50+
})
51+
}
5452

55-
if (controller.signal.aborted) return
56-
if (params === 'ambiguous') {
57-
lastAcceptedAmbiguousMethodSnippetSuggestion = suggestionName
58-
return
59-
}
60-
61-
if (!params) {
62-
return
63-
}
53+
const documentation = typeof item.documentation === 'object' ? item.documentation.value : item.documentation
54+
const dataMarker = '<!--tep '
55+
if (!documentation?.startsWith(dataMarker)) return
56+
const parsed = JSON.parse(documentation.slice(dataMarker.length, documentation.indexOf('e-->')))
57+
const { methodSnippet: params, isAmbiguous } = parsed
58+
if (!params) return
6459

65-
const replaceArguments = getExtensionSetting('methodSnippets.replaceArguments')
66-
67-
const snippet = new vscode.SnippetString('')
68-
snippet.appendText('(')
69-
// todo maybe when have optional (skipped), add a way to leave trailing , with tabstop (previous behavior)
70-
for (const [i, param] of params.entries()) {
71-
const replacer = replaceArguments[param.replace(/\?$/, '')]
72-
if (replacer === null) continue
73-
if (replacer) {
74-
useReplacer(snippet, replacer)
75-
} else {
76-
snippet.appendPlaceholder(param)
77-
}
60+
if (isAmbiguous && lastAcceptedAmbiguousMethodSnippetSuggestion !== suggestionName) {
61+
lastAcceptedAmbiguousMethodSnippetSuggestion = suggestionName
62+
return
63+
}
7864

79-
if (i !== params.length - 1) snippet.appendText(', ')
65+
const replaceArguments = getExtensionSetting('methodSnippets.replaceArguments')
66+
67+
const snippet = new vscode.SnippetString('')
68+
snippet.appendText('(')
69+
// todo maybe when have optional (skipped), add a way to leave trailing , with tabstop (previous behavior)
70+
for (const [i, param] of params.entries()) {
71+
const replacer = replaceArguments[param.replace(/\?$/, '')]
72+
if (replacer === null) continue
73+
if (replacer) {
74+
useReplacer(snippet, replacer)
75+
} else {
76+
snippet.appendPlaceholder(param)
8077
}
8178

82-
snippet.appendText(')')
83-
void editor.insertSnippet(snippet, undefined, {
84-
undoStopAfter: false,
85-
undoStopBefore: false,
86-
})
87-
if (vscode.workspace.getConfiguration('editor.parameterHints').get('enabled') && params.length > 0) {
88-
void vscode.commands.executeCommand('editor.action.triggerParameterHints')
89-
}
79+
if (i !== params.length - 1) snippet.appendText(', ')
80+
}
81+
82+
snippet.appendText(')')
83+
void editor.insertSnippet(snippet, undefined, {
84+
undoStopAfter: false,
85+
undoStopBefore: false,
86+
})
87+
if (vscode.workspace.getConfiguration('editor.parameterHints').get('enabled') && params.length > 0) {
88+
void vscode.commands.executeCommand('editor.action.triggerParameterHints')
9089
}
9190
}
9291
})

typescript/src/completionEntryDetails.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PrevCompletionMap, PrevCompletionsAdditionalData } from './completionsAtPosition'
2+
import constructMethodSnippet from './constructMethodSnippet'
23
import { RequestResponseTypes } from './ipcTypes'
34
import namespaceAutoImports from './namespaceAutoImports'
45
import { GetConfig } from './types'
@@ -12,7 +13,7 @@ export default function completionEntryDetails(
1213
languageService: ts.LanguageService,
1314
prevCompletionsMap: PrevCompletionMap,
1415
c: GetConfig,
15-
prevCompletionsAdittionalData: PrevCompletionsAdditionalData,
16+
{ enableMethodCompletion, completionsSymbolMap }: PrevCompletionsAdditionalData,
1617
): ts.CompletionEntryDetails | undefined {
1718
const [fileName, position, entryName, formatOptions, source, preferences, data] = inputArgs
1819
lastResolvedCompletion.value = { name: entryName }
@@ -48,6 +49,23 @@ export default function completionEntryDetails(
4849
prior.displayParts = [{ kind: 'text', text: detailPrepend }, ...prior.displayParts]
4950
}
5051
if (!prior) return
52+
const nextChar = sourceFile.getFullText().slice(position, position + 1)
53+
54+
if (enableMethodCompletion && c('enableMethodSnippets') && !['(', '.', '`'].includes(nextChar)) {
55+
const symbol = completionsSymbolMap.get(entryName)?.find(c => c.source === source)?.symbol
56+
if (symbol) {
57+
const resolveData = {
58+
isAmbiguous: false,
59+
}
60+
console.time('resolve methodSnippet')
61+
const methodSnippet = constructMethodSnippet(languageService, sourceFile, position, symbol, c, resolveData)
62+
console.timeEnd('resolve methodSnippet')
63+
if (methodSnippet) {
64+
const data = JSON.stringify({ methodSnippet, isAmbiguous: resolveData.isAmbiguous })
65+
prior.documentation = [{ kind: 'text', text: `<!--tep ${data} e-->` }, ...(prior.documentation ?? [])]
66+
}
67+
}
68+
}
5169
if (source) {
5270
const namespaceImport = namespaceAutoImports(
5371
c,

typescript/src/completionsAtPosition.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export type PrevCompletionMap = Record<
4545
>
4646
export type PrevCompletionsAdditionalData = {
4747
enableMethodCompletion: boolean
48+
completionsSymbolMap: Map</*entryName*/ string, Array<{ symbol: ts.Symbol; source?: string }>>
4849
}
4950

5051
type GetCompletionAtPositionReturnType = {
@@ -363,14 +364,28 @@ export const getCompletionsAtPosition = (
363364
prior.entries = prior.entries.map(({ ...entry }, index) => ({
364365
...entry,
365366
sortText: `${entry.sortText ?? ''}${index.toString().padStart(4, '0')}`,
366-
symbol: undefined,
367367
}))
368368
}
369+
const needsCompletionsSymbolMap = c('enableMethodSnippets')
370+
const completionsSymbolMap: PrevCompletionsAdditionalData['completionsSymbolMap'] = new Map()
371+
if (needsCompletionsSymbolMap) {
372+
for (const { name, source, symbol } of prior.entries) {
373+
if (!symbol) continue
374+
completionsSymbolMap.set(name, [
375+
...(completionsSymbolMap.get(name) ?? []),
376+
{
377+
symbol,
378+
source,
379+
},
380+
])
381+
}
382+
}
369383
return {
370384
completions: prior,
371385
prevCompletionsMap,
372386
prevCompletionsAdittionalData: {
373387
enableMethodCompletion: goodPositionForMethodCompletions,
388+
completionsSymbolMap,
374389
},
375390
}
376391
}

typescript/src/constructMethodSnippet.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,30 @@ import { GetConfig } from './types'
44
import { findChildContainingExactPosition } from './utils'
55

66
// todo-low-ee inspect any last arg infer
7-
export default (languageService: ts.LanguageService, sourceFile: ts.SourceFile, position: number, c: GetConfig, acceptAmbiguous: boolean) => {
8-
let node = findChildContainingExactPosition(sourceFile, position)
9-
if (!node || isTypeNode(node)) return
7+
export default (
8+
languageService: ts.LanguageService,
9+
sourceFile: ts.SourceFile,
10+
position: number,
11+
symbol: ts.Symbol,
12+
c: GetConfig,
13+
// acceptAmbiguous: boolean,
14+
resolveData: {
15+
isAmbiguous: boolean
16+
},
17+
) => {
18+
let containerNode = findChildContainingExactPosition(sourceFile, position)
19+
if (!containerNode || isTypeNode(containerNode)) return
1020

1121
const checker = languageService.getProgram()!.getTypeChecker()!
12-
const type = checker.getTypeAtLocation(node)
22+
const type = checker.getTypeOfSymbol(symbol)
1323

14-
if (ts.isIdentifier(node)) node = node.parent
15-
if (ts.isPropertyAccessExpression(node)) node = node.parent
24+
if (ts.isIdentifier(containerNode)) containerNode = containerNode.parent
25+
if (ts.isPropertyAccessExpression(containerNode)) containerNode = containerNode.parent
1626

17-
const isNewExpression = ts.isNewExpression(node)
18-
if (!isNewExpression && !acceptAmbiguous && (type.getProperties().length > 0 || type.getStringIndexType() || type.getNumberIndexType())) return 'ambiguous'
27+
const isNewExpression = ts.isNewExpression(containerNode)
28+
if (!isNewExpression && (type.getProperties().length > 0 || type.getStringIndexType() || type.getNumberIndexType())) {
29+
resolveData.isAmbiguous = true
30+
}
1931

2032
const signatures = checker.getSignaturesOfType(type, isNewExpression ? ts.SignatureKind.Construct : ts.SignatureKind.Call)
2133
// ensure node is not used below

typescript/src/ipcTypes.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export const triggerCharacterCommands = [
1313
'twoStepCodeActionSecondStep',
1414
'getFixAllEdits',
1515
'acceptRenameWithParams',
16-
'getFullMethodSnippet',
1716
'getExtendedCodeActionEdits',
1817
'getLastResolvedCompletion',
1918
] as const
@@ -78,7 +77,6 @@ export type RequestResponseTypes = {
7877
}
7978
turnArrayIntoObjectEdit: ts.TextChange[]
8079
getFixAllEdits: ts.TextChange[]
81-
getFullMethodSnippet: string[] | 'ambiguous' | undefined
8280
getExtendedCodeActionEdits: ApplyExtendedCodeActionResult
8381
getLastResolvedCompletion: {
8482
name: string
@@ -114,9 +112,6 @@ export type RequestOptionsTypes = {
114112
range: [number, number]
115113
applyCodeActionTitle: string
116114
}
117-
getFullMethodSnippet: {
118-
acceptAmbiguous: boolean
119-
}
120115
}
121116

122117
// export type EmmetResult = {

typescript/src/specialCommands/handle.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,6 @@ export default (
9494
const node = findChildContainingPosition(ts, sourceFile, position)
9595
return !node ? undefined : nodeToApiResponse(node)
9696
}
97-
if (specialCommand === 'getFullMethodSnippet') {
98-
changeType<RequestOptionsTypes['getFullMethodSnippet']>(specialCommandArg)
99-
return constructMethodSnippet(
100-
languageService,
101-
sourceFile,
102-
position,
103-
configuration,
104-
specialCommandArg.acceptAmbiguous,
105-
) satisfies RequestResponseTypes['getFullMethodSnippet']
106-
}
10797
if (specialCommand === 'getSpanOfEnclosingComment') {
10898
return languageService.getSpanOfEnclosingComment(fileName, position, false)
10999
}

0 commit comments

Comments
 (0)