diff options
Diffstat (limited to 'src/console')
-rw-r--r-- | src/console/actions/completion.ts | 243 | ||||
-rw-r--r-- | src/console/actions/console.ts | 17 | ||||
-rw-r--r-- | src/console/completion/actions.ts | 76 | ||||
-rw-r--r-- | src/console/completion/context.ts | 9 | ||||
-rw-r--r-- | src/console/completion/hooks.ts | 277 | ||||
-rw-r--r-- | src/console/completion/provider.tsx | 25 | ||||
-rw-r--r-- | src/console/completion/reducer.ts (renamed from src/console/reducers/completion.ts) | 51 | ||||
-rw-r--r-- | src/console/components/CommandPrompt.tsx | 219 | ||||
-rw-r--r-- | src/console/components/Console.tsx | 2 | ||||
-rw-r--r-- | src/console/reducers/console.ts | 3 |
10 files changed, 477 insertions, 445 deletions
diff --git a/src/console/actions/completion.ts b/src/console/actions/completion.ts deleted file mode 100644 index 2f6f82f..0000000 --- a/src/console/actions/completion.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { Command } from "../../shared/Command"; -import CompletionClient from "../clients/CompletionClient"; -import CompletionType from "../../shared/CompletionType"; -import Completions from "../Completions"; -import TabFlag from "../../shared/TabFlag"; - -const completionClient = new CompletionClient(); - -const commandDocs = { - [Command.Set]: "Set a value of the property", - [Command.Open]: "Open a URL or search by keywords in current tab", - [Command.TabOpen]: "Open a URL or search by keywords in new tab", - [Command.WindowOpen]: "Open a URL or search by keywords in new window", - [Command.Buffer]: "Select tabs by matched keywords", - [Command.BufferDelete]: "Close a certain tab matched by keywords", - [Command.BuffersDelete]: "Close all tabs matched by keywords", - [Command.Quit]: "Close the current tab", - [Command.QuitAll]: "Close all tabs", - [Command.AddBookmark]: "Add current page to bookmarks", - [Command.Help]: "Open Vim Vixen help in new tab", -}; - -const propertyDocs: { [key: string]: string } = { - hintchars: "hint characters on follow mode", - smoothscroll: "smooth scroll", - complete: "which are completed at the open page", - colorscheme: "color scheme of the console", -}; - -export const COMPLETION_START_COMPLETION = "console.start.completion"; -export const COMPLETION_SET_COMPLETIONS = "console.set.completions"; -export const COMPLETION_COMPLETION_NEXT = "completion.completion.next"; -export const COMPLETION_COMPLETION_PREV = "completion.completion.prev"; - -export interface CompletionStartCompletionAction { - type: typeof COMPLETION_START_COMPLETION; - completionTypes: CompletionType[]; -} - -export interface SetCompletionsAction { - type: typeof COMPLETION_SET_COMPLETIONS; - completions: Completions; - completionSource: string; -} - -export interface CompletionNextAction { - type: typeof COMPLETION_COMPLETION_NEXT; -} - -export interface CompletionPrevAction { - type: typeof COMPLETION_COMPLETION_PREV; -} - -export type CompletionAction = - | CompletionStartCompletionAction - | SetCompletionsAction - | CompletionNextAction - | CompletionPrevAction; -const startCompletion = async (): Promise<CompletionStartCompletionAction> => { - const completionTypes = await completionClient.getCompletionTypes(); - return { - type: COMPLETION_START_COMPLETION, - completionTypes, - }; -}; - -const getCommandCompletions = (text: string): SetCompletionsAction => { - const items = Object.entries(commandDocs) - .filter(([name]) => name.startsWith(text.trimLeft())) - .map(([name, doc]) => ({ - caption: name, - content: name, - url: doc, - })); - const completions = [ - { - name: "Console Command", - items, - }, - ]; - return { - type: COMPLETION_SET_COMPLETIONS, - completions, - completionSource: text, - }; -}; - -const getOpenCompletions = async ( - types: CompletionType[], - original: string, - command: Command, - query: string -): Promise<SetCompletionsAction> => { - const completions: Completions = []; - for (const type of types) { - switch (type) { - case CompletionType.SearchEngines: { - const items = await completionClient.requestSearchEngines(query); - if (items.length === 0) { - break; - } - completions.push({ - name: "Search Engines", - items: items.map((key) => ({ - caption: key.title, - content: command + " " + key.title, - })), - }); - break; - } - case CompletionType.History: { - const items = await completionClient.requestHistory(query); - if (items.length === 0) { - break; - } - completions.push({ - name: "History", - items: items.map((item) => ({ - caption: item.title, - content: command + " " + item.url, - url: item.url, - })), - }); - break; - } - case CompletionType.Bookmarks: { - const items = await completionClient.requestBookmarks(query); - if (items.length === 0) { - break; - } - completions.push({ - name: "Bookmarks", - items: items.map((item) => ({ - caption: item.title, - content: command + " " + item.url, - url: item.url, - })), - }); - break; - } - } - } - - return { - type: COMPLETION_SET_COMPLETIONS, - completions, - completionSource: original, - }; -}; - -const getTabCompletions = async ( - original: string, - command: Command, - query: string, - excludePinned: boolean -): Promise<SetCompletionsAction> => { - const items = await completionClient.requestTabs(query, excludePinned); - let completions: Completions = []; - if (items.length > 0) { - completions = [ - { - name: "Buffers", - items: items.map((item) => ({ - content: command + " " + item.url, - caption: `${item.index}: ${ - item.flag != TabFlag.None ? item.flag : " " - } ${item.title}`, - url: item.url, - icon: item.faviconUrl, - })), - }, - ]; - } - return { - type: COMPLETION_SET_COMPLETIONS, - completions, - completionSource: original, - }; -}; - -const getPropertyCompletions = async ( - original: string, - command: Command, - query: string -): Promise<SetCompletionsAction> => { - const properties = await completionClient.getProperties(); - const items = properties - .map((item) => { - const desc = propertyDocs[item.name] || ""; - if (item.type === "boolean") { - return [ - { - caption: item.name, - content: command + " " + item.name, - url: "Enable " + desc, - }, - { - caption: "no" + item.name, - content: command + " no" + item.name, - url: "Disable " + desc, - }, - ]; - } else { - return [ - { - caption: item.name, - content: command + " " + item.name, - url: "Set " + desc, - }, - ]; - } - }) - .reduce((acc, val) => acc.concat(val), []) - .filter((item) => item.caption.startsWith(query)); - const completions: Completions = [{ name: "Properties", items }]; - return { - type: COMPLETION_SET_COMPLETIONS, - completions, - completionSource: original, - }; -}; - -const completionNext = (): CompletionNextAction => { - return { - type: COMPLETION_COMPLETION_NEXT, - }; -}; - -const completionPrev = (): CompletionPrevAction => { - return { - type: COMPLETION_COMPLETION_PREV, - }; -}; - -export { - startCompletion, - getCommandCompletions, - getOpenCompletions, - getTabCompletions, - getPropertyCompletions, - completionNext, - completionPrev, -}; diff --git a/src/console/actions/console.ts b/src/console/actions/console.ts index bce2c67..2338067 100644 --- a/src/console/actions/console.ts +++ b/src/console/actions/console.ts @@ -4,7 +4,6 @@ export const CONSOLE_SHOW_COMMAND = "console.show.command"; export const CONSOLE_SHOW_ERROR = "console.show.error"; export const CONSOLE_SHOW_INFO = "console.show.info"; export const CONSOLE_HIDE_COMMAND = "console.hide.command"; -export const CONSOLE_SET_CONSOLE_TEXT = "console.set.command"; export const CONSOLE_SHOW_FIND = "console.show.find"; export const CONSOLE_HIDE = "console.hide"; @@ -35,19 +34,13 @@ export interface HideCommandAction { type: typeof CONSOLE_HIDE_COMMAND; } -export interface SetConsoleTextAction { - type: typeof CONSOLE_SET_CONSOLE_TEXT; - consoleText: string; -} - export type ConsoleAction = | HideAction | ShowCommand | ShowFindAction | ShowErrorAction | ShowInfoAction - | HideCommandAction - | SetConsoleTextAction; + | HideCommandAction; const hide = (): ConsoleAction => { return { @@ -113,13 +106,6 @@ const enterFind = (text?: string): HideCommandAction => { return hideCommand(); }; -const setConsoleText = (consoleText: string): SetConsoleTextAction => { - return { - type: CONSOLE_SET_CONSOLE_TEXT, - consoleText, - }; -}; - export { hide, showCommand, @@ -127,7 +113,6 @@ export { showError, showInfo, hideCommand, - setConsoleText, enterCommand, enterFind, }; diff --git a/src/console/completion/actions.ts b/src/console/completion/actions.ts new file mode 100644 index 0000000..59d1a04 --- /dev/null +++ b/src/console/completion/actions.ts @@ -0,0 +1,76 @@ +import CompletionType from "../../shared/CompletionType"; +import Completions from "../Completions"; + +export const INIT_COMPLETIONS = "reset.completions"; +export const SET_COMPLETION_SOURCE = "set.completion.source"; +export const SET_COMPLETIONS = "set.completions"; +export const COMPLETION_NEXT = "completion.next"; +export const COMPLETION_PREV = "completion.prev"; + +export interface InitCompletionAction { + type: typeof INIT_COMPLETIONS; + completionTypes: CompletionType[]; +} + +export interface SetCompletionSourceAction { + type: typeof SET_COMPLETION_SOURCE; + completionSource: string; +} + +export interface SetCompletionsAction { + type: typeof SET_COMPLETIONS; + completions: Completions; +} + +export interface CompletionNextAction { + type: typeof COMPLETION_NEXT; +} + +export interface CompletionPrevAction { + type: typeof COMPLETION_PREV; +} + +export type CompletionAction = + | InitCompletionAction + | SetCompletionSourceAction + | SetCompletionsAction + | CompletionNextAction + | CompletionPrevAction; + +export const initCompletion = ( + completionTypes: CompletionType[] +): InitCompletionAction => { + return { + type: INIT_COMPLETIONS, + completionTypes, + }; +}; +export const setCompletionSource = ( + query: string +): SetCompletionSourceAction => { + return { + type: SET_COMPLETION_SOURCE, + completionSource: query, + }; +}; + +export const setCompletions = ( + completions: Completions +): SetCompletionsAction => { + return { + type: SET_COMPLETIONS, + completions, + }; +}; + +export const selectNext = (): CompletionNextAction => { + return { + type: COMPLETION_NEXT, + }; +}; + +export const selectPrev = (): CompletionPrevAction => { + return { + type: COMPLETION_PREV, + }; +}; diff --git a/src/console/completion/context.ts b/src/console/completion/context.ts new file mode 100644 index 0000000..2fafcf8 --- /dev/null +++ b/src/console/completion/context.ts @@ -0,0 +1,9 @@ +import React from "react"; +import { State, defaultState } from "./reducer"; +import { CompletionAction } from "./actions"; + +export const CompletionStateContext = React.createContext<State>(defaultState); + +export const CompletionDispatchContext = React.createContext< + (action: CompletionAction) => void +>(() => {}); diff --git a/src/console/completion/hooks.ts b/src/console/completion/hooks.ts new file mode 100644 index 0000000..aac431b --- /dev/null +++ b/src/console/completion/hooks.ts @@ -0,0 +1,277 @@ +import React from "react"; +import * as actions from "./actions"; +import { Command } from "../../shared/Command"; +import TabFlag from "../../shared/TabFlag"; +import { CompletionStateContext, CompletionDispatchContext } from "./context"; +import CompletionClient from "../clients/CompletionClient"; +import CommandLineParser, { + CommandLine, + InputPhase, +} from "../commandline/CommandLineParser"; +import { UnknownCommandError } from "../commandline/CommandParser"; +import Completions from "../Completions"; +import CompletionType from "../../shared/CompletionType"; + +const commandDocs = { + [Command.Set]: "Set a value of the property", + [Command.Open]: "Open a URL or search by keywords in current tab", + [Command.TabOpen]: "Open a URL or search by keywords in new tab", + [Command.WindowOpen]: "Open a URL or search by keywords in new window", + [Command.Buffer]: "Select tabs by matched keywords", + [Command.BufferDelete]: "Close a certain tab matched by keywords", + [Command.BuffersDelete]: "Close all tabs matched by keywords", + [Command.Quit]: "Close the current tab", + [Command.QuitAll]: "Close all tabs", + [Command.AddBookmark]: "Add current page to bookmarks", + [Command.Help]: "Open Vim Vixen help in new tab", +}; + +const propertyDocs: { [key: string]: string } = { + hintchars: "hint characters on follow mode", + smoothscroll: "smooth scroll", + complete: "which are completed at the open page", + colorscheme: "color scheme of the console", +}; + +const completionClient = new CompletionClient(); + +const getCommandCompletions = async (query: string): Promise<Completions> => { + const items = Object.entries(commandDocs) + .filter(([name]) => name.startsWith(query)) + .map(([name, doc]) => ({ + caption: name, + content: name, + url: doc, + })); + return [ + { + name: "Console Command", + items, + }, + ]; +}; + +const getOpenCompletions = async ( + command: string, + query: string, + completionTypes: CompletionType[] +): Promise<Completions> => { + const completions: Completions = []; + for (const type of completionTypes) { + switch (type) { + case CompletionType.SearchEngines: { + const items = await completionClient.requestSearchEngines(query); + if (items.length === 0) { + break; + } + completions.push({ + name: "Search Engines", + items: items.map((key) => ({ + caption: key.title, + content: command + " " + key.title, + })), + }); + break; + } + case CompletionType.History: { + const items = await completionClient.requestHistory(query); + if (items.length === 0) { + break; + } + completions.push({ + name: "History", + items: items.map((item) => ({ + caption: item.title, + content: command + " " + item.url, + url: item.url, + })), + }); + break; + } + case CompletionType.Bookmarks: { + const items = await completionClient.requestBookmarks(query); + if (items.length === 0) { + break; + } + completions.push({ + name: "Bookmarks", + items: items.map((item) => ({ + caption: item.title, + content: command + " " + item.url, + url: item.url, + })), + }); + break; + } + } + } + return completions; +}; + +export const getTabCompletions = async ( + command: string, + query: string, + excludePinned: boolean +): Promise<Completions> => { + const items = await completionClient.requestTabs(query, excludePinned); + if (items.length === 0) { + return []; + } + + return [ + { + name: "Buffers", + items: items.map((item) => ({ + content: command + " " + item.url, + caption: `${item.index}: ${ + item.flag != TabFlag.None ? item.flag : " " + } ${item.title}`, + url: item.url, + icon: item.faviconUrl, + })), + }, + ]; +}; + +export const getPropertyCompletions = async ( + command: string, + query: string +): Promise<Completions> => { + const properties = await completionClient.getProperties(); + const items = properties + .map((item) => { + const desc = propertyDocs[item.name] || ""; + if (item.type === "boolean") { + return [ + { + caption: item.name, + content: command + " " + item.name, + url: "Enable " + desc, + }, + { + caption: "no" + item.name, + content: command + " no" + item.name, + url: "Disable " + desc, + }, + ]; + } else { + return [ + { + caption: item.name, + content: command + " " + item.name, + url: "Set " + desc, + }, + ]; + } + }) + .reduce((acc, val) => acc.concat(val), []) + .filter((item) => item.caption.startsWith(query)); + return [{ name: "Properties", items }]; +}; + +export const useCompletions = () => { + const state = React.useContext(CompletionStateContext); + const dispatch = React.useContext(CompletionDispatchContext); + const commandLineParser = React.useMemo(() => new CommandLineParser(), []); + + const updateCompletions = React.useCallback((source: string) => { + dispatch(actions.setCompletionSource(source)); + }, []); + + const initCompletion = React.useCallback((source: string) => { + completionClient.getCompletionTypes().then((completionTypes) => { + dispatch(actions.initCompletion(completionTypes)); + dispatch(actions.setCompletionSource(source)); + }); + }, []); + + React.useEffect(() => { + const text = state.completionSource; + const phase = commandLineParser.inputPhase(text); + if (phase === InputPhase.OnCommand) { + getCommandCompletions(text).then((completions) => + dispatch(actions.setCompletions(completions)) + ); + } else { + let cmd: CommandLine | null = null; + try { + cmd = commandLineParser.parse(text); + } catch (e) { + if (e instanceof UnknownCommandError) { + return; + } + } + switch (cmd?.command) { + case Command.Open: + case Command.TabOpen: + case Command.WindowOpen: + if (!state.completionTypes) { + initCompletion(text); + return; + } + + getOpenCompletions( + cmd.command, + cmd.args, + state.completionTypes + ).then((completions) => + dispatch(actions.setCompletions(completions)) + ); + break; + case Command.Buffer: + getTabCompletions(cmd.command, cmd.args, false).then((completions) => + dispatch(actions.setCompletions(completions)) + ); + break; + case Command.BufferDelete: + case Command.BuffersDelete: + getTabCompletions(cmd.command, cmd.args, true).then((completions) => + dispatch(actions.setCompletions(completions)) + ); + break; + case Command.BufferDeleteForce: + case Command.BuffersDeleteForce: + getTabCompletions(cmd.command, cmd.args, false).then((completions) => + dispatch(actions.setCompletions(completions)) + ); + break; + case Command.Set: + getPropertyCompletions(cmd.command, cmd.args).then((completions) => + dispatch(actions.setCompletions(completions)) + ); + break; + } + } + }, [state.completionSource, state.completionTypes]); + + return { + completions: state.completions, + updateCompletions, + initCompletion, + }; +}; + +export const useSelectCompletion = () => { + const state = React.useContext(CompletionStateContext); + const dispatch = React.useContext(CompletionDispatchContext); + const next = React.useCallback(() => dispatch(actions.selectNext()), [ + dispatch, + ]); + const prev = React.useCallback(() => dispatch(actions.selectPrev()), [ + dispatch, + ]); + const currentValue = React.useMemo(() => { + if (state.select < 0) { + return state.completionSource; + } + const items = state.completions.map((g) => g.items).flat(); + return items[state.select]?.content || ""; + }, [state.completionSource, state.select]); + + return { + select: state.select, + currentValue, + selectNext: next, + selectPrev: prev, + }; +}; diff --git a/src/console/completion/provider.tsx b/src/console/completion/provider.tsx new file mode 100644 index 0000000..c0de250 --- /dev/null +++ b/src/console/completion/provider.tsx @@ -0,0 +1,25 @@ +import reducer, { defaultState } from "./reducer"; +import React from "react"; +import { CompletionDispatchContext, CompletionStateContext } from "./context"; + +interface Props { + initialInputValue: string; +} + +export const CompletionProvider: React.FC<Props> = ({ + initialInputValue, + children, +}) => { + const initialState = { + ...defaultState, + completionSource: initialInputValue, + }; + const [state, dispatch] = React.useReducer(reducer, initialState); + return ( + <CompletionStateContext.Provider value={state}> + <CompletionDispatchContext.Provider value={dispatch}> + {children} + </CompletionDispatchContext.Provider> + </CompletionStateContext.Provider> + ); +}; diff --git a/src/console/reducers/completion.ts b/src/console/completion/reducer.ts index 2c7ee55..905451f 100644 --- a/src/console/reducers/completion.ts +++ b/src/console/completion/reducer.ts @@ -1,38 +1,38 @@ import Completions from "../Completions"; import CompletionType from "../../shared/CompletionType"; import { - COMPLETION_COMPLETION_NEXT, - COMPLETION_COMPLETION_PREV, - COMPLETION_SET_COMPLETIONS, - COMPLETION_START_COMPLETION, + INIT_COMPLETIONS, + SET_COMPLETION_SOURCE, + SET_COMPLETIONS, + COMPLETION_NEXT, + COMPLETION_PREV, CompletionAction, -} from "../actions/completion"; +} from "./actions"; export interface State { - completionTypes: CompletionType[]; + completionTypes?: CompletionType[]; completionSource: string; completions: Completions; select: number; } export const defaultState = { - completionTypes: [], + completionTypes: undefined, completionSource: "", completions: [], select: -1, }; const nextSelection = (state: State): number => { - if (state.completions.length === 0) { + const length = state.completions + .map((g) => g.items.length) + .reduce((x, y) => x + y, 0); + if (length === 0) { return -1; } if (state.select < 0) { return 0; } - - const length = state.completions - .map((g) => g.items.length) - .reduce((x, y) => x + y); if (state.select + 1 < length) { return state.select + 1; } @@ -40,6 +40,9 @@ const nextSelection = (state: State): number => { }; const prevSelection = (state: State): number => { + if (state.completions.length === 0) { + return -1; + } const length = state.completions .map((g) => g.items.length) .reduce((x, y) => x + y); @@ -49,44 +52,38 @@ const prevSelection = (state: State): number => { return state.select - 1; }; -export const completedText = (state: State): string => { - if (state.select < 0) { - return state.completionSource; - } - const items = state.completions - .map((g) => g.items) - .reduce((g1, g2) => g1.concat(g2)); - return items[state.select].content || ""; -}; - // eslint-disable-next-line max-lines-per-function export default function reducer( state: State = defaultState, action: CompletionAction ): State { switch (action.type) { - case COMPLETION_START_COMPLETION: + case INIT_COMPLETIONS: return { ...state, completionTypes: action.completionTypes, completions: [], select: -1, }; - case COMPLETION_SET_COMPLETIONS: + case SET_COMPLETION_SOURCE: return { ...state, - completions: action.completions, completionSource: action.completionSource, select: -1, }; - case COMPLETION_COMPLETION_NEXT: { + case SET_COMPLETIONS: + return { + ...state, + completions: action.completions, + }; + case COMPLETION_NEXT: { const select = nextSelection(state); return { ...state, select: select, }; } - case COMPLETION_COMPLETION_PREV: { + case COMPLETION_PREV: { const select = prevSelection(state); return { ...state, diff --git a/src/console/components/CommandPrompt.tsx b/src/console/components/CommandPrompt.tsx index f6f4d8f..24f46ae 100644 --- a/src/console/components/CommandPrompt.tsx +++ b/src/console/components/CommandPrompt.tsx @@ -1,17 +1,12 @@ import React from "react"; import * as consoleActions from "../actions/console"; -import * as completionActions from "../actions/completion"; import AppContext from "./AppContext"; -import CommandLineParser, { - InputPhase, -} from "../commandline/CommandLineParser"; import Completion from "./console/Completion"; import Input from "./console//Input"; -import { Command } from "../../shared/Command"; import styled from "styled-components"; -import reducer, { defaultState, completedText } from "../reducers/completion"; -import CompletionType from "../../shared/CompletionType"; +import { useCompletions, useSelectCompletion } from "../completion/hooks"; import useAutoResize from "../hooks/useAutoResize"; +import { CompletionProvider } from "../completion/provider"; const COMPLETION_MAX_ITEMS = 33; @@ -19,13 +14,20 @@ const ConsoleWrapper = styled.div` border-top: 1px solid gray; `; -const CommandPrompt: React.FC = () => { - const { state, dispatch } = React.useContext(AppContext); - const [completionState, completionDispatch] = React.useReducer( - reducer, - defaultState - ); - const commandLineParser = new CommandLineParser(); +interface Props { + initialInputValue: string; +} + +const CommandPromptInner: React.FC<Props> = ({ initialInputValue }) => { + const { dispatch } = React.useContext(AppContext); + const [inputValue, setInputValue] = React.useState(initialInputValue); + const { completions, updateCompletions } = useCompletions(); + const { + select, + currentValue, + selectNext, + selectPrev, + } = useSelectCompletion(); useAutoResize(); @@ -33,174 +35,81 @@ const CommandPrompt: React.FC = () => { dispatch(consoleActions.hideCommand()); }; - const doEnter = (e: React.KeyboardEvent<HTMLInputElement>) => { - e.stopPropagation(); - e.preventDefault(); + const isCancelKey = React.useCallback( + (e: React.KeyboardEvent<HTMLInputElement>) => + e.key === "Escape" || + (e.ctrlKey && e.key === "[") || + (e.ctrlKey && e.key === "c"), + [] + ); - const value = (e.target as HTMLInputElement).value; - dispatch(consoleActions.enterCommand(value)); - }; + const isNextKey = React.useCallback( + (e: React.KeyboardEvent<HTMLInputElement>) => + (!e.shiftKey && e.key === "Tab") || (e.ctrlKey && e.key === "n"), + [] + ); - const selectNext = (e: React.KeyboardEvent<HTMLInputElement>) => { - completionDispatch(completionActions.completionNext()); - e.stopPropagation(); - e.preventDefault(); - }; + const isPrevKey = React.useCallback( + (e: React.KeyboardEvent<HTMLInputElement>) => + (e.shiftKey && e.key === "Tab") || (e.ctrlKey && e.key === "p"), + [] + ); - const selectPrev = (e: React.KeyboardEvent<HTMLInputElement>) => { - completionDispatch(completionActions.completionPrev()); - e.stopPropagation(); - e.preventDefault(); - }; + const isEnterKey = React.useCallback( + (e: React.KeyboardEvent<HTMLInputElement>) => + e.key === "Enter" || (e.ctrlKey && e.key === "m"), + [] + ); const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { - switch (e.key) { - case "Escape": - dispatch(consoleActions.hideCommand()); - break; - case "Enter": - doEnter(e); - break; - case "Tab": - if (e.shiftKey) { - completionDispatch(completionActions.completionPrev()); - } else { - completionDispatch(completionActions.completionNext()); - } - e.stopPropagation(); - e.preventDefault(); - break; - case "[": - if (e.ctrlKey) { - e.preventDefault(); - dispatch(consoleActions.hideCommand()); - } - break; - case "c": - if (e.ctrlKey) { - e.preventDefault(); - dispatch(consoleActions.hideCommand()); - } - break; - case "m": - if (e.ctrlKey) { - doEnter(e); - } - break; - case "n": - if (e.ctrlKey) { - selectNext(e); - } - break; - case "p": - if (e.ctrlKey) { - selectPrev(e); - } - break; + if (isCancelKey(e)) { + dispatch(consoleActions.hideCommand()); + } else if (isEnterKey(e)) { + const value = (e.target as HTMLInputElement).value; + dispatch(consoleActions.enterCommand(value)); + } else if (isNextKey(e)) { + selectNext(); + } else if (isPrevKey(e)) { + selectPrev(); + } else { + return; } + + e.stopPropagation(); + e.preventDefault(); }; const onChange = (e: React.ChangeEvent<HTMLInputElement>) => { const text = e.target.value; - dispatch(consoleActions.setConsoleText(text)); - const action = getCompletionAction(text); - Promise.resolve(action).then((a) => { - if (a) { - completionDispatch(a); - } - }); + setInputValue(text); }; React.useEffect(() => { - completionActions.startCompletion().then((action) => { - completionDispatch(action); - - const completionAction = getCompletionAction( - state.consoleText, - action.completionTypes - ); - Promise.resolve(completionAction).then((a) => { - if (a) { - completionDispatch(a); - } - }); - }); - }, []); - - const getCompletionAction = ( - text: string, - completionTypes: CompletionType[] | undefined = undefined - ) => { - const types = completionTypes || completionState.completionTypes; - const phase = commandLineParser.inputPhase(text); - if (phase === InputPhase.OnCommand) { - return completionActions.getCommandCompletions(text); - } else { - const cmd = commandLineParser.parse(text); - switch (cmd.command) { - case Command.Open: - case Command.TabOpen: - case Command.WindowOpen: - return completionActions.getOpenCompletions( - types, - text, - cmd.command, - cmd.args - ); - case Command.Buffer: - return completionActions.getTabCompletions( - text, - cmd.command, - cmd.args, - false - ); - case Command.BufferDelete: - case Command.BuffersDelete: - return completionActions.getTabCompletions( - text, - cmd.command, - cmd.args, - true - ); - case Command.BufferDeleteForce: - case Command.BuffersDeleteForce: - return completionActions.getTabCompletions( - text, - cmd.command, - cmd.args, - false - ); - case Command.Set: - return completionActions.getPropertyCompletions( - text, - cmd.command, - cmd.args - ); - } - } - return undefined; - }; + updateCompletions(inputValue); + }, [inputValue]); return ( <ConsoleWrapper> <Completion size={COMPLETION_MAX_ITEMS} - completions={completionState.completions} - select={completionState.select} + completions={completions} + select={select} /> <Input prompt={":"} onBlur={onBlur} onKeyDown={onKeyDown} onChange={onChange} - value={ - completionState.select < 0 - ? state.consoleText - : completedText(completionState) - } + value={select == -1 ? inputValue : currentValue} /> </ConsoleWrapper> ); }; +const CommandPrompt: React.FC<Props> = ({ initialInputValue }) => ( + <CompletionProvider initialInputValue={initialInputValue}> + <CommandPromptInner initialInputValue={initialInputValue} /> + </CompletionProvider> +); + export default CommandPrompt; diff --git a/src/console/components/Console.tsx b/src/console/components/Console.tsx index f6f4234..508c6eb 100644 --- a/src/console/components/Console.tsx +++ b/src/console/components/Console.tsx @@ -16,7 +16,7 @@ const Console: React.FC = () => { switch (state.mode) { case "command": - return <CommandPrompt />; + return <CommandPrompt initialInputValue={state.consoleText} />; case "find": return <FindPrompt />; case "info": diff --git a/src/console/reducers/console.ts b/src/console/reducers/console.ts index 37f1efc..babad69 100644 --- a/src/console/reducers/console.ts +++ b/src/console/reducers/console.ts @@ -1,7 +1,6 @@ import { CONSOLE_HIDE, CONSOLE_HIDE_COMMAND, - CONSOLE_SET_CONSOLE_TEXT, CONSOLE_SHOW_COMMAND, CONSOLE_SHOW_ERROR, CONSOLE_SHOW_FIND, @@ -47,8 +46,6 @@ export default function reducer( mode: state.mode === "command" || state.mode === "find" ? "" : state.mode, }; - case CONSOLE_SET_CONSOLE_TEXT: - return { ...state, consoleText: action.consoleText }; default: return state; } |