Skip to content

Commit 45e31b1

Browse files
authored
Merge pull request #84 from zardoy/develop
2 parents 5b9fe6b + 6332af9 commit 45e31b1

File tree

9 files changed

+229
-56
lines changed

9 files changed

+229
-56
lines changed

src/configurationType.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ export type Configuration = {
211211
* We already change sorting of suggestions, but enabling this option will also make:
212212
* - removing `id` from input suggestions
213213
* - simplify textarea
214+
* - removes uppercase suggestions e.g. `Foo` (write React component name after `<` for proper completions)
214215
* Doesn't change preview text for now!
215216
* @default false
216217
*/
@@ -277,7 +278,8 @@ export type Configuration = {
277278
removeImportsFromReferences: boolean
278279
/**
279280
* Small definition improvements by cleaning them out:
280-
* - remove node_modules definition on React.FC component click
281+
* - remove node_modules type definition on React.FC components (e.g. <Foo />)
282+
* - remove classes index definition on css modules (https://github.com/clinyong/vscode-css-modules/issues/63#issuecomment-1372851831)
281283
* @default true
282284
*/
283285
miscDefinitionImprovement: boolean
@@ -434,4 +436,11 @@ export type Configuration = {
434436
* @default []
435437
*/
436438
'autoImport.alwaysIgnoreInImportAll': string[]
439+
/**
440+
* Enable to display additional information about source declaration in completion's documentation
441+
* For now only displays function's body
442+
* Requires symbol patching
443+
* @default false
444+
*/
445+
displayAdditionalInfoInCompletions: boolean
437446
}

src/emmet.ts

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as vscode from 'vscode'
2+
import { compact } from '@zardoy/utils'
23
import { getExtensionSetting, registerExtensionCommand } from 'vscode-framework'
34
import { EmmetResult } from '../typescript/src/ipcTypes'
45
import { sendCommand } from './sendCommand'
@@ -15,6 +16,7 @@ export const registerEmmet = async () => {
1516

1617
const emmet = await import('@vscode/emmet-helper')
1718
const reactLangs = ['javascriptreact', 'typescriptreact']
19+
let lastStartOffset: number | undefined
1820
vscode.languages.registerCompletionItemProvider(
1921
reactLangs,
2022
{
@@ -23,10 +25,19 @@ export const registerEmmet = async () => {
2325
const emmetConfig = vscode.workspace.getConfiguration('emmet')
2426
if (isEmmetEnabled && !emmetConfig.excludeLanguages.includes(document.languageId)) return
2527

26-
const result = await sendCommand<EmmetResult>('emmet-completions', { document, position })
27-
if (!result) return
28-
const offset: number = document.offsetAt(position)
29-
const sendToEmmet = document.getText().slice(offset + result.emmetTextOffset, offset)
28+
const cursorOffset: number = document.offsetAt(position)
29+
30+
if (context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions || !lastStartOffset) {
31+
const result = await sendCommand<EmmetResult>('emmet-completions', { document, position })
32+
if (!result) {
33+
lastStartOffset = undefined
34+
return
35+
}
36+
37+
lastStartOffset = cursorOffset + result.emmetTextOffset
38+
}
39+
40+
const sendToEmmet = document.getText().slice(lastStartOffset, cursorOffset)
3041
const emmetCompletions = emmet.doComplete(
3142
{
3243
getText: () => sendToEmmet,
@@ -52,14 +63,16 @@ export const registerEmmet = async () => {
5263
})
5364
return {
5465
items:
55-
improveEmmetCompletions<any>(normalizedCompletions)?.map(({ label, insertText, rangeLength, documentation, sortText }) => ({
56-
label: { label, description: 'EMMET' },
57-
// sortText is overrided if its a number
58-
sortText: Number.isNaN(+sortText) ? '075' : sortText,
59-
insertText: new vscode.SnippetString(insertText),
60-
range: new vscode.Range(position.translate(0, -rangeLength), position),
61-
documentation: documentation as string,
62-
})) ?? [],
66+
improveEmmetCompletions<any>(normalizedCompletions, sendToEmmet)?.map(
67+
({ label, insertText, rangeLength, documentation, sortText }) => ({
68+
label: { label, description: 'EMMET' },
69+
// sortText is overrided if its a number
70+
sortText: Number.isNaN(+sortText) ? '075' : sortText,
71+
insertText: new vscode.SnippetString(insertText),
72+
range: new vscode.Range(position.translate(0, -rangeLength), position),
73+
documentation: documentation as string,
74+
}),
75+
) ?? [],
6376
isIncomplete: true,
6477
}
6578
},
@@ -115,27 +128,34 @@ function getEmmetConfiguration() {
115128
}
116129
}
117130

118-
const improveEmmetCompletions = <T extends Record<'label' | 'insertText' | 'sortText', string>>(items: T[] | undefined) => {
131+
const improveEmmetCompletions = <T extends Record<'label' | 'insertText' | 'sortText', string>>(items: T[] | undefined, sendedText: string) => {
119132
if (!items) return
120133
// TODO-low make to tw= by default when twin.macro is installed?
121134
const dotSnippetOverride = getExtensionSetting('jsxEmmet.dotOverride')
122135
const modernEmmet = getExtensionSetting('jsxEmmet.modernize')
123136

124-
return items.map(item => {
125-
const { label } = item
126-
if (label === '.' && typeof dotSnippetOverride === 'string') item.insertText = dotSnippetOverride
127-
// change sorting to most used
128-
if (['div', 'b'].includes(label)) item.sortText = '070'
129-
if (label.startsWith('btn')) item.sortText = '073'
130-
if (modernEmmet) {
131-
// remove id from input suggestions
132-
if (label === 'inp' || label.startsWith('input:password')) {
133-
item.insertText = item.insertText.replace(/ id="\${\d}"/, '')
134-
}
137+
return compact(
138+
items.map(item => {
139+
const { label } = item
140+
if (label === '.' && typeof dotSnippetOverride === 'string') item.insertText = dotSnippetOverride
141+
// change sorting to most used
142+
if (['div', 'b'].includes(label)) item.sortText = '070'
143+
if (label.startsWith('btn')) item.sortText = '073'
144+
if (modernEmmet) {
145+
// note that it still allows to use Item* pattern
146+
if (sendedText[0] && !sendedText.startsWith(sendedText[0].toLowerCase()) && item.insertText === `<${sendedText}>\${0}</${sendedText}>`) {
147+
return undefined
148+
}
135149

136-
if (label === 'textarea') item.insertText = `<textarea>$1</textarea>`
137-
}
150+
// remove id from input suggestions
151+
if (label === 'inp' || label.startsWith('input:password')) {
152+
item.insertText = item.insertText.replace(/ id="\${\d}"/, '')
153+
}
154+
155+
if (label === 'textarea') item.insertText = `<textarea>$1</textarea>`
156+
}
138157

139-
return item
140-
})
158+
return item
159+
}),
160+
)
141161
}

src/vueVolarSupport.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as vscode from 'vscode'
22
import { watchExtensionSettings } from '@zardoy/vscode-utils/build/settings'
33
import { extensionCtx, getExtensionSetting } from 'vscode-framework'
4+
import { join } from 'path-browserify'
45

56
export default () => {
67
const handler = () => {
@@ -18,9 +19,14 @@ export default () => {
1819

1920
handler()
2021
watchExtensionSettings(['enableVueSupport'], handler)
22+
vscode.extensions.onDidChange(handler)
2123
}
2224

2325
const isConfigValueChanged = (id: string) => {
2426
const config = vscode.workspace.getConfiguration('')
25-
return config.get(id) !== config.inspect(id)!.defaultValue
27+
const userValue = config.get<string>(id)
28+
if (userValue === config.inspect(id)!.defaultValue) return false
29+
// means that value was set by us programmatically, let's update it
30+
if (userValue?.startsWith(join(extensionCtx.extensionPath, '..'))) return false
31+
return true
2632
}

typescript/src/completionEntryDetails.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { oneOf } from '@zardoy/utils'
2-
import { isGoodPositionMethodCompletion } from './completions/isGoodPositionMethodCompletion'
32
import { getParameterListParts } from './completions/snippetForFunctionCall'
3+
import { PrevCompletionsAdditionalData } from './completionsAtPosition'
44
import { GetConfig } from './types'
55

66
export default (
@@ -10,6 +10,7 @@ export default (
1010
position: number,
1111
sourceFile: ts.SourceFile,
1212
prior: ts.CompletionEntryDetails,
13+
{ enableMethodCompletion }: PrevCompletionsAdditionalData,
1314
) => {
1415
if (
1516
c('enableMethodSnippets') &&
@@ -23,15 +24,15 @@ export default (
2324
)
2425
) {
2526
// - 1 to look for possibly previous completing item
26-
let goodPosition = isGoodPositionMethodCompletion(ts, fileName, sourceFile, position - 1, languageService, c)
2727
let rawPartsOverride: ts.SymbolDisplayPart[] | undefined
28-
if (goodPosition && prior.kind === ts.ScriptElementKind.alias) {
29-
goodPosition = prior.displayParts[5]?.text === 'method' || (prior.displayParts[4]?.kind === 'keyword' && prior.displayParts[4].text === 'function')
28+
if (enableMethodCompletion && prior.kind === ts.ScriptElementKind.alias) {
29+
enableMethodCompletion =
30+
prior.displayParts[5]?.text === 'method' || (prior.displayParts[4]?.kind === 'keyword' && prior.displayParts[4].text === 'function')
3031
const { parts, gotMethodHit, hasOptionalParameters } = getParameterListParts(prior.displayParts)
3132
if (gotMethodHit) rawPartsOverride = hasOptionalParameters ? [...parts, { kind: '', text: ' ' }] : parts
3233
}
3334
const punctuationIndex = prior.displayParts.findIndex(({ kind, text }) => kind === 'punctuation' && text === ':')
34-
if (goodPosition && punctuationIndex !== 1) {
35+
if (enableMethodCompletion && punctuationIndex !== 1) {
3536
const isParsableMethod = prior.displayParts
3637
// next is space
3738
.slice(punctuationIndex + 2)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { PrevCompletionMap } from '../completionsAtPosition'
2+
import { GetConfig } from '../types'
3+
import stringDedent from 'string-dedent'
4+
5+
export default (entries: ts.CompletionEntry[], prevCompletionsMap: PrevCompletionMap, c: GetConfig): ts.CompletionEntry[] | undefined => {
6+
if (!c('displayAdditionalInfoInCompletions')) return entries
7+
return entries.map(entry => {
8+
const symbol = entry['symbol'] as ts.Symbol | undefined
9+
if (!symbol) return entry
10+
const addNodeText = (node: ts.Node) => {
11+
let text = node.getText().trim()
12+
if (ts.isBlock(node)) text = text.slice(1, -1)
13+
try {
14+
text = stringDedent(text)
15+
} catch (e) {
16+
// ignore
17+
}
18+
prevCompletionsMap[entry.name] = {
19+
documentationAppend: `\nFunction source:\n\`\`\`ts\n${text}\n\`\`\`\n`,
20+
}
21+
}
22+
let node: ts.Node = symbol.valueDeclaration!
23+
if (!node) return entry
24+
if (ts.isVariableDeclaration(node)) node = node.initializer!
25+
if (!node) return entry
26+
if ((ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) && node.body) {
27+
const { body } = node
28+
if (ts.isBlock(body) && body.statements.length === 1 && ts.isReturnStatement(body.statements[0]!)) {
29+
addNodeText(body.statements[0])
30+
} else {
31+
addNodeText(body)
32+
}
33+
}
34+
return entry
35+
})
36+
}

typescript/src/completionsAtPosition.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ import markOrRemoveGlobalCompletions from './completions/markOrRemoveGlobalLibCo
2323
import { oneOf } from '@zardoy/utils'
2424
import filterWIthIgnoreAutoImports from './completions/ignoreAutoImports'
2525
import escapeStringRegexp from 'escape-string-regexp'
26+
import addSourceDefinition from './completions/addSourceDefinition'
2627

27-
export type PrevCompletionMap = Record<string, { originalName?: string; documentationOverride?: string | ts.SymbolDisplayPart[] }>
28+
export type PrevCompletionMap = Record<string, { originalName?: string; documentationOverride?: string | ts.SymbolDisplayPart[]; documentationAppend?: string }>
29+
export type PrevCompletionsAdditionalData = {
30+
enableMethodCompletion: boolean
31+
}
2832

2933
export const getCompletionsAtPosition = (
3034
fileName: string,
@@ -40,6 +44,7 @@ export const getCompletionsAtPosition = (
4044
completions: ts.CompletionInfo
4145
/** Let default getCompletionEntryDetails to know original name or let add documentation from here */
4246
prevCompletionsMap: PrevCompletionMap
47+
prevCompletionsAdittionalData: PrevCompletionsAdditionalData
4348
}
4449
| undefined => {
4550
const prevCompletionsMap: PrevCompletionMap = {}
@@ -214,6 +219,8 @@ export const getCompletionsAtPosition = (
214219
}
215220
}
216221

222+
prior.entries = addSourceDefinition(prior.entries, prevCompletionsMap, c) ?? prior.entries
223+
217224
if (c('improveJsxCompletions') && leftNode) prior.entries = improveJsxCompletions(prior.entries, leftNode, position, sourceFile, c('jsxCompletionsMap'))
218225

219226
const processedEntries = new Set<ts.CompletionEntry>()
@@ -282,7 +289,8 @@ export const getCompletionsAtPosition = (
282289
}
283290

284291
// prevent vscode-builtin wrong insertText with methods snippets enabled
285-
if (!isGoodPositionBuiltinMethodCompletion(ts, sourceFile, position - 1, c)) {
292+
const goodPositionForMethodCompletions = isGoodPositionBuiltinMethodCompletion(ts, sourceFile, position - 1, c)
293+
if (!goodPositionForMethodCompletions) {
286294
prior.entries = prior.entries.map(item => {
287295
if (item.isSnippet) return item
288296
return { ...item, insertText: (item.insertText ?? item.name).replace(/\$/g, '\\$'), isSnippet: true }
@@ -302,6 +310,9 @@ export const getCompletionsAtPosition = (
302310
return {
303311
completions: prior,
304312
prevCompletionsMap,
313+
prevCompletionsAdittionalData: {
314+
enableMethodCompletion: goodPositionForMethodCompletions,
315+
},
305316
}
306317
}
307318

typescript/src/decorateProxy.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getCompletionsAtPosition, PrevCompletionMap } from './completionsAtPosition'
1+
import { getCompletionsAtPosition, PrevCompletionMap, PrevCompletionsAdditionalData } from './completionsAtPosition'
22
import { TriggerCharacterCommand } from './ipcTypes'
33
import { getNavTreeItems } from './getPatchedNavTree'
44
import decorateCodeActions from './codeActions/decorateProxy'
@@ -37,6 +37,7 @@ export const decorateLanguageService = (
3737
const proxy = getInitialProxy(languageService, existingProxy)
3838

3939
let prevCompletionsMap: PrevCompletionMap
40+
let prevCompletionsAdittionalData: PrevCompletionsAdditionalData
4041
// eslint-disable-next-line complexity
4142
proxy.getCompletionsAtPosition = (fileName, position, options, formatOptions) => {
4243
const updateConfigCommand = 'updateConfig'
@@ -66,14 +67,15 @@ export const decorateLanguageService = (
6667
const result = getCompletionsAtPosition(fileName, position, options, c, languageService, scriptSnapshot, formatOptions, { scriptKind })
6768
if (!result) return
6869
prevCompletionsMap = result.prevCompletionsMap
70+
prevCompletionsAdittionalData = result.prevCompletionsAdittionalData
6971
return result.completions
7072
}
7173

7274
proxy.getCompletionEntryDetails = (fileName, position, entryName, formatOptions, source, preferences, data) => {
7375
const program = languageService.getProgram()
7476
const sourceFile = program?.getSourceFile(fileName)
7577
if (!program || !sourceFile) return
76-
const { documentationOverride } = prevCompletionsMap[entryName] ?? {}
78+
const { documentationOverride, documentationAppend } = prevCompletionsMap[entryName] ?? {}
7779
if (documentationOverride) {
7880
return {
7981
name: entryName,
@@ -92,7 +94,10 @@ export const decorateLanguageService = (
9294
data,
9395
)
9496
if (!prior) return
95-
return completionEntryDetails(languageService, c, fileName, position, sourceFile, prior)
97+
if (documentationAppend) {
98+
prior.documentation = [...(prior.documentation ?? []), { kind: 'text', text: documentationAppend }]
99+
}
100+
return completionEntryDetails(languageService, c, fileName, position, sourceFile, prior, prevCompletionsAdittionalData)
96101
}
97102

98103
decorateCodeActions(proxy, languageService, c)

0 commit comments

Comments
 (0)