@master/css-language-service 2.0.0-rc.74 → 2.0.0-rc.76

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 CHANGED
@@ -91,6 +91,20 @@ const semanticTokens = languageService.renderSemanticTokensAtPosition(textDocume
91
91
 
92
92
  CSS directive syntax in CSS-family documents is highlighted by the shared TextMate grammar. Semantic tokens are only added for directive class-list spans such as bare `@compose` preludes and quoted `@safelist` strings.
93
93
 
94
+ ### Directive formatting
95
+
96
+ ```ts
97
+ const edits = languageService.formatDirectives(textDocument)
98
+ ```
99
+
100
+ Directive formatting returns LSP text edits for Master CSS directive source. It supports CSS, SCSS, and LESS documents, plus CSS-family `<style>` blocks in Vue, Svelte, and Astro documents. The formatter repairs directive class-list important markers such as `bg:transparent !` to `bg:transparent!` and does not change compiler semantics or generated CSS.
101
+
102
+ Pass an LSP range as the second argument for range formatting:
103
+
104
+ ```ts
105
+ const edits = languageService.formatDirectives(textDocument, range)
106
+ ```
107
+
94
108
  ## Settings
95
109
 
96
110
  ```js
@@ -109,6 +123,7 @@ import { settings } from '@master/css-language-service'
109
123
  | `inspectSyntax` | `boolean` | `true` | Enables hover inspection and generated CSS previews. |
110
124
  | `renderSyntaxColors` | `boolean` | `true` | Enables color rendering. |
111
125
  | `editSyntaxColors` | `boolean` | `true` | Enables color editing. |
126
+ | `formatDirectives` | `boolean` | `true` | Enables Master CSS directive formatting. |
112
127
  | `embeddedSyntaxHighlighting` | `'active' \| 'always' \| 'off'` | `'active'` | Controls embedded semantic token highlighting. |
113
128
 
114
129
  Use `@master/css-language` directly for browser helpers, Shiki integration, TextMate grammar assets, and raw class-position scanning.
package/dist/core.d.ts CHANGED
@@ -6,6 +6,7 @@ import { Settings } from "./settings.js";
6
6
  import inspectSyntax from "./features/inspect-syntax.js";
7
7
  import renderSyntaxColors from "./features/render-syntax-colors.js";
8
8
  import editSyntaxColors from "./features/edit-syntax-colors.js";
9
+ import formatDirectives from "./features/format-directives.js";
9
10
  import renderSemanticTokens$1, { renderSemanticTokensAtPosition as renderSemanticTokensAtPosition$1 } from "./features/render-semantic-tokens.js";
10
11
  import suggestSyntax from "./features/suggest-syntax.js";
11
12
 
@@ -25,6 +26,7 @@ declare class CSSLanguageService extends EventEmitter {
25
26
  inspectSyntax(...params: Parameters<typeof inspectSyntax>): import("vscode-languageserver-protocol").Hover | undefined;
26
27
  renderSyntaxColors(...params: Parameters<typeof renderSyntaxColors>): Promise<import("vscode-languageserver-protocol").ColorInformation[] | undefined> | undefined;
27
28
  editSyntaxColors(...params: Parameters<typeof editSyntaxColors>): import("vscode-languageserver-protocol").ColorPresentation[] | undefined;
29
+ formatDirectives(...params: Parameters<typeof formatDirectives>): import("vscode-languageserver-protocol").TextEdit[] | undefined;
28
30
  renderSemanticTokens(...params: Parameters<typeof renderSemanticTokens$1>): import("vscode-languageserver-protocol").SemanticTokens | undefined;
29
31
  renderSemanticTokensAtPosition(...params: Parameters<typeof renderSemanticTokensAtPosition$1>): import("vscode-languageserver-protocol").SemanticTokens | undefined;
30
32
  suggestSyntax(document: Parameters<typeof suggestSyntax>[0], position: Parameters<typeof suggestSyntax>[1], context: Parameters<typeof suggestSyntax>[2]): import("vscode-languageserver-protocol").CompletionItem[] | undefined;
package/dist/core.js CHANGED
@@ -2,6 +2,7 @@ import settings from "./settings.js";
2
2
  import inspectSyntax from "./features/inspect-syntax.js";
3
3
  import renderSyntaxColors from "./features/render-syntax-colors.js";
4
4
  import editSyntaxColors from "./features/edit-syntax-colors.js";
5
+ import formatDirectives from "./features/format-directives.js";
5
6
  import renderSemanticTokens$1, { renderSemanticTokensAtPosition as renderSemanticTokensAtPosition$1 } from "./features/render-semantic-tokens.js";
6
7
  import { createCompletionIndex } from "./utils/completion-index.js";
7
8
  import suggestSyntax from "./features/suggest-syntax.js";
@@ -40,6 +41,9 @@ var CSSLanguageService = class extends EventEmitter {
40
41
  editSyntaxColors(...params) {
41
42
  if (this.settings.editSyntaxColors && this.isDocumentAccepted(params[0])) return editSyntaxColors?.call(this, ...params);
42
43
  }
44
+ formatDirectives(...params) {
45
+ if (this.settings.formatDirectives && this.isDocumentAccepted(params[0])) return formatDirectives?.call(this, ...params);
46
+ }
43
47
  renderSemanticTokens(...params) {
44
48
  if (this.isDocumentAccepted(params[0])) return renderSemanticTokens$1?.call(this, ...params);
45
49
  }
@@ -0,0 +1,7 @@
1
+ import { Range, TextEdit } from "vscode-languageserver-protocol";
2
+ import { TextDocument } from "vscode-languageserver-textdocument";
3
+
4
+ //#region src/features/format-directives.d.ts
5
+ declare function formatDirectives(document: TextDocument, range?: Range): TextEdit[] | undefined;
6
+ //#endregion
7
+ export { formatDirectives as default };
@@ -0,0 +1,72 @@
1
+ import { formatMasterCSSDirectives } from "@master/css-language";
2
+ //#region src/features/format-directives.ts
3
+ const CSS_FORMAT_LANGUAGE_IDS = /* @__PURE__ */ new Set([
4
+ "css",
5
+ "scss",
6
+ "less"
7
+ ]);
8
+ const SFC_FORMAT_LANGUAGE_IDS = /* @__PURE__ */ new Set([
9
+ "vue",
10
+ "svelte",
11
+ "astro"
12
+ ]);
13
+ const STYLE_BLOCK_RE = /<style\b([^>]*)>([\s\S]*?)<\/style>/gi;
14
+ function getSFCStyleLanguage(attributes) {
15
+ const match = /\blang\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/i.exec(attributes);
16
+ return (match?.[1] || match?.[2] || match?.[3] || "css").toLowerCase();
17
+ }
18
+ function toOffsetRange(document, range) {
19
+ if (!range) return;
20
+ return {
21
+ start: document.offsetAt(range.start),
22
+ end: document.offsetAt(range.end)
23
+ };
24
+ }
25
+ function intersects(a, b) {
26
+ return a.start < b.end && b.start < a.end;
27
+ }
28
+ function toTextEdit(document, edit) {
29
+ return {
30
+ range: {
31
+ start: document.positionAt(edit.start),
32
+ end: document.positionAt(edit.end)
33
+ },
34
+ newText: edit.newText
35
+ };
36
+ }
37
+ function collectCSSDocumentFormatEdits(document, range) {
38
+ return formatMasterCSSDirectives(document.getText(), { range: toOffsetRange(document, range) }).map(toTextEdit.bind(void 0, document));
39
+ }
40
+ function collectSFCFormatEdits(document, range) {
41
+ const source = document.getText();
42
+ const offsetRange = toOffsetRange(document, range);
43
+ const edits = [];
44
+ STYLE_BLOCK_RE.lastIndex = 0;
45
+ for (const match of source.matchAll(STYLE_BLOCK_RE)) {
46
+ const styleLanguage = getSFCStyleLanguage(match[1]);
47
+ if (!CSS_FORMAT_LANGUAGE_IDS.has(styleLanguage)) continue;
48
+ const styleText = match[2];
49
+ const styleStart = (match.index || 0) + match[0].indexOf(styleText);
50
+ const styleEnd = styleStart + styleText.length;
51
+ if (offsetRange && !intersects(offsetRange, {
52
+ start: styleStart,
53
+ end: styleEnd
54
+ })) continue;
55
+ const relativeRange = offsetRange ? {
56
+ start: Math.max(offsetRange.start, styleStart) - styleStart,
57
+ end: Math.min(offsetRange.end, styleEnd) - styleStart
58
+ } : void 0;
59
+ for (const edit of formatMasterCSSDirectives(styleText, { range: relativeRange })) edits.push({
60
+ start: styleStart + edit.start,
61
+ end: styleStart + edit.end,
62
+ newText: edit.newText
63
+ });
64
+ }
65
+ return edits.map(toTextEdit.bind(void 0, document));
66
+ }
67
+ function formatDirectives(document, range) {
68
+ if (CSS_FORMAT_LANGUAGE_IDS.has(document.languageId)) return collectCSSDocumentFormatEdits(document, range);
69
+ if (SFC_FORMAT_LANGUAGE_IDS.has(document.languageId)) return collectSFCFormatEdits(document, range);
70
+ }
71
+ //#endregion
72
+ export { formatDirectives as default };
@@ -14,6 +14,7 @@ declare interface Settings extends LanguageSettings {
14
14
  inspectSyntax?: boolean;
15
15
  renderSyntaxColors?: boolean;
16
16
  editSyntaxColors?: boolean;
17
+ formatDirectives?: boolean;
17
18
  }
18
19
  //#endregion
19
20
  export { Settings, settings as default };
package/dist/settings.js CHANGED
@@ -52,6 +52,7 @@ const settings = {
52
52
  inspectSyntax: true,
53
53
  renderSyntaxColors: true,
54
54
  editSyntaxColors: true,
55
+ formatDirectives: true,
55
56
  embeddedSyntaxHighlighting: "active"
56
57
  };
57
58
  //#endregion
@@ -24,10 +24,14 @@ function getVariableKeyByNamespace(variableName, namespace) {
24
24
  const key = positiveName === namespace ? "" : positiveName.slice(namespace.length + 1);
25
25
  return negative ? "-" + key : key;
26
26
  }
27
+ function uniqueKeys(...keys) {
28
+ return [...new Set(keys)];
29
+ }
27
30
  function getValueCompletionItems(css = createDefaultCSS(), ruleKey, valuePrefix = "", completionIndex = createCompletionIndex(css)) {
28
31
  const completionItems = [];
29
32
  const addedLabels = /* @__PURE__ */ new Set();
30
33
  const canonicalRuleKey = completionIndex.keyAliases[ruleKey] || ruleKey;
34
+ const valueLookupKeys = uniqueKeys(ruleKey, canonicalRuleKey);
31
35
  const nativeKey = completionIndex.nativeUtilityKeys.get(canonicalRuleKey) || (getMdnPropertySyntax(canonicalRuleKey) ? canonicalRuleKey : void 0);
32
36
  const nativePropertyValues = getMdnPropertyValueNames(nativeKey);
33
37
  const pushCompletionItem = (completionItem) => {
@@ -97,14 +101,14 @@ function getValueCompletionItems(css = createDefaultCSS(), ruleKey, valuePrefix
97
101
  * Scoped variables
98
102
  * @example box: + content -> box-sizing:content
99
103
  */
100
- for (const { name, variable } of completionIndex.scopedVariablesByKey.get(canonicalRuleKey) || []) addScopedVariableCompletionItem(variable, name);
101
- for (const { value } of completionIndex.patternValuesByKey.get(canonicalRuleKey) || []) {
104
+ for (const valueLookupKey of valueLookupKeys) for (const { name, variable } of completionIndex.scopedVariablesByKey.get(valueLookupKey) || []) addScopedVariableCompletionItem(variable, name);
105
+ for (const valueLookupKey of valueLookupKeys) for (const { value } of completionIndex.patternValuesByKey.get(valueLookupKey) || []) {
102
106
  if (addedLabels.has(value)) continue;
103
107
  pushCompletionItem({
104
108
  label: value,
105
109
  kind: CompletionItemKind.Value,
106
110
  documentation: createCSSMarkdownDocumentation(generateCSS([ruleKey + ":" + value], css, completionIndex.runtime)),
107
- detail: canonicalRuleKey + ": " + value
111
+ detail: valueLookupKey + ": " + value
108
112
  });
109
113
  }
110
114
  /**
@@ -128,7 +132,7 @@ function getValueCompletionItems(css = createDefaultCSS(), ruleKey, valuePrefix
128
132
  });
129
133
  });
130
134
  const usedKeys = /* @__PURE__ */ new Set();
131
- for (const variableNamespace of completionIndex.nativeVariableNamespacesByProperty.get(canonicalRuleKey) || []) for (const variable of completionIndex.variables) {
135
+ for (const valueLookupKey of valueLookupKeys) for (const variableNamespace of completionIndex.nativeVariableNamespacesByProperty.get(valueLookupKey) || []) for (const variable of completionIndex.variables) {
132
136
  const variableName = getVariableKeyByNamespace(variable.name, variableNamespace);
133
137
  if (variableName === void 0 || usedKeys.has(variableName)) continue;
134
138
  usedKeys.add(variableName);
package/package.json CHANGED
@@ -16,14 +16,14 @@
16
16
  "minimatch": "^10.2.5",
17
17
  "vscode-languageserver-protocol": "^3.18.0",
18
18
  "vscode-languageserver-textdocument": "^1.0.12",
19
- "@master/css": "^2.0.0-rc.74",
20
- "@master/css-engine": "^2.0.0-rc.74",
21
- "@master/css-language": "^2.0.0-rc.74"
19
+ "@master/css": "^2.0.0-rc.76",
20
+ "@master/css-engine": "^2.0.0-rc.76",
21
+ "@master/css-language": "^2.0.0-rc.76"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/vscode": "^1.125.0",
25
25
  "vscode-languageserver": "^10.0.0",
26
- "@master/css-schema": "^2.0.0-rc.74"
26
+ "@master/css-schema": "^2.0.0-rc.76"
27
27
  },
28
28
  "sideEffects": false,
29
29
  "main": "./dist/index.js",
@@ -50,7 +50,7 @@
50
50
  "access": "public",
51
51
  "provenance": true
52
52
  },
53
- "version": "2.0.0-rc.74",
53
+ "version": "2.0.0-rc.76",
54
54
  "scripts": {
55
55
  "build": "tsdown --config ../../shared/tsdown-package.config.ts",
56
56
  "dev": "tsdown --config ../../shared/tsdown-package.config.ts --watch",