Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/@wterm/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const bridge = await WasmBridge.load();
bridge.init(80, 24);
bridge.writeString("Hello, world!\r\n");

const cell = bridge.getCell(0, 0); // { char, fg, bg, flags }
const cell = bridge.getCell(0, 0); // { char, fg, bg, flags, width? }
const cursor = bridge.getCursor(); // { row, col, visible }
```

Expand All @@ -59,7 +59,7 @@ const cursor = bridge.getCursor(); // { row, col, visible }
| `writeString(str)` | Write a UTF-8 string to the terminal |
| `writeRaw(data: Uint8Array)` | Write raw bytes to the terminal |
| `resize(cols, rows)` | Resize the terminal grid |
| `getCell(row, col)` | Get cell data (`{ char, fg, bg, flags }`) |
| `getCell(row, col)` | Get cell data (`{ char, fg, bg, flags, width? }`) |
| `getCursor()` | Get cursor state (`{ row, col, visible }`) |
| `getCols()` / `getRows()` | Get current grid dimensions |
| `isDirtyRow(row)` | Check if a row needs re-rendering |
Expand Down
2 changes: 2 additions & 0 deletions packages/@wterm/core/src/terminal-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export interface CellData {
fg: number;
bg: number;
flags: number;
/** Display width in terminal columns. Width 0 marks a continuation cell for a wide glyph. */
width?: 0 | 1 | 2;
/** Resolved 24-bit foreground color (0xRRGGBB). Present when the core provides true color. */
fgRgb?: number;
/** Resolved 24-bit background color (0xRRGGBB). Present when the core provides true color. */
Expand Down
2 changes: 2 additions & 0 deletions packages/@wterm/core/src/wasm-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export class WasmBridge implements TerminalCore {
fg: dv.getUint16(offset + 4, true),
bg: dv.getUint16(offset + 6, true),
flags: dv.getUint8(offset + 8),
width: 1,
};
}

Expand Down Expand Up @@ -187,6 +188,7 @@ export class WasmBridge implements TerminalCore {
fg: dv.getUint16(off + 4, true),
bg: dv.getUint16(off + 6, true),
flags: dv.getUint8(off + 8),
width: 1,
};
}

Expand Down
35 changes: 35 additions & 0 deletions packages/@wterm/dom/src/__tests__/renderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,41 @@ describe("Renderer", () => {
expect(text).toContain("i");
});

it("skips wide-character continuation cells", () => {
const grid = [
[
makeCell("A"),
{ ...makeCell("あ"), width: 2 as const },
{ char: 32, fg: 256, bg: 256, flags: 0, width: 0 as const },
makeCell("B"),
],
];
const bridge = createMockBridge(4, 1, grid);
const renderer = new Renderer(container);
renderer.render(bridge as any);

expect(container.textContent).toContain("AあB");
expect(container.textContent).not.toContain("あ B");
});

it("keeps cursor placement after a wide-character continuation cell", () => {
const grid = [
[
makeCell("A"),
{ ...makeCell("あ"), width: 2 as const },
{ char: 32, fg: 256, bg: 256, flags: 0, width: 0 as const },
makeCell("B"),
],
];
const bridge = createMockBridge(4, 1, grid);
bridge.getCursor = () => ({ row: 0, col: 3, visible: true });
const renderer = new Renderer(container);
renderer.render(bridge as any);

const cursor = container.querySelector(".term-cursor");
expect(cursor?.textContent).toBe("B");
});

it("applies cursor class to cursor position", () => {
const grid = [[makeCell("A"), makeCell("B")]];
const bridge = createMockBridge(2, 1, grid);
Expand Down
18 changes: 9 additions & 9 deletions packages/@wterm/dom/src/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TerminalCore } from "@wterm/core";
import type { CellData, TerminalCore } from "@wterm/core";

const DEFAULT_COLOR = 256;
const FLAG_BOLD = 0x01;
Expand Down Expand Up @@ -238,14 +238,7 @@ export class Renderer {

private _buildRowContent(
rowEl: HTMLDivElement,
getCell: (col: number) => {
char: number;
fg: number;
bg: number;
flags: number;
fgRgb?: number;
bgRgb?: number;
},
getCell: (col: number) => CellData,
lineLen: number,
cursorCol: number,
rowIndex: number,
Expand Down Expand Up @@ -290,6 +283,13 @@ export class Renderer {
const cell = getCell(col);
const inBounds = col < lineLen;
const cp = inBounds ? cell.char : 0;
if (inBounds && cell.width === 0) {
flushRun(col);
runStyle = "";
runText = "";
runStart = col + 1;
continue;
}

if (inBounds && cp >= 0x2580 && cp <= 0x259f) {
flushRun(col);
Expand Down
34 changes: 22 additions & 12 deletions packages/@wterm/ghostty/src/ghostty-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,25 @@ function packRgb(r: number, g: number, b: number): number {
return (r << 16) | (g << 8) | b;
}

function toCellData(cell: ReturnType<typeof parseCell>): CellData {
const result: CellData = {
char: cell.codepoint || 32,
fg: DEFAULT_COLOR,
bg: DEFAULT_COLOR,
flags: cell.flags,
width: cell.width === 0 || cell.width === 2 ? cell.width : 1,
};
if (cell.colorFlags & 1) result.fgRgb = packRgb(cell.fgR, cell.fgG, cell.fgB);
if (cell.colorFlags & 2) result.bgRgb = packRgb(cell.bgR, cell.bgG, cell.bgB);
return result;
}

const BLANK_CELL: CellData = {
char: 32,
fg: DEFAULT_COLOR,
bg: DEFAULT_COLOR,
flags: 0,
width: 1,
};

export interface GhosttyOptions {
Expand Down Expand Up @@ -143,20 +157,15 @@ export class GhosttyCore implements TerminalCore {
if (byteOffset + CELL_BYTES > this._viewportBufSize) return BLANK_CELL;

const cell = parseCell(view, byteOffset);
if (cell.codepoint === 0 && cell.flags === 0 && cell.colorFlags === 0)
if (
cell.codepoint === 0 &&
cell.flags === 0 &&
cell.colorFlags === 0 &&
cell.width !== 0
)
return BLANK_CELL;

const result: CellData = {
char: cell.codepoint || 32,
fg: DEFAULT_COLOR,
bg: DEFAULT_COLOR,
flags: cell.flags,
};
if (cell.colorFlags & 1)
result.fgRgb = packRgb(cell.fgR, cell.fgG, cell.fgB);
if (cell.colorFlags & 2)
result.bgRgb = packRgb(cell.bgR, cell.bgG, cell.bgB);
return result;
return toCellData(cell);
}

isDirtyRow(row: number): boolean {
Expand Down Expand Up @@ -262,6 +271,7 @@ export class GhosttyCore implements TerminalCore {
fg: DEFAULT_COLOR,
bg: DEFAULT_COLOR,
flags: cell.flags,
width: cell.width === 0 || cell.width === 2 ? cell.width : 1,
fgRgb: packRgb(cell.fgR, cell.fgG, cell.fgB),
bgRgb: packRgb(cell.bgR, cell.bgG, cell.bgB),
};
Expand Down