From 8ded18f74c1d769661a9c0e51f93b3ffd421278a Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 4 Sep 2020 12:14:14 +0200 Subject: [PATCH 1/3] (feat) global var completions Add option to set css files which will be checked for global vars which will appear in autocompletion #521 --- packages/language-server/src/ls-config.ts | 2 + .../src/plugins/css/CSSPlugin.ts | 32 ++++++++++- .../src/plugins/css/global-vars.ts | 54 +++++++++++++++++++ packages/svelte-vscode/README.md | 4 ++ packages/svelte-vscode/package.json | 6 +++ 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 packages/language-server/src/plugins/css/global-vars.ts diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index b08cdc9ec..95f0dd6ff 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -16,6 +16,7 @@ const defaultLSConfig: LSConfig = { }, css: { enable: true, + globals: '', diagnostics: { enable: true }, hover: { enable: true }, completions: { enable: true }, @@ -79,6 +80,7 @@ export interface LSTypescriptConfig { export interface LSCSSConfig { enable: boolean; + globals: string; diagnostics: { enable: boolean; }; diff --git a/packages/language-server/src/plugins/css/CSSPlugin.ts b/packages/language-server/src/plugins/css/CSSPlugin.ts index 4fb0fb22d..3bd1a0611 100644 --- a/packages/language-server/src/plugins/css/CSSPlugin.ts +++ b/packages/language-server/src/plugins/css/CSSPlugin.ts @@ -11,6 +11,8 @@ import { Position, Range, SymbolInformation, + CompletionItem, + CompletionItemKind, } from 'vscode-languageserver'; import { Document, @@ -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 @@ -45,9 +48,11 @@ export class CSSPlugin private configManager: LSConfigManager; private cssDocuments = new WeakMap(); private triggerCharacters = ['.', ':', '-', '/']; + private globalVars = new GlobalVars(); constructor(docManager: DocumentManager, configManager: LSConfigManager) { this.configManager = configManager; + this.globalVars.watchFiles(this.configManager.get('css.globals')); docManager.on('documentChange', (document) => this.cssDocuments.set(document, new CSSDocument(document)), @@ -149,14 +154,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.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 []; diff --git a/packages/language-server/src/plugins/css/global-vars.ts b/packages/language-server/src/plugins/css/global-vars.ts new file mode 100644 index 000000000..c84c7fd6b --- /dev/null +++ b/packages/language-server/src/plugins/css/global-vars.ts @@ -0,0 +1,54 @@ +import { watch, FSWatcher } from 'chokidar'; +import { readFile } from 'fs'; +import { isNotNullOrUndefined, flatten } from '../../utils'; + +const varRegex = /^\s*(--\w+.*?):\s*?([^;]*)/; + +export interface GlobalVar { + name: string; + value: string; +} + +export class GlobalVars { + private fsWatcher?: FSWatcher; + private globalVars = new Map(); + + watchFiles(filesToWatch: string): void { + if (!filesToWatch) { + return; + } + + if (this.fsWatcher) { + this.fsWatcher.close(); + } + 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(file: string) { + // Inside a small timeout because it seems chikidar is "too fast" + // and reading the file will then return empty content + setTimeout(() => { + readFile(file, 'utf-8', (error, contents) => { + if (error) { + return; + } + + const globalVarsForFile = contents + .split('\n') + .map((line) => line.match(varRegex)) + .filter(isNotNullOrUndefined) + .map((line) => ({ name: line[1], value: line[2] })); + this.globalVars.set(file, globalVarsForFile); + }); + }, 1000); + } + + getGlobalVars(): GlobalVar[] { + return flatten([...this.globalVars.values()]); + } +} diff --git a/packages/svelte-vscode/README.md b/packages/svelte-vscode/README.md index bf83031e9..55d11f37f 100644 --- a/packages/svelte-vscode/README.md +++ b/packages/svelte-vscode/README.md @@ -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. You need to reload for the changes to take effect. + ##### `svelte.plugin.css.diagnostics` Enable diagnostic messages for CSS. _Default_: `true` diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index 7739db56e..85cfe3ee4 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -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. You need to reload for the changes to take effect." + }, "svelte.plugin.css.diagnostics.enable": { "type": "boolean", "default": true, From 6840cd0d170670a986acbf07a3d571cde441b966 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Sun, 6 Sep 2020 14:55:43 +0200 Subject: [PATCH 2/3] better details with filename and var name --- packages/language-server/src/plugins/css/CSSPlugin.ts | 2 +- packages/language-server/src/plugins/css/global-vars.ts | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/language-server/src/plugins/css/CSSPlugin.ts b/packages/language-server/src/plugins/css/CSSPlugin.ts index 3bd1a0611..1e549f2ef 100644 --- a/packages/language-server/src/plugins/css/CSSPlugin.ts +++ b/packages/language-server/src/plugins/css/CSSPlugin.ts @@ -175,7 +175,7 @@ export class CSSPlugin .getGlobalVars() .map((globalVar) => ({ label: globalVar.name, - detail: globalVar.value, + detail: `${globalVar.filename}\n\n${globalVar.name}: ${globalVar.value}`, textEdit: value.textEdit && { ...value.textEdit, newText: `var(${globalVar.name})`, diff --git a/packages/language-server/src/plugins/css/global-vars.ts b/packages/language-server/src/plugins/css/global-vars.ts index c84c7fd6b..57cef357d 100644 --- a/packages/language-server/src/plugins/css/global-vars.ts +++ b/packages/language-server/src/plugins/css/global-vars.ts @@ -6,6 +6,7 @@ const varRegex = /^\s*(--\w+.*?):\s*?([^;]*)/; export interface GlobalVar { name: string; + filename: string; value: string; } @@ -29,11 +30,11 @@ export class GlobalVars { .addListener('unlink', (file) => this.globalVars.delete(file)); } - private updateForFile(file: string) { + 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(file, 'utf-8', (error, contents) => { + readFile(filename, 'utf-8', (error, contents) => { if (error) { return; } @@ -42,8 +43,8 @@ export class GlobalVars { .split('\n') .map((line) => line.match(varRegex)) .filter(isNotNullOrUndefined) - .map((line) => ({ name: line[1], value: line[2] })); - this.globalVars.set(file, globalVarsForFile); + .map((line) => ({ filename, name: line[1], value: line[2] })); + this.globalVars.set(filename, globalVarsForFile); }); }, 1000); } From 994dbddc6bcbb28fd61f082d9dfd493faa907fa2 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Sun, 6 Sep 2020 15:13:53 +0200 Subject: [PATCH 3/3] add config onchange listener to update global vars settings without the need to reload --- packages/language-server/src/ls-config.ts | 10 ++++++++++ packages/language-server/src/plugins/css/CSSPlugin.ts | 4 ++++ .../language-server/src/plugins/css/global-vars.ts | 2 ++ packages/svelte-vscode/README.md | 2 +- packages/svelte-vscode/package.json | 2 +- 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index 95f0dd6ff..d54f53977 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -147,6 +147,7 @@ type DeepPartial = T extends CompilerWarningsSettings export class LSConfigManager { private config: LSConfig = defaultLSConfig; + private listeners: ((config: LSConfigManager) => void)[] = []; /** * Updates config. @@ -161,6 +162,8 @@ export class LSConfigManager { if (config.svelte?.compilerWarnings) { this.config.svelte.compilerWarnings = config.svelte.compilerWarnings; } + + this.listeners.forEach((listener) => listener(this)); } /** @@ -185,6 +188,13 @@ export class LSConfigManager { getConfig(): Readonly { 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(); diff --git a/packages/language-server/src/plugins/css/CSSPlugin.ts b/packages/language-server/src/plugins/css/CSSPlugin.ts index 1e549f2ef..0fefed511 100644 --- a/packages/language-server/src/plugins/css/CSSPlugin.ts +++ b/packages/language-server/src/plugins/css/CSSPlugin.ts @@ -52,7 +52,11 @@ export class CSSPlugin 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)), diff --git a/packages/language-server/src/plugins/css/global-vars.ts b/packages/language-server/src/plugins/css/global-vars.ts index 57cef357d..2495b13d2 100644 --- a/packages/language-server/src/plugins/css/global-vars.ts +++ b/packages/language-server/src/plugins/css/global-vars.ts @@ -21,7 +21,9 @@ export class GlobalVars { if (this.fsWatcher) { this.fsWatcher.close(); + this.globalVars.clear(); } + this.fsWatcher = watch(filesToWatch.split(',')) .addListener('add', (file) => this.updateForFile(file)) .addListener('change', (file) => { diff --git a/packages/svelte-vscode/README.md b/packages/svelte-vscode/README.md index 55d11f37f..4b620c5e3 100644 --- a/packages/svelte-vscode/README.md +++ b/packages/svelte-vscode/README.md @@ -100,7 +100,7 @@ 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. You need to reload for the changes to take effect. +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` diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index 85cfe3ee4..52d3102c1 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -114,7 +114,7 @@ "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. You need to reload for the changes to take effect." + "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",