Skip to content

Commit a3e3fe9

Browse files
committed
Refactor command line commands and keybindings
- Renamed command IDs for clarity and consistency. - Consolidated command registration logic in CommandLine component. - Updated built-in command contributions to reflect new command IDs. - Removed deprecated interaction commands and context. - Enhanced command routing by simplifying event handling. - Improved keybinding registration for command line interactions.
1 parent f3c44df commit a3e3fe9

18 files changed

Lines changed: 516 additions & 843 deletions

packages/ui/lib/app.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { useActivePanelNavigation } from "@/features/panels/panelControllers";
1515
import { Terminal, TerminalToolbar } from "@/features/terminal/Terminal";
1616
import { useSystemTheme } from "@/features/themes/useSystemTheme";
1717
import { useFocusContext } from "@/focusContext";
18-
import { useInteractionCommands } from "@/hooks/useInteractionCommands";
1918
import { useViewerEditorState } from "@/hooks/useViewerEditorState";
2019
import { useWorkspacePersistenceProcess, useWorkspaceRestoreProcess } from "@/processes/workspace-session/model/useWorkspaceSessionProcess";
2120
import baseStyles from "@/styles/base.module.css";
@@ -136,7 +135,6 @@ export const App = forwardRef<AppHandle, { widget: React.ReactNode }>(function A
136135
viewerOpen,
137136
});
138137

139-
useInteractionCommands();
140138
useCommandRouting(rootRef);
141139

142140
useEffect(() => {

packages/ui/lib/appServices.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { CommandRegistryProvider } from "@/features/commands/commands";
22
import { FileSystemWatchRegistryProvider } from "@/features/file-system/fs";
33
import { FocusProvider } from "@/focusContext";
4-
import { InteractionProvider } from "@/interactionContext";
54
import { ViewerEditorRegistryProvider } from "@/viewerEditorRegistry";
65
import type { ReactNode } from "react";
76

@@ -10,9 +9,7 @@ export function AppServicesProvider({ children }: { children: ReactNode }) {
109
<FileSystemWatchRegistryProvider>
1110
<CommandRegistryProvider>
1211
<FocusProvider>
13-
<InteractionProvider>
14-
<ViewerEditorRegistryProvider>{children}</ViewerEditorRegistryProvider>
15-
</InteractionProvider>
12+
<ViewerEditorRegistryProvider>{children}</ViewerEditorRegistryProvider>
1613
</FocusProvider>
1714
</CommandRegistryProvider>
1815
</FileSystemWatchRegistryProvider>

packages/ui/lib/components/AutocompleteInput/AutocompleteInput.tsx

Lines changed: 114 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1-
import { useInteractionContext } from "@/interactionContext";
2-
import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
1+
import {
2+
ACCEPT,
3+
CANCEL,
4+
CURSOR_DOWN,
5+
CURSOR_END,
6+
CURSOR_HOME,
7+
CURSOR_PAGE_DOWN,
8+
CURSOR_PAGE_UP,
9+
CURSOR_UP,
10+
} from "@/features/commands/commandIds";
11+
import { useCommandRegistry } from "@/features/commands/commands";
12+
import { useFocusContext, useManagedFocusLayer } from "@/focusContext";
13+
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
314
import styles from "./AutocompleteInput.module.css";
415

516
export interface AutocompleteOption {
@@ -46,11 +57,11 @@ export function AutocompleteInput({
4657
enterKeyHint,
4758
keepOpenOnSelect = false,
4859
}: AutocompleteInputProps) {
49-
const interactionContext = useInteractionContext();
60+
const commandRegistry = useCommandRegistry();
61+
const focusContext = useFocusContext();
5062
const localInputRef = useRef<HTMLInputElement>(null);
5163
const mergedInputRef = inputRef ?? localInputRef;
5264
const [open, setOpen] = useState(false);
53-
const [focused, setFocused] = useState(false);
5465
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
5566
const pointerDownRef = useRef(false);
5667
const dropdownRef = useRef<HTMLDivElement>(null);
@@ -69,20 +80,45 @@ export function AutocompleteInput({
6980
[groups],
7081
);
7182
const dropdownOpen = open && flattened.length > 0;
83+
useManagedFocusLayer("autocomplete", dropdownOpen);
7284
const flattenedRef = useRef(flattened);
7385
const selectedIndexRef = useRef(selectedIndex);
7486
const dropdownOpenRef = useRef(dropdownOpen);
7587
flattenedRef.current = flattened;
7688
selectedIndexRef.current = selectedIndex;
7789
dropdownOpenRef.current = dropdownOpen;
7890

79-
const moveInputCursor = useCallback((position: "start" | "end") => {
80-
const input = mergedInputRef.current;
81-
if (!input) return;
82-
const nextPosition = position === "start" ? 0 : input.value.length;
83-
input.focus();
84-
input.setSelectionRange(nextPosition, nextPosition);
85-
}, [mergedInputRef]);
91+
useEffect(() => {
92+
return focusContext.registerAdapter("autocomplete", {
93+
focus() {
94+
mergedInputRef.current?.focus();
95+
},
96+
contains(node) {
97+
return node instanceof Node
98+
? mergedInputRef.current?.contains(node) === true || dropdownRef.current?.contains(node) === true
99+
: false;
100+
},
101+
isEditableTarget(node) {
102+
return mergedInputRef.current?.contains(node as Node) === true;
103+
},
104+
allowCommandRouting(event) {
105+
if (!dropdownOpenRef.current) return false;
106+
switch (event.key) {
107+
case "ArrowUp":
108+
case "ArrowDown":
109+
case "PageUp":
110+
case "PageDown":
111+
case "Home":
112+
case "End":
113+
case "Enter":
114+
case "Escape":
115+
return true;
116+
default:
117+
return false;
118+
}
119+
},
120+
});
121+
}, [focusContext, mergedInputRef]);
86122

87123
useEffect(() => {
88124
setSelectedIndex((current) => {
@@ -104,74 +140,73 @@ export function AutocompleteInput({
104140
commitSelectionRef.current = commitSelection;
105141

106142
useEffect(() => {
107-
return interactionContext.registerController({
108-
contains(node) {
109-
const input = mergedInputRef.current;
110-
const dropdown = dropdownRef.current;
111-
if (!(node instanceof Node)) return false;
112-
return Boolean((input && input.contains(node)) || (dropdown && dropdown.contains(node)));
113-
},
114-
isActive() {
115-
return focused;
116-
},
117-
handleIntent(intent) {
118-
switch (intent) {
119-
case "cancel":
120-
if (!dropdownOpenRef.current) return false;
121-
setOpen(false);
122-
return true;
123-
case "cursorDown":
124-
if (!dropdownOpenRef.current) return false;
125-
setSelectedIndex((current) =>
126-
current === null ? 0 : Math.min(flattenedRef.current.length - 1, current + 1),
127-
);
128-
return true;
129-
case "cursorUp":
130-
if (!dropdownOpenRef.current) return false;
131-
setSelectedIndex((current) =>
132-
current === null ? Math.max(0, flattenedRef.current.length - 1) : Math.max(0, current - 1),
133-
);
134-
return true;
135-
case "cursorPageDown":
136-
if (!dropdownOpenRef.current) return false;
137-
setSelectedIndex((current) =>
138-
current === null ? 0 : Math.min(flattenedRef.current.length - 1, current + PAGE_STEP),
139-
);
140-
return true;
141-
case "cursorPageUp":
142-
if (!dropdownOpenRef.current) return false;
143-
setSelectedIndex((current) => (current === null ? 0 : Math.max(0, current - PAGE_STEP)));
144-
return true;
145-
case "cursorHome":
146-
if (!dropdownOpenRef.current) {
147-
moveInputCursor("start");
148-
return true;
149-
}
150-
if (flattenedRef.current.length === 0) return false;
151-
setSelectedIndex(0);
152-
return true;
153-
case "cursorEnd":
154-
if (!dropdownOpenRef.current) {
155-
moveInputCursor("end");
156-
return true;
157-
}
158-
if (flattenedRef.current.length === 0) return false;
159-
setSelectedIndex(flattenedRef.current.length - 1);
160-
return true;
161-
case "accept": {
162-
if (!dropdownOpenRef.current) return false;
163-
if (selectedIndexRef.current === null) return false;
164-
const selected = flattenedRef.current[selectedIndexRef.current];
165-
if (!selected) return false;
166-
commitSelectionRef.current(selected.option.value);
167-
return true;
168-
}
169-
default:
170-
return false;
171-
}
172-
},
173-
});
174-
}, [focused, interactionContext, mergedInputRef, moveInputCursor]);
143+
if (!dropdownOpen) return;
144+
const disposables = [
145+
commandRegistry.registerCommand(
146+
CANCEL,
147+
() => {
148+
setOpen(false);
149+
setSelectedIndex(null);
150+
},
151+
),
152+
commandRegistry.registerCommand(
153+
CURSOR_DOWN,
154+
() => {
155+
setSelectedIndex((current) =>
156+
current === null ? 0 : Math.min(flattenedRef.current.length - 1, current + 1),
157+
);
158+
},
159+
),
160+
commandRegistry.registerCommand(
161+
CURSOR_UP,
162+
() => {
163+
setSelectedIndex((current) =>
164+
current === null ? Math.max(0, flattenedRef.current.length - 1) : Math.max(0, current - 1),
165+
);
166+
},
167+
),
168+
commandRegistry.registerCommand(
169+
CURSOR_PAGE_DOWN,
170+
() => {
171+
setSelectedIndex((current) =>
172+
current === null ? 0 : Math.min(flattenedRef.current.length - 1, current + PAGE_STEP),
173+
);
174+
},
175+
),
176+
commandRegistry.registerCommand(
177+
CURSOR_PAGE_UP,
178+
() => {
179+
setSelectedIndex((current) => (current === null ? 0 : Math.max(0, current - PAGE_STEP)));
180+
},
181+
),
182+
commandRegistry.registerCommand(
183+
CURSOR_HOME,
184+
() => {
185+
if (flattenedRef.current.length === 0) return;
186+
setSelectedIndex(0);
187+
},
188+
),
189+
commandRegistry.registerCommand(
190+
CURSOR_END,
191+
() => {
192+
if (flattenedRef.current.length === 0) return;
193+
setSelectedIndex(flattenedRef.current.length - 1);
194+
},
195+
),
196+
commandRegistry.registerCommand(
197+
ACCEPT,
198+
() => {
199+
if (selectedIndexRef.current === null) return;
200+
const selected = flattenedRef.current[selectedIndexRef.current];
201+
if (!selected) return;
202+
commitSelectionRef.current(selected.option.value);
203+
},
204+
),
205+
];
206+
return () => {
207+
disposables.forEach((dispose) => dispose());
208+
};
209+
}, [commandRegistry, dropdownOpen]);
175210

176211
useEffect(() => {
177212
const dropdown = dropdownRef.current;
@@ -238,11 +273,7 @@ export function AutocompleteInput({
238273
setOpen(true);
239274
setSelectedIndex(null);
240275
}}
241-
onFocus={() => {
242-
setFocused(true);
243-
}}
244276
onBlur={() => {
245-
setFocused(false);
246277
if (pointerDownRef.current) return;
247278
setOpen(false);
248279
setSelectedIndex(null);

0 commit comments

Comments
 (0)