11import fetch from "node-fetch" ;
2+ import { FSWatcher , watch } from "chokidar" ;
23import { parse , walk } from "css-tree" ;
34import {
45 CancellationToken ,
@@ -20,6 +21,7 @@ import {
2021export 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 = / ^ h t t p s ? : \/ \/ / 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 = / < s t y l e [ ^ > ] * > ( [ ^ < ] + ) < \/ s t y l e > / 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+
170202export 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