@master/css-language-server 2.0.0-rc.69 → 2.0.0-rc.70

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
@@ -48,16 +48,55 @@
48
48
  <img alt="Follow @mastercorg" src="https://img.shields.io/static/v1?label=%20&message=twitter&color=f6f7f8&logo=twitter&style=for-the-badge">
49
49
  </picture>
50
50
  </a>
51
- <a aria-label="Github Actions" href="https://github.com/master-co/css/actions/workflows/release.yml">
51
+ <a aria-label="Github Actions" href="https://github.com/master-co/css/actions/workflows/ci-release.yml">
52
52
  <picture>
53
- <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/release.yml?branch=rc&label=%20&message=twitter&color=212022&logo=githubactions&style=for-the-badge">
54
- <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/release.yml?branch=rc&label=%20&message=twitter&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
55
- <img alt="Github release actions" src="https://img.shields.io/github/actions/workflow/status/master-co/css/release.yml?branch=rc&label=%20&message=twitter&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
53
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/ci-release.yml?branch=rc&label=%20&message=twitter&color=212022&logo=githubactions&style=for-the-badge">
54
+ <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/ci-release.yml?branch=rc&label=%20&message=twitter&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
55
+ <img alt="Github release actions" src="https://img.shields.io/github/actions/workflow/status/master-co/css/ci-release.yml?branch=rc&label=%20&message=twitter&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
56
56
  </picture>
57
57
  </a>
58
58
  </p>
59
59
 
60
60
  </div>
61
61
 
62
- ## Documentation
63
- Check out the official [documentation](https://rc.css.master.co/reference/language-server).
62
+ ## Installation
63
+
64
+ ```bash
65
+ npm install @master/css-language-server
66
+ ```
67
+
68
+ ## Usage
69
+
70
+ ```js
71
+ import CSSLanguageServer from '@master/css-language-server'
72
+
73
+ const languageServer = new CSSLanguageServer(connection, customSettings)
74
+ ```
75
+
76
+ `CSSLanguageServer` wraps `@master/css-language-service` with LSP workspace lifecycle, completion, hover, document color, color presentation, and semantic token handlers.
77
+
78
+ ## Semantic tokens
79
+
80
+ The language server supports full-document and active-position semantic tokens.
81
+
82
+ | `embeddedSyntaxHighlighting` | Behavior |
83
+ | --- | --- |
84
+ | `always` | Advertises the standard LSP `semanticTokensProvider` and handles `textDocument/semanticTokens/full`. |
85
+ | `active` | Uses the VS Code extension request for active class-context highlighting. |
86
+ | `off` | Disables embedded utility highlighting. |
87
+
88
+ CSS directive syntax is highlighted by the shared TextMate grammar. Server semantic tokens cover Master CSS class-list spans and directive class-list spans such as `@compose` and `@safelist`.
89
+
90
+ ## Settings
91
+
92
+ ```js
93
+ import { settings } from '@master/css-language-server'
94
+ ```
95
+
96
+ | Setting | Type | Default | Description |
97
+ | --- | --- | --- | --- |
98
+ | `workspaces` | `FastGlobPattern[] \| 'auto'` | `'auto'` | Workspace roots where independent language services are created. The root workspace is always included. |
99
+ | `verbose` | `boolean` | `false` | Print server logs. |
100
+ | `embeddedSyntaxHighlighting` | `'active' \| 'always' \| 'off'` | `'active'` | Inherited language-service semantic token mode. |
101
+
102
+ With `workspaces: 'auto'`, the server creates workspaces from CSS files containing `@master;` or `@import '@master/css'`, and from `package.json` files that declare Master CSS package dependencies.
package/dist/core.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { TextDocuments, InitializeParams, WorkspaceFolder, Connection, ClientCapabilities, TextDocumentChangeEvent, DidChangeConfigurationParams, HoverParams, CompletionParams, DocumentColorParams, ColorPresentationParams, RemoteConsole } from 'vscode-languageserver/node';
1
+ import { TextDocuments, InitializeParams, InitializeResult, WorkspaceFolder, Connection, ClientCapabilities, TextDocumentChangeEvent, DidChangeConfigurationParams, HoverParams, CompletionParams, DocumentColorParams, ColorPresentationParams, RemoteConsole, SemanticTokensParams, TextDocumentPositionParams } from 'vscode-languageserver/node';
2
2
  import { TextDocument } from 'vscode-languageserver-textdocument';
3
3
  import CSSLanguageService, { Settings as CSSLanguageServiceSettings } from '@master/css-language-service';
4
4
  import { Settings } from './settings';
@@ -7,7 +7,10 @@ export declare interface Workspace {
7
7
  openedTextDocuments: TextDocument[];
8
8
  languageService?: CSSLanguageService;
9
9
  languageServiceSettings: CSSLanguageServiceSettings;
10
+ planEntries?: string[];
10
11
  }
12
+ export declare const ACTIVE_SEMANTIC_TOKENS_REQUEST = "masterCSS/renderActiveSemanticTokens";
13
+ export declare const DOCUMENT_SEMANTIC_TOKENS_REQUEST = "masterCSS/renderDocumentSemanticTokens";
11
14
  export default class CSSLanguageServer {
12
15
  connection: Connection;
13
16
  customSettings?: Settings | undefined;
@@ -23,20 +26,28 @@ export default class CSSLanguageServer {
23
26
  constructor(connection?: Connection, customSettings?: Settings | undefined);
24
27
  start(): void;
25
28
  init(): Promise<void>;
26
- onInitialize(params: InitializeParams): {
27
- capabilities: import("vscode-languageserver").ServerCapabilities<any>;
28
- };
29
- onHover(params: HoverParams): Promise<import("vscode-languageserver").Hover | undefined>;
30
- onCompletion(params: CompletionParams): Promise<import("vscode-languageserver").CompletionItem[] | undefined>;
31
- onDocumentColor(params: DocumentColorParams): Promise<import("vscode-languageserver").ColorInformation[] | undefined>;
32
- onColorPresentation(params: ColorPresentationParams): Promise<import("vscode-languageserver").ColorPresentation[] | undefined>;
29
+ onInitialize(params: InitializeParams): InitializeResult;
30
+ onHover(params: HoverParams): Promise<import("vscode-languageserver-protocol").Hover | undefined>;
31
+ onCompletion(params: CompletionParams): Promise<import("vscode-languageserver-protocol").CompletionItem[] | undefined>;
32
+ onDocumentColor(params: DocumentColorParams): Promise<import("vscode-languageserver-protocol").ColorInformation[]>;
33
+ onColorPresentation(params: ColorPresentationParams): Promise<import("vscode-languageserver-protocol").ColorPresentation[]>;
34
+ onSemanticTokens(params: SemanticTokensParams): Promise<import("vscode-languageserver-protocol").SemanticTokens>;
35
+ onDocumentSemanticTokens(params: SemanticTokensParams): Promise<import("vscode-languageserver-protocol").SemanticTokens>;
36
+ onActiveSemanticTokens(params: TextDocumentPositionParams): Promise<import("vscode-languageserver-protocol").SemanticTokens>;
33
37
  onDidOpen(params: TextDocumentChangeEvent<TextDocument>): Promise<void>;
38
+ onDidChangeContent(params: TextDocumentChangeEvent<TextDocument>): Promise<void>;
34
39
  onDidClose(params: TextDocumentChangeEvent<TextDocument>): Promise<void>;
35
40
  onDidSave(params: TextDocumentChangeEvent<TextDocument>): Promise<void>;
36
- onDidChangeConfiguration({ settings }: DidChangeConfigurationParams): Promise<void>;
41
+ onDidChangeConfiguration({ settings: changedSettings }: DidChangeConfigurationParams): Promise<void>;
37
42
  private initWorkspaceFolder;
38
- initWorkspaceLanguageService(workspace: Workspace): void;
43
+ initWorkspaceLanguageService(workspace: Workspace): Promise<void>;
44
+ private loadWorkspacePlan;
39
45
  destroyLanguageService(workspace: Workspace): void;
40
46
  findClosestWorkspace(textDocumentURI: string): Workspace;
47
+ private publishCSSDirectiveDiagnostics;
48
+ private createCSSDirectiveDiagnostic;
49
+ private createCSSDirectiveRelatedInformation;
50
+ private createCSSDirectiveRange;
51
+ private refreshSemanticTokens;
41
52
  stop(): void;
42
53
  }
package/dist/core.mjs CHANGED
@@ -1,13 +1,120 @@
1
- import { createConnection, TextDocuments } from 'vscode-languageserver/node';
1
+ import { TextDocumentSyncKind, TextDocuments, createConnection, DiagnosticSeverity } from 'vscode-languageserver/node';
2
2
  import { TextDocument } from 'vscode-languageserver-textdocument';
3
3
  import path from 'node:path';
4
- import CSSLanguageService, { SERVER_CAPABILITIES } from '@master/css-language-service';
5
- import exploreConfig from '@master/css-explore-config';
4
+ import CSSLanguageService from '@master/css-language-service';
5
+ import { compileCSSManifest } from '@master/css-compiler';
6
+ import { findMasterCSSWorkspaceDirectories, findCSSManifestEntryFiles } from '@master/css-project/entries';
7
+ import { loadProjectManifest } from '@master/css-project/manifest';
6
8
  import extend from '@techor/extend';
7
9
  import settings from './settings.mjs';
10
+ import { INVOKED_TRIGGER_CHARACTERS, VALUE_TRIGGER_CHARACTERS, SELECTOR_TRIGGER_CHARACTERS, QUERY_TRIGGER_CHARACTERS, DECLARATION_SEPARATOR_TRIGGER_CHARACTER, AT_TRIGGER_CHARACTER, GROUP_TRIGGER_CHARACTER } from '@master/css-language-service/common';
11
+ import { SEMANTIC_TOKENS_LEGEND } from '@master/css-language';
8
12
  import glob from 'fast-glob';
9
13
  import { URI } from 'vscode-uri';
14
+ import { CSSDirectiveError } from '@master/css-schema/css-directives';
10
15
 
16
+ const ACTIVE_SEMANTIC_TOKENS_REQUEST = 'masterCSS/renderActiveSemanticTokens';
17
+ const DOCUMENT_SEMANTIC_TOKENS_REQUEST = 'masterCSS/renderDocumentSemanticTokens';
18
+ const SERVER_CAPABILITIES = {
19
+ textDocumentSync: TextDocumentSyncKind.Incremental,
20
+ completionProvider: {
21
+ resolveProvider: false,
22
+ workDoneProgress: false,
23
+ triggerCharacters: [
24
+ ...new Set([
25
+ ...INVOKED_TRIGGER_CHARACTERS,
26
+ ...VALUE_TRIGGER_CHARACTERS,
27
+ ...SELECTOR_TRIGGER_CHARACTERS,
28
+ ...QUERY_TRIGGER_CHARACTERS,
29
+ DECLARATION_SEPARATOR_TRIGGER_CHARACTER,
30
+ AT_TRIGGER_CHARACTER,
31
+ GROUP_TRIGGER_CHARACTER
32
+ ])
33
+ ]
34
+ },
35
+ colorProvider: true,
36
+ hoverProvider: true,
37
+ semanticTokensProvider: {
38
+ legend: SEMANTIC_TOKENS_LEGEND,
39
+ full: true
40
+ },
41
+ workspace: {
42
+ workspaceFolders: {
43
+ supported: true
44
+ }
45
+ }
46
+ };
47
+ const CSS_DIAGNOSTIC_LANGUAGE_IDS = new Set([
48
+ 'css',
49
+ 'scss',
50
+ 'less'
51
+ ]);
52
+ const SFC_DIAGNOSTIC_LANGUAGE_IDS = new Set([
53
+ 'vue',
54
+ 'svelte',
55
+ 'astro'
56
+ ]);
57
+ const STYLE_BLOCK_RE = /<style\b([^>]*)>([\s\S]*?)<\/style>/gi;
58
+ function isCSSDiagnosticDocument(textDocument) {
59
+ return CSS_DIAGNOSTIC_LANGUAGE_IDS.has(textDocument.languageId) || SFC_DIAGNOSTIC_LANGUAGE_IDS.has(textDocument.languageId);
60
+ }
61
+ function getSFCStyleLanguage(attributes) {
62
+ const match = /\blang\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/i.exec(attributes);
63
+ return (match?.[1] || match?.[2] || match?.[3] || 'css').toLowerCase();
64
+ }
65
+ function getCSSDiagnosticSources(textDocument) {
66
+ const text = textDocument.getText();
67
+ if (CSS_DIAGNOSTIC_LANGUAGE_IDS.has(textDocument.languageId)) {
68
+ return [
69
+ {
70
+ source: text,
71
+ offset: 0
72
+ }
73
+ ];
74
+ }
75
+ if (!SFC_DIAGNOSTIC_LANGUAGE_IDS.has(textDocument.languageId)) return [];
76
+ const sources = [];
77
+ STYLE_BLOCK_RE.lastIndex = 0;
78
+ for (const match of text.matchAll(STYLE_BLOCK_RE)){
79
+ const styleLanguage = getSFCStyleLanguage(match[1]);
80
+ if (!CSS_DIAGNOSTIC_LANGUAGE_IDS.has(styleLanguage)) continue;
81
+ const source = match[2];
82
+ const offset = (match.index || 0) + match[0].indexOf(source);
83
+ sources.push({
84
+ source,
85
+ offset
86
+ });
87
+ }
88
+ return sources;
89
+ }
90
+ function isCSSDirectiveError(error) {
91
+ return error instanceof CSSDirectiveError || !!error && typeof error === 'object' && error.name === 'CSSDirectiveError' && typeof error.code === 'string';
92
+ }
93
+ function getInitializationSettings(initializationOptions) {
94
+ if (!initializationOptions || typeof initializationOptions !== 'object') return;
95
+ const options = initializationOptions;
96
+ return options.masterCSS ?? options;
97
+ }
98
+ function isWindowsFilePath(fsPath) {
99
+ return /^[a-z]:[\\/]/i.test(fsPath) || fsPath.startsWith('\\\\') || fsPath.startsWith('//');
100
+ }
101
+ function getFilePathTools(...fsPaths) {
102
+ return fsPaths.some(isWindowsFilePath) ? path.win32 : path;
103
+ }
104
+ function toComparableFilePath(uri) {
105
+ const parsedURI = URI.parse(uri);
106
+ if (parsedURI.scheme !== 'file') return;
107
+ const fsPath = parsedURI.fsPath;
108
+ if (!fsPath) return;
109
+ const filePathTools = getFilePathTools(fsPath);
110
+ const resolvedPath = filePathTools.resolve(fsPath);
111
+ return isWindowsFilePath(resolvedPath) ? resolvedPath.toLowerCase() : resolvedPath;
112
+ }
113
+ function containsFilePath(parentPath, childPath) {
114
+ const filePathTools = getFilePathTools(parentPath, childPath);
115
+ const relativePath = filePathTools.relative(parentPath, childPath);
116
+ return relativePath === '' || !!relativePath && relativePath !== '..' && !relativePath.startsWith(`..${filePathTools.sep}`) && !filePathTools.isAbsolute(relativePath);
117
+ }
11
118
  class CSSLanguageServer {
12
119
  connection;
13
120
  customSettings;
@@ -29,6 +136,7 @@ class CSSLanguageServer {
29
136
  this.customSettings = customSettings;
30
137
  this.documents = new TextDocuments(TextDocument);
31
138
  this.settings = extend(settings, this.customSettings);
139
+ this.globalWorkspace.languageServiceSettings = this.settings;
32
140
  this.console = new Proxy(this.connection.console, {
33
141
  get: (target, prop)=>{
34
142
  if (!this.settings?.verbose) return ()=>{};
@@ -37,7 +145,7 @@ class CSSLanguageServer {
37
145
  });
38
146
  }
39
147
  start() {
40
- this.disposables.push(this.documents.onDidSave(this.onDidSave.bind(this)), this.documents.onDidOpen(this.onDidOpen.bind(this)), this.documents.onDidClose(this.onDidClose.bind(this)), this.documents.listen(this.connection), this.connection.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)), this.connection.onHover(this.onHover.bind(this)), this.connection.onCompletion(this.onCompletion.bind(this)), this.connection.onDocumentColor(this.onDocumentColor.bind(this)), this.connection.onColorPresentation(this.onColorPresentation.bind(this)), this.connection.onInitialize(this.onInitialize.bind(this)), this.connection.onInitialized(()=>this.init()));
148
+ this.disposables.push(this.documents.onDidSave(this.onDidSave.bind(this)), this.documents.onDidOpen(this.onDidOpen.bind(this)), this.documents.onDidChangeContent(this.onDidChangeContent.bind(this)), this.documents.onDidClose(this.onDidClose.bind(this)), this.documents.listen(this.connection), this.connection.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)), this.connection.onHover(this.onHover.bind(this)), this.connection.onCompletion(this.onCompletion.bind(this)), this.connection.onDocumentColor(this.onDocumentColor.bind(this)), this.connection.onColorPresentation(this.onColorPresentation.bind(this)), this.connection.languages.semanticTokens.on(this.onSemanticTokens.bind(this)), this.connection.onRequest(DOCUMENT_SEMANTIC_TOKENS_REQUEST, this.onDocumentSemanticTokens.bind(this)), this.connection.onRequest(ACTIVE_SEMANTIC_TOKENS_REQUEST, this.onActiveSemanticTokens.bind(this)), this.connection.onInitialize(this.onInitialize.bind(this)), this.connection.onInitialized(()=>this.init()));
41
149
  this.connection.listen();
42
150
  }
43
151
  init() {
@@ -49,11 +157,23 @@ class CSSLanguageServer {
49
157
  }
50
158
  onInitialize(params) {
51
159
  this.clientCapabilities = params.capabilities;
160
+ const initializationSettings = getInitializationSettings(params.initializationOptions);
161
+ if (initializationSettings) {
162
+ this.customSettings = extend(this.customSettings, initializationSettings);
163
+ this.settings = extend(settings, this.customSettings);
164
+ this.globalWorkspace.languageServiceSettings = this.settings;
165
+ }
52
166
  if (params.workspaceFolders?.length) {
53
167
  this.workspaceFolders = params.workspaceFolders;
54
168
  }
169
+ const capabilities = {
170
+ ...SERVER_CAPABILITIES
171
+ };
172
+ if (this.settings?.embeddedSyntaxHighlighting !== 'always') {
173
+ delete capabilities.semanticTokensProvider;
174
+ }
55
175
  return {
56
- capabilities: SERVER_CAPABILITIES
176
+ capabilities
57
177
  };
58
178
  }
59
179
  async onHover(params) {
@@ -77,31 +197,77 @@ class CSSLanguageServer {
77
197
  const workspace = this.findClosestWorkspace(params.textDocument.uri);
78
198
  if (workspace?.languageService) {
79
199
  const document = this.documents.get(params.textDocument.uri);
80
- if (document) return workspace.languageService.renderSyntaxColors(document);
200
+ if (document) return await workspace.languageService.renderSyntaxColors(document) ?? [];
81
201
  }
202
+ return [];
82
203
  }
83
204
  async onColorPresentation(params) {
84
205
  await this.init();
85
206
  const workspace = this.findClosestWorkspace(params.textDocument.uri);
86
207
  if (workspace?.languageService) {
87
208
  const document = this.documents.get(params.textDocument.uri);
88
- if (document) return workspace.languageService.editSyntaxColors(document, params.color, params.range);
209
+ if (document) return workspace.languageService.editSyntaxColors(document, params.color, params.range) ?? [];
89
210
  }
211
+ return [];
212
+ }
213
+ async onSemanticTokens(params) {
214
+ await this.init();
215
+ const workspace = this.findClosestWorkspace(params.textDocument.uri);
216
+ if (workspace?.languageService) {
217
+ const document = this.documents.get(params.textDocument.uri) ?? workspace.openedTextDocuments.find((document)=>document.uri === params.textDocument.uri);
218
+ if (document) return workspace.languageService.renderSemanticTokens(document) ?? {
219
+ data: []
220
+ };
221
+ }
222
+ return {
223
+ data: []
224
+ };
225
+ }
226
+ async onDocumentSemanticTokens(params) {
227
+ return this.onSemanticTokens(params);
228
+ }
229
+ async onActiveSemanticTokens(params) {
230
+ await this.init();
231
+ const workspace = this.findClosestWorkspace(params.textDocument.uri);
232
+ if (workspace?.languageService) {
233
+ const document = this.documents.get(params.textDocument.uri) ?? workspace.openedTextDocuments.find((document)=>document.uri === params.textDocument.uri);
234
+ if (document) return workspace.languageService.renderSemanticTokensAtPosition(document, params.position) ?? {
235
+ data: []
236
+ };
237
+ }
238
+ return {
239
+ data: []
240
+ };
90
241
  }
91
242
  async onDidOpen(params) {
92
243
  await this.init();
93
244
  const workspace = this.findClosestWorkspace(params.document.uri);
94
- if (!workspace || workspace.openedTextDocuments.includes(params.document)) return;
245
+ if (!workspace) return;
246
+ if (workspace.openedTextDocuments.includes(params.document)) {
247
+ this.publishCSSDirectiveDiagnostics(params.document, workspace);
248
+ return;
249
+ }
95
250
  if (!workspace.openedTextDocuments.length) {
96
- this.initWorkspaceLanguageService(workspace);
251
+ await this.initWorkspaceLanguageService(workspace);
97
252
  }
98
253
  workspace.openedTextDocuments.push(params.document);
254
+ this.publishCSSDirectiveDiagnostics(params.document, workspace);
255
+ }
256
+ async onDidChangeContent(params) {
257
+ await this.init();
258
+ const workspace = this.findClosestWorkspace(params.document.uri);
259
+ if (!workspace) return;
260
+ this.publishCSSDirectiveDiagnostics(params.document, workspace);
99
261
  }
100
262
  async onDidClose(params) {
101
263
  await this.init();
102
264
  const workspace = this.findClosestWorkspace(params.document.uri);
103
265
  if (!workspace) return;
104
266
  workspace.openedTextDocuments.splice(workspace.openedTextDocuments.indexOf(params.document), 1);
267
+ this.connection.sendDiagnostics({
268
+ uri: params.document.uri,
269
+ diagnostics: []
270
+ });
105
271
  if (!workspace.openedTextDocuments.length) {
106
272
  this.destroyLanguageService(workspace);
107
273
  }
@@ -110,21 +276,34 @@ class CSSLanguageServer {
110
276
  await this.init();
111
277
  const workspace = this.findClosestWorkspace(params.document.uri);
112
278
  if (!workspace) return;
113
- const name = path.parse(params.document.uri).name;
114
- if (name === 'master.css' || name.endsWith('.css')) {
279
+ this.publishCSSDirectiveDiagnostics(params.document, workspace);
280
+ const name = path.basename(URI.parse(params.document.uri).fsPath);
281
+ if (name.endsWith('.css')) {
282
+ this.refreshSemanticTokens();
115
283
  this.connection.sendRequest('masterCSS/restart', {
116
284
  title: 'Updating Master CSS configuration'
117
285
  });
118
286
  }
119
287
  }
120
- async onDidChangeConfiguration({ settings }) {
288
+ async onDidChangeConfiguration({ settings: changedSettings }) {
121
289
  await this.init();
122
- if (settings?.masterCSS) {
123
- this.connection.sendNotification('masterCSS/globalSettingsChanged', settings.masterCSS);
124
- this.customSettings = settings.masterCSS;
290
+ if (changedSettings?.masterCSS) {
291
+ this.connection.sendNotification('masterCSS/globalSettingsChanged', changedSettings.masterCSS);
292
+ this.customSettings = changedSettings.masterCSS;
293
+ this.settings = extend(settings, this.customSettings);
294
+ this.globalWorkspace.languageServiceSettings = this.settings;
295
+ this.refreshSemanticTokens();
125
296
  this.connection.sendRequest('masterCSS/restart', {
126
297
  title: 'Updating Master CSS settings'
127
298
  });
299
+ for (const workspace of [
300
+ this.globalWorkspace,
301
+ ...this.workspaces.values()
302
+ ]){
303
+ for (const document of workspace.openedTextDocuments){
304
+ this.publishCSSDirectiveDiagnostics(document, workspace);
305
+ }
306
+ }
128
307
  }
129
308
  }
130
309
  async initWorkspaceFolder(workspaceFolderURI) {
@@ -146,14 +325,9 @@ class CSSLanguageServer {
146
325
  this.console.info(`Registered global workspace folder`);
147
326
  }
148
327
  if (workspaces === 'auto') {
149
- (await glob('**/master.css.*', {
150
- cwd: workspaceFolderCWD,
151
- absolute: true,
152
- onlyFiles: true,
153
- ignore: [
154
- '**/node_modules/**'
155
- ]
156
- })).forEach((workspaceFile)=>resolvedWorkspaceDirectories.add(path.dirname(path.resolve(workspaceFile))));
328
+ for (const workspaceDir of (await findMasterCSSWorkspaceDirectories(workspaceFolderCWD))){
329
+ resolvedWorkspaceDirectories.add(workspaceDir);
330
+ }
157
331
  } else if (workspaces?.length) {
158
332
  (await glob(workspaces, {
159
333
  cwd: workspaceFolderCWD,
@@ -164,55 +338,142 @@ class CSSLanguageServer {
164
338
  ]
165
339
  })).forEach((workspaceDir)=>resolvedWorkspaceDirectories.add(path.resolve(workspaceDir)));
166
340
  }
167
- resolvedWorkspaceDirectories.forEach(async (workspaceDir)=>{
341
+ for (const workspaceDir of resolvedWorkspaceDirectories){
168
342
  const workspaceURI = URI.file(workspaceDir).toString();
169
343
  this.console.info(`Added workspace ${workspaceURI}`);
170
344
  this.workspaces.set(workspaceURI, {
171
345
  uri: workspaceURI,
172
346
  openedTextDocuments: [],
173
- languageServiceSettings
347
+ languageServiceSettings,
348
+ planEntries: await findCSSManifestEntryFiles(workspaceDir)
174
349
  });
175
- });
350
+ }
176
351
  }
177
- initWorkspaceLanguageService(workspace) {
178
- let workspaceConfig;
352
+ async initWorkspaceLanguageService(workspace) {
353
+ let workspacePlan;
179
354
  if (workspace !== this.globalWorkspace) {
180
355
  try {
181
- workspaceConfig = exploreConfig({
182
- cwd: workspace.uri && URI.parse(workspace.uri).fsPath,
183
- found: undefined
184
- });
356
+ workspacePlan = await this.loadWorkspacePlan(workspace);
185
357
  } catch (e) {
186
- this.console.info(`Failed to load config from ${workspace}`);
187
- this.console.error(e);
358
+ this.console.info(`Failed to load manifest from ${workspace.uri}`);
359
+ this.console.error(e instanceof Error ? e.stack : e.toString());
188
360
  }
189
- if (workspaceConfig) {
190
- this.console.info(`Initialized workspace ${workspaceConfig ? '(with config file)' : ''} ${workspace.uri}`);
361
+ if (workspacePlan) {
362
+ this.console.info(`Initialized workspace ${workspace.planEntries?.length ? '(with manifest entry)' : '(with manifest)'} ${workspace.uri}`);
191
363
  } else {
192
364
  this.console.info(`Initialized workspace ${workspace.uri}`);
193
365
  }
194
366
  }
195
367
  workspace.languageService = new CSSLanguageService({
196
368
  ...workspace.languageServiceSettings,
197
- config: workspaceConfig
369
+ manifest: workspacePlan
198
370
  });
199
371
  }
372
+ async loadWorkspacePlan(workspace) {
373
+ const cwd = workspace.uri ? URI.parse(workspace.uri).fsPath : process.cwd();
374
+ const result = await loadProjectManifest(cwd);
375
+ return result.entries.length ? result.manifest : workspace.languageServiceSettings.manifest;
376
+ }
200
377
  destroyLanguageService(workspace) {
201
378
  this.console.info(`Destroyed workspace ${workspace.uri}`);
202
379
  delete workspace.languageService;
203
380
  }
204
381
  findClosestWorkspace(textDocumentURI) {
382
+ const documentPath = toComparableFilePath(textDocumentURI);
383
+ if (!documentPath) {
384
+ this.console.info(`This is an external document ${textDocumentURI} with the global workspace`);
385
+ return this.globalWorkspace;
386
+ }
205
387
  let foundWorkspace;
388
+ let foundWorkspacePath = '';
206
389
  for (const [uri, workspace] of this.workspaces){
207
390
  if (!uri) continue;
208
- if (textDocumentURI.startsWith(uri) && (!foundWorkspace || uri.length > (foundWorkspace.uri || '').length)) {
391
+ const workspacePath = toComparableFilePath(uri);
392
+ if (!workspacePath) continue;
393
+ if (containsFilePath(workspacePath, documentPath) && workspacePath.length > foundWorkspacePath.length) {
209
394
  foundWorkspace = workspace;
395
+ foundWorkspacePath = workspacePath;
210
396
  }
211
397
  }
212
398
  if (foundWorkspace) return foundWorkspace;
213
399
  this.console.info(`This is an external document ${textDocumentURI} with the global workspace`);
214
400
  return this.globalWorkspace;
215
401
  }
402
+ publishCSSDirectiveDiagnostics(textDocument, workspace) {
403
+ if (!isCSSDiagnosticDocument(textDocument)) return;
404
+ const diagnostics = [];
405
+ const documentFile = path.resolve(URI.parse(textDocument.uri).fsPath);
406
+ for (const { source, offset } of getCSSDiagnosticSources(textDocument)){
407
+ try {
408
+ compileCSSManifest(source, {
409
+ from: documentFile
410
+ });
411
+ } catch (error) {
412
+ if (!isCSSDirectiveError(error)) continue;
413
+ const diagnostic = this.createCSSDirectiveDiagnostic(error, textDocument, documentFile, offset);
414
+ if (diagnostic) diagnostics.push(diagnostic);
415
+ }
416
+ }
417
+ this.connection.sendDiagnostics({
418
+ uri: textDocument.uri,
419
+ diagnostics
420
+ });
421
+ }
422
+ createCSSDirectiveDiagnostic(error, textDocument, documentFile, sourceOffset) {
423
+ if (error.source?.file && path.resolve(error.source.file) !== documentFile) return;
424
+ return {
425
+ range: this.createCSSDirectiveRange(error.source, textDocument, documentFile, sourceOffset),
426
+ severity: DiagnosticSeverity.Error,
427
+ code: error.code,
428
+ source: 'Master CSS',
429
+ message: error.message,
430
+ relatedInformation: this.createCSSDirectiveRelatedInformation(error.related, textDocument, documentFile, sourceOffset)
431
+ };
432
+ }
433
+ createCSSDirectiveRelatedInformation(related, textDocument, documentFile, sourceOffset) {
434
+ if (!related?.length) return;
435
+ return related.map(({ message, source })=>({
436
+ message,
437
+ location: {
438
+ uri: source?.file ? URI.file(source.file).toString() : textDocument.uri,
439
+ range: this.createCSSDirectiveRange(source, textDocument, documentFile, sourceOffset)
440
+ }
441
+ }));
442
+ }
443
+ createCSSDirectiveRange(source, textDocument, documentFile, sourceOffset) {
444
+ if (source?.file && path.resolve(source.file) !== documentFile) {
445
+ return source.loc ? {
446
+ start: {
447
+ line: Math.max(0, source.loc.start.line - 1),
448
+ character: Math.max(0, source.loc.start.column - 1)
449
+ },
450
+ end: {
451
+ line: Math.max(0, source.loc.end.line - 1),
452
+ character: Math.max(0, source.loc.end.column - 1)
453
+ }
454
+ } : {
455
+ start: {
456
+ line: 0,
457
+ character: 0
458
+ },
459
+ end: {
460
+ line: 0,
461
+ character: 0
462
+ }
463
+ };
464
+ }
465
+ const start = Math.max(0, (source?.range.start || 0) + sourceOffset);
466
+ const end = Math.max(start, (source?.range.end || 0) + sourceOffset);
467
+ return {
468
+ start: textDocument.positionAt(start),
469
+ end: textDocument.positionAt(end)
470
+ };
471
+ }
472
+ refreshSemanticTokens() {
473
+ if (this.settings?.embeddedSyntaxHighlighting !== 'always') return;
474
+ if (!this.clientCapabilities.workspace?.semanticTokens?.refreshSupport) return;
475
+ this.connection.languages.semanticTokens.refresh();
476
+ }
216
477
  stop() {
217
478
  this.connection.sendNotification('masterCSS/dispose');
218
479
  this.disposables.forEach((disposable)=>disposable.dispose());
@@ -222,4 +483,4 @@ class CSSLanguageServer {
222
483
  }
223
484
  }
224
485
 
225
- export { CSSLanguageServer as default };
486
+ export { ACTIVE_SEMANTIC_TOKENS_REQUEST, DOCUMENT_SEMANTIC_TOKENS_REQUEST, CSSLanguageServer as default };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default, default as CSSLanguageServer } from './core';
2
+ export { ACTIVE_SEMANTIC_TOKENS_REQUEST, DOCUMENT_SEMANTIC_TOKENS_REQUEST } from './core';
2
3
  export { default as settings, type Settings } from './settings';
3
4
  export type { Workspace } from './core';
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export { default as CSSLanguageServer, default } from './core.mjs';
1
+ export { ACTIVE_SEMANTIC_TOKENS_REQUEST, default as CSSLanguageServer, DOCUMENT_SEMANTIC_TOKENS_REQUEST, default } from './core.mjs';
2
2
  export { default as settings } from './settings.mjs';
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@master/css-language-server","type":"module","scripts":{"build":"techor build \"src/**/*.ts\"","dev":"pnpm build --watch","type-check":"tsc --noEmit","lint":"eslint","test":"vitest"},"description":"Master CSS Language Server","author":"Aoyue Design LLC.","license":"MIT","repository":{"type":"git","url":"https://github.com/master-co/css.git","directory":"packages/language-server"},"dependencies":{"@master/css":"^2.0.0-rc.69","@master/css-explore-config":"^2.0.0-rc.69","@master/css-language-service":"^2.0.0-rc.69","@techor/extend":"^3.2.5","fast-glob":"^3.3.2","js-beautify":"^1.15.1","nanoid":"^5.0.6","vscode-languageserver":"^9.0.1","vscode-languageserver-textdocument":"^1.0.11","vscode-uri":"^3.0.8"},"devDependencies":{"shared":"^2.0.0-rc.69"},"sideEffects":false,"main":"./dist/index.cjs","module":"./dist/index.mjs","jsnext:main":"./dist/index.mjs","esnext":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"}},"files":["dist"],"publishConfig":{"access":"public","provenance":true},"version":"2.0.0-rc.69"}
1
+ {"name":"@master/css-language-server","type":"module","scripts":{"build":"techor build \"src/**/*.ts\" --formats esm","dev":"pnpm build --watch","type-check":"tsc --noEmit","lint":"eslint","test":"vitest"},"description":"Master CSS Language Server","author":"Aoyue Design LLC.","license":"MIT","repository":{"type":"git","url":"https://github.com/master-co/css.git","directory":"packages/language-server"},"dependencies":{"@master/css":"^2.0.0-rc.70","@master/css-compiler":"^2.0.0-rc.70","@master/css-language":"^2.0.0-rc.70","@master/css-language-service":"^2.0.0-rc.70","@master/css-project":"^2.0.0-rc.70","@techor/extend":"^3.2.5","fast-glob":"^3.3.3","js-beautify":"^1.15.4","nanoid":"^5.1.15","vscode-languageserver":"^10.0.0","vscode-languageserver-textdocument":"^1.0.12","vscode-uri":"^3.1.0","@master/css-schema":"^2.0.0-rc.70"},"sideEffects":false,"main":"./dist/index.mjs","module":"./dist/index.mjs","jsnext:main":"./dist/index.mjs","esnext":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","default":"./dist/index.mjs"}},"files":["dist"],"publishConfig":{"access":"public","provenance":true},"version":"2.0.0-rc.70"}
package/dist/core.cjs DELETED
@@ -1,227 +0,0 @@
1
- 'use strict';
2
-
3
- var node = require('vscode-languageserver/node');
4
- var vscodeLanguageserverTextdocument = require('vscode-languageserver-textdocument');
5
- var path = require('node:path');
6
- var CSSLanguageService = require('@master/css-language-service');
7
- var exploreConfig = require('@master/css-explore-config');
8
- var extend = require('@techor/extend');
9
- var settings = require('./settings.cjs');
10
- var glob = require('fast-glob');
11
- var vscodeUri = require('vscode-uri');
12
-
13
- class CSSLanguageServer {
14
- connection;
15
- customSettings;
16
- workspaceFolders = [];
17
- workspaces = new Map();
18
- globalWorkspace = {
19
- uri: '',
20
- openedTextDocuments: [],
21
- languageServiceSettings: this.settings
22
- };
23
- documents;
24
- initializing;
25
- clientCapabilities = {};
26
- settings;
27
- console;
28
- disposables = [];
29
- constructor(connection = process.argv.includes('--stdio') ? node.createConnection(process.stdin, process.stdout) : node.createConnection(), customSettings){
30
- this.connection = connection;
31
- this.customSettings = customSettings;
32
- this.documents = new node.TextDocuments(vscodeLanguageserverTextdocument.TextDocument);
33
- this.settings = extend(settings, this.customSettings);
34
- this.console = new Proxy(this.connection.console, {
35
- get: (target, prop)=>{
36
- if (!this.settings?.verbose) return ()=>{};
37
- return this.connection.console[prop];
38
- }
39
- });
40
- }
41
- start() {
42
- this.disposables.push(this.documents.onDidSave(this.onDidSave.bind(this)), this.documents.onDidOpen(this.onDidOpen.bind(this)), this.documents.onDidClose(this.onDidClose.bind(this)), this.documents.listen(this.connection), this.connection.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)), this.connection.onHover(this.onHover.bind(this)), this.connection.onCompletion(this.onCompletion.bind(this)), this.connection.onDocumentColor(this.onDocumentColor.bind(this)), this.connection.onColorPresentation(this.onColorPresentation.bind(this)), this.connection.onInitialize(this.onInitialize.bind(this)), this.connection.onInitialized(()=>this.init()));
43
- this.connection.listen();
44
- }
45
- init() {
46
- if (this.initializing) return this.initializing;
47
- return this.initializing = new Promise(async (resolve)=>{
48
- await Promise.all(this.workspaceFolders.map((folder)=>this.initWorkspaceFolder(folder.uri)));
49
- resolve();
50
- });
51
- }
52
- onInitialize(params) {
53
- this.clientCapabilities = params.capabilities;
54
- if (params.workspaceFolders?.length) {
55
- this.workspaceFolders = params.workspaceFolders;
56
- }
57
- return {
58
- capabilities: CSSLanguageService.SERVER_CAPABILITIES
59
- };
60
- }
61
- async onHover(params) {
62
- await this.init();
63
- const workspace = this.findClosestWorkspace(params.textDocument.uri);
64
- if (workspace?.languageService) {
65
- const document = this.documents.get(params.textDocument.uri);
66
- if (document) return workspace.languageService.inspectSyntax(document, params.position);
67
- }
68
- }
69
- async onCompletion(params) {
70
- await this.init();
71
- const workspace = this.findClosestWorkspace(params.textDocument.uri);
72
- if (workspace?.languageService) {
73
- const document = this.documents.get(params.textDocument.uri);
74
- if (document) return workspace.languageService.suggestSyntax(document, params.position, params.context);
75
- }
76
- }
77
- async onDocumentColor(params) {
78
- await this.init();
79
- const workspace = this.findClosestWorkspace(params.textDocument.uri);
80
- if (workspace?.languageService) {
81
- const document = this.documents.get(params.textDocument.uri);
82
- if (document) return workspace.languageService.renderSyntaxColors(document);
83
- }
84
- }
85
- async onColorPresentation(params) {
86
- await this.init();
87
- const workspace = this.findClosestWorkspace(params.textDocument.uri);
88
- if (workspace?.languageService) {
89
- const document = this.documents.get(params.textDocument.uri);
90
- if (document) return workspace.languageService.editSyntaxColors(document, params.color, params.range);
91
- }
92
- }
93
- async onDidOpen(params) {
94
- await this.init();
95
- const workspace = this.findClosestWorkspace(params.document.uri);
96
- if (!workspace || workspace.openedTextDocuments.includes(params.document)) return;
97
- if (!workspace.openedTextDocuments.length) {
98
- this.initWorkspaceLanguageService(workspace);
99
- }
100
- workspace.openedTextDocuments.push(params.document);
101
- }
102
- async onDidClose(params) {
103
- await this.init();
104
- const workspace = this.findClosestWorkspace(params.document.uri);
105
- if (!workspace) return;
106
- workspace.openedTextDocuments.splice(workspace.openedTextDocuments.indexOf(params.document), 1);
107
- if (!workspace.openedTextDocuments.length) {
108
- this.destroyLanguageService(workspace);
109
- }
110
- }
111
- async onDidSave(params) {
112
- await this.init();
113
- const workspace = this.findClosestWorkspace(params.document.uri);
114
- if (!workspace) return;
115
- const name = path.parse(params.document.uri).name;
116
- if (name === 'master.css' || name.endsWith('.css')) {
117
- this.connection.sendRequest('masterCSS/restart', {
118
- title: 'Updating Master CSS configuration'
119
- });
120
- }
121
- }
122
- async onDidChangeConfiguration({ settings }) {
123
- await this.init();
124
- if (settings?.masterCSS) {
125
- this.connection.sendNotification('masterCSS/globalSettingsChanged', settings.masterCSS);
126
- this.customSettings = settings.masterCSS;
127
- this.connection.sendRequest('masterCSS/restart', {
128
- title: 'Updating Master CSS settings'
129
- });
130
- }
131
- }
132
- async initWorkspaceFolder(workspaceFolderURI) {
133
- const workspaceFolderCWD = vscodeUri.URI.parse(workspaceFolderURI).fsPath;
134
- let customWorkspaceFolderSettings;
135
- if (this.clientCapabilities.workspace?.configuration) {
136
- customWorkspaceFolderSettings = await this.connection.workspace.getConfiguration({
137
- scopeUri: workspaceFolderURI,
138
- section: 'masterCSS'
139
- });
140
- }
141
- const { workspaces, ...languageServiceSettings } = extend(settings, this.customSettings, customWorkspaceFolderSettings);
142
- const resolvedWorkspaceDirectories = new Set([
143
- workspaceFolderCWD
144
- ]);
145
- if (workspaceFolderCWD) {
146
- this.console.info(`Registered workspace folder ${workspaceFolderURI}`);
147
- } else {
148
- this.console.info(`Registered global workspace folder`);
149
- }
150
- if (workspaces === 'auto') {
151
- (await glob('**/master.css.*', {
152
- cwd: workspaceFolderCWD,
153
- absolute: true,
154
- onlyFiles: true,
155
- ignore: [
156
- '**/node_modules/**'
157
- ]
158
- })).forEach((workspaceFile)=>resolvedWorkspaceDirectories.add(path.dirname(path.resolve(workspaceFile))));
159
- } else if (workspaces?.length) {
160
- (await glob(workspaces, {
161
- cwd: workspaceFolderCWD,
162
- absolute: true,
163
- onlyDirectories: true,
164
- ignore: [
165
- '**/node_modules/**'
166
- ]
167
- })).forEach((workspaceDir)=>resolvedWorkspaceDirectories.add(path.resolve(workspaceDir)));
168
- }
169
- resolvedWorkspaceDirectories.forEach(async (workspaceDir)=>{
170
- const workspaceURI = vscodeUri.URI.file(workspaceDir).toString();
171
- this.console.info(`Added workspace ${workspaceURI}`);
172
- this.workspaces.set(workspaceURI, {
173
- uri: workspaceURI,
174
- openedTextDocuments: [],
175
- languageServiceSettings
176
- });
177
- });
178
- }
179
- initWorkspaceLanguageService(workspace) {
180
- let workspaceConfig;
181
- if (workspace !== this.globalWorkspace) {
182
- try {
183
- workspaceConfig = exploreConfig({
184
- cwd: workspace.uri && vscodeUri.URI.parse(workspace.uri).fsPath,
185
- found: undefined
186
- });
187
- } catch (e) {
188
- this.console.info(`Failed to load config from ${workspace}`);
189
- this.console.error(e);
190
- }
191
- if (workspaceConfig) {
192
- this.console.info(`Initialized workspace ${workspaceConfig ? '(with config file)' : ''} ${workspace.uri}`);
193
- } else {
194
- this.console.info(`Initialized workspace ${workspace.uri}`);
195
- }
196
- }
197
- workspace.languageService = new CSSLanguageService({
198
- ...workspace.languageServiceSettings,
199
- config: workspaceConfig
200
- });
201
- }
202
- destroyLanguageService(workspace) {
203
- this.console.info(`Destroyed workspace ${workspace.uri}`);
204
- delete workspace.languageService;
205
- }
206
- findClosestWorkspace(textDocumentURI) {
207
- let foundWorkspace;
208
- for (const [uri, workspace] of this.workspaces){
209
- if (!uri) continue;
210
- if (textDocumentURI.startsWith(uri) && (!foundWorkspace || uri.length > (foundWorkspace.uri || '').length)) {
211
- foundWorkspace = workspace;
212
- }
213
- }
214
- if (foundWorkspace) return foundWorkspace;
215
- this.console.info(`This is an external document ${textDocumentURI} with the global workspace`);
216
- return this.globalWorkspace;
217
- }
218
- stop() {
219
- this.connection.sendNotification('masterCSS/dispose');
220
- this.disposables.forEach((disposable)=>disposable.dispose());
221
- this.disposables.length = 0;
222
- this.connection.dispose();
223
- this.initializing = undefined;
224
- }
225
- }
226
-
227
- module.exports = CSSLanguageServer;
package/dist/index.cjs DELETED
@@ -1,12 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var core = require('./core.cjs');
6
- var settings = require('./settings.cjs');
7
-
8
-
9
-
10
- exports.CSSLanguageServer = core;
11
- exports.default = core;
12
- exports.settings = settings;
package/dist/settings.cjs DELETED
@@ -1,11 +0,0 @@
1
- 'use strict';
2
-
3
- var CSSLanguageService = require('@master/css-language-service');
4
-
5
- const settings = {
6
- ...CSSLanguageService.settings,
7
- workspaces: 'auto',
8
- verbose: false
9
- };
10
-
11
- module.exports = settings;
@@ -1,30 +0,0 @@
1
- 'use strict';
2
-
3
- var vscodeLanguageserverTextdocument = require('vscode-languageserver-textdocument');
4
- var nanoid = require('nanoid');
5
- var path = require('path');
6
- var vscodeUri = require('vscode-uri');
7
-
8
- const extOfLang = {
9
- typescript: 'ts',
10
- typescriptreact: 'tsx',
11
- javascript: 'js',
12
- javascriptreact: 'jsx',
13
- css: 'css',
14
- scss: 'scss',
15
- less: 'less',
16
- html: 'html',
17
- json: 'json',
18
- vue: 'vue',
19
- svelte: 'svelte',
20
- astro: 'astro'
21
- };
22
- function createDocument(text, options) {
23
- const lang = options?.lang || 'html';
24
- const dir = options?.dir || './';
25
- const filePath = path.join(dir, `${nanoid.nanoid()}.${extOfLang[lang]}`);
26
- const fileURI = vscodeUri.URI.file(filePath).toString();
27
- return vscodeLanguageserverTextdocument.TextDocument.create(fileURI, lang, 0, text);
28
- }
29
-
30
- module.exports = createDocument;