Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions redisinsight/ui/src/pages/search/utils/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,8 @@ export const findArgByToken = (list: SearchCommand[], arg: string): Maybe<Search
? cArg.arguments?.some((oneOfArg: SearchCommand) => oneOfArg?.token?.toLowerCase() === arg?.toLowerCase())
: cArg.arguments?.[0]?.token?.toLowerCase() === arg.toLowerCase()))

export const isCompositeArgument = (arg: string, prevArg?: string) =>
COMPOSITE_ARGS.includes([prevArg?.toUpperCase(), arg?.toUpperCase()].join(' '))
export const isCompositeArgument = (arg: string, prevArg?: string, args: string[] = []) =>
args.includes([prevArg?.toUpperCase(), arg?.toUpperCase()].join(' '))

export const generateDetail = (command: Maybe<SearchCommand>) => {
if (!command) return ''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,11 @@ import cx from 'classnames'
import MonacoEditor, { monaco as monacoEditor } from 'react-monaco-editor'
import { useParams } from 'react-router-dom'

import {
Theme,
MonacoLanguage,
DSLNaming,
IRedisCommand,
} from 'uiSrc/constants'
import { DSLNaming, ICommandTokenType, IRedisCommand, MonacoLanguage, Theme, } from 'uiSrc/constants'
import {
actionTriggerParameterHints,
createSyntaxWidget,
decoration,
findArgIndexByCursor,
findCompleteQuery,
getMonacoAction,
IMonacoQuery,
Expand All @@ -28,35 +22,26 @@ import { ThemeContext } from 'uiSrc/contexts/themeContext'
import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands'
import { IEditorMount, ISnippetController } from 'uiSrc/pages/workbench/interfaces'
import { CommandExecutionUI, RedisResponseBuffer } from 'uiSrc/slices/interfaces'
import { RunQueryMode, ResultsMode } from 'uiSrc/slices/interfaces/workbench'
import { ResultsMode, RunQueryMode } from 'uiSrc/slices/interfaces/workbench'
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import { stopProcessing, workbenchResultsSelector } from 'uiSrc/slices/workbench/wb-results'
import DedicatedEditor from 'uiSrc/components/monaco-editor/components/dedicated-editor'
import { QueryActions, QueryTutorials } from 'uiSrc/components/query'

import {
addOwnTokenToArgs,
} from 'uiSrc/pages/workbench/utils/query'
import { addOwnTokenToArgs, findCurrentArgument, } from 'uiSrc/pages/workbench/utils/query'
import { getRange, getRediSearchSignutureProvider, } from 'uiSrc/pages/workbench/utils/monaco'
import { CursorContext } from 'uiSrc/pages/workbench/types'
import {
asSuggestionsRef,
getCommandsSuggestions,
isIndexComplete
} from 'uiSrc/pages/workbench/utils/suggestions'
import {
COMMANDS_TO_GET_INDEX_INFO,
EmptySuggestionsIds,
} from 'uiSrc/pages/workbench/constants'
import { asSuggestionsRef, getCommandsSuggestions, isIndexComplete } from 'uiSrc/pages/workbench/utils/suggestions'
import { COMMANDS_TO_GET_INDEX_INFO, COMPOSITE_ARGS, EmptySuggestionsIds, } from 'uiSrc/pages/workbench/constants'
import { useDebouncedEffect } from 'uiSrc/services'
import { fetchRedisearchInfoAction } from 'uiSrc/slices/browser/redisearch'
import { findSuggestionsByArg } from 'uiSrc/pages/workbench/utils/searchSuggestions'
import {
aroundQuotesRegExp,
argInQuotesRegExp,
aroundQuotesRegExp,
options,
SYNTAX_CONTEXT_ID,
SYNTAX_WIDGET_ID,
options,
TUTORIALS
} from './constants'
import styles from './styles.module.scss'
Expand Down Expand Up @@ -343,7 +328,7 @@ const Query = (props: Props) => {
return
}

const command = findCompleteQuery(model, e.position, REDIS_COMMANDS_SPEC, REDIS_COMMANDS_ARRAY)
const command = findCompleteQuery(model, e.position, REDIS_COMMANDS_SPEC, REDIS_COMMANDS_ARRAY, COMPOSITE_ARGS)
handleSuggestions(editor, command)
handleDslSyntax(e, command)
}
Expand Down Expand Up @@ -427,33 +412,30 @@ const Query = (props: Props) => {
return
}

const queryArgIndex = command.info?.arguments?.findIndex((arg) => arg.dsl) || -1
const cursorPosition = command.commandCursorPosition || 0
const { allArgs } = command || {}
if (!allArgs.length || queryArgIndex < 0) {
const isContainsDSL = command.info?.arguments?.some((arg) => arg.dsl)
if (!isContainsDSL) {
isWidgetEscaped.current = false
return
}

const argIndex = findArgIndexByCursor(allArgs, command.fullQuery, cursorPosition)
if (argIndex === null) {
isWidgetEscaped.current = false
return
}

const queryArg = allArgs[argIndex]
const argDSL = command.info?.arguments?.[argIndex]?.dsl || ''
const [beforeOffsetArgs, [currentOffsetArg]] = command.args
const foundArg = findCurrentArgument([{
...command.info,
type: ICommandTokenType.Block,
token: command.name,
arguments: command.info?.arguments
}], beforeOffsetArgs)

if (queryArgIndex === argIndex && argInQuotesRegExp.test(queryArg)) {
const DSL = foundArg?.stopArg?.dsl
if (DSL && argInQuotesRegExp.test(currentOffsetArg)) {
if (isWidgetEscaped.current) return
const lang = DSLNaming[argDSL] ?? null

const lang = DSLNaming[DSL] ?? null
lang && showSyntaxWidget(editor, e.position, lang)
selectedArg.current = queryArg
syntaxCommand.current = {
...command,
lang: argDSL,
argToReplace: queryArg
}
selectedArg.current = currentOffsetArg
syntaxCommand.current = { ...command, lang: DSL, argToReplace: currentOffsetArg }
} else {
isWidgetEscaped.current = false
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -714,14 +714,10 @@
"optional": true
},
{
"name": "queryword",
"type": "pure-token",
"name": "query",
"type": "token",
"token": "QUERY",
"expression": true
},
{
"name": "query",
"type": "string"
}
],
"since": "2.2.0",
Expand Down
113 changes: 10 additions & 103 deletions redisinsight/ui/src/pages/workbench/utils/query.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,10 @@
/* eslint-disable no-continue */

import { isNumber, toNumber } from 'lodash'
import { findLastIndex, isNumber, toNumber } from 'lodash'
import { generateArgsNames, Maybe, Nullable } from 'uiSrc/utils'
import { CommandProvider, IRedisCommand, IRedisCommandTree, ICommandTokenType } from 'uiSrc/constants'
import { COMPOSITE_ARGS } from 'uiSrc/pages/workbench/constants'
import { ArgName, FoundCommandArgument } from '../types'

export const splitQueryByArgs = (query: string, position: number = 0) => {
const args: [string[], string[]] = [[], []]
let arg = ''
let inQuotes = false
let escapeNextChar = false
let quoteChar = ''
let isCursorInQuotes = false
let lastArg = ''
let argLeftOffset = 0
let argRightOffset = 0

const pushToProperTuple = (isAfterOffset: boolean, arg: string) => {
lastArg = arg
isAfterOffset ? args[1].push(arg) : args[0].push(arg)
}

const updateLastArgument = (isAfterOffset: boolean, arg: string) => {
const argsBySide = args[isAfterOffset ? 1 : 0]
argsBySide[argsBySide.length - 1] = `${argsBySide[argsBySide.length - 1]} ${arg}`
}

const updateArgOffsets = (left: number, right: number) => {
argLeftOffset = left
argRightOffset = right
}

for (let i = 0; i < query.length; i++) {
const char = query[i]
const isAfterOffset = i >= position + (inQuotes ? -1 : 0)

if (escapeNextChar) {
arg += char
escapeNextChar = !quoteChar
} else if (char === '\\') {
escapeNextChar = true
} else if (inQuotes) {
if (char === quoteChar) {
inQuotes = false
const argWithChat = arg + char

if (isAfterOffset && !argLeftOffset) {
updateArgOffsets(i - arg.length, i + 1)
}

if (isCompositeArgument(argWithChat, lastArg)) {
updateLastArgument(isAfterOffset, argWithChat)
} else {
pushToProperTuple(isAfterOffset, argWithChat)
}

arg = ''
} else {
arg += char
}
} else if (char === '"' || char === "'") {
inQuotes = true
quoteChar = char
arg += char
} else if (char === ' ' || char === '\n') {
if (arg.length > 0) {
if (isAfterOffset && !argLeftOffset) {
updateArgOffsets(i - arg.length, i)
}

if (isCompositeArgument(arg, lastArg)) {
updateLastArgument(isAfterOffset, arg)
} else {
pushToProperTuple(isAfterOffset, arg)
}

arg = ''
}
} else {
arg += char
}

if (i === position - 1) isCursorInQuotes = inQuotes
}

if (arg.length > 0) {
if (!argLeftOffset) updateArgOffsets(query.length - arg.length, query.length)
pushToProperTuple(true, arg)
}

const cursor = {
isCursorInQuotes,
prevCursorChar: query[position - 1]?.trim() || '',
nextCursorChar: query[position]?.trim() || '',
argLeftOffset,
argRightOffset
}

return { args, cursor }
}

export const findCurrentArgument = (
args: IRedisCommand[],
prev: string[],
Expand Down Expand Up @@ -340,7 +244,7 @@ export const getArgumentSuggestions = (
const isBlockHasParent = current?.arguments?.some(({ name }) => parent?.name && name === parent?.name)
const foundParent = isBlockHasParent ? { ...parent, parent: current } : (parent || current)

const isBlockComplete = !stopArgument && current?.name === lastArgument?.name
const isBlockComplete = !stopArgument && isPrevArgWasMandatory
const beforeMandatoryOptionalArgs = getAllRestArguments(foundParent, lastArgument, untilTokenArgs, isBlockComplete)
const requiredArgsLength = restNotFilledArgs.filter((arg) => !arg.optional).length

Expand Down Expand Up @@ -395,8 +299,14 @@ export const getAllRestArguments = (
skipLevel = false
) => {
const appendArgs: Array<IRedisCommand[]> = []
const currentLvlNextArgs = removeNotSuggestedArgs(

const currentToken = current?.type === ICommandTokenType.Block ? current?.arguments?.[0].token : current?.token
const lastTokenIndex = findLastIndex(
untilTokenArgs,
(arg) => arg?.toLowerCase() === currentToken?.toLowerCase()
)
const currentLvlNextArgs = removeNotSuggestedArgs(
untilTokenArgs.slice(lastTokenIndex > 0 ? lastTokenIndex : 0),
getRestArguments(current, stopArgument)
)

Expand Down Expand Up @@ -464,10 +374,7 @@ export const findArgByToken = (list: IRedisCommand[], arg: string): Maybe<IRedis
list.find((cArg) =>
(cArg.type === ICommandTokenType.OneOf
? cArg.arguments?.some((oneOfArg: IRedisCommand) => oneOfArg?.token?.toLowerCase() === arg?.toLowerCase())
: cArg.arguments?.[0]?.token?.toLowerCase() === arg.toLowerCase()))

export const isCompositeArgument = (arg: string, prevArg?: string) =>
COMPOSITE_ARGS.includes([prevArg?.toUpperCase(), arg?.toUpperCase()].join(' '))
: cArg.arguments?.[0]?.token?.toLowerCase() === arg?.toLowerCase()))

export const generateDetail = (command: Maybe<IRedisCommand>) => {
if (!command) return ''
Expand Down
47 changes: 24 additions & 23 deletions redisinsight/ui/src/pages/workbench/utils/searchSuggestions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { monaco as monacoEditor } from 'react-monaco-editor'
import { isNumber } from 'lodash'
import { IMonacoQuery, Nullable } from 'uiSrc/utils'
import { IMonacoQuery, Nullable, splitQueryByArgs } from 'uiSrc/utils'
import { CursorContext, FoundCommandArgument } from 'uiSrc/pages/workbench/types'
import { findCurrentArgument, splitQueryByArgs } from 'uiSrc/pages/workbench/utils/query'
import { findCurrentArgument } from 'uiSrc/pages/workbench/utils/query'
import { IRedisCommand } from 'uiSrc/constants'
import {
asSuggestionsRef,
Expand Down Expand Up @@ -49,28 +49,27 @@ export const findSuggestionsByArg = (
return handleFieldSuggestions(additionData.fields || [], foundArg, cursorContext.range)
}

if (foundArg?.stopArg?.token && !foundArg?.isBlocked) {
return handleCommonSuggestions(
command.fullQuery,
foundArg,
allArgs,
additionData.fields || [],
cursorContext,
isEscaped
)
}

const { indexes, fields } = additionData
switch (foundArg?.stopArg?.name) {
case DefinedArgumentName.index: {
return handleIndexSuggestions(
additionData.indexes || [],
command,
foundArg,
currentOffsetArg,
cursorContext.range
)
return handleIndexSuggestions(indexes, command, foundArg, currentOffsetArg, cursorContext)
}
case DefinedArgumentName.query: {
return handleQuerySuggestions(foundArg)
}
default: {
return handleCommonSuggestions(
command.fullQuery,
foundArg,
allArgs,
additionData.fields || [],
cursorContext,
isEscaped
)
return handleCommonSuggestions(command.fullQuery, foundArg, allArgs, fields, cursorContext, isEscaped)
}
}
}
Expand All @@ -88,11 +87,11 @@ const handleFieldSuggestions = (
}

const handleIndexSuggestions = (
indexes: any[],
indexes: any[] = [],
command: IMonacoQuery,
foundArg: FoundCommandArgument,
currentOffsetArg: Nullable<string>,
range: monacoEditor.IRange
cursorContext: CursorContext
) => {
const isIndex = indexes.length > 0
const helpWidget = { isOpen: isIndex, parent: command.info, currentArg: foundArg?.stopArg }
Expand All @@ -109,7 +108,7 @@ const handleIndexSuggestions = (
helpWidget.isOpen = !!currentOffsetArg

return {
suggestions: asSuggestionsRef(!currentOffsetArg ? getNoIndexesSuggestion(range) : [], true),
suggestions: asSuggestionsRef(!currentOffsetArg ? getNoIndexesSuggestion(cursorContext.range) : [], true),
helpWidget
}
}
Expand All @@ -127,7 +126,7 @@ const handleIndexSuggestions = (
&& currentCommand?.arguments?.[argumentIndex + 1]?.name === DefinedArgumentName.query

return {
suggestions: asSuggestionsRef(getIndexesSuggestions(indexes, range, isNextArgQuery)),
suggestions: asSuggestionsRef(getIndexesSuggestions(indexes, cursorContext.range, isNextArgQuery)),
helpWidget
}
}
Expand Down Expand Up @@ -171,11 +170,13 @@ const handleCommonSuggestions = (
value: string,
foundArg: Nullable<FoundCommandArgument>,
allArgs: string[],
fields: any[],
fields: any[] = [],
cursorContext: CursorContext,
isEscaped: boolean
) => {
if (foundArg?.stopArg?.expression) return handleExpressionSuggestions(value, foundArg, cursorContext)
if (foundArg?.stopArg?.expression && foundArg.isBlocked) {
return handleExpressionSuggestions(value, foundArg, cursorContext)
}

const { prevCursorChar, nextCursorChar, isCursorInQuotes } = cursorContext
const shouldHideSuggestions = isCursorInQuotes || nextCursorChar || (prevCursorChar && isEscaped)
Expand Down
Loading