fix: added fix for context breaking with spaces

This commit is contained in:
ahrefabhi 2025-07-10 17:35:23 +05:30
parent e0582f6edb
commit b0a2f64ebe

View File

@ -17,37 +17,6 @@ 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,
@ -576,33 +545,8 @@ 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 = normalizedQuery || '';
const input = query || '';
const chars = CharStreams.fromString(input);
const lexer = new FilterQueryLexer(chars);
@ -626,15 +570,12 @@ 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 < adjustedCursorIndex ||
token.stop + 1 === adjustedCursorIndex
) {
if (token.stop < cursorIndex || token.stop + 1 === cursorIndex) {
lastTokenBeforeCursor = token;
}
// If we found a token that starts after the cursor, we're done searching
if (token.start > adjustedCursorIndex) {
if (token.start > cursorIndex) {
break;
}
}
@ -645,13 +586,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, adjustedCursorIndex);
currentPair = getCurrentQueryPair(queryPairs, query, cursorIndex);
}
// Determine precise context boundaries
const contextBoundaries = determineContextBoundaries(
query,
adjustedCursorIndex,
cursorIndex,
allTokens,
queryPairs,
);
@ -660,54 +601,54 @@ export function getQueryContextAtCursor(
// FIXED: Include the case where the cursor is exactly at the end of a boundary
const isInKeyBoundary =
contextBoundaries.keyContext &&
((adjustedCursorIndex >= contextBoundaries.keyContext.start &&
adjustedCursorIndex <= contextBoundaries.keyContext.end) ||
adjustedCursorIndex === contextBoundaries.keyContext.end + 1);
((cursorIndex >= contextBoundaries.keyContext.start &&
cursorIndex <= contextBoundaries.keyContext.end) ||
cursorIndex === contextBoundaries.keyContext.end + 1);
const isInNegationBoundary =
contextBoundaries.negationContext &&
((adjustedCursorIndex >= contextBoundaries.negationContext.start &&
adjustedCursorIndex <= contextBoundaries.negationContext.end) ||
adjustedCursorIndex === contextBoundaries.negationContext.end + 1);
((cursorIndex >= contextBoundaries.negationContext.start &&
cursorIndex <= contextBoundaries.negationContext.end) ||
cursorIndex === contextBoundaries.negationContext.end + 1);
const isInOperatorBoundary =
contextBoundaries.operatorContext &&
((adjustedCursorIndex >= contextBoundaries.operatorContext.start &&
adjustedCursorIndex <= contextBoundaries.operatorContext.end) ||
adjustedCursorIndex === contextBoundaries.operatorContext.end + 1);
((cursorIndex >= contextBoundaries.operatorContext.start &&
cursorIndex <= contextBoundaries.operatorContext.end) ||
cursorIndex === contextBoundaries.operatorContext.end + 1);
const isInValueBoundary =
contextBoundaries.valueContext &&
((adjustedCursorIndex >= contextBoundaries.valueContext.start &&
adjustedCursorIndex <= contextBoundaries.valueContext.end) ||
adjustedCursorIndex === contextBoundaries.valueContext.end + 1);
((cursorIndex >= contextBoundaries.valueContext.start &&
cursorIndex <= contextBoundaries.valueContext.end) ||
cursorIndex === contextBoundaries.valueContext.end + 1);
const isInConjunctionBoundary =
contextBoundaries.conjunctionContext &&
((adjustedCursorIndex >= contextBoundaries.conjunctionContext.start &&
adjustedCursorIndex <= contextBoundaries.conjunctionContext.end) ||
adjustedCursorIndex === contextBoundaries.conjunctionContext.end + 1);
((cursorIndex >= contextBoundaries.conjunctionContext.start &&
cursorIndex <= contextBoundaries.conjunctionContext.end) ||
cursorIndex === contextBoundaries.conjunctionContext.end + 1);
// Check for bracket list context (used for IN operator values)
const isInBracketListBoundary =
contextBoundaries.bracketContext &&
contextBoundaries.bracketContext.isForList &&
adjustedCursorIndex >= contextBoundaries.bracketContext.start &&
adjustedCursorIndex <= contextBoundaries.bracketContext.end + 1;
cursorIndex >= contextBoundaries.bracketContext.start &&
cursorIndex <= contextBoundaries.bracketContext.end + 1;
// Check for general parenthesis context (not for IN operator lists)
const isInParenthesisBoundary =
contextBoundaries.bracketContext &&
!contextBoundaries.bracketContext.isForList &&
adjustedCursorIndex >= contextBoundaries.bracketContext.start &&
adjustedCursorIndex <= contextBoundaries.bracketContext.end + 1;
cursorIndex >= contextBoundaries.bracketContext.start &&
cursorIndex <= 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 &&
adjustedCursorIndex === contextBoundaries.bracketContext.end + 2 &&
cursorIndex === contextBoundaries.bracketContext.end + 2 &&
query[contextBoundaries.bracketContext.end + 1] === ' ';
// If cursor is within a specific context boundary, this takes precedence
@ -741,8 +682,8 @@ export function getQueryContextAtCursor(
return {
tokenType: -1,
text: '',
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: '',
isInKey: isInKeyBoundary || false,
isInNegation: isInNegationBoundary || false,
@ -770,7 +711,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 (adjustedCursorIndex >= input.length && allTokens.length > 0) {
if (cursorIndex >= input.length && allTokens.length > 0) {
const lastRealToken = allTokens
.filter((t) => t.type !== FilterQueryLexer.EOF)
.pop();
@ -789,10 +730,7 @@ export function getQueryContextAtCursor(
}
// FIXED: Check if cursor is within token bounds (inclusive) or exactly at the end
if (
token.start <= adjustedCursorIndex &&
adjustedCursorIndex <= token.stop + 1
) {
if (token.start <= cursorIndex && cursorIndex <= token.stop + 1) {
exactToken = token;
previousToken = i > 0 ? allTokens[i - 1] : null;
nextToken = i < allTokens.length - 1 ? allTokens[i + 1] : null;
@ -812,10 +750,7 @@ export function getQueryContextAtCursor(
continue;
}
if (
current.stop + 1 < adjustedCursorIndex &&
adjustedCursorIndex < next.start
) {
if (current.stop + 1 < cursorIndex && cursorIndex < next.start) {
previousToken = current;
nextToken = next;
break;
@ -829,8 +764,8 @@ export function getQueryContextAtCursor(
return {
tokenType: -1,
text: '',
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: '',
isInKey: true, // Default to key context when input is empty
isInNegation: false,
@ -859,8 +794,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: false,
isInNegation: false,
@ -880,8 +815,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: false,
isInNegation: false,
@ -904,8 +839,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: false,
isInNegation: false,
@ -929,8 +864,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: false,
isInNegation: false,
@ -953,8 +888,8 @@ export function getQueryContextAtCursor(
return {
tokenType: lastTokenBeforeCursor.type,
text: lastTokenBeforeCursor.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: lastTokenBeforeCursor.text,
isInKey: true, // After conjunction + space, should be key context
isInNegation: false,
@ -972,7 +907,7 @@ export function getQueryContextAtCursor(
// FIXED: Consider the case where the cursor is at the end of a token
// with no space yet (user is actively typing)
if (exactToken && adjustedCursorIndex === exactToken.stop + 1) {
if (exactToken && cursorIndex === exactToken.stop + 1) {
const tokenContext = determineTokenContext(exactToken, input);
// When the cursor is at the end of a token, return the current token context
@ -1047,8 +982,8 @@ export function getQueryContextAtCursor(
return {
tokenType: previousToken.type,
text: previousToken.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: previousToken.text,
isInKey: false,
isInNegation: false,
@ -1070,8 +1005,8 @@ export function getQueryContextAtCursor(
return {
tokenType: previousToken.type,
text: previousToken.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: previousToken.text,
isInKey: false,
isInNegation: false,
@ -1091,8 +1026,8 @@ export function getQueryContextAtCursor(
return {
tokenType: previousToken.type,
text: previousToken.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: previousToken.text,
isInKey: false,
isInNegation: false,
@ -1114,8 +1049,8 @@ export function getQueryContextAtCursor(
return {
tokenType: previousToken.type,
text: previousToken.text,
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: previousToken.text,
isInKey: true, // After conjunction, progress back to key context
isInNegation: false,
@ -1135,8 +1070,8 @@ export function getQueryContextAtCursor(
return {
tokenType: -1,
text: '',
start: adjustedCursorIndex,
stop: adjustedCursorIndex,
start: cursorIndex,
stop: cursorIndex,
currentToken: '',
isInKey: true,
isInNegation: false,
@ -1188,11 +1123,8 @@ 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 = normalizedQuery || '';
const input = query || '';
const chars = CharStreams.fromString(input);
const lexer = new FilterQueryLexer(chars);