Compare commits

..

3 Commits

Author SHA1 Message Date
ahrefabhi
4120b53c27 fix: remove unused function call in getCurrentQueryPair 2025-07-10 22:26:47 +05:30
ahrefabhi
a7cbfe4587 fix: enhance context detection after closing parenthesis in query 2025-07-10 22:25:33 +05:30
ahrefabhi
74d480e113 fix: fixed context for query in parenthesis 2025-07-10 21:18:54 +05:30

View File

@@ -17,6 +17,37 @@ import {
} from './tokenUtils';
import { NON_VALUE_OPERATORS } from 'constants/antlrQueryConstants';
// Function to normalize multiple spaces to single spaces when not in quotes
function normalizeSpaces(query: string): string {
let result = '';
let inQuotes = false;
let lastChar = '';
for (let i = 0; i < query.length; i++) {
const char = query[i];
// Track quote state
if (char === "'" && (i === 0 || query[i - 1] !== '\\')) {
inQuotes = !inQuotes;
}
// If we're in quotes, always keep the original character
if (inQuotes) {
result += char;
}
// Otherwise, collapse multiple spaces to a single space
else if (char === ' ' && lastChar === ' ') {
// Skip this space (don't add it)
} else {
result += char;
}
lastChar = char;
}
return result;
}
// Function to create a context object
export function createContext(
token: Token,
@@ -545,8 +576,33 @@ export function getQueryContextAtCursor(
(isAtSpace && isAfterToken) ||
(cursorIndex === query.length && isAfterToken);
// First normalize the query to handle multiple spaces
// We need to adjust cursorIndex based on space normalization
let adjustedCursorIndex = cursorIndex;
let spaceCount = 0;
let inQuotes = false;
// Count consecutive spaces before the cursor to adjust the cursor position
for (let i = 0; i < cursorIndex; i++) {
// Track quote state
if (query[i] === "'" && (i === 0 || query[i - 1] !== '\\')) {
inQuotes = !inQuotes;
}
// Only count spaces when not in quotes
if (!inQuotes && query[i] === ' ' && (i === 0 || query[i - 1] === ' ')) {
spaceCount++;
}
}
// Adjust cursor position based on removed spaces
adjustedCursorIndex = cursorIndex - spaceCount;
// Normalize the query by removing extra spaces when not in quotes
const normalizedQuery = normalizeSpaces(query);
// Create input stream and lexer with normalized query
const input = query || '';
const input = normalizedQuery || '';
const chars = CharStreams.fromString(input);
const lexer = new FilterQueryLexer(chars);
@@ -570,12 +626,15 @@ export function getQueryContextAtCursor(
// FIXED: Consider a token to be the lastTokenBeforeCursor if the cursor is
// exactly at the end of the token (including the last character)
if (token.stop < cursorIndex || token.stop + 1 === cursorIndex) {
if (
token.stop < adjustedCursorIndex ||
token.stop + 1 === adjustedCursorIndex
) {
lastTokenBeforeCursor = token;
}
// If we found a token that starts after the cursor, we're done searching
if (token.start > cursorIndex) {
if (token.start > adjustedCursorIndex) {
break;
}
}
@@ -586,13 +645,13 @@ export function getQueryContextAtCursor(
// Find the current pair without causing a circular dependency
let currentPair: IQueryPair | null = null;
if (queryPairs.length > 0) {
currentPair = getCurrentQueryPair(queryPairs, query, cursorIndex);
currentPair = getCurrentQueryPair(queryPairs, query, adjustedCursorIndex);
}
// Determine precise context boundaries
const contextBoundaries = determineContextBoundaries(
query,
cursorIndex,
adjustedCursorIndex,
allTokens,
queryPairs,
);
@@ -601,54 +660,54 @@ export function getQueryContextAtCursor(
// FIXED: Include the case where the cursor is exactly at the end of a boundary
const isInKeyBoundary =
contextBoundaries.keyContext &&
((cursorIndex >= contextBoundaries.keyContext.start &&
cursorIndex <= contextBoundaries.keyContext.end) ||
cursorIndex === contextBoundaries.keyContext.end + 1);
((adjustedCursorIndex >= contextBoundaries.keyContext.start &&
adjustedCursorIndex <= contextBoundaries.keyContext.end) ||
adjustedCursorIndex === contextBoundaries.keyContext.end + 1);
const isInNegationBoundary =
contextBoundaries.negationContext &&
((cursorIndex >= contextBoundaries.negationContext.start &&
cursorIndex <= contextBoundaries.negationContext.end) ||
cursorIndex === contextBoundaries.negationContext.end + 1);
((adjustedCursorIndex >= contextBoundaries.negationContext.start &&
adjustedCursorIndex <= contextBoundaries.negationContext.end) ||
adjustedCursorIndex === contextBoundaries.negationContext.end + 1);
const isInOperatorBoundary =
contextBoundaries.operatorContext &&
((cursorIndex >= contextBoundaries.operatorContext.start &&
cursorIndex <= contextBoundaries.operatorContext.end) ||
cursorIndex === contextBoundaries.operatorContext.end + 1);
((adjustedCursorIndex >= contextBoundaries.operatorContext.start &&
adjustedCursorIndex <= contextBoundaries.operatorContext.end) ||
adjustedCursorIndex === contextBoundaries.operatorContext.end + 1);
const isInValueBoundary =
contextBoundaries.valueContext &&
((cursorIndex >= contextBoundaries.valueContext.start &&
cursorIndex <= contextBoundaries.valueContext.end) ||
cursorIndex === contextBoundaries.valueContext.end + 1);
((adjustedCursorIndex >= contextBoundaries.valueContext.start &&
adjustedCursorIndex <= contextBoundaries.valueContext.end) ||
adjustedCursorIndex === contextBoundaries.valueContext.end + 1);
const isInConjunctionBoundary =
contextBoundaries.conjunctionContext &&
((cursorIndex >= contextBoundaries.conjunctionContext.start &&
cursorIndex <= contextBoundaries.conjunctionContext.end) ||
cursorIndex === contextBoundaries.conjunctionContext.end + 1);
((adjustedCursorIndex >= contextBoundaries.conjunctionContext.start &&
adjustedCursorIndex <= contextBoundaries.conjunctionContext.end) ||
adjustedCursorIndex === contextBoundaries.conjunctionContext.end + 1);
// Check for bracket list context (used for IN operator values)
const isInBracketListBoundary =
contextBoundaries.bracketContext &&
contextBoundaries.bracketContext.isForList &&
cursorIndex >= contextBoundaries.bracketContext.start &&
cursorIndex <= contextBoundaries.bracketContext.end + 1;
adjustedCursorIndex >= contextBoundaries.bracketContext.start &&
adjustedCursorIndex <= contextBoundaries.bracketContext.end + 1;
// Check for general parenthesis context (not for IN operator lists)
const isInParenthesisBoundary =
contextBoundaries.bracketContext &&
!contextBoundaries.bracketContext.isForList &&
cursorIndex >= contextBoundaries.bracketContext.start &&
cursorIndex <= contextBoundaries.bracketContext.end + 1;
adjustedCursorIndex >= contextBoundaries.bracketContext.start &&
adjustedCursorIndex <= contextBoundaries.bracketContext.end + 1;
// Check if we're right after a closing bracket for a list (IN operator)
// This helps transition to conjunction context after a multi-value list
const isAfterClosingBracketList =
contextBoundaries.bracketContext &&
contextBoundaries.bracketContext.isForList &&
cursorIndex === contextBoundaries.bracketContext.end + 2 &&
adjustedCursorIndex === contextBoundaries.bracketContext.end + 2 &&
query[contextBoundaries.bracketContext.end + 1] === ' ';
// If cursor is within a specific context boundary, this takes precedence
@@ -659,7 +718,6 @@ export function getQueryContextAtCursor(
isInValueBoundary ||
isInConjunctionBoundary ||
isInBracketListBoundary ||
isInParenthesisBoundary ||
isAfterClosingBracketList
) {
// Extract information from the current pair (if available)
@@ -682,8 +740,8 @@ export function getQueryContextAtCursor(
return {
tokenType: -1,
text: '',
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: '',
isInKey: isInKeyBoundary || false,
isInNegation: isInNegationBoundary || false,
@@ -711,7 +769,7 @@ export function getQueryContextAtCursor(
// Continue with existing token-based logic for cases not covered by context boundaries
// Handle cursor at the very end of input
if (cursorIndex >= input.length && allTokens.length > 0) {
if (adjustedCursorIndex >= input.length && allTokens.length > 0) {
const lastRealToken = allTokens
.filter((t) => t.type !== FilterQueryLexer.EOF)
.pop();
@@ -730,7 +788,10 @@ export function getQueryContextAtCursor(
}
// FIXED: Check if cursor is within token bounds (inclusive) or exactly at the end
if (token.start <= cursorIndex && cursorIndex <= token.stop + 1) {
if (
token.start <= adjustedCursorIndex &&
adjustedCursorIndex <= token.stop + 1
) {
exactToken = token;
previousToken = i > 0 ? allTokens[i - 1] : null;
nextToken = i < allTokens.length - 1 ? allTokens[i + 1] : null;
@@ -750,7 +811,10 @@ export function getQueryContextAtCursor(
continue;
}
if (current.stop + 1 < cursorIndex && cursorIndex < next.start) {
if (
current.stop + 1 < adjustedCursorIndex &&
adjustedCursorIndex < next.start
) {
previousToken = current;
nextToken = next;
break;
@@ -764,8 +828,8 @@ export function getQueryContextAtCursor(
return {
tokenType: -1,
text: '',
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: '',
isInKey: true, // Default to key context when input is empty
isInNegation: false,
@@ -794,8 +858,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: false,
isInNegation: false,
@@ -815,8 +879,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: false,
isInNegation: false,
@@ -839,8 +903,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: false,
isInNegation: false,
@@ -864,8 +928,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: false,
isInNegation: false,
@@ -888,8 +952,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: true, // After conjunction + space, should be key context
isInNegation: false,
@@ -903,11 +967,35 @@ export function getQueryContextAtCursor(
currentPair: currentPair,
};
}
if (
lastTokenContext.isInParenthesis &&
lastTokenBeforeCursor.type === FilterQueryLexer.RPAREN
) {
// If we are after a parenthesis we should enter the conjunction context.
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: false,
isInNegation: false,
isInOperator: false,
isInValue: false,
isInFunction: false,
isInConjunction: true, // After RPARAN + space, should be conjunction context
isInParenthesis: false,
isInBracketList: false,
queryPairs: queryPairs,
currentPair: currentPair,
};
}
}
// FIXED: Consider the case where the cursor is at the end of a token
// with no space yet (user is actively typing)
if (exactToken && cursorIndex === exactToken.stop + 1) {
if (exactToken && adjustedCursorIndex === exactToken.stop + 1) {
const tokenContext = determineTokenContext(exactToken, input);
// When the cursor is at the end of a token, return the current token context
@@ -982,8 +1070,8 @@ export function getQueryContextAtCursor(
return {
tokenType: previousToken.type,
text: previousToken.text,
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: previousToken.text,
isInKey: false,
isInNegation: false,
@@ -1005,8 +1093,8 @@ export function getQueryContextAtCursor(
return {
tokenType: previousToken.type,
text: previousToken.text,
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: previousToken.text,
isInKey: false,
isInNegation: false,
@@ -1026,8 +1114,8 @@ export function getQueryContextAtCursor(
return {
tokenType: previousToken.type,
text: previousToken.text,
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: previousToken.text,
isInKey: false,
isInNegation: false,
@@ -1049,8 +1137,8 @@ export function getQueryContextAtCursor(
return {
tokenType: previousToken.type,
text: previousToken.text,
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: previousToken.text,
isInKey: true, // After conjunction, progress back to key context
isInNegation: false,
@@ -1070,8 +1158,8 @@ export function getQueryContextAtCursor(
return {
tokenType: -1,
text: '',
start: cursorIndex,
stop: cursorIndex,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
currentToken: '',
isInKey: true,
isInNegation: false,
@@ -1123,8 +1211,11 @@ export function extractQueryPairs(query: string): IQueryPair[] {
return [];
}
// Normalize the query to handle multiple spaces
const normalizedQuery = normalizeSpaces(query);
// Create input stream and lexer with normalized query
const input = query || '';
const input = normalizedQuery || '';
const chars = CharStreams.fromString(input);
const lexer = new FilterQueryLexer(chars);
@@ -1386,6 +1477,19 @@ export function extractQueryPairs(query: string): IQueryPair[] {
}
}
function getIndexTillSpace(pair: IQueryPair, query: string): number {
const { position } = pair;
let pairEnd = position.valueEnd || position.operatorEnd || position.keyEnd;
// Start from the next index after pairEnd
pairEnd += 1;
while (pairEnd < query.length && query.charAt(pairEnd) === ' ') {
pairEnd += 1;
}
return pairEnd;
}
/**
* Gets the current query pair at the cursor position
* This is useful for getting suggestions based on the current context
@@ -1415,12 +1519,14 @@ export function getCurrentQueryPair(
position.valueEnd || position.operatorEnd || position.keyEnd;
const pairStart =
position.keyStart || position.operatorStart || position.valueStart || 0;
position.keyStart ?? (position.operatorStart || position.valueStart || 0);
// If this pair ends at or before the cursor, and it's further right than our previous best match
if (
pairEnd >= cursorIndex &&
pairStart <= cursorIndex &&
((pairEnd >= cursorIndex && pairStart <= cursorIndex) ||
(!pair.isComplete &&
pairStart <= cursorIndex &&
getIndexTillSpace(pair, query) >= cursorIndex)) &&
(!bestMatch ||
pairEnd >
(bestMatch.position.valueEnd ||