@@ -16,6 +16,7 @@ import { NULL_COMPLETION } from '../../nullMode';
16
16
import { getModifierProvider , Modifier } from '../modifierProvider' ;
17
17
import { toMarkupContent } from '../../../utils/strings' ;
18
18
import { Priority } from '../tagProviders/common' ;
19
+ import { kebabCase } from 'lodash' ;
19
20
20
21
export function doComplete (
21
22
document : TextDocument ,
@@ -42,7 +43,6 @@ export function doComplete(
42
43
const scanner = createScanner ( text , node . start ) ;
43
44
let currentTag : string ;
44
45
let currentAttributeName = '' ;
45
- let currentTagStartOffset : number ;
46
46
47
47
function getReplaceRange ( replaceStart : number , replaceEnd : number = offset ) : Range {
48
48
if ( replaceStart > offset ) {
@@ -148,12 +148,15 @@ export function doComplete(
148
148
return result ;
149
149
}
150
150
151
- function collectAttributeNameSuggestions (
152
- usedAttributes : Set < string > ,
153
- nameStart : number ,
154
- nameEnd : number = offset
155
- ) : CompletionList {
156
- const execArray = / ^ [: @ ] / . exec ( scanner . getTokenText ( ) ) ;
151
+ function getUsedAttributes ( offset : number ) {
152
+ const node = htmlDocument . findNodeBefore ( offset ) ;
153
+ return node . attributeNames . map ( normalizeAttributeNameToKebabCase ) ;
154
+ }
155
+
156
+ function collectAttributeNameSuggestions ( nameStart : number , nameEnd : number = offset ) : CompletionList {
157
+ const usedAttributes = getUsedAttributes ( nameStart ) ;
158
+ const currentAttribute = scanner . getTokenText ( ) ;
159
+ const execArray = / ^ [: @ ] / . exec ( currentAttribute ) ;
157
160
const filterPrefix = execArray ? execArray [ 0 ] : '' ;
158
161
const start = filterPrefix ? nameStart + 1 : nameStart ;
159
162
const range = getReplaceRange ( start , nameEnd ) ;
@@ -164,12 +167,14 @@ export function doComplete(
164
167
const priority = provider . priority ;
165
168
provider . collectAttributes ( currentTag , ( attribute , type , documentation ) => {
166
169
if (
167
- usedAttributes . has ( normalizeAttribute ( attribute ) ) &&
170
+ // include current typing attribute for completing `="$1"`
171
+ ! ( attribute === currentAttribute && text [ nameEnd ] !== '=' ) &&
168
172
// can listen to same event by adding modifiers
169
173
type !== 'event' &&
170
174
// `class` and `:class`, `style` and `:style` can coexist
171
175
attribute !== 'class' &&
172
- attribute !== 'style'
176
+ attribute !== 'style' &&
177
+ usedAttributes . includes ( normalizeAttributeNameToKebabCase ( attribute ) )
173
178
) {
174
179
return ;
175
180
}
@@ -286,23 +291,6 @@ export function doComplete(
286
291
return offset ;
287
292
}
288
293
289
- function collectUsedAttributes ( ) : Set < string > {
290
- const attrScanner = createScanner ( text , currentTagStartOffset ) ;
291
-
292
- let token = attrScanner . scan ( ) ;
293
- let currentAttributeName ! : string ;
294
- const attrs = new Set < string > ( ) ;
295
- while ( token !== TokenType . EOS && token !== TokenType . StartTagClose ) {
296
- if ( token === TokenType . AttributeName ) {
297
- currentAttributeName = normalizeAttribute ( attrScanner . getTokenText ( ) ) ;
298
- } else if ( token === TokenType . AttributeValue ) {
299
- attrs . add ( currentAttributeName ) ;
300
- }
301
- token = attrScanner . scan ( ) ;
302
- }
303
- return attrs ;
304
- }
305
-
306
294
let token = scanner . scan ( ) ;
307
295
308
296
while ( token !== TokenType . EOS && scanner . getTokenOffset ( ) <= offset ) {
@@ -312,7 +300,6 @@ export function doComplete(
312
300
const endPos = scanNextForEndPos ( TokenType . StartTag ) ;
313
301
return collectTagSuggestions ( offset , endPos ) ;
314
302
}
315
- currentTagStartOffset = scanner . getTokenOffset ( ) ;
316
303
break ;
317
304
case TokenType . StartTag :
318
305
if ( scanner . getTokenOffset ( ) <= offset && offset <= scanner . getTokenEnd ( ) ) {
@@ -322,8 +309,7 @@ export function doComplete(
322
309
break ;
323
310
case TokenType . AttributeName :
324
311
if ( scanner . getTokenOffset ( ) <= offset && offset <= scanner . getTokenEnd ( ) ) {
325
- const usedAttrs = collectUsedAttributes ( ) ;
326
- return collectAttributeNameSuggestions ( usedAttrs , scanner . getTokenOffset ( ) , scanner . getTokenEnd ( ) ) ;
312
+ return collectAttributeNameSuggestions ( scanner . getTokenOffset ( ) , scanner . getTokenEnd ( ) ) ;
327
313
}
328
314
currentAttributeName = scanner . getTokenText ( ) ;
329
315
break ;
@@ -355,8 +341,7 @@ export function doComplete(
355
341
return collectTagSuggestions ( startPos , endTagPos ) ;
356
342
case ScannerState . WithinTag :
357
343
case ScannerState . AfterAttributeName :
358
- const usedAttrs = collectUsedAttributes ( ) ;
359
- return collectAttributeNameSuggestions ( usedAttrs , scanner . getTokenEnd ( ) ) ;
344
+ return collectAttributeNameSuggestions ( scanner . getTokenEnd ( ) ) ;
360
345
case ScannerState . BeforeAttributeValue :
361
346
return collectAttributeValueSuggestions ( currentAttributeName , scanner . getTokenEnd ( ) ) ;
362
347
case ScannerState . AfterOpeningEndTag :
@@ -428,9 +413,25 @@ function getWordEnd(s: string, offset: number, limit: number): number {
428
413
return offset ;
429
414
}
430
415
431
- function normalizeAttribute ( attr : string ) : string {
432
- // trim modifiers
433
- attr = attr . replace ( / \. .+ $ / , '' ) ;
416
+ export function normalizeAttributeNameToKebabCase ( attr : string ) : string {
417
+ let result = attr ;
434
418
435
- return attr . replace ( / ^ (?: v - b i n d : | : ) / , '' ) . replace ( / ^ v - o n : / , '@' ) ;
419
+ if ( result . startsWith ( 'v-model:' ) ) {
420
+ result = attr . slice ( 'v-model:' . length ) ;
421
+ }
422
+
423
+ if ( result . startsWith ( 'v-bind:' ) ) {
424
+ result = attr . slice ( 'v-bind:' . length ) ;
425
+ } else if ( result . startsWith ( ':' ) ) {
426
+ result = attr . slice ( ':' . length ) ;
427
+ }
428
+
429
+ // Remove modifiers
430
+ if ( result . includes ( '.' ) ) {
431
+ result = result . slice ( 0 , result . indexOf ( '.' ) ) ;
432
+ }
433
+
434
+ result = kebabCase ( result ) ;
435
+
436
+ return result ;
436
437
}
0 commit comments