diff --git a/packages/ui/src/components/terminal/suggestion.ts b/packages/ui/src/components/terminal/suggestion.ts index 06aa5ea..278c89c 100644 --- a/packages/ui/src/components/terminal/suggestion.ts +++ b/packages/ui/src/components/terminal/suggestion.ts @@ -9,8 +9,7 @@ import { import { ReadonlyDeep } from "@rbxts/centurion/out/shared/util/data"; import { Players } from "@rbxts/services"; import { ArgumentSuggestion, CommandSuggestion } from "../../types"; - -const MAX_OTHER_SUGGESTIONS = 3; +import { containsSpace, getQuoteChar } from "../../utils/string"; export interface SingleArgument { kind: "single"; @@ -28,33 +27,42 @@ export interface ListArgument { export type Argument = SingleArgument | ListArgument; -function formatText(text: string) { - return text.match("%s").isEmpty() ? text : `"${text}"`; -} - function getMatches( strings: string[], text?: string, ): [number, string, number][] { if (text === undefined) { - return strings.sort().map((str, i) => [i, str, str.size()]); + return strings.sort().map((str, i) => { + const formatted = containsSpace(str) ? `"${str}"` : str; + return [i, formatted, formatted.size()]; + }); } const textLower = text.lower(); const textEndIndex = text.size(); - return strings - .mapFiltered<[number, string, number] | undefined>((str, i) => { - const part = str.lower().sub(0, textEndIndex); - if (part === textLower) return [i, str, str.size()]; - }) - .sort((a, b) => a[1] < b[1]); + const quoteChar = getQuoteChar(text); + const quoted = quoteChar !== undefined; + + const results: [number, string, number][] = []; + for (const i of $range(0, strings.size() - 1)) { + let str = strings[i]; + if (quoted) { + str = `${quoteChar}${str}${quoteChar}`; + } else if (containsSpace(str)) { + str = `"${str}"`; + } + + const part = str.lower().sub(0, textEndIndex); + if (part === textLower) { + results.push([i, str, str.size()]); + } + } + return results.sort((a, b) => a[1] < b[1]); } export function getArgumentSuggestion(arg: Argument, textPart?: string) { const suggestions = - arg.options.suggestions !== undefined - ? arg.options.suggestions.map(formatText) - : []; + arg.options.suggestions !== undefined ? [...arg.options.suggestions] : []; const singleArg = arg.kind === "single"; const typeSuggestions = singleArg @@ -62,7 +70,7 @@ export function getArgumentSuggestion(arg: Argument, textPart?: string) { : arg.type.suggestions?.(arg.input, Players.LocalPlayer); if (typeSuggestions !== undefined) { for (const text of typeSuggestions) { - suggestions.push(formatText(text)); + suggestions.push(text); } } @@ -118,7 +126,7 @@ export function getCommandSuggestion( return { type: "command", title: firstPath.tail(), - others: sortedPaths.map(([, str]) => formatText(str)), + others: sortedPaths.map(([, str]) => str), description: mainData.description, shortcuts: (mainData as CommandOptions).shortcuts, }; diff --git a/packages/ui/src/components/terminal/terminal.tsx b/packages/ui/src/components/terminal/terminal.tsx index 90f10a7..accc3ba 100644 --- a/packages/ui/src/components/terminal/terminal.tsx +++ b/packages/ui/src/components/terminal/terminal.tsx @@ -30,7 +30,6 @@ import { TerminalTextField } from "./terminal-text-field"; const MAX_HEIGHT = HISTORY_TEXT_SIZE * 10; const TEXT_FIELD_HEIGHT = 40; -const START_QUOTE_PATTERN = `^(['"])`; const TRAILING_SPACE_PATTERN = "(%s+)$"; export function Terminal() { diff --git a/packages/ui/src/store.ts b/packages/ui/src/store.ts index 25a54d9..a4c46dd 100644 --- a/packages/ui/src/store.ts +++ b/packages/ui/src/store.ts @@ -3,6 +3,7 @@ import { splitString } from "@rbxts/centurion/out/shared/util/string"; import { atom, computed } from "@rbxts/charm"; import { DEFAULT_INTERFACE_OPTIONS } from "./constants/options"; import { Suggestion } from "./types"; +import { isQuoteEnded, isQuoteStarted } from "./utils/string"; export const interfaceVisible = atom(false); export const interfaceOptions = atom(DEFAULT_INTERFACE_OPTIONS); @@ -23,9 +24,10 @@ export const atNextPart = computed(() => { const parts = terminalTextParts(); const textPart = parts[parts.size() - 1] as string | undefined; - const startQuote = textPart?.match(`^(['"])`); - const endQuote = textPart?.match(`(['"])%s*$`); - const quoted = startQuote !== undefined && endQuote === undefined; + let quoted = false; + if (textPart !== undefined) { + quoted = isQuoteStarted(textPart) && !isQuoteEnded(textPart); + } return terminalText().sub(-1) === " " && !quoted; }); export const terminalTextValid = atom(false); diff --git a/packages/ui/src/utils/string.ts b/packages/ui/src/utils/string.ts new file mode 100644 index 0000000..ee42f4b --- /dev/null +++ b/packages/ui/src/utils/string.ts @@ -0,0 +1,22 @@ +const START_QUOTE_PATTERN = `^(['"])`; +const END_QUOTE_PATTERN = `(['"])%s*$`; + +export function containsSpace(text: string) { + return !text.match("%s").isEmpty(); +} + +export function stripQuotes(text: string) { + return text.gsub(START_QUOTE_PATTERN, "")[0].gsub(END_QUOTE_PATTERN, "")[0]; +} + +export function getQuoteChar(text: string) { + return text.match(START_QUOTE_PATTERN)[0] as string; +} + +export function isQuoteStarted(text: string) { + return text.match(START_QUOTE_PATTERN)[0] !== undefined; +} + +export function isQuoteEnded(text: string) { + return text.match(END_QUOTE_PATTERN)[0] !== undefined; +}