UNPKG

common-intellisense

Version:
898 lines (816 loc) 39.3 kB
import * as vscode from 'vscode' import type { CompletionItem } from 'vscode' import { createCompletionItem, createHover, createMarkdownString, getActiveTextEditorLanguageId, getCurrentFileUrl, getLocale, setCommandParams } from '@vscode-use/utils' import type { CompletionItemOptions } from '@vscode-use/utils' import type { Component, Slots, SuggestionItem } from './ui-type' export interface PropsOptions { uiName: string lib: string map: Component[] extensionContext?: vscode.ExtensionContext prefix?: string } export type IconsItem = any export type Icons = IconsItem[] export type SubCompletionItem = CompletionItem & { content: string params?: FixParams hover?: vscode.Hover loc?: vscode.Range snippet?: string details?: string propType?: string } export interface PropsConfigItem { completions: ((isVue?: boolean) => SubCompletionItem[])[] events: ((isVue?: boolean) => SubCompletionItem[])[] methods: SubCompletionItem[] exposed: SubCompletionItem[] slots: SubCompletionItem[] suggestions: (string | SuggestionItem)[] tableDocument: vscode.MarkdownString rawSlots?: Slots uiName: string lib: string } export interface FixParams { data: Component lib: string isReact: boolean prefix: string dynamicLib: string importWay: string } export type PropsConfig = Record<string, PropsConfigItem> & { icons?: Icons } export function proxyCreateCompletionItem(options: CompletionItemOptions & { params?: string | string[] }): SubCompletionItem { return createCompletionItem(options) } export function propsReducer(options: PropsOptions) { const { uiName, lib, map, prefix = '' } = options const result: PropsConfig = {} // 不再支持 icon, 或者考虑将 icon 生成字体图标,产生预览效果 // let icons // if (iconData) { // const prefix = iconData.prefix // icons = iconData.icons.map((icon) => { // const imagePath = vscode.Uri.file(extensionContext.asAbsolutePath(`images/${iconData.type}/${icon}.svg`)) // const documentation = new vscode.MarkdownString(`![img](${imagePath})`) // const snippet = `${prefix}-${icon}` // return createCompletionItem({ content: icon, type: 19, documentation, snippet, params: [uiName] }) // }) // result.icons = icons // } return map.reduce((result, item: Component) => { const completions: ((isVue?: boolean) => SubCompletionItem[])[] = [] const events: ((isVue?: boolean) => SubCompletionItem[])[] = [] const methods: SubCompletionItem[] = [] const exposed: SubCompletionItem[] = [] const slots: SubCompletionItem[] = [] const isZh = getLocale().includes('zh') const completionsDeferCallback = (isVue?: boolean) => { const data: SubCompletionItem[] = [ 'id', isVue ? 'class' : 'className', ].map(item => proxyCreateCompletionItem({ content: item, snippet: `${item}="\${1:}"`, type: 5, params: [] })) if (isVue) data.push(proxyCreateCompletionItem({ content: 'style', snippet: 'style="$1"', type: 5, params: [] })) else data.push(proxyCreateCompletionItem({ content: 'style', snippet: 'style={$1}', type: 5, params: [] })) Object.keys(item.props!).forEach((key) => { const value = (item.props as any)[key] let type = vscode.CompletionItemKind.Property if (typeof value.value !== 'string') type = vscode.CompletionItemKind.Enum const documentation = new vscode.MarkdownString() documentation.isTrusted = true documentation.supportHtml = true const detail = [] detail.push(`## ${uiName} [${item.name}]`) if (value.default !== undefined && value.default !== '') { value.default = String(value.default) detail.push(`#### 💎 ${isZh ? '默认值' : 'default'}: ***\`${value.default.replace(/[`\n]/g, '')}\`***`) } if (value.version) { if (isZh) detail.push(`#### 🚀 版本: ***\`${value.version}\`***`) else detail.push(`#### 🚀 version: ***\`${value.version}\`***`) } if (value.description) { if (isZh) detail.push(`#### 🔦 说明: ***\`${value.description_zh || value.description}\`***`) else detail.push(`#### 🔦 description: ***\`${value.description}\`***`) } if (value.type) detail.push(`#### 💡 ${isZh ? '类型' : 'type'}: ***\`${value.type.replace(/`/g, '')}\`***`) documentation.appendMarkdown(detail.join('\n\n')) if (item.typeDetail && Object.keys(item.typeDetail).length) { const data = `🌈 类型详情:\n${Object.keys(item.typeDetail).reduce((result, key) => { if (Array.isArray(item.typeDetail![key])) { return result += key[0] === '$' ? `\ntype ${key.slice(1).replace(/-(\w)/g, v => v.toUpperCase())} = \n${item.typeDetail![key].map((typeItem: any) => `${typeItem.name} /*${typeItem.description}*/`).join('\n| ')}\n\n` : `\ninterface ${key} {\n ${item.typeDetail![key].map((typeItem: any) => `${typeItem.name}${typeItem.optional ? '?' : ''}: ${typeItem.type} /*${typeItem.description}${typeItem.default ? ` 默认值: ***${typeItem.default.replace(/\n/g, '')}***` : ''}*/`).join('\n ')}\n}` } return result += `\n${item.typeDetail![key].split('|').join('\n|')}` }, '')}` documentation.appendCodeblock(data, 'typescript') } // command:extension.openDocumentLink?%7B%22link%22%3A%22https%3A%2F%2Fexample.com%2F%22%7D if (item.link) documentation.appendMarkdown(`\n[🔗 ${isZh ? '文档链接' : 'Documentation link'}](command:intellisense.openDocument?%7B%22link%22%3A%22${encodeURIComponent(isZh ? (item?.link_zh || item.link) : item.link)}%22%7D)\` \`[🔗 ${isZh ? '外部文档链接' : 'External document links'}](command:intellisense.openDocumentExternal?%7B%22link%22%3A%22${encodeURIComponent(isZh ? (item?.link_zh || item.link) : item.link)}%22%7D)`) let content = '' let snippet = '' if (value.type && value.type.toLowerCase().trim() === 'boolean' && value.default === 'false') { content = snippet = key } else if (value.type && value.type.toLowerCase().trim() === 'boolean' && value.default === 'true') { if (isVue) { content = key snippet = `:${key}="false"` } else { content = key snippet = `${key}={false}` } } else if (key.startsWith(':')) { if (isVue) { const _key = key.replace('v-model', 'model') content = `${key.replace(':v-model', 'v-model')}="${getComponentTagName(item.name)}${_key[1].toUpperCase()}${toCamel(_key.slice(2))}"` snippet = `${key.replace(':v-model', 'v-model')}="\${1|${generateSnippetNameOptions(item, _key, prefix)}|}"$2` } else { content = `${key.slice(1)}={${getComponentTagName(item.name)}${key[1].toUpperCase()}${toCamel(key.slice(2))}}` snippet = `${key.slice(1)}={\${1|${generateSnippetNameOptions(item, key, prefix)}|}}$2` } } else { content = `${key}=""` if (value.type.includes('/')) snippet = `${key}="\${1|${value.type.split('/').map((i: string) => i.replace(/['`\s]/g, '').replace(/,/g, '\\,')).join(',')}|}"` else snippet = `${key}="\${1}"` } const details = `${content}\n- ${isZh ? (value.description_zh || value.description) : value.description}\n- ${value.default ? ` ${isZh ? '默认' : 'default'}:${value.default.replace(/\n/g, '')}` : ''}` content += ` ${isZh ? (value.description_zh || value.description) : value.description} ${value.default ? ` ${isZh ? '默认' : 'default'}:${value.default.replace(/\n/g, '')}` : ''}` data.push(createCompletionItem({ content, details, snippet, type, documentation, preselect: true, sortText: 'a', params: [uiName, key.replace(/^:/, '')], propType: value.type, command: { command: 'editor.action.triggerSuggest', // 这个命令会触发代码提示 title: 'Trigger Suggest', }, })) }) return data } completions.push(completionsDeferCallback) if (!item.events) item.events = [] if (item.events) { const deferEventsCall = (isVue?: boolean) => { const lan = getActiveTextEditorLanguageId() const originEvent = [ { name: isVue ? 'click' : lan === 'svelte' ? 'on:click' : 'onClick', description: isZh ? '点击事件' : 'click event', params: [], }, ] originEvent.forEach((_event) => { if (!item.events.find(event => event.name === _event.name)) item.events.push(_event) }) return item.events.map((events: any) => { const detail: string[] = [] const { name, description, params, description_zh } = events detail.push(`## ${uiName} [${item.name}]`) if (description) { if (isZh) detail.push(`#### 🔦 说明: ***\`${description_zh || description}\`***`) else detail.push(`#### 🔦 description: ***\`${description}\`***`) } if (params) detail.push(`#### 🔮 ${isZh ? '回调参数' : 'callback parameters'}: ***\`${params}\`***`) let snippet let content if (isVue) { const _name = name.split(':').map((item: string) => item[0].toUpperCase() + item.slice(1), ).join('').replace(/-(\w)/g, (_: string, v: string) => v.toUpperCase()) snippet = `${name}="\${1:on${_name}}"` content = `@${name}="on${_name}"` } else if (lan === 'svelte') { snippet = `${name}={\${1:${name.replace(/:(\w)/, (_: string, v: string) => v.toUpperCase())}}}` content = `${name}={${name.replace(/:(\w)/, (_: string, v: string) => v.toUpperCase())}}` } else { let _name = name if (!_name.startsWith('on')) _name = `on${_name[0].toUpperCase()}${_name.slice(1)}` snippet = `${_name}={\${1:${_name}}}` content = `${_name}={${_name}}` } content += ` ${isZh ? (description_zh || description) : description}${params ? ` ${isZh ? '参数' : 'params'}:${params}` : ''}` const documentation = new vscode.MarkdownString() documentation.isTrusted = true documentation.supportHtml = true documentation.appendMarkdown(detail.join('\n\n')) return proxyCreateCompletionItem({ content, snippet, documentation, type: vscode.CompletionItemKind.Event, sortText: 'b', preselect: true, params: [uiName, name] }) }, ) } events.push(deferEventsCall) } if (item.methods) { methods.push(...item.methods.map((method) => { const documentation = new vscode.MarkdownString() documentation.isTrusted = true documentation.supportHtml = true const detail: string[] = [] const { name, description, params, description_zh } = method detail.push(`## ${uiName} [${item.name}]`) if (name) detail.push(`\n#### 💨 ${isZh ? '方法' : 'method'} ${name}:`) if (description) { if (isZh) detail.push(`- 👓 说明: ***\`${description_zh || description}\`***`) else detail.push(`- 👓 description: ***\`${description}\`***`) } if (params) detail.push(`- 🚢 ${isZh ? '参数' : 'params'}: ***\`${params}\`***`) documentation.appendMarkdown(detail.join('\n\n')) const hover = createHover(documentation) return proxyCreateCompletionItem({ content: method.name, snippet: `${name.endsWith('()') ? name : `${name}()`}$1`, documentation, type: 1, sortText: 'b', params: uiName, hover }) })) } if (item.exposed) { exposed.push(...item.exposed.map((expose) => { const documentation = new vscode.MarkdownString() documentation.isTrusted = true documentation.supportHtml = true const details: string[] = [] const { name, description, detail, description_zh } = expose details.push(`## ${uiName} [${item.name}]`) if (name) details.push(`\n#### 💨 ${isZh ? '导出' : 'exposed'} ${name}:`) if (description) { if (isZh) details.push(`- 👓 说明: ***\`${description_zh || description}\`***`) else details.push(`- 👓 description: ***\`${description}\`***`) } if (detail) details.push(`- 🚢 ${isZh ? '详情' : 'detail'}: ***\`${detail}\`***`) documentation.appendMarkdown(details.join('\n\n')) const hover = createHover(documentation) return proxyCreateCompletionItem({ content: expose.name, snippet: expose.detail.startsWith('()') ? `${expose.name}()` : expose.name, detail, documentation, type: 1, sortText: 'b', params: uiName, hover }) })) } if (item.slots) { item.slots.forEach((slot) => { const { name, description, description_zh } = slot const documentation = new vscode.MarkdownString() documentation.isTrusted = true documentation.supportHtml = true const detail = [] if (description) { if (isZh) detail.push(`- 👓 说明: ***\`${description_zh || description}\`***`) else detail.push(`- 👓 description: ***\`${description}\`***`) } documentation.appendMarkdown(detail.join('\n\n')) slots.push(createCompletionItem({ content: `slot="${name}"`, snippet: `slot="${name}"$1`, documentation, type: 1, preselect: true, sortText: 'b', params: uiName })) }) } const createTableDocument = () => { const documentation = new vscode.MarkdownString() documentation.isTrusted = true documentation.supportHtml = true const details: string[] = [] let text = `## ${uiName} [${item.name}]` if (item.link) { text += `\` \`[🔗 ${isZh ? '文档链接' : 'Documentation link'}](command:intellisense.openDocument?%7B%22link%22%3A%22${encodeURIComponent(isZh ? (item?.link_zh || item.link) : item.link)}%22%7D)\` \`[🔗 ${isZh ? '外部链接' : 'External document links'}](command:intellisense.openDocumentExternal?%7B%22link%22%3A%22${encodeURIComponent(isZh ? (item?.link_zh || item.link) : item.link)}%22%7D)` } details.push(text) if (item.props) { if (isZh) details.push('### 参数:') else details.push('### Props:') const tableHeader = `| ${isZh ? '属性名' : 'Name'} | ${isZh ? '描述' : 'Description'} | ${isZh ? '类型' : 'Type'} | ${isZh ? '默认值' : 'Default'} |` const tableDivider = '| --- | --- | --- | --- |' const tableContent = [ tableHeader, tableDivider, ...Object.keys(item.props).map((name) => { const { default: defaultValue = '', type, description, description_zh } = item.props[name] let value = String(defaultValue).replace(/\s+/g, ' ').replace(/\|/g, ' \\| ').trim() value = String(defaultValue).length > 20 ? '...' : value return `| \`${name}\` | \`${isZh ? description_zh : description}\` | \`${type}\` | \`${value}\` |` }), ].join('\n') details.push(tableContent) } if (item.methods && item.methods.length) { if (isZh) details.push('## 方法:') else details.push('## Methods:') const tableHeader = `| ${isZh ? '方法名' : 'Method Name'} | ${isZh ? '描述' : 'Description'} | ${isZh ? '参数' : 'Params'} |` const tableDivider = '| --- | --- | --- |' const tableContent = [ tableHeader, tableDivider, ...item.methods.map((m) => { const { name, params, description, description_zh } = m return `| ${name} | ${isZh ? description_zh : description} | ${params} |` }), ].join('\n') details.push(tableContent) } if (item.events && item.events.length) { if (isZh) details.push('## 事件:') else details.push('## Events:') const tableHeader = `| ${isZh ? '事件名' : 'Event Name'} | ${isZh ? '描述' : 'Description'} | ${isZh ? '参数' : 'Params'} |` const tableDivider = '| --- | --- | --- |' const tableContent = [ tableHeader, tableDivider, ...item.events.map((m) => { const { name, params, description, description_zh } = m return `| ${name} | ${isZh ? description_zh : description} | ${params || '-'} |` }), ].join('\n') details.push(tableContent) } if (item.link) details.push(`[🔗 ${isZh ? '文档链接' : 'Documentation link'}](command:intellisense.openDocument?%7B%22link%22%3A%22${encodeURIComponent(isZh ? (item?.link_zh || item.link) : item.link)}%22%7D)\` \` [🔗 ${isZh ? '外部链接' : 'External document links'}](command:intellisense.openDocumentExternal?%7B%22link%22%3A%22${encodeURIComponent(isZh ? (item?.link_zh || item.link) : item.link)}%22%7D)`) documentation.appendMarkdown(details.join('\n\n')) return documentation } const tableDocument = createTableDocument() result[item.name!] = { completions, events, methods, exposed, slots, suggestions: item.suggestions || [], tableDocument, rawSlots: item.slots, uiName, lib } return result }, result) } export type Directives = { name: string description: string description_zh: string documentation?: string documentationType?: string params?: { name: string description: string description_zh: string type: string default: string }[] link: string link_zh: string }[] // todo: 重构参数,参数过多,改为 options export interface ComponentOptions { map: any[] isSeperatorByHyphen?: boolean prefix?: string lib: string isReact?: boolean dynamicLib?: string importWay?: 'as default' | 'default' | 'specifier' directives?: Directives } export interface ComponentsConfigItem { prefix: string directives?: Directives lib: string data: () => CompletionItem[] isReact?: boolean dynamicLib?: string importWay?: 'as default' | 'default' | 'specifier' } export type ComponentsConfig = ComponentsConfigItem[] export function componentsReducer(options: ComponentOptions): ComponentsConfig { const { map, isSeperatorByHyphen = true, prefix = '', lib, isReact = false, dynamicLib, importWay = 'specifier', directives } = options const isZh = getLocale().includes('zh') if (!isReact && prefix) { return [ { prefix, directives, lib, data: () => (map as [Component | string, string, string?][]).map(([content, detail, demo]) => { const isVue = isVueOrVine() let snippet = '' let _content = '' let description = '' if (typeof content === 'object') { let [requiredProps, index] = getRequireProp(content, 0, isVue) const tag = isSeperatorByHyphen ? hyphenate(content.name) : content.name if (requiredProps.length) { if (content?.suggestions?.length === 1) { const suggestionTag = content.suggestions[0] const suggestion = findTargetMap(map, suggestionTag) if (suggestion) { const [childRequiredProps, _index] = getRequireProp(suggestion, index, isVue) index = _index snippet = `<${tag}${requiredProps.length ? ` ${requiredProps.join(' ')}` : ''}>\n <${suggestionTag}${childRequiredProps.length ? ` ${childRequiredProps.join(' ')}` : ''}>\$${++index}</${suggestionTag}>\n</${tag}>` } else { snippet = `<${tag}>\$${++index}</${tag}>` } } else { snippet = `<${tag}${requiredProps.length ? ` ${requiredProps.join(' ')}` : ''}>$${++index}</${tag}>` } } else { if (content?.suggestions?.length === 1) { const suggestionTag = content.suggestions[0] const suggestion = findTargetMap(map, suggestionTag) if (suggestion) { const [childRequiredProps, _index] = getRequireProp(suggestion, index, isVue) index = _index snippet = `<${tag}>\n <${suggestionTag}${childRequiredProps.length ? ` ${childRequiredProps.join(' ')}` : ''}>\$${++index}</${suggestionTag}>\n</${tag}>` } else { snippet = `<${tag}>$1</${tag}>` } } else { snippet = `<${tag}>$1</${tag}>` } } _content = `${tag} ${content.tag || detail}` description = isZh && content.description_zh ? content.description_zh : content.description || '' } else { snippet = `<${content}>$1</${content}>` _content = `${content} ${detail}` } if (!demo) demo = snippet const documentation = createMarkdownString() documentation.isTrusted = true documentation.supportHtml = true documentation.appendMarkdown(`#### 🍀 ${lib} ${detail}\n`) if (typeof content === 'object' && content.suggestions?.length) { documentation.appendMarkdown(`\n#### 👗 ${isZh ? '常用搭配' : 'Common collocation'} \n`) // FIXME: suggestions的Item有对象形式的vant4里面,里面的文案要怎么展示 documentation.appendMarkdown(`${content.suggestions.map((item: string | SuggestionItem) => `- ${item}`).join('\n')}\n`) } const copyIcon = '<img width="12" height="12" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2UyOWNkMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuNSI+PHBhdGggZD0iTTIwLjk5OCAxMGMtLjAxMi0yLjE3NS0uMTA4LTMuMzUzLS44NzctNC4xMjFDMTkuMjQzIDUgMTcuODI4IDUgMTUgNWgtM2MtMi44MjggMC00LjI0MyAwLTUuMTIxLjg3OUM2IDYuNzU3IDYgOC4xNzIgNiAxMXY1YzAgMi44MjggMCA0LjI0My44NzkgNS4xMjFDNy43NTcgMjIgOS4xNzIgMjIgMTIgMjJoM2MyLjgyOCAwIDQuMjQzIDAgNS4xMjEtLjg3OUMyMSAyMC4yNDMgMjEgMTguODI4IDIxIDE2di0xIi8+PHBhdGggZD0iTTMgMTB2NmEzIDMgMCAwIDAgMyAzTTE4IDVhMyAzIDAgMCAwLTMtM2gtNEM3LjIyOSAyIDUuMzQzIDIgNC4xNzIgMy4xNzJDMy41MTggMy44MjUgMy4yMjkgNC43IDMuMTAyIDYiLz48L2c+PC9zdmc+" />' documentation.appendMarkdown(`#### 🌰 ${isZh ? '例子' : 'example'}\n`) documentation.appendCodeblock(demo, 'html') // FIXME: 要求输入数组,但是demo类型是字符串,但是都通过JSON.stringify处理了,所以这里转成[demo]? const params = setCommandParams(demo as any) documentation.appendMarkdown(`\n<a href="command:intellisense.copyDemo?${params}">${copyIcon}</a>\n`) // FIXME: params要求string| string[] // const fixParams: FixParams = [content as Component, lib, isReact, prefix, dynamicLib || '', importWay || ''] const fixParams: any = { data: content, lib, isReact, prefix, dynamicLib, importWay, } return createCompletionItem({ content: _content, snippet, detail: description, documentation, type: vscode.CompletionItemKind.TypeParameter, sortText: 'a', params: fixParams, demo }) }), }, { prefix: '', directives, lib, data: () => (map as [Component | string, string, string?][]).map(([content, detail, demo]) => { const isVue = isVueOrVine() let snippet = '' let _content = '' let description = '' if (typeof content === 'object') { let [requiredProps, index] = getRequireProp(content, 0, isVue) const tag = content.name.slice(prefix.length) if (requiredProps.length) { if (content?.suggestions?.length === 1) { let suggestionTag = content.suggestions[0] const suggestion = findTargetMap(map, suggestionTag) if (suggestion) { suggestionTag = suggestion.name.slice(prefix.length) const [childRequiredProps, _index] = getRequireProp(suggestion, index, isVue) index = _index snippet = `<${tag}${requiredProps.length ? ` ${requiredProps.join(' ')}` : ''}>\n <${suggestionTag}${childRequiredProps.length ? ` ${childRequiredProps.join(' ')}` : ''}>\$${++index}</${suggestionTag}>\n</${tag}>` } else { snippet = `<${tag}>\$${++index}</${tag}>` } } else { snippet = `<${tag}${requiredProps.length ? ` ${requiredProps.join(' ')}` : ''}>$${++index}</${tag}>` } } else { if (content?.suggestions?.length === 1) { let suggestionTag = content.suggestions[0] const suggestion = findTargetMap(map, suggestionTag) if (suggestion) { suggestionTag = suggestion.name.slice(prefix.length) const [childRequiredProps, _index] = getRequireProp(suggestion, index, isVue) index = _index snippet = `<${tag}>\n <${suggestionTag}${childRequiredProps.length ? ` ${childRequiredProps.join(' ')}` : ''}>\$${++index}</${suggestionTag}>\n</${tag}>` } else { snippet = `<${tag}>$1</${tag}>` } } else { snippet = `<${tag}>$1</${tag}>` } } _content = `${tag} ${content.tag || detail}` description = isZh && content.description_zh ? content.description_zh : content.description || '' } else { snippet = `<${content}>$1</${content}>` _content = `${content} ${detail}` } if (!demo) demo = snippet const documentation = new vscode.MarkdownString() documentation.isTrusted = true documentation.supportHtml = true documentation.appendMarkdown(`#### 🍀 ${lib} ${detail}\n`) if (typeof content === 'object' && content.suggestions?.length) { documentation.appendMarkdown(`\n#### 👗 ${isZh ? '常用搭配' : 'Common collocation'} \n`) documentation.appendMarkdown(`${content.suggestions.map((item: string | SuggestionItem) => `- ${typeof item === 'string' ? item : item.name}`).join('\n')}\n`) } const copyIcon = '<img width="12" height="12" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2UyOWNkMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuNSI+PHBhdGggZD0iTTIwLjk5OCAxMGMtLjAxMi0yLjE3NS0uMTA4LTMuMzUzLS44NzctNC4xMjFDMTkuMjQzIDUgMTcuODI4IDUgMTUgNWgtM2MtMi44MjggMC00LjI0MyAwLTUuMTIxLjg3OUM2IDYuNzU3IDYgOC4xNzIgNiAxMXY1YzAgMi44MjggMCA0LjI0My44NzkgNS4xMjFDNy43NTcgMjIgOS4xNzIgMjIgMTIgMjJoM2MyLjgyOCAwIDQuMjQzIDAgNS4xMjEtLjg3OUMyMSAyMC4yNDMgMjEgMTguODI4IDIxIDE2di0xIi8+PHBhdGggZD0iTTMgMTB2NmEzIDMgMCAwIDAgMyAzTTE4IDVhMyAzIDAgMCAwLTMtM2gtNEM3LjIyOSAyIDUuMzQzIDIgNC4xNzIgMy4xNzJDMy41MTggMy44MjUgMy4yMjkgNC43IDMuMTAyIDYiLz48L2c+PC9zdmc+" />' documentation.appendMarkdown(`#### 🌰 ${isZh ? '例子' : 'example'}\n`) documentation.appendCodeblock(demo, 'html') // FIXME: 同上 const params = setCommandParams(demo as any) documentation.appendMarkdown(`\n<a href="command:intellisense.copyDemo?${params}">${copyIcon}</a>\n`) // FIXME: params要求string| string[] const fixParams: any = { data: { ...(content as any), name: (content as any).name?.slice(prefix.length) }, lib, isReact: true, prefix, dynamicLib, importWay, } // const fixParams: any = [{ ...(content as any), name: (content as any).name?.slice(prefix.length) }, lib, true, prefix, dynamicLib, importWay] return createCompletionItem({ content: _content, detail: description, snippet, documentation, type: vscode.CompletionItemKind.TypeParameter, sortText: 'a', params: fixParams, demo }) }), }, ] } return [{ prefix, directives, lib, data: () => (map as [Component | string, string, string?][]).map(([content, detail, demo]) => { const isVue = isVueOrVine() let snippet = '' let _content = '' let description = '' if (typeof content === 'object') { let [requiredProps, index] = getRequireProp(content, 0, isVue) const tag = isSeperatorByHyphen ? hyphenate(content.name) : content.name if (requiredProps.length) { if (content?.suggestions?.length === 1) { const suggestionTag = content.suggestions[0] const suggestion = findTargetMap(map, suggestionTag) if (suggestion) { const [childRequiredProps, _index] = getRequireProp(suggestion, index, isVue) index = _index snippet = `<${tag}${requiredProps.length ? ` ${requiredProps.join(' ')}` : ''}>\n <${suggestionTag}${childRequiredProps.length ? ` ${childRequiredProps.join(' ')}` : ''}>\$${++index}</${suggestionTag}>\n</${tag}>` } else { snippet = `<${tag}>\$${++index}</${tag}>` } } else { snippet = `<${tag}${requiredProps.length ? ` ${requiredProps.join(' ')}` : ''}>$${++index}</${tag}>` } } else { if (content?.suggestions?.length === 1) { const suggestionTag = content.suggestions[0] const suggestion = findTargetMap(map, suggestionTag) if (suggestion) { const [childRequiredProps, _index] = getRequireProp(suggestion, index, isVue) index = _index snippet = `<${tag}>\n <${suggestionTag}${childRequiredProps.length ? ` ${childRequiredProps.join(' ')}` : ''}>\$${++index}</${suggestionTag}>\n</${tag}>` } else { snippet = `<${tag}>$1</${tag}>` } } else { snippet = `<${tag}>$1</${tag}>` } } _content = `${tag} ${content.tag || detail}` description = isZh && content.description_zh ? content.description_zh : content.description || '' } else { snippet = `<${content}>$1</${content}>` _content = `${content} ${detail}` } if (!demo) demo = snippet const documentation = new vscode.MarkdownString() documentation.isTrusted = true documentation.supportHtml = true documentation.appendMarkdown(`#### 🍀 ${lib} ${detail}\n`) if (typeof content === 'object' && content.suggestions?.length) { documentation.appendMarkdown(`\n#### 👗 ${isZh ? '常用搭配' : 'Common collocation'} \n`) documentation.appendMarkdown(`${content.suggestions.map((item: string | SuggestionItem) => `- ${typeof item === 'string' ? item : item.name}`).join('\n')}\n`) } const copyIcon = '<img width="12" height="12" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2UyOWNkMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuNSI+PHBhdGggZD0iTTIwLjk5OCAxMGMtLjAxMi0yLjE3NS0uMTA4LTMuMzUzLS44NzctNC4xMjFDMTkuMjQzIDUgMTcuODI4IDUgMTUgNWgtM2MtMi44MjggMC00LjI0MyAwLTUuMTIxLjg3OUM2IDYuNzU3IDYgOC4xNzIgNiAxMXY1YzAgMi44MjggMCA0LjI0My44NzkgNS4xMjFDNy43NTcgMjIgOS4xNzIgMjIgMTIgMjJoM2MyLjgyOCAwIDQuMjQzIDAgNS4xMjEtLjg3OUMyMSAyMC4yNDMgMjEgMTguODI4IDIxIDE2di0xIi8+PHBhdGggZD0iTTMgMTB2NmEzIDMgMCAwIDAgMyAzTTE4IDVhMyAzIDAgMCAwLTMtM2gtNEM3LjIyOSAyIDUuMzQzIDIgNC4xNzIgMy4xNzJDMy41MTggMy44MjUgMy4yMjkgNC43IDMuMTAyIDYiLz48L2c+PC9zdmc+" />' documentation.appendMarkdown(`#### 🌰 ${isZh ? '例子' : 'example'}\n`) documentation.appendCodeblock(demo, 'html') // FIXME: setCommandParams要求 string[] const params = setCommandParams(demo as any) documentation.appendMarkdown(`\n<a href="command:intellisense.copyDemo?${params}">${copyIcon}</a>\n`) // FIXME: params要求string| string[] // const fixParams: any = [content, lib, isReact, prefix, dynamicLib || '', importWay || ''] const fixParams: any = { data: content, lib, isReact, prefix, dynamicLib, importWay, } const completionItem: CompletionItem = createCompletionItem({ content: _content, snippet, detail: description, documentation, type: vscode.CompletionItemKind.TypeParameter, sortText: 'a', params: fixParams, demo }) return completionItem }), }] } function getComponentTagName(str: string) { return str.replace(/([a-z])([A-Z])/g, '$1-$2').split('-').slice(-1)[0].toLowerCase() } export function hyphenate(s: string): string { return s.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '') } export function toCamel(s: string) { return s.replace(/-(\w)/g, (_, v) => v.toUpperCase()) } export function getRequireProp(content: any, index = 0, isVue: boolean): [string[], number] { const requiredProps: string[] = [] if (!content.props) return [requiredProps, index] Object.keys(content.props).forEach((key) => { const item = content.props[key] if (!item.required) return let attr = '' const v = item.value if (key.startsWith(':')) { const tagName = getComponentTagName(content.name) const keyName = toCamel(key.split(':').slice(-1)[0]) if (item.foreach) { if (requiredProps.some(p => p.includes('v-for='))) attr = `${key}="item.\${${++index}:${keyName}}"` else attr = `v-for="item in \${${++index}:${tagName}Options}" :key="item.\${${++index}:key}" ${key}="item.\${${++index}:${keyName}}"` } else { key = key.replace(':v-model', 'v-model') ++index if (!v) { if (isVue) attr = `${key}="\${${index}:${tagName}${keyName[0].toUpperCase()}${keyName.slice(1)}}"` else attr = `${key.slice(1)}={\${${index}:${tagName}${keyName[0].toUpperCase()}${keyName.slice(1)}}}` } else { if (isVue) attr = `${key}="\${${index}:${tagName}${keyName[0].toUpperCase()}${keyName.slice(1)}}"` else attr = `${key.slice(1)}={\${${index}:${v}}}` } } } else if (item.type && item.type.includes('boolean') && item.default === 'false') { // 还要进一步看它的 type 如果 type === boolean 提供 true or false 如果是字符串,使用 / 或着 | 分割,作为提示 if (isVue) attr = key else attr = `${key}="true"` } else { const tempMap: any = {} const types = item.type.replace(/\s+/g, ' ').replace(/\{((?:[^{}]|\{[^{}]*\})*)\}|<((?:[^<>]|<[^<>]*>)*)>/g, (_: string) => { const key = hash(_) tempMap[key] = _.replace(/,/g, '\,') return key }) .split(/[|/]/) .filter((item: string) => { // 如果 item长度太长,可能有问题,所以也过滤掉 return !!item && item.length < 40 }).map((item: string) => item.replace(/['"]/g, '').trim()).map((item: string) => { Object.keys(tempMap).forEach((i) => { item = item.replace(i, tempMap[i]) }) return item }) if (item.default && types.includes(item.default)) { // 如果 item.default 并且在 type 中,将 types 的 default 值,放到 const i = types.findIndex((i: string) => i === item.default) types.splice(i, 1) types.unshift(item.default) } const typeTipes = types .map((item: string) => escapeRegExp(item).replace(/,/g, '\\,')) .join(',') if (v) attr = `${key}="${v}"` else attr = `${key}="\${${++index}|${typeTipes}|}"` } requiredProps.push(attr) }) return [requiredProps, index] } function findTargetMap(maps: any, suggestionTag: string | SuggestionItem) { let label = typeof suggestionTag === 'string' ? suggestionTag : suggestionTag.name label = toCamel(`-${label}`) for (const map of maps) { if (typeof map[0] === 'object') { if (map[0].name === label) return map[0] } else if (map[0] === label) { return map } } } /** * generateSnippetNameOptions * return string name1,name2,name3 */ function generateSnippetNameOptions(item: any, keyName: string, prefix: string) { if (keyName[0] === ':') keyName = keyName.slice(1) keyName = toCamel(keyName.replace(/:.*/, '')) const componentName = prefix ? item.name[prefix.length].toLowerCase() + item.name.slice(prefix.length + 1) : item.name const splitNames = componentName.split(/(?=[A-Z])/).map((i: string) => `${i.toLocaleLowerCase()}${keyName[0].toUpperCase()}${keyName.slice(1)}`) const splitNamesReverse = componentName.split(/(?=[A-Z])/).map((i: string) => `${keyName.toLocaleLowerCase()}${i}`) return [ keyName, `${keyName}Value`, `is${keyName[0].toUpperCase()}${keyName.slice(1)}`, ...splitNames, ...splitNamesReverse, `${componentName}${keyName[0].toUpperCase()}${keyName.slice(1)}`, `${componentName}_${keyName}`, ].join(',') } export function hash(str: string) { let i let l let hval = 0x811C9DC5 for (i = 0, l = str.length; i < l; i++) { hval ^= str.charCodeAt(i) hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24) } return `00000${(hval >>> 0).toString(36)}`.slice(-6) } export function isVue() { const currentFileUrl = getCurrentFileUrl()! return currentFileUrl.endsWith('.vue') } export function isVine() { const currentFileUrl = getCurrentFileUrl()! return currentFileUrl.endsWith('.vine.ts') } export function isVueOrVine() { return isVue() || isVine() } /** * escapeRegExp * @description 对字符串中的特殊字符进行转义以在正则表达式中使用它 * @param str string * @returns string */ export function escapeRegExp(str: string) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') }