diff --git a/changelogs/fragments/7991.yml b/changelogs/fragments/7991.yml new file mode 100644 index 000000000000..f60b36710c5e --- /dev/null +++ b/changelogs/fragments/7991.yml @@ -0,0 +1,2 @@ +feat: +- Keep Autocomplete suggestion window open and put user hints below the suggestion window ([#7991](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7991)) \ No newline at end of file diff --git a/src/plugins/data/public/antlr/dql/code_completion.test.ts b/src/plugins/data/public/antlr/dql/code_completion.test.ts index 7a4efe719cc1..547ccc615e31 100644 --- a/src/plugins/data/public/antlr/dql/code_completion.test.ts +++ b/src/plugins/data/public/antlr/dql/code_completion.test.ts @@ -174,11 +174,11 @@ const testingIndex = ({ } as unknown) as IndexPattern; const booleanOperatorSuggestions = [ - { text: 'or', type: 17, detail: 'Keyword' }, - { text: 'and', type: 17, detail: 'Keyword' }, + { text: 'or', type: 11, detail: 'Operator', insertText: 'or ' }, + { text: 'and', type: 11, detail: 'Operator', insertText: 'and ' }, ]; -const notOperatorSuggestion = { text: 'not', type: 17, detail: 'Keyword' }; +const notOperatorSuggestion = { text: 'not', type: 11, detail: 'Operator', insertText: 'not ' }; const fieldNameSuggestions: Array<{ text: string; @@ -211,27 +211,31 @@ const carrierValues = [ ]; const allCarrierValueSuggestions = [ - { text: 'Logstash Airways', type: 13, detail: 'Value' }, - { text: 'BeatsWest', type: 13, detail: 'Value' }, + { text: 'Logstash Airways', type: 13, detail: 'Value', insertText: 'Logstash Airways ' }, + { text: 'BeatsWest', type: 13, detail: 'Value', insertText: 'BeatsWest ' }, { text: 'OpenSearch Dashboards Airlines', type: 13, detail: 'Value', + insertText: 'OpenSearch Dashboards Airlines ', }, - { text: 'OpenSearch-Air', type: 13, detail: 'Value' }, + { text: 'OpenSearch-Air', type: 13, detail: 'Value', insertText: 'OpenSearch-Air ' }, ]; const carrierWithNotSuggestions = allCarrierValueSuggestions.concat(notOperatorSuggestion); -const logCarrierValueSuggestion = [{ text: 'Logstash Airways', type: 13, detail: 'Value' }]; +const logCarrierValueSuggestion = [ + { text: 'Logstash Airways', type: 13, detail: 'Value', insertText: 'Logstash Airways ' }, +]; const openCarrierValueSuggestion = [ { text: 'OpenSearch Dashboards Airlines', type: 13, detail: 'Value', + insertText: 'OpenSearch Dashboards Airlines ', }, - { text: 'OpenSearch-Air', type: 13, detail: 'Value' }, + { text: 'OpenSearch-Air', type: 13, detail: 'Value', insertText: 'OpenSearch-Air ' }, ]; const addPositionToValue = (vals: any, start: number, end: number) => diff --git a/src/plugins/data/public/antlr/dql/code_completion.ts b/src/plugins/data/public/antlr/dql/code_completion.ts index a14310f56979..5968d2408c11 100644 --- a/src/plugins/data/public/antlr/dql/code_completion.ts +++ b/src/plugins/data/public/antlr/dql/code_completion.ts @@ -135,7 +135,7 @@ export const getSuggestions = async ({ // check to see if field rule is a candidate. if so, suggest field names if (candidates.rules.has(DQLParser.RULE_field)) { - completions.push(...fetchFieldSuggestions(indexPattern, (f) => `${f}: `)); + completions.push(...fetchFieldSuggestions(indexPattern, (f: any) => `${f}: `)); } interface FoundLastValue { @@ -247,12 +247,15 @@ export const getSuggestions = async ({ cursorLine, cursorColumn + 1 ), + insertText: `${val} `, }; }) ); } } + const dqlOperators = new Set([DQLParser.AND, DQLParser.OR, DQLParser.NOT]); + // suggest other candidates, mainly keywords [...candidates.tokens.keys()].forEach((token: number) => { // ignore identifier, already handled with field rule @@ -261,11 +264,19 @@ export const getSuggestions = async ({ } const tokenSymbolName = parser.vocabulary.getSymbolicName(token)?.toLowerCase(); + if (tokenSymbolName) { + let type = monaco.languages.CompletionItemKind.Keyword; + let detail = SuggestionItemDetailsTags.Keyword; + if (dqlOperators.has(token)) { + type = monaco.languages.CompletionItemKind.Operator; + detail = SuggestionItemDetailsTags.Operator; + } completions.push({ text: tokenSymbolName, - type: monaco.languages.CompletionItemKind.Keyword, - detail: SuggestionItemDetailsTags.Keyword, + type, + detail, + insertText: `${tokenSymbolName} `, }); } }); diff --git a/src/plugins/data/public/antlr/shared/constants.ts b/src/plugins/data/public/antlr/shared/constants.ts index bd38cedc3822..16fe97b737a0 100644 --- a/src/plugins/data/public/antlr/shared/constants.ts +++ b/src/plugins/data/public/antlr/shared/constants.ts @@ -8,5 +8,6 @@ export const enum SuggestionItemDetailsTags { Keyword = 'Keyword', AggregateFunction = 'Aggregate Function', Value = 'Value', + Operator = 'Operator', } export const quotesRegex = /^'(.*)'$/; diff --git a/src/plugins/data/public/ui/query_editor/_query_editor.scss b/src/plugins/data/public/ui/query_editor/_query_editor.scss index 770160de8c1d..3e9a6ac61c76 100644 --- a/src/plugins/data/public/ui/query_editor/_query_editor.scss +++ b/src/plugins/data/public/ui/query_editor/_query_editor.scss @@ -199,3 +199,23 @@ border: none; } } + +.suggest-widget { + position: relative; + + &.visible::after { + position: absolute; + height: auto; + bottom: auto; + left: 0; + width: 100%; + background-color: $euiColorLightestShade; + border: $euiBorderThin; + padding: $euiSizeXS; + text-align: left; + color: $euiColorDarkShade; + font-size: $euiFontSizeXS; + content: "Tab to insert, ESC to close window"; + display: block; + } +} diff --git a/src/plugins/data/public/ui/query_editor/editors/default_editor/index.tsx b/src/plugins/data/public/ui/query_editor/editors/default_editor/index.tsx index b7bb11c18a8d..764b25d4b9ac 100644 --- a/src/plugins/data/public/ui/query_editor/editors/default_editor/index.tsx +++ b/src/plugins/data/public/ui/query_editor/editors/default_editor/index.tsx @@ -55,6 +55,7 @@ export const DefaultInput: React.FC = ({ }} suggestionProvider={{ provideCompletionItems, + triggerCharacters: [' '], }} languageConfiguration={{ autoClosingPairs: [ @@ -65,6 +66,7 @@ export const DefaultInput: React.FC = ({ { open: "'", close: "'" }, ], }} + triggerSuggestOnFocus={true} />
{footerItems && ( diff --git a/src/plugins/data/public/ui/query_editor/editors/shared.tsx b/src/plugins/data/public/ui/query_editor/editors/shared.tsx index 6608b95042de..32cb0aa18a59 100644 --- a/src/plugins/data/public/ui/query_editor/editors/shared.tsx +++ b/src/plugins/data/public/ui/query_editor/editors/shared.tsx @@ -95,6 +95,7 @@ export const SingleLineInput: React.FC = ({ }} suggestionProvider={{ provideCompletionItems, + triggerCharacters: [' '], }} languageConfiguration={{ autoClosingPairs: [ @@ -108,6 +109,7 @@ export const SingleLineInput: React.FC = ({ }, ], }} + triggerSuggestOnFocus={true} />
diff --git a/src/plugins/data/public/ui/query_editor/query_editor.tsx b/src/plugins/data/public/ui/query_editor/query_editor.tsx index dc75d0be3d7f..0abe9796bbf9 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor.tsx @@ -309,6 +309,7 @@ export default class QueryEditorUI extends Component { insertText: s.insertText ?? s.text, range: s.replacePosition ?? defaultRange, detail: s.detail, + command: { id: 'editor.action.triggerSuggest', title: 'Trigger Next Suggestion' }, }; }) : [], diff --git a/src/plugins/opensearch_dashboards_react/public/code_editor/code_editor.tsx b/src/plugins/opensearch_dashboards_react/public/code_editor/code_editor.tsx index 8996297775c9..137f369c9b9c 100644 --- a/src/plugins/opensearch_dashboards_react/public/code_editor/code_editor.tsx +++ b/src/plugins/opensearch_dashboards_react/public/code_editor/code_editor.tsx @@ -108,6 +108,11 @@ export interface Props { * Should the editor use the dark theme */ useDarkTheme?: boolean; + + /** + * Whether the suggestion widget/window will be triggered upon clicking into the editor + */ + triggerSuggestOnFocus?: boolean; } export class CodeEditor extends React.Component { @@ -141,6 +146,12 @@ export class CodeEditor extends React.Component { if (this.props.editorDidMount) { this.props.editorDidMount(editor); } + + if (this.props.triggerSuggestOnFocus) { + editor.onDidFocusEditorWidget(() => { + editor.trigger('keyboard', 'editor.action.triggerSuggest', {}); + }); + } }; render() {