Skip to content

Commit

Permalink
Merge pull request #84 from zardoy/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
zardoy authored Jan 24, 2023
2 parents 5b9fe6b + 6332af9 commit 45e31b1
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 56 deletions.
11 changes: 10 additions & 1 deletion src/configurationType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export type Configuration = {
* We already change sorting of suggestions, but enabling this option will also make:
* - removing `id` from input suggestions
* - simplify textarea
* - removes uppercase suggestions e.g. `Foo` (write React component name after `<` for proper completions)
* Doesn't change preview text for now!
* @default false
*/
Expand Down Expand Up @@ -277,7 +278,8 @@ export type Configuration = {
removeImportsFromReferences: boolean
/**
* Small definition improvements by cleaning them out:
* - remove node_modules definition on React.FC component click
* - remove node_modules type definition on React.FC components (e.g. <Foo />)
* - remove classes index definition on css modules (https://github.com/clinyong/vscode-css-modules/issues/63#issuecomment-1372851831)
* @default true
*/
miscDefinitionImprovement: boolean
Expand Down Expand Up @@ -434,4 +436,11 @@ export type Configuration = {
* @default []
*/
'autoImport.alwaysIgnoreInImportAll': string[]
/**
* Enable to display additional information about source declaration in completion's documentation
* For now only displays function's body
* Requires symbol patching
* @default false
*/
displayAdditionalInfoInCompletions: boolean
}
76 changes: 48 additions & 28 deletions src/emmet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as vscode from 'vscode'
import { compact } from '@zardoy/utils'
import { getExtensionSetting, registerExtensionCommand } from 'vscode-framework'
import { EmmetResult } from '../typescript/src/ipcTypes'
import { sendCommand } from './sendCommand'
Expand All @@ -15,6 +16,7 @@ export const registerEmmet = async () => {

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

const result = await sendCommand<EmmetResult>('emmet-completions', { document, position })
if (!result) return
const offset: number = document.offsetAt(position)
const sendToEmmet = document.getText().slice(offset + result.emmetTextOffset, offset)
const cursorOffset: number = document.offsetAt(position)

if (context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions || !lastStartOffset) {
const result = await sendCommand<EmmetResult>('emmet-completions', { document, position })
if (!result) {
lastStartOffset = undefined
return
}

lastStartOffset = cursorOffset + result.emmetTextOffset
}

const sendToEmmet = document.getText().slice(lastStartOffset, cursorOffset)
const emmetCompletions = emmet.doComplete(
{
getText: () => sendToEmmet,
Expand All @@ -52,14 +63,16 @@ export const registerEmmet = async () => {
})
return {
items:
improveEmmetCompletions<any>(normalizedCompletions)?.map(({ label, insertText, rangeLength, documentation, sortText }) => ({
label: { label, description: 'EMMET' },
// sortText is overrided if its a number
sortText: Number.isNaN(+sortText) ? '075' : sortText,
insertText: new vscode.SnippetString(insertText),
range: new vscode.Range(position.translate(0, -rangeLength), position),
documentation: documentation as string,
})) ?? [],
improveEmmetCompletions<any>(normalizedCompletions, sendToEmmet)?.map(
({ label, insertText, rangeLength, documentation, sortText }) => ({
label: { label, description: 'EMMET' },
// sortText is overrided if its a number
sortText: Number.isNaN(+sortText) ? '075' : sortText,
insertText: new vscode.SnippetString(insertText),
range: new vscode.Range(position.translate(0, -rangeLength), position),
documentation: documentation as string,
}),
) ?? [],
isIncomplete: true,
}
},
Expand Down Expand Up @@ -115,27 +128,34 @@ function getEmmetConfiguration() {
}
}

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

return items.map(item => {
const { label } = item
if (label === '.' && typeof dotSnippetOverride === 'string') item.insertText = dotSnippetOverride
// change sorting to most used
if (['div', 'b'].includes(label)) item.sortText = '070'
if (label.startsWith('btn')) item.sortText = '073'
if (modernEmmet) {
// remove id from input suggestions
if (label === 'inp' || label.startsWith('input:password')) {
item.insertText = item.insertText.replace(/ id="\${\d}"/, '')
}
return compact(
items.map(item => {
const { label } = item
if (label === '.' && typeof dotSnippetOverride === 'string') item.insertText = dotSnippetOverride
// change sorting to most used
if (['div', 'b'].includes(label)) item.sortText = '070'
if (label.startsWith('btn')) item.sortText = '073'
if (modernEmmet) {
// note that it still allows to use Item* pattern
if (sendedText[0] && !sendedText.startsWith(sendedText[0].toLowerCase()) && item.insertText === `<${sendedText}>\${0}</${sendedText}>`) {
return undefined
}

if (label === 'textarea') item.insertText = `<textarea>$1</textarea>`
}
// remove id from input suggestions
if (label === 'inp' || label.startsWith('input:password')) {
item.insertText = item.insertText.replace(/ id="\${\d}"/, '')
}

if (label === 'textarea') item.insertText = `<textarea>$1</textarea>`
}

return item
})
return item
}),
)
}
8 changes: 7 additions & 1 deletion src/vueVolarSupport.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from 'vscode'
import { watchExtensionSettings } from '@zardoy/vscode-utils/build/settings'
import { extensionCtx, getExtensionSetting } from 'vscode-framework'
import { join } from 'path-browserify'

export default () => {
const handler = () => {
Expand All @@ -18,9 +19,14 @@ export default () => {

handler()
watchExtensionSettings(['enableVueSupport'], handler)
vscode.extensions.onDidChange(handler)
}

const isConfigValueChanged = (id: string) => {
const config = vscode.workspace.getConfiguration('')
return config.get(id) !== config.inspect(id)!.defaultValue
const userValue = config.get<string>(id)
if (userValue === config.inspect(id)!.defaultValue) return false
// means that value was set by us programmatically, let's update it
if (userValue?.startsWith(join(extensionCtx.extensionPath, '..'))) return false
return true
}
11 changes: 6 additions & 5 deletions typescript/src/completionEntryDetails.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { oneOf } from '@zardoy/utils'
import { isGoodPositionMethodCompletion } from './completions/isGoodPositionMethodCompletion'
import { getParameterListParts } from './completions/snippetForFunctionCall'
import { PrevCompletionsAdditionalData } from './completionsAtPosition'
import { GetConfig } from './types'

export default (
Expand All @@ -10,6 +10,7 @@ export default (
position: number,
sourceFile: ts.SourceFile,
prior: ts.CompletionEntryDetails,
{ enableMethodCompletion }: PrevCompletionsAdditionalData,
) => {
if (
c('enableMethodSnippets') &&
Expand All @@ -23,15 +24,15 @@ export default (
)
) {
// - 1 to look for possibly previous completing item
let goodPosition = isGoodPositionMethodCompletion(ts, fileName, sourceFile, position - 1, languageService, c)
let rawPartsOverride: ts.SymbolDisplayPart[] | undefined
if (goodPosition && prior.kind === ts.ScriptElementKind.alias) {
goodPosition = prior.displayParts[5]?.text === 'method' || (prior.displayParts[4]?.kind === 'keyword' && prior.displayParts[4].text === 'function')
if (enableMethodCompletion && prior.kind === ts.ScriptElementKind.alias) {
enableMethodCompletion =
prior.displayParts[5]?.text === 'method' || (prior.displayParts[4]?.kind === 'keyword' && prior.displayParts[4].text === 'function')
const { parts, gotMethodHit, hasOptionalParameters } = getParameterListParts(prior.displayParts)
if (gotMethodHit) rawPartsOverride = hasOptionalParameters ? [...parts, { kind: '', text: ' ' }] : parts
}
const punctuationIndex = prior.displayParts.findIndex(({ kind, text }) => kind === 'punctuation' && text === ':')
if (goodPosition && punctuationIndex !== 1) {
if (enableMethodCompletion && punctuationIndex !== 1) {
const isParsableMethod = prior.displayParts
// next is space
.slice(punctuationIndex + 2)
Expand Down
36 changes: 36 additions & 0 deletions typescript/src/completions/addSourceDefinition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { PrevCompletionMap } from '../completionsAtPosition'
import { GetConfig } from '../types'
import stringDedent from 'string-dedent'

export default (entries: ts.CompletionEntry[], prevCompletionsMap: PrevCompletionMap, c: GetConfig): ts.CompletionEntry[] | undefined => {
if (!c('displayAdditionalInfoInCompletions')) return entries
return entries.map(entry => {
const symbol = entry['symbol'] as ts.Symbol | undefined
if (!symbol) return entry
const addNodeText = (node: ts.Node) => {
let text = node.getText().trim()
if (ts.isBlock(node)) text = text.slice(1, -1)
try {
text = stringDedent(text)
} catch (e) {
// ignore
}
prevCompletionsMap[entry.name] = {
documentationAppend: `\nFunction source:\n\`\`\`ts\n${text}\n\`\`\`\n`,
}
}
let node: ts.Node = symbol.valueDeclaration!
if (!node) return entry
if (ts.isVariableDeclaration(node)) node = node.initializer!
if (!node) return entry
if ((ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) && node.body) {
const { body } = node
if (ts.isBlock(body) && body.statements.length === 1 && ts.isReturnStatement(body.statements[0]!)) {
addNodeText(body.statements[0])
} else {
addNodeText(body)
}
}
return entry
})
}
15 changes: 13 additions & 2 deletions typescript/src/completionsAtPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ import markOrRemoveGlobalCompletions from './completions/markOrRemoveGlobalLibCo
import { oneOf } from '@zardoy/utils'
import filterWIthIgnoreAutoImports from './completions/ignoreAutoImports'
import escapeStringRegexp from 'escape-string-regexp'
import addSourceDefinition from './completions/addSourceDefinition'

export type PrevCompletionMap = Record<string, { originalName?: string; documentationOverride?: string | ts.SymbolDisplayPart[] }>
export type PrevCompletionMap = Record<string, { originalName?: string; documentationOverride?: string | ts.SymbolDisplayPart[]; documentationAppend?: string }>
export type PrevCompletionsAdditionalData = {
enableMethodCompletion: boolean
}

export const getCompletionsAtPosition = (
fileName: string,
Expand All @@ -40,6 +44,7 @@ export const getCompletionsAtPosition = (
completions: ts.CompletionInfo
/** Let default getCompletionEntryDetails to know original name or let add documentation from here */
prevCompletionsMap: PrevCompletionMap
prevCompletionsAdittionalData: PrevCompletionsAdditionalData
}
| undefined => {
const prevCompletionsMap: PrevCompletionMap = {}
Expand Down Expand Up @@ -214,6 +219,8 @@ export const getCompletionsAtPosition = (
}
}

prior.entries = addSourceDefinition(prior.entries, prevCompletionsMap, c) ?? prior.entries

if (c('improveJsxCompletions') && leftNode) prior.entries = improveJsxCompletions(prior.entries, leftNode, position, sourceFile, c('jsxCompletionsMap'))

const processedEntries = new Set<ts.CompletionEntry>()
Expand Down Expand Up @@ -282,7 +289,8 @@ export const getCompletionsAtPosition = (
}

// prevent vscode-builtin wrong insertText with methods snippets enabled
if (!isGoodPositionBuiltinMethodCompletion(ts, sourceFile, position - 1, c)) {
const goodPositionForMethodCompletions = isGoodPositionBuiltinMethodCompletion(ts, sourceFile, position - 1, c)
if (!goodPositionForMethodCompletions) {
prior.entries = prior.entries.map(item => {
if (item.isSnippet) return item
return { ...item, insertText: (item.insertText ?? item.name).replace(/\$/g, '\\$'), isSnippet: true }
Expand All @@ -302,6 +310,9 @@ export const getCompletionsAtPosition = (
return {
completions: prior,
prevCompletionsMap,
prevCompletionsAdittionalData: {
enableMethodCompletion: goodPositionForMethodCompletions,
},
}
}

Expand Down
11 changes: 8 additions & 3 deletions typescript/src/decorateProxy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getCompletionsAtPosition, PrevCompletionMap } from './completionsAtPosition'
import { getCompletionsAtPosition, PrevCompletionMap, PrevCompletionsAdditionalData } from './completionsAtPosition'
import { TriggerCharacterCommand } from './ipcTypes'
import { getNavTreeItems } from './getPatchedNavTree'
import decorateCodeActions from './codeActions/decorateProxy'
Expand Down Expand Up @@ -37,6 +37,7 @@ export const decorateLanguageService = (
const proxy = getInitialProxy(languageService, existingProxy)

let prevCompletionsMap: PrevCompletionMap
let prevCompletionsAdittionalData: PrevCompletionsAdditionalData
// eslint-disable-next-line complexity
proxy.getCompletionsAtPosition = (fileName, position, options, formatOptions) => {
const updateConfigCommand = 'updateConfig'
Expand Down Expand Up @@ -66,14 +67,15 @@ export const decorateLanguageService = (
const result = getCompletionsAtPosition(fileName, position, options, c, languageService, scriptSnapshot, formatOptions, { scriptKind })
if (!result) return
prevCompletionsMap = result.prevCompletionsMap
prevCompletionsAdittionalData = result.prevCompletionsAdittionalData
return result.completions
}

proxy.getCompletionEntryDetails = (fileName, position, entryName, formatOptions, source, preferences, data) => {
const program = languageService.getProgram()
const sourceFile = program?.getSourceFile(fileName)
if (!program || !sourceFile) return
const { documentationOverride } = prevCompletionsMap[entryName] ?? {}
const { documentationOverride, documentationAppend } = prevCompletionsMap[entryName] ?? {}
if (documentationOverride) {
return {
name: entryName,
Expand All @@ -92,7 +94,10 @@ export const decorateLanguageService = (
data,
)
if (!prior) return
return completionEntryDetails(languageService, c, fileName, position, sourceFile, prior)
if (documentationAppend) {
prior.documentation = [...(prior.documentation ?? []), { kind: 'text', text: documentationAppend }]
}
return completionEntryDetails(languageService, c, fileName, position, sourceFile, prior, prevCompletionsAdittionalData)
}

decorateCodeActions(proxy, languageService, c)
Expand Down
Loading

0 comments on commit 45e31b1

Please sign in to comment.