css-variable-lsp 1.0.6 → 1.0.8-beta.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/.git-blame-ignore-revs +3 -0
- package/AGENTS.md +10 -0
- package/LIMITATIONS.md +28 -8
- package/README.md +66 -76
- package/check_node_html_parser.ts +10 -0
- package/out/colorService.js +161 -155
- package/out/colorService.js.map +1 -1
- package/out/cssVariableManager.js +121 -69
- package/out/cssVariableManager.js.map +1 -1
- package/out/domTree.js +2 -9
- package/out/domTree.js.map +1 -1
- package/out/server.js +331 -96
- package/out/server.js.map +1 -1
- package/out/specificity.js +13 -13
- package/out/specificity.js.map +1 -1
- package/package.json +2 -2
- package/tsconfig.test.json +9 -0
- package/CHANGES_SUMMARY.md +0 -221
package/out/server.js
CHANGED
|
@@ -3,13 +3,119 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const node_1 = require("vscode-languageserver/node");
|
|
5
5
|
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const vscode_uri_1 = require("vscode-uri");
|
|
6
8
|
const cssVariableManager_1 = require("./cssVariableManager");
|
|
7
9
|
const specificity_1 = require("./specificity");
|
|
8
10
|
const colorService_1 = require("./colorService");
|
|
9
11
|
// Parse command-line arguments
|
|
10
12
|
const args = process.argv.slice(2);
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
+
function getArgValue(name) {
|
|
14
|
+
const flag = `--${name}`;
|
|
15
|
+
const directIndex = args.indexOf(flag);
|
|
16
|
+
if (directIndex !== -1) {
|
|
17
|
+
const candidate = args[directIndex + 1];
|
|
18
|
+
if (candidate && !candidate.startsWith("-")) {
|
|
19
|
+
return candidate;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const prefix = `${flag}=`;
|
|
24
|
+
const withEquals = args.find((arg) => arg.startsWith(prefix));
|
|
25
|
+
if (withEquals) {
|
|
26
|
+
return withEquals.slice(prefix.length);
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function parseOptionalInt(value) {
|
|
31
|
+
if (!value) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const parsed = Number.parseInt(value, 10);
|
|
35
|
+
if (Number.isNaN(parsed)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
function normalizePathDisplayMode(value) {
|
|
41
|
+
if (!value) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
switch (value.toLowerCase()) {
|
|
45
|
+
case "relative":
|
|
46
|
+
return "relative";
|
|
47
|
+
case "absolute":
|
|
48
|
+
return "absolute";
|
|
49
|
+
case "abbreviated":
|
|
50
|
+
case "abbr":
|
|
51
|
+
case "fish":
|
|
52
|
+
return "abbreviated";
|
|
53
|
+
default:
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function parsePathDisplay(value) {
|
|
58
|
+
if (!value) {
|
|
59
|
+
return { mode: null, abbrevLength: null };
|
|
60
|
+
}
|
|
61
|
+
const [modePart, lengthPart] = value.split(":", 2);
|
|
62
|
+
const mode = normalizePathDisplayMode(modePart);
|
|
63
|
+
const abbrevLength = parseOptionalInt(lengthPart);
|
|
64
|
+
return { mode, abbrevLength };
|
|
65
|
+
}
|
|
66
|
+
function splitLookupList(value) {
|
|
67
|
+
return value
|
|
68
|
+
.split(",")
|
|
69
|
+
.map((entry) => entry.trim())
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
}
|
|
72
|
+
function resolveLookupFiles(argv) {
|
|
73
|
+
const cliFiles = [];
|
|
74
|
+
for (let i = 0; i < argv.length; i++) {
|
|
75
|
+
const arg = argv[i];
|
|
76
|
+
if (arg === "--lookup-files" && argv[i + 1]) {
|
|
77
|
+
cliFiles.push(...splitLookupList(argv[i + 1]));
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (arg.startsWith("--lookup-files=")) {
|
|
82
|
+
cliFiles.push(...splitLookupList(arg.slice("--lookup-files=".length)));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (arg === "--lookup-file" && argv[i + 1]) {
|
|
86
|
+
cliFiles.push(argv[i + 1]);
|
|
87
|
+
i++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (arg.startsWith("--lookup-file=")) {
|
|
91
|
+
cliFiles.push(arg.slice("--lookup-file=".length));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (cliFiles.length > 0) {
|
|
95
|
+
return cliFiles;
|
|
96
|
+
}
|
|
97
|
+
const envValue = process.env.CSS_LSP_LOOKUP_FILES;
|
|
98
|
+
if (envValue) {
|
|
99
|
+
const envFiles = splitLookupList(envValue);
|
|
100
|
+
if (envFiles.length > 0) {
|
|
101
|
+
return envFiles;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
const ENABLE_COLOR_PROVIDER = !args.includes("--no-color-preview");
|
|
107
|
+
const COLOR_ONLY_ON_VARIABLES = args.includes("--color-only-variables") ||
|
|
108
|
+
process.env.CSS_LSP_COLOR_ONLY_VARIABLES === "1";
|
|
109
|
+
const LOOKUP_FILES = resolveLookupFiles(args);
|
|
110
|
+
const pathDisplayArg = getArgValue("path-display");
|
|
111
|
+
const pathDisplayEnv = process.env.CSS_LSP_PATH_DISPLAY;
|
|
112
|
+
const parsedPathDisplay = parsePathDisplay(pathDisplayArg ?? pathDisplayEnv);
|
|
113
|
+
const PATH_DISPLAY_MODE = parsedPathDisplay.mode ?? "relative";
|
|
114
|
+
const pathDisplayLengthArg = getArgValue("path-display-length");
|
|
115
|
+
const pathDisplayLengthEnv = process.env.CSS_LSP_PATH_DISPLAY_LENGTH;
|
|
116
|
+
const abbrevLengthRaw = parseOptionalInt(pathDisplayLengthArg ?? pathDisplayLengthEnv) ??
|
|
117
|
+
parsedPathDisplay.abbrevLength;
|
|
118
|
+
const PATH_DISPLAY_ABBREV_LENGTH = Math.max(0, abbrevLengthRaw ?? 1);
|
|
13
119
|
// Create a connection for the server, using Node's IPC as a transport.
|
|
14
120
|
// Also include all preview / proposed LSP features.
|
|
15
121
|
const connection = (0, node_1.createConnection)(node_1.ProposedFeatures.all);
|
|
@@ -20,14 +126,59 @@ function logDebug(label, payload) {
|
|
|
20
126
|
connection.console.log(message);
|
|
21
127
|
}
|
|
22
128
|
}
|
|
129
|
+
function toNormalizedFsPath(uri) {
|
|
130
|
+
try {
|
|
131
|
+
const fsPath = vscode_uri_1.URI.parse(uri).fsPath;
|
|
132
|
+
return fsPath ? path.normalize(fsPath) : null;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function updateWorkspaceFolderPaths(folders) {
|
|
139
|
+
if (!folders) {
|
|
140
|
+
workspaceFolderPaths = [];
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const paths = folders
|
|
144
|
+
.map((folder) => toNormalizedFsPath(folder.uri))
|
|
145
|
+
.filter((folderPath) => Boolean(folderPath));
|
|
146
|
+
workspaceFolderPaths = paths.toSorted((a, b) => b.length - a.length);
|
|
147
|
+
}
|
|
148
|
+
function formatUriForDisplay(uri) {
|
|
149
|
+
const fsPath = toNormalizedFsPath(uri);
|
|
150
|
+
if (!fsPath) {
|
|
151
|
+
return uri;
|
|
152
|
+
}
|
|
153
|
+
const roots = workspaceFolderPaths.length
|
|
154
|
+
? workspaceFolderPaths
|
|
155
|
+
: rootFolderPath
|
|
156
|
+
? [rootFolderPath]
|
|
157
|
+
: [];
|
|
158
|
+
let bestRelative = null;
|
|
159
|
+
for (const root of roots) {
|
|
160
|
+
const relativePath = path.relative(root, fsPath);
|
|
161
|
+
if (!relativePath ||
|
|
162
|
+
relativePath.startsWith("..") ||
|
|
163
|
+
path.isAbsolute(relativePath)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!bestRelative || relativePath.length < bestRelative.length) {
|
|
167
|
+
bestRelative = relativePath;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return bestRelative || fsPath;
|
|
171
|
+
}
|
|
23
172
|
// Create a simple text document manager.
|
|
24
173
|
const documents = new node_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument);
|
|
25
|
-
const cssVariableManager = new cssVariableManager_1.CssVariableManager(connection.console);
|
|
174
|
+
const cssVariableManager = new cssVariableManager_1.CssVariableManager(connection.console, LOOKUP_FILES);
|
|
26
175
|
let hasConfigurationCapability = false;
|
|
27
176
|
let hasWorkspaceFolderCapability = false;
|
|
28
177
|
let hasDiagnosticRelatedInformationCapability = false;
|
|
178
|
+
let workspaceFolderPaths = [];
|
|
179
|
+
let rootFolderPath = null;
|
|
29
180
|
connection.onInitialize((params) => {
|
|
30
|
-
logDebug(
|
|
181
|
+
logDebug("initialize", {
|
|
31
182
|
rootUri: params.rootUri,
|
|
32
183
|
// rootPath is deprecated and optional in InitializeParams
|
|
33
184
|
rootPath: params.rootPath,
|
|
@@ -42,13 +193,25 @@ connection.onInitialize((params) => {
|
|
|
42
193
|
hasDiagnosticRelatedInformationCapability = !!(capabilities.textDocument &&
|
|
43
194
|
capabilities.textDocument.publishDiagnostics &&
|
|
44
195
|
capabilities.textDocument.publishDiagnostics.relatedInformation);
|
|
196
|
+
if (params.rootUri) {
|
|
197
|
+
try {
|
|
198
|
+
rootFolderPath = path.normalize(vscode_uri_1.URI.parse(params.rootUri).fsPath);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
rootFolderPath = null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (params.rootPath) {
|
|
205
|
+
rootFolderPath = path.normalize(params.rootPath);
|
|
206
|
+
}
|
|
207
|
+
updateWorkspaceFolderPaths(params.workspaceFolders || undefined);
|
|
45
208
|
const result = {
|
|
46
209
|
capabilities: {
|
|
47
210
|
textDocumentSync: node_1.TextDocumentSyncKind.Incremental,
|
|
48
211
|
// Tell the client that this server supports code completion.
|
|
49
212
|
completionProvider: {
|
|
50
213
|
resolveProvider: true,
|
|
51
|
-
triggerCharacters: [
|
|
214
|
+
triggerCharacters: ["-"],
|
|
52
215
|
},
|
|
53
216
|
definitionProvider: true,
|
|
54
217
|
hoverProvider: true,
|
|
@@ -56,14 +219,14 @@ connection.onInitialize((params) => {
|
|
|
56
219
|
renameProvider: true,
|
|
57
220
|
documentSymbolProvider: true,
|
|
58
221
|
workspaceSymbolProvider: true,
|
|
59
|
-
colorProvider: ENABLE_COLOR_PROVIDER
|
|
60
|
-
}
|
|
222
|
+
colorProvider: ENABLE_COLOR_PROVIDER,
|
|
223
|
+
},
|
|
61
224
|
};
|
|
62
225
|
if (hasWorkspaceFolderCapability) {
|
|
63
226
|
result.capabilities.workspace = {
|
|
64
227
|
workspaceFolders: {
|
|
65
|
-
supported: true
|
|
66
|
-
}
|
|
228
|
+
supported: true,
|
|
229
|
+
},
|
|
67
230
|
};
|
|
68
231
|
}
|
|
69
232
|
return result;
|
|
@@ -74,15 +237,19 @@ connection.onInitialized(async () => {
|
|
|
74
237
|
connection.client.register(node_1.DidChangeConfigurationNotification.type, undefined);
|
|
75
238
|
}
|
|
76
239
|
if (hasWorkspaceFolderCapability) {
|
|
77
|
-
connection.workspace.onDidChangeWorkspaceFolders(_event => {
|
|
78
|
-
connection.console.log(
|
|
240
|
+
connection.workspace.onDidChangeWorkspaceFolders((_event) => {
|
|
241
|
+
connection.console.log("Workspace folder change event received.");
|
|
242
|
+
void connection.workspace.getWorkspaceFolders().then((folders) => {
|
|
243
|
+
updateWorkspaceFolderPaths(folders || undefined);
|
|
244
|
+
});
|
|
79
245
|
});
|
|
80
246
|
}
|
|
81
247
|
// Scan workspace for CSS variables on initialization with progress reporting
|
|
82
248
|
const workspaceFolders = await connection.workspace.getWorkspaceFolders();
|
|
83
249
|
if (workspaceFolders) {
|
|
84
|
-
|
|
85
|
-
|
|
250
|
+
updateWorkspaceFolderPaths(workspaceFolders || undefined);
|
|
251
|
+
connection.console.log("Scanning workspace for CSS variables...");
|
|
252
|
+
const folderUris = workspaceFolders.map((f) => f.uri);
|
|
86
253
|
// Scan with progress callback that logs to console
|
|
87
254
|
let lastLoggedPercentage = 0;
|
|
88
255
|
await cssVariableManager.scanWorkspace(folderUris, (current, total) => {
|
|
@@ -103,7 +270,7 @@ const defaultSettings = { maxNumberOfProblems: 1000 };
|
|
|
103
270
|
let globalSettings = defaultSettings;
|
|
104
271
|
// Cache the settings of all open documents
|
|
105
272
|
const documentSettings = new Map();
|
|
106
|
-
connection.onDidChangeConfiguration(change => {
|
|
273
|
+
connection.onDidChangeConfiguration((change) => {
|
|
107
274
|
if (hasConfigurationCapability) {
|
|
108
275
|
// Reset all cached document settings
|
|
109
276
|
documentSettings.clear();
|
|
@@ -122,7 +289,7 @@ function getDocumentSettings(resource) {
|
|
|
122
289
|
if (!result) {
|
|
123
290
|
result = connection.workspace.getConfiguration({
|
|
124
291
|
scopeUri: resource,
|
|
125
|
-
section:
|
|
292
|
+
section: "cssVariableLsp",
|
|
126
293
|
});
|
|
127
294
|
documentSettings.set(resource, result);
|
|
128
295
|
}
|
|
@@ -143,7 +310,7 @@ const validationTimeouts = new Map();
|
|
|
143
310
|
// when the text document first opened or when its content has changed.
|
|
144
311
|
// Note: We don't need a separate onDidOpen handler because onDidChangeContent
|
|
145
312
|
// already fires when a document is first opened, avoiding double-parsing.
|
|
146
|
-
documents.onDidChangeContent(change => {
|
|
313
|
+
documents.onDidChangeContent((change) => {
|
|
147
314
|
// Parse immediately (needed for completion/hover)
|
|
148
315
|
cssVariableManager.parseDocument(change.document);
|
|
149
316
|
// Debounce validation to avoid excessive diagnostic updates while typing
|
|
@@ -161,7 +328,6 @@ documents.onDidChangeContent(change => {
|
|
|
161
328
|
validationTimeouts.set(uri, timeout);
|
|
162
329
|
});
|
|
163
330
|
async function validateTextDocument(textDocument) {
|
|
164
|
-
const settings = await getDocumentSettings(textDocument.uri);
|
|
165
331
|
const text = textDocument.getText();
|
|
166
332
|
const diagnostics = [];
|
|
167
333
|
// Find all var(--variable) usages
|
|
@@ -178,10 +344,10 @@ async function validateTextDocument(textDocument) {
|
|
|
178
344
|
severity: node_1.DiagnosticSeverity.Warning,
|
|
179
345
|
range: {
|
|
180
346
|
start: startPos,
|
|
181
|
-
end: endPos
|
|
347
|
+
end: endPos,
|
|
182
348
|
},
|
|
183
349
|
message: `CSS variable '${variableName}' is not defined in the workspace`,
|
|
184
|
-
source:
|
|
350
|
+
source: "css-variable-lsp",
|
|
185
351
|
};
|
|
186
352
|
if (hasDiagnosticRelatedInformationCapability) {
|
|
187
353
|
diagnostic.relatedInformation = [];
|
|
@@ -194,8 +360,8 @@ async function validateTextDocument(textDocument) {
|
|
|
194
360
|
}
|
|
195
361
|
connection.onDidChangeWatchedFiles(async (change) => {
|
|
196
362
|
// Monitored files have changed in the client
|
|
197
|
-
connection.console.log(
|
|
198
|
-
logDebug(
|
|
363
|
+
connection.console.log("Received file change event");
|
|
364
|
+
logDebug("didChangeWatchedFiles", change);
|
|
199
365
|
for (const fileEvent of change.changes) {
|
|
200
366
|
if (fileEvent.type === node_1.FileChangeType.Deleted) {
|
|
201
367
|
cssVariableManager.removeFile(fileEvent.uri);
|
|
@@ -225,26 +391,32 @@ function getPropertyNameFromContext(beforeCursor) {
|
|
|
225
391
|
let lastBracePos = -1;
|
|
226
392
|
for (let i = beforeCursor.length - 1; i >= 0; i--) {
|
|
227
393
|
const char = beforeCursor[i];
|
|
228
|
-
if (char ===
|
|
394
|
+
if (char === ")")
|
|
229
395
|
inParens++;
|
|
230
|
-
else if (char ===
|
|
396
|
+
else if (char === "(") {
|
|
231
397
|
inParens--;
|
|
232
398
|
if (inParens < 0)
|
|
233
399
|
break;
|
|
234
400
|
}
|
|
235
|
-
else if (char ===
|
|
401
|
+
else if (char === "}")
|
|
236
402
|
inBraces++;
|
|
237
|
-
else if (char ===
|
|
403
|
+
else if (char === "{") {
|
|
238
404
|
inBraces--;
|
|
239
405
|
if (inBraces < 0) {
|
|
240
406
|
lastBracePos = i;
|
|
241
407
|
break;
|
|
242
408
|
}
|
|
243
409
|
}
|
|
244
|
-
else if (char ===
|
|
410
|
+
else if (char === ":" &&
|
|
411
|
+
inParens === 0 &&
|
|
412
|
+
inBraces === 0 &&
|
|
413
|
+
lastColonPos === -1) {
|
|
245
414
|
lastColonPos = i;
|
|
246
415
|
}
|
|
247
|
-
else if (char ===
|
|
416
|
+
else if (char === ";" &&
|
|
417
|
+
inParens === 0 &&
|
|
418
|
+
inBraces === 0 &&
|
|
419
|
+
lastSemicolonPos === -1) {
|
|
248
420
|
lastSemicolonPos = i;
|
|
249
421
|
}
|
|
250
422
|
}
|
|
@@ -268,65 +440,116 @@ function scoreVariableRelevance(varName, propertyName) {
|
|
|
268
440
|
}
|
|
269
441
|
const lowerVarName = varName.toLowerCase();
|
|
270
442
|
// Color-related properties
|
|
271
|
-
const colorProperties = [
|
|
443
|
+
const colorProperties = [
|
|
444
|
+
"color",
|
|
445
|
+
"background-color",
|
|
446
|
+
"background",
|
|
447
|
+
"border-color",
|
|
448
|
+
"outline-color",
|
|
449
|
+
"text-decoration-color",
|
|
450
|
+
"fill",
|
|
451
|
+
"stroke",
|
|
452
|
+
];
|
|
272
453
|
if (colorProperties.includes(propertyName)) {
|
|
273
454
|
// High relevance: variable name contains color-related keywords
|
|
274
|
-
if (lowerVarName.includes(
|
|
275
|
-
lowerVarName.includes(
|
|
276
|
-
lowerVarName.includes(
|
|
455
|
+
if (lowerVarName.includes("color") ||
|
|
456
|
+
lowerVarName.includes("bg") ||
|
|
457
|
+
lowerVarName.includes("background") ||
|
|
458
|
+
lowerVarName.includes("primary") ||
|
|
459
|
+
lowerVarName.includes("secondary") ||
|
|
460
|
+
lowerVarName.includes("accent") ||
|
|
461
|
+
lowerVarName.includes("text") ||
|
|
462
|
+
lowerVarName.includes("border") ||
|
|
463
|
+
lowerVarName.includes("link")) {
|
|
277
464
|
return 10;
|
|
278
465
|
}
|
|
279
466
|
// Low relevance for non-color variables
|
|
280
|
-
if (lowerVarName.includes(
|
|
281
|
-
lowerVarName.includes(
|
|
282
|
-
lowerVarName.includes(
|
|
467
|
+
if (lowerVarName.includes("spacing") ||
|
|
468
|
+
lowerVarName.includes("margin") ||
|
|
469
|
+
lowerVarName.includes("padding") ||
|
|
470
|
+
lowerVarName.includes("size") ||
|
|
471
|
+
lowerVarName.includes("width") ||
|
|
472
|
+
lowerVarName.includes("height") ||
|
|
473
|
+
lowerVarName.includes("font") ||
|
|
474
|
+
lowerVarName.includes("weight") ||
|
|
475
|
+
lowerVarName.includes("radius")) {
|
|
283
476
|
return 0;
|
|
284
477
|
}
|
|
285
478
|
// Medium relevance: might be a color
|
|
286
479
|
return 5;
|
|
287
480
|
}
|
|
288
481
|
// Spacing-related properties
|
|
289
|
-
const spacingProperties = [
|
|
290
|
-
|
|
291
|
-
|
|
482
|
+
const spacingProperties = [
|
|
483
|
+
"margin",
|
|
484
|
+
"margin-top",
|
|
485
|
+
"margin-right",
|
|
486
|
+
"margin-bottom",
|
|
487
|
+
"margin-left",
|
|
488
|
+
"padding",
|
|
489
|
+
"padding-top",
|
|
490
|
+
"padding-right",
|
|
491
|
+
"padding-bottom",
|
|
492
|
+
"padding-left",
|
|
493
|
+
"gap",
|
|
494
|
+
"row-gap",
|
|
495
|
+
"column-gap",
|
|
496
|
+
];
|
|
292
497
|
if (spacingProperties.includes(propertyName)) {
|
|
293
|
-
if (lowerVarName.includes(
|
|
294
|
-
lowerVarName.includes(
|
|
498
|
+
if (lowerVarName.includes("spacing") ||
|
|
499
|
+
lowerVarName.includes("margin") ||
|
|
500
|
+
lowerVarName.includes("padding") ||
|
|
501
|
+
lowerVarName.includes("gap")) {
|
|
295
502
|
return 10;
|
|
296
503
|
}
|
|
297
|
-
if (lowerVarName.includes(
|
|
504
|
+
if (lowerVarName.includes("color") ||
|
|
505
|
+
lowerVarName.includes("bg") ||
|
|
506
|
+
lowerVarName.includes("background")) {
|
|
298
507
|
return 0;
|
|
299
508
|
}
|
|
300
509
|
return 5;
|
|
301
510
|
}
|
|
302
511
|
// Size-related properties
|
|
303
|
-
const sizeProperties = [
|
|
512
|
+
const sizeProperties = [
|
|
513
|
+
"width",
|
|
514
|
+
"height",
|
|
515
|
+
"max-width",
|
|
516
|
+
"max-height",
|
|
517
|
+
"min-width",
|
|
518
|
+
"min-height",
|
|
519
|
+
"font-size",
|
|
520
|
+
];
|
|
304
521
|
if (sizeProperties.includes(propertyName)) {
|
|
305
|
-
if (lowerVarName.includes(
|
|
522
|
+
if (lowerVarName.includes("width") ||
|
|
523
|
+
lowerVarName.includes("height") ||
|
|
524
|
+
lowerVarName.includes("size")) {
|
|
306
525
|
return 10;
|
|
307
526
|
}
|
|
308
|
-
if (lowerVarName.includes(
|
|
527
|
+
if (lowerVarName.includes("color") ||
|
|
528
|
+
lowerVarName.includes("bg") ||
|
|
529
|
+
lowerVarName.includes("background")) {
|
|
309
530
|
return 0;
|
|
310
531
|
}
|
|
311
532
|
return 5;
|
|
312
533
|
}
|
|
313
534
|
// Border-radius properties
|
|
314
|
-
if (propertyName.includes(
|
|
315
|
-
if (lowerVarName.includes(
|
|
535
|
+
if (propertyName.includes("radius")) {
|
|
536
|
+
if (lowerVarName.includes("radius") || lowerVarName.includes("rounded")) {
|
|
316
537
|
return 10;
|
|
317
538
|
}
|
|
318
|
-
if (lowerVarName.includes(
|
|
539
|
+
if (lowerVarName.includes("color") ||
|
|
540
|
+
lowerVarName.includes("bg") ||
|
|
541
|
+
lowerVarName.includes("background")) {
|
|
319
542
|
return 0;
|
|
320
543
|
}
|
|
321
544
|
return 5;
|
|
322
545
|
}
|
|
323
546
|
// Font-related properties
|
|
324
|
-
const fontProperties = [
|
|
547
|
+
const fontProperties = ["font-family", "font-weight", "font-style"];
|
|
325
548
|
if (fontProperties.includes(propertyName)) {
|
|
326
|
-
if (lowerVarName.includes(
|
|
549
|
+
if (lowerVarName.includes("font")) {
|
|
327
550
|
return 10;
|
|
328
551
|
}
|
|
329
|
-
if (lowerVarName.includes(
|
|
552
|
+
if (lowerVarName.includes("color") || lowerVarName.includes("spacing")) {
|
|
330
553
|
return 0;
|
|
331
554
|
}
|
|
332
555
|
return 5;
|
|
@@ -359,26 +582,32 @@ function isInCssValueContext(document, position) {
|
|
|
359
582
|
for (let i = beforeCursor.length - 1; i >= 0; i--) {
|
|
360
583
|
const char = beforeCursor[i];
|
|
361
584
|
// Track nesting (scanning backwards)
|
|
362
|
-
if (char ===
|
|
585
|
+
if (char === ")")
|
|
363
586
|
inParens++;
|
|
364
|
-
else if (char ===
|
|
587
|
+
else if (char === "(") {
|
|
365
588
|
inParens--;
|
|
366
589
|
if (inParens < 0)
|
|
367
590
|
break; // We've left the current context
|
|
368
591
|
}
|
|
369
|
-
else if (char ===
|
|
592
|
+
else if (char === "}")
|
|
370
593
|
inBraces++;
|
|
371
|
-
else if (char ===
|
|
594
|
+
else if (char === "{") {
|
|
372
595
|
inBraces--;
|
|
373
596
|
if (inBraces < 0) {
|
|
374
597
|
lastBracePos = i;
|
|
375
598
|
break; // Found the opening brace of our block
|
|
376
599
|
}
|
|
377
600
|
}
|
|
378
|
-
else if (char ===
|
|
601
|
+
else if (char === ":" &&
|
|
602
|
+
inParens === 0 &&
|
|
603
|
+
inBraces === 0 &&
|
|
604
|
+
lastColonPos === -1) {
|
|
379
605
|
lastColonPos = i;
|
|
380
606
|
}
|
|
381
|
-
else if (char ===
|
|
607
|
+
else if (char === ";" &&
|
|
608
|
+
inParens === 0 &&
|
|
609
|
+
inBraces === 0 &&
|
|
610
|
+
lastSemicolonPos === -1) {
|
|
382
611
|
lastSemicolonPos = i;
|
|
383
612
|
}
|
|
384
613
|
}
|
|
@@ -416,19 +645,19 @@ connection.onCompletion((textDocumentPosition) => {
|
|
|
416
645
|
const variables = cssVariableManager.getAllVariables();
|
|
417
646
|
// Deduplicate by name
|
|
418
647
|
const uniqueVars = new Map();
|
|
419
|
-
variables.forEach(v => {
|
|
648
|
+
variables.forEach((v) => {
|
|
420
649
|
if (!uniqueVars.has(v.name)) {
|
|
421
650
|
uniqueVars.set(v.name, v);
|
|
422
651
|
}
|
|
423
652
|
});
|
|
424
653
|
// Score and filter variables based on property context
|
|
425
|
-
const scoredVars = Array.from(uniqueVars.values()).map(v => ({
|
|
654
|
+
const scoredVars = Array.from(uniqueVars.values()).map((v) => ({
|
|
426
655
|
variable: v,
|
|
427
|
-
score: scoreVariableRelevance(v.name, propertyName)
|
|
656
|
+
score: scoreVariableRelevance(v.name, propertyName),
|
|
428
657
|
}));
|
|
429
658
|
// Filter out score 0 (not relevant) and sort by score (higher first)
|
|
430
659
|
const filteredAndSorted = scoredVars
|
|
431
|
-
.filter(sv => sv.score !== 0)
|
|
660
|
+
.filter((sv) => sv.score !== 0)
|
|
432
661
|
.sort((a, b) => {
|
|
433
662
|
// Sort by score (descending)
|
|
434
663
|
if (a.score !== b.score) {
|
|
@@ -437,11 +666,11 @@ connection.onCompletion((textDocumentPosition) => {
|
|
|
437
666
|
// Same score: alphabetical order
|
|
438
667
|
return a.variable.name.localeCompare(b.variable.name);
|
|
439
668
|
});
|
|
440
|
-
return filteredAndSorted.map(sv => ({
|
|
669
|
+
return filteredAndSorted.map((sv) => ({
|
|
441
670
|
label: sv.variable.name,
|
|
442
671
|
kind: node_1.CompletionItemKind.Variable,
|
|
443
672
|
detail: sv.variable.value,
|
|
444
|
-
documentation: `Defined in ${sv.variable.uri}
|
|
673
|
+
documentation: `Defined in ${formatUriForDisplay(sv.variable.uri)}`,
|
|
445
674
|
}));
|
|
446
675
|
});
|
|
447
676
|
// This handler resolves additional information for the item selected in
|
|
@@ -464,17 +693,19 @@ connection.onHover((params) => {
|
|
|
464
693
|
return undefined;
|
|
465
694
|
}
|
|
466
695
|
const word = left[0] + right[0];
|
|
467
|
-
if (word.startsWith(
|
|
696
|
+
if (word.startsWith("--")) {
|
|
468
697
|
const variables = cssVariableManager.getVariables(word);
|
|
469
698
|
if (variables.length === 0) {
|
|
470
699
|
return undefined;
|
|
471
700
|
}
|
|
472
701
|
// Get all usages to find context if hovering over a usage
|
|
473
702
|
const usages = cssVariableManager.getVariableUsages(word);
|
|
474
|
-
const hoverUsage = usages.find(u => document.positionAt(document.offsetAt(u.range.start)) ===
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
703
|
+
const hoverUsage = usages.find((u) => document.positionAt(document.offsetAt(u.range.start)) ===
|
|
704
|
+
params.position ||
|
|
705
|
+
(offset >= document.offsetAt(u.range.start) &&
|
|
706
|
+
offset <= document.offsetAt(u.range.end)));
|
|
707
|
+
const usageContext = hoverUsage?.usageContext || "";
|
|
708
|
+
const isInlineStyle = usageContext === "inline-style";
|
|
478
709
|
// Get DOM tree and node if available (for HTML documents)
|
|
479
710
|
const domTree = cssVariableManager.getDOMTree(document.uri);
|
|
480
711
|
const domNode = hoverUsage?.domNode;
|
|
@@ -515,12 +746,13 @@ connection.onHover((params) => {
|
|
|
515
746
|
}
|
|
516
747
|
else {
|
|
517
748
|
// Multiple definitions - show full cascade
|
|
518
|
-
hoverText +=
|
|
749
|
+
hoverText += "**Definitions** (CSS cascade order):\n\n";
|
|
519
750
|
sortedVars.forEach((v, index) => {
|
|
520
751
|
const spec = (0, specificity_1.calculateSpecificity)(v.selector);
|
|
521
752
|
// Use DOM-aware matching if available, otherwise fall back to simple matching
|
|
522
|
-
const isApplicable = usageContext
|
|
523
|
-
(0, specificity_1.matchesContext)(v.selector, usageContext, domTree, domNode)
|
|
753
|
+
const isApplicable = usageContext
|
|
754
|
+
? (0, specificity_1.matchesContext)(v.selector, usageContext, domTree, domNode)
|
|
755
|
+
: true;
|
|
524
756
|
const isWinner = index === 0 && (isApplicable || isInlineStyle);
|
|
525
757
|
let line = `${index + 1}. \`${v.value}\``;
|
|
526
758
|
if (v.important) {
|
|
@@ -532,39 +764,39 @@ connection.onHover((params) => {
|
|
|
532
764
|
}
|
|
533
765
|
if (isWinner && usageContext) {
|
|
534
766
|
if (v.important) {
|
|
535
|
-
line +=
|
|
767
|
+
line += " ✓ **Wins (!important)**";
|
|
536
768
|
}
|
|
537
769
|
else if (isInlineStyle) {
|
|
538
|
-
line +=
|
|
770
|
+
line += " ✓ **Would apply (inline style)**";
|
|
539
771
|
}
|
|
540
772
|
else if (domTree && domNode) {
|
|
541
|
-
line +=
|
|
773
|
+
line += " ✓ **Applies (DOM match)**";
|
|
542
774
|
}
|
|
543
775
|
else {
|
|
544
|
-
line +=
|
|
776
|
+
line += " ✓ **Applies here**";
|
|
545
777
|
}
|
|
546
778
|
}
|
|
547
779
|
else if (!isApplicable && usageContext && !isInlineStyle) {
|
|
548
|
-
line +=
|
|
780
|
+
line += " _(selector doesn't match)_";
|
|
549
781
|
}
|
|
550
782
|
else if (index > 0 && usageContext) {
|
|
551
783
|
// Explain why it doesn't win
|
|
552
784
|
const winner = sortedVars[0];
|
|
553
785
|
if (winner.important && !v.important) {
|
|
554
|
-
line +=
|
|
786
|
+
line += " _(overridden by !important)_";
|
|
555
787
|
}
|
|
556
788
|
else {
|
|
557
789
|
const winnerSpec = (0, specificity_1.calculateSpecificity)(winner.selector);
|
|
558
790
|
const cmp = (0, specificity_1.compareSpecificity)(winnerSpec, spec);
|
|
559
791
|
if (cmp > 0) {
|
|
560
|
-
line +=
|
|
792
|
+
line += " _(lower specificity)_";
|
|
561
793
|
}
|
|
562
794
|
else if (cmp === 0) {
|
|
563
|
-
line +=
|
|
795
|
+
line += " _(earlier in source)_";
|
|
564
796
|
}
|
|
565
797
|
}
|
|
566
798
|
}
|
|
567
|
-
hoverText += line +
|
|
799
|
+
hoverText += line + "\n";
|
|
568
800
|
});
|
|
569
801
|
if (usageContext) {
|
|
570
802
|
if (isInlineStyle) {
|
|
@@ -580,9 +812,9 @@ connection.onHover((params) => {
|
|
|
580
812
|
}
|
|
581
813
|
return {
|
|
582
814
|
contents: {
|
|
583
|
-
kind:
|
|
584
|
-
value: hoverText
|
|
585
|
-
}
|
|
815
|
+
kind: "markdown",
|
|
816
|
+
value: hoverText,
|
|
817
|
+
},
|
|
586
818
|
};
|
|
587
819
|
}
|
|
588
820
|
return undefined;
|
|
@@ -600,12 +832,12 @@ connection.onDefinition((params) => {
|
|
|
600
832
|
return undefined;
|
|
601
833
|
}
|
|
602
834
|
const word = left[0] + right[0];
|
|
603
|
-
if (word.startsWith(
|
|
835
|
+
if (word.startsWith("--")) {
|
|
604
836
|
const variables = cssVariableManager.getVariables(word);
|
|
605
837
|
if (variables.length > 0) {
|
|
606
838
|
return {
|
|
607
839
|
uri: variables[0].uri,
|
|
608
|
-
range: variables[0].range
|
|
840
|
+
range: variables[0].range,
|
|
609
841
|
};
|
|
610
842
|
}
|
|
611
843
|
}
|
|
@@ -625,9 +857,9 @@ connection.onReferences((params) => {
|
|
|
625
857
|
return [];
|
|
626
858
|
}
|
|
627
859
|
const word = left[0] + right[0];
|
|
628
|
-
if (word.startsWith(
|
|
860
|
+
if (word.startsWith("--")) {
|
|
629
861
|
const references = cssVariableManager.getReferences(word);
|
|
630
|
-
return references.map(ref => node_1.Location.create(ref.uri, ref.range));
|
|
862
|
+
return references.map((ref) => node_1.Location.create(ref.uri, ref.range));
|
|
631
863
|
}
|
|
632
864
|
return [];
|
|
633
865
|
});
|
|
@@ -645,7 +877,7 @@ connection.onRenameRequest((params) => {
|
|
|
645
877
|
return null;
|
|
646
878
|
}
|
|
647
879
|
const word = left[0] + right[0];
|
|
648
|
-
if (word.startsWith(
|
|
880
|
+
if (word.startsWith("--")) {
|
|
649
881
|
const references = cssVariableManager.getReferences(word);
|
|
650
882
|
const changes = {};
|
|
651
883
|
for (const ref of references) {
|
|
@@ -656,9 +888,9 @@ connection.onRenameRequest((params) => {
|
|
|
656
888
|
// For usages in var(), replace just the variable name part
|
|
657
889
|
const edit = {
|
|
658
890
|
range: ref.range,
|
|
659
|
-
newText:
|
|
891
|
+
newText: "value" in ref
|
|
660
892
|
? `${params.newName}: ${ref.value};` // Definition
|
|
661
|
-
: `var(${params.newName})
|
|
893
|
+
: `var(${params.newName})`, // Usage
|
|
662
894
|
};
|
|
663
895
|
changes[ref.uri].push(edit);
|
|
664
896
|
}
|
|
@@ -673,16 +905,16 @@ connection.onDocumentSymbol((params) => {
|
|
|
673
905
|
return [];
|
|
674
906
|
}
|
|
675
907
|
const variables = cssVariableManager.getDocumentDefinitions(document.uri);
|
|
676
|
-
return variables.map(v => node_1.DocumentSymbol.create(v.name, v.value, node_1.SymbolKind.Variable, v.range, v.range));
|
|
908
|
+
return variables.map((v) => node_1.DocumentSymbol.create(v.name, v.value, node_1.SymbolKind.Variable, v.range, v.range));
|
|
677
909
|
});
|
|
678
910
|
// Workspace symbols handler
|
|
679
911
|
connection.onWorkspaceSymbol((params) => {
|
|
680
912
|
const query = params.query.toLowerCase();
|
|
681
913
|
const allVariables = cssVariableManager.getAllDefinitions();
|
|
682
914
|
const filtered = query
|
|
683
|
-
? allVariables.filter(v => v.name.toLowerCase().includes(query))
|
|
915
|
+
? allVariables.filter((v) => v.name.toLowerCase().includes(query))
|
|
684
916
|
: allVariables;
|
|
685
|
-
return filtered.map(v => node_1.WorkspaceSymbol.create(v.name, node_1.SymbolKind.Variable, v.uri, v.range));
|
|
917
|
+
return filtered.map((v) => node_1.WorkspaceSymbol.create(v.name, node_1.SymbolKind.Variable, v.uri, v.range));
|
|
686
918
|
});
|
|
687
919
|
// Color Provider: Document Colors
|
|
688
920
|
connection.onDocumentColor((params) => {
|
|
@@ -707,24 +939,27 @@ connection.onDocumentColor((params) => {
|
|
|
707
939
|
if (def.valueRange) {
|
|
708
940
|
colors.push({
|
|
709
941
|
range: def.valueRange,
|
|
710
|
-
color: color
|
|
942
|
+
color: color,
|
|
711
943
|
});
|
|
712
944
|
}
|
|
713
945
|
else {
|
|
714
946
|
// Fallback: find the value within the declaration text
|
|
715
947
|
// This handles cases where valueRange wasn't captured (shouldn't happen normally)
|
|
716
948
|
const defText = text.substring(document.offsetAt(def.range.start), document.offsetAt(def.range.end));
|
|
717
|
-
const colonIndex = defText.indexOf(
|
|
949
|
+
const colonIndex = defText.indexOf(":");
|
|
718
950
|
if (colonIndex !== -1) {
|
|
719
951
|
const afterColon = defText.substring(colonIndex + 1);
|
|
720
952
|
const valueIndex = afterColon.indexOf(def.value.trim());
|
|
721
953
|
if (valueIndex !== -1) {
|
|
722
|
-
const absoluteValueStart = document.offsetAt(def.range.start) +
|
|
954
|
+
const absoluteValueStart = document.offsetAt(def.range.start) +
|
|
955
|
+
colonIndex +
|
|
956
|
+
1 +
|
|
957
|
+
valueIndex;
|
|
723
958
|
const start = document.positionAt(absoluteValueStart);
|
|
724
959
|
const end = document.positionAt(absoluteValueStart + def.value.trim().length);
|
|
725
960
|
colors.push({
|
|
726
961
|
range: { start, end },
|
|
727
|
-
color: color
|
|
962
|
+
color: color,
|
|
728
963
|
});
|
|
729
964
|
}
|
|
730
965
|
}
|
|
@@ -744,7 +979,7 @@ connection.onDocumentColor((params) => {
|
|
|
744
979
|
const end = document.positionAt(match.index + match[0].length);
|
|
745
980
|
colors.push({
|
|
746
981
|
range: { start, end },
|
|
747
|
-
color: color
|
|
982
|
+
color: color,
|
|
748
983
|
});
|
|
749
984
|
}
|
|
750
985
|
}
|