-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ES|QL] Support replacement range #190465
Changes from all commits
de7fabf
7bad51a
6ce8c7a
08d5594
23995d3
50235bb
ffd29ce
51ac38f
70c8313
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -88,6 +88,7 @@ import { | |
import { ESQLCallbacks } from '../shared/types'; | ||
import { | ||
getFunctionsToIgnoreForStats, | ||
getOverlapRange, | ||
getParamAtPosition, | ||
getQueryForFields, | ||
getSourcesFromCommands, | ||
|
@@ -656,31 +657,52 @@ async function getExpressionSuggestionsByType( | |
} | ||
// Suggest fields or variables | ||
if (argDef.type === 'column' || argDef.type === 'any') { | ||
// ... | <COMMAND> <suggest> | ||
if ((!nodeArg || isNewExpression) && !endsWithNot) { | ||
suggestions.push( | ||
...(await getFieldsOrFunctionsSuggestions( | ||
argDef.innerTypes ?? ['any'], | ||
command.name, | ||
option?.name, | ||
getFieldsByType, | ||
{ | ||
// TODO instead of relying on canHaveAssignments and other command name checks | ||
// we should have a more generic way to determine if a command can have functions. | ||
// I think it comes down to the definition of 'column' since 'any' should always | ||
// include functions. | ||
functions: canHaveAssignments || command.name === 'sort', | ||
fields: !argDef.constantOnly, | ||
variables: anyVariables, | ||
literals: argDef.constantOnly, | ||
}, | ||
{ | ||
ignoreFields: isNewExpression | ||
? command.args.filter(isColumnItem).map(({ name }) => name) | ||
: [], | ||
} | ||
)) | ||
const fieldSuggestions = await getFieldsOrFunctionsSuggestions( | ||
argDef.innerTypes || ['any'], | ||
command.name, | ||
option?.name, | ||
getFieldsByType, | ||
{ | ||
// TODO instead of relying on canHaveAssignments and other command name checks | ||
// we should have a more generic way to determine if a command can have functions. | ||
// I think it comes down to the definition of 'column' since 'any' should always | ||
// include functions. | ||
functions: canHaveAssignments || command.name === 'sort', | ||
fields: !argDef.constantOnly, | ||
variables: anyVariables, | ||
literals: argDef.constantOnly, | ||
}, | ||
{ | ||
ignoreFields: isNewExpression | ||
? command.args.filter(isColumnItem).map(({ name }) => name) | ||
: [], | ||
} | ||
); | ||
|
||
/** | ||
* @TODO — this string manipulation is crude and can't support all cases | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we make an issue for this todo to keep track or is that already existing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. There is no issue. But, using the AST more is a key tenant of my vision for a re-architecture of this code. In other words, I am tracking this but I think an RFC/architecture document is the best first step here. From that, we can create more specific issues. |
||
* Checking for a partial word and computing the replacement range should | ||
* really be done using the AST node, but we'll have to refactor further upstream | ||
* to make that available. This is a quick fix to support the most common case. | ||
*/ | ||
const words = innerText.split(/\s+/); | ||
const lastWord = words[words.length - 1]; | ||
if (lastWord !== '') { | ||
// ... | <COMMAND> <word><suggest> | ||
suggestions.push( | ||
...fieldSuggestions.map((suggestion) => ({ | ||
...suggestion, | ||
rangeToReplace: { | ||
start: innerText.length - lastWord.length + 1, | ||
end: innerText.length, | ||
}, | ||
})) | ||
); | ||
} else { | ||
// ... | <COMMAND> <suggest> | ||
suggestions.push(...fieldSuggestions); | ||
} | ||
} | ||
} | ||
if (argDef.type === 'function' || argDef.type === 'any') { | ||
|
@@ -784,6 +806,7 @@ async function getExpressionSuggestionsByType( | |
const nodeArgType = extractFinalTypeFromArg(nodeArg, references); | ||
suggestions.push( | ||
...(await getBuiltinFunctionNextArgument( | ||
innerText, | ||
command, | ||
option, | ||
argDef, | ||
|
@@ -859,7 +882,7 @@ async function getExpressionSuggestionsByType( | |
// i.e. ... | <COMMAND> field >= <suggest> | ||
// i.e. ... | <COMMAND> field > 0 <suggest> | ||
// i.e. ... | <COMMAND> field + otherN <suggest> | ||
|
||
// "FROM a | WHERE doubleField IS NOT N" | ||
if (nodeArgType) { | ||
if (isFunctionItem(nodeArg)) { | ||
if (nodeArg.name === 'not') { | ||
|
@@ -879,6 +902,7 @@ async function getExpressionSuggestionsByType( | |
} else { | ||
suggestions.push( | ||
...(await getBuiltinFunctionNextArgument( | ||
innerText, | ||
command, | ||
option, | ||
argDef, | ||
|
@@ -988,6 +1012,7 @@ async function getExpressionSuggestionsByType( | |
} | ||
|
||
async function getBuiltinFunctionNextArgument( | ||
queryText: string, | ||
command: ESQLCommand, | ||
option: ESQLCommandOption | undefined, | ||
argDef: { type: string }, | ||
|
@@ -1072,7 +1097,16 @@ async function getBuiltinFunctionNextArgument( | |
} | ||
} | ||
} | ||
return suggestions; | ||
return suggestions.map<SuggestionRawDefinition>((s) => { | ||
const overlap = getOverlapRange(queryText, s.text); | ||
return { | ||
...s, | ||
rangeToReplace: { | ||
start: overlap.start, | ||
end: overlap.end, | ||
}, | ||
}; | ||
}); | ||
} | ||
|
||
function pushItUpInTheList(suggestions: SuggestionRawDefinition[], shouldPromote: boolean) { | ||
|
@@ -1636,6 +1670,7 @@ async function getOptionArgsSuggestions( | |
if (isFunctionItem(nodeArg) && !isFunctionArgComplete(nodeArg, references).complete) { | ||
suggestions.push( | ||
...(await getBuiltinFunctionNextArgument( | ||
innerText, | ||
command, | ||
option, | ||
{ type: argDef?.type || 'any' }, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not seeing this behavior in the editor with
from kibana_sample_data_flights | where AvgTicketPrice is n
. Makes sense that it suggestsIS NOT NULL
andIS NULL
where the cursor is but why the rest?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a choice: make it so that the range only gets added for
IS NOT NULL
andIS NULL
or just add it in a generic way for all.I opted for the latter because
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. Thanks for the context!