@tooee/shell 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/command-palette-provider.d.ts +5 -0
- package/dist/command-palette-provider.d.ts.map +1 -0
- package/dist/command-palette-provider.js +28 -0
- package/dist/command-palette-provider.js.map +1 -0
- package/dist/commands.d.ts +9 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +52 -1
- package/dist/commands.js.map +1 -1
- package/dist/copy-hook.d.ts +12 -0
- package/dist/copy-hook.d.ts.map +1 -0
- package/dist/copy-hook.js +35 -0
- package/dist/copy-hook.js.map +1 -0
- package/dist/copy-on-select.d.ts.map +1 -1
- package/dist/copy-on-select.js +1 -3
- package/dist/copy-on-select.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/navigation.d.ts +24 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +239 -0
- package/dist/navigation.js.map +1 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +4 -1
- package/dist/provider.js.map +1 -1
- package/dist/search-hook.d.ts +14 -0
- package/dist/search-hook.d.ts.map +1 -0
- package/dist/search-hook.js +120 -0
- package/dist/search-hook.js.map +1 -0
- package/package.json +20 -20
- package/src/command-palette-provider.tsx +39 -0
- package/src/commands.ts +55 -1
- package/src/copy-hook.ts +45 -0
- package/src/copy-on-select.ts +1 -4
- package/src/index.ts +15 -5
- package/src/navigation.ts +297 -0
- package/src/provider.tsx +6 -1
- package/src/search-hook.ts +147 -0
- package/dist/command-palette.d.ts +0 -9
- package/dist/command-palette.d.ts.map +0 -1
- package/dist/command-palette.js +0 -51
- package/dist/command-palette.js.map +0 -1
- package/dist/modal.d.ts +0 -33
- package/dist/modal.d.ts.map +0 -1
- package/dist/modal.js +0 -395
- package/dist/modal.js.map +0 -1
- package/src/command-palette.ts +0 -73
- package/src/modal.ts +0 -454
package/src/provider.tsx
CHANGED
|
@@ -4,7 +4,9 @@ import { ThemeSwitcherProvider } from "@tooee/themes"
|
|
|
4
4
|
import { CommandProvider, useProvideCommandContext, type Mode } from "@tooee/commands"
|
|
5
5
|
import { ToastProvider, useToast, type ToastController } from "@tooee/toasts"
|
|
6
6
|
import { OverlayProvider } from "./overlay.js"
|
|
7
|
+
import { CommandPaletteProvider } from "./command-palette-provider.js"
|
|
7
8
|
import { useCopyOnSelect } from "./copy-on-select.js"
|
|
9
|
+
import { useDebugConsoleCommand } from "./commands.js"
|
|
8
10
|
|
|
9
11
|
declare module "@tooee/commands" {
|
|
10
12
|
interface CommandContext {
|
|
@@ -49,7 +51,9 @@ function TooeeProviderInner({
|
|
|
49
51
|
<CommandProvider leader={leader} keymap={config.keys} initialMode={initialMode}>
|
|
50
52
|
<ToastProvider>
|
|
51
53
|
<ToastContextBridge>
|
|
52
|
-
<OverlayProvider>
|
|
54
|
+
<OverlayProvider>
|
|
55
|
+
<CommandPaletteProvider>{children}</CommandPaletteProvider>
|
|
56
|
+
</OverlayProvider>
|
|
53
57
|
</ToastContextBridge>
|
|
54
58
|
</ToastProvider>
|
|
55
59
|
</CommandProvider>
|
|
@@ -65,6 +69,7 @@ function ToastContextBridge({ children }: { children: ReactNode }) {
|
|
|
65
69
|
}))
|
|
66
70
|
|
|
67
71
|
useCopyOnSelect()
|
|
72
|
+
useDebugConsoleCommand()
|
|
68
73
|
|
|
69
74
|
return <>{children}</>
|
|
70
75
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from "react"
|
|
2
|
+
import { useCommand, useMode, useSetMode, type Mode } from "@tooee/commands"
|
|
3
|
+
|
|
4
|
+
export interface UseSearchOptions {
|
|
5
|
+
match: (query: string) => number[]
|
|
6
|
+
onJump: (index: number) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SearchState {
|
|
10
|
+
searchQuery: string
|
|
11
|
+
searchActive: boolean
|
|
12
|
+
setSearchQuery: (query: string) => void
|
|
13
|
+
matchingLines: number[]
|
|
14
|
+
currentMatchIndex: number
|
|
15
|
+
submitSearch: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const EMPTY: number[] = []
|
|
19
|
+
const CURSOR_MODES: Mode[] = ["cursor"]
|
|
20
|
+
const ALL_MODES: Mode[] = ["cursor", "select", "insert"]
|
|
21
|
+
|
|
22
|
+
export function useSearch({ match, onJump }: UseSearchOptions): SearchState {
|
|
23
|
+
const mode = useMode()
|
|
24
|
+
const setMode = useSetMode()
|
|
25
|
+
|
|
26
|
+
const [searchQuery, setSearchQuery] = useState("")
|
|
27
|
+
const [searchActive, setSearchActive] = useState(false)
|
|
28
|
+
const [currentMatchIndex, setCurrentMatchIndex] = useState(0)
|
|
29
|
+
const [committedQuery, setCommittedQuery] = useState("")
|
|
30
|
+
const preSearchModeRef = useRef<Mode>("cursor")
|
|
31
|
+
|
|
32
|
+
const matchRef = useRef(match)
|
|
33
|
+
matchRef.current = match
|
|
34
|
+
|
|
35
|
+
const onJumpRef = useRef(onJump)
|
|
36
|
+
onJumpRef.current = onJump
|
|
37
|
+
|
|
38
|
+
const activeQuery = searchActive ? searchQuery : committedQuery
|
|
39
|
+
|
|
40
|
+
const matchingLines = useMemo(() => {
|
|
41
|
+
if (!activeQuery) return EMPTY
|
|
42
|
+
return matchRef.current(activeQuery)
|
|
43
|
+
}, [activeQuery])
|
|
44
|
+
|
|
45
|
+
const matchingLinesRef = useRef(matchingLines)
|
|
46
|
+
matchingLinesRef.current = matchingLines
|
|
47
|
+
|
|
48
|
+
// Imperatively set search query, reset match index, and jump to first match.
|
|
49
|
+
const updateSearchQuery = useCallback((query: string) => {
|
|
50
|
+
setSearchQuery(query)
|
|
51
|
+
setCurrentMatchIndex(0)
|
|
52
|
+
const matches = query ? matchRef.current(query) : []
|
|
53
|
+
if (matches[0] != null) {
|
|
54
|
+
onJumpRef.current(matches[0])
|
|
55
|
+
}
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
58
|
+
useCommand({
|
|
59
|
+
id: "cursor-search-start",
|
|
60
|
+
title: "Search",
|
|
61
|
+
hotkey: "/",
|
|
62
|
+
modes: CURSOR_MODES,
|
|
63
|
+
handler: () => {
|
|
64
|
+
preSearchModeRef.current = mode
|
|
65
|
+
setSearchActive(true)
|
|
66
|
+
setSearchQuery("")
|
|
67
|
+
setMode("insert")
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
useCommand({
|
|
72
|
+
id: "cursor-search-next",
|
|
73
|
+
title: "Next match",
|
|
74
|
+
hotkey: "n",
|
|
75
|
+
modes: CURSOR_MODES,
|
|
76
|
+
when: () => !searchActive,
|
|
77
|
+
handler: () => {
|
|
78
|
+
const matches = matchingLinesRef.current
|
|
79
|
+
if (matches.length === 0) return
|
|
80
|
+
|
|
81
|
+
setCurrentMatchIndex((index) => {
|
|
82
|
+
const nextIndex = (index + 1) % matches.length
|
|
83
|
+
const nextMatch = matches[nextIndex]
|
|
84
|
+
if (nextMatch != null) {
|
|
85
|
+
onJumpRef.current(nextMatch)
|
|
86
|
+
}
|
|
87
|
+
return nextIndex
|
|
88
|
+
})
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
useCommand({
|
|
93
|
+
id: "cursor-search-prev",
|
|
94
|
+
title: "Previous match",
|
|
95
|
+
hotkey: "shift+n",
|
|
96
|
+
modes: CURSOR_MODES,
|
|
97
|
+
when: () => !searchActive,
|
|
98
|
+
handler: () => {
|
|
99
|
+
const matches = matchingLinesRef.current
|
|
100
|
+
if (matches.length === 0) return
|
|
101
|
+
|
|
102
|
+
setCurrentMatchIndex((index) => {
|
|
103
|
+
const nextIndex = (index - 1 + matches.length) % matches.length
|
|
104
|
+
const nextMatch = matches[nextIndex]
|
|
105
|
+
if (nextMatch != null) {
|
|
106
|
+
onJumpRef.current(nextMatch)
|
|
107
|
+
}
|
|
108
|
+
return nextIndex
|
|
109
|
+
})
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
useCommand({
|
|
114
|
+
id: "search-cancel",
|
|
115
|
+
title: "Cancel search",
|
|
116
|
+
hotkey: "escape",
|
|
117
|
+
modes: ALL_MODES,
|
|
118
|
+
when: () => searchActive,
|
|
119
|
+
handler: () => {
|
|
120
|
+
setSearchActive(false)
|
|
121
|
+
setSearchQuery("")
|
|
122
|
+
setCommittedQuery("")
|
|
123
|
+
setCurrentMatchIndex(0)
|
|
124
|
+
setMode(preSearchModeRef.current)
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const submitSearch = useCallback(() => {
|
|
129
|
+
setCommittedQuery(searchQuery)
|
|
130
|
+
setSearchActive(false)
|
|
131
|
+
setCurrentMatchIndex(0)
|
|
132
|
+
const matches = searchQuery ? matchRef.current(searchQuery) : []
|
|
133
|
+
if (matches[0] != null) {
|
|
134
|
+
onJumpRef.current(matches[0])
|
|
135
|
+
}
|
|
136
|
+
setMode(preSearchModeRef.current)
|
|
137
|
+
}, [searchQuery, setMode])
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
searchQuery,
|
|
141
|
+
searchActive,
|
|
142
|
+
setSearchQuery: updateSearchQuery,
|
|
143
|
+
matchingLines,
|
|
144
|
+
currentMatchIndex,
|
|
145
|
+
submitSearch,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { CommandPaletteEntry } from "@tooee/renderers";
|
|
2
|
-
export interface CommandPaletteState {
|
|
3
|
-
isOpen: boolean;
|
|
4
|
-
open: () => void;
|
|
5
|
-
close: () => void;
|
|
6
|
-
entries: CommandPaletteEntry[];
|
|
7
|
-
}
|
|
8
|
-
export declare function useCommandPalette(): CommandPaletteState;
|
|
9
|
-
//# sourceMappingURL=command-palette.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"command-palette.d.ts","sourceRoot":"","sources":["../src/command-palette.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAM3D,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,OAAO,EAAE,mBAAmB,EAAE,CAAA;CAC/B;AAED,wBAAgB,iBAAiB,IAAI,mBAAmB,CAqDvD"}
|
package/dist/command-palette.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { useCallback, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { createElement } from "react";
|
|
3
|
-
import { useCommandContext, useCommand, useMode } from "@tooee/commands";
|
|
4
|
-
import { useOverlay } from "@tooee/overlays";
|
|
5
|
-
import { CommandPaletteOverlay } from "./CommandPaletteOverlay.js";
|
|
6
|
-
const DEFAULT_MODES = ["cursor"];
|
|
7
|
-
const OVERLAY_ID = "command-palette";
|
|
8
|
-
export function useCommandPalette() {
|
|
9
|
-
const { commands } = useCommandContext();
|
|
10
|
-
const mode = useMode();
|
|
11
|
-
const overlay = useOverlay();
|
|
12
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
13
|
-
const launchModeRef = useRef(mode);
|
|
14
|
-
const launchMode = launchModeRef.current;
|
|
15
|
-
const entries = useMemo(() => {
|
|
16
|
-
return commands
|
|
17
|
-
.filter((cmd) => !cmd.hidden)
|
|
18
|
-
.filter((cmd) => {
|
|
19
|
-
const cmdModes = cmd.modes ?? DEFAULT_MODES;
|
|
20
|
-
return cmdModes.includes(launchMode);
|
|
21
|
-
})
|
|
22
|
-
.map((cmd) => ({
|
|
23
|
-
id: cmd.id,
|
|
24
|
-
title: cmd.title,
|
|
25
|
-
hotkey: cmd.defaultHotkey,
|
|
26
|
-
category: cmd.category,
|
|
27
|
-
icon: cmd.icon,
|
|
28
|
-
}));
|
|
29
|
-
}, [commands, launchMode]);
|
|
30
|
-
const close = useCallback(() => {
|
|
31
|
-
setIsOpen(false);
|
|
32
|
-
overlay.hide(OVERLAY_ID);
|
|
33
|
-
}, [overlay]);
|
|
34
|
-
const open = useCallback(() => {
|
|
35
|
-
launchModeRef.current = mode;
|
|
36
|
-
setIsOpen(true);
|
|
37
|
-
overlay.open(OVERLAY_ID, ({ close }) => createElement(CommandPaletteOverlay, {
|
|
38
|
-
launchMode: mode,
|
|
39
|
-
close: () => close(),
|
|
40
|
-
}), null, { mode: "insert", dismissOnEscape: true, onClose: () => setIsOpen(false) });
|
|
41
|
-
}, [overlay, mode]);
|
|
42
|
-
useCommand({
|
|
43
|
-
id: "command-palette",
|
|
44
|
-
title: "Command Palette",
|
|
45
|
-
hotkey: ":",
|
|
46
|
-
modes: ["cursor"],
|
|
47
|
-
handler: open,
|
|
48
|
-
});
|
|
49
|
-
return { isOpen, open, close, entries };
|
|
50
|
-
}
|
|
51
|
-
//# sourceMappingURL=command-palette.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"command-palette.js","sourceRoot":"","sources":["../src/command-palette.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AACrC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAG5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAElE,MAAM,aAAa,GAAW,CAAC,QAAQ,CAAC,CAAA;AACxC,MAAM,UAAU,GAAG,iBAAiB,CAAA;AASpC,MAAM,UAAU,iBAAiB;IAC/B,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACxC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IACtB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,aAAa,GAAG,MAAM,CAAO,IAAI,CAAC,CAAA;IAExC,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,CAAA;IACxC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE;QAC3B,OAAO,QAAQ;aACZ,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;aAC5B,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;YACd,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,IAAI,aAAa,CAAA;YAC3C,OAAO,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QACtC,CAAC,CAAC;aACD,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACb,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,GAAG,CAAC,aAAa;YACzB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC,CAAC,CAAA;IACP,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAA;IAE1B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,SAAS,CAAC,KAAK,CAAC,CAAA;QAChB,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC1B,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEb,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,aAAa,CAAC,OAAO,GAAG,IAAI,CAAA;QAC5B,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,OAAO,CAAC,IAAI,CACV,UAAU,EACV,CAAC,EAAE,KAAK,EAAoD,EAAE,EAAE,CAC9D,aAAa,CAAC,qBAAqB,EAAE;YACnC,UAAU,EAAE,IAAI;YAChB,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE;SACrB,CAAC,EACJ,IAAI,EACJ,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAC3E,CAAA;IACH,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAA;IAEnB,UAAU,CAAC;QACT,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,iBAAiB;QACxB,MAAM,EAAE,GAAG;QACX,KAAK,EAAE,CAAC,QAAQ,CAAC;QACjB,OAAO,EAAE,IAAI;KACd,CAAC,CAAA;IAEF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AACzC,CAAC"}
|
package/dist/modal.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { type Mode } from "@tooee/commands";
|
|
2
|
-
export interface Position {
|
|
3
|
-
line: number;
|
|
4
|
-
col: number;
|
|
5
|
-
}
|
|
6
|
-
export interface ModalNavigationState {
|
|
7
|
-
mode: Mode;
|
|
8
|
-
setMode: (mode: Mode) => void;
|
|
9
|
-
cursor: Position | null;
|
|
10
|
-
selection: {
|
|
11
|
-
start: Position;
|
|
12
|
-
end: Position;
|
|
13
|
-
} | null;
|
|
14
|
-
toggledIndices: Set<number>;
|
|
15
|
-
searchQuery: string;
|
|
16
|
-
searchActive: boolean;
|
|
17
|
-
setSearchQuery: (query: string) => void;
|
|
18
|
-
matchingLines: number[];
|
|
19
|
-
currentMatchIndex: number;
|
|
20
|
-
submitSearch: () => void;
|
|
21
|
-
}
|
|
22
|
-
export interface ModalNavigationOptions {
|
|
23
|
-
totalLines: number;
|
|
24
|
-
viewportHeight?: number;
|
|
25
|
-
getText?: () => string | undefined;
|
|
26
|
-
blockCount?: number;
|
|
27
|
-
blockLineMap?: number[];
|
|
28
|
-
/** Offset to subtract from search line numbers to get block indices (for when getText has different line structure than visual) */
|
|
29
|
-
searchLineOffset?: number;
|
|
30
|
-
multiSelect?: boolean;
|
|
31
|
-
}
|
|
32
|
-
export declare function useModalNavigationCommands(opts: ModalNavigationOptions): ModalNavigationState;
|
|
33
|
-
//# sourceMappingURL=modal.d.ts.map
|
package/dist/modal.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.ts"],"names":[],"mappings":"AACA,OAAO,EAAmC,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAK5E,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,IAAI,CAAA;IACV,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IAC7B,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAA;IACpD,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IACzB,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,mIAAmI;IACnI,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,sBAAsB,GAAG,oBAAoB,CAia7F"}
|
package/dist/modal.js
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
|
2
|
-
import { useCommand, useMode, useSetMode } from "@tooee/commands";
|
|
3
|
-
import { copyToClipboard } from "@tooee/clipboard";
|
|
4
|
-
import { useTerminalDimensions } from "@opentui/react";
|
|
5
|
-
import { findMatchingLines } from "./search.js";
|
|
6
|
-
export function useModalNavigationCommands(opts) {
|
|
7
|
-
const { height: terminalHeight } = useTerminalDimensions();
|
|
8
|
-
const { totalLines, viewportHeight = terminalHeight - 2, getText, blockCount, blockLineMap, searchLineOffset = 0, multiSelect = false, } = opts;
|
|
9
|
-
const mode = useMode();
|
|
10
|
-
const setMode = useSetMode();
|
|
11
|
-
const [cursor, setCursor] = useState(null);
|
|
12
|
-
const [selectionAnchor, setSelectionAnchor] = useState(null);
|
|
13
|
-
const [toggledIndices, setToggledIndices] = useState(new Set());
|
|
14
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
15
|
-
const [searchActive, setSearchActive] = useState(false);
|
|
16
|
-
const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
|
|
17
|
-
const preSearchModeRef = useRef("cursor");
|
|
18
|
-
// Track the "committed" search query (persists after search bar closes)
|
|
19
|
-
const [committedQuery, setCommittedQuery] = useState("");
|
|
20
|
-
// Derived state: search matches computed from committed query + content
|
|
21
|
-
const matchingLines = useMemo(() => {
|
|
22
|
-
if (!committedQuery)
|
|
23
|
-
return [];
|
|
24
|
-
const text = getText?.();
|
|
25
|
-
return text ? findMatchingLines(text, committedQuery) : [];
|
|
26
|
-
}, [committedQuery]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
27
|
-
// Live-update committed query while search bar is open
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
if (searchActive && searchQuery) {
|
|
30
|
-
setCommittedQuery(searchQuery);
|
|
31
|
-
}
|
|
32
|
-
}, [searchActive, searchQuery]);
|
|
33
|
-
const searchQueryRef = useRef(searchQuery);
|
|
34
|
-
searchQueryRef.current = searchQuery;
|
|
35
|
-
const matchingLinesRef = useRef(matchingLines);
|
|
36
|
-
matchingLinesRef.current = matchingLines;
|
|
37
|
-
// Auto-jump to first match when search results change
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
setCurrentMatchIndex(0);
|
|
40
|
-
if (matchingLines.length > 0) {
|
|
41
|
-
setCursor((c) => (c ? { line: matchingLines[0], col: 0 } : c));
|
|
42
|
-
}
|
|
43
|
-
}, [matchingLines]);
|
|
44
|
-
const isBlockMode = blockCount != null;
|
|
45
|
-
const cursorMax = isBlockMode ? Math.max(0, blockCount - 1) : Math.max(0, totalLines - 1);
|
|
46
|
-
const clampCursor = useCallback((value) => Math.max(0, Math.min(value, cursorMax)), [cursorMax]);
|
|
47
|
-
// Clean up toggled indices when cursorMax shrinks (render-time state adjustment)
|
|
48
|
-
const [prevCursorMax, setPrevCursorMax] = useState(cursorMax);
|
|
49
|
-
if (cursorMax !== prevCursorMax) {
|
|
50
|
-
setPrevCursorMax(cursorMax);
|
|
51
|
-
setToggledIndices((prev) => {
|
|
52
|
-
const filtered = Array.from(prev).filter((index) => index <= cursorMax);
|
|
53
|
-
return filtered.length === prev.size ? prev : new Set(filtered);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
// When entering cursor mode, initialize cursor if not already set
|
|
57
|
-
const prevMode = useRef(null);
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (mode === "cursor" && prevMode.current !== "cursor" && prevMode.current !== "select") {
|
|
60
|
-
setCursor((existing) => {
|
|
61
|
-
// If cursor already exists (e.g. set by a command via palette), preserve it
|
|
62
|
-
if (existing)
|
|
63
|
-
return existing;
|
|
64
|
-
// If there's an active search match, start cursor there; otherwise start at scroll position
|
|
65
|
-
const matches = matchingLinesRef.current;
|
|
66
|
-
if (matches.length > 0) {
|
|
67
|
-
const matchLine = matches[currentMatchIndex] ?? matches[0];
|
|
68
|
-
// In block mode, convert search line to block index using offset
|
|
69
|
-
if (isBlockMode) {
|
|
70
|
-
const blockIndex = Math.max(0, matchLine - searchLineOffset);
|
|
71
|
-
return { line: Math.min(blockIndex, cursorMax), col: 0 };
|
|
72
|
-
}
|
|
73
|
-
return { line: matchLine, col: 0 };
|
|
74
|
-
}
|
|
75
|
-
// No search match - start at top
|
|
76
|
-
return { line: 0, col: 0 };
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
if (mode === "select" && prevMode.current === "cursor" && cursor) {
|
|
80
|
-
setSelectionAnchor({ ...cursor });
|
|
81
|
-
}
|
|
82
|
-
prevMode.current = mode;
|
|
83
|
-
}, [mode]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
84
|
-
// === CURSOR MODE ===
|
|
85
|
-
useCommand({
|
|
86
|
-
id: "cursor-down",
|
|
87
|
-
title: "Cursor down",
|
|
88
|
-
hotkey: "j",
|
|
89
|
-
modes: ["cursor"],
|
|
90
|
-
handler: () => {
|
|
91
|
-
setCursor((c) => {
|
|
92
|
-
if (!c)
|
|
93
|
-
return c;
|
|
94
|
-
return { line: clampCursor(c.line + 1), col: 0 };
|
|
95
|
-
});
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
useCommand({
|
|
99
|
-
id: "cursor-toggle",
|
|
100
|
-
title: "Toggle selection",
|
|
101
|
-
hotkey: "tab",
|
|
102
|
-
modes: ["cursor"],
|
|
103
|
-
when: () => multiSelect,
|
|
104
|
-
handler: () => {
|
|
105
|
-
setToggledIndices((prev) => {
|
|
106
|
-
if (!cursor)
|
|
107
|
-
return prev;
|
|
108
|
-
const next = new Set(prev);
|
|
109
|
-
const idx = cursor.line;
|
|
110
|
-
if (next.has(idx)) {
|
|
111
|
-
next.delete(idx);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
next.add(idx);
|
|
115
|
-
}
|
|
116
|
-
return next;
|
|
117
|
-
});
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
useCommand({
|
|
121
|
-
id: "cursor-toggle-up",
|
|
122
|
-
title: "Toggle and move up",
|
|
123
|
-
hotkey: "shift+tab",
|
|
124
|
-
modes: ["cursor"],
|
|
125
|
-
when: () => multiSelect,
|
|
126
|
-
handler: () => {
|
|
127
|
-
setToggledIndices((prev) => {
|
|
128
|
-
if (!cursor)
|
|
129
|
-
return prev;
|
|
130
|
-
const next = new Set(prev);
|
|
131
|
-
const idx = cursor.line;
|
|
132
|
-
if (next.has(idx)) {
|
|
133
|
-
next.delete(idx);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
next.add(idx);
|
|
137
|
-
}
|
|
138
|
-
return next;
|
|
139
|
-
});
|
|
140
|
-
setCursor((c) => {
|
|
141
|
-
if (!c)
|
|
142
|
-
return c;
|
|
143
|
-
return { line: clampCursor(c.line - 1), col: 0 };
|
|
144
|
-
});
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
useCommand({
|
|
148
|
-
id: "cursor-up",
|
|
149
|
-
title: "Cursor up",
|
|
150
|
-
hotkey: "k",
|
|
151
|
-
modes: ["cursor"],
|
|
152
|
-
handler: () => {
|
|
153
|
-
setCursor((c) => {
|
|
154
|
-
if (!c)
|
|
155
|
-
return c;
|
|
156
|
-
return { line: clampCursor(c.line - 1), col: 0 };
|
|
157
|
-
});
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
useCommand({
|
|
161
|
-
id: "cursor-half-down",
|
|
162
|
-
title: "Cursor half page down",
|
|
163
|
-
hotkey: "ctrl+d",
|
|
164
|
-
modes: ["cursor"],
|
|
165
|
-
handler: () => {
|
|
166
|
-
setCursor((c) => {
|
|
167
|
-
if (!c)
|
|
168
|
-
return c;
|
|
169
|
-
const step = isBlockMode ? Math.floor(cursorMax / 4) || 1 : Math.floor(viewportHeight / 2);
|
|
170
|
-
return { line: clampCursor(c.line + step), col: 0 };
|
|
171
|
-
});
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
useCommand({
|
|
175
|
-
id: "cursor-half-up",
|
|
176
|
-
title: "Cursor half page up",
|
|
177
|
-
hotkey: "ctrl+u",
|
|
178
|
-
modes: ["cursor"],
|
|
179
|
-
handler: () => {
|
|
180
|
-
setCursor((c) => {
|
|
181
|
-
if (!c)
|
|
182
|
-
return c;
|
|
183
|
-
const step = isBlockMode ? Math.floor(cursorMax / 4) || 1 : Math.floor(viewportHeight / 2);
|
|
184
|
-
return { line: clampCursor(c.line - step), col: 0 };
|
|
185
|
-
});
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
useCommand({
|
|
189
|
-
id: "cursor-top",
|
|
190
|
-
title: "Cursor to top",
|
|
191
|
-
hotkey: "g g",
|
|
192
|
-
modes: ["cursor"],
|
|
193
|
-
handler: () => {
|
|
194
|
-
setCursor({ line: 0, col: 0 });
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
useCommand({
|
|
198
|
-
id: "cursor-bottom",
|
|
199
|
-
title: "Cursor to bottom",
|
|
200
|
-
hotkey: "shift+g",
|
|
201
|
-
modes: ["cursor"],
|
|
202
|
-
handler: () => {
|
|
203
|
-
setCursor({ line: cursorMax, col: 0 });
|
|
204
|
-
},
|
|
205
|
-
});
|
|
206
|
-
useCommand({
|
|
207
|
-
id: "cursor-search-start",
|
|
208
|
-
title: "Search",
|
|
209
|
-
hotkey: "/",
|
|
210
|
-
modes: ["cursor"],
|
|
211
|
-
handler: () => {
|
|
212
|
-
preSearchModeRef.current = mode;
|
|
213
|
-
setSearchActive(true);
|
|
214
|
-
setSearchQuery("");
|
|
215
|
-
setMode("insert");
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
useCommand({
|
|
219
|
-
id: "cursor-search-next",
|
|
220
|
-
title: "Next match",
|
|
221
|
-
hotkey: "n",
|
|
222
|
-
modes: ["cursor"],
|
|
223
|
-
when: () => !searchActive,
|
|
224
|
-
handler: () => {
|
|
225
|
-
const matches = matchingLinesRef.current;
|
|
226
|
-
if (matches.length === 0)
|
|
227
|
-
return;
|
|
228
|
-
setCurrentMatchIndex((idx) => {
|
|
229
|
-
const next = (idx + 1) % matches.length;
|
|
230
|
-
const line = matches[next];
|
|
231
|
-
setCursor({ line, col: 0 });
|
|
232
|
-
return next;
|
|
233
|
-
});
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
useCommand({
|
|
237
|
-
id: "cursor-search-prev",
|
|
238
|
-
title: "Previous match",
|
|
239
|
-
hotkey: "shift+n",
|
|
240
|
-
modes: ["cursor"],
|
|
241
|
-
when: () => !searchActive,
|
|
242
|
-
handler: () => {
|
|
243
|
-
const matches = matchingLinesRef.current;
|
|
244
|
-
if (matches.length === 0)
|
|
245
|
-
return;
|
|
246
|
-
setCurrentMatchIndex((idx) => {
|
|
247
|
-
const next = (idx - 1 + matches.length) % matches.length;
|
|
248
|
-
const line = matches[next];
|
|
249
|
-
setCursor({ line, col: 0 });
|
|
250
|
-
return next;
|
|
251
|
-
});
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
// === SELECT MODE ===
|
|
255
|
-
useCommand({
|
|
256
|
-
id: "select-down",
|
|
257
|
-
title: "Extend selection down",
|
|
258
|
-
hotkey: "j",
|
|
259
|
-
modes: ["select"],
|
|
260
|
-
handler: () => {
|
|
261
|
-
setCursor((c) => {
|
|
262
|
-
if (!c)
|
|
263
|
-
return c;
|
|
264
|
-
return { line: clampCursor(c.line + 1), col: 0 };
|
|
265
|
-
});
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
useCommand({
|
|
269
|
-
id: "select-up",
|
|
270
|
-
title: "Extend selection up",
|
|
271
|
-
hotkey: "k",
|
|
272
|
-
modes: ["select"],
|
|
273
|
-
handler: () => {
|
|
274
|
-
setCursor((c) => {
|
|
275
|
-
if (!c)
|
|
276
|
-
return c;
|
|
277
|
-
return { line: clampCursor(c.line - 1), col: 0 };
|
|
278
|
-
});
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
useCommand({
|
|
282
|
-
id: "select-toggle",
|
|
283
|
-
title: "Toggle selection",
|
|
284
|
-
hotkey: "tab",
|
|
285
|
-
modes: ["select"],
|
|
286
|
-
when: () => multiSelect,
|
|
287
|
-
handler: () => {
|
|
288
|
-
setToggledIndices((prev) => {
|
|
289
|
-
if (!cursor)
|
|
290
|
-
return prev;
|
|
291
|
-
const next = new Set(prev);
|
|
292
|
-
const idx = cursor.line;
|
|
293
|
-
if (next.has(idx)) {
|
|
294
|
-
next.delete(idx);
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
next.add(idx);
|
|
298
|
-
}
|
|
299
|
-
return next;
|
|
300
|
-
});
|
|
301
|
-
},
|
|
302
|
-
});
|
|
303
|
-
useCommand({
|
|
304
|
-
id: "select-copy",
|
|
305
|
-
title: "Copy selection",
|
|
306
|
-
hotkey: "y",
|
|
307
|
-
modes: ["select"],
|
|
308
|
-
handler: () => {
|
|
309
|
-
if (!getText || !selectionAnchor || !cursor)
|
|
310
|
-
return;
|
|
311
|
-
const text = getText();
|
|
312
|
-
if (!text)
|
|
313
|
-
return;
|
|
314
|
-
if (isBlockMode && blockLineMap) {
|
|
315
|
-
// Block-based copy: use blockLineMap to find line ranges
|
|
316
|
-
const startBlock = Math.min(selectionAnchor.line, cursor.line);
|
|
317
|
-
const endBlock = Math.max(selectionAnchor.line, cursor.line);
|
|
318
|
-
const startLine = blockLineMap[startBlock] ?? 0;
|
|
319
|
-
const endLine = endBlock + 1 < blockLineMap.length
|
|
320
|
-
? (blockLineMap[endBlock + 1] ?? text.split("\n").length)
|
|
321
|
-
: text.split("\n").length;
|
|
322
|
-
const lines = text.split("\n");
|
|
323
|
-
const selected = lines.slice(startLine, endLine).join("\n");
|
|
324
|
-
if (selected) {
|
|
325
|
-
void copyToClipboard(selected);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
else {
|
|
329
|
-
const lines = text.split("\n");
|
|
330
|
-
const startLine = Math.min(selectionAnchor.line, cursor.line);
|
|
331
|
-
const endLine = Math.max(selectionAnchor.line, cursor.line);
|
|
332
|
-
const selected = lines.slice(startLine, endLine + 1).join("\n");
|
|
333
|
-
if (selected) {
|
|
334
|
-
void copyToClipboard(selected);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
setMode("cursor");
|
|
338
|
-
},
|
|
339
|
-
});
|
|
340
|
-
useCommand({
|
|
341
|
-
id: "select-cancel",
|
|
342
|
-
title: "Cancel selection",
|
|
343
|
-
hotkey: "escape",
|
|
344
|
-
modes: ["select"],
|
|
345
|
-
handler: () => setMode("cursor"),
|
|
346
|
-
});
|
|
347
|
-
// === MODE TRANSITIONS ===
|
|
348
|
-
useCommand({
|
|
349
|
-
id: "enter-select",
|
|
350
|
-
title: "Enter select mode",
|
|
351
|
-
hotkey: "v",
|
|
352
|
-
modes: ["cursor"],
|
|
353
|
-
handler: () => setMode("select"),
|
|
354
|
-
});
|
|
355
|
-
// === SEARCH CANCEL (any mode) ===
|
|
356
|
-
useCommand({
|
|
357
|
-
id: "search-cancel",
|
|
358
|
-
title: "Cancel search",
|
|
359
|
-
hotkey: "escape",
|
|
360
|
-
modes: ["cursor", "select", "insert"],
|
|
361
|
-
when: () => searchActive,
|
|
362
|
-
handler: () => {
|
|
363
|
-
setSearchActive(false);
|
|
364
|
-
setSearchQuery("");
|
|
365
|
-
setCommittedQuery("");
|
|
366
|
-
setCurrentMatchIndex(0);
|
|
367
|
-
setMode(preSearchModeRef.current);
|
|
368
|
-
},
|
|
369
|
-
});
|
|
370
|
-
// Compute selection from anchor + cursor
|
|
371
|
-
const selection = selectionAnchor && cursor && mode === "select"
|
|
372
|
-
? {
|
|
373
|
-
start: selectionAnchor.line <= cursor.line ? selectionAnchor : cursor,
|
|
374
|
-
end: selectionAnchor.line <= cursor.line ? cursor : selectionAnchor,
|
|
375
|
-
}
|
|
376
|
-
: null;
|
|
377
|
-
const submitSearch = useCallback(() => {
|
|
378
|
-
setSearchActive(false);
|
|
379
|
-
setMode(preSearchModeRef.current);
|
|
380
|
-
}, [setMode]);
|
|
381
|
-
return {
|
|
382
|
-
mode,
|
|
383
|
-
setMode,
|
|
384
|
-
cursor,
|
|
385
|
-
selection,
|
|
386
|
-
toggledIndices,
|
|
387
|
-
searchQuery,
|
|
388
|
-
searchActive,
|
|
389
|
-
setSearchQuery,
|
|
390
|
-
matchingLines,
|
|
391
|
-
currentMatchIndex,
|
|
392
|
-
submitSearch,
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
//# sourceMappingURL=modal.js.map
|