@@ -205,16 +205,14 @@ export class DefaultCompletionService implements CompletionService {
205
205
// find the current node
206
206
const node = findAtOffset ( { offset, includeRightBound : true } , api ) ;
207
207
// only if we have a node
208
- // TODO add jsonSchema completion, see experiments/apidom-monaco and vscode-json-languageservice
209
208
if ( node ) {
210
- // const sm = getSourceMap(node);
211
209
const caretContext = this . resolveCaretContext ( node , offset ) ;
212
210
const completionNode = this . resolveCompletionNode ( node , caretContext ) ;
213
- // const completionNodeSm = getSourceMap(completionNode);
214
211
const completionNodeContext = this . resolveCompletionNodeContext ( caretContext ) ;
215
212
// const currentWord = DefaultCompletionService.getCurrentWord(textDocument, offset);
216
213
217
- let overwriteRange : Range ;
214
+ let overwriteRange : Range | undefined ;
215
+ let quotes : string | undefined ;
218
216
219
217
const supportsCommitCharacters = false ; // this.doesSupportsCommitCharacters(); disabled for now, waiting for new API: https://github.com/microsoft/vscode/issues/42544
220
218
@@ -225,6 +223,7 @@ export class DefaultCompletionService implements CompletionService {
225
223
const item : CompletionItem = JSON . parse ( JSON . stringify ( suggestion ) ) ;
226
224
let { label } = item ;
227
225
const existing = proposed [ label ] ;
226
+ // don't suggest properties that are already present
228
227
if ( ! existing ) {
229
228
label = label . replace ( / [ \n ] / g, '↵' ) ;
230
229
if ( label . length > 60 ) {
@@ -257,9 +256,8 @@ export class DefaultCompletionService implements CompletionService {
257
256
} ,
258
257
} ;
259
258
260
- // don't suggest properties that are already present
261
259
if (
262
- isObject ( completionNode ) && // TODO added to get type check on node
260
+ isObject ( completionNode ) &&
263
261
( CompletionNodeContext . OBJECT === completionNodeContext ||
264
262
CompletionNodeContext . VALUE_OBJECT === completionNodeContext ) &&
265
263
( caretContext === CaretContext . KEY_INNER ||
@@ -273,12 +271,6 @@ export class DefaultCompletionService implements CompletionService {
273
271
proposed [ p . key . toValue ( ) ] = CompletionItem . create ( '__' ) ;
274
272
}
275
273
}
276
- }
277
-
278
- if ( schema ) {
279
- // TODO complete schema based, see json language service and "lsp" branch
280
- // DefaultCompletionService.getJsonSchemaPropertyCompletions(schema, api, node, addValue, separatorAfter, collector);
281
- } else {
282
274
const inNewLine = text . substring ( offset , text . indexOf ( '\n' , offset ) ) . trim ( ) . length === 0 ;
283
275
DefaultCompletionService . getMetadataPropertyCompletions (
284
276
api ,
@@ -287,6 +279,116 @@ export class DefaultCompletionService implements CompletionService {
287
279
! isJsonDoc ( textDocument ) ,
288
280
inNewLine ,
289
281
) ;
282
+ } else if (
283
+ // in a primitive value node
284
+ ! isObject ( completionNode ) &&
285
+ ( caretContext === CaretContext . MEMBER ||
286
+ caretContext === CaretContext . PRIMITIVE_VALUE_INNER ||
287
+ caretContext === CaretContext . PRIMITIVE_VALUE_END ||
288
+ caretContext === CaretContext . PRIMITIVE_VALUE_START )
289
+ ) {
290
+ const inNewLine = text . substring ( offset , text . indexOf ( '\n' , offset ) ) . trim ( ) . length === 0 ;
291
+ const nodeSourceMap = getSourceMap ( completionNode ) ;
292
+ // TODO Apidom doesn't hold quotes in its content currently, therefore we must use text + offset
293
+ const nodeValueFromText = text . substring ( nodeSourceMap . offset , nodeSourceMap . endOffset ) ;
294
+ quotes =
295
+ nodeValueFromText . charAt ( 0 ) === '"' || nodeValueFromText . charAt ( 0 ) === "'"
296
+ ? nodeValueFromText . charAt ( 0 )
297
+ : undefined ;
298
+ const word = DefaultCompletionService . getCurrentWord ( textDocument , offset ) ;
299
+ proposed [ completionNode . toValue ( ) ] = CompletionItem . create ( '__' ) ;
300
+ proposed [ nodeValueFromText ] = CompletionItem . create ( '__' ) ;
301
+ let withQuotes = false ;
302
+ // if node is not empty we must replace text
303
+ if ( nodeValueFromText . length > 0 ) {
304
+ /*
305
+ cases:
306
+
307
+ - quoted string, offset inside quotes
308
+ - quoted string, offset before quotes
309
+ - quoted empty string, offset before quotes
310
+ - quoted empty string, offset before quoted
311
+ - non quoted string
312
+ */
313
+
314
+ enum CompletionOffsetContextEnum {
315
+ NON_QUOTED ,
316
+ QUOTED_INSIDE ,
317
+ QUOTED_BEFORE ,
318
+ EMPTY_QUOTED_INSIDE ,
319
+ EMPTY_QUOTED_BEFORE ,
320
+ }
321
+
322
+ let completionOffsetContext = CompletionOffsetContextEnum . NON_QUOTED ;
323
+
324
+ if ( quotes && offset > nodeSourceMap . offset && completionNode . toValue ( ) . length > 0 ) {
325
+ completionOffsetContext = CompletionOffsetContextEnum . QUOTED_INSIDE ;
326
+ } else if (
327
+ quotes &&
328
+ offset <= nodeSourceMap . offset &&
329
+ completionNode . toValue ( ) . length > 0
330
+ ) {
331
+ completionOffsetContext = CompletionOffsetContextEnum . QUOTED_BEFORE ;
332
+ } else if (
333
+ quotes &&
334
+ offset <= nodeSourceMap . offset &&
335
+ completionNode . toValue ( ) . length === 0
336
+ ) {
337
+ completionOffsetContext = CompletionOffsetContextEnum . EMPTY_QUOTED_BEFORE ;
338
+ } else if (
339
+ quotes &&
340
+ offset > nodeSourceMap . offset &&
341
+ completionNode . toValue ( ) . length === 0
342
+ ) {
343
+ completionOffsetContext = CompletionOffsetContextEnum . EMPTY_QUOTED_INSIDE ;
344
+ }
345
+ const location = { offset : nodeSourceMap . offset , length : nodeSourceMap . length } ;
346
+ let targetRangeStart = location . offset ;
347
+ let targetRangeEnd = location . offset + location . length ;
348
+
349
+ if ( completionOffsetContext === CompletionOffsetContextEnum . QUOTED_INSIDE ) {
350
+ withQuotes = false ;
351
+ targetRangeStart = location . offset + 1 ;
352
+ targetRangeEnd = location . offset - 1 + location . length ;
353
+ } else if ( completionOffsetContext === CompletionOffsetContextEnum . QUOTED_BEFORE ) {
354
+ withQuotes = true ;
355
+ targetRangeStart = location . offset ;
356
+ targetRangeEnd = location . offset + location . length ;
357
+ } else if ( completionOffsetContext === CompletionOffsetContextEnum . EMPTY_QUOTED_INSIDE ) {
358
+ withQuotes = false ;
359
+ targetRangeStart = location . offset + 1 ;
360
+ targetRangeEnd = location . offset - 1 + location . length ;
361
+ } else if ( completionOffsetContext === CompletionOffsetContextEnum . EMPTY_QUOTED_BEFORE ) {
362
+ withQuotes = true ;
363
+ targetRangeStart = location . offset ;
364
+ targetRangeEnd = location . offset + location . length ;
365
+ }
366
+
367
+ overwriteRange = Range . create (
368
+ textDocument . positionAt ( targetRangeStart ) ,
369
+ textDocument . positionAt ( targetRangeEnd ) ,
370
+ ) ;
371
+ } else {
372
+ // node is empty
373
+ overwriteRange = undefined ;
374
+ }
375
+ DefaultCompletionService . getMetadataPropertyCompletions (
376
+ api ,
377
+ completionNode ,
378
+ collector ,
379
+ ! isJsonDoc ( textDocument ) ,
380
+ inNewLine ,
381
+ word ,
382
+ quotes ,
383
+ withQuotes ,
384
+ ) ;
385
+ }
386
+
387
+ if ( schema ) {
388
+ // TODO complete schema based, see json language service and "lsp" branch
389
+ // DefaultCompletionService.getJsonSchemaPropertyCompletions(schema, api, node, addValue, separatorAfter, collector);
390
+ } else {
391
+ //
290
392
}
291
393
}
292
394
@@ -356,19 +458,48 @@ export class DefaultCompletionService implements CompletionService {
356
458
collector : CompletionsCollector ,
357
459
yaml : boolean ,
358
460
inNewLine : boolean ,
461
+ word ?: string ,
462
+ quotes ?: string ,
463
+ withQuotes ?: boolean ,
359
464
) : void {
360
- const apidomCompletions : CompletionItem [ ] = doc . meta
361
- . get ( 'metadataMap' )
362
- ?. get ( node . element )
363
- ?. get ( yaml ? 'yaml' : 'json' )
364
- ?. get ( 'completion' )
365
- ?. toValue ( ) ;
465
+ const apidomCompletions : CompletionItem [ ] = [ ] ;
466
+ if ( node . classes ) {
467
+ const set : string [ ] = Array . from ( new Set ( node . classes . toValue ( ) ) ) ;
468
+ set . unshift ( node . element ) ;
469
+ set . forEach ( ( s ) => {
470
+ const classCompletions : CompletionItem [ ] = doc . meta
471
+ . get ( 'metadataMap' )
472
+ ?. get ( s )
473
+ ?. get ( yaml ? 'yaml' : 'json' )
474
+ ?. get ( 'completion' )
475
+ ?. toValue ( ) ;
476
+ if ( classCompletions ) {
477
+ apidomCompletions . push ( ...classCompletions ) ;
478
+ }
479
+ } ) ;
480
+ }
481
+
366
482
if ( apidomCompletions ) {
367
483
for ( const item of apidomCompletions ) {
484
+ if ( withQuotes ) {
485
+ const completionTextQuotes =
486
+ item . insertText ?. charAt ( 0 ) === '"' || item . insertText ?. charAt ( 0 ) === "'"
487
+ ? item . insertText ?. charAt ( 0 )
488
+ : undefined ;
489
+ if ( ! completionTextQuotes && quotes ) {
490
+ item . insertText = quotes + item . insertText + quotes ;
491
+ }
492
+ }
368
493
if ( inNewLine ) {
369
494
item . insertText = item . insertText ?. substring ( 0 , item . insertText ?. length - 1 ) ;
370
495
}
371
- collector . add ( item ) ;
496
+
497
+ const strippedQuotesWord = quotes && word ? word . substring ( 1 , word . length ) : word ! ;
498
+ if ( word && word . length > 0 && item . insertText ?. startsWith ( strippedQuotesWord ) ) {
499
+ collector . add ( item ) ;
500
+ } else if ( ! word ) {
501
+ collector . add ( item ) ;
502
+ }
372
503
}
373
504
}
374
505
}
0 commit comments