UNPKG

common-intellisense

Version:
859 lines (783 loc) 33.1 kB
import fsp from 'node:fs/promises' import * as vscode from 'vscode' import { addEventListener, createCompletionItem, createHover, createMarkdownString, createPosition, createRange, createSelect, getActiveText, getActiveTextEditor, getActiveTextEditorLanguageId, getConfiguration, getCurrentFileUrl, getLineText, getLocale, getPosition, getSelection, insertText, message, openExternalUrl, registerCommand, registerCompletionItemProvider, setConfiguration, setCopyText, updateText } from '@vscode-use/utils' import { CreateWebview } from '@vscode-use/createwebview' import { createFilter } from '@rollup/pluginutils' import { detectSlots, findDynamicComponent, findRefs, getImportDeps, getReactRefsMap, parser, parserVine, registerCodeLensProviderFn, transformVue } from './utils' import { UINames as UINamesMap } from './constants' import type { Directives, PropsConfig, SubCompletionItem } from './ui/utils' import { isVine, isVue, toCamel } from './ui/utils' import { completionsCallbacks, deactivateUICache, eventCallbacks, findUI, getCacheMap, getCurrentPkgUiNames, getOptionsComponents, getUiCompletions, logger } from './ui-find' import { getIsShowSlots, getUiDeps } from './ui-utils' import { cacheFetch, localCacheUri } from './fetch' const defaultExclude = getConfiguration('common-intellisense.exclude') const filterId = createFilter(defaultExclude) const filter = ['javascript', 'javascriptreact', 'typescript', 'typescriptreact', 'vue', 'svelte'] function isSkip() { const id = getActiveTextEditorLanguageId() return !id || !filter.includes(id) } // todo: 补充类型 // todo: 补充example export async function activate(context: vscode.ExtensionContext) { // todo: createWebviewPanel // createWebviewPanel(context) logger.info('common-intellisense activate!') const isZh = getLocale().includes('zh') const LANS = ['javascriptreact', 'typescript', 'typescriptreact', 'vue', 'svelte', 'solid', 'swan', 'react', 'js', 'ts', 'tsx', 'jsx'] if (!isSkip()) findUI(context, detectSlots) const provider = new CreateWebview(context, { viewColumn: vscode.ViewColumn.Beside, scripts: ['main.js'], }) context.subscriptions.push(registerCommand('common-intellisense.cleanCache', () => { fsp.rmdir(localCacheUri) cacheFetch.clear() findUI(context, detectSlots) })) context.subscriptions.push(registerCodeLensProviderFn()) context.subscriptions.push(addEventListener('activeText-change', (editor?: vscode.TextEditor) => { if (!editor) return if (isSkip()) return // 找到当前活动的编辑器 const visibleEditors = vscode.window.visibleTextEditors const currentEditor = visibleEditors.find(e => e === editor) if (currentEditor) findUI(context, detectSlots) })) context.subscriptions.push(registerCommand('intellisense.copyDemo', (demo) => { setCopyText(demo) message.info('copy successfully') })) context.subscriptions.push(registerCommand('common-intellisense.pickUI', () => { const currentPkgUiNames = getCurrentPkgUiNames() if (currentPkgUiNames && currentPkgUiNames.length) { if (currentPkgUiNames.some(i => i.includes('bitsUi'))) { currentPkgUiNames.filter(i => i.startsWith('bitsUi')).map(i => i.replace('bitsUi', 'shadcnSvelte')).forEach((i) => { if (!currentPkgUiNames!.includes(i)) currentPkgUiNames!.push(i) }) } const currentSelect = getConfiguration('common-intellisense.ui') as (string[] | undefined) let options: ({ label: string, picked?: boolean })[] = [] if (currentSelect) { options = currentPkgUiNames.map((label) => { if (currentSelect.includes(label)) { return { label, picked: true, } } else { return { label, } } }) } createSelect(options, { canSelectMany: true, placeHolder: isZh ? '请指定你需要提示的 UI 库' : 'Please specify the UI library you need to prompt.', title: 'common intellisense', }).then((data: string[]) => { setConfiguration('common-intellisense.ui', data) }) } else { message.error(isZh ? '当前项目中并没有安装 common intellisense 支持的 UI 库' : 'There is no UI library supported by common intelligence in the current project.') } })) context.subscriptions.push(addEventListener('config-change', (e) => { if (e.affectsConfiguration('common-intellisense.ui')) findUI(context, detectSlots) })) context.subscriptions.push(registerCommand('common-intellisense.import', (params, loc, _lineOffset) => { if (!params) return const { data, lib, prefix, dynamicLib, importWay } = params const name = data.name.split('.')[0] const fromName = data.from const from = fromName || dynamicLib ? dynamicLib.replace('${name}', name.toLowerCase()) : lib const code = getActiveText()! const uiComponents = getImportUiComponents(code) let deps = data.suggestions?.length === 1 ? data.suggestions.map((i: any) => { if (i.includes('-')) return toCamel(i).slice(prefix.length) return i }) : [] if (uiComponents[lib]) deps.push(...uiComponents[lib].components) else deps.push(name) deps = [...new Set(deps)] if (uiComponents[lib]) { if (deps.includes(name)) return deps.push(name) const offsetStart = code.match(uiComponents[lib].match[0])!.index! const offsetEnd = offsetStart + uiComponents[lib].match[0].length const posStart = getPosition(offsetStart).position const posEnd = getPosition(offsetEnd).position const str = importWay === 'as default' ? `import * as ${deps.join(', ')} from '${from}'` : importWay === 'default' ? `import ${deps.join(', ')} from '${from}'` : `import { ${deps.join(', ')} } from '${from}'` updateText((edit) => { edit.replace(createRange(posStart, posEnd), str) }) } else { // 顶部导入 let str = importWay === 'as default' ? `import * as ${deps.join(', ')} from '${from}'` : importWay === 'default' ? `import ${deps.join(', ')} from '${from}'` : `import { ${deps.join(', ')} } from '${from}'` let pos: any = null if (isVue()) { if (loc) { if (getLineText(loc.start.line)?.trim()) { str += '\n' } pos = createPosition(loc.start.line, 0) } else { const match = code.match(/<script[^>]*>/) if (match) { const offset = match.index! + match[0].length pos = getPosition(offset) str = `\n${str}` } else { pos = createPosition(0, 0) str = `<script setup>\n${str}</script>` } } } else { const match = code.match(/<script[^>]*>/) if (match) { const offset = match.index! + match[0].length pos = getPosition(offset) str = `\n ${str}` } else { str += '\n' pos = createPosition(0, 0) } } updateText((edit) => { edit.insert(pos, str) }) } })) // 监听pkg变化 if (getIsShowSlots()) { context.subscriptions.push(registerCommand('common-intellisense.slots', (child, name, offset) => { const UiCompletions = getUiCompletions() const activeText = getActiveText() if (!activeText) return if (!child && UiCompletions) { const uiDeps = getUiDeps(activeText) const optionsComponents = getOptionsComponents() const componentsPrefix = optionsComponents.prefix detectSlots(UiCompletions, uiDeps, componentsPrefix) return } if (!child.children) return let lastChild = child.children[child.children.findLastIndex((c: any) => c.type !== 2)] let slotName = `#${name}` if (child.range) slotName = `v-slot:${name}` if (lastChild) { if (isVine() && lastChild.codegenNode) { lastChild = lastChild.codegenNode } const pos = lastChild.loc.end const endColumn = Math.max(pos.column - 1, 0) if (isVine()) insertText(`\n<template ${slotName}>$1</template>`, getPosition(pos.offset + offset).position) else insertText(`\n<template ${slotName}>$1</template>`, createPosition(pos.line - 1, endColumn)) // updateText((edit) => { // if (isVine()) // edit.insert(getPosition(pos.offset + offset).position, `\n${empty}<template ${slotName}></template>`) // else // edit.insert(createPosition(pos.line - 1, endColumn), `\n${empty}<template ${slotName}></template>`) // }) } else { const empty = ' '.repeat(Math.max(child.loc.start.column - 1, 0)) if (child.isSelfClosing) { if (isVine()) insertText(`>\n${empty} <template ${slotName}>$1</template>\n</${child.tag}>`, getPosition(child.loc.end.offset + offset - 3).position) else insertText(`>\n${empty} <template ${slotName}>$1</template>\n</${child.tag}>`, createPosition(child.loc.end.line - 1, child.loc.end.column - 3)) // updateText((edit) => { // if (isVine()) // edit.replace(createRange(getPosition(child.loc.end.offset + offset - 3), getPosition(child.loc.end.offset + offset - 1)), `>\n${empty} <template ${slotName}></template>\n${empty}</${child.tag}>`) // else // edit.replace(createRange([child.loc.end.line - 1, child.loc.end.column - 3], [child.loc.end.line - 1, child.loc.end.column - 1]), `>\n${empty} <template ${slotName}></template>\n${empty}</${child.tag}>`) // }) } else { const isNeedLineBlock = child.loc.start.line === child.loc.end.line const index = child.loc.start.offset + child.loc.source.indexOf(`</${child.tag}`) - (isNeedLineBlock ? 0 : (child.loc.end.column - `</${child.tag}>`.length - 1)) const pos = getPosition(index) if (isVine()) insertText(`${isNeedLineBlock ? '\n' : empty} <template ${slotName}>$1</template>\n`, getPosition(index + offset).position) else insertText(`${isNeedLineBlock ? '\n' : empty} <template ${slotName}>$1</template>\n`, createPosition(pos.line, pos.column)) // updateText((edit) => { // if (isVine()) // edit.insert(getPosition(pos.offset + offset).position, `${isNeedLineBlock ? '\n' : ''}${empty} <template ${slotName}></template>\n${isNeedLineBlock ? empty : ''}`) // else // edit.insert(createPosition(pos), `${isNeedLineBlock ? '\n' : ''}${empty} <template ${slotName}></template>\n${isNeedLineBlock ? empty : ''}`) // }) } } })) context.subscriptions.push(addEventListener('text-change', ({ contentChanges, document }) => { if (contentChanges.length === 0 || document.languageId === 'Log') return const UiCompletions = getUiCompletions() const optionsComponents = getOptionsComponents() const componentsPrefix = optionsComponents.prefix if (isSkip()) return const activeText = getActiveText() if (UiCompletions && activeText) { const uiDeps = getUiDeps(activeText) detectSlots(UiCompletions, uiDeps, componentsPrefix) } })) } context.subscriptions.push(registerCompletionItemProvider(filter, async (document, position) => { const optionsComponents = getOptionsComponents() const componentsPrefix = optionsComponents.prefix let UiCompletions = getUiCompletions() if (!UiCompletions) return const { lineText } = getSelection()! const p = position const activeTextEditor = getActiveTextEditor() if (!activeTextEditor) return if (isSkip()) return const preText = lineText.slice(0, activeTextEditor.selection.active.character) let completionsCallback: SubCompletionItem[] | undefined const activeText = getEffectWord(preText) const result = parser(document.getText(), p) if (!result) return if (activeText === ':' && result.type === 'text') return const lan = getActiveTextEditorLanguageId() const isVue = (lan === 'vue' && result.template) || isVine() const code = getActiveText() if (!code) return const deps = isVue ? getImportDeps(code) : {} const uiDeps = getUiDeps(code) const { character } = position const isPreEmpty = lineText[character - 1] === ' ' const isValue = result.isValue if (result.type === 'script' && Object.keys(result.refsMap || {}).length && !isPreEmpty) { if (lineText?.slice(-1)[0] === '.') { for (const key in result.refsMap) { const value = result.refsMap[key] if (isVue && (lineText.endsWith(`.$refs.${key}.`) || lineText.endsWith(`${key}.value.`)) && UiCompletions[value]) return [...UiCompletions[value].methods, ...UiCompletions[value].exposed] else if (!isVue && lineText.endsWith(`${key}.current.`) && UiCompletions[value]) return [...UiCompletions[value].methods, ...UiCompletions[value].exposed] } } if (isVue && lineText.slice(character, character + 6) !== '.value' && !/\.value\.?$/.test(lineText.slice(0, character))) return result.refs.map((refName: string) => createCompletionItem({ content: refName, snippet: `${refName}.value`, documentation: `${refName}.value`, preselect: true, sortText: 'a' })) if (!isVue && lineText.slice(character, character + 8) !== '.current' && !/\.current\.?$/.test(lineText.slice(0, character))) return result.refs.map((refName: string) => createCompletionItem({ content: refName, snippet: `${refName}.current`, documentation: `${refName}.current`, preselect: true, sortText: 'a' })) } if (result.parent && result.tag === 'template') { const parentTag = result.parent.tag || result.parent.name if (parentTag) { const name = toCamel(parentTag) const component = UiCompletions[name[0].toUpperCase() + name.slice(1)] const slots = component?.slots if (slots) return slots } } if (UiCompletions && result?.type === 'props') { const name = result.tag[0].toUpperCase() + result.tag.replace(/(-\w)/g, (match: string) => match[1].toUpperCase()).slice(1) if (result.propName === 'icon') return UiCompletions.icons const propName = result.propName const from = uiDeps?.[name] const cacheMap = getCacheMap() if (from && cacheMap.size > 2) { // 存在多个 UI 库 const nameReg = new RegExp(`${toCamel(from)}\\d+$`) const keys = Array.from(cacheMap.keys()) const targetKey = keys.find(k => nameReg.test(k))! const targetValue = cacheMap.get(targetKey)! as PropsConfig UiCompletions = targetValue } let target = await findDynamicComponent(name, deps, UiCompletions, componentsPrefix, from) const importUiSource = uiDeps?.[name] if (importUiSource && (!target || target.uiName !== importUiSource)) { for (const p of optionsComponents.prefix.filter(Boolean)) { const realName = p[0].toUpperCase() + p.slice(1) + name const newTarget = UiCompletions[realName] if (!newTarget) continue if (newTarget.uiName === importUiSource) { target = newTarget break } } } if (!target) return const { events, completions, uiName } = target const key = uiName + name if (!completionsCallbacks.has(key)) { const directives = optionsComponents.directivesMap[uiName] const directivesCompletions = directives ? directives.map((item: Directives[0]) => { const detail = isZh ? item.description_zh : item.description const content = `${item.name} ${detail}` const documentation = createMarkdownString() if (item.documentation) documentation.appendMarkdown(item.documentation) else if (item.documentationType) documentation.appendCodeblock(item.documentationType, 'typescript') if (item.params?.length) { documentation.appendCodeblock('\n') item.params.forEach((i) => { documentation.appendMarkdown(`### 🌟 ${i.name}: \n`) documentation.appendMarkdown(`- ${isZh ? '类型' : 'type'}: ${i.type}\n`) documentation.appendMarkdown(`- ${isZh ? '描述' : 'description'}: ${isZh ? i.description_zh : i.description}\n`) documentation.appendMarkdown(`- ${isZh ? '默认值' : 'default'}: ${i.default}\n`) }) } const snippet = item.params?.length ? `:${item.name}="${JSON.stringify(item.params.reduce((acc, i) => { const key = i.name const type = i.type.toLocaleLowerCase() const value = i.default || type === 'boolean' ? false : type === 'number' ? 0 : type === 'string' ? '' : '' acc[key] = value return acc }, {} as Record<string, any>), null, 2).replace(/"([^"]+)":/g, '$1:').replace(/"/g, '`')}"` : item.name return createCompletionItem({ content, detail, sortText: 'a', type: vscode.CompletionItemKind.Enum, snippet, params: [uiName, item.name], preselect: true, documentation, }) }) : [] const _events = events[0](isVue) eventCallbacks.set(key, _events) completionsCallbacks.set(key, [...completions[0](isVue), ...(isVue ? [] : _events), ...directivesCompletions]) } if (!eventCallbacks.has(key)) eventCallbacks.set(key, events[0](isVue)) completionsCallback = completionsCallbacks.get(key) const hasProps = result.props ? result.props.map((item: any) => { if (item.name === 'on' && item.arg) return `${item.arg.content}` if (typeof item.name === 'object' && item.name.name !== 'on') return item.name.name if (item.name === 'model' && item?.loc?.source?.startsWith('v-model')) return item.loc.source.split('=')[0] if (item.name === 'bind') return item?.arg?.content if (item.name !== 'on') return item.name return false }).filter(Boolean) : [] if (propName === 'on') { return (eventCallbacks.get(key) || []).filter((item: any) => !hasProps.find((prop: any) => item?.params?.[1] === prop)) } else if (propName) { const r: any[] = [] if (isValue) { (completionsCallback ?? []).filter((item: any) => hasProps.find((prop: any) => item?.params?.[1] === prop)) .filter((item: any) => { const reg = propName === 'bind' ? new RegExp('^:') : new RegExp(`^:?${propName}`) return reg.test(item.label) }).forEach((item: any) => { item.propType?.split('/').forEach((p: string) => { r.push(createCompletionItem({ content: p.trim(), snippet: p.trim().replace(/'`/g, ''), documentation: item.documentation, sortText: 'a', detail: item.detail, type: item.kind, })) }) }) } else { r.push(...(completionsCallback ?? []).filter((item: any) => !hasProps.find((prop: any) => item?.params?.[1] === prop)) .map((item: any) => createCompletionItem(({ content: item.content, snippet: item.snippet, documentation: item.documentation, detail: item.detail, sortText: 'a', preselect: true, type: item.kind, })))) } const events = isVue ? [] : isValue ? [] : (eventCallbacks.get(key) || []).filter((item: any) => !hasProps.find((prop: any) => item?.params?.[1] === prop)) if (propName === 'o') return [...events, ...r] return [...r, ...events] } else if (hasProps.length) { return (completionsCallback ?? []).filter((item: any) => !hasProps.find((prop: any) => item.params?.[1] === prop)) } else { return completionsCallback } } else if (!result.isInTemplate || isPreEmpty || !optionsComponents) { return } const prefix = lineText.trim().split(' ').slice(-1)[0] if (prefix.toLowerCase() === prefix ? optionsComponents.prefix.some((reg: string) => prefix.startsWith(reg) || reg.startsWith(prefix)) : true) { const parent = result.parent const data = optionsComponents.data.map(c => c()).flat() if (parent) { const parentTag = parent.tag || parent.name if (UiCompletions) { const suggestions = UiCompletions[toCamel(parentTag)[0].toUpperCase() + toCamel(parentTag).slice(1)]?.suggestions if (suggestions && suggestions.length) { data.forEach((child) => { const label = typeof child.label === 'string' ? child.label.split(' ')[0] : child.label.label.split(' ')[0] child.sortText = suggestions.includes(label) ? '1' : '2'; (child as any).loc = result.loc }) } else { data.forEach((child: any) => { child.sortText = '2' child.loc = result.loc }) } } } return data } }, (item: SubCompletionItem) => { if (!item.command) { if (item.params?.isReact) { item.command = { title: 'common-intellisense-import', command: 'common-intellisense.import', arguments: [item.params, item.loc, (item.snippet || item.content).split('\n').length - 1], } } else { item.command = { title: 'common-intellisense.slots', command: 'common-intellisense.slots', arguments: [], } } } return item }, ['"', '\'', '-', ' ', '@', '.', ':'])) context.subscriptions.push(registerCommand('intellisense.openDocument', (args) => { // 注册全局的 link 点击事件 const url = args.link if (!url) return provider.create(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Webview</title> <style> body{ width:100%; height:100vh; } </style> </head> <body> <iframe src="${url}" width="100%" height="100%"></iframe> </body> </html> `, ({ data, type }) => { // callback 获取 js 层的 postMessage 数据 if (type === 'copy') { setCopyText(data).then(() => { const isZh = getLocale().includes('zh') message.info(`${isZh ? '复制成功' : 'copy successfully'}! ✅`) }) } }) })) context.subscriptions.push(registerCommand('intellisense.openDocumentExternal', (args) => { // 注册全局的 link 点击事件 const url = args.link if (!url) return openExternalUrl(url) })) context.subscriptions.push(vscode.languages.registerHoverProvider(LANS, { async provideHover(document, position) { const optionsComponents = getOptionsComponents() const componentsPrefix = optionsComponents.prefix let UiCompletions = getUiCompletions() if (!optionsComponents || !UiCompletions) return const editor = getActiveTextEditor() if (!editor) return const currentFileUrl = getCurrentFileUrl() if (!currentFileUrl) return if (filterId(currentFileUrl)) return const range = document.getWordRangeAtPosition(position) if (!range) return let word = document.getText(range) const lineText = getLineText(position.line) if (!lineText) return const code = document.getText() const uiDeps = getUiDeps(code) // word 修正 if (lineText[range.end.character] === '.' || lineText[range.end.character] === '-') { let index = range.end.character while (!/[>\s/]/.test(lineText[index]) && index < lineText.length) { word += lineText[index] index++ } } if (lineText[range.start.character - 1] === '.') { let index = range.start.character - 1 while (!/[<\s/]/.test(lineText[index]) && index >= 0) { word = lineText[index] + word index-- } } else if (lineText[range.start.character - 1] !== '<') { const result = parser(code, position as any) if (!result) return if (result.type === 'tag') { const data = optionsComponents.data.map(c => c()).flat() if (!data?.length || !word) return createHover('') const tag = toCamel(result.tag)[0].toUpperCase() + toCamel(result.tag).slice(1) const target = await findDynamicComponent(tag, {}, UiCompletions, componentsPrefix, uiDeps?.[tag]) if (!target) return const tableDocument = target.tableDocument if (tableDocument) return createHover(tableDocument) } else if (!result.propName) { return } const propName = result.propName === 'bind' ? result.props[0].arg.content : result.propName if (['class', 'className', 'style', 'id'].includes(propName)) return const tag = toCamel(result.tag)[0].toUpperCase() + toCamel(result.tag).slice(1) const r = UiCompletions[tag] || await findDynamicComponent(tag, {}, UiCompletions, componentsPrefix) if (!r) return const completions = result.isEvent ? r.events[0]?.() : r.completions[0]?.() if (!completions) return const detail = getHoverAttribute(completions, propName) if (!detail) return return createHover(`## Details \n\n${detail}`) } // todo: 优化这里的条件,在 react 中, 也可以减少更多的处理步骤 if (isVue()) { const r = transformVue(code, position) if (r) { if (!r.template) return if (word.includes('.value.') && r.type === 'script' && r.refs.length) { const refsMap = findRefs(r.template, r.refs) const index = word.indexOf('.value.') const key = word.slice(0, index) const refName = refsMap[key] if (!refName) return if (lineText.slice(range.start.character, range.end.character) === 'value') { // hover .value.区域 提示所有方法 const groupMd = createMarkdownString() ;[...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].forEach((m, i) => { let content = typeof m.documentation === 'string' ? m.documentation : m.documentation?.value || '' if (i !== 0) { content = content.replace(/##[^\]\n]*[\]\n]/, '') } groupMd.appendMarkdown(content) groupMd.appendMarkdown('\n') }) return createHover(groupMd) } const targetKey = word.slice(index + '.value.'.length) // FIXME: label可能是对象,string | vscode.CompletionItemLabel const target = [...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].find(item => item.label === targetKey) if (!target) return return target.hover } if (r.type === 'script') return } } else if (isVine()) { const r = parserVine(code, position) if (r) { if (word.includes('.value.') && r.type === 'script' && Object.keys(r.refsMap || {}).length) { const index = word.indexOf('.value.') const key = word.slice(0, index) const refName = r.refsMap[key] if (!refName) return if (lineText.slice(range.start.character, range.end.character) === 'value') { // hover .value.区域 提示所有方法 const groupMd = createMarkdownString() ;[...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].forEach((m: any, i: number) => { let content = m.documentation.value if (content && i !== 0) { content = content.replace(/##[^\]\n]*[\]\n]/, '') } groupMd.appendMarkdown(content) groupMd.appendMarkdown('\n') }) return createHover(groupMd) } const targetKey = word.slice(index + '.value.'.length) const target = [...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].find((item: any) => item.label === targetKey) if (!target) return return target.hover } if (r.type === 'script') return } } else if (getActiveTextEditorLanguageId()?.includes('react')) { if (word.includes('.current.')) { const r = getReactRefsMap() const index = word.indexOf('.current.') const key = word.slice(0, index) const refName = r.refsMap[key] if (!refName) return if (lineText.slice(range.start.character, range.end.character) === 'current') { // hover .value.区域 提示所有方法 const groupMd = createMarkdownString() ;[...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].forEach((m, i) => { let content = typeof m.documentation === 'string' ? m.documentation : m.documentation?.value || '' if (i !== 0) { content = content.replace(/##[^\]\n]*[\]\n]/, '') } groupMd.appendMarkdown(content) groupMd.appendMarkdown('\n') }) return createHover(groupMd) } const targetKey = word.slice(index + '.current.'.length) const target = [...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].find(item => item.label === targetKey) if (!target) return return target.hover } } const data = optionsComponents.data.map(c => c()).flat() if (!data?.length || !word) return createHover('') word = toCamel(word)[0].toUpperCase() + toCamel(word).slice(1) const from = uiDeps?.[word] const cacheMap = getCacheMap() if (from && cacheMap.size > 2) { // 存在多个 UI 库 const nameReg = new RegExp(`${toCamel(from)}\\d+$`) const keys = Array.from(cacheMap.keys()) const targetKey = keys.find(k => nameReg.test(k))! const targetValue = cacheMap.get(targetKey)! as PropsConfig UiCompletions = targetValue } const target = await findDynamicComponent(word, {}, UiCompletions, optionsComponents.prefix, uiDeps?.[word]) if (!target) return const tableDocument = target.tableDocument if (tableDocument) return createHover(tableDocument) }, })) } export function deactivate() { deactivateUICache() } function getEffectWord(preText: string) { let i = preText.length - 1 let active = '' while (preText[i] && (preText[i] !== ' ')) { active = `${preText[i]}${active}` i-- } return active } function getHoverAttribute(attributeList: any[], attr: string) { return attributeList.filter(a => toCamel(a?.params?.[1]?.replace('v-model:', '') || '') === toCamel(attr), ).map(i => `- ${i.details}`).join('\n\n') } const IMPORT_UI_REG = /import\s+\{([^}]+)\}\s+from\s+['"]([^"']+)['"]/g function getImportUiComponents(text: string) { // 读取需要按需导入的ui库, 例如 antd, 拿出导入的 components const deps: Record<string, any> = {} for (const match of text.matchAll(IMPORT_UI_REG)) { if (!match) continue const from = match[2] if (UINamesMap.includes(from)) { deps[from] = { match, components: match[1].split(',').map(i => i.trim()), } } } return deps }