@@ -12,14 +12,30 @@ import Fetcher from "./fetcher";
1212import Notifier from "./notifier" ;
1313import ParseEngineGateway from "./parse-engine-gateway" ;
1414
15- const notifier : Notifier = new Notifier ( "html-css-class-completion.cache" ) ;
15+ enum Command {
16+ Cache = "html-css-class-completion.cache" ,
17+ }
18+
19+ enum Configuration {
20+ IncludeGlobPattern = "html-css-class-completion.includeGlobPattern" ,
21+ ExcludeGlobPattern = "html-css-class-completion.excludeGlobPattern" ,
22+ EnableEmmetSupport = "html-css-class-completion.enableEmmetSupport" ,
23+ HTMLLanguages = "html-css-class-completion.HTMLLanguages" ,
24+ CSSLanguages = "html-css-class-completion.CSSLanguages" ,
25+ JavaScriptLanguages = "html-css-class-completion.JavaScriptLanguages" ,
26+ }
27+
28+ const notifier : Notifier = new Notifier ( Command . Cache ) ;
1629let uniqueDefinitions : CssClassDefinition [ ] = [ ] ;
1730
1831const completionTriggerChars = [ '"' , "'" , " " , "." ] ;
1932
2033let caching = false ;
2134
22- const emmetDisposables : Array < { dispose ( ) : any } > = [ ] ;
35+ const htmlDisposables : Disposable [ ] = [ ] ;
36+ const cssDisposables : Disposable [ ] = [ ] ;
37+ const javaScriptDisposables : Disposable [ ] = [ ] ;
38+ const emmetDisposables : Disposable [ ] = [ ] ;
2339
2440async function cache ( ) : Promise < void > {
2541 try {
@@ -77,76 +93,125 @@ async function cache(): Promise<void> {
7793 }
7894}
7995
80- function provideCompletionItemsGenerator ( languageSelector : string , classMatchRegex : RegExp ,
81- classPrefix : string = "" , splitChar : string = " " ) {
82- return languages . registerCompletionItemProvider ( languageSelector , {
83- provideCompletionItems ( document : TextDocument , position : Position ) : CompletionItem [ ] {
84- const start : Position = new Position ( position . line , 0 ) ;
85- const range : Range = new Range ( start , position ) ;
86- const text : string = document . getText ( range ) ;
87-
88- // Check if the cursor is on a class attribute and retrieve all the css rules in this class attribute
89- const rawClasses : RegExpMatchArray = text . match ( classMatchRegex ) ;
90- if ( ! rawClasses || rawClasses . length === 1 ) {
91- return [ ] ;
92- }
96+ const registerCompletionProvider = (
97+ languageSelector : string ,
98+ classMatchRegex : RegExp ,
99+ classPrefix = "" ,
100+ splitChar = " "
101+ ) => languages . registerCompletionItemProvider ( languageSelector , {
102+ provideCompletionItems ( document : TextDocument , position : Position ) : CompletionItem [ ] {
103+ const start : Position = new Position ( position . line , 0 ) ;
104+ const range : Range = new Range ( start , position ) ;
105+ const text : string = document . getText ( range ) ;
106+
107+ // Check if the cursor is on a class attribute and retrieve all the css rules in this class attribute
108+ const rawClasses : RegExpMatchArray = text . match ( classMatchRegex ) ;
109+ if ( ! rawClasses || rawClasses . length === 1 ) {
110+ return [ ] ;
111+ }
93112
94- // Will store the classes found on the class attribute
95- const classesOnAttribute = rawClasses [ 1 ] . split ( splitChar ) ;
113+ // Will store the classes found on the class attribute
114+ const classesOnAttribute = rawClasses [ 1 ] . split ( splitChar ) ;
96115
97- // Creates a collection of CompletionItem based on the classes already cached
98- const completionItems = uniqueDefinitions . map ( ( definition ) => {
99- const completionItem = new CompletionItem ( definition . className , CompletionItemKind . Variable ) ;
100- const completionClassName = `${ classPrefix } ${ definition . className } ` ;
116+ // Creates a collection of CompletionItem based on the classes already cached
117+ const completionItems = uniqueDefinitions . map ( ( definition ) => {
118+ const completionItem = new CompletionItem ( definition . className , CompletionItemKind . Variable ) ;
119+ const completionClassName = `${ classPrefix } ${ definition . className } ` ;
101120
102- completionItem . filterText = completionClassName ;
103- completionItem . insertText = completionClassName ;
121+ completionItem . filterText = completionClassName ;
122+ completionItem . insertText = completionClassName ;
104123
105- return completionItem ;
106- } ) ;
124+ return completionItem ;
125+ } ) ;
107126
108- // Removes from the collection the classes already specified on the class attribute
109- for ( const classOnAttribute of classesOnAttribute ) {
110- for ( let j = 0 ; j < completionItems . length ; j ++ ) {
111- if ( completionItems [ j ] . insertText === classOnAttribute ) {
112- completionItems . splice ( j , 1 ) ;
113- }
127+ // Removes from the collection the classes already specified on the class attribute
128+ for ( const classOnAttribute of classesOnAttribute ) {
129+ for ( let j = 0 ; j < completionItems . length ; j ++ ) {
130+ if ( completionItems [ j ] . insertText === classOnAttribute ) {
131+ completionItems . splice ( j , 1 ) ;
114132 }
115133 }
134+ }
116135
117- return completionItems ;
118- } ,
119- } , ...completionTriggerChars ) ;
120- }
121-
122- function enableEmmetSupport ( disposables : Disposable [ ] ) {
123- const emmetRegex = / (? = \. ) ( [ \w -\. ] * $ ) / ;
124- const languageModes = [ "html" , "django-html" , "razor" , "php" , "blade" , "vue" , "twig" , "markdown" , "erb" ,
125- "handlebars" , "ejs" , "typescriptreact" , "javascript" , "javascriptreact" ] ;
126- languageModes . forEach ( ( language ) => {
127- emmetDisposables . push ( provideCompletionItemsGenerator ( language , emmetRegex , "" , "." ) ) ;
128- } ) ;
136+ return completionItems ;
137+ } ,
138+ } , ...completionTriggerChars ) ;
139+
140+ const registerHTMLProviders = ( disposables : Disposable [ ] ) =>
141+ workspace . getConfiguration ( )
142+ . get < string [ ] > ( Configuration . HTMLLanguages )
143+ . forEach ( ( extension ) => {
144+ disposables . push ( registerCompletionProvider ( extension , / c l a s s = [ " | ' ] ( [ \w - ] * $ ) / ) ) ;
145+ } ) ;
146+
147+ const registerCSSProviders = ( disposables : Disposable [ ] ) =>
148+ workspace . getConfiguration ( )
149+ . get < string [ ] > ( Configuration . CSSLanguages )
150+ . forEach ( ( extension ) => {
151+ // The @apply rule was a CSS proposal which has since been abandoned,
152+ // check the proposal for more info: http://tabatkins.github.io/specs/css-apply-rule/
153+ // Its support should probably be removed
154+ disposables . push ( registerCompletionProvider ( extension , / @ a p p l y ( [ . \w - ] * $ ) / , "." ) ) ;
155+ } ) ;
156+
157+ const registerJavaScriptProviders = ( disposables : Disposable [ ] ) =>
158+ workspace . getConfiguration ( )
159+ . get < string [ ] > ( Configuration . JavaScriptLanguages )
160+ . forEach ( ( extension ) => {
161+ disposables . push ( registerCompletionProvider ( extension , / c l a s s N a m e = [ " | ' ] ( [ \w - ] * $ ) / ) ) ;
162+ disposables . push ( registerCompletionProvider ( extension , / c l a s s = [ " | ' ] ( [ \w - ] * $ ) / ) ) ;
163+ } ) ;
164+
165+ function registerEmmetProviders ( disposables : Disposable [ ] ) {
166+ const emmetRegex = / (? = \. ) ( [ \w - . ] * $ ) / ;
167+
168+ const registerProviders = ( modes : string [ ] ) => {
169+ modes . forEach ( ( language ) => {
170+ disposables . push ( registerCompletionProvider ( language , emmetRegex , "" , "." ) ) ;
171+ } ) ;
172+ } ;
173+
174+ registerProviders (
175+ workspace . getConfiguration ( ) . get < string [ ] > ( Configuration . HTMLLanguages )
176+ ) ;
177+ registerProviders (
178+ workspace . getConfiguration ( ) . get < string [ ] > ( Configuration . JavaScriptLanguages )
179+ ) ;
129180}
130181
131- function disableEmmetSupport ( disposables : Disposable [ ] ) {
132- for ( const emmetDisposable of disposables ) {
133- emmetDisposable . dispose ( ) ;
134- }
182+ function unregisterProviders ( disposables : Disposable [ ] ) {
183+ disposables . forEach ( disposable => disposable . dispose ( ) ) ;
184+ disposables . length = 0 ;
135185}
136186
137187export async function activate ( context : ExtensionContext ) : Promise < void > {
138188 const disposables : Disposable [ ] = [ ] ;
139189 workspace . onDidChangeConfiguration ( async ( e ) => {
140190 try {
141- if ( e . affectsConfiguration ( "html-css-class-completion.includeGlobPattern" ) ||
142- e . affectsConfiguration ( "html-css-class-completion.excludeGlobPattern" ) ) {
191+ if ( e . affectsConfiguration ( Configuration . IncludeGlobPattern ) ||
192+ e . affectsConfiguration ( Configuration . ExcludeGlobPattern ) ) {
143193 await cache ( ) ;
144194 }
145195
146- if ( e . affectsConfiguration ( "html-css-class-completion.enableEmmetSupport" ) ) {
196+ if ( e . affectsConfiguration ( Configuration . EnableEmmetSupport ) ) {
147197 const isEnabled = workspace . getConfiguration ( )
148- . get < boolean > ( "html-css-class-completion.enableEmmetSupport" ) ;
149- isEnabled ? enableEmmetSupport ( emmetDisposables ) : disableEmmetSupport ( emmetDisposables ) ;
198+ . get < boolean > ( Configuration . EnableEmmetSupport ) ;
199+ isEnabled ? registerEmmetProviders ( emmetDisposables ) : unregisterProviders ( emmetDisposables ) ;
200+ }
201+
202+ if ( e . affectsConfiguration ( Configuration . HTMLLanguages ) ) {
203+ unregisterProviders ( htmlDisposables ) ;
204+ registerHTMLProviders ( htmlDisposables ) ;
205+ }
206+
207+ if ( e . affectsConfiguration ( Configuration . CSSLanguages ) ) {
208+ unregisterProviders ( cssDisposables ) ;
209+ registerCSSProviders ( cssDisposables ) ;
210+ }
211+
212+ if ( e . affectsConfiguration ( Configuration . JavaScriptLanguages ) ) {
213+ unregisterProviders ( javaScriptDisposables ) ;
214+ registerJavaScriptProviders ( javaScriptDisposables ) ;
150215 }
151216 } catch ( err ) {
152217 const newErr = new VError ( err , "Failed to automatically reload the extension after the configuration change" ) ;
@@ -156,7 +221,7 @@ export async function activate(context: ExtensionContext): Promise<void> {
156221 } , null , disposables ) ;
157222 context . subscriptions . push ( ...disposables ) ;
158223
159- context . subscriptions . push ( commands . registerCommand ( "html-css-class-completion.cache" , async ( ) => {
224+ context . subscriptions . push ( commands . registerCommand ( Command . Cache , async ( ) => {
160225 if ( caching ) {
161226 return ;
162227 }
@@ -173,27 +238,13 @@ export async function activate(context: ExtensionContext): Promise<void> {
173238 }
174239 } ) ) ;
175240
176- // Enable Emmet Completion on startup if param is set to true
177- if ( workspace . getConfiguration ( ) . get < boolean > ( "html-css-class-completion.enableEmmetSupport" ) ) {
178- enableEmmetSupport ( emmetDisposables ) ;
241+ if ( workspace . getConfiguration ( ) . get < boolean > ( Configuration . EnableEmmetSupport ) ) {
242+ registerEmmetProviders ( emmetDisposables ) ;
179243 }
180244
181- // Javascript based extensions
182- workspace . getConfiguration ( ) . get < string [ ] > ( "html-css-class-completion.enabledJavascriptLanguages" ) . forEach ( ( extension ) => {
183- context . subscriptions . push ( provideCompletionItemsGenerator ( extension , / c l a s s N a m e = [ " | ' ] ( [ \w - ] * $ ) / ) ) ;
184- context . subscriptions . push ( provideCompletionItemsGenerator ( extension , / c l a s s = [ " | ' ] ( [ \w - ] * $ ) / ) ) ;
185- } ) ;
186-
187- // HTML based extensions
188- workspace . getConfiguration ( ) . get < string [ ] > ( "html-css-class-completion.enabledHTMLLanguages" ) . forEach ( ( extension ) => {
189- context . subscriptions . push ( provideCompletionItemsGenerator ( extension , / c l a s s = [ " | ' ] ( [ \w - ] * $ ) / ) ) ;
190- } ) ;
191-
192- // CSS based extensions
193- workspace . getConfiguration ( ) . get < string [ ] > ( "html-css-class-completion.enabledCSSLanguages" ) . forEach ( ( extension ) => {
194- // Support for Tailwind CSS
195- context . subscriptions . push ( provideCompletionItemsGenerator ( extension , / @ a p p l y ( [ \. \w - ] * $ ) / , "." ) ) ;
196- } ) ;
245+ registerHTMLProviders ( htmlDisposables ) ;
246+ registerCSSProviders ( cssDisposables ) ;
247+ registerJavaScriptProviders ( javaScriptDisposables ) ;
197248
198249 caching = true ;
199250 try {
@@ -208,5 +259,8 @@ export async function activate(context: ExtensionContext): Promise<void> {
208259}
209260
210261export function deactivate ( ) : void {
211- emmetDisposables . forEach ( ( disposable ) => disposable . dispose ( ) ) ;
262+ unregisterProviders ( htmlDisposables ) ;
263+ unregisterProviders ( cssDisposables ) ;
264+ unregisterProviders ( javaScriptDisposables ) ;
265+ unregisterProviders ( emmetDisposables ) ;
212266}
0 commit comments