diff --git a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx index d292e2deb82b..1ca10354805b 100644 --- a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx +++ b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx @@ -349,13 +349,11 @@ function CodeMirrorWhereClause(): JSX.Element { } else if (queryContext.isInConjunction) { color = 'magenta'; text = 'Conjunction'; + } else if (queryContext.isInParenthesis) { + color = 'grey'; + text = 'Parenthesis'; } - // else if (queryContext.isInParenthesis) { - // color = 'grey'; - // text = 'Parenthesis'; - // } - return ; }; @@ -451,7 +449,8 @@ function CodeMirrorWhereClause(): JSX.Element { options = [ { label: 'HAS', type: 'function' }, { label: 'HASANY', type: 'function' }, - // Add more function options here + { label: 'HASALL', type: 'function' }, + { label: 'HASNONE', type: 'function' }, ]; return { from: word?.from ?? 0, @@ -470,6 +469,45 @@ function CodeMirrorWhereClause(): JSX.Element { }; } + if (queryContext.isInParenthesis) { + // Different suggestions based on the context within parenthesis or bracket + const curChar = query.charAt(cursorPos.ch - 1) || ''; + + if (curChar === '(' || curChar === '[') { + // Right after opening parenthesis/bracket + if (curChar === '(') { + // In expression context, suggest keys, functions, or nested parentheses + return { + from: word?.from ?? 0, + options: [ + ...(keySuggestions || []), + { label: '(', type: 'parenthesis', info: 'Open nested group' }, + { label: 'NOT', type: 'operator', info: 'Negate expression' }, + ...options.filter((opt) => opt.type === 'function'), + ], + }; + } + + // Inside square brackets (likely for IN operator) + // Suggest values, commas, or closing bracket + return { + from: word?.from ?? 0, + options: valueSuggestions, + }; + } + + if (curChar === ')' || curChar === ']') { + // After closing parenthesis/bracket, suggest conjunctions + return { + from: word?.from ?? 0, + options: [ + { label: 'AND', type: 'conjunction' }, + { label: 'OR', type: 'conjunction' }, + ], + }; + } + } + return { from: word?.from ?? 0, options: [], diff --git a/frontend/src/utils/antlrQueryUtils.ts b/frontend/src/utils/antlrQueryUtils.ts index 0d69766ea00e..10166785c88b 100644 --- a/frontend/src/utils/antlrQueryUtils.ts +++ b/frontend/src/utils/antlrQueryUtils.ts @@ -409,11 +409,13 @@ export function getQueryContextAtCursor( currentToken.type, ); - // // Determine if the current token is a parenthesis - // const isInParenthesis = [ - // FilterQueryLexer.LPAREN, - // FilterQueryLexer.RPAREN, - // ].includes(currentToken.type); + // Determine if the current token is a parenthesis or bracket + const isInParenthesis = [ + FilterQueryLexer.LPAREN, + FilterQueryLexer.RPAREN, + FilterQueryLexer.LBRACK, + FilterQueryLexer.RBRACK, + ].includes(currentToken.type); // Determine the context based on the token type const isInValue = [ @@ -540,6 +542,66 @@ export function getQueryContextAtCursor( ...relationTokens, // Include related tokens }; } + + if (isInParenthesis) { + // After a parenthesis/bracket + space, determine context based on which bracket + if (currentToken.type === FilterQueryLexer.LPAREN) { + // After an opening parenthesis + space, we should be in key context + return { + tokenType: currentToken.type, + text: currentToken.text, + start: currentToken.start, + stop: currentToken.stop, + currentToken: currentToken.text, + isInValue: false, + isInKey: true, + isInOperator: false, + isInFunction: false, + isInConjunction: false, + isInParenthesis: false, + ...relationTokens, + }; + } + + if ( + currentToken.type === FilterQueryLexer.RPAREN || + currentToken.type === FilterQueryLexer.RBRACK + ) { + // After a closing parenthesis/bracket + space, we should be in conjunction context + return { + tokenType: currentToken.type, + text: currentToken.text, + start: currentToken.start, + stop: currentToken.stop, + currentToken: currentToken.text, + isInValue: false, + isInKey: false, + isInOperator: false, + isInFunction: false, + isInConjunction: true, + isInParenthesis: false, + ...relationTokens, + }; + } + + if (currentToken.type === FilterQueryLexer.LBRACK) { + // After an opening bracket + space, we should be in value context (for arrays) + return { + tokenType: currentToken.type, + text: currentToken.text, + start: currentToken.start, + stop: currentToken.stop, + currentToken: currentToken.text, + isInValue: true, + isInKey: false, + isInOperator: false, + isInFunction: false, + isInConjunction: false, + isInParenthesis: false, + ...relationTokens, + }; + } + } } // Add logic for context detection that works for both forward and backward navigation @@ -722,6 +784,31 @@ export function getQueryContextAtCursor( ...relationTokens, // Include related tokens }; } + + // Add case for parentheses and brackets + if ( + [ + FilterQueryLexer.LPAREN, + FilterQueryLexer.RPAREN, + FilterQueryLexer.LBRACK, + FilterQueryLexer.RBRACK, + ].includes(nextToken.type) + ) { + return { + tokenType: -1, + text: '', + start: cursorIndex, + stop: cursorIndex, + currentToken: '', + isInValue: false, + isInKey: false, + isInOperator: false, + isInFunction: false, + isInConjunction: false, + isInParenthesis: true, + ...relationTokens, // Include related tokens + }; + } } // Fall back to default context detection based on current token @@ -736,7 +823,7 @@ export function getQueryContextAtCursor( isInOperator, isInFunction, isInConjunction, - // isInParenthesis, + isInParenthesis, ...relationTokens, // Include related tokens }; } catch (error) {