@tooee/view 0.1.20 → 0.1.22
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/README.md +28 -0
- package/dist/View.d.ts +7 -1
- package/dist/View.d.ts.map +1 -1
- package/dist/View.js +2 -2
- package/dist/View.js.map +1 -1
- package/dist/components/subviews/CodeSubview.d.ts.map +1 -1
- package/dist/components/subviews/CodeSubview.js +26 -3
- package/dist/components/subviews/CodeSubview.js.map +1 -1
- package/dist/components/subviews/CustomSubview.d.ts.map +1 -1
- package/dist/components/subviews/CustomSubview.js +15 -1
- package/dist/components/subviews/CustomSubview.js.map +1 -1
- package/dist/components/subviews/MarkdownSubview.d.ts +3 -1
- package/dist/components/subviews/MarkdownSubview.d.ts.map +1 -1
- package/dist/components/subviews/MarkdownSubview.js +60 -5
- package/dist/components/subviews/MarkdownSubview.js.map +1 -1
- package/dist/components/subviews/TableSubview.d.ts.map +1 -1
- package/dist/components/subviews/TableSubview.js +28 -3
- package/dist/components/subviews/TableSubview.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useContentLoader.d.ts.map +1 -1
- package/dist/hooks/useContentLoader.js +13 -7
- package/dist/hooks/useContentLoader.js.map +1 -1
- package/dist/hooks/useViewCommandContext.d.ts +59 -0
- package/dist/hooks/useViewCommandContext.d.ts.map +1 -1
- package/dist/hooks/useViewCommandContext.js +52 -20
- package/dist/hooks/useViewCommandContext.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/launch.d.ts +7 -0
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js +1 -1
- package/dist/launch.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -10
- package/src/View.tsx +10 -2
- package/src/components/subviews/CodeSubview.tsx +33 -2
- package/src/components/subviews/CustomSubview.tsx +18 -1
- package/src/components/subviews/MarkdownSubview.tsx +77 -3
- package/src/components/subviews/TableSubview.tsx +35 -2
- package/src/hooks/index.ts +10 -1
- package/src/hooks/useContentLoader.ts +15 -7
- package/src/hooks/useViewCommandContext.ts +128 -20
- package/src/index.ts +12 -0
- package/src/launch.tsx +8 -0
- package/src/types.ts +10 -0
package/src/hooks/index.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export { useContentLoader } from "./useContentLoader.js"
|
|
2
2
|
export { useMarkState } from "./useMarkState.js"
|
|
3
3
|
export { useViewCommands } from "./useViewCommands.js"
|
|
4
|
-
export {
|
|
4
|
+
export {
|
|
5
|
+
createViewCommandContext,
|
|
6
|
+
useProvideViewCommandContext,
|
|
7
|
+
useViewCommandContext,
|
|
8
|
+
} from "./useViewCommandContext.js"
|
|
9
|
+
export type {
|
|
10
|
+
CreateViewCommandContextOptions,
|
|
11
|
+
ProvideViewCommandContextOptions,
|
|
12
|
+
ViewCommandContext,
|
|
13
|
+
} from "./useViewCommandContext.js"
|
|
@@ -92,12 +92,18 @@ export function useContentLoader(contentProvider: ContentProvider, reloadTrigger
|
|
|
92
92
|
setContent(current)
|
|
93
93
|
setStreaming(true)
|
|
94
94
|
|
|
95
|
+
// Hold the iterator explicitly so cleanup can close it: a cleanup that
|
|
96
|
+
// only flips `cancelled` leaves a pending next() (and the provider's
|
|
97
|
+
// subprocess/file handle/network stream) alive until the next chunk.
|
|
98
|
+
const iterator = loaded[Symbol.asyncIterator]()
|
|
99
|
+
|
|
95
100
|
;(async () => {
|
|
96
101
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
while (true) {
|
|
103
|
+
const result = await iterator.next()
|
|
104
|
+
if (cancelled || result.done) break
|
|
105
|
+
const chunk = result.value
|
|
99
106
|
if (chunk.type === "marks") {
|
|
100
|
-
if (cancelled) break
|
|
101
107
|
// Streamed mark set: merge by replacing any existing set with same namespace
|
|
102
108
|
setProviderMarks((prev) => {
|
|
103
109
|
const filtered = prev.filter((s) => s.namespace !== chunk.set.namespace)
|
|
@@ -109,8 +115,8 @@ export function useContentLoader(contentProvider: ContentProvider, reloadTrigger
|
|
|
109
115
|
setContent(current)
|
|
110
116
|
}
|
|
111
117
|
} catch (err) {
|
|
112
|
-
if (!cancelled
|
|
113
|
-
setError(err.message)
|
|
118
|
+
if (!cancelled) {
|
|
119
|
+
setError(err instanceof Error ? err.message : String(err))
|
|
114
120
|
}
|
|
115
121
|
} finally {
|
|
116
122
|
if (!cancelled) {
|
|
@@ -121,6 +127,8 @@ export function useContentLoader(contentProvider: ContentProvider, reloadTrigger
|
|
|
121
127
|
|
|
122
128
|
return () => {
|
|
123
129
|
cancelled = true
|
|
130
|
+
// Close the iterator to release provider resources mid-stream.
|
|
131
|
+
Promise.resolve(iterator.return?.()).catch(() => {})
|
|
124
132
|
}
|
|
125
133
|
}
|
|
126
134
|
|
|
@@ -130,8 +138,8 @@ export function useContentLoader(contentProvider: ContentProvider, reloadTrigger
|
|
|
130
138
|
.then((value) => {
|
|
131
139
|
if (active) setContent(value)
|
|
132
140
|
})
|
|
133
|
-
.catch((err:
|
|
134
|
-
if (active) setError(err.message)
|
|
141
|
+
.catch((err: unknown) => {
|
|
142
|
+
if (active) setError(err instanceof Error ? err.message : String(err))
|
|
135
143
|
})
|
|
136
144
|
return () => {
|
|
137
145
|
active = false
|
|
@@ -1,8 +1,123 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useProvideCommandContextKey, useMode } from "@tooee/commands"
|
|
2
|
+
import type { Mode } from "@tooee/commands"
|
|
2
3
|
import type { NavigationState } from "@tooee/shell"
|
|
3
4
|
import type { MarkSet } from "@tooee/marks"
|
|
4
5
|
import type { AnyContent } from "../types.js"
|
|
5
6
|
|
|
7
|
+
/** The `view` field contributed to the command context (see augmentation below). */
|
|
8
|
+
export interface ViewCommandContext {
|
|
9
|
+
content: AnyContent
|
|
10
|
+
format: string
|
|
11
|
+
title?: string
|
|
12
|
+
data?: unknown
|
|
13
|
+
cursor: number | null
|
|
14
|
+
selection: { start: number; end: number } | null
|
|
15
|
+
mode: Mode
|
|
16
|
+
toggledIndices: Set<number>
|
|
17
|
+
reload: () => void
|
|
18
|
+
marks: {
|
|
19
|
+
setMarkSet: (set: MarkSet) => void
|
|
20
|
+
clearNamespace: (namespace: string) => void
|
|
21
|
+
clearAll: () => void
|
|
22
|
+
userMarks: MarkSet[]
|
|
23
|
+
providerMarks: MarkSet[]
|
|
24
|
+
}
|
|
25
|
+
/** Subview extras (e.g. activeRow/selectedRows for tables). */
|
|
26
|
+
[key: string]: unknown
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare module "@tooee/commands" {
|
|
30
|
+
interface CommandContext {
|
|
31
|
+
view: ViewCommandContext
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const noop = () => {}
|
|
36
|
+
|
|
37
|
+
type ViewNavigationContext = Pick<NavigationState, "cursor" | "selection" | "toggledIndices">
|
|
38
|
+
|
|
39
|
+
export interface CreateViewCommandContextOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Content represented by this command context. Custom/headless surfaces may
|
|
42
|
+
* omit it; the builder will synthesize a minimal custom content object.
|
|
43
|
+
*/
|
|
44
|
+
content?: AnyContent
|
|
45
|
+
format?: string
|
|
46
|
+
title?: string
|
|
47
|
+
data?: unknown
|
|
48
|
+
nav?: ViewNavigationContext
|
|
49
|
+
cursor?: number | null
|
|
50
|
+
selection?: { start: number; end: number } | null
|
|
51
|
+
/** Caller-owned mode value. Prefer `useProvideViewCommandContext` in React so this is live. */
|
|
52
|
+
mode: Mode
|
|
53
|
+
toggledIndices?: Set<number>
|
|
54
|
+
reload?: () => void
|
|
55
|
+
marks?: Partial<ViewCommandContext["marks"]>
|
|
56
|
+
extras?: Record<string, unknown>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ProvideViewCommandContextOptions = Omit<CreateViewCommandContextOptions, "mode"> & {
|
|
60
|
+
/** Advanced override; normally omitted so the hook injects the live command mode. */
|
|
61
|
+
mode?: Mode
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createViewCommandContext({
|
|
65
|
+
content,
|
|
66
|
+
format,
|
|
67
|
+
title,
|
|
68
|
+
data,
|
|
69
|
+
nav,
|
|
70
|
+
cursor,
|
|
71
|
+
selection,
|
|
72
|
+
mode,
|
|
73
|
+
toggledIndices,
|
|
74
|
+
reload,
|
|
75
|
+
marks,
|
|
76
|
+
extras,
|
|
77
|
+
}: CreateViewCommandContextOptions): ViewCommandContext {
|
|
78
|
+
const resolvedFormat = format ?? content?.format ?? "custom"
|
|
79
|
+
const resolvedContent: AnyContent =
|
|
80
|
+
content ??
|
|
81
|
+
({
|
|
82
|
+
format: resolvedFormat,
|
|
83
|
+
data,
|
|
84
|
+
title,
|
|
85
|
+
} satisfies AnyContent)
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
...extras,
|
|
89
|
+
content: resolvedContent,
|
|
90
|
+
format: resolvedFormat,
|
|
91
|
+
title: title ?? content?.title,
|
|
92
|
+
data: data ?? ("data" in resolvedContent ? resolvedContent.data : undefined),
|
|
93
|
+
cursor: cursor ?? nav?.cursor ?? null,
|
|
94
|
+
selection: selection ?? nav?.selection ?? null,
|
|
95
|
+
mode,
|
|
96
|
+
toggledIndices: toggledIndices ?? nav?.toggledIndices ?? new Set<number>(),
|
|
97
|
+
reload: reload ?? noop,
|
|
98
|
+
marks: {
|
|
99
|
+
setMarkSet: marks?.setMarkSet ?? noop,
|
|
100
|
+
clearNamespace: marks?.clearNamespace ?? noop,
|
|
101
|
+
clearAll: marks?.clearAll ?? noop,
|
|
102
|
+
userMarks: marks?.userMarks ?? [],
|
|
103
|
+
providerMarks: marks?.providerMarks ?? [],
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function useProvideViewCommandContext(
|
|
109
|
+
options: ProvideViewCommandContextOptions | (() => ProvideViewCommandContextOptions),
|
|
110
|
+
) {
|
|
111
|
+
const mode = useMode()
|
|
112
|
+
useProvideCommandContextKey("view", () => {
|
|
113
|
+
const resolvedOptions = typeof options === "function" ? options() : options
|
|
114
|
+
return createViewCommandContext({
|
|
115
|
+
...resolvedOptions,
|
|
116
|
+
mode: resolvedOptions.mode ?? mode,
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
6
121
|
interface UseViewCommandContextParams {
|
|
7
122
|
content: AnyContent
|
|
8
123
|
nav: NavigationState
|
|
@@ -27,24 +142,17 @@ export function useViewCommandContext({
|
|
|
27
142
|
clearAllUserMarks,
|
|
28
143
|
extras,
|
|
29
144
|
}: UseViewCommandContextParams) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
marks: {
|
|
41
|
-
setMarkSet,
|
|
42
|
-
clearNamespace: clearMarkNamespace,
|
|
43
|
-
clearAll: clearAllUserMarks,
|
|
44
|
-
userMarks,
|
|
45
|
-
providerMarks,
|
|
46
|
-
},
|
|
47
|
-
...extras,
|
|
145
|
+
useProvideViewCommandContext({
|
|
146
|
+
content,
|
|
147
|
+
nav,
|
|
148
|
+
reload,
|
|
149
|
+
marks: {
|
|
150
|
+
setMarkSet,
|
|
151
|
+
clearNamespace: clearMarkNamespace,
|
|
152
|
+
clearAll: clearAllUserMarks,
|
|
153
|
+
userMarks,
|
|
154
|
+
providerMarks,
|
|
48
155
|
},
|
|
49
|
-
|
|
156
|
+
extras,
|
|
157
|
+
})
|
|
50
158
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,16 @@ export {
|
|
|
10
10
|
} from "./default-provider.js"
|
|
11
11
|
export { listDirectoryFiles } from "./directory-provider.js"
|
|
12
12
|
export type { DirectoryEntry } from "./directory-provider.js"
|
|
13
|
+
export {
|
|
14
|
+
createViewCommandContext,
|
|
15
|
+
useProvideViewCommandContext,
|
|
16
|
+
useViewCommandContext,
|
|
17
|
+
} from "./hooks/useViewCommandContext.js"
|
|
18
|
+
export type {
|
|
19
|
+
CreateViewCommandContextOptions,
|
|
20
|
+
ProvideViewCommandContextOptions,
|
|
21
|
+
ViewCommandContext,
|
|
22
|
+
} from "./hooks/useViewCommandContext.js"
|
|
13
23
|
export type {
|
|
14
24
|
AnyContent,
|
|
15
25
|
Content,
|
|
@@ -25,3 +35,5 @@ export type {
|
|
|
25
35
|
TableRow,
|
|
26
36
|
} from "./types.js"
|
|
27
37
|
export { getTextContent, isBuiltinContent, isCustomContent } from "./types.js"
|
|
38
|
+
export { CodeBlockChrome } from "@tooee/renderers"
|
|
39
|
+
export type { CodeBlockRenderer, CodeBlockRendererProps, CodeBlockHScroll } from "@tooee/renderers"
|
package/src/launch.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { launchCli } from "@tooee/shell"
|
|
2
2
|
import type { ActionDefinition } from "@tooee/commands"
|
|
3
|
+
import type { CodeBlockRenderer } from "@tooee/renderers"
|
|
3
4
|
import { View } from "./View.js"
|
|
4
5
|
import { DirectoryView } from "./DirectoryView.js"
|
|
5
6
|
import type { ContentProvider, ContentRenderer } from "./types.js"
|
|
@@ -8,6 +9,12 @@ export interface ViewLaunchOptions {
|
|
|
8
9
|
contentProvider: ContentProvider
|
|
9
10
|
actions?: ActionDefinition[]
|
|
10
11
|
renderers?: Record<string, ContentRenderer>
|
|
12
|
+
/**
|
|
13
|
+
* Custom renderers for fenced code blocks in markdown content, keyed by
|
|
14
|
+
* fence type (first word of the fence info string, case-insensitive).
|
|
15
|
+
* Unmatched types fall back to the default syntax-highlighted code block.
|
|
16
|
+
*/
|
|
17
|
+
codeBlockRenderers?: Record<string, CodeBlockRenderer>
|
|
11
18
|
}
|
|
12
19
|
|
|
13
20
|
export interface DirectoryLaunchOptions {
|
|
@@ -21,6 +28,7 @@ export async function launch(options: ViewLaunchOptions): Promise<void> {
|
|
|
21
28
|
contentProvider={options.contentProvider}
|
|
22
29
|
actions={options.actions}
|
|
23
30
|
renderers={options.renderers}
|
|
31
|
+
codeBlockRenderers={options.codeBlockRenderers}
|
|
24
32
|
/>,
|
|
25
33
|
)
|
|
26
34
|
}
|
package/src/types.ts
CHANGED
|
@@ -57,6 +57,16 @@ export interface ContentRendererProps {
|
|
|
57
57
|
selectionEnd?: number
|
|
58
58
|
/** Combined mark state (nav + provider + user marks) */
|
|
59
59
|
marks?: MarkState
|
|
60
|
+
/**
|
|
61
|
+
* Select a row by index — the mouse equivalent of the keyboard cursor move.
|
|
62
|
+
* A custom renderer that has a notion of rows should call this from its own
|
|
63
|
+
* `onMouseDown`/`onRowClick` handlers to make left-click select a row, the
|
|
64
|
+
* same way the built-in code/markdown/table views do. Selection is a no-op
|
|
65
|
+
* while a modal overlay is open (the host guards it), so renderers can wire
|
|
66
|
+
* it unconditionally. Renderers that own a `RowDocumentRenderable` can map a
|
|
67
|
+
* click's screen-Y to a row via its `getRowAtScreenY(event.y)` helper.
|
|
68
|
+
*/
|
|
69
|
+
onSelectRow?: (index: number) => void
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
export type ContentRenderer = (props: ContentRendererProps) => ReactNode
|