[ES|QL] Fixes the suggestion problem in where for multiline queries (#213240)

## Summary

Closes https://github.com/elastic/kibana/issues/213323

This is the attempt to fix this bug:

```
FROM kibana_sample_data_logs
| WHERE event.dataset == # cursor on this line
| LIMIT 10
```

In main the suggestions do not trigger. The problem is that the range is
completely wrong. The lineNumber is 3 while it should be 2 and the start
and end columns are also wrong.


This PR attempts to fix it (hopefully).

![meow](https://github.com/user-attachments/assets/2741891e-5186-477b-900f-ef42bb3371da)


### Checklist

- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Stratoula Kalafateli 2025-03-10 13:02:21 +01:00 committed by GitHub
parent da0480bde9
commit 6f831770fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 78 additions and 13 deletions

View file

@ -411,7 +411,7 @@ describe('WHERE <expression>', () => {
detail: 'Click to create',
command: { id: 'esql.control.values.create', title: 'Click to create' },
sortText: '11',
rangeToReplace: { start: 31, end: 31 },
rangeToReplace: { start: 30, end: 30 },
});
});
@ -439,7 +439,7 @@ describe('WHERE <expression>', () => {
detail: 'Named parameter',
command: undefined,
sortText: '11A',
rangeToReplace: { start: 31, end: 31 },
rangeToReplace: { start: 30, end: 30 },
});
});
});

View file

@ -608,7 +608,7 @@ export async function getSuggestionsToRightOfOperatorExpression({
) {
suggestions.push(listCompleteItem);
} else {
const finalType = leftArgType || leftArgType || 'any';
const finalType = leftArgType || 'any';
const supportedTypes = getSupportedTypesForBinaryOperators(fnDef, finalType as string);
// this is a special case with AND/OR
@ -671,12 +671,11 @@ export async function getSuggestionsToRightOfOperatorExpression({
}
return suggestions.map<SuggestionRawDefinition>((s) => {
const overlap = getOverlapRange(queryText, s.text);
const offset = overlap.start === overlap.end ? 1 : 0;
return {
...s,
rangeToReplace: {
start: overlap.start + offset,
end: overlap.end + offset,
start: overlap.start,
end: overlap.end,
},
};
});

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { offsetRangeToMonacoRange } from './utils';
describe('offsetRangeToMonacoRange', () => {
test('should convert offset range to monaco range when the cursor is not at the end', () => {
const expression = 'FROM test | WHERE test == | LIMIT 1';
const range = { start: 26, end: 26 };
const monacoRange = offsetRangeToMonacoRange(expression, range);
expect(monacoRange).toEqual({
startColumn: 26,
endColumn: 26,
startLineNumber: 1,
endLineNumber: 1,
});
});
test('should convert offset range to monaco range when the cursor is at the end', () => {
const expression = 'FROM test | WHERE test == 1 | LIMIT 1';
const range = { start: 37, end: 37 };
const monacoRange = offsetRangeToMonacoRange(expression, range);
expect(monacoRange).toEqual({
startColumn: 37,
endColumn: 37,
startLineNumber: 1,
endLineNumber: 1,
});
});
test('should convert offset range to monaco range for multiple lines query when the cursor is not at the end', () => {
const expression = 'FROM test \n| WHERE test == \n| LIMIT 1';
const range = { start: 27, end: 27 };
const monacoRange = offsetRangeToMonacoRange(expression, range);
expect(monacoRange).toEqual({
startColumn: 16,
endColumn: 16,
startLineNumber: 2,
endLineNumber: 2,
});
});
test('should convert offset range to monaco range for multiple lines query when the cursor is at the end', () => {
const expression = 'FROM test \n| WHERE test == \n| LIMIT 1';
const range = { start: 35, end: 35 };
const monacoRange = offsetRangeToMonacoRange(expression, range);
expect(monacoRange).toEqual({
startColumn: 7,
endColumn: 7,
startLineNumber: 3,
endLineNumber: 3,
});
});
});

View file

@ -47,22 +47,25 @@ export const offsetRangeToMonacoRange = (
let endLineNumber = 1;
let currentLine = 1;
for (let i = 0; i < expression.length; i++) {
if (expression[i] === '\n') {
currentLine++;
currentOffset = i + 1;
}
const hasMultipleLines = expression.includes('\n');
const offset = hasMultipleLines ? 1 : 0;
if (i === range.start) {
for (let i = 0; i < expression.length; i++) {
if (i === range.start - offset) {
startLineNumber = currentLine;
startColumn = i - currentOffset;
}
if (i === range.end) {
if (i === range.end - offset) {
endLineNumber = currentLine;
endColumn = i - currentOffset;
break; // No need to continue once we find the end position
}
if (expression[i] === '\n') {
currentLine++;
currentOffset = i;
}
}
// Handle the case where the start offset is past the end of the string