9
9
ParseTreeListener ,
10
10
PredictionMode ,
11
11
ANTLRErrorListener ,
12
+ Parser ,
12
13
} from 'antlr4ng' ;
13
14
import { CandidatesCollection , CodeCompletionCore } from 'antlr4-c3' ;
14
15
import { SQLParserBase } from '../../lib/SQLParserBase' ;
@@ -28,6 +29,8 @@ import type { EntityCollector } from './entityCollector';
28
29
import { EntityContext } from './entityCollector' ;
29
30
import SemanticContextCollector from './semanticContextCollector' ;
30
31
32
+ export const SQL_SPLIT_SYMBOL_TEXT = ';' ;
33
+
31
34
/**
32
35
* Basic SQL class, every sql needs extends it.
33
36
*/
@@ -264,7 +267,6 @@ export abstract class BasicSQL<
264
267
return null ;
265
268
}
266
269
const splitListener = this . splitListener ;
267
-
268
270
this . listen ( splitListener , this . _parseTree ) ;
269
271
270
272
const res = splitListener . statementsContext
@@ -277,35 +279,102 @@ export abstract class BasicSQL<
277
279
}
278
280
279
281
/**
280
- * Get a minimum boundary parser near tokenIndex.
281
- * @param input source string.
282
- * @param tokenIndex start from which index to minimize the boundary.
283
- * @param originParseTree the parse tree need to be minimized, default value is the result of parsing `input`.
284
- * @returns minimum parser info
282
+ * Get the smaller range of input
283
+ * @param input string
284
+ * @param allTokens all tokens from input
285
+ * @param tokenIndexOffset offset of the tokenIndex in the range of input
286
+ * @param caretTokenIndex tokenIndex of caretPosition
287
+ * @returns inputSlice: string, caretTokenIndex: number
285
288
*/
286
- public getMinimumParserInfo (
289
+ private splitInputBySymbolText (
287
290
input : string ,
288
- tokenIndex : number ,
289
- originParseTree ?: ParserRuleContext | null
290
- ) {
291
- if ( arguments . length <= 2 ) {
292
- this . parseWithCache ( input ) ;
293
- originParseTree = this . _parseTree ;
291
+ allTokens : Token [ ] ,
292
+ tokenIndexOffset : number ,
293
+ caretTokenIndex : number
294
+ ) : { inputSlice : string ; allTokens : Token [ ] ; caretTokenIndex : number } {
295
+ const tokens = allTokens . slice ( tokenIndexOffset ) ;
296
+ /**
297
+ * Set startToken
298
+ */
299
+ let startToken : Token | null = null ;
300
+ for ( let tokenIndex = caretTokenIndex - tokenIndexOffset ; tokenIndex >= 0 ; tokenIndex -- ) {
301
+ const token = tokens [ tokenIndex ] ;
302
+ if ( token ?. text === SQL_SPLIT_SYMBOL_TEXT ) {
303
+ startToken = tokens [ tokenIndex + 1 ] ;
304
+ break ;
305
+ }
306
+ }
307
+ if ( startToken === null ) {
308
+ startToken = tokens [ 0 ] ;
309
+ }
310
+
311
+ /**
312
+ * Set stopToken
313
+ */
314
+ let stopToken : Token | null = null ;
315
+ for (
316
+ let tokenIndex = caretTokenIndex - tokenIndexOffset ;
317
+ tokenIndex < tokens . length ;
318
+ tokenIndex ++
319
+ ) {
320
+ const token = tokens [ tokenIndex ] ;
321
+ if ( token ?. text === SQL_SPLIT_SYMBOL_TEXT ) {
322
+ stopToken = token ;
323
+ break ;
324
+ }
325
+ }
326
+ if ( stopToken === null ) {
327
+ stopToken = tokens [ tokens . length - 1 ] ;
294
328
}
295
329
330
+ const indexOffset = tokens [ 0 ] . start ;
331
+ let startIndex = startToken . start - indexOffset ;
332
+ let stopIndex = stopToken . stop + 1 - indexOffset ;
333
+
334
+ /**
335
+ * Save offset of the tokenIndex in the range of input
336
+ * compared to the tokenIndex in the whole input
337
+ */
338
+ const _tokenIndexOffset = startToken . tokenIndex ;
339
+ const _caretTokenIndex = caretTokenIndex - _tokenIndexOffset ;
340
+
341
+ /**
342
+ * Get the smaller range of _input
343
+ */
344
+ const _input = input . slice ( startIndex , stopIndex ) ;
345
+
346
+ return {
347
+ inputSlice : _input ,
348
+ allTokens : allTokens . slice ( _tokenIndexOffset ) ,
349
+ caretTokenIndex : _caretTokenIndex ,
350
+ } ;
351
+ }
352
+
353
+ /**
354
+ * Get the minimum input string that can be parsed successfully by c3.
355
+ * @param input source string
356
+ * @param caretTokenIndex tokenIndex of caretPosition
357
+ * @param originParseTree origin parseTree
358
+ * @returns MinimumInputInfo
359
+ */
360
+ public getMinimumInputInfo (
361
+ input : string ,
362
+ caretTokenIndex : number ,
363
+ originParseTree : ParserRuleContext | undefined
364
+ ) : { input : string ; tokenIndexOffset : number ; statementCount : number } | null {
296
365
if ( ! originParseTree || ! input ?. length ) return null ;
366
+ let inputSlice = input ;
297
367
298
- const splitListener = this . splitListener ;
299
368
/**
300
369
* Split sql by statement.
301
370
* Try to collect candidates in as small a range as possible.
302
371
*/
372
+ const splitListener = this . splitListener ;
303
373
this . listen ( splitListener , originParseTree ) ;
374
+
304
375
const statementCount = splitListener . statementsContext ?. length ;
305
376
const statementsContext = splitListener . statementsContext ;
306
377
let tokenIndexOffset = 0 ;
307
- let sqlParserIns = this . _parser ;
308
- let parseTree = originParseTree ;
309
378
310
379
// If there are multiple statements.
311
380
if ( statementCount > 1 ) {
@@ -330,14 +399,14 @@ export abstract class BasicSQL<
330
399
const isNextCtxValid =
331
400
index === statementCount - 1 || ! statementsContext [ index + 1 ] ?. exception ;
332
401
333
- if ( ctx . stop && ctx . stop . tokenIndex < tokenIndex && isPrevCtxValid ) {
402
+ if ( ctx . stop && ctx . stop . tokenIndex < caretTokenIndex && isPrevCtxValid ) {
334
403
startStatement = ctx ;
335
404
}
336
405
337
406
if (
338
407
ctx . start &&
339
408
! stopStatement &&
340
- ctx . start . tokenIndex > tokenIndex &&
409
+ ctx . start . tokenIndex > caretTokenIndex &&
341
410
isNextCtxValid
342
411
) {
343
412
stopStatement = ctx ;
@@ -347,41 +416,64 @@ export abstract class BasicSQL<
347
416
348
417
// A boundary consisting of the index of the input.
349
418
const startIndex = startStatement ?. start ?. start ?? 0 ;
350
- const stopIndex = stopStatement ?. stop ?. stop ?? input . length - 1 ;
419
+ const stopIndex = stopStatement ?. stop ?. stop ?? inputSlice . length - 1 ;
351
420
352
421
/**
353
422
* Save offset of the tokenIndex in the range of input
354
423
* compared to the tokenIndex in the whole input
355
424
*/
356
425
tokenIndexOffset = startStatement ?. start ?. tokenIndex ?? 0 ;
357
- tokenIndex = tokenIndex - tokenIndexOffset ;
426
+ inputSlice = inputSlice . slice ( startIndex , stopIndex ) ;
427
+ }
358
428
359
- /**
360
- * Reparse the input fragment,
361
- * and c3 will collect candidates in the newly generated parseTree.
362
- */
363
- const inputSlice = input . slice ( startIndex , stopIndex ) ;
429
+ return {
430
+ input : inputSlice ,
431
+ tokenIndexOffset,
432
+ statementCount,
433
+ } ;
434
+ }
364
435
365
- const lexer = this . createLexer ( inputSlice ) ;
366
- lexer . removeErrorListeners ( ) ;
367
- const tokenStream = new CommonTokenStream ( lexer ) ;
368
- tokenStream . fill ( ) ;
436
+ /**
437
+ * Get a minimum boundary parser near caretTokenIndex.
438
+ * @param input source string.
439
+ * @param caretTokenIndex start from which index to minimize the boundary.
440
+ * @param originParseTree the parse tree need to be minimized, default value is the result of parsing `input`.
441
+ * @returns minimum parser info
442
+ */
443
+ public getMinimumParserInfo (
444
+ input : string ,
445
+ caretTokenIndex : number ,
446
+ originParseTree : ParserRuleContext | undefined
447
+ ) : {
448
+ parser : Parser ;
449
+ parseTree : ParserRuleContext ;
450
+ tokenIndexOffset : number ;
451
+ newTokenIndex : number ;
452
+ } | null {
453
+ if ( ! originParseTree || ! input ?. length ) return null ;
369
454
370
- const parser = this . createParserFromTokenStream ( tokenStream ) ;
371
- parser . interpreter . predictionMode = PredictionMode . SLL ;
372
- parser . removeErrorListeners ( ) ;
373
- parser . buildParseTrees = true ;
374
- parser . errorHandler = new ErrorStrategy ( ) ;
455
+ const inputInfo = this . getMinimumInputInfo ( input , caretTokenIndex , originParseTree ) ;
456
+ if ( ! inputInfo ) return null ;
457
+ const { input : inputSlice , tokenIndexOffset } = inputInfo ;
458
+ caretTokenIndex = caretTokenIndex - tokenIndexOffset ;
375
459
376
- sqlParserIns = parser ;
377
- parseTree = parser . program ( ) ;
460
+ let sqlParserIns = this . _parser ;
461
+ let parseTree = originParseTree ;
462
+
463
+ /**
464
+ * Reparse the input fragment,
465
+ * and c3 will collect candidates in the newly generated parseTree when input changed.
466
+ */
467
+ if ( inputSlice !== input ) {
468
+ sqlParserIns = this . createParser ( inputSlice ) ;
469
+ parseTree = sqlParserIns . program ( ) ;
378
470
}
379
471
380
472
return {
381
473
parser : sqlParserIns ,
382
474
parseTree,
383
475
tokenIndexOffset,
384
- newTokenIndex : tokenIndex ,
476
+ newTokenIndex : caretTokenIndex ,
385
477
} ;
386
478
}
387
479
@@ -396,33 +488,63 @@ export abstract class BasicSQL<
396
488
caretPosition : CaretPosition
397
489
) : Suggestions | null {
398
490
this . parseWithCache ( input ) ;
399
-
400
491
if ( ! this . _parseTree ) return null ;
401
492
402
- const allTokens = this . getAllTokens ( input ) ;
493
+ let allTokens = this . getAllTokens ( input ) ;
403
494
let caretTokenIndex = findCaretTokenIndex ( caretPosition , allTokens ) ;
404
-
405
495
if ( ! caretTokenIndex && caretTokenIndex !== 0 ) return null ;
406
496
407
- const minimumParser = this . getMinimumParserInfo ( input , caretTokenIndex ) ;
497
+ const inputInfo = this . getMinimumInputInfo ( input , caretTokenIndex , this . _parseTree ) ;
498
+ if ( ! inputInfo ) return null ;
499
+ const { input : _input , tokenIndexOffset, statementCount } = inputInfo ;
500
+ let inputSlice = _input ;
501
+
502
+ /**
503
+ * Split the inputSlice by separator to get the smaller range of inputSlice.
504
+ */
505
+ if ( inputSlice . includes ( SQL_SPLIT_SYMBOL_TEXT ) ) {
506
+ const {
507
+ inputSlice : _inputSlice ,
508
+ allTokens : _allTokens ,
509
+ caretTokenIndex : _caretTokenIndex ,
510
+ } = this . splitInputBySymbolText (
511
+ inputSlice ,
512
+ allTokens ,
513
+ tokenIndexOffset ,
514
+ caretTokenIndex
515
+ ) ;
516
+
517
+ allTokens = _allTokens ;
518
+ caretTokenIndex = _caretTokenIndex ;
519
+ inputSlice = _inputSlice ;
520
+ } else {
521
+ if ( statementCount > 1 ) {
522
+ caretTokenIndex = caretTokenIndex - tokenIndexOffset ;
523
+ }
524
+ }
525
+
526
+ let sqlParserIns = this . _parser ;
527
+ let parseTree = this . _parseTree ;
408
528
409
- if ( ! minimumParser ) return null ;
529
+ /**
530
+ * Reparse the input fragment,
531
+ * and c3 will collect candidates in the newly generated parseTree when input changed.
532
+ */
533
+ if ( inputSlice !== input ) {
534
+ sqlParserIns = this . createParser ( inputSlice ) ;
535
+ parseTree = sqlParserIns . program ( ) ;
536
+ }
410
537
411
- const {
412
- parser : sqlParserIns ,
413
- tokenIndexOffset,
414
- newTokenIndex,
415
- parseTree : c3Context ,
416
- } = minimumParser ;
417
538
const core = new CodeCompletionCore ( sqlParserIns ) ;
418
539
core . preferredRules = this . preferredRules ;
419
540
420
- const candidates = core . collectCandidates ( newTokenIndex , c3Context ) ;
541
+ const candidates = core . collectCandidates ( caretTokenIndex , parseTree ) ;
421
542
const originalSuggestions = this . processCandidates (
422
543
candidates ,
423
544
allTokens ,
424
- newTokenIndex ,
425
- tokenIndexOffset
545
+ caretTokenIndex ,
546
+ 0
547
+ // tokenIndexOffset
426
548
) ;
427
549
428
550
const syntaxSuggestions : SyntaxSuggestion < WordRange > [ ] = originalSuggestions . syntax . map (
0 commit comments