mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-06-28 05:29:29 +00:00
Fix binary buffer encoding to support large terminals
The binary encoding was using 16-bit unsigned integers which failed when: - Terminal has many rows (>65k) - Cursor is above the viewport (negative relative position) Changes: - Upgrade to version 2 of the binary format - Use 32-bit integers for dimensions and positions - Use signed integers for viewport/cursor positions - Update header size from 16 to 32 bytes - Update documentation to reflect new format This fixes the issue where cursorY could be negative when the cursor is above the visible viewport (e.g., cursorY=0, viewportY=46 results in relative cursorY=-46). 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b041743287
commit
ef7a679c2b
3 changed files with 49 additions and 46 deletions
|
|
@ -11,26 +11,28 @@ The snapshot format is a compact binary representation of terminal buffer state,
|
|||
```
|
||||
┌──────────────┬─────────────────────────────────┐
|
||||
│ Header │ Cell Stream │
|
||||
│ (16 bytes) │ (variable, 4+ bytes/cell) │
|
||||
│ (32 bytes) │ (variable, 4+ bytes/cell) │
|
||||
└──────────────┴─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Header Format (16 bytes)
|
||||
## Header Format (32 bytes) - Version 2
|
||||
|
||||
```
|
||||
Offset Size Field Description
|
||||
------ ---- ---------- -----------
|
||||
0x00 2 Magic 0x5654 ("VT" in ASCII)
|
||||
0x02 1 Version Format version (currently 0x01)
|
||||
0x02 1 Version Format version (0x02 for 32-bit support)
|
||||
0x03 1 Flags Reserved for future use
|
||||
0x04 2 Cols Terminal width (little-endian)
|
||||
0x06 2 Rows Number of rows in this snapshot (little-endian)
|
||||
0x08 2 ViewportY Starting line number in buffer (little-endian)
|
||||
0x0A 2 CursorX Cursor column position (little-endian)
|
||||
0x0C 2 CursorY Cursor row position relative to viewport (little-endian)
|
||||
0x0E 2 Reserved Reserved for future use
|
||||
0x04 4 Cols Terminal width (32-bit unsigned, little-endian)
|
||||
0x08 4 Rows Number of rows in this snapshot (32-bit unsigned, little-endian)
|
||||
0x0C 4 ViewportY Starting line number in buffer (32-bit signed, little-endian)
|
||||
0x10 4 CursorX Cursor column position (32-bit signed, little-endian)
|
||||
0x14 4 CursorY Cursor row position relative to viewport (32-bit signed, little-endian)
|
||||
0x18 4 Reserved Reserved for future use
|
||||
```
|
||||
|
||||
Note: CursorY is relative to the viewport and can be negative if the cursor is above the visible area.
|
||||
|
||||
## Cell Format
|
||||
|
||||
Each cell uses a variable-length encoding:
|
||||
|
|
@ -131,16 +133,17 @@ Content-Length: {size}
|
|||
For a 80x24 terminal showing "Hello" on black background:
|
||||
|
||||
```
|
||||
Header (16 bytes):
|
||||
56 54 01 00 50 00 18 00 00 00 05 00 00 00 00 00
|
||||
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─ Reserved
|
||||
│ │ │ │ │ │ │ │ │ │ │ └───┴─┴─ Cursor (5,0)
|
||||
│ │ │ │ │ │ │ │ └─┴─ ViewportY (0)
|
||||
│ │ │ │ │ │ └─┴─ Rows (24)
|
||||
│ │ │ │ └─┴─ Cols (80)
|
||||
Header (32 bytes):
|
||||
56 54 02 00 50 00 00 00 18 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00
|
||||
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─ Reserved
|
||||
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─ CursorY (0)
|
||||
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─ CursorX (5)
|
||||
│ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─ ViewportY (0)
|
||||
│ │ │ │ │ │ │ │ └─┴─┴─┴─ Rows (24)
|
||||
│ │ │ │ └─┴─┴─┴─ Cols (80)
|
||||
│ │ │ └─ Flags (0)
|
||||
│ │ └─ Version (1)
|
||||
│ │ └─ Version (2)
|
||||
└─┴─ Magic "VT"
|
||||
|
||||
Cells:
|
||||
|
|
|
|||
|
|
@ -286,22 +286,22 @@ export class TerminalRenderer {
|
|||
}
|
||||
|
||||
const version = view.getUint8(offset++);
|
||||
if (version !== 0x01) {
|
||||
throw new Error('Unsupported buffer version');
|
||||
if (version !== 0x02) {
|
||||
throw new Error(`Unsupported buffer version: ${version}`);
|
||||
}
|
||||
|
||||
const _flags = view.getUint8(offset++);
|
||||
const cols = view.getUint16(offset, true);
|
||||
offset += 2;
|
||||
const rows = view.getUint16(offset, true);
|
||||
offset += 2;
|
||||
const viewportY = view.getUint16(offset, true);
|
||||
offset += 2;
|
||||
const cursorX = view.getUint16(offset, true);
|
||||
offset += 2;
|
||||
const cursorY = view.getUint16(offset, true);
|
||||
offset += 2;
|
||||
offset += 2; // Skip reserved
|
||||
const cols = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
const rows = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
const viewportY = view.getInt32(offset, true); // Signed
|
||||
offset += 4;
|
||||
const cursorX = view.getInt32(offset, true); // Signed
|
||||
offset += 4;
|
||||
const cursorY = view.getInt32(offset, true); // Signed
|
||||
offset += 4;
|
||||
offset += 4; // Skip reserved
|
||||
|
||||
// Decode cells
|
||||
const cells: BufferCell[][] = [];
|
||||
|
|
|
|||
|
|
@ -301,29 +301,29 @@ export class TerminalManager {
|
|||
const { cols, rows, viewportY, cursorX, cursorY, cells } = snapshot;
|
||||
|
||||
// Calculate buffer size (rough estimate)
|
||||
const estimatedSize = 16 + rows * cols * 4;
|
||||
const estimatedSize = 32 + rows * cols * 4; // Increased header size
|
||||
const buffer = Buffer.allocUnsafe(estimatedSize);
|
||||
let offset = 0;
|
||||
|
||||
// Write header (16 bytes)
|
||||
// Write header (32 bytes)
|
||||
buffer.writeUInt16LE(0x5654, offset);
|
||||
offset += 2; // Magic "VT"
|
||||
buffer.writeUInt8(0x01, offset);
|
||||
buffer.writeUInt8(0x02, offset); // Version 2 with 32-bit values
|
||||
offset += 1; // Version
|
||||
buffer.writeUInt8(0x00, offset);
|
||||
offset += 1; // Flags
|
||||
buffer.writeUInt16LE(cols, offset);
|
||||
offset += 2; // Cols
|
||||
buffer.writeUInt16LE(rows, offset);
|
||||
offset += 2; // Rows
|
||||
buffer.writeUInt16LE(viewportY, offset);
|
||||
offset += 2; // ViewportY
|
||||
buffer.writeUInt16LE(cursorX, offset);
|
||||
offset += 2; // CursorX
|
||||
buffer.writeUInt16LE(cursorY, offset);
|
||||
offset += 2; // CursorY
|
||||
buffer.writeUInt16LE(0, offset);
|
||||
offset += 2; // Reserved
|
||||
buffer.writeUInt32LE(cols, offset);
|
||||
offset += 4; // Cols (32-bit)
|
||||
buffer.writeUInt32LE(rows, offset);
|
||||
offset += 4; // Rows (32-bit)
|
||||
buffer.writeInt32LE(viewportY, offset); // Signed for large buffers
|
||||
offset += 4; // ViewportY (32-bit signed)
|
||||
buffer.writeInt32LE(cursorX, offset); // Signed for consistency
|
||||
offset += 4; // CursorX (32-bit signed)
|
||||
buffer.writeInt32LE(cursorY, offset); // Signed for relative positions
|
||||
offset += 4; // CursorY (32-bit signed)
|
||||
buffer.writeUInt32LE(0, offset);
|
||||
offset += 4; // Reserved
|
||||
|
||||
// Write cells with run-length encoding
|
||||
let lastCell: BufferCell | null = null;
|
||||
|
|
|
|||
Loading…
Reference in a new issue