@@ -26,6 +26,7 @@ import {
2626 CaretPosition ,
2727 LOCALE_TYPE ,
2828 SemanticCollectOptions ,
29+ SuggestionOptions ,
2930 Suggestions ,
3031 SyntaxSuggestion ,
3132} from './types' ;
@@ -48,6 +49,7 @@ export abstract class BasicSQL<
4849 protected _parseTree : PRC | null ;
4950 protected _parsedInput : string ;
5051 protected _parseErrors : ParseError [ ] = [ ] ;
52+ private _statementStartTokenTypes : Set < number > | null = null ;
5153 /** members for cache end */
5254
5355 private _errorListener : ErrorListener = ( error ) => {
@@ -555,15 +557,95 @@ export abstract class BasicSQL<
555557 } ;
556558 }
557559
560+ /**
561+ * Get the minimum statement tree for collecting completion candidates
562+ */
563+ private getSuggestionParseTree (
564+ parseTree : ParserRuleContext ,
565+ caretTokenIndex : number
566+ ) : ParserRuleContext {
567+ const children = parseTree . children ;
568+ if ( ! children ?. length ) return parseTree ;
569+
570+ for ( let index = children . length - 1 ; index >= 0 ; index -- ) {
571+ const child = children [ index ] ;
572+ if ( ! ( child instanceof ParserRuleContext ) ) continue ;
573+
574+ const startTokenIndex = child . start ?. tokenIndex ;
575+ const stopTokenIndex = child . stop ?. tokenIndex ;
576+ if (
577+ startTokenIndex === undefined ||
578+ stopTokenIndex === undefined ||
579+ startTokenIndex > caretTokenIndex
580+ )
581+ continue ;
582+
583+ // Use the current statement tree when the caret is inside it
584+ if ( stopTokenIndex >= caretTokenIndex ) return child ;
585+
586+ // Keep using the current statement until it ends with a semicolon
587+ return child . stop ?. text === SQL_SPLIT_SYMBOL_TEXT ? parseTree : child ;
588+ }
589+
590+ return parseTree ;
591+ }
592+
593+ /**
594+ * Collect candidates for the current statement and remove new-statement-only keywords
595+ */
596+ private collectSuggestionCandidates (
597+ parser : Parser ,
598+ parseTree : ParserRuleContext ,
599+ caretTokenIndex : number
600+ ) : CandidatesCollection {
601+ const core = new CodeCompletionCore ( parser ) ;
602+ core . preferredRules = this . preferredRules ;
603+ const candidates = core . collectCandidates ( caretTokenIndex , parseTree ) ;
604+ const suggestionParseTree = this . getSuggestionParseTree ( parseTree , caretTokenIndex ) ;
605+
606+ if ( suggestionParseTree === parseTree ) return candidates ;
607+
608+ const statementCore = new CodeCompletionCore ( parser ) ;
609+ statementCore . preferredRules = this . preferredRules ;
610+ const statementCandidates = statementCore . collectCandidates (
611+ caretTokenIndex ,
612+ suggestionParseTree
613+ ) ;
614+
615+ if ( this . _statementStartTokenTypes === null ) {
616+ const statementStartCore = new CodeCompletionCore ( parser ) ;
617+ statementStartCore . preferredRules = this . preferredRules ;
618+ const statementStartCandidates = statementStartCore . collectCandidates ( 0 , parseTree ) ;
619+ this . _statementStartTokenTypes = new Set ( statementStartCandidates . tokens . keys ( ) ) ;
620+ }
621+
622+ const tokens = new Map ( candidates . tokens ) ;
623+ for ( const tokenType of this . _statementStartTokenTypes ) {
624+ if ( ! statementCandidates . tokens . has ( tokenType ) ) {
625+ tokens . delete ( tokenType ) ;
626+ } else if ( tokens . has ( tokenType ) ) {
627+ // Use the current statement follow-list to preserve valid combined keywords
628+ tokens . set ( tokenType , statementCandidates . tokens . get ( tokenType ) ! ) ;
629+ }
630+ }
631+
632+ return {
633+ rules : candidates . rules ,
634+ tokens,
635+ } ;
636+ }
637+
558638 /**
559639 * Get suggestions of syntax and token at caretPosition
560640 * @param input source string
561641 * @param caretPosition caret position, such as cursor position
642+ * @param options suggestion options
562643 * @returns suggestion
563644 */
564645 public getSuggestionAtCaretPosition (
565646 input : string ,
566- caretPosition : CaretPosition
647+ caretPosition : CaretPosition ,
648+ options ?: SuggestionOptions
567649 ) : Suggestions | null {
568650 this . parseWithCache ( input ) ;
569651 if ( ! this . _parseTree ) return null ;
@@ -614,12 +696,11 @@ export abstract class BasicSQL<
614696 parseTree = sqlParserIns . program ( ) ;
615697 }
616698
617- const core = new CodeCompletionCore ( sqlParserIns ) ;
618- core . preferredRules = this . preferredRules ;
619- // core.showRuleStack = true;
620- // core.showResult = true;
621-
622- const candidates = core . collectCandidates ( caretTokenIndex , parseTree ) ;
699+ const candidates = this . collectSuggestionCandidates (
700+ sqlParserIns ,
701+ parseTree ,
702+ caretTokenIndex
703+ ) ;
623704 const originalSuggestions = this . processCandidates ( candidates , allTokens , caretTokenIndex ) ;
624705
625706 const syntaxSuggestions : SyntaxSuggestion < WordRange > [ ] = originalSuggestions . syntax . map (
@@ -633,9 +714,14 @@ export abstract class BasicSQL<
633714 } ;
634715 }
635716 ) ;
717+ const keywordFilter = options ?. keywordFilter ;
718+ const keywords = keywordFilter
719+ ? originalSuggestions . keywords . filter ( ( keyword ) => keywordFilter ( keyword ) )
720+ : originalSuggestions . keywords ;
721+
636722 return {
637723 syntax : syntaxSuggestions ,
638- keywords : originalSuggestions . keywords ,
724+ keywords,
639725 } ;
640726 }
641727
0 commit comments