@master/css-language-service 0.0.0-site.1793659883.g69c9aebcf628
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/README.md +131 -0
- package/dist/common.d.ts +2 -0
- package/dist/common.js +1 -0
- package/dist/core.d.ts +34 -0
- package/dist/core.js +81 -0
- package/dist/document-analysis.d.ts +16 -0
- package/dist/document-analysis.js +34 -0
- package/dist/features/edit-syntax-colors.d.ts +5 -0
- package/dist/features/edit-syntax-colors.js +36 -0
- package/dist/features/format-directives.d.ts +5 -0
- package/dist/features/format-directives.js +60 -0
- package/dist/features/inspect-syntax.d.ts +5 -0
- package/dist/features/inspect-syntax.js +48 -0
- package/dist/features/render-semantic-tokens.d.ts +6 -0
- package/dist/features/render-semantic-tokens.js +36 -0
- package/dist/features/render-syntax-colors.d.ts +5 -0
- package/dist/features/render-syntax-colors.js +47 -0
- package/dist/features/suggest-syntax.d.ts +5 -0
- package/dist/features/suggest-syntax.js +165 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/settings.d.ts +21 -0
- package/dist/settings.js +64 -0
- package/dist/shiki/hast.d.ts +13 -0
- package/dist/shiki/hast.js +174 -0
- package/dist/shiki/languages.d.ts +8 -0
- package/dist/shiki/languages.js +69 -0
- package/dist/shiki.d.ts +159 -0
- package/dist/shiki.js +522 -0
- package/dist/utils/beautify-css.d.ts +2 -0
- package/dist/utils/beautify-css.js +12 -0
- package/dist/utils/convert-color-token.d.ts +4 -0
- package/dist/utils/convert-color-token.js +31 -0
- package/dist/utils/create-css-markdown-documentation.d.ts +6 -0
- package/dist/utils/create-css-markdown-documentation.js +12 -0
- package/dist/utils/create-doc.d.ts +17 -0
- package/dist/utils/create-doc.js +20 -0
- package/dist/utils/get-position.d.ts +2 -0
- package/dist/utils/get-position.js +8 -0
- package/dist/utils/get-range.d.ts +3 -0
- package/dist/utils/get-range.js +20 -0
- package/package.json +60 -0
- package/src/shiki.css +7 -0
- package/syntaxes/master-css.tmLanguage.json +881 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { analyzeDocument, documentRequest } from "../document-analysis.js";
|
|
2
|
+
import createCSSMarkdownDocumentation from "../utils/create-css-markdown-documentation.js";
|
|
3
|
+
import { CompletionItemKind, InsertTextFormat } from "vscode-languageserver-protocol";
|
|
4
|
+
// src/features/suggest-syntax.ts
|
|
5
|
+
function sortCompletionItems(items) {
|
|
6
|
+
return items.sort((left, right) => (left.sortText || left.label).localeCompare(right.sortText || right.label, void 0, { numeric: true }));
|
|
7
|
+
}
|
|
8
|
+
function documentation(service, entry, className) {
|
|
9
|
+
const text = entry.documentationText || service.session.inspectClassName(className).text;
|
|
10
|
+
return text ? createCSSMarkdownDocumentation(text) : void 0;
|
|
11
|
+
}
|
|
12
|
+
function classCompletionItem(service, entry, label = entry.label, className = entry.label, generateDocumentation = false) {
|
|
13
|
+
return {
|
|
14
|
+
label,
|
|
15
|
+
kind: entry.kind === "property" ? CompletionItemKind.Property : entry.kind === "function" ? CompletionItemKind.Function : CompletionItemKind.Value,
|
|
16
|
+
insertText: entry.kind === "function" ? entry.insertText ?? `${label.slice(0, -2)}($0)` : void 0,
|
|
17
|
+
insertTextFormat: entry.kind === "function" ? InsertTextFormat.Snippet : void 0,
|
|
18
|
+
detail: entry.detail,
|
|
19
|
+
documentation: entry.documentationText || generateDocumentation ? documentation(service, entry, className) : void 0,
|
|
20
|
+
sortText: entry.sortText,
|
|
21
|
+
command: entry.triggerSuggest ? {
|
|
22
|
+
title: "Suggest",
|
|
23
|
+
command: "editor.action.triggerSuggest"
|
|
24
|
+
} : void 0
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Root candidates for the class typed so far. A `(property:` head also matches the
|
|
29
|
+
* bare property name, and replaces the whole typed class so its parenthesis is
|
|
30
|
+
* never doubled.
|
|
31
|
+
*/
|
|
32
|
+
function rootCompletionItems(service, entries, query, range) {
|
|
33
|
+
return sortCompletionItems(entries.filter(({ label }) => {
|
|
34
|
+
if (label.startsWith(":") || label.startsWith("@")) return false;
|
|
35
|
+
if (label.includes(":") && !label.endsWith(":")) return false;
|
|
36
|
+
return !query || label.startsWith(query) || label.startsWith("(") && label.startsWith(query, 1);
|
|
37
|
+
}).map((entry) => {
|
|
38
|
+
const item = classCompletionItem(service, entry);
|
|
39
|
+
if (range) {
|
|
40
|
+
item.filterText = entry.label.startsWith("(") && !query.startsWith("(") ? entry.label.slice(1) : entry.label;
|
|
41
|
+
item.textEdit = {
|
|
42
|
+
range,
|
|
43
|
+
newText: item.insertText ?? entry.label
|
|
44
|
+
};
|
|
45
|
+
item.insertText = void 0;
|
|
46
|
+
}
|
|
47
|
+
return item;
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
/** Values of a `(property:` head; `suffix` is the text the head still lacks, such as its `)`. */
|
|
51
|
+
function valueCompletionItems(service, entries, key, valuePrefix, range, suffix) {
|
|
52
|
+
const prefix = `(${key}:`;
|
|
53
|
+
return sortCompletionItems(entries.filter(({ kind, label }) => kind !== "property" && label.startsWith(prefix) && label.endsWith(")")).filter(({ label }) => valuePrefix.startsWith("-") || !label.slice(prefix.length).startsWith("-")).map((entry) => {
|
|
54
|
+
const value = entry.label.slice(prefix.length, -1);
|
|
55
|
+
const item = classCompletionItem(service, entry, value, entry.label, entry.label === "(font-style:italic)");
|
|
56
|
+
item.filterText = value;
|
|
57
|
+
item.textEdit = {
|
|
58
|
+
range,
|
|
59
|
+
newText: entry.kind === "function" ? `${value.slice(0, -1)}$0)${suffix}` : `${value}${suffix}`
|
|
60
|
+
};
|
|
61
|
+
item.insertText = void 0;
|
|
62
|
+
return item;
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
const SELECTOR_TRIGGER = /(?:::?|[_>+~])$/u;
|
|
66
|
+
const hasSyntaxError = (diagnostics) => Boolean(diagnostics?.some(({ code }) => code === "CLASS_SYNTAX_ERROR"));
|
|
67
|
+
function selectorCompletionItems(service, entries, field) {
|
|
68
|
+
const elementOnly = field.endsWith("::");
|
|
69
|
+
const triggerLength = elementOnly ? 2 : field.endsWith(":") ? 1 : 0;
|
|
70
|
+
const byLabel = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (!entry.label.startsWith(":")) continue;
|
|
73
|
+
if (elementOnly && !entry.label.startsWith("::")) continue;
|
|
74
|
+
const label = entry.label;
|
|
75
|
+
if (byLabel.has(label)) continue;
|
|
76
|
+
const insertText = triggerLength ? label.slice(triggerLength) : void 0;
|
|
77
|
+
const className = `${field}${triggerLength ? label.slice(triggerLength) : label}`;
|
|
78
|
+
const includeDocumentation = (/* @__PURE__ */ new Set([
|
|
79
|
+
":first-child",
|
|
80
|
+
":hover",
|
|
81
|
+
"::placeholder"
|
|
82
|
+
])).has(label);
|
|
83
|
+
byLabel.set(label, {
|
|
84
|
+
label,
|
|
85
|
+
insertText,
|
|
86
|
+
kind: CompletionItemKind.Function,
|
|
87
|
+
detail: entry.detail,
|
|
88
|
+
documentation: entry.documentationText || includeDocumentation ? documentation(service, entry, className) : void 0,
|
|
89
|
+
sortText: entry.sortText
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return sortCompletionItems([...byLabel.values()]);
|
|
93
|
+
}
|
|
94
|
+
function queryCompletionItems(entries) {
|
|
95
|
+
return sortCompletionItems(entries.filter(({ label }) => label.startsWith("@")).map((entry) => ({
|
|
96
|
+
label: entry.label,
|
|
97
|
+
filterText: entry.label.slice(1),
|
|
98
|
+
insertText: entry.label.slice(1),
|
|
99
|
+
detail: entry.detail,
|
|
100
|
+
kind: entry.kind === "function" ? CompletionItemKind.Function : CompletionItemKind.Keyword,
|
|
101
|
+
sortText: entry.sortText
|
|
102
|
+
})));
|
|
103
|
+
}
|
|
104
|
+
function suggestSyntax(document, position, context) {
|
|
105
|
+
const offset = document.offsetAt(position);
|
|
106
|
+
const keyframe = analyzeDocument(this, document).keyframePositions.find(({ range }) => offset >= range.start && offset <= range.end);
|
|
107
|
+
if (keyframe) return this.session.completionIndex().keyframes.map((entry) => ({
|
|
108
|
+
label: entry.name,
|
|
109
|
+
kind: CompletionItemKind.Value,
|
|
110
|
+
documentation: createCSSMarkdownDocumentation(entry.text),
|
|
111
|
+
textEdit: {
|
|
112
|
+
range: {
|
|
113
|
+
start: document.positionAt(keyframe.range.start),
|
|
114
|
+
end: document.positionAt(keyframe.range.end)
|
|
115
|
+
},
|
|
116
|
+
newText: keyframe.raw ? entry.insertText : ` ${entry.insertText} `
|
|
117
|
+
}
|
|
118
|
+
}));
|
|
119
|
+
const classPosition = this.getClassPosition(document, position);
|
|
120
|
+
if (!classPosition) return;
|
|
121
|
+
const field = context?.triggerCharacter === " " ? "" : document.getText({
|
|
122
|
+
start: document.positionAt(classPosition.range.start),
|
|
123
|
+
end: position
|
|
124
|
+
});
|
|
125
|
+
const entries = this.session.completionIndex().classEntries;
|
|
126
|
+
if (!field || context?.triggerCharacter === " ") return rootCompletionItems(this, entries, "");
|
|
127
|
+
if (field.startsWith("@") || field.startsWith("~")) return [];
|
|
128
|
+
const typed = {
|
|
129
|
+
start: document.positionAt(classPosition.range.start),
|
|
130
|
+
end: position
|
|
131
|
+
};
|
|
132
|
+
const trigger = field.match(SELECTOR_TRIGGER)?.[0];
|
|
133
|
+
if (trigger && trigger.length < field.length) {
|
|
134
|
+
const head = this.session.inspectClassName(field.slice(0, -trigger.length));
|
|
135
|
+
if (head.kind !== "unknown" && !hasSyntaxError(head.diagnostics)) return selectorCompletionItems(this, entries, field);
|
|
136
|
+
}
|
|
137
|
+
if (field.startsWith("(")) {
|
|
138
|
+
const head = this.session.nativeDeclarationCompletion({
|
|
139
|
+
...documentRequest(this, document),
|
|
140
|
+
offset
|
|
141
|
+
});
|
|
142
|
+
if (head) {
|
|
143
|
+
if (head.kind === "none" || !head.range) return [];
|
|
144
|
+
const range = {
|
|
145
|
+
start: document.positionAt(head.range.start),
|
|
146
|
+
end: document.positionAt(head.range.end)
|
|
147
|
+
};
|
|
148
|
+
if (head.kind === "property") return rootCompletionItems(this, entries, `(${head.prefix}`, {
|
|
149
|
+
start: typed.start,
|
|
150
|
+
end: range.end
|
|
151
|
+
});
|
|
152
|
+
return valueCompletionItems(this, entries, head.property ?? "", head.prefix, range, head.suffix ?? "");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const inspection = this.session.inspectClassName(field);
|
|
156
|
+
if (hasSyntaxError(inspection.diagnostics)) return [];
|
|
157
|
+
const suffix = inspection.suffix ?? "";
|
|
158
|
+
if (suffix.includes("@")) {
|
|
159
|
+
if (field.endsWith("@")) return queryCompletionItems(entries);
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
if (suffix || inspection.kind === "declaration") return [];
|
|
163
|
+
return rootCompletionItems(this, entries, field, typed);
|
|
164
|
+
}
|
|
165
|
+
export { suggestSyntax as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MasterCSSLanguageServiceSettings, defaultLanguageServiceSettings } from "./settings.js";
|
|
2
|
+
import inspectSyntax from "./features/inspect-syntax.js";
|
|
3
|
+
import renderSyntaxColors from "./features/render-syntax-colors.js";
|
|
4
|
+
import editSyntaxColors from "./features/edit-syntax-colors.js";
|
|
5
|
+
import suggestSyntax from "./features/suggest-syntax.js";
|
|
6
|
+
import { MasterCSSLanguageService, MasterCSSLanguageServiceOptions } from "./core.js";
|
|
7
|
+
import { AT_TRIGGER_CHARACTER, INVOKED_TRIGGER_CHARACTERS, QUERY_TRIGGER_CHARACTERS, SELECTOR_TRIGGER_CHARACTERS, VALUE_TRIGGER_CHARACTERS } from "./common.js";
|
|
8
|
+
export { AT_TRIGGER_CHARACTER, INVOKED_TRIGGER_CHARACTERS, MasterCSSLanguageService, type MasterCSSLanguageServiceOptions, type MasterCSSLanguageServiceSettings, QUERY_TRIGGER_CHARACTERS, SELECTOR_TRIGGER_CHARACTERS, VALUE_TRIGGER_CHARACTERS, defaultLanguageServiceSettings, editSyntaxColors, inspectSyntax, renderSyntaxColors, suggestSyntax };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defaultLanguageServiceSettings } from "./settings.js";
|
|
2
|
+
import { AT_TRIGGER_CHARACTER, INVOKED_TRIGGER_CHARACTERS, QUERY_TRIGGER_CHARACTERS, SELECTOR_TRIGGER_CHARACTERS, VALUE_TRIGGER_CHARACTERS } from "./common.js";
|
|
3
|
+
import inspectSyntax from "./features/inspect-syntax.js";
|
|
4
|
+
import renderSyntaxColors from "./features/render-syntax-colors.js";
|
|
5
|
+
import editSyntaxColors from "./features/edit-syntax-colors.js";
|
|
6
|
+
import suggestSyntax from "./features/suggest-syntax.js";
|
|
7
|
+
import { MasterCSSLanguageService } from "./core.js";
|
|
8
|
+
export { AT_TRIGGER_CHARACTER, INVOKED_TRIGGER_CHARACTERS, MasterCSSLanguageService, QUERY_TRIGGER_CHARACTERS, SELECTOR_TRIGGER_CHARACTERS, VALUE_TRIGGER_CHARACTERS, defaultLanguageServiceSettings, editSyntaxColors, inspectSyntax, renderSyntaxColors, suggestSyntax };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MasterCSSManifest } from "@master/css-schema/manifest";
|
|
2
|
+
/**
|
|
3
|
+
* @example styles https://regex101.com/r/HLPdsw/1
|
|
4
|
+
**/
|
|
5
|
+
interface MasterCSSLanguageServiceSettings {
|
|
6
|
+
includedLanguages?: string[];
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
manifest?: MasterCSSManifest;
|
|
9
|
+
classAttributes?: string[];
|
|
10
|
+
classFunctions?: string[];
|
|
11
|
+
classDeclarations?: string[];
|
|
12
|
+
classAttributeBindings?: Record<string, [string, string] | false>;
|
|
13
|
+
embeddedSyntaxHighlighting?: 'active' | 'always' | 'off';
|
|
14
|
+
suggestSyntax?: boolean;
|
|
15
|
+
inspectSyntax?: boolean;
|
|
16
|
+
renderSyntaxColors?: boolean;
|
|
17
|
+
editSyntaxColors?: boolean;
|
|
18
|
+
formatDirectives?: boolean;
|
|
19
|
+
}
|
|
20
|
+
declare const defaultLanguageServiceSettings: Readonly<MasterCSSLanguageServiceSettings>;
|
|
21
|
+
export { MasterCSSLanguageServiceSettings, defaultLanguageServiceSettings };
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CLASS_ATTRIBUTES, CLASS_DECLARATIONS, CLASS_FUNCTIONS } from "@master/css-tooling/language";
|
|
2
|
+
// src/settings.ts
|
|
3
|
+
const defaultLanguageServiceSettings = Object.freeze({
|
|
4
|
+
includedLanguages: Object.freeze([
|
|
5
|
+
"html",
|
|
6
|
+
"php",
|
|
7
|
+
"javascript",
|
|
8
|
+
"typescript",
|
|
9
|
+
"javascriptreact",
|
|
10
|
+
"typescriptreact",
|
|
11
|
+
"css",
|
|
12
|
+
"scss",
|
|
13
|
+
"less",
|
|
14
|
+
"vue",
|
|
15
|
+
"svelte",
|
|
16
|
+
"rust",
|
|
17
|
+
"astro",
|
|
18
|
+
"markdown",
|
|
19
|
+
"mdx",
|
|
20
|
+
"astro"
|
|
21
|
+
]),
|
|
22
|
+
/**
|
|
23
|
+
* @example <div class="a b">
|
|
24
|
+
* @example <div className="a b">
|
|
25
|
+
*/
|
|
26
|
+
classAttributes: Object.freeze([...CLASS_ATTRIBUTES]),
|
|
27
|
+
/**
|
|
28
|
+
* @example <div class={active ? 'a' : 'b'}>
|
|
29
|
+
* @example <div className={active ? 'a' : 'b'}>
|
|
30
|
+
*/
|
|
31
|
+
classAttributeBindings: Object.freeze({
|
|
32
|
+
"className": Object.freeze(["{", "}"]),
|
|
33
|
+
"class": Object.freeze(["{", "}"]),
|
|
34
|
+
"class:list": Object.freeze(["{", "}"]),
|
|
35
|
+
":class": Object.freeze(["\"", "\""]),
|
|
36
|
+
"v-bind:class": Object.freeze(["\"", "\""]),
|
|
37
|
+
"[class]": Object.freeze(["\"", "\""]),
|
|
38
|
+
"[className]": Object.freeze(["\"", "\""]),
|
|
39
|
+
"[ngClass]": Object.freeze(["\"", "\""])
|
|
40
|
+
}),
|
|
41
|
+
/**
|
|
42
|
+
* @example const classes = 'a b'
|
|
43
|
+
* @example { classes: { btn: 'a b' } }
|
|
44
|
+
*/
|
|
45
|
+
classDeclarations: Object.freeze([...CLASS_DECLARATIONS]),
|
|
46
|
+
/**
|
|
47
|
+
* @example clsx('a b')
|
|
48
|
+
* @example styled`a b`
|
|
49
|
+
* @example .classList.add('a')
|
|
50
|
+
*/
|
|
51
|
+
classFunctions: Object.freeze([...CLASS_FUNCTIONS]),
|
|
52
|
+
exclude: Object.freeze([
|
|
53
|
+
"**/.git/**",
|
|
54
|
+
"**/node_modules/**",
|
|
55
|
+
"**/.hg/**"
|
|
56
|
+
]),
|
|
57
|
+
suggestSyntax: true,
|
|
58
|
+
inspectSyntax: true,
|
|
59
|
+
renderSyntaxColors: true,
|
|
60
|
+
editSyntaxColors: true,
|
|
61
|
+
formatDirectives: true,
|
|
62
|
+
embeddedSyntaxHighlighting: "active"
|
|
63
|
+
});
|
|
64
|
+
export { defaultLanguageServiceSettings };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface ShikiPosition {
|
|
2
|
+
line: number;
|
|
3
|
+
character: number;
|
|
4
|
+
}
|
|
5
|
+
interface ShikiDecoration {
|
|
6
|
+
start: number | ShikiPosition;
|
|
7
|
+
end: number | ShikiPosition;
|
|
8
|
+
tagName?: string;
|
|
9
|
+
properties?: Record<string, unknown>;
|
|
10
|
+
transform?: (element: unknown, type: 'wrapper' | 'line' | 'token') => unknown | undefined;
|
|
11
|
+
}
|
|
12
|
+
declare function applyClassAttributeValueWrappers(root: unknown, source: string, decorations: ShikiDecoration[], sourceLineNumbers?: WeakMap<object, number>): void;
|
|
13
|
+
export { applyClassAttributeValueWrappers };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// src/shiki/hast.ts
|
|
2
|
+
function resolveClassNames(value) {
|
|
3
|
+
if (Array.isArray(value)) return value.flatMap(resolveClassNames);
|
|
4
|
+
return typeof value === "string" ? value.split(/\\s+/).filter(Boolean) : [];
|
|
5
|
+
}
|
|
6
|
+
function isHastElement(node) {
|
|
7
|
+
return Boolean(node && typeof node === "object" && node.type === "element");
|
|
8
|
+
}
|
|
9
|
+
function getHastText(node) {
|
|
10
|
+
if (!node || typeof node !== "object") return "";
|
|
11
|
+
if (node.type === "text") return node.value;
|
|
12
|
+
if (!isHastElement(node)) return "";
|
|
13
|
+
return (node.children ?? []).map(getHastText).join("");
|
|
14
|
+
}
|
|
15
|
+
function hasHastClass(element, className) {
|
|
16
|
+
return resolveClassNames(element.properties?.class).includes(className);
|
|
17
|
+
}
|
|
18
|
+
function collectCodeElements(node, codeElements = []) {
|
|
19
|
+
if (isHastElement(node) && node.tagName === "code") codeElements.push(node);
|
|
20
|
+
const children = node && typeof node === "object" && Array.isArray(node.children) ? node.children : [];
|
|
21
|
+
for (const child of children) collectCodeElements(child, codeElements);
|
|
22
|
+
return codeElements;
|
|
23
|
+
}
|
|
24
|
+
function createSourceLineRanges(source) {
|
|
25
|
+
const ranges = [];
|
|
26
|
+
let start = 0;
|
|
27
|
+
for (let index = 0; index < source.length; index++) {
|
|
28
|
+
if (source[index] !== "\n") continue;
|
|
29
|
+
ranges.push({
|
|
30
|
+
start,
|
|
31
|
+
end: index
|
|
32
|
+
});
|
|
33
|
+
start = index + 1;
|
|
34
|
+
}
|
|
35
|
+
ranges.push({
|
|
36
|
+
start,
|
|
37
|
+
end: source.length
|
|
38
|
+
});
|
|
39
|
+
return ranges;
|
|
40
|
+
}
|
|
41
|
+
function createHastText(value) {
|
|
42
|
+
return value ? {
|
|
43
|
+
type: "text",
|
|
44
|
+
value
|
|
45
|
+
} : void 0;
|
|
46
|
+
}
|
|
47
|
+
function cloneHastElement(element, children) {
|
|
48
|
+
return {
|
|
49
|
+
...element,
|
|
50
|
+
properties: element.properties ? { ...element.properties } : void 0,
|
|
51
|
+
children
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function pushHastNode(target, node) {
|
|
55
|
+
if (node) target.push(node);
|
|
56
|
+
}
|
|
57
|
+
function pushHastElementSlice(target, element, children) {
|
|
58
|
+
if (children.length) target.push(cloneHastElement(element, children));
|
|
59
|
+
}
|
|
60
|
+
function splitHastNodeByRange(node, nodeStart, start, end) {
|
|
61
|
+
const before = [];
|
|
62
|
+
const inside = [];
|
|
63
|
+
const after = [];
|
|
64
|
+
const nodeTextLength = getHastText(node).length;
|
|
65
|
+
const nodeEnd = nodeStart + nodeTextLength;
|
|
66
|
+
if (nodeEnd <= start) {
|
|
67
|
+
before.push(node);
|
|
68
|
+
return {
|
|
69
|
+
before,
|
|
70
|
+
inside,
|
|
71
|
+
after,
|
|
72
|
+
length: nodeTextLength
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (nodeStart >= end) {
|
|
76
|
+
after.push(node);
|
|
77
|
+
return {
|
|
78
|
+
before,
|
|
79
|
+
inside,
|
|
80
|
+
after,
|
|
81
|
+
length: nodeTextLength
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (node.type === "text") {
|
|
85
|
+
const localStart = Math.max(0, start - nodeStart);
|
|
86
|
+
const localEnd = Math.min(node.value.length, end - nodeStart);
|
|
87
|
+
pushHastNode(before, createHastText(node.value.slice(0, localStart)));
|
|
88
|
+
pushHastNode(inside, createHastText(node.value.slice(localStart, localEnd)));
|
|
89
|
+
pushHastNode(after, createHastText(node.value.slice(localEnd)));
|
|
90
|
+
return {
|
|
91
|
+
before,
|
|
92
|
+
inside,
|
|
93
|
+
after,
|
|
94
|
+
length: nodeTextLength
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (start <= nodeStart && nodeEnd <= end) {
|
|
98
|
+
inside.push(node);
|
|
99
|
+
return {
|
|
100
|
+
before,
|
|
101
|
+
inside,
|
|
102
|
+
after,
|
|
103
|
+
length: nodeTextLength
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const splitChildren = splitHastNodesByRange(node.children ?? [], Math.max(0, start - nodeStart), Math.min(nodeTextLength, end - nodeStart));
|
|
107
|
+
pushHastElementSlice(before, node, splitChildren.before);
|
|
108
|
+
pushHastElementSlice(inside, node, splitChildren.inside);
|
|
109
|
+
pushHastElementSlice(after, node, splitChildren.after);
|
|
110
|
+
return {
|
|
111
|
+
before,
|
|
112
|
+
inside,
|
|
113
|
+
after,
|
|
114
|
+
length: nodeTextLength
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function splitHastNodesByRange(nodes, start, end) {
|
|
118
|
+
const before = [];
|
|
119
|
+
const inside = [];
|
|
120
|
+
const after = [];
|
|
121
|
+
let offset = 0;
|
|
122
|
+
for (const node of nodes) {
|
|
123
|
+
const splitNode = splitHastNodeByRange(node, offset, start, end);
|
|
124
|
+
before.push(...splitNode.before);
|
|
125
|
+
inside.push(...splitNode.inside);
|
|
126
|
+
after.push(...splitNode.after);
|
|
127
|
+
offset += splitNode.length;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
before,
|
|
131
|
+
inside,
|
|
132
|
+
after
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function applyClassAttributeValueWrapperToLine(line, start, end, decoration) {
|
|
136
|
+
const splitChildren = splitHastNodesByRange(line.children ?? [], start, end);
|
|
137
|
+
if (!splitChildren.inside.length) return;
|
|
138
|
+
const wrapper = {
|
|
139
|
+
type: "element",
|
|
140
|
+
tagName: decoration.tagName ?? "span",
|
|
141
|
+
properties: { ...decoration.properties ?? {} },
|
|
142
|
+
children: splitChildren.inside
|
|
143
|
+
};
|
|
144
|
+
const transformedWrapper = decoration.transform?.(wrapper, "wrapper");
|
|
145
|
+
line.children = [
|
|
146
|
+
...splitChildren.before,
|
|
147
|
+
isHastElement(transformedWrapper) ? transformedWrapper : wrapper,
|
|
148
|
+
...splitChildren.after
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
function applyClassAttributeValueWrappers(root, source, decorations, sourceLineNumbers) {
|
|
152
|
+
if (!decorations.length) return;
|
|
153
|
+
const sourceLineRanges = createSourceLineRanges(source);
|
|
154
|
+
const codeElements = collectCodeElements(root);
|
|
155
|
+
for (const codeElement of codeElements) {
|
|
156
|
+
const lines = (codeElement.children ?? []).filter((child) => isHastElement(child) && hasHastClass(child, "line"));
|
|
157
|
+
const linesBySourceIndex = new Map(lines.map((line, index) => [(sourceLineNumbers?.get(line) ?? index + 1) - 1, line]));
|
|
158
|
+
for (const decoration of decorations) {
|
|
159
|
+
const { start, end } = decoration;
|
|
160
|
+
if (typeof start !== "number" || typeof end !== "number") continue;
|
|
161
|
+
for (let lineIndex = 0; lineIndex < sourceLineRanges.length; lineIndex++) {
|
|
162
|
+
const lineRange = sourceLineRanges[lineIndex];
|
|
163
|
+
if (end <= lineRange.start || start >= lineRange.end) continue;
|
|
164
|
+
const line = linesBySourceIndex.get(lineIndex);
|
|
165
|
+
if (!line) continue;
|
|
166
|
+
const visibleLineEnd = lineRange.start + getHastText(line).length;
|
|
167
|
+
const lineStart = Math.max(start, lineRange.start) - lineRange.start;
|
|
168
|
+
const lineEnd = Math.min(end, lineRange.end, visibleLineEnd) - lineRange.start;
|
|
169
|
+
if (lineStart < lineEnd) applyClassAttributeValueWrapperToLine(line, lineStart, lineEnd, decoration);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export { applyClassAttributeValueWrappers };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare const languageServiceLanguageIds: Record<string, string>;
|
|
2
|
+
declare const cssDirectiveLanguageIds: Set<string>;
|
|
3
|
+
declare const classAttributeValueWrappersKey = "__masterCSSClassAttributeValueWrappers";
|
|
4
|
+
declare function isMasterCSSClassListLanguage(lang?: string): boolean;
|
|
5
|
+
declare function getMasterCSSShikiLanguageId(lang?: string): string | undefined;
|
|
6
|
+
declare function isMasterCSSShikiSupportedLanguage(lang?: string): boolean;
|
|
7
|
+
declare function getLanguageServiceLanguageId(lang?: string): string | undefined;
|
|
8
|
+
export { classAttributeValueWrappersKey, cssDirectiveLanguageIds, getLanguageServiceLanguageId, getMasterCSSShikiLanguageId, isMasterCSSClassListLanguage, isMasterCSSShikiSupportedLanguage, languageServiceLanguageIds };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/shiki/languages.ts
|
|
2
|
+
const shikiLanguageIds = {
|
|
3
|
+
html: "html",
|
|
4
|
+
"angular-html": "angular-html",
|
|
5
|
+
js: "javascript",
|
|
6
|
+
javascript: "javascript",
|
|
7
|
+
jsx: "jsx",
|
|
8
|
+
javascriptreact: "jsx",
|
|
9
|
+
ts: "typescript",
|
|
10
|
+
typescript: "typescript",
|
|
11
|
+
tsx: "tsx",
|
|
12
|
+
typescriptreact: "tsx",
|
|
13
|
+
css: "css",
|
|
14
|
+
scss: "scss",
|
|
15
|
+
less: "less",
|
|
16
|
+
postcss: "postcss",
|
|
17
|
+
vue: "vue",
|
|
18
|
+
svelte: "svelte",
|
|
19
|
+
astro: "astro",
|
|
20
|
+
md: "markdown",
|
|
21
|
+
markdown: "markdown",
|
|
22
|
+
mdx: "mdx"
|
|
23
|
+
};
|
|
24
|
+
const languageServiceLanguageIds = {
|
|
25
|
+
html: "html",
|
|
26
|
+
"angular-html": "html",
|
|
27
|
+
js: "javascript",
|
|
28
|
+
javascript: "javascript",
|
|
29
|
+
jsx: "javascriptreact",
|
|
30
|
+
javascriptreact: "javascriptreact",
|
|
31
|
+
ts: "typescript",
|
|
32
|
+
typescript: "typescript",
|
|
33
|
+
tsx: "typescriptreact",
|
|
34
|
+
typescriptreact: "typescriptreact",
|
|
35
|
+
css: "css",
|
|
36
|
+
scss: "scss",
|
|
37
|
+
less: "less",
|
|
38
|
+
postcss: "postcss",
|
|
39
|
+
vue: "vue",
|
|
40
|
+
svelte: "svelte",
|
|
41
|
+
astro: "astro",
|
|
42
|
+
md: "markdown",
|
|
43
|
+
markdown: "markdown",
|
|
44
|
+
mdx: "mdx"
|
|
45
|
+
};
|
|
46
|
+
const masterCSSClassListLanguageIds = /* @__PURE__ */ new Set(["mcss", "master-css"]);
|
|
47
|
+
const cssDirectiveLanguageIds = /* @__PURE__ */ new Set([
|
|
48
|
+
"css",
|
|
49
|
+
"scss",
|
|
50
|
+
"less",
|
|
51
|
+
"postcss"
|
|
52
|
+
]);
|
|
53
|
+
const classAttributeValueWrappersKey = "__masterCSSClassAttributeValueWrappers";
|
|
54
|
+
const masterCSSShikiSupportedLanguageIds = /* @__PURE__ */ new Set([...Object.keys(shikiLanguageIds), ...Object.values(shikiLanguageIds)]);
|
|
55
|
+
function isMasterCSSClassListLanguage(lang) {
|
|
56
|
+
return Boolean(lang && masterCSSClassListLanguageIds.has(lang));
|
|
57
|
+
}
|
|
58
|
+
function getMasterCSSShikiLanguageId(lang) {
|
|
59
|
+
if (!lang) return;
|
|
60
|
+
return shikiLanguageIds[lang] ?? lang;
|
|
61
|
+
}
|
|
62
|
+
function isMasterCSSShikiSupportedLanguage(lang) {
|
|
63
|
+
const languageId = getMasterCSSShikiLanguageId(lang);
|
|
64
|
+
return Boolean(languageId && masterCSSShikiSupportedLanguageIds.has(languageId));
|
|
65
|
+
}
|
|
66
|
+
function getLanguageServiceLanguageId(lang) {
|
|
67
|
+
return lang ? languageServiceLanguageIds[lang] ?? lang : void 0;
|
|
68
|
+
}
|
|
69
|
+
export { classAttributeValueWrappersKey, cssDirectiveLanguageIds, getLanguageServiceLanguageId, getMasterCSSShikiLanguageId, isMasterCSSClassListLanguage, isMasterCSSShikiSupportedLanguage, languageServiceLanguageIds };
|