Skip to content

Commit 3270cac

Browse files
committed
feat(method-snippets): Now to insert a method snippet for Object or lodash you need to accept suggestion twice (ambiguous suggestions)
1 parent 848259c commit 3270cac

File tree

6 files changed

+98
-29
lines changed

6 files changed

+98
-29
lines changed

README.MD

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,22 @@ function Foo() {
315315
}
316316
```
317317

318+
### Ambiguous Suggestions
319+
320+
Some variables like `Object` or `lodash` are common to access properties as well as call directly:
321+
322+
```ts
323+
Object.assign(...)
324+
Object()
325+
// or
326+
lodash.version
327+
lodash(...)
328+
```
329+
330+
To not be annoying, we don't insert a method snippet on such suggestion accept.
331+
332+
Instead, for these *ambiguous* suggestions we require you to accept the same suggestion twice to ensure you actually want a method snippet.
333+
318334
## Auto Imports
319335

320336
With this plugin you have total (almost) control over auto imports that appear in completions, quick fixes and import all quick fix. Some examples of what you can do:

src/onCompletionAccepted.ts

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { getActiveRegularEditor } from '@zardoy/vscode-utils'
33
import { expandPosition } from '@zardoy/vscode-utils/build/position'
44
import { getExtensionSetting, registerExtensionCommand } from 'vscode-framework'
55
import { oneOf } from '@zardoy/utils'
6-
import { RequestResponseTypes } from '../typescript/src/ipcTypes'
6+
import { RequestOptionsTypes, RequestResponseTypes } from '../typescript/src/ipcTypes'
77
import { sendCommand } from './sendCommand'
88

99
export default (tsApi: { onCompletionAccepted }) => {
1010
let inFlightMethodSnippetOperation: undefined | AbortController
1111
let justAcceptedReturnKeywordSuggestion = false
12+
let lastAcceptedAmbiguousMethodSnippetSuggestion: string | undefined
1213
let onCompletionAcceptedOverride: ((item: any) => void) | undefined
1314

1415
// eslint-disable-next-line complexity
@@ -19,6 +20,7 @@ export default (tsApi: { onCompletionAccepted }) => {
1920
}
2021

2122
const { label, insertText, kind } = item
23+
const suggestionName = typeof label === 'object' ? label.label : label
2224
if (kind === vscode.CompletionItemKind.Keyword) {
2325
if (insertText === 'return ') justAcceptedReturnKeywordSuggestion = true
2426
else if (insertText === 'default ') void vscode.commands.executeCommand('editor.action.triggerSuggest')
@@ -41,35 +43,49 @@ export default (tsApi: { onCompletionAccepted }) => {
4143
const startPos = editor.selection.start
4244
const nextSymbol = editor.document.getText(new vscode.Range(startPos, startPos.translate(0, 1)))
4345
if (!['(', '.', '`'].includes(nextSymbol)) {
46+
// all-in handling
4447
const controller = new AbortController()
4548
inFlightMethodSnippetOperation = controller
46-
const params: RequestResponseTypes['getFullMethodSnippet'] | undefined = await sendCommand('getFullMethodSnippet')
47-
if (!controller.signal.aborted && params) {
48-
const replaceArguments = getExtensionSetting('methodSnippets.replaceArguments')
49-
50-
const snippet = new vscode.SnippetString('')
51-
snippet.appendText('(')
52-
// todo maybe when have optional (skipped), add a way to leave trailing , with tabstop (previous behavior)
53-
for (const [i, param] of params.entries()) {
54-
const replacer = replaceArguments[param.replace(/\?$/, '')]
55-
if (replacer === null) continue
56-
if (replacer) {
57-
useReplacer(snippet, replacer)
58-
} else {
59-
snippet.appendPlaceholder(param)
60-
}
49+
const params: RequestResponseTypes['getFullMethodSnippet'] | undefined = await sendCommand('getFullMethodSnippet', {
50+
inputOptions: {
51+
acceptAmbiguous: lastAcceptedAmbiguousMethodSnippetSuggestion === suggestionName,
52+
} satisfies RequestOptionsTypes['getFullMethodSnippet'],
53+
})
6154

62-
if (i !== params.length - 1) snippet.appendText(', ')
63-
}
55+
if (controller.signal.aborted) return
56+
if (params === 'ambiguous') {
57+
lastAcceptedAmbiguousMethodSnippetSuggestion = suggestionName
58+
return
59+
}
6460

65-
snippet.appendText(')')
66-
void editor.insertSnippet(snippet, undefined, {
67-
undoStopAfter: false,
68-
undoStopBefore: false,
69-
})
70-
if (vscode.workspace.getConfiguration('editor.parameterHints').get('enabled') && params.length > 0) {
71-
void vscode.commands.executeCommand('editor.action.triggerParameterHints')
61+
if (!params) {
62+
return
63+
}
64+
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)
7277
}
78+
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')
7389
}
7490
}
7591
}
@@ -136,6 +152,12 @@ export default (tsApi: { onCompletionAccepted }) => {
136152
justAcceptedReturnKeywordSuggestion = false
137153
}
138154
})
155+
156+
vscode.window.onDidChangeTextEditorSelection(({ textEditor }) => {
157+
if (textEditor !== vscode.window.activeTextEditor) return
158+
// cursor position changed
159+
lastAcceptedAmbiguousMethodSnippetSuggestion = undefined
160+
})
139161
}
140162

141163
function useReplacer(snippet: vscode.SnippetString, replacer: string) {

typescript/src/constructMethodSnippet.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { isTypeNode } from './completions/keywordsSpace'
33
import { GetConfig } from './types'
44
import { findChildContainingExactPosition } from './utils'
55

6-
export default (languageService: ts.LanguageService, sourceFile: ts.SourceFile, position: number, c: GetConfig) => {
6+
// todo-low-ee inspect any last arg infer
7+
export default (languageService: ts.LanguageService, sourceFile: ts.SourceFile, position: number, c: GetConfig, acceptAmbiguous: boolean) => {
78
let node = findChildContainingExactPosition(sourceFile, position)
89
if (!node || isTypeNode(node)) return
910

@@ -14,6 +15,8 @@ export default (languageService: ts.LanguageService, sourceFile: ts.SourceFile,
1415
if (ts.isPropertyAccessExpression(node)) node = node.parent
1516

1617
const isNewExpression = ts.isNewExpression(node)
18+
if (!isNewExpression && !acceptAmbiguous && (type.getProperties().length || type.getStringIndexType() || type.getNumberIndexType())) return 'ambiguous'
19+
1720
const signatures = checker.getSignaturesOfType(type, isNewExpression ? ts.SignatureKind.Construct : ts.SignatureKind.Call)
1821
// ensure node is not used below
1922
if (signatures.length === 0) return

typescript/src/ipcTypes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export type RequestResponseTypes = {
7777
}
7878
turnArrayIntoObjectEdit: ts.TextChange[]
7979
getFixAllEdits: ts.TextChange[]
80-
getFullMethodSnippet: string[] | undefined
80+
getFullMethodSnippet: string[] | 'ambiguous' | undefined
8181
getExtendedCodeActionEdits: ApplyExtendedCodeActionResult
8282
}
8383

@@ -110,6 +110,9 @@ export type RequestOptionsTypes = {
110110
range: [number, number]
111111
applyCodeActionTitle: string
112112
}
113+
getFullMethodSnippet: {
114+
acceptAmbiguous: boolean
115+
}
113116
}
114117

115118
// export type EmmetResult = {

typescript/src/specialCommands/handle.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,14 @@ export default (
101101
return !node ? undefined : nodeToApiResponse(node)
102102
}
103103
if (specialCommand === 'getFullMethodSnippet') {
104-
return constructMethodSnippet(languageService, sourceFile, position, configuration) satisfies RequestResponseTypes['getFullMethodSnippet']
104+
changeType<RequestOptionsTypes['getFullMethodSnippet']>(specialCommandArg)
105+
return constructMethodSnippet(
106+
languageService,
107+
sourceFile,
108+
position,
109+
configuration,
110+
specialCommandArg.acceptAmbiguous,
111+
) satisfies RequestResponseTypes['getFullMethodSnippet']
105112
}
106113
if (specialCommand === 'getSpanOfEnclosingComment') {
107114
return languageService.getSpanOfEnclosingComment(fileName, position, false)

typescript/test/completions.spec.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,11 @@ test('Function props: cleans & highlights', () => {
109109
const compareMethodSnippetAgainstMarker = (inputMarkers: number[], marker: number, expected: string | null | string[]) => {
110110
const obj = Object.fromEntries(inputMarkers.entries())
111111
const markerPos = obj[marker]!
112-
const methodSnippet = constructMethodSnippet(languageService, getSourceFile(), markerPos, defaultConfigFunc)
112+
const methodSnippet = constructMethodSnippet(languageService, getSourceFile(), markerPos, defaultConfigFunc, false)
113+
if (methodSnippet === 'ambiguous') {
114+
expect(methodSnippet).toEqual(expected)
115+
return
116+
}
113117
const snippetToInsert = methodSnippet ? `(${methodSnippet.join(', ')})` : null
114118
expect(Array.isArray(expected) ? methodSnippet : snippetToInsert, `At marker ${marker}`).toEqual(expected)
115119
}
@@ -254,6 +258,20 @@ describe('Method snippets', () => {
254258
compareMethodSnippetAgainstMarker(markers, 2, [''])
255259
settingsOverride['methodSnippets.skip'] = 'no-skip'
256260
})
261+
262+
test('Ambiguous', () => {
263+
const [, _, markers] = fileContentsSpecialPositions(/* ts */ `
264+
declare const a: {
265+
(): void
266+
[a: string]: 5
267+
}
268+
a/*1*/
269+
Object/*2*/
270+
`)
271+
272+
compareMethodSnippetAgainstMarker(markers, 1, 'ambiguous')
273+
compareMethodSnippetAgainstMarker(markers, 2, 'ambiguous')
274+
})
257275
})
258276

259277
test('Emmet completion', () => {

0 commit comments

Comments
 (0)