From 65ddd365e407989f3c1722a3ba811492b32e692f Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 24 Nov 2021 13:18:03 +0000 Subject: Use React.StrictMode --- src/console/App.tsx | 51 +++++++++++++++++++++++++++++++++++++ src/console/index.tsx | 70 +++++++++------------------------------------------ 2 files changed, 63 insertions(+), 58 deletions(-) create mode 100644 src/console/App.tsx diff --git a/src/console/App.tsx b/src/console/App.tsx new file mode 100644 index 0000000..6b45418 --- /dev/null +++ b/src/console/App.tsx @@ -0,0 +1,51 @@ +import * as messages from "../shared/messages"; +import Console from "./components/Console"; +import React from "react"; +import { + useCommandMode, + useFindMode, + useInfoMessage, + useErrorMessage, + useHide, +} from "./app/hooks"; + +const App: React.FC = () => { + const hide = useHide(); + const { show: showCommand } = useCommandMode(); + const { show: showFind } = useFindMode(); + const { show: showError } = useErrorMessage(); + const { show: showInfo } = useInfoMessage(); + + React.useEffect(() => { + const onMessage = async (message: any): Promise => { + const msg = messages.valueOf(message); + switch (msg.type) { + case messages.CONSOLE_SHOW_COMMAND: + showCommand(msg.command); + break; + case messages.CONSOLE_SHOW_FIND: + showFind(); + break; + case messages.CONSOLE_SHOW_ERROR: + showError(msg.text); + break; + case messages.CONSOLE_SHOW_INFO: + showInfo(msg.text); + break; + case messages.CONSOLE_HIDE: + hide(); + break; + } + }; + + browser.runtime.onMessage.addListener(onMessage); + const port = browser.runtime.connect(undefined, { + name: "vimvixen-console", + }); + port.onMessage.addListener(onMessage); + }, []); + + return ; +}; + +export default App; diff --git a/src/console/index.tsx b/src/console/index.tsx index 29fa11f..5b97917 100644 --- a/src/console/index.tsx +++ b/src/console/index.tsx @@ -1,66 +1,20 @@ -import * as messages from "../shared/messages"; -import Console from "./components/Console"; -import "./index.css"; import React from "react"; import ReactDOM from "react-dom"; import ColorSchemeProvider from "./colorscheme/providers"; import { AppProvider } from "./app/provider"; -import { - useCommandMode, - useFindMode, - useInfoMessage, - useErrorMessage, - useHide, -} from "./app/hooks"; - -const RootComponent: React.FC = () => { - const hide = useHide(); - const { show: showCommand } = useCommandMode(); - const { show: showFind } = useFindMode(); - const { show: showError } = useErrorMessage(); - const { show: showInfo } = useInfoMessage(); - - React.useEffect(() => { - const onMessage = async (message: any): Promise => { - const msg = messages.valueOf(message); - switch (msg.type) { - case messages.CONSOLE_SHOW_COMMAND: - showCommand(msg.command); - break; - case messages.CONSOLE_SHOW_FIND: - showFind(); - break; - case messages.CONSOLE_SHOW_ERROR: - showError(msg.text); - break; - case messages.CONSOLE_SHOW_INFO: - showInfo(msg.text); - break; - case messages.CONSOLE_HIDE: - hide(); - break; - } - }; - - browser.runtime.onMessage.addListener(onMessage); - const port = browser.runtime.connect(undefined, { - name: "vimvixen-console", - }); - port.onMessage.addListener(onMessage); - }, []); - - return ; -}; - -const App: React.FC = () => ( - - - - - -); +import App from "./App"; +import "./index.css"; window.addEventListener("DOMContentLoaded", () => { const wrapper = document.getElementById("vimvixen-console"); - ReactDOM.render(, wrapper); + ReactDOM.render( + + + + + + + , + wrapper + ); }); -- cgit v1.2.3 From 823bad63384de90c11653f0afa253cc5d0e19239 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 4 May 2022 11:27:42 +0000 Subject: Rename completion fields --- src/console/Completions.ts | 6 +-- src/console/completion/hooks.ts | 50 +++++++++++----------- src/console/components/console/Completion.tsx | 8 ++-- src/console/components/console/CompletionItem.tsx | 14 +++--- .../console/components/console/Completion.test.tsx | 14 +++--- .../components/console/CompletionItem.test.tsx | 4 +- 6 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/console/Completions.ts b/src/console/Completions.ts index a18f160..b04e480 100644 --- a/src/console/Completions.ts +++ b/src/console/Completions.ts @@ -1,9 +1,9 @@ type Completions = { readonly name: string; readonly items: { - readonly caption?: string; - readonly content?: string; - readonly url?: string; + readonly primary?: string; + readonly secondary?: string; + readonly value?: string; readonly icon?: string; }[]; }[]; diff --git a/src/console/completion/hooks.ts b/src/console/completion/hooks.ts index 4402b70..ad315e6 100644 --- a/src/console/completion/hooks.ts +++ b/src/console/completion/hooks.ts @@ -74,9 +74,9 @@ const getCommandCompletions = async (query: string): Promise => { const items = Object.entries(commandDocs) .filter(([name]) => name.startsWith(query)) .map(([name, doc]) => ({ - caption: name, - content: name, - url: doc, + primary: name, + secondary: doc, + value: name, })); return [ { @@ -102,8 +102,8 @@ const getOpenCompletions = async ( completions.push({ name: "Search Engines", items: items.map((key) => ({ - caption: key.title, - content: command + " " + key.title, + primary: key.title, + value: command + " " + key.title, })), }); break; @@ -116,9 +116,9 @@ const getOpenCompletions = async ( completions.push({ name: "History", items: items.map((item) => ({ - caption: item.title, - content: command + " " + item.url, - url: item.url, + primary: item.title, + secondary: item.url, + value: command + " " + item.url, })), }); break; @@ -131,9 +131,9 @@ const getOpenCompletions = async ( completions.push({ name: "Bookmarks", items: items.map((item) => ({ - caption: item.title, - content: command + " " + item.url, - url: item.url, + primary: item.title, + secondary: item.url, + value: command + " " + item.url, })), }); break; @@ -157,11 +157,11 @@ export const getTabCompletions = async ( { name: "Buffers", items: items.map((item) => ({ - content: command + " " + item.url, - caption: `${item.index}: ${ + primary: `${item.index}: ${ item.flag != TabFlag.None ? item.flag : " " } ${item.title}`, - url: item.url, + secondary: item.url, + value: command + " " + item.url, icon: item.faviconUrl, })), }, @@ -179,28 +179,28 @@ export const getPropertyCompletions = async ( if (item.type === "boolean") { return [ { - caption: item.name, - content: command + " " + item.name, - url: "Enable " + desc, + primary: item.name, + secondary: "Enable " + desc, + value: command + " " + item.name, }, { - caption: "no" + item.name, - content: command + " no" + item.name, - url: "Disable " + desc, + primary: "no" + item.name, + secondary: "Disable " + desc, + value: command + " no" + item.name, }, ]; } else { return [ { - caption: item.name, - content: command + " " + item.name, - url: "Set " + desc, + primary: item.name, + secondary: "Set " + desc, + value: command + " " + item.name, }, ]; } }) .reduce((acc, val) => acc.concat(val), []) - .filter((item) => item.caption.startsWith(query)); + .filter((item) => item.primary.startsWith(query)); return [{ name: "Properties", items }]; }; @@ -308,7 +308,7 @@ export const useSelectCompletion = () => { return state.completionSource; } const items = state.completions.map((g) => g.items).flat(); - return items[state.select]?.content || ""; + return items[state.select]?.value || ""; }, [state.completionSource, state.select]); return { diff --git a/src/console/components/console/Completion.tsx b/src/console/components/console/Completion.tsx index ed271aa..6a58a40 100644 --- a/src/console/components/console/Completion.tsx +++ b/src/console/components/console/Completion.tsx @@ -4,8 +4,8 @@ import CompletionTitle from "./CompletionTitle"; interface Item { icon?: string; - caption?: string; - url?: string; + primary?: string; + secondary?: string; } interface Group { @@ -75,8 +75,8 @@ const Completion: React.FC = ({ select, size, completions }) => { shown={viewOffset <= viewIndex && viewIndex < viewOffset + size} key={`item-${itemIndex}`} icon={item.icon} - caption={item.caption} - url={item.url} + primary={item.primary} + secondary={item.secondary} highlight={itemIndex === select} aria-selected={itemIndex === select} role="menuitem" diff --git a/src/console/components/console/CompletionItem.tsx b/src/console/components/console/CompletionItem.tsx index 2de1375..394af04 100644 --- a/src/console/components/console/CompletionItem.tsx +++ b/src/console/components/console/CompletionItem.tsx @@ -23,14 +23,14 @@ const Container = styled.li<{ white-space: pre; `; -const Caption = styled.span` +const Primary = styled.span` display: inline-block; width: 40%; text-overflow: ellipsis; overflow: hidden; `; -const Description = styled.span` +const Secondary = styled.span` display: inline-block; color: ${({ theme }) => theme.completionItemDescriptionForeground}; width: 60%; @@ -41,19 +41,19 @@ const Description = styled.span` interface Props extends React.HTMLAttributes { shown: boolean; highlight: boolean; - caption?: string; - url?: string; + primary?: string; + secondary?: string; icon?: string; } const CompletionItem: React.FC = (props) => ( - {props.caption} - {props.url} + {props.primary} + {props.secondary} ); diff --git a/test/console/components/console/Completion.test.tsx b/test/console/components/console/Completion.test.tsx index 7dd634f..dee0e80 100644 --- a/test/console/components/console/Completion.test.tsx +++ b/test/console/components/console/Completion.test.tsx @@ -9,17 +9,17 @@ describe("console/components/console/completion/Completion", () => { { name: "Fruit", items: [ - { caption: "apple" }, - { caption: "banana" }, - { caption: "cherry" }, + { primary: "apple" }, + { primary: "banana" }, + { primary: "cherry" }, ], }, { name: "Element", items: [ - { caption: "argon" }, - { caption: "boron" }, - { caption: "carbon" }, + { primary: "argon" }, + { primary: "boron" }, + { primary: "carbon" }, ], }, ]; @@ -39,7 +39,7 @@ describe("console/components/console/completion/Completion", () => { const items = group.findAllByType(CompletionItem); expect(items).toHaveLength(completions[i].items.length); items.forEach((item, j) => { - expect(item.props.caption).toEqual(completions[i].items[j].caption); + expect(item.props.primary).toEqual(completions[i].items[j].primary); }); }); }); diff --git a/test/console/components/console/CompletionItem.test.tsx b/test/console/components/console/CompletionItem.test.tsx index ae73b21..6e89bc8 100644 --- a/test/console/components/console/CompletionItem.test.tsx +++ b/test/console/components/console/CompletionItem.test.tsx @@ -8,8 +8,8 @@ describe("console/components/console/completion/CompletionItem", () => { ).root; const spans = root.findAllByType("span"); -- cgit v1.2.3 From bc890c55bfbd6d1aa4ece5b0ef96a9ab6d358ad7 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Mon, 14 Mar 2022 13:24:01 +0000 Subject: Get completion types on component initialization --- src/console/completion/actions.ts | 16 ---------------- src/console/completion/hooks.ts | 24 ++++++++---------------- src/console/completion/hooks/clients.ts | 23 +++++++++++++++++++++++ src/console/completion/reducer.ts | 8 -------- test/console/completion/reducer.test.ts | 16 ---------------- 5 files changed, 31 insertions(+), 56 deletions(-) create mode 100644 src/console/completion/hooks/clients.ts diff --git a/src/console/completion/actions.ts b/src/console/completion/actions.ts index 59d1a04..0c5e1f1 100644 --- a/src/console/completion/actions.ts +++ b/src/console/completion/actions.ts @@ -1,17 +1,10 @@ -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; @@ -31,20 +24,11 @@ export interface CompletionPrevAction { } 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 => { diff --git a/src/console/completion/hooks.ts b/src/console/completion/hooks.ts index ad315e6..62baab4 100644 --- a/src/console/completion/hooks.ts +++ b/src/console/completion/hooks.ts @@ -11,6 +11,7 @@ import CommandLineParser, { import { UnknownCommandError } from "../commandline/CommandParser"; import Completions from "../Completions"; import CompletionType from "../../shared/CompletionType"; +import { useGetCompletionTypes } from "./hooks/clients"; const commandDocs = { [Command.Set]: "Set a value of the property", @@ -208,21 +209,15 @@ export const useCompletions = () => { const state = React.useContext(CompletionStateContext); const dispatch = React.useContext(CompletionDispatchContext); const commandLineParser = React.useMemo(() => new CommandLineParser(), []); + const [completionTypes] = useGetCompletionTypes(); 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)); - }); - }, []); - const { delayedCallback: queryCompletions, enableDelay } = useDelayedCallback( React.useCallback( - (text: string, completionTypes?: CompletionType[]) => { + (text: string, completionTypes: CompletionType[]) => { const phase = commandLineParser.inputPhase(text); if (phase === InputPhase.OnCommand) { getCommandCompletions(text).then((completions) => @@ -241,11 +236,6 @@ export const useCompletions = () => { case Command.Open: case Command.TabOpen: case Command.WindowOpen: - if (!completionTypes) { - initCompletion(text); - return; - } - getOpenCompletions(cmd.command, cmd.args, completionTypes).then( (completions) => dispatch(actions.setCompletions(completions)) ); @@ -282,13 +272,15 @@ export const useCompletions = () => { ); React.useEffect(() => { - queryCompletions(state.completionSource, state.completionTypes); - }, [state.completionSource, state.completionTypes]); + if (typeof completionTypes === "undefined") { + return; + } + queryCompletions(state.completionSource, completionTypes); + }, [state.completionSource, completionTypes]); return { completions: state.completions, updateCompletions, - initCompletion, }; }; diff --git a/src/console/completion/hooks/clients.ts b/src/console/completion/hooks/clients.ts new file mode 100644 index 0000000..49deb0c --- /dev/null +++ b/src/console/completion/hooks/clients.ts @@ -0,0 +1,23 @@ +import React from "react"; +import CompletionClient from "../../clients/CompletionClient"; +import CompletionType from "../../../shared/CompletionType"; + +const completionClient = new CompletionClient(); + +export const useGetCompletionTypes = (): [ + CompletionType[] | undefined, + boolean +] => { + type State = { + loading: boolean; + result?: CompletionType[]; + }; + const [state, setState] = React.useState({ loading: true }); + + React.useEffect(() => { + completionClient.getCompletionTypes().then((result) => { + setState({ loading: false, result }); + }); + }, []); + return [state.result, state.loading]; +}; diff --git a/src/console/completion/reducer.ts b/src/console/completion/reducer.ts index 905451f..0b34114 100644 --- a/src/console/completion/reducer.ts +++ b/src/console/completion/reducer.ts @@ -1,7 +1,6 @@ import Completions from "../Completions"; import CompletionType from "../../shared/CompletionType"; import { - INIT_COMPLETIONS, SET_COMPLETION_SOURCE, SET_COMPLETIONS, COMPLETION_NEXT, @@ -58,13 +57,6 @@ export default function reducer( action: CompletionAction ): State { switch (action.type) { - case INIT_COMPLETIONS: - return { - ...state, - completionTypes: action.completionTypes, - completions: [], - select: -1, - }; case SET_COMPLETION_SOURCE: return { ...state, diff --git a/test/console/completion/reducer.test.ts b/test/console/completion/reducer.test.ts index 43b9807..865d066 100644 --- a/test/console/completion/reducer.test.ts +++ b/test/console/completion/reducer.test.ts @@ -3,29 +3,13 @@ import reducer, { State, } from "../../../src/console/completion/reducer"; import { - initCompletion, selectNext, selectPrev, setCompletions, setCompletionSource, } from "../../../src/console/completion/actions"; -import CompletionType from "../../../src/shared/CompletionType"; describe("completion reducer", () => { - describe("initCompletion", () => { - it("initializes completions", () => { - const nextState = reducer( - defaultState, - initCompletion([CompletionType.Bookmarks, CompletionType.History]) - ); - - expect(nextState.completionTypes).toEqual([ - CompletionType.Bookmarks, - CompletionType.History, - ]); - }); - }); - describe("setCompletionSource", () => { it("sets a completion source", () => { const nextState = reducer(defaultState, setCompletionSource("open ")); -- cgit v1.2.3 From 0d17912972de6b9e8b08aedf141260413c60d7fd Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 5 May 2022 07:01:55 +0000 Subject: Use useDebounce --- src/console/completion/hooks.ts | 135 +++++++++++-------------------- src/console/components/CommandPrompt.tsx | 6 +- src/console/hooks/useDebounce.ts | 19 +++++ 3 files changed, 71 insertions(+), 89 deletions(-) create mode 100644 src/console/hooks/useDebounce.ts diff --git a/src/console/completion/hooks.ts b/src/console/completion/hooks.ts index 62baab4..3a2ec33 100644 --- a/src/console/completion/hooks.ts +++ b/src/console/completion/hooks.ts @@ -36,41 +36,6 @@ const propertyDocs: { [key: string]: string } = { const completionClient = new CompletionClient(); -const useDelayedCallback = ( - callback: (arg1: T, arg2: U) => void, - timeout: number -) => { - const [timer, setTimer] = React.useState< - ReturnType | undefined - >(); - const [enabled, setEnabled] = React.useState(false); - - const enableDelay = React.useCallback(() => { - setEnabled(true); - }, [setEnabled]); - - const delayedCallback = React.useCallback( - (arg1: T, arg2: U) => { - if (enabled) { - if (typeof timer !== "undefined") { - clearTimeout(timer); - } - const id = setTimeout(() => { - callback(arg1, arg2); - clearTimeout(timer!); - setTimer(undefined); - }, timeout); - setTimer(id); - } else { - callback(arg1, arg2); - } - }, - [enabled, timer] - ); - - return { enableDelay, delayedCallback }; -}; - const getCommandCompletions = async (query: string): Promise => { const items = Object.entries(commandDocs) .filter(([name]) => name.startsWith(query)) @@ -215,60 +180,56 @@ export const useCompletions = () => { dispatch(actions.setCompletionSource(source)); }, []); - const { delayedCallback: queryCompletions, enableDelay } = useDelayedCallback( - React.useCallback( - (text: string, completionTypes: CompletionType[]) => { - 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: - getOpenCompletions(cmd.command, cmd.args, 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; + const queryCompletions = React.useCallback( + (text: string, completionTypes: CompletionType[]) => { + 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; } - enableDelay(); } - }, - [dispatch] - ), - 100 + switch (cmd?.command) { + case Command.Open: + case Command.TabOpen: + case Command.WindowOpen: + getOpenCompletions(cmd.command, cmd.args, 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; + } + } + }, + [dispatch] ); React.useEffect(() => { diff --git a/src/console/components/CommandPrompt.tsx b/src/console/components/CommandPrompt.tsx index 0e2506c..0312fe4 100644 --- a/src/console/components/CommandPrompt.tsx +++ b/src/console/components/CommandPrompt.tsx @@ -3,6 +3,7 @@ import Completion from "./console/Completion"; import Input from "./console//Input"; import styled from "styled-components"; import { useCompletions, useSelectCompletion } from "../completion/hooks"; +import useDebounce from "../hooks/useDebounce"; import useAutoResize from "../hooks/useAutoResize"; import { CompletionProvider } from "../completion/provider"; import { useExecCommand, useHide } from "../app/hooks"; @@ -20,6 +21,7 @@ interface Props { const CommandPromptInner: React.FC = ({ initialInputValue }) => { const hide = useHide(); const [inputValue, setInputValue] = React.useState(initialInputValue); + const debouncedValue = useDebounce(inputValue, 100); const { completions, updateCompletions } = useCompletions(); const { select, currentValue, selectNext, selectPrev } = useSelectCompletion(); @@ -82,8 +84,8 @@ const CommandPromptInner: React.FC = ({ initialInputValue }) => { }; React.useEffect(() => { - updateCompletions(inputValue); - }, [inputValue]); + updateCompletions(debouncedValue); + }, [debouncedValue]); return ( diff --git a/src/console/hooks/useDebounce.ts b/src/console/hooks/useDebounce.ts new file mode 100644 index 0000000..838ff34 --- /dev/null +++ b/src/console/hooks/useDebounce.ts @@ -0,0 +1,19 @@ +import React from "react"; + +const useDebounce = (value: T, delay: number) => { + const [debouncedValue, setDebouncedValue] = React.useState(value); + + React.useEffect(() => { + const timer = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; +}; + +export default useDebounce; -- cgit v1.2.3 From cc427e967d63523b90ecc1c6aa2c864cb13b4b58 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 5 May 2022 10:17:20 +0000 Subject: Pass source string via argument --- src/console/completion/hooks.ts | 17 ++++++----------- src/console/components/CommandPrompt.tsx | 6 +----- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/console/completion/hooks.ts b/src/console/completion/hooks.ts index 3a2ec33..a2e8bde 100644 --- a/src/console/completion/hooks.ts +++ b/src/console/completion/hooks.ts @@ -170,16 +170,12 @@ export const getPropertyCompletions = async ( return [{ name: "Properties", items }]; }; -export const useCompletions = () => { +export const useCompletions = (source: string) => { const state = React.useContext(CompletionStateContext); const dispatch = React.useContext(CompletionDispatchContext); const commandLineParser = React.useMemo(() => new CommandLineParser(), []); const [completionTypes] = useGetCompletionTypes(); - const updateCompletions = React.useCallback((source: string) => { - dispatch(actions.setCompletionSource(source)); - }, []); - const queryCompletions = React.useCallback( (text: string, completionTypes: CompletionType[]) => { const phase = commandLineParser.inputPhase(text); @@ -233,16 +229,15 @@ export const useCompletions = () => { ); React.useEffect(() => { + dispatch(actions.setCompletionSource(source)); + if (typeof completionTypes === "undefined") { return; } - queryCompletions(state.completionSource, completionTypes); - }, [state.completionSource, completionTypes]); + queryCompletions(source, completionTypes); + }, [source, completionTypes]); - return { - completions: state.completions, - updateCompletions, - }; + return { completions: state.completions }; }; export const useSelectCompletion = () => { diff --git a/src/console/components/CommandPrompt.tsx b/src/console/components/CommandPrompt.tsx index 0312fe4..89acf57 100644 --- a/src/console/components/CommandPrompt.tsx +++ b/src/console/components/CommandPrompt.tsx @@ -22,7 +22,7 @@ const CommandPromptInner: React.FC = ({ initialInputValue }) => { const hide = useHide(); const [inputValue, setInputValue] = React.useState(initialInputValue); const debouncedValue = useDebounce(inputValue, 100); - const { completions, updateCompletions } = useCompletions(); + const { completions } = useCompletions(debouncedValue); const { select, currentValue, selectNext, selectPrev } = useSelectCompletion(); const execCommand = useExecCommand(); @@ -83,10 +83,6 @@ const CommandPromptInner: React.FC = ({ initialInputValue }) => { setInputValue(text); }; - React.useEffect(() => { - updateCompletions(debouncedValue); - }, [debouncedValue]); - return ( Date: Thu, 5 May 2022 14:26:06 +0000 Subject: Await loading completions done on selecting items --- src/console/completion/hooks.ts | 38 +++++++++++++++++++++++--------- src/console/components/CommandPrompt.tsx | 30 +++++++++++++++++++++---- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/console/completion/hooks.ts b/src/console/completion/hooks.ts index a2e8bde..cc6cd30 100644 --- a/src/console/completion/hooks.ts +++ b/src/console/completion/hooks.ts @@ -175,6 +175,7 @@ export const useCompletions = (source: string) => { const dispatch = React.useContext(CompletionDispatchContext); const commandLineParser = React.useMemo(() => new CommandLineParser(), []); const [completionTypes] = useGetCompletionTypes(); + const [loading, setLoading] = React.useState(false); const queryCompletions = React.useCallback( (text: string, completionTypes: CompletionType[]) => { @@ -192,40 +193,57 @@ export const useCompletions = (source: string) => { return; } } + + setLoading(true); switch (cmd?.command) { case Command.Open: case Command.TabOpen: case Command.WindowOpen: getOpenCompletions(cmd.command, cmd.args, completionTypes).then( - (completions) => dispatch(actions.setCompletions(completions)) + (completions) => { + dispatch(actions.setCompletions(completions)); + setLoading(false); + } ); break; case Command.Buffer: getTabCompletions(cmd.command, cmd.args, false).then( - (completions) => dispatch(actions.setCompletions(completions)) + (completions) => { + dispatch(actions.setCompletions(completions)); + setLoading(false); + } ); break; case Command.BufferDelete: case Command.BuffersDelete: - getTabCompletions(cmd.command, cmd.args, true).then((completions) => - dispatch(actions.setCompletions(completions)) + getTabCompletions(cmd.command, cmd.args, true).then( + (completions) => { + dispatch(actions.setCompletions(completions)); + setLoading(false); + } ); break; case Command.BufferDeleteForce: case Command.BuffersDeleteForce: getTabCompletions(cmd.command, cmd.args, false).then( - (completions) => dispatch(actions.setCompletions(completions)) + (completions) => { + dispatch(actions.setCompletions(completions)); + setLoading(false); + } ); break; case Command.Set: - getPropertyCompletions(cmd.command, cmd.args).then((completions) => - dispatch(actions.setCompletions(completions)) + getPropertyCompletions(cmd.command, cmd.args).then( + (completions) => { + dispatch(actions.setCompletions(completions)); + setLoading(false); + } ); break; } } }, - [dispatch] + [dispatch, source] ); React.useEffect(() => { @@ -237,7 +255,7 @@ export const useCompletions = (source: string) => { queryCompletions(source, completionTypes); }, [source, completionTypes]); - return { completions: state.completions }; + return { completions: state.completions, loading }; }; export const useSelectCompletion = () => { @@ -257,7 +275,7 @@ export const useSelectCompletion = () => { } const items = state.completions.map((g) => g.items).flat(); return items[state.select]?.value || ""; - }, [state.completionSource, state.select]); + }, [state.completionSource, state.select, state.completions]); return { select: state.select, diff --git a/src/console/components/CommandPrompt.tsx b/src/console/components/CommandPrompt.tsx index 89acf57..5d4cb6e 100644 --- a/src/console/components/CommandPrompt.tsx +++ b/src/console/components/CommandPrompt.tsx @@ -18,15 +18,23 @@ interface Props { initialInputValue: string; } +enum SelectQueueType { + SelectNext, + SelectPrev, +} + const CommandPromptInner: React.FC = ({ initialInputValue }) => { const hide = useHide(); const [inputValue, setInputValue] = React.useState(initialInputValue); const debouncedValue = useDebounce(inputValue, 100); - const { completions } = useCompletions(debouncedValue); + const { completions, loading } = useCompletions(debouncedValue); const { select, currentValue, selectNext, selectPrev } = useSelectCompletion(); const execCommand = useExecCommand(); + // The value is set after the user presses Tab (or Shift+Tab) key and waiting the completion + const [selecting, setSelecting] = React.useState(); + useAutoResize(); const onBlur = () => { @@ -67,9 +75,9 @@ const CommandPromptInner: React.FC = ({ initialInputValue }) => { execCommand(value); hide(); } else if (isNextKey(e)) { - selectNext(); + setSelecting(SelectQueueType.SelectNext); } else if (isPrevKey(e)) { - selectPrev(); + setSelecting(SelectQueueType.SelectPrev); } else { return; } @@ -78,6 +86,20 @@ const CommandPromptInner: React.FC = ({ initialInputValue }) => { e.preventDefault(); }; + React.useEffect(() => { + if (inputValue !== debouncedValue || loading) { + // The completions of the latest input value are not fetched + return; + } + if (selecting === SelectQueueType.SelectNext) { + selectNext(); + setSelecting(undefined); + } else if (selecting === SelectQueueType.SelectPrev) { + selectPrev(); + setSelecting(undefined); + } + }, [inputValue, debouncedValue, selecting, loading]); + const onChange = (e: React.ChangeEvent) => { const text = e.target.value; setInputValue(text); @@ -95,7 +117,7 @@ const CommandPromptInner: React.FC = ({ initialInputValue }) => { onBlur={onBlur} onKeyDown={onKeyDown} onChange={onChange} - value={select == -1 ? inputValue : currentValue} + value={select == -1 || loading ? inputValue : currentValue} /> ); -- cgit v1.2.3 From 2a6d6b0967c6f6e269c3eedf4bd6002aee26b9da Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 5 May 2022 14:48:41 +0000 Subject: Stabilize test --- e2e/completion.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/e2e/completion.test.ts b/e2e/completion.test.ts index 258353c..8fddc0a 100644 --- a/e2e/completion.test.ts +++ b/e2e/completion.test.ts @@ -41,11 +41,13 @@ describe("general completion test", () => { const console = await page.showConsole(); await console.inputKeys("b"); - const groups = await console.getCompletions(); - const items = groups[0].items; - assert.ok(items[0].text.startsWith("buffer")); - assert.ok(items[1].text.startsWith("bdelete")); - assert.ok(items[2].text.startsWith("bdeletes")); + await eventually(async () => { + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.ok(items[0].text.startsWith("buffer")); + assert.ok(items[1].text.startsWith("bdelete")); + assert.ok(items[2].text.startsWith("bdeletes")); + }); }); // > byffer -- cgit v1.2.3