@north-light/crouter 0.3.200 → 0.3.202
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/api/client.d.ts +2 -1
- package/dist/api/client.js +3 -0
- package/dist/api/dto/inbox.d.ts +1 -1
- package/dist/api/dto/nodes.d.ts +16 -0
- package/dist/builtin-memory/insights/capture.md +1 -1
- package/dist/clients/attach/ansi-cells.d.ts +13 -4
- package/dist/clients/attach/ansi-cells.js +16 -10
- package/dist/clients/attach/overlays/help.js +6 -0
- package/dist/clients/attach/render/transcript-search.d.ts +28 -0
- package/dist/clients/attach/render/transcript-search.js +153 -0
- package/dist/clients/attach/render/viewport.d.ts +10 -1
- package/dist/clients/attach/render/viewport.js +27 -2
- package/dist/clients/attach/session/frame.d.ts +2 -1
- package/dist/clients/attach/session/frame.js +9 -0
- package/dist/clients/attach/session/keys.d.ts +5 -0
- package/dist/clients/attach/session/keys.js +3 -0
- package/dist/clients/attach/session/layout.d.ts +2 -2
- package/dist/clients/attach/session/layout.js +3 -2
- package/dist/clients/attach/session/transcript-search.d.ts +20 -0
- package/dist/clients/attach/session/transcript-search.js +151 -0
- package/dist/clients/attach/viewer.js +712 -710
- package/dist/commands/memory/delete.js +14 -3
- package/dist/commands/memory/edit.d.ts +1 -0
- package/dist/commands/memory/edit.js +166 -0
- package/dist/commands/memory/history.d.ts +1 -0
- package/dist/commands/memory/history.js +179 -0
- package/dist/commands/memory/list.js +1 -1
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +27 -0
- package/dist/commands/memory/shared.js +69 -0
- package/dist/commands/memory/write.js +54 -61
- package/dist/commands/memory.js +6 -3
- package/dist/core/__tests__/seam/dormancy-release.test.js +2 -0
- package/dist/core/human/scan.d.ts +1 -3
- package/dist/core/human/scan.js +3 -3
- package/dist/core/human/types.d.ts +1 -0
- package/dist/core/keybindings/attach-control.d.ts +3 -0
- package/dist/core/keybindings/attach-control.js +1 -0
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +6 -0
- package/dist/core/memory/history.d.ts +54 -0
- package/dist/core/memory/history.js +202 -0
- package/dist/core/memory-resolver.d.ts +21 -0
- package/dist/core/memory-resolver.js +47 -16
- package/dist/core/preview-registry.js +2 -0
- package/dist/core/runtime/node-read.d.ts +11 -0
- package/dist/core/runtime/node-read.js +107 -41
- package/dist/daemon/api/handlers/inbox.js +1 -0
- package/dist/daemon/api/handlers/nodes.js +34 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// session/transcript-search.ts — incremental transcript search: the mode, its
|
|
2
|
+
// one-row bar, and the keys that drive it.
|
|
3
|
+
//
|
|
4
|
+
// The viewer keeps the terminal in the normal buffer and renders exactly one
|
|
5
|
+
// screenful per frame, so tmux copy-mode's `/` only ever sees what is already
|
|
6
|
+
// on screen. Searching the transcript therefore has to happen here, against the
|
|
7
|
+
// same rendered rows the viewport draws.
|
|
8
|
+
//
|
|
9
|
+
// Two states, both visible on the bar. EDITING owns typed characters and jumps
|
|
10
|
+
// as you type; BROWSING owns only n/N (and `/` to edit again), and any other
|
|
11
|
+
// printable key closes search and falls through to the editor, so getting back
|
|
12
|
+
// to typing a prompt never needs a deliberate dismissal.
|
|
13
|
+
import { truncateToWidth } from '@earendil-works/pi-tui';
|
|
14
|
+
import { visibleWidth } from '../../../core/canvas/nav-render.js';
|
|
15
|
+
import { formatBinding } from '../../../core/keybindings/index.js';
|
|
16
|
+
import { matchesAttachBinding } from './bindings.js';
|
|
17
|
+
/** A chunk is typed text only when every code point in it is printable — an
|
|
18
|
+
* escape sequence, control byte, or paste of either is somebody else's. */
|
|
19
|
+
function printableText(data) {
|
|
20
|
+
if (data === '')
|
|
21
|
+
return undefined;
|
|
22
|
+
for (const character of data) {
|
|
23
|
+
const code = character.codePointAt(0);
|
|
24
|
+
if (code < 0x20 || code === 0x7f)
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
export function createTranscriptSearch(tui, pal, viewport, model, hooks) {
|
|
30
|
+
let mode = 'closed';
|
|
31
|
+
/** Where the query search started from, so editing re-seeks from one place
|
|
32
|
+
* instead of chasing its own jumps character by character. */
|
|
33
|
+
let anchor = 0;
|
|
34
|
+
let draft = '';
|
|
35
|
+
const width = () => tui.terminal.columns;
|
|
36
|
+
const jump = (match) => {
|
|
37
|
+
if (match !== undefined)
|
|
38
|
+
viewport.scrollToLine(match.line);
|
|
39
|
+
tui.requestRender();
|
|
40
|
+
};
|
|
41
|
+
const applyDraft = () => {
|
|
42
|
+
model.setQuery(draft, anchor);
|
|
43
|
+
jump(model.current(width()));
|
|
44
|
+
};
|
|
45
|
+
const close = () => {
|
|
46
|
+
mode = 'closed';
|
|
47
|
+
draft = '';
|
|
48
|
+
model.setQuery('', undefined);
|
|
49
|
+
tui.requestRender();
|
|
50
|
+
};
|
|
51
|
+
const open = () => {
|
|
52
|
+
if (mode === 'closed')
|
|
53
|
+
anchor = viewport.topLine() ?? 0;
|
|
54
|
+
mode = 'editing';
|
|
55
|
+
draft = model.query();
|
|
56
|
+
tui.requestRender();
|
|
57
|
+
};
|
|
58
|
+
const step = (direction) => {
|
|
59
|
+
const match = model.step(width(), direction);
|
|
60
|
+
if (match === undefined) {
|
|
61
|
+
hooks.setNotice(`No matches for “${model.query()}”`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
jump(match);
|
|
65
|
+
};
|
|
66
|
+
const component = {
|
|
67
|
+
render(renderWidth) {
|
|
68
|
+
if (mode === 'closed')
|
|
69
|
+
return [];
|
|
70
|
+
const { index, total } = model.status(renderWidth);
|
|
71
|
+
const left = mode === 'editing'
|
|
72
|
+
? `${pal.accent('⌕')} ${draft}${pal.accent('▌')}`
|
|
73
|
+
: `${pal.accent('⌕')} ${draft}`;
|
|
74
|
+
const count = total === 0
|
|
75
|
+
? (model.query() === '' ? pal.faint('type to search what is on screen') : pal.error('no matches'))
|
|
76
|
+
: pal.info(`${index}/${total}`);
|
|
77
|
+
const hints = mode === 'editing'
|
|
78
|
+
? pal.faint('enter browse · esc cancel')
|
|
79
|
+
: pal.faint([
|
|
80
|
+
`${formatBinding(hooks.bindings(), 'crtr.attach.search.next')} next`,
|
|
81
|
+
`${formatBinding(hooks.bindings(), 'crtr.attach.search.previous')} prev`,
|
|
82
|
+
`${formatBinding(hooks.bindings(), 'crtr.attach.search.edit')} edit`,
|
|
83
|
+
`${formatBinding(hooks.bindings(), 'crtr.attach.search.close')} close`,
|
|
84
|
+
].join(' · '));
|
|
85
|
+
const right = `${count} ${hints}`;
|
|
86
|
+
const gap = renderWidth - visibleWidth(left) - visibleWidth(right) - 2;
|
|
87
|
+
const line = gap < 1
|
|
88
|
+
? truncateToWidth(` ${left}`, renderWidth, '', true)
|
|
89
|
+
: ` ${left}${' '.repeat(gap)}${right} `;
|
|
90
|
+
return [line];
|
|
91
|
+
},
|
|
92
|
+
invalidate() {
|
|
93
|
+
/* Rendered fresh from the model on every frame. */
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const handleKey = (data) => {
|
|
97
|
+
if (mode === 'closed')
|
|
98
|
+
return false;
|
|
99
|
+
const bindings = hooks.bindings();
|
|
100
|
+
if (matchesAttachBinding(bindings, 'crtr.attach.search.close', data)) {
|
|
101
|
+
close();
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (mode === 'editing') {
|
|
105
|
+
if (data === '\x03') {
|
|
106
|
+
close();
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (data === '\r' || data === '\n') {
|
|
110
|
+
if (model.status(width()).total === 0) {
|
|
111
|
+
hooks.setNotice(draft === '' ? 'Type something to search for' : `No matches for “${draft}”`);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
mode = 'browsing';
|
|
115
|
+
tui.requestRender();
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (data === '\x7f' || data === '\b') {
|
|
119
|
+
draft = [...draft].slice(0, -1).join('');
|
|
120
|
+
applyDraft();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
const typed = printableText(data);
|
|
124
|
+
if (typed === undefined)
|
|
125
|
+
return false;
|
|
126
|
+
draft += typed;
|
|
127
|
+
applyDraft();
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (matchesAttachBinding(bindings, 'crtr.attach.search.next', data) || data === '\r' || data === '\n') {
|
|
131
|
+
step(1);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (matchesAttachBinding(bindings, 'crtr.attach.search.previous', data)) {
|
|
135
|
+
step(-1);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (matchesAttachBinding(bindings, 'crtr.attach.search.edit', data)) {
|
|
139
|
+
open();
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
// Browsing holds only its own gestures. Ordinary typing means the user is
|
|
143
|
+
// done searching: dismiss and let the chunk land in the editor as typed.
|
|
144
|
+
if (printableText(data) !== undefined) {
|
|
145
|
+
close();
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
};
|
|
150
|
+
return { component, open, close, handleKey };
|
|
151
|
+
}
|