@aitty/browser 0.1.2 → 0.2.0
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 +84 -0
- package/dist/browser.d.ts +6 -4
- package/dist/browser.js +3 -1
- package/dist/frontend/aitty-sw.js +43 -0
- package/dist/frontend/ansi-sequences.d.ts +7 -2
- package/dist/frontend/ansi-sequences.js +65 -2
- package/dist/frontend/ansi-style-tracker.d.ts +2 -6
- package/dist/frontend/ansi-style-tracker.js +11 -47
- package/dist/frontend/browser-terminal-renderer.d.ts +23 -15
- package/dist/frontend/browser-terminal-renderer.js +266 -102
- package/dist/frontend/cell-width.d.ts +1 -1
- package/dist/frontend/cell-width.js +1 -1
- package/dist/frontend/shell-controls.d.ts +24 -0
- package/dist/frontend/shell-controls.js +221 -0
- package/dist/frontend/terminal-app.d.ts +84 -20
- package/dist/frontend/terminal-app.js +2672 -278
- package/dist/frontend/terminal-config.d.ts +62 -0
- package/dist/frontend/terminal-config.js +126 -0
- package/dist/frontend/terminal-input-policies.d.ts +12 -2
- package/dist/frontend/terminal-input-policies.js +131 -49
- package/dist/frontend/terminal-scroll-anchor.js +25 -0
- package/dist/frontend/terminal-scroll-follow.js +23 -0
- package/dist/frontend/terminal-scrollback-window.js +18 -0
- package/dist/frontend/terminal-theme-protocol.d.ts +1 -1
- package/dist/frontend/terminal-theme-protocol.js +1 -1
- package/dist/frontend/terminal.css +161 -19
- package/dist/frontend/virtual-transcript-window.js +42 -0
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @aitty/browser
|
|
2
|
+
|
|
3
|
+
Browser-side DOM terminal renderer for aitty sessions.
|
|
4
|
+
|
|
5
|
+
Use this package inside your web UI to mount a PTY-backed agent session created
|
|
6
|
+
by `@aitty/server`.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i @aitty/browser
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Import the stylesheet once:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import "@aitty/browser/style.css";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { mountAitty } from "@aitty/browser";
|
|
24
|
+
import "@aitty/browser/style.css";
|
|
25
|
+
|
|
26
|
+
const terminal = await mountAitty("#terminal", {
|
|
27
|
+
src: sessionUrl,
|
|
28
|
+
config: {
|
|
29
|
+
appearance: {
|
|
30
|
+
fontSize: 16,
|
|
31
|
+
lineHeight: 1.5,
|
|
32
|
+
theme: "light",
|
|
33
|
+
themeTarget: "container"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
onStatusChange(status) {
|
|
37
|
+
console.log(status.connection, status.sessionState, status.message);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
terminal.updateConfig({
|
|
42
|
+
appearance: {
|
|
43
|
+
theme: "dark"
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<div id="terminal" style="height: 100dvh"></div>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If the page is served directly from an aitty session URL, `src` can be omitted.
|
|
53
|
+
When embedding aitty inside an existing app route, pass `src`.
|
|
54
|
+
|
|
55
|
+
By default, `@aitty/browser` scopes theme writes to the mounted container and
|
|
56
|
+
does not install the reference shell controls. That keeps host apps in control
|
|
57
|
+
of their own header, fullscreen UI, theme switcher, status indicator, and page
|
|
58
|
+
theme. The reference CLI shell opts into document-level theme and shell controls
|
|
59
|
+
explicitly through its own `data-aitty-*` attributes.
|
|
60
|
+
|
|
61
|
+
`themeTarget` is a mount-time ownership setting. Keep it at `"container"` for
|
|
62
|
+
embedded apps; use `"document"` only for full-page shells.
|
|
63
|
+
|
|
64
|
+
## Main Exports
|
|
65
|
+
|
|
66
|
+
- `mountAitty()` mounts the terminal into a selector or element and connects to a session.
|
|
67
|
+
- `defineAittyTerminalElement()` registers the web component wrapper.
|
|
68
|
+
- `mountTerminalApp()` mounts with explicit elements and dependencies for advanced integrations.
|
|
69
|
+
- `createBufferedTerminalWriter()` buffers terminal writes until the renderer is ready.
|
|
70
|
+
- `BrowserTerminalRenderer` exposes the DOM renderer used by aitty.
|
|
71
|
+
- `normalizeTerminalConfig()`, `mergeTerminalConfig()`, and `cloneTerminalConfig()` help host apps manage settings state.
|
|
72
|
+
- `installShellControls()` wires the optional reference shell buttons used by the CLI page.
|
|
73
|
+
- `installTerminalInputPolicies()` wires keyboard, IME, paste, and shortcut handling.
|
|
74
|
+
|
|
75
|
+
## Integration Hooks
|
|
76
|
+
|
|
77
|
+
- Use `onStatusChange` to render your own header, badge, or connection state UI.
|
|
78
|
+
- Use `config.appearance` and `terminal.updateConfig()` to keep host UI and terminal state aligned.
|
|
79
|
+
- Use `scrollAdapter` when integrating custom scrollbar libraries such as OverlayScrollbars.
|
|
80
|
+
- Use `config.behavior.input.resolveKey` for agent-specific shortcuts while leaving browser-reserved shortcuts to the browser.
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
Apache-2.0
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { BrowserTerminalBridge, BrowserTerminalRenderer
|
|
2
|
-
import { TerminalInputCallbacks, TerminalKeyResolver, installTerminalInputPolicies, isBrowserSafeShortcut, isCompositionKeyDown, isContainedTerminalNavigationKey } from "./frontend/terminal-input-policies.js";
|
|
3
|
-
import {
|
|
1
|
+
import { BrowserTerminalBridge, BrowserTerminalRenderer } from "./frontend/browser-terminal-renderer.js";
|
|
2
|
+
import { TerminalInputCallbacks, TerminalKeyResolver, TerminalPasteShortcut, TerminalPasteShortcutResult, installTerminalInputPolicies, isBrowserSafeShortcut, isCompositionKeyDown, isContainedTerminalNavigationKey } from "./frontend/terminal-input-policies.js";
|
|
3
|
+
import { TerminalAppearanceConfig, TerminalBehaviorConfig, TerminalConfig, TerminalConfigListener, TerminalConfigPatch, TerminalFontSizeRange, TerminalResolvedAppearanceConfig, TerminalResolvedBehaviorConfig, TerminalResolvedConfig, TerminalTheme, TerminalThemeTarget, cloneTerminalConfig, mergeTerminalConfig, normalizeTerminalConfig } from "./frontend/terminal-config.js";
|
|
4
|
+
import { BinaryWriteTarget, BufferedTerminalWriter, FrameScheduler, MountAittyContainerOptions, MountAittyOptions, MountTerminalDependencies, MountTerminalElements, MountedTerminalApp, TerminalConnectionState, TerminalLoadingPresentation, TerminalOutputState, TerminalScrollAdapter, TerminalScrollAdapterContext, TerminalScrollAdapterFactory, TerminalScrollMetrics, TerminalScrollOptions, TerminalSessionPresentationState, TerminalStatusListener, TerminalStatusSnapshot, TerminalTransport, TransportCloseEvent, TransportHandlers, createBufferedTerminalWriter, defineAittyTerminalElement, mountAitty, mountTerminalApp } from "./frontend/terminal-app.js";
|
|
4
5
|
import { AittyTheme, AittyThemeSource, AittyThemeSubscription } from "./theme-source.js";
|
|
5
6
|
import { AnsiStyleTracker, createAnsiStyleTracker } from "./frontend/ansi-style-tracker.js";
|
|
6
|
-
|
|
7
|
+
import { ShellControlOptions, ShellControlScrollAnchor, installShellControls } from "./frontend/shell-controls.js";
|
|
8
|
+
export { type AittyTheme, type AittyThemeSource, type AittyThemeSubscription, type AnsiStyleTracker, type BinaryWriteTarget, type BrowserTerminalBridge, BrowserTerminalRenderer, type BufferedTerminalWriter, type FrameScheduler, type MountAittyContainerOptions, type MountAittyOptions, type MountTerminalDependencies, type MountTerminalElements, type MountedTerminalApp, type ShellControlOptions, type ShellControlScrollAnchor, type TerminalAppearanceConfig, type TerminalBehaviorConfig, type TerminalConfig, type TerminalConfigListener, type TerminalConfigPatch, type TerminalConnectionState, type TerminalFontSizeRange, type TerminalInputCallbacks, type TerminalKeyResolver, type TerminalLoadingPresentation, type TerminalOutputState, type TerminalPasteShortcut, type TerminalPasteShortcutResult, type TerminalResolvedAppearanceConfig, type TerminalResolvedBehaviorConfig, type TerminalResolvedConfig, type TerminalScrollAdapter, type TerminalScrollAdapterContext, type TerminalScrollAdapterFactory, type TerminalScrollMetrics, type TerminalScrollOptions, type TerminalSessionPresentationState, type TerminalStatusListener, type TerminalStatusSnapshot, type TerminalTheme, type TerminalThemeTarget, type TerminalTransport, type TransportCloseEvent, type TransportHandlers, cloneTerminalConfig, createAnsiStyleTracker, createBufferedTerminalWriter, defineAittyTerminalElement, installShellControls, installTerminalInputPolicies, isBrowserSafeShortcut, isCompositionKeyDown, isContainedTerminalNavigationKey, mergeTerminalConfig, mountAitty, mountTerminalApp, normalizeTerminalConfig };
|
package/dist/browser.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createAnsiStyleTracker } from "./frontend/ansi-style-tracker.js";
|
|
2
2
|
import { BrowserTerminalRenderer } from "./frontend/browser-terminal-renderer.js";
|
|
3
|
+
import { cloneTerminalConfig, mergeTerminalConfig, normalizeTerminalConfig } from "./frontend/terminal-config.js";
|
|
4
|
+
import { installShellControls } from "./frontend/shell-controls.js";
|
|
3
5
|
import { installTerminalInputPolicies, isBrowserSafeShortcut, isCompositionKeyDown, isContainedTerminalNavigationKey } from "./frontend/terminal-input-policies.js";
|
|
4
6
|
import { createBufferedTerminalWriter, defineAittyTerminalElement, mountAitty, mountTerminalApp } from "./frontend/terminal-app.js";
|
|
5
|
-
export { BrowserTerminalRenderer, createAnsiStyleTracker, createBufferedTerminalWriter, defineAittyTerminalElement, installTerminalInputPolicies, isBrowserSafeShortcut, isCompositionKeyDown, isContainedTerminalNavigationKey, mountAitty, mountTerminalApp };
|
|
7
|
+
export { BrowserTerminalRenderer, cloneTerminalConfig, createAnsiStyleTracker, createBufferedTerminalWriter, defineAittyTerminalElement, installShellControls, installTerminalInputPolicies, isBrowserSafeShortcut, isCompositionKeyDown, isContainedTerminalNavigationKey, mergeTerminalConfig, mountAitty, mountTerminalApp, normalizeTerminalConfig };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
self.addEventListener("push", (event) => {
|
|
2
|
+
let payload = {};
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
payload = event.data ? event.data.json() : {};
|
|
6
|
+
} catch {
|
|
7
|
+
payload = {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const title = typeof payload.title === "string" && payload.title ? payload.title : "aitty";
|
|
11
|
+
const options = {
|
|
12
|
+
body: typeof payload.body === "string" ? payload.body : "Terminal session needs your attention.",
|
|
13
|
+
data: {
|
|
14
|
+
requestId: typeof payload.requestId === "string" ? payload.requestId : "",
|
|
15
|
+
url: typeof payload.url === "string" && payload.url ? payload.url : "/"
|
|
16
|
+
},
|
|
17
|
+
tag: typeof payload.tag === "string" && payload.tag ? payload.tag : "aitty-notification"
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
event.waitUntil(self.registration.showNotification(title, options));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
self.addEventListener("notificationclick", (event) => {
|
|
24
|
+
event.notification.close();
|
|
25
|
+
|
|
26
|
+
const targetUrl = new URL(event.notification.data?.url || "/", self.location.origin).toString();
|
|
27
|
+
|
|
28
|
+
event.waitUntil((async () => {
|
|
29
|
+
const windows = await self.clients.matchAll({
|
|
30
|
+
includeUncontrolled: true,
|
|
31
|
+
type: "window"
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
for (const client of windows) {
|
|
35
|
+
if (client.url === targetUrl || new URL(client.url).pathname === new URL(targetUrl).pathname) {
|
|
36
|
+
await client.focus();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await self.clients.openWindow(targetUrl);
|
|
42
|
+
})());
|
|
43
|
+
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
//#region src/frontend/ansi-sequences.d.ts
|
|
1
|
+
//#region packages/browser/src/frontend/ansi-sequences.d.ts
|
|
2
2
|
declare function parseCsiParams(paramBuffer: string): number[];
|
|
3
3
|
declare function nextAltScreenState(currentState: boolean, privateMarker: string, paramBuffer: string, finalByte: string): boolean;
|
|
4
|
+
type FocusReportingParser = {
|
|
5
|
+
append(chunk: string | Uint8Array): boolean | null;
|
|
6
|
+
reset(): void;
|
|
7
|
+
};
|
|
8
|
+
declare function createFocusReportingParser(): FocusReportingParser;
|
|
4
9
|
//#endregion
|
|
5
|
-
export { nextAltScreenState, parseCsiParams };
|
|
10
|
+
export { FocusReportingParser, createFocusReportingParser, nextAltScreenState, parseCsiParams };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
//#region src/frontend/ansi-sequences.ts
|
|
1
|
+
//#region packages/browser/src/frontend/ansi-sequences.ts
|
|
2
2
|
const ALT_SCREEN_PARAMS = new Set([
|
|
3
3
|
47,
|
|
4
4
|
1047,
|
|
5
5
|
1049
|
|
6
6
|
]);
|
|
7
|
+
const FOCUS_REPORTING_PARAM = 1004;
|
|
7
8
|
function parseCsiParams(paramBuffer) {
|
|
8
9
|
if (!paramBuffer) return [];
|
|
9
10
|
return paramBuffer.split(/[;:]/).map((value) => Number(value)).filter((value) => !Number.isNaN(value));
|
|
@@ -13,5 +14,67 @@ function nextAltScreenState(currentState, privateMarker, paramBuffer, finalByte)
|
|
|
13
14
|
if (!parseCsiParams(paramBuffer).some((value) => ALT_SCREEN_PARAMS.has(value))) return currentState;
|
|
14
15
|
return finalByte === "h";
|
|
15
16
|
}
|
|
17
|
+
function createFocusReportingParser() {
|
|
18
|
+
const decoder = new TextDecoder();
|
|
19
|
+
let parserState = "text";
|
|
20
|
+
let csiPrivate = "";
|
|
21
|
+
let csiParams = "";
|
|
22
|
+
const resetCsi = () => {
|
|
23
|
+
csiPrivate = "";
|
|
24
|
+
csiParams = "";
|
|
25
|
+
};
|
|
26
|
+
const reset = () => {
|
|
27
|
+
parserState = "text";
|
|
28
|
+
resetCsi();
|
|
29
|
+
};
|
|
30
|
+
const finishCsi = (finalByte) => {
|
|
31
|
+
const nextState = nextFocusReportingState(null, csiPrivate, csiParams, finalByte);
|
|
32
|
+
reset();
|
|
33
|
+
return nextState;
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
append(chunk) {
|
|
37
|
+
const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
38
|
+
let latestState = null;
|
|
39
|
+
for (const character of text) {
|
|
40
|
+
if (parserState === "escape") {
|
|
41
|
+
if (character === "[") {
|
|
42
|
+
parserState = "csi";
|
|
43
|
+
resetCsi();
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (character === "c") latestState = false;
|
|
47
|
+
parserState = "text";
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (parserState === "csi") {
|
|
51
|
+
if (character === "?") {
|
|
52
|
+
csiPrivate = "?";
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (character >= "0" && character <= "9" || character === ";" || character === ":") {
|
|
56
|
+
csiParams += character;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (character >= "@" && character <= "~") {
|
|
60
|
+
const nextState = finishCsi(character);
|
|
61
|
+
if (nextState !== null) latestState = nextState;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
reset();
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (character === "\x1B") parserState = "escape";
|
|
68
|
+
}
|
|
69
|
+
return latestState;
|
|
70
|
+
},
|
|
71
|
+
reset
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function nextFocusReportingState(currentState, privateMarker, paramBuffer, finalByte) {
|
|
75
|
+
if (privateMarker !== "?" || finalByte !== "h" && finalByte !== "l") return currentState;
|
|
76
|
+
if (!parseCsiParams(paramBuffer).includes(FOCUS_REPORTING_PARAM)) return currentState;
|
|
77
|
+
return finalByte === "h";
|
|
78
|
+
}
|
|
16
79
|
//#endregion
|
|
17
|
-
export { nextAltScreenState, parseCsiParams };
|
|
80
|
+
export { createFocusReportingParser, nextAltScreenState, parseCsiParams };
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
//#region src/frontend/ansi-style-tracker.d.ts
|
|
1
|
+
//#region packages/browser/src/frontend/ansi-style-tracker.d.ts
|
|
2
2
|
type NullableStyle = {
|
|
3
3
|
bg: string | null;
|
|
4
4
|
fg: string | null;
|
|
5
5
|
};
|
|
6
|
-
type StyleCell = (NullableStyle & {
|
|
7
|
-
displayStart?: number;
|
|
8
|
-
hidden?: boolean;
|
|
9
|
-
width?: number;
|
|
10
|
-
}) | null;
|
|
6
|
+
type StyleCell = (NullableStyle & {}) | null;
|
|
11
7
|
type CursorPosition = {
|
|
12
8
|
col: number;
|
|
13
9
|
row: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { nextAltScreenState, parseCsiParams } from "./ansi-sequences.js";
|
|
2
2
|
import { cellWidthForCodePoint } from "./cell-width.js";
|
|
3
|
-
//#region src/frontend/ansi-style-tracker.ts
|
|
3
|
+
//#region packages/browser/src/frontend/ansi-style-tracker.ts
|
|
4
4
|
const ESC = "\x1B";
|
|
5
5
|
function createBlankRow(cols) {
|
|
6
6
|
return Array.from({ length: cols }, () => null);
|
|
@@ -10,10 +10,7 @@ function cloneRow(row) {
|
|
|
10
10
|
if (!cell) return null;
|
|
11
11
|
return {
|
|
12
12
|
bg: cell.bg ?? null,
|
|
13
|
-
|
|
14
|
-
fg: cell.fg ?? null,
|
|
15
|
-
hidden: cell.hidden,
|
|
16
|
-
width: cell.width
|
|
13
|
+
fg: cell.fg ?? null
|
|
17
14
|
};
|
|
18
15
|
});
|
|
19
16
|
}
|
|
@@ -24,13 +21,11 @@ function createBuffer(cols, rows) {
|
|
|
24
21
|
return {
|
|
25
22
|
cursor: {
|
|
26
23
|
col: 0,
|
|
27
|
-
displayCol: 0,
|
|
28
24
|
row: 0
|
|
29
25
|
},
|
|
30
26
|
rows: Array.from({ length: rows }, () => createBlankRow(cols)),
|
|
31
27
|
savedCursor: {
|
|
32
28
|
col: 0,
|
|
33
|
-
displayCol: 0,
|
|
34
29
|
row: 0
|
|
35
30
|
},
|
|
36
31
|
scrollback: []
|
|
@@ -64,46 +59,20 @@ function createAnsiStyleTracker(options = {}) {
|
|
|
64
59
|
const clampCursor = (cursor) => {
|
|
65
60
|
cursor.row = clamp(cursor.row, 0, rows - 1);
|
|
66
61
|
cursor.col = clamp(cursor.col, 0, cols - 1);
|
|
67
|
-
cursor.displayCol = clamp(cursor.displayCol, 0, cols - 1);
|
|
68
62
|
};
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const makeCellState = (style, sourceCol, displayStart, width, hidden = false) => {
|
|
72
|
-
if (!hidden && style.fg === null && style.bg === null && displayStart === sourceCol && width === 1) return null;
|
|
63
|
+
const makeCellState = (style) => {
|
|
64
|
+
if (style.fg === null && style.bg === null) return null;
|
|
73
65
|
return {
|
|
74
66
|
bg: style.bg,
|
|
75
|
-
|
|
76
|
-
fg: style.fg,
|
|
77
|
-
hidden,
|
|
78
|
-
width
|
|
67
|
+
fg: style.fg
|
|
79
68
|
};
|
|
80
69
|
};
|
|
81
|
-
const markCellHidden = (row, col) => {
|
|
82
|
-
const cell = row[col];
|
|
83
|
-
row[col] = {
|
|
84
|
-
bg: cell?.bg ?? null,
|
|
85
|
-
displayStart: cellDisplayStart(row, col),
|
|
86
|
-
fg: cell?.fg ?? null,
|
|
87
|
-
hidden: true,
|
|
88
|
-
width: cellWidth(row, col)
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
const hideCellsCoveredByDisplayRange = (row, displayStart, width, sourceCol) => {
|
|
92
|
-
const displayEnd = displayStart + width;
|
|
93
|
-
for (let col = 0; col < cols; col += 1) {
|
|
94
|
-
if (col === sourceCol) continue;
|
|
95
|
-
const cellStart = cellDisplayStart(row, col);
|
|
96
|
-
const cellEnd = cellStart + cellWidth(row, col);
|
|
97
|
-
if (cellStart < displayEnd && cellEnd > displayStart) markCellHidden(row, col);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
70
|
const storeCellAtCursor = (width) => {
|
|
101
71
|
const buffer = activeBuffer();
|
|
102
72
|
const row = buffer.rows[buffer.cursor.row];
|
|
103
73
|
if (!row) return;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
row[buffer.cursor.col] = makeCellState(currentStyle, buffer.cursor.col, displayStart, width);
|
|
74
|
+
row[buffer.cursor.col] = makeCellState(currentStyle);
|
|
75
|
+
if (width > 1 && buffer.cursor.col + 1 < cols) row[buffer.cursor.col + 1] = makeCellState(currentStyle);
|
|
107
76
|
};
|
|
108
77
|
const scrollActiveBuffer = () => {
|
|
109
78
|
const buffer = activeBuffer();
|
|
@@ -114,17 +83,15 @@ function createAnsiStyleTracker(options = {}) {
|
|
|
114
83
|
const moveCursorTo = (buffer, row, col) => {
|
|
115
84
|
buffer.cursor.row = clamp(row, 0, rows - 1);
|
|
116
85
|
buffer.cursor.col = clamp(col, 0, cols - 1);
|
|
117
|
-
buffer.cursor.displayCol = cellDisplayStart(buffer.rows[buffer.cursor.row] ?? [], buffer.cursor.col);
|
|
118
86
|
};
|
|
119
87
|
const advanceCursor = (width) => {
|
|
120
88
|
const buffer = activeBuffer();
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
buffer.cursor.
|
|
89
|
+
const nextCol = buffer.cursor.col + width;
|
|
90
|
+
if (nextCol < cols) {
|
|
91
|
+
buffer.cursor.col = nextCol;
|
|
124
92
|
return;
|
|
125
93
|
}
|
|
126
94
|
buffer.cursor.col = 0;
|
|
127
|
-
buffer.cursor.displayCol = 0;
|
|
128
95
|
if (buffer.cursor.row < rows - 1) {
|
|
129
96
|
buffer.cursor.row += 1;
|
|
130
97
|
return;
|
|
@@ -170,9 +137,7 @@ function createAnsiStyleTracker(options = {}) {
|
|
|
170
137
|
return row.slice(0, nextCols);
|
|
171
138
|
});
|
|
172
139
|
buffer.cursor.col = clamp(buffer.cursor.col, 0, nextCols - 1);
|
|
173
|
-
buffer.cursor.displayCol = clamp(buffer.cursor.displayCol, 0, nextCols - 1);
|
|
174
140
|
buffer.savedCursor.col = clamp(buffer.savedCursor.col, 0, nextCols - 1);
|
|
175
|
-
buffer.savedCursor.displayCol = clamp(buffer.savedCursor.displayCol, 0, nextCols - 1);
|
|
176
141
|
};
|
|
177
142
|
const setAltBuffer = (nextState) => {
|
|
178
143
|
if (nextState === useAltBuffer) return;
|
|
@@ -312,11 +277,10 @@ function createAnsiStyleTracker(options = {}) {
|
|
|
312
277
|
}
|
|
313
278
|
if (character === "\r") {
|
|
314
279
|
buffer.cursor.col = 0;
|
|
315
|
-
buffer.cursor.displayCol = 0;
|
|
316
280
|
return;
|
|
317
281
|
}
|
|
318
282
|
if (character === " ") {
|
|
319
|
-
const nextTabStop = (Math.floor(buffer.cursor.
|
|
283
|
+
const nextTabStop = (Math.floor(buffer.cursor.col / 8) + 1) * 8;
|
|
320
284
|
moveCursorTo(buffer, buffer.cursor.row, nextTabStop);
|
|
321
285
|
return;
|
|
322
286
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
//#region src/frontend/browser-terminal-renderer.d.ts
|
|
1
|
+
//#region packages/browser/src/frontend/browser-terminal-renderer.d.ts
|
|
2
2
|
type TerminalCell = {
|
|
3
3
|
bg: number;
|
|
4
|
+
bgRgb?: number;
|
|
4
5
|
char: number;
|
|
5
6
|
fg: number;
|
|
7
|
+
fgRgb?: number;
|
|
6
8
|
flags: number;
|
|
9
|
+
width?: number;
|
|
7
10
|
};
|
|
8
11
|
type TerminalCursor = {
|
|
9
12
|
col: number;
|
|
@@ -25,33 +28,35 @@ type BrowserTerminalBridge = {
|
|
|
25
28
|
isDirtyRow(row: number): boolean;
|
|
26
29
|
usingAltScreen(): boolean;
|
|
27
30
|
};
|
|
28
|
-
type BrowserTerminalStyleOverride = {
|
|
29
|
-
bg?: string | null;
|
|
30
|
-
fg?: string | null;
|
|
31
|
-
hidden?: boolean;
|
|
32
|
-
} | null;
|
|
33
|
-
type BrowserTerminalStyleTracker = {
|
|
34
|
-
getGridOverride(row: number, col: number): BrowserTerminalStyleOverride;
|
|
35
|
-
getScrollbackOverride(offset: number, col: number): BrowserTerminalStyleOverride;
|
|
36
|
-
};
|
|
37
31
|
type BrowserTerminalRenderOptions = {
|
|
38
32
|
force?: boolean;
|
|
39
33
|
};
|
|
34
|
+
type BrowserTerminalRendererOptions = {
|
|
35
|
+
onScrollbackRowsDropped?: (rows: string[]) => void;
|
|
36
|
+
renderedScrollbackLimit?: number;
|
|
37
|
+
};
|
|
40
38
|
type SnapshotRow = {
|
|
41
39
|
element: HTMLElement;
|
|
42
40
|
text: string;
|
|
43
41
|
};
|
|
44
42
|
type GetCell = (col: number) => TerminalCell;
|
|
45
|
-
|
|
43
|
+
declare function resolveTerminalCellColumnSpan(getCell: GetCell, col: number, _lineLength: number): 1 | 2;
|
|
44
|
+
declare function serializeRowText(getCell: GetCell, lineLength: number): string;
|
|
46
45
|
declare class BrowserTerminalRenderer {
|
|
47
46
|
private _altScreenMeaningfulScrollbackCount;
|
|
48
47
|
private _altScreenVisibleRowSnapshot;
|
|
49
48
|
private _lastAltScreenState;
|
|
49
|
+
private _lastTotalScrollbackCount;
|
|
50
50
|
private _renderedScrollbackCount;
|
|
51
|
+
private _renderedScrollbackTexts;
|
|
51
52
|
private _restoreSnapshotCount;
|
|
53
|
+
private _rowSegmentKeys;
|
|
54
|
+
private _rowSegmentTexts;
|
|
52
55
|
private _scrollbackRowEls;
|
|
53
56
|
cols: number;
|
|
54
57
|
container: HTMLElement;
|
|
58
|
+
renderedScrollbackLimit: number;
|
|
59
|
+
onScrollbackRowsDropped: (rows: string[]) => void;
|
|
55
60
|
prevContainerBg: string;
|
|
56
61
|
prevCursorCol: number;
|
|
57
62
|
prevCursorRow: number;
|
|
@@ -59,13 +64,13 @@ declare class BrowserTerminalRenderer {
|
|
|
59
64
|
prevRowBg: string[];
|
|
60
65
|
rowEls: HTMLElement[];
|
|
61
66
|
rows: number;
|
|
62
|
-
|
|
63
|
-
constructor(container: HTMLElement, styleTracker: BrowserTerminalStyleTracker);
|
|
67
|
+
constructor(container: HTMLElement, options?: BrowserTerminalRendererOptions);
|
|
64
68
|
_resolveFirstLiveGridRow(): HTMLElement | null;
|
|
65
69
|
_insertScrollbackFragment(fragment: DocumentFragment): void;
|
|
66
70
|
setup(cols: number, rows: number): void;
|
|
67
|
-
_buildRowContent(rowEl: HTMLElement, getCell: GetCell,
|
|
71
|
+
_buildRowContent(rowEl: HTMLElement, getCell: GetCell, lineLen: number, cursorCol: number, rowIndex: number): void;
|
|
68
72
|
_buildScrollbackRowEl(bridge: BrowserTerminalBridge, offset: number): HTMLElement;
|
|
73
|
+
_readScrollbackRowTexts(bridge: BrowserTerminalBridge, scrollbackCount?: number): string[];
|
|
69
74
|
_captureVisibleRowSnapshot(): void;
|
|
70
75
|
_clearAltScreenRestoreSnapshot(): void;
|
|
71
76
|
_readGridRowText(bridge: BrowserTerminalBridge, row: number): string;
|
|
@@ -75,6 +80,9 @@ declare class BrowserTerminalRenderer {
|
|
|
75
80
|
trimPadding?: boolean;
|
|
76
81
|
}): string[];
|
|
77
82
|
_readScrollbackRowText(bridge: BrowserTerminalBridge, offset: number): string;
|
|
83
|
+
_isShiftedScrollbackMatch(bridge: BrowserTerminalBridge, shiftCount: number, scrollbackCount: number): boolean;
|
|
84
|
+
_resolveShiftedScrollbackCount(bridge: BrowserTerminalBridge, scrollbackCount: number): number | null;
|
|
85
|
+
getRenderedScrollbackCount(): number;
|
|
78
86
|
_readLiveTranscriptRows(bridge: BrowserTerminalBridge, currentVisibleRows: string[]): string[];
|
|
79
87
|
_liveTranscriptContainsSnapshots(bridge: BrowserTerminalBridge, snapshots: SnapshotRow[], currentVisibleRows: string[]): boolean;
|
|
80
88
|
_getRestoredScrollbackSnapshots(bridge: BrowserTerminalBridge): SnapshotRow[];
|
|
@@ -83,4 +91,4 @@ declare class BrowserTerminalRenderer {
|
|
|
83
91
|
render(bridge: BrowserTerminalBridge, options?: BrowserTerminalRenderOptions): void;
|
|
84
92
|
}
|
|
85
93
|
//#endregion
|
|
86
|
-
export { BrowserTerminalBridge, BrowserTerminalRenderOptions, BrowserTerminalRenderer,
|
|
94
|
+
export { BrowserTerminalBridge, BrowserTerminalRenderOptions, BrowserTerminalRenderer, BrowserTerminalRendererOptions, resolveTerminalCellColumnSpan, serializeRowText };
|