Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/language-server/src/ls-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const defaultLSConfig: LSConfig = {
},
css: {
enable: true,
globals: '',
diagnostics: { enable: true },
hover: { enable: true },
completions: { enable: true },
Expand Down Expand Up @@ -79,6 +80,7 @@ export interface LSTypescriptConfig {

export interface LSCSSConfig {
enable: boolean;
globals: string;
diagnostics: {
enable: boolean;
};
Expand Down Expand Up @@ -145,6 +147,7 @@ type DeepPartial<T> = T extends CompilerWarningsSettings

export class LSConfigManager {
private config: LSConfig = defaultLSConfig;
private listeners: ((config: LSConfigManager) => void)[] = [];

/**
* Updates config.
Expand All @@ -159,6 +162,8 @@ export class LSConfigManager {
if (config.svelte?.compilerWarnings) {
this.config.svelte.compilerWarnings = config.svelte.compilerWarnings;
}

this.listeners.forEach((listener) => listener(this));
}

/**
Expand All @@ -183,6 +188,13 @@ export class LSConfigManager {
getConfig(): Readonly<LSConfig> {
return this.config;
}

/**
* Register a listener which is invoked when the config changed.
*/
onChange(callback: (config: LSConfigManager) => void): void {
this.listeners.push(callback);
}
}

export const lsConfig = new LSConfigManager();
36 changes: 34 additions & 2 deletions packages/language-server/src/plugins/css/CSSPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
Position,
Range,
SymbolInformation,
CompletionItem,
CompletionItemKind,
} from 'vscode-languageserver';
import {
Document,
Expand All @@ -33,6 +35,7 @@ import {
} from '../interfaces';
import { CSSDocument } from './CSSDocument';
import { getLanguage, getLanguageService } from './service';
import { GlobalVars } from './global-vars';

export class CSSPlugin
implements
Expand All @@ -45,10 +48,16 @@ export class CSSPlugin
private configManager: LSConfigManager;
private cssDocuments = new WeakMap<Document, CSSDocument>();
private triggerCharacters = ['.', ':', '-', '/'];
private globalVars = new GlobalVars();

constructor(docManager: DocumentManager, configManager: LSConfigManager) {
this.configManager = configManager;

this.globalVars.watchFiles(this.configManager.get('css.globals'));
this.configManager.onChange((config) =>
this.globalVars.watchFiles(config.get('css.globals')),
);

docManager.on('documentChange', (document) =>
this.cssDocuments.set(document, new CSSDocument(document)),
);
Expand Down Expand Up @@ -149,14 +158,37 @@ export class CSSPlugin
cssDocument.stylesheet,
);
return CompletionList.create(
[...(results ? results.items : []), ...emmetResults.items].map((completionItem) =>
mapCompletionItemToOriginal(cssDocument, completionItem),
this.appendGlobalVars(
[...(results ? results.items : []), ...emmetResults.items].map((completionItem) =>
mapCompletionItemToOriginal(cssDocument, completionItem),
),
),
// Emmet completions change on every keystroke, so they are never complete
emmetResults.items.length > 0,
);
}

private appendGlobalVars(items: CompletionItem[]): CompletionItem[] {
// Finding one value with that item kind means we are in a value completion scenario
const value = items.find((item) => item.kind === CompletionItemKind.Value);
if (!value) {
return items;
}

const additionalItems: CompletionItem[] = this.globalVars
.getGlobalVars()
.map((globalVar) => ({
label: globalVar.name,
detail: `${globalVar.filename}\n\n${globalVar.name}: ${globalVar.value}`,
textEdit: value.textEdit && {
...value.textEdit,
newText: `var(${globalVar.name})`,
},
kind: CompletionItemKind.Value,
}));
return [...items, ...additionalItems];
}

getDocumentColors(document: Document): ColorInformation[] {
if (!this.featureEnabled('documentColors')) {
return [];
Expand Down
57 changes: 57 additions & 0 deletions packages/language-server/src/plugins/css/global-vars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { watch, FSWatcher } from 'chokidar';
import { readFile } from 'fs';
import { isNotNullOrUndefined, flatten } from '../../utils';

const varRegex = /^\s*(--\w+.*?):\s*?([^;]*)/;

export interface GlobalVar {
name: string;
filename: string;
value: string;
}

export class GlobalVars {
private fsWatcher?: FSWatcher;
private globalVars = new Map<string, GlobalVar[]>();

watchFiles(filesToWatch: string): void {
if (!filesToWatch) {
return;
}

if (this.fsWatcher) {
this.fsWatcher.close();
this.globalVars.clear();
}

this.fsWatcher = watch(filesToWatch.split(','))
.addListener('add', (file) => this.updateForFile(file))
.addListener('change', (file) => {
this.updateForFile(file);
})
.addListener('unlink', (file) => this.globalVars.delete(file));
}

private updateForFile(filename: string) {
// Inside a small timeout because it seems chikidar is "too fast"
// and reading the file will then return empty content
setTimeout(() => {
readFile(filename, 'utf-8', (error, contents) => {
if (error) {
return;
}

const globalVarsForFile = contents
.split('\n')
.map((line) => line.match(varRegex))
.filter(isNotNullOrUndefined)
.map((line) => ({ filename, name: line[1], value: line[2] }));
this.globalVars.set(filename, globalVarsForFile);
});
}, 1000);
}

getGlobalVars(): GlobalVar[] {
return flatten([...this.globalVars.values()]);
}
}
4 changes: 4 additions & 0 deletions packages/svelte-vscode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ Enable code actions for TypeScript. _Default_: `true`

Enable the CSS plugin. _Default_: `true`

##### `svelte.plugin.css.globals`

Which css files should be checked for global variables (`--global-var: value;`). These variables are added to the css completions. String of comma-separated file paths or globs relative to workspace root.

##### `svelte.plugin.css.diagnostics`

Enable diagnostic messages for CSS. _Default_: `true`
Expand Down
6 changes: 6 additions & 0 deletions packages/svelte-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@
"title": "CSS",
"description": "Enable the CSS plugin"
},
"svelte.plugin.css.globals": {
"type": "string",
"default": "",
"title": "CSS: Global Files",
"description": "Which css files should be checked for global variables (`--global-var: value;`). These variables are added to the css completions. String of comma-separated file paths or globs relative to workspace root."
},
"svelte.plugin.css.diagnostics.enable": {
"type": "boolean",
"default": true,
Expand Down