From 3f347b66be898b54ffa45265ae9cce7b35e04433 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
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<Props> = ({ 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<SelectQueueType>();
+
   useAutoResize();
 
   const onBlur = () => {
@@ -67,9 +75,9 @@ const CommandPromptInner: React.FC<Props> = ({ 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<Props> = ({ 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<HTMLInputElement>) => {
     const text = e.target.value;
     setInputValue(text);
@@ -95,7 +117,7 @@ const CommandPromptInner: React.FC<Props> = ({ initialInputValue }) => {
         onBlur={onBlur}
         onKeyDown={onKeyDown}
         onChange={onChange}
-        value={select == -1 ? inputValue : currentValue}
+        value={select == -1 || loading ? inputValue : currentValue}
       />
     </ConsoleWrapper>
   );
-- 
cgit v1.2.3