Skip to content

Commit 722879d

Browse files
authored
Merge pull request #65 from zardoy/develop
2 parents 4c8fd9d + 9e5e4d4 commit 722879d

File tree

8 files changed

+230
-57
lines changed

8 files changed

+230
-57
lines changed

README.MD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Top Features
44

5+
### Special Commands
6+
7+
See [special commands list](#special-commands-list)
8+
59
### JSX Outline
610

711
(*disabled by default*) Enable with `tsEssentialPlugins.patchOutline`
@@ -144,6 +148,12 @@ type A<T extends 'foo' | 'bar' = ''> = ...
144148
145149
### Builtin CodeFix Fixes
146150
151+
## Special Commands List
152+
153+
### Go to / Select Nodes by Kind
154+
155+
Use cases: search excluding comments, search & replace only within strings, find interested JSX attribute node
156+
147157
## Even Even More
148158
149159
Please look at extension settings, as this extension has much more features than described here!

package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
"title": "Remove Function Arguments Types in Selection",
1818
"category": "TS Essentials"
1919
},
20+
{
21+
"command": "inspectAcceptedCompletion",
22+
"title": "Inspect Accepted Completion",
23+
"category": "TS Essentials (Developer)"
24+
},
2025
{
2126
"command": "goToEndOfValue",
2227
"title": "Go to End of Special Value",
@@ -36,6 +41,16 @@
3641
"command": "pickAndInsertFunctionArguments",
3742
"title": "Pick and Insert Function Arguments",
3843
"category": "TS Essentials"
44+
},
45+
{
46+
"command": "goToNodeBySyntaxKind",
47+
"title": "Go to Node by Syntax Kind",
48+
"category": "TS Essentials"
49+
},
50+
{
51+
"command": "goToNodeBySyntaxKindWithinSelection",
52+
"title": "Go to Node by Syntax Kind Within Selection",
53+
"category": "TS Essentials"
3954
}
4055
],
4156
"typescriptServerPlugins": [

src/onCompletionAccepted.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@ import * as vscode from 'vscode'
22
import { getActiveRegularEditor } from '@zardoy/vscode-utils'
33
import { conditionallyRegister } from '@zardoy/vscode-utils/build/settings'
44
import { expandPosition } from '@zardoy/vscode-utils/build/position'
5-
import { getExtensionSetting } from 'vscode-framework'
5+
import { getExtensionSetting, registerExtensionCommand } from 'vscode-framework'
66
import { oneOf } from '@zardoy/utils'
77

88
export default (tsApi: { onCompletionAccepted }) => {
99
let justAcceptedReturnKeywordSuggestion = false
10+
let onCompletionAcceptedOverride: ((item: any) => void) | undefined
1011

1112
tsApi.onCompletionAccepted((item: vscode.CompletionItem & { document: vscode.TextDocument }) => {
12-
const enableMethodSnippets = vscode.workspace.getConfiguration(process.env.IDS_PREFIX, item.document).get('enableMethodSnippets')
13+
if (onCompletionAcceptedOverride) {
14+
onCompletionAcceptedOverride(item)
15+
return
16+
}
17+
1318
const { insertText, documentation = '', kind } = item
1419
if (kind === vscode.CompletionItemKind.Keyword && insertText === 'return ') {
1520
justAcceptedReturnKeywordSuggestion = true
1621
}
1722

23+
const enableMethodSnippets = vscode.workspace.getConfiguration(process.env.IDS_PREFIX, item.document).get('enableMethodSnippets')
1824
const documentationString = documentation instanceof vscode.MarkdownString ? documentation.value : documentation
1925
const insertFuncArgs = /<!-- insert-func: (.*)-->/.exec(documentationString)?.[1]
2026
console.debug('insertFuncArgs', insertFuncArgs)
@@ -46,6 +52,29 @@ export default (tsApi: { onCompletionAccepted }) => {
4652
}
4753
})
4854

55+
registerExtensionCommand('inspectAcceptedCompletion', async () => {
56+
await vscode.window.withProgress(
57+
{
58+
location: vscode.ProgressLocation.Notification,
59+
title: 'Waiting for completion to be accepted',
60+
cancellable: true,
61+
},
62+
async (_progress, token) => {
63+
const accepted = await new Promise<boolean>(resolve => {
64+
token.onCancellationRequested(() => {
65+
onCompletionAcceptedOverride = undefined
66+
resolve(false)
67+
})
68+
onCompletionAcceptedOverride = item => {
69+
console.dir(item, { depth: 4 })
70+
resolve(true)
71+
}
72+
})
73+
if (accepted) void vscode.window.showInformationMessage('Completion accepted, see console for details')
74+
},
75+
)
76+
})
77+
4978
conditionallyRegister(
5079
'suggestions.keywordsInsertText',
5180
() =>

src/specialCommands.ts

Lines changed: 109 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import * as vscode from 'vscode'
2-
import { getActiveRegularEditor } from '@zardoy/vscode-utils'
3-
import { registerExtensionCommand } from 'vscode-framework'
2+
import { getActiveRegularEditor, rangeToSelection } from '@zardoy/vscode-utils'
3+
import { getExtensionCommandId, registerExtensionCommand, VSCodeQuickPickItem } from 'vscode-framework'
44
import { showQuickPick } from '@zardoy/vscode-utils/build/quickPick'
5+
import _ from 'lodash'
6+
import { compact } from '@zardoy/utils'
57
import { RequestOptionsTypes, RequestResponseTypes } from '../typescript/src/ipcTypes'
68
import { sendCommand } from './sendCommand'
7-
import { tsRangeToVscode } from './util'
9+
import { tsRangeToVscode, tsRangeToVscodeSelection } from './util'
810

911
export default () => {
1012
registerExtensionCommand('removeFunctionArgumentsTypesInSelection', async () => {
@@ -55,45 +57,51 @@ export default () => {
5557
editor.selection = new vscode.Selection(currentValueRange.start, currentValueRange.end)
5658
})
5759

58-
registerExtensionCommand('pickAndInsertFunctionArguments', async () => {
59-
const editor = getActiveRegularEditor()
60-
if (!editor) return
61-
const result = await sendCommand<RequestResponseTypes['pickAndInsertFunctionArguments']>('pickAndInsertFunctionArguments')
62-
if (!result) return
60+
const nodePicker = async <T>(data: T[], renderItem: (item: T) => Omit<VSCodeQuickPickItem, 'value'> & { nodeRange: [number, number] }) => {
61+
const editor = vscode.window.activeTextEditor!
6362
const originalSelections = editor.selections
64-
65-
const renderArgs = (args: Array<[name: string, type: string]>) => `${args.map(([name, type]) => (type ? `${name}: ${type}` : name)).join(', ')}`
66-
6763
let revealBack = true
68-
const selectedFunction = await showQuickPick(
69-
result.functions.map(func => {
70-
const [name, _decl, args] = func
64+
65+
// todo-p1 button to merge nodes with duplicated contents (e.g. same strings)
66+
const selected = await showQuickPick(
67+
data.map(item => {
68+
const custom = renderItem(item)
7169
return {
72-
label: name,
73-
value: func,
74-
description: `(${renderArgs(args)})`,
70+
...custom,
71+
value: item,
7572
buttons: [
7673
{
7774
iconPath: new vscode.ThemeIcon('go-to-file'),
7875
tooltip: 'Go to declaration',
76+
action: 'goToStartPos',
77+
},
78+
{
79+
iconPath: new vscode.ThemeIcon('arrow-both'),
80+
tooltip: 'Add to selection',
81+
action: 'addSelection',
7982
},
8083
],
8184
}
8285
}),
8386
{
84-
onDidTriggerItemButton(event) {
87+
title: 'Select node...',
88+
onDidTriggerItemButton({ item, button }) {
89+
const { action } = button as any
90+
const sel = tsRangeToVscodeSelection(editor.document, (item as any).nodeRange)
8591
revealBack = false
86-
this.hide()
87-
const pos = editor.document.positionAt(event.item.value[1][0])
88-
editor.selection = new vscode.Selection(pos, pos)
89-
editor.revealRange(editor.selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport)
92+
if (action === 'goToStartPos') {
93+
editor.selection = new vscode.Selection(sel.start, sel.start)
94+
editor.revealRange(editor.selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport)
95+
this.hide()
96+
} else {
97+
editor.selections = [...editor.selections, sel]
98+
}
9099
},
91100
onDidChangeFirstActive(item) {
92-
const pos = editor.document.positionAt(item.value[1][0])
101+
const pos = editor.document.positionAt((item as any).nodeRange[0])
93102
editor.selection = new vscode.Selection(pos, pos)
94103
editor.revealRange(editor.selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport)
95104
},
96-
onDidShow() {},
97105
matchOnDescription: true,
98106
},
99107
)
@@ -102,6 +110,23 @@ export default () => {
102110
editor.revealRange(editor.selection)
103111
}
104112

113+
return selected
114+
}
115+
116+
registerExtensionCommand('pickAndInsertFunctionArguments', async () => {
117+
const editor = getActiveRegularEditor()
118+
if (!editor) return
119+
const result = await sendCommand<RequestResponseTypes['pickAndInsertFunctionArguments']>('pickAndInsertFunctionArguments')
120+
if (!result) return
121+
122+
const renderArgs = (args: Array<[name: string, type: string]>) => `${args.map(([name, type]) => (type ? `${name}: ${type}` : name)).join(', ')}`
123+
124+
const selectedFunction = await nodePicker(result.functions, ([name, decl, args]) => ({
125+
label: name,
126+
description: `(${renderArgs(args)})`,
127+
nodeRange: decl,
128+
}))
129+
105130
if (!selectedFunction) return
106131
const selectedArgs = await showQuickPick(
107132
selectedFunction[2].map(arg => {
@@ -130,4 +155,64 @@ export default () => {
130155
}
131156
})
132157
})
158+
159+
registerExtensionCommand('goToNodeBySyntaxKind', async (_arg, { filterWithSelection = false }: { filterWithSelection?: boolean } = {}) => {
160+
const editor = vscode.window.activeTextEditor
161+
if (!editor) return
162+
const { document } = editor
163+
const result = await sendCommand<RequestResponseTypes['filterBySyntaxKind']>('filterBySyntaxKind')
164+
if (!result) return
165+
// todo optimize
166+
if (filterWithSelection) {
167+
result.nodesByKind = Object.fromEntries(
168+
compact(
169+
Object.entries(result.nodesByKind).map(([kind, nodes]) => {
170+
const filteredNodes = nodes.filter(({ range: tsRange }) =>
171+
editor.selections.some(sel => sel.contains(tsRangeToVscode(document, tsRange))),
172+
)
173+
if (filteredNodes.length === 0) return
174+
return [kind, filteredNodes]
175+
}),
176+
),
177+
)
178+
}
179+
180+
const selectedKindNodes = await showQuickPick(
181+
_.sortBy(Object.entries(result.nodesByKind), ([, nodes]) => nodes.length)
182+
.reverse()
183+
.map(([kind, nodes]) => ({
184+
label: kind,
185+
description: nodes.length.toString(),
186+
value: nodes,
187+
buttons: [
188+
{
189+
iconPath: new vscode.ThemeIcon('arrow-both'),
190+
tooltip: 'Select all nodes of this kind',
191+
},
192+
],
193+
})),
194+
{
195+
onDidTriggerItemButton(button) {
196+
editor.selections = button.item.value.map(({ range }) => tsRangeToVscodeSelection(document, range))
197+
this.hide()
198+
},
199+
},
200+
)
201+
if (!selectedKindNodes) return
202+
const selectedNode = await nodePicker(selectedKindNodes, node => ({
203+
label: document
204+
.getText(tsRangeToVscode(document, node.range))
205+
.trim()
206+
.replace(/\r?\n\s+/g, ' '),
207+
nodeRange: node.range,
208+
value: node,
209+
}))
210+
if (!selectedNode) return
211+
editor.selection = tsRangeToVscodeSelection(document, selectedNode.range)
212+
editor.revealRange(editor.selection)
213+
})
214+
215+
registerExtensionCommand('goToNodeBySyntaxKindWithinSelection', async () => {
216+
await vscode.commands.executeCommand(getExtensionCommandId('goToNodeBySyntaxKind'), { filterWithSelection: true })
217+
})
133218
}

src/util.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ import * as vscode from 'vscode'
22

33
export const tsRangeToVscode = (document: vscode.TextDocument, [start, end]: [number, number]) =>
44
new vscode.Range(document.positionAt(start), document.positionAt(end))
5+
6+
export const tsRangeToVscodeSelection = (document: vscode.TextDocument, [start, end]: [number, number]) =>
7+
new vscode.Selection(document.positionAt(start), document.positionAt(end))

typescript/src/definitions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ export default (proxy: ts.LanguageService, info: ts.server.PluginCreateInfo, c:
44
proxy.getDefinitionAndBoundSpan = (fileName, position) => {
55
const prior = info.languageService.getDefinitionAndBoundSpan(fileName, position)
66
if (!prior) return
7-
if (__WEB__)
7+
if (__WEB__) {
88
// let extension handle it
99
// TODO failedAliasResolution
1010
prior.definitions = prior.definitions?.filter(def => {
1111
return !def.unverified || def.fileName === fileName
1212
})
13+
}
1314

1415
// used after check
1516
const firstDef = prior.definitions![0]!

typescript/src/ipcTypes.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,23 @@ export type NodeAtPositionResponse = {
1818
end: number
1919
}
2020

21-
export type PickFunctionArgsType = [name: string, declaration: [number, number], args: [name: string, type: string][]]
21+
type TsRange = [number, number]
22+
23+
export type PickFunctionArgsType = [name: string, declaration: TsRange, args: [name: string, type: string][]]
2224

2325
export type RequestResponseTypes = {
2426
removeFunctionArgumentsTypesInSelection: {
25-
ranges: [number, number][]
27+
ranges: TsRange[]
2628
}
2729
getRangeOfSpecialValue: {
28-
range: [number, number]
30+
range: TsRange
2931
}
3032
pickAndInsertFunctionArguments: {
3133
functions: PickFunctionArgsType[]
3234
}
35+
filterBySyntaxKind: {
36+
nodesByKind: Record<string, Array<{ range: TsRange }>>
37+
}
3338
}
3439

3540
export type RequestOptionsTypes = {

0 commit comments

Comments
 (0)