Skip to content

Commit 261681f

Browse files
committed
Added workspace completion
1 parent 2202bf0 commit 261681f

File tree

3 files changed

+81
-9
lines changed

3 files changed

+81
-9
lines changed

package-lock.json

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"@types/vscode": "^1.51.0",
8181
"@typescript-eslint/eslint-plugin": "^4.1.1",
8282
"@typescript-eslint/parser": "^4.1.1",
83+
"@types/chokidar": "^2.1.3",
8384
"css-tree": "^1.1.2",
8485
"eslint": "^7.9.0",
8586
"glob": "^7.1.6",
@@ -90,6 +91,7 @@
9091
"vsce": "^1.81.1",
9192
"vscode-test": "^1.4.0",
9293
"webpack-cli": "^4.2.0",
93-
"webpack": "^5.9.0"
94+
"webpack": "^5.9.0",
95+
"chokidar": "^3.4.3"
9496
}
95-
}
97+
}

src/extension.ts

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fetch from "node-fetch";
2+
import { FSWatcher, watch } from "chokidar";
23
import { parse, walk } from "css-tree";
34
import {
45
CancellationToken,
@@ -20,6 +21,7 @@ import {
2021
export class ClassCompletionItemProvider implements CompletionItemProvider {
2122

2223
readonly start = new Position(0, 0);
24+
readonly files = new Set<string>();
2325
readonly cache = new Map<string, Map<string, CompletionItem>>();
2426
readonly none = "__!NONE!__";
2527
readonly isRemote = /^https?:\/\//i;
@@ -58,6 +60,18 @@ export class ClassCompletionItemProvider implements CompletionItemProvider {
5860
});
5961
}
6062

63+
fetchLocal(key: string): Thenable<string> {
64+
return new Promise(resolve => {
65+
const items = new Map<string, CompletionItem>();
66+
67+
workspace.fs.readFile(Uri.file(key)).then(content => {
68+
this.parseTextToItems(content.toString(), items);
69+
this.cache.set(key, items);
70+
resolve(key);
71+
}, () => resolve(this.none));
72+
});
73+
}
74+
6175
fetchStyleSheet(key: string): Thenable<string> {
6276
return new Promise(resolve => {
6377
if (key === this.none) {
@@ -67,6 +81,8 @@ export class ClassCompletionItemProvider implements CompletionItemProvider {
6781
resolve(key);
6882
} else if (this.isRemote.test(key)) {
6983
this.fetchRemote(key).then(key => resolve(key));
84+
} else if (key.startsWith("/")) {
85+
this.fetchLocal(key).then(key => resolve(key));
7086
} else {
7187
resolve(this.none);
7288
}
@@ -117,6 +133,19 @@ export class ClassCompletionItemProvider implements CompletionItemProvider {
117133
});
118134
}
119135

136+
findLocalStyles(): Thenable<Set<string>> {
137+
return new Promise(resolve => {
138+
const promises = [];
139+
const keys = new Set<string>();
140+
141+
for (const path of this.files) {
142+
promises.push(this.fetchStyleSheet(path).then(k => keys.add(k)));
143+
}
144+
145+
Promise.all(promises).then(() => resolve(keys));
146+
});
147+
}
148+
120149
findDocumentStyles(text: string): Map<string, CompletionItem> {
121150
const items = new Map<string, CompletionItem>();
122151
const findStyles = /<style[^>]*>([^<]+)<\/style>/gi;
@@ -156,9 +185,10 @@ export class ClassCompletionItemProvider implements CompletionItemProvider {
156185
if (canComplete) {
157186
const items = this.findDocumentStyles(text);
158187

159-
this.findRemoteStyles(document.uri).then(styles =>
160-
this.findDocumentLinks(text).then(links =>
161-
resolve(this.buildItems(items, styles, links))));
188+
this.findLocalStyles().then(locals =>
189+
this.findRemoteStyles(document.uri).then(styles =>
190+
this.findDocumentLinks(text).then(links =>
191+
resolve(this.buildItems(items, styles, links, locals)))));
162192
} else {
163193
reject();
164194
}
@@ -167,14 +197,45 @@ export class ClassCompletionItemProvider implements CompletionItemProvider {
167197
}
168198
}
169199

200+
export let watcher: FSWatcher;
201+
170202
export function activate(context: ExtensionContext) {
203+
const provider = new ClassCompletionItemProvider();
204+
171205
const config = workspace.getConfiguration("css");
172206
const enabledLanguages = config.get<string[]>("enabledLanguages", ["html"]);
173207
const triggerCharacters = config.get<string[]>("triggerCharacters", ["\"", "'"]);
174208

175209
context.subscriptions.push(languages
176-
.registerCompletionItemProvider(enabledLanguages,
177-
new ClassCompletionItemProvider(), ...triggerCharacters));
210+
.registerCompletionItemProvider(enabledLanguages, provider, ...triggerCharacters));
211+
212+
const glob = "**/*.css";
213+
const folders = workspace.workspaceFolders?.map(folder => `${folder.uri.fsPath}/${glob}`);
214+
215+
if (folders) {
216+
watcher = watch(folders, { ignored: ["**/node_modules/**", "**/test*/**"] })
217+
.on("add", key => provider.files.add(key))
218+
.on("unlink", key => provider.files.delete(key))
219+
.on("change", key => provider.cache.delete(key));
220+
}
221+
222+
workspace.onDidChangeWorkspaceFolders(e => {
223+
if (watcher) {
224+
e.removed.forEach(folder => {
225+
watcher.unwatch(`${folder.uri.fsPath}/${glob}`);
226+
227+
for (const key of provider.files) {
228+
if (key.startsWith(folder.uri.fsPath)) {
229+
provider.files.delete(key);
230+
}
231+
}
232+
});
233+
234+
e.added.forEach(folder => watcher.add(`${folder.uri.fsPath}/${glob}`));
235+
}
236+
});
178237
}
179238

180-
export function deactivate() { }
239+
export function deactivate() {
240+
return watcher?.close();
241+
}

0 commit comments

Comments
 (0)