@@ -12,14 +12,30 @@ import Fetcher from "./fetcher";
12
12
import Notifier from "./notifier" ;
13
13
import ParseEngineGateway from "./parse-engine-gateway" ;
14
14
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 ) ;
16
29
let uniqueDefinitions : CssClassDefinition [ ] = [ ] ;
17
30
18
31
const completionTriggerChars = [ '"' , "'" , " " , "." ] ;
19
32
20
33
let caching = false ;
21
34
22
- const emmetDisposables : Array < { dispose ( ) : any } > = [ ] ;
35
+ const htmlDisposables : Disposable [ ] = [ ] ;
36
+ const cssDisposables : Disposable [ ] = [ ] ;
37
+ const javaScriptDisposables : Disposable [ ] = [ ] ;
38
+ const emmetDisposables : Disposable [ ] = [ ] ;
23
39
24
40
async function cache ( ) : Promise < void > {
25
41
try {
@@ -77,76 +93,125 @@ async function cache(): Promise<void> {
77
93
}
78
94
}
79
95
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
+ }
93
112
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 ) ;
96
115
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 } ` ;
101
120
102
- completionItem . filterText = completionClassName ;
103
- completionItem . insertText = completionClassName ;
121
+ completionItem . filterText = completionClassName ;
122
+ completionItem . insertText = completionClassName ;
104
123
105
- return completionItem ;
106
- } ) ;
124
+ return completionItem ;
125
+ } ) ;
107
126
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 ) ;
114
132
}
115
133
}
134
+ }
116
135
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
+ ) ;
129
180
}
130
181
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 ;
135
185
}
136
186
137
187
export async function activate ( context : ExtensionContext ) : Promise < void > {
138
188
const disposables : Disposable [ ] = [ ] ;
139
189
workspace . onDidChangeConfiguration ( async ( e ) => {
140
190
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 ) ) {
143
193
await cache ( ) ;
144
194
}
145
195
146
- if ( e . affectsConfiguration ( "html-css-class-completion.enableEmmetSupport" ) ) {
196
+ if ( e . affectsConfiguration ( Configuration . EnableEmmetSupport ) ) {
147
197
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 ) ;
150
215
}
151
216
} catch ( err ) {
152
217
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> {
156
221
} , null , disposables ) ;
157
222
context . subscriptions . push ( ...disposables ) ;
158
223
159
- context . subscriptions . push ( commands . registerCommand ( "html-css-class-completion.cache" , async ( ) => {
224
+ context . subscriptions . push ( commands . registerCommand ( Command . Cache , async ( ) => {
160
225
if ( caching ) {
161
226
return ;
162
227
}
@@ -173,27 +238,13 @@ export async function activate(context: ExtensionContext): Promise<void> {
173
238
}
174
239
} ) ) ;
175
240
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 ) ;
179
243
}
180
244
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 ) ;
197
248
198
249
caching = true ;
199
250
try {
@@ -208,5 +259,8 @@ export async function activate(context: ExtensionContext): Promise<void> {
208
259
}
209
260
210
261
export function deactivate ( ) : void {
211
- emmetDisposables . forEach ( ( disposable ) => disposable . dispose ( ) ) ;
262
+ unregisterProviders ( htmlDisposables ) ;
263
+ unregisterProviders ( cssDisposables ) ;
264
+ unregisterProviders ( javaScriptDisposables ) ;
265
+ unregisterProviders ( emmetDisposables ) ;
212
266
}
0 commit comments