common-intellisense
Version:
898 lines (816 loc) • 39.3 kB
text/typescript
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(``)
// 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, '\\$&')
}