aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2021-04-11 18:00:51 +0900
committerShin'ya Ueoka <ueokande@i-beam.org>2021-04-11 22:34:14 +0900
commit618fb497c443662531eb3befe7696a04efe9651d (patch)
tree530af78bf75f03e7ffd71e4ca5ad1cf864584be0 /src
parent21f863d76fbb5ed752ad529f8fbe33e75460027e (diff)
Replace completion state with Custom Hooks
Diffstat (limited to 'src')
-rw-r--r--src/console/actions/completion.ts243
-rw-r--r--src/console/actions/console.ts17
-rw-r--r--src/console/completion/actions.ts76
-rw-r--r--src/console/completion/context.ts9
-rw-r--r--src/console/completion/hooks.ts277
-rw-r--r--src/console/completion/provider.tsx25
-rw-r--r--src/console/completion/reducer.ts (renamed from src/console/reducers/completion.ts)51
-rw-r--r--src/console/components/CommandPrompt.tsx219
-rw-r--r--src/console/components/Console.tsx2
-rw-r--r--src/console/reducers/console.ts3
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;
}