@sunshj/vscode-ab5x-utils 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +126 -0
- package/dist/index.d.mts +115 -0
- package/dist/index.mjs +121 -0
- package/package.json +27 -0
- package/src/index.ts +388 -0
- package/tsdown.config.ts +22 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
let vscode = require("vscode");
|
|
2
|
+
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
/** 创建基于正则的收集函数 */
|
|
5
|
+
function createRegexCollector(regex, setupContextOrTransform, transform) {
|
|
6
|
+
const hasSetupContext = transform !== void 0;
|
|
7
|
+
const setupContext = hasSetupContext ? setupContextOrTransform : void 0;
|
|
8
|
+
const transformFn = hasSetupContext ? transform : setupContextOrTransform;
|
|
9
|
+
const context = setupContext?.();
|
|
10
|
+
return (document) => {
|
|
11
|
+
const items = [];
|
|
12
|
+
const text = document.getText();
|
|
13
|
+
const globalRegex = new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
|
|
14
|
+
let match;
|
|
15
|
+
while ((match = globalRegex.exec(text)) !== null) if (transformFn) {
|
|
16
|
+
const item = hasSetupContext ? transformFn(match, context) : transformFn(match);
|
|
17
|
+
if (item) items.push(item);
|
|
18
|
+
} else items.push({
|
|
19
|
+
text: match[0],
|
|
20
|
+
startIndex: match.index,
|
|
21
|
+
endIndex: match.index + match[0].length
|
|
22
|
+
});
|
|
23
|
+
return items;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** 从匹配项创建 Range */
|
|
27
|
+
function createRange(document, item) {
|
|
28
|
+
return new vscode.Range(document.positionAt(item.startIndex), document.positionAt(item.endIndex));
|
|
29
|
+
}
|
|
30
|
+
/** 获取匹配项的 extra 数据 */
|
|
31
|
+
function getItemExtra(link) {
|
|
32
|
+
return link.__extra;
|
|
33
|
+
}
|
|
34
|
+
/** 设置匹配项的 extra 数据到 DocumentLink */
|
|
35
|
+
function setItemExtra(link, extra) {
|
|
36
|
+
link.__extra = extra;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 定义模式匹配 Provider,统一管理 DocumentLink、Hover、Completion、Decoration、CodeLens 等功能
|
|
40
|
+
*/
|
|
41
|
+
function definePatternProvider(options) {
|
|
42
|
+
const { selector = { scheme: "file" }, fileFilter = () => true, collect: collectFn, documentLink, hover, completion, decoration, codeLens, commands: commandsConfig } = options;
|
|
43
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
function collect(document, skipCache = false) {
|
|
45
|
+
if (!skipCache && cache.has(document.uri)) return cache.get(document.uri);
|
|
46
|
+
const items = collectFn(document, skipCache);
|
|
47
|
+
cache.set(document.uri, items);
|
|
48
|
+
return items;
|
|
49
|
+
}
|
|
50
|
+
const decorationType = decoration?.enabled && decoration.style ? vscode.window.createTextEditorDecorationType(decoration.style) : void 0;
|
|
51
|
+
function createDocumentLinkProvider() {
|
|
52
|
+
return { provideDocumentLinks(document) {
|
|
53
|
+
if (!fileFilter(document)) return;
|
|
54
|
+
return collect(document).map(function(item) {
|
|
55
|
+
const range = createRange(document, item);
|
|
56
|
+
const target = documentLink?.target?.(item, document);
|
|
57
|
+
const link = new vscode.DocumentLink(range, target);
|
|
58
|
+
link.tooltip = documentLink?.tooltip?.(item, document);
|
|
59
|
+
setItemExtra(link, item.extra);
|
|
60
|
+
return link;
|
|
61
|
+
});
|
|
62
|
+
} };
|
|
63
|
+
}
|
|
64
|
+
function createHoverProvider() {
|
|
65
|
+
return { provideHover(document, position) {
|
|
66
|
+
if (!fileFilter(document)) return;
|
|
67
|
+
const items = collect(document);
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
if (!createRange(document, item).contains(position)) continue;
|
|
70
|
+
return hover?.content(item, document);
|
|
71
|
+
}
|
|
72
|
+
} };
|
|
73
|
+
}
|
|
74
|
+
function createCompletionProvider() {
|
|
75
|
+
return { provideCompletionItems(document, position) {
|
|
76
|
+
if (!fileFilter(document)) return;
|
|
77
|
+
if (completion?.shouldTrigger && !completion.shouldTrigger(document, position)) return;
|
|
78
|
+
return completion?.items(document, position);
|
|
79
|
+
} };
|
|
80
|
+
}
|
|
81
|
+
function createCodeLensProvider() {
|
|
82
|
+
return { provideCodeLenses(document) {
|
|
83
|
+
if (!fileFilter(document)) return;
|
|
84
|
+
return collect(document).flatMap((item) => codeLens?.lenses(item, document) ?? []);
|
|
85
|
+
} };
|
|
86
|
+
}
|
|
87
|
+
function register() {
|
|
88
|
+
const disposables = [];
|
|
89
|
+
if (commandsConfig?.length) for (const cmd of commandsConfig) disposables.push(vscode.commands.registerCommand(cmd.id, cmd.handler));
|
|
90
|
+
if (documentLink?.enabled !== false) disposables.push(vscode.languages.registerDocumentLinkProvider(selector, createDocumentLinkProvider()));
|
|
91
|
+
if (hover?.enabled) disposables.push(vscode.languages.registerHoverProvider(selector, createHoverProvider()));
|
|
92
|
+
if (completion?.enabled) disposables.push(vscode.languages.registerCompletionItemProvider(selector, createCompletionProvider(), ...completion.triggerCharacters ?? []));
|
|
93
|
+
if (codeLens?.enabled) disposables.push(vscode.languages.registerCodeLensProvider(selector, createCodeLensProvider()));
|
|
94
|
+
if (decoration?.enabled && decorationType) {
|
|
95
|
+
const updateDecorations = (editor) => {
|
|
96
|
+
if (!editor || !fileFilter(editor.document)) return;
|
|
97
|
+
const decorations = collect(editor.document, true).map((item) => decoration.render(item, editor.document)).filter((opt) => opt !== void 0);
|
|
98
|
+
editor.setDecorations(decorationType, decorations);
|
|
99
|
+
};
|
|
100
|
+
updateDecorations(vscode.window.activeTextEditor);
|
|
101
|
+
disposables.push(vscode.window.onDidChangeActiveTextEditor(updateDecorations), vscode.workspace.onDidChangeTextDocument((e) => {
|
|
102
|
+
if (vscode.window.activeTextEditor?.document === e.document) updateDecorations(vscode.window.activeTextEditor);
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
return disposables;
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
cache,
|
|
109
|
+
collect,
|
|
110
|
+
decorationType,
|
|
111
|
+
register
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Vue 文件过滤器 */
|
|
115
|
+
const vueFileFilter = (document) => document.fileName.endsWith(".vue");
|
|
116
|
+
function createExtFilter(...exts) {
|
|
117
|
+
return (document) => exts.some((ext) => document.fileName.endsWith(ext));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
exports.createExtFilter = createExtFilter;
|
|
122
|
+
exports.createRange = createRange;
|
|
123
|
+
exports.createRegexCollector = createRegexCollector;
|
|
124
|
+
exports.definePatternProvider = definePatternProvider;
|
|
125
|
+
exports.getItemExtra = getItemExtra;
|
|
126
|
+
exports.vueFileFilter = vueFileFilter;
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { CodeLens, CompletionItem, DecorationOptions, Disposable, DocumentLink, DocumentSelector, Hover, Position, Range, TextDocument, TextEditorDecorationType, Uri, window } from "vscode";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
/** 匹配结果项 */
|
|
5
|
+
interface MatchItem<TExtra = unknown> {
|
|
6
|
+
/** 匹配的文本 */
|
|
7
|
+
text: string;
|
|
8
|
+
/** 匹配开始索引 */
|
|
9
|
+
startIndex: number;
|
|
10
|
+
/** 匹配结束索引 */
|
|
11
|
+
endIndex: number;
|
|
12
|
+
/** 额外数据,用于 Hover/Completion/Decoration */
|
|
13
|
+
extra?: TExtra;
|
|
14
|
+
}
|
|
15
|
+
/** 收集函数类型 */
|
|
16
|
+
type CollectFn<TExtra = unknown> = (document: TextDocument, skipCache?: boolean) => Array<MatchItem<TExtra>>;
|
|
17
|
+
/** DocumentLink 配置 */
|
|
18
|
+
interface DocumentLinkConfig<TExtra = unknown> {
|
|
19
|
+
/** 是否启用 */
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
/** 生成链接目标 URI */
|
|
22
|
+
target?: (item: MatchItem<TExtra>, document: TextDocument) => Uri | undefined;
|
|
23
|
+
/** 生成 tooltip */
|
|
24
|
+
tooltip?: (item: MatchItem<TExtra>, document: TextDocument) => string | undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Hover 配置 */
|
|
27
|
+
interface HoverConfig<TExtra = unknown> {
|
|
28
|
+
/** 是否启用 */
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
/** 生成 Hover 内容 */
|
|
31
|
+
content: (item: MatchItem<TExtra>, document: TextDocument) => Hover | undefined;
|
|
32
|
+
}
|
|
33
|
+
/** Completion 配置 */
|
|
34
|
+
interface CompletionConfig {
|
|
35
|
+
/** 是否启用 */
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
/** 触发字符 */
|
|
38
|
+
triggerCharacters?: string[];
|
|
39
|
+
/** 判断当前位置是否应该触发补全 */
|
|
40
|
+
shouldTrigger?: (document: TextDocument, position: Position) => boolean;
|
|
41
|
+
/** 生成补全项列表 */
|
|
42
|
+
items: (document: TextDocument, position: Position) => CompletionItem[] | undefined;
|
|
43
|
+
}
|
|
44
|
+
/** Decoration 配置 */
|
|
45
|
+
interface DecorationConfig<TExtra = unknown> {
|
|
46
|
+
/** 是否启用 */
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
/** 装饰器类型样式 */
|
|
49
|
+
style?: Parameters<typeof window.createTextEditorDecorationType>[0];
|
|
50
|
+
/** 生成装饰器选项 */
|
|
51
|
+
render: (item: MatchItem<TExtra>, document: TextDocument) => DecorationOptions | undefined;
|
|
52
|
+
}
|
|
53
|
+
/** CodeLens 配置 */
|
|
54
|
+
interface CodeLensConfig<TExtra = unknown> {
|
|
55
|
+
/** 是否启用 */
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
/** 生成 CodeLens 列表 */
|
|
58
|
+
lenses: (item: MatchItem<TExtra>, document: TextDocument) => CodeLens[] | undefined;
|
|
59
|
+
}
|
|
60
|
+
/** 命令配置 */
|
|
61
|
+
interface CommandConfig {
|
|
62
|
+
/** 命令 ID */
|
|
63
|
+
id: string;
|
|
64
|
+
/** 命令处理函数 */
|
|
65
|
+
handler: (...args: any[]) => any;
|
|
66
|
+
}
|
|
67
|
+
/** 配置选项 */
|
|
68
|
+
interface PatternProviderOptions<TExtra = unknown> {
|
|
69
|
+
/** 文档选择器 */
|
|
70
|
+
selector?: DocumentSelector;
|
|
71
|
+
/** 文件过滤器,返回 false 则跳过该文档 */
|
|
72
|
+
fileFilter?: (document: TextDocument) => boolean;
|
|
73
|
+
/** 收集匹配项的函数 */
|
|
74
|
+
collect: CollectFn<TExtra>;
|
|
75
|
+
/** DocumentLink 配置 */
|
|
76
|
+
documentLink?: DocumentLinkConfig<TExtra>;
|
|
77
|
+
/** Hover 配置 */
|
|
78
|
+
hover?: HoverConfig<TExtra>;
|
|
79
|
+
/** Completion 配置 */
|
|
80
|
+
completion?: CompletionConfig;
|
|
81
|
+
/** Decoration 配置 */
|
|
82
|
+
decoration?: DecorationConfig<TExtra>;
|
|
83
|
+
/** CodeLens 配置 */
|
|
84
|
+
codeLens?: CodeLensConfig<TExtra>;
|
|
85
|
+
/** 注册的命令 */
|
|
86
|
+
commands?: CommandConfig[];
|
|
87
|
+
}
|
|
88
|
+
/** 返回值 */
|
|
89
|
+
interface PatternProviderReturn<TExtra = unknown> {
|
|
90
|
+
/** 缓存的链接 */
|
|
91
|
+
cache: WeakMap<Uri, Array<MatchItem<TExtra>>>;
|
|
92
|
+
/** 收集匹配项 */
|
|
93
|
+
collect: (document: TextDocument, skipCache?: boolean) => Array<MatchItem<TExtra>>;
|
|
94
|
+
/** 装饰器类型(如果启用) */
|
|
95
|
+
decorationType?: TextEditorDecorationType;
|
|
96
|
+
/** 注册所有 Provider,返回 disposables */
|
|
97
|
+
register: () => Disposable[];
|
|
98
|
+
}
|
|
99
|
+
/** 创建基于正则的收集函数 - 无 context */
|
|
100
|
+
declare function createRegexCollector<TExtra = unknown>(regex: RegExp, transform?: (match: RegExpExecArray) => MatchItem<TExtra> | undefined): CollectFn<TExtra>;
|
|
101
|
+
/** 创建基于正则的收集函数 - 带 context */
|
|
102
|
+
declare function createRegexCollector<TContext, TExtra = unknown>(regex: RegExp, setupContext: () => TContext, transform: (match: RegExpExecArray, context: TContext) => MatchItem<TExtra> | undefined): CollectFn<TExtra>;
|
|
103
|
+
/** 从匹配项创建 Range */
|
|
104
|
+
declare function createRange(document: TextDocument, item: MatchItem): Range;
|
|
105
|
+
/** 获取匹配项的 extra 数据 */
|
|
106
|
+
declare function getItemExtra<TExtra>(link: DocumentLink): TExtra | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* 定义模式匹配 Provider,统一管理 DocumentLink、Hover、Completion、Decoration、CodeLens 等功能
|
|
109
|
+
*/
|
|
110
|
+
declare function definePatternProvider<TExtra = unknown>(options: PatternProviderOptions<TExtra>): PatternProviderReturn<TExtra>;
|
|
111
|
+
/** Vue 文件过滤器 */
|
|
112
|
+
declare const vueFileFilter: (document: TextDocument) => boolean;
|
|
113
|
+
declare function createExtFilter(...exts: string[]): (document: TextDocument) => boolean;
|
|
114
|
+
//#endregion
|
|
115
|
+
export { CodeLensConfig, CollectFn, CommandConfig, CompletionConfig, DecorationConfig, DocumentLinkConfig, HoverConfig, MatchItem, PatternProviderOptions, PatternProviderReturn, createExtFilter, createRange, createRegexCollector, definePatternProvider, getItemExtra, vueFileFilter };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { DocumentLink, Range, commands, languages, window, workspace } from "vscode";
|
|
2
|
+
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
/** 创建基于正则的收集函数 */
|
|
5
|
+
function createRegexCollector(regex, setupContextOrTransform, transform) {
|
|
6
|
+
const hasSetupContext = transform !== void 0;
|
|
7
|
+
const setupContext = hasSetupContext ? setupContextOrTransform : void 0;
|
|
8
|
+
const transformFn = hasSetupContext ? transform : setupContextOrTransform;
|
|
9
|
+
const context = setupContext?.();
|
|
10
|
+
return (document) => {
|
|
11
|
+
const items = [];
|
|
12
|
+
const text = document.getText();
|
|
13
|
+
const globalRegex = new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
|
|
14
|
+
let match;
|
|
15
|
+
while ((match = globalRegex.exec(text)) !== null) if (transformFn) {
|
|
16
|
+
const item = hasSetupContext ? transformFn(match, context) : transformFn(match);
|
|
17
|
+
if (item) items.push(item);
|
|
18
|
+
} else items.push({
|
|
19
|
+
text: match[0],
|
|
20
|
+
startIndex: match.index,
|
|
21
|
+
endIndex: match.index + match[0].length
|
|
22
|
+
});
|
|
23
|
+
return items;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** 从匹配项创建 Range */
|
|
27
|
+
function createRange(document, item) {
|
|
28
|
+
return new Range(document.positionAt(item.startIndex), document.positionAt(item.endIndex));
|
|
29
|
+
}
|
|
30
|
+
/** 获取匹配项的 extra 数据 */
|
|
31
|
+
function getItemExtra(link) {
|
|
32
|
+
return link.__extra;
|
|
33
|
+
}
|
|
34
|
+
/** 设置匹配项的 extra 数据到 DocumentLink */
|
|
35
|
+
function setItemExtra(link, extra) {
|
|
36
|
+
link.__extra = extra;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 定义模式匹配 Provider,统一管理 DocumentLink、Hover、Completion、Decoration、CodeLens 等功能
|
|
40
|
+
*/
|
|
41
|
+
function definePatternProvider(options) {
|
|
42
|
+
const { selector = { scheme: "file" }, fileFilter = () => true, collect: collectFn, documentLink, hover, completion, decoration, codeLens, commands: commandsConfig } = options;
|
|
43
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
function collect(document, skipCache = false) {
|
|
45
|
+
if (!skipCache && cache.has(document.uri)) return cache.get(document.uri);
|
|
46
|
+
const items = collectFn(document, skipCache);
|
|
47
|
+
cache.set(document.uri, items);
|
|
48
|
+
return items;
|
|
49
|
+
}
|
|
50
|
+
const decorationType = decoration?.enabled && decoration.style ? window.createTextEditorDecorationType(decoration.style) : void 0;
|
|
51
|
+
function createDocumentLinkProvider() {
|
|
52
|
+
return { provideDocumentLinks(document) {
|
|
53
|
+
if (!fileFilter(document)) return;
|
|
54
|
+
return collect(document).map(function(item) {
|
|
55
|
+
const range = createRange(document, item);
|
|
56
|
+
const target = documentLink?.target?.(item, document);
|
|
57
|
+
const link = new DocumentLink(range, target);
|
|
58
|
+
link.tooltip = documentLink?.tooltip?.(item, document);
|
|
59
|
+
setItemExtra(link, item.extra);
|
|
60
|
+
return link;
|
|
61
|
+
});
|
|
62
|
+
} };
|
|
63
|
+
}
|
|
64
|
+
function createHoverProvider() {
|
|
65
|
+
return { provideHover(document, position) {
|
|
66
|
+
if (!fileFilter(document)) return;
|
|
67
|
+
const items = collect(document);
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
if (!createRange(document, item).contains(position)) continue;
|
|
70
|
+
return hover?.content(item, document);
|
|
71
|
+
}
|
|
72
|
+
} };
|
|
73
|
+
}
|
|
74
|
+
function createCompletionProvider() {
|
|
75
|
+
return { provideCompletionItems(document, position) {
|
|
76
|
+
if (!fileFilter(document)) return;
|
|
77
|
+
if (completion?.shouldTrigger && !completion.shouldTrigger(document, position)) return;
|
|
78
|
+
return completion?.items(document, position);
|
|
79
|
+
} };
|
|
80
|
+
}
|
|
81
|
+
function createCodeLensProvider() {
|
|
82
|
+
return { provideCodeLenses(document) {
|
|
83
|
+
if (!fileFilter(document)) return;
|
|
84
|
+
return collect(document).flatMap((item) => codeLens?.lenses(item, document) ?? []);
|
|
85
|
+
} };
|
|
86
|
+
}
|
|
87
|
+
function register() {
|
|
88
|
+
const disposables = [];
|
|
89
|
+
if (commandsConfig?.length) for (const cmd of commandsConfig) disposables.push(commands.registerCommand(cmd.id, cmd.handler));
|
|
90
|
+
if (documentLink?.enabled !== false) disposables.push(languages.registerDocumentLinkProvider(selector, createDocumentLinkProvider()));
|
|
91
|
+
if (hover?.enabled) disposables.push(languages.registerHoverProvider(selector, createHoverProvider()));
|
|
92
|
+
if (completion?.enabled) disposables.push(languages.registerCompletionItemProvider(selector, createCompletionProvider(), ...completion.triggerCharacters ?? []));
|
|
93
|
+
if (codeLens?.enabled) disposables.push(languages.registerCodeLensProvider(selector, createCodeLensProvider()));
|
|
94
|
+
if (decoration?.enabled && decorationType) {
|
|
95
|
+
const updateDecorations = (editor) => {
|
|
96
|
+
if (!editor || !fileFilter(editor.document)) return;
|
|
97
|
+
const decorations = collect(editor.document, true).map((item) => decoration.render(item, editor.document)).filter((opt) => opt !== void 0);
|
|
98
|
+
editor.setDecorations(decorationType, decorations);
|
|
99
|
+
};
|
|
100
|
+
updateDecorations(window.activeTextEditor);
|
|
101
|
+
disposables.push(window.onDidChangeActiveTextEditor(updateDecorations), workspace.onDidChangeTextDocument((e) => {
|
|
102
|
+
if (window.activeTextEditor?.document === e.document) updateDecorations(window.activeTextEditor);
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
return disposables;
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
cache,
|
|
109
|
+
collect,
|
|
110
|
+
decorationType,
|
|
111
|
+
register
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Vue 文件过滤器 */
|
|
115
|
+
const vueFileFilter = (document) => document.fileName.endsWith(".vue");
|
|
116
|
+
function createExtFilter(...exts) {
|
|
117
|
+
return (document) => exts.some((ext) => document.fileName.endsWith(ext));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
export { createExtFilter, createRange, createRegexCollector, definePatternProvider, getItemExtra, vueFileFilter };
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sunshj/vscode-ab5x-utils",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "AB5X VSCode Utils",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [],
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"author": "sunshj",
|
|
9
|
+
"main": "dist/index.cjs",
|
|
10
|
+
"module": "dist/index.mjs",
|
|
11
|
+
"types": "dist/index.d.mts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.mts",
|
|
15
|
+
"require": "./dist/index.cjs",
|
|
16
|
+
"import": "./dist/index.mjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsdown",
|
|
24
|
+
"prepublishOnly": "pnpm build",
|
|
25
|
+
"release": "bumpp && npm publish"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commands,
|
|
3
|
+
DocumentLink,
|
|
4
|
+
languages,
|
|
5
|
+
Range,
|
|
6
|
+
window,
|
|
7
|
+
workspace,
|
|
8
|
+
type CodeLens,
|
|
9
|
+
type CodeLensProvider,
|
|
10
|
+
type CompletionItem,
|
|
11
|
+
type CompletionItemProvider,
|
|
12
|
+
type CompletionList,
|
|
13
|
+
type DecorationOptions,
|
|
14
|
+
type Disposable,
|
|
15
|
+
type DocumentLinkProvider,
|
|
16
|
+
type DocumentSelector,
|
|
17
|
+
type Hover,
|
|
18
|
+
type HoverProvider,
|
|
19
|
+
type Position,
|
|
20
|
+
type ProviderResult,
|
|
21
|
+
type TextDocument,
|
|
22
|
+
type TextEditor,
|
|
23
|
+
type TextEditorDecorationType,
|
|
24
|
+
type Uri
|
|
25
|
+
} from 'vscode'
|
|
26
|
+
|
|
27
|
+
/** 匹配结果项 */
|
|
28
|
+
export interface MatchItem<TExtra = unknown> {
|
|
29
|
+
/** 匹配的文本 */
|
|
30
|
+
text: string
|
|
31
|
+
/** 匹配开始索引 */
|
|
32
|
+
startIndex: number
|
|
33
|
+
/** 匹配结束索引 */
|
|
34
|
+
endIndex: number
|
|
35
|
+
/** 额外数据,用于 Hover/Completion/Decoration */
|
|
36
|
+
extra?: TExtra
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 收集函数类型 */
|
|
40
|
+
export type CollectFn<TExtra = unknown> = (
|
|
41
|
+
document: TextDocument,
|
|
42
|
+
skipCache?: boolean
|
|
43
|
+
) => Array<MatchItem<TExtra>>
|
|
44
|
+
|
|
45
|
+
/** DocumentLink 配置 */
|
|
46
|
+
export interface DocumentLinkConfig<TExtra = unknown> {
|
|
47
|
+
/** 是否启用 */
|
|
48
|
+
enabled?: boolean
|
|
49
|
+
/** 生成链接目标 URI */
|
|
50
|
+
target?: (item: MatchItem<TExtra>, document: TextDocument) => Uri | undefined
|
|
51
|
+
/** 生成 tooltip */
|
|
52
|
+
tooltip?: (item: MatchItem<TExtra>, document: TextDocument) => string | undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Hover 配置 */
|
|
56
|
+
export interface HoverConfig<TExtra = unknown> {
|
|
57
|
+
/** 是否启用 */
|
|
58
|
+
enabled?: boolean
|
|
59
|
+
/** 生成 Hover 内容 */
|
|
60
|
+
content: (item: MatchItem<TExtra>, document: TextDocument) => Hover | undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Completion 配置 */
|
|
64
|
+
export interface CompletionConfig {
|
|
65
|
+
/** 是否启用 */
|
|
66
|
+
enabled?: boolean
|
|
67
|
+
/** 触发字符 */
|
|
68
|
+
triggerCharacters?: string[]
|
|
69
|
+
/** 判断当前位置是否应该触发补全 */
|
|
70
|
+
shouldTrigger?: (document: TextDocument, position: Position) => boolean
|
|
71
|
+
/** 生成补全项列表 */
|
|
72
|
+
items: (document: TextDocument, position: Position) => CompletionItem[] | undefined
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Decoration 配置 */
|
|
76
|
+
export interface DecorationConfig<TExtra = unknown> {
|
|
77
|
+
/** 是否启用 */
|
|
78
|
+
enabled?: boolean
|
|
79
|
+
/** 装饰器类型样式 */
|
|
80
|
+
style?: Parameters<typeof window.createTextEditorDecorationType>[0]
|
|
81
|
+
/** 生成装饰器选项 */
|
|
82
|
+
render: (item: MatchItem<TExtra>, document: TextDocument) => DecorationOptions | undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** CodeLens 配置 */
|
|
86
|
+
export interface CodeLensConfig<TExtra = unknown> {
|
|
87
|
+
/** 是否启用 */
|
|
88
|
+
enabled?: boolean
|
|
89
|
+
/** 生成 CodeLens 列表 */
|
|
90
|
+
lenses: (item: MatchItem<TExtra>, document: TextDocument) => CodeLens[] | undefined
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 命令配置 */
|
|
94
|
+
export interface CommandConfig {
|
|
95
|
+
/** 命令 ID */
|
|
96
|
+
id: string
|
|
97
|
+
/** 命令处理函数 */
|
|
98
|
+
handler: (...args: any[]) => any
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 配置选项 */
|
|
102
|
+
export interface PatternProviderOptions<TExtra = unknown> {
|
|
103
|
+
/** 文档选择器 */
|
|
104
|
+
selector?: DocumentSelector
|
|
105
|
+
/** 文件过滤器,返回 false 则跳过该文档 */
|
|
106
|
+
fileFilter?: (document: TextDocument) => boolean
|
|
107
|
+
/** 收集匹配项的函数 */
|
|
108
|
+
collect: CollectFn<TExtra>
|
|
109
|
+
/** DocumentLink 配置 */
|
|
110
|
+
documentLink?: DocumentLinkConfig<TExtra>
|
|
111
|
+
/** Hover 配置 */
|
|
112
|
+
hover?: HoverConfig<TExtra>
|
|
113
|
+
/** Completion 配置 */
|
|
114
|
+
completion?: CompletionConfig
|
|
115
|
+
/** Decoration 配置 */
|
|
116
|
+
decoration?: DecorationConfig<TExtra>
|
|
117
|
+
/** CodeLens 配置 */
|
|
118
|
+
codeLens?: CodeLensConfig<TExtra>
|
|
119
|
+
/** 注册的命令 */
|
|
120
|
+
commands?: CommandConfig[]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 返回值 */
|
|
124
|
+
export interface PatternProviderReturn<TExtra = unknown> {
|
|
125
|
+
/** 缓存的链接 */
|
|
126
|
+
cache: WeakMap<Uri, Array<MatchItem<TExtra>>>
|
|
127
|
+
/** 收集匹配项 */
|
|
128
|
+
collect: (document: TextDocument, skipCache?: boolean) => Array<MatchItem<TExtra>>
|
|
129
|
+
/** 装饰器类型(如果启用) */
|
|
130
|
+
decorationType?: TextEditorDecorationType
|
|
131
|
+
/** 注册所有 Provider,返回 disposables */
|
|
132
|
+
register: () => Disposable[]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 创建基于正则的收集函数 - 无 context */
|
|
136
|
+
export function createRegexCollector<TExtra = unknown>(
|
|
137
|
+
regex: RegExp,
|
|
138
|
+
transform?: (match: RegExpExecArray) => MatchItem<TExtra> | undefined
|
|
139
|
+
): CollectFn<TExtra>
|
|
140
|
+
|
|
141
|
+
/** 创建基于正则的收集函数 - 带 context */
|
|
142
|
+
export function createRegexCollector<TContext, TExtra = unknown>(
|
|
143
|
+
regex: RegExp,
|
|
144
|
+
setupContext: () => TContext,
|
|
145
|
+
transform: (match: RegExpExecArray, context: TContext) => MatchItem<TExtra> | undefined
|
|
146
|
+
): CollectFn<TExtra>
|
|
147
|
+
|
|
148
|
+
/** 创建基于正则的收集函数 */
|
|
149
|
+
export function createRegexCollector<TContext = undefined, TExtra = unknown>(
|
|
150
|
+
regex: RegExp,
|
|
151
|
+
setupContextOrTransform?:
|
|
152
|
+
| (() => TContext)
|
|
153
|
+
| ((match: RegExpExecArray) => MatchItem<TExtra> | undefined),
|
|
154
|
+
transform?: (match: RegExpExecArray, context: TContext) => MatchItem<TExtra> | undefined
|
|
155
|
+
): CollectFn<TExtra> {
|
|
156
|
+
// 判断第二个参数是 setupContext 还是 transform
|
|
157
|
+
const hasSetupContext = transform !== undefined
|
|
158
|
+
const setupContext = hasSetupContext ? (setupContextOrTransform as () => TContext) : undefined
|
|
159
|
+
const transformFn = hasSetupContext
|
|
160
|
+
? transform
|
|
161
|
+
: (setupContextOrTransform as
|
|
162
|
+
| ((match: RegExpExecArray) => MatchItem<TExtra> | undefined)
|
|
163
|
+
| undefined)
|
|
164
|
+
|
|
165
|
+
const context = setupContext?.()
|
|
166
|
+
|
|
167
|
+
return (document: TextDocument) => {
|
|
168
|
+
const items: Array<MatchItem<TExtra>> = []
|
|
169
|
+
const text = document.getText()
|
|
170
|
+
// 确保正则有 g 标志
|
|
171
|
+
const globalRegex = new RegExp(
|
|
172
|
+
regex.source,
|
|
173
|
+
regex.flags.includes('g') ? regex.flags : `${regex.flags}g`
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
let match: RegExpExecArray | null
|
|
177
|
+
while ((match = globalRegex.exec(text)) !== null) {
|
|
178
|
+
if (transformFn) {
|
|
179
|
+
const item = hasSetupContext
|
|
180
|
+
? (transformFn as (m: RegExpExecArray, c: TContext) => MatchItem<TExtra> | undefined)(
|
|
181
|
+
match,
|
|
182
|
+
context!
|
|
183
|
+
)
|
|
184
|
+
: (transformFn as (m: RegExpExecArray) => MatchItem<TExtra> | undefined)(match)
|
|
185
|
+
if (item) items.push(item)
|
|
186
|
+
} else {
|
|
187
|
+
items.push({
|
|
188
|
+
text: match[0],
|
|
189
|
+
startIndex: match.index,
|
|
190
|
+
endIndex: match.index + match[0].length
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return items
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** 从匹配项创建 Range */
|
|
199
|
+
export function createRange(document: TextDocument, item: MatchItem): Range {
|
|
200
|
+
return new Range(document.positionAt(item.startIndex), document.positionAt(item.endIndex))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** 获取匹配项的 extra 数据 */
|
|
204
|
+
export function getItemExtra<TExtra>(link: DocumentLink): TExtra | undefined {
|
|
205
|
+
return (link as any).__extra as TExtra
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 设置匹配项的 extra 数据到 DocumentLink */
|
|
209
|
+
function setItemExtra<TExtra>(link: DocumentLink, extra: TExtra): void {
|
|
210
|
+
;(link as any).__extra = extra
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 定义模式匹配 Provider,统一管理 DocumentLink、Hover、Completion、Decoration、CodeLens 等功能
|
|
215
|
+
*/
|
|
216
|
+
export function definePatternProvider<TExtra = unknown>(
|
|
217
|
+
options: PatternProviderOptions<TExtra>
|
|
218
|
+
): PatternProviderReturn<TExtra> {
|
|
219
|
+
const {
|
|
220
|
+
selector = { scheme: 'file' },
|
|
221
|
+
fileFilter = () => true,
|
|
222
|
+
collect: collectFn,
|
|
223
|
+
documentLink,
|
|
224
|
+
hover,
|
|
225
|
+
completion,
|
|
226
|
+
decoration,
|
|
227
|
+
codeLens,
|
|
228
|
+
commands: commandsConfig
|
|
229
|
+
} = options
|
|
230
|
+
|
|
231
|
+
const cache = new WeakMap<Uri, Array<MatchItem<TExtra>>>()
|
|
232
|
+
|
|
233
|
+
// 带缓存的收集函数
|
|
234
|
+
function collect(document: TextDocument, skipCache = false): Array<MatchItem<TExtra>> {
|
|
235
|
+
if (!skipCache && cache.has(document.uri)) {
|
|
236
|
+
return cache.get(document.uri)!
|
|
237
|
+
}
|
|
238
|
+
const items = collectFn(document, skipCache)
|
|
239
|
+
cache.set(document.uri, items)
|
|
240
|
+
return items
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 创建装饰器类型(如果启用)
|
|
244
|
+
const decorationType =
|
|
245
|
+
decoration?.enabled && decoration.style
|
|
246
|
+
? window.createTextEditorDecorationType(decoration.style)
|
|
247
|
+
: undefined
|
|
248
|
+
|
|
249
|
+
// DocumentLink Provider
|
|
250
|
+
function createDocumentLinkProvider(): DocumentLinkProvider {
|
|
251
|
+
return {
|
|
252
|
+
provideDocumentLinks(document: TextDocument): ProviderResult<DocumentLink[]> {
|
|
253
|
+
if (!fileFilter(document)) return
|
|
254
|
+
const items = collect(document)
|
|
255
|
+
return items.map(function (item) {
|
|
256
|
+
const range = createRange(document, item)
|
|
257
|
+
const target = documentLink?.target?.(item, document)
|
|
258
|
+
const link = new DocumentLink(range, target)
|
|
259
|
+
link.tooltip = documentLink?.tooltip?.(item, document)
|
|
260
|
+
setItemExtra(link, item.extra)
|
|
261
|
+
return link
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Hover Provider
|
|
268
|
+
function createHoverProvider(): HoverProvider {
|
|
269
|
+
return {
|
|
270
|
+
provideHover(document: TextDocument, position: Position): ProviderResult<Hover> {
|
|
271
|
+
if (!fileFilter(document)) return
|
|
272
|
+
const items = collect(document)
|
|
273
|
+
|
|
274
|
+
for (const item of items) {
|
|
275
|
+
const range = createRange(document, item)
|
|
276
|
+
if (!range.contains(position)) continue
|
|
277
|
+
return hover?.content(item, document)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Completion Provider
|
|
284
|
+
function createCompletionProvider(): CompletionItemProvider {
|
|
285
|
+
return {
|
|
286
|
+
provideCompletionItems(
|
|
287
|
+
document: TextDocument,
|
|
288
|
+
position: Position
|
|
289
|
+
): ProviderResult<CompletionItem[] | CompletionList<CompletionItem>> {
|
|
290
|
+
if (!fileFilter(document)) return
|
|
291
|
+
if (completion?.shouldTrigger && !completion.shouldTrigger(document, position)) return
|
|
292
|
+
return completion?.items(document, position)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// CodeLens Provider
|
|
298
|
+
function createCodeLensProvider(): CodeLensProvider {
|
|
299
|
+
return {
|
|
300
|
+
provideCodeLenses(document: TextDocument): ProviderResult<CodeLens[]> {
|
|
301
|
+
if (!fileFilter(document)) return
|
|
302
|
+
const items = collect(document)
|
|
303
|
+
return items.flatMap(item => codeLens?.lenses(item, document) ?? [])
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 注册函数
|
|
309
|
+
function register(): Disposable[] {
|
|
310
|
+
const disposables: Disposable[] = []
|
|
311
|
+
|
|
312
|
+
// 注册命令
|
|
313
|
+
if (commandsConfig?.length) {
|
|
314
|
+
for (const cmd of commandsConfig) {
|
|
315
|
+
disposables.push(commands.registerCommand(cmd.id, cmd.handler))
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 注册 DocumentLink Provider
|
|
320
|
+
if (documentLink?.enabled !== false) {
|
|
321
|
+
disposables.push(
|
|
322
|
+
languages.registerDocumentLinkProvider(selector, createDocumentLinkProvider())
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 注册 Hover Provider
|
|
327
|
+
if (hover?.enabled) {
|
|
328
|
+
disposables.push(languages.registerHoverProvider(selector, createHoverProvider()))
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 注册 Completion Provider
|
|
332
|
+
if (completion?.enabled) {
|
|
333
|
+
disposables.push(
|
|
334
|
+
languages.registerCompletionItemProvider(
|
|
335
|
+
selector,
|
|
336
|
+
createCompletionProvider(),
|
|
337
|
+
...(completion.triggerCharacters ?? [])
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 注册 CodeLens Provider
|
|
343
|
+
if (codeLens?.enabled) {
|
|
344
|
+
disposables.push(languages.registerCodeLensProvider(selector, createCodeLensProvider()))
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 注册 Decoration
|
|
348
|
+
if (decoration?.enabled && decorationType) {
|
|
349
|
+
const updateDecorations = (editor: TextEditor | undefined) => {
|
|
350
|
+
if (!editor || !fileFilter(editor.document)) return
|
|
351
|
+
const items = collect(editor.document, true)
|
|
352
|
+
const decorations = items
|
|
353
|
+
.map(item => decoration.render(item, editor.document))
|
|
354
|
+
.filter((opt): opt is DecorationOptions => opt !== undefined)
|
|
355
|
+
editor.setDecorations(decorationType, decorations)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 初始更新
|
|
359
|
+
updateDecorations(window.activeTextEditor)
|
|
360
|
+
|
|
361
|
+
disposables.push(
|
|
362
|
+
// 监听编辑器切换
|
|
363
|
+
window.onDidChangeActiveTextEditor(updateDecorations),
|
|
364
|
+
// 监听文档变化
|
|
365
|
+
workspace.onDidChangeTextDocument(e => {
|
|
366
|
+
if (window.activeTextEditor?.document === e.document) {
|
|
367
|
+
updateDecorations(window.activeTextEditor)
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return disposables
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
cache,
|
|
378
|
+
collect,
|
|
379
|
+
decorationType,
|
|
380
|
+
register
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Vue 文件过滤器 */
|
|
385
|
+
export const vueFileFilter = (document: TextDocument) => document.fileName.endsWith('.vue')
|
|
386
|
+
export function createExtFilter(...exts: string[]) {
|
|
387
|
+
return (document: TextDocument) => exts.some(ext => document.fileName.endsWith(ext))
|
|
388
|
+
}
|
package/tsdown.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig, type UserConfig } from 'tsdown'
|
|
2
|
+
|
|
3
|
+
const sharedConfig: UserConfig = {
|
|
4
|
+
entry: ['src/index.ts'],
|
|
5
|
+
platform: 'node',
|
|
6
|
+
external: ['vscode']
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default defineConfig([
|
|
10
|
+
{
|
|
11
|
+
...sharedConfig,
|
|
12
|
+
name: 'lib-bundle:esm',
|
|
13
|
+
format: 'esm',
|
|
14
|
+
dts: true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
...sharedConfig,
|
|
18
|
+
name: 'lib-bundle:cjs',
|
|
19
|
+
format: 'cjs',
|
|
20
|
+
dts: false
|
|
21
|
+
}
|
|
22
|
+
])
|