/
autocomplete.ts
118 lines (100 loc) · 3.23 KB
/
autocomplete.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import type { UnocssAutocomplete } from '@unocss/autocomplete'
import { createAutocomplete } from '@unocss/autocomplete'
import type { CompletionItemProvider, ExtensionContext } from 'vscode'
import { CompletionItem, CompletionItemKind, CompletionList, MarkdownString, Range, languages } from 'vscode'
import type { UnoGenerator, UnocssPluginContext } from '@unocss/core'
import { getPrettiedMarkdown, isCssId } from './utils'
import { log } from './log'
import type { ContextLoader } from './contextLoader'
const languageIds = [
'erb',
'haml',
'hbs',
'html',
'css',
'javascript',
'javascriptreact',
'markdown',
'ejs',
'php',
'svelte',
'typescript',
'typescriptreact',
'vue-html',
'vue',
]
const delimiters = ['-', ':']
class UnoCompletionItem extends CompletionItem {
uno: UnoGenerator
constructor(label: string, kind: CompletionItemKind, uno: UnoGenerator) {
super(label, kind)
this.uno = uno
}
}
export async function registerAutoComplete(
contextLoader: ContextLoader,
ext: ExtensionContext,
) {
const autoCompletes = new Map<UnocssPluginContext, UnocssAutocomplete>()
contextLoader.events.on('contextReload', (ctx) => {
autoCompletes.delete(ctx)
})
contextLoader.events.on('contextUnload', (ctx) => {
autoCompletes.delete(ctx)
})
function getAutocomplete(ctx: UnocssPluginContext) {
const cached = autoCompletes.get(ctx)
if (cached)
return cached
const autocomplete = createAutocomplete(ctx.uno)
autoCompletes.set(ctx, autocomplete)
return autocomplete
}
async function getMarkdown(uno: UnoGenerator, util: string) {
return new MarkdownString(await getPrettiedMarkdown(uno, util))
}
const provider: CompletionItemProvider<UnoCompletionItem> = {
async provideCompletionItems(doc, position) {
const code = doc.getText()
const id = doc.uri.fsPath
if (!code)
return null
let ctx = await contextLoader.resolveContext(code, id)
if (!ctx && isCssId(id))
ctx = await contextLoader.resolveClosestContext(code, id)
else if (!ctx?.filter(code, id))
return null
try {
const autoComplete = getAutocomplete(ctx)
const result = await autoComplete.suggestInFile(code, doc.offsetAt(position))
log.appendLine(`[autocomplete] ${id} | ${result.suggestions.slice(0, 10).map(v => `[${v[0]}, ${v[1]}]`).join(', ')}`)
if (!result.suggestions.length)
return
return new CompletionList(result.suggestions.map(([value, label]) => {
const resolved = result.resolveReplacement(value)
const item = new UnoCompletionItem(label, CompletionItemKind.EnumMember, ctx!.uno)
item.insertText = resolved.replacement
item.range = new Range(doc.positionAt(resolved.start), doc.positionAt(resolved.end))
return item
}), true)
}
catch (e) {
log.appendLine(`[error] ${String(e)}`)
return null
}
},
async resolveCompletionItem(item) {
return {
...item,
documentation: await getMarkdown(item.uno, item.label as string),
}
},
}
ext.subscriptions.push(
languages.registerCompletionItemProvider(
languageIds,
provider,
...delimiters,
),
)
}