Skip to content

Commit 4c9cff8

Browse files
committed
✨ add row offset to createTerm for region-mode rendering
Add a `row` option to TermOptions that offsets all CUP sequences by the given number of rows, enabling rendering into a region below existing terminal content rather than always starting at row 1.
1 parent dde5727 commit 4c9cff8

7 files changed

Lines changed: 151 additions & 53 deletions

File tree

src/clayterm.c

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
/* ── Instance state ───────────────────────────────────────────────── */
3636

3737
struct Clayterm {
38-
int w, h;
38+
int w, h, row;
3939
Cell *front;
4040
Cell *back;
4141
Buffer out;
@@ -131,7 +131,7 @@ static void emit_attr(struct Clayterm *ct, uint32_t fg, uint32_t bg) {
131131

132132
static void emit_cursor(struct Clayterm *ct, int x, int y) {
133133
buf_str(&ct->out, "\x1b[");
134-
buf_num(&ct->out, y + 1);
134+
buf_num(&ct->out, y + 1 + ct->row);
135135
buf_put(&ct->out, ";", 1);
136136
buf_num(&ct->out, x + 1);
137137
buf_put(&ct->out, "H", 1);
@@ -352,7 +352,7 @@ int clayterm_size(int w, int h) {
352352

353353
static void clay_error(Clay_ErrorData err) { (void)err; }
354354

355-
struct Clayterm *init(void *mem, int w, int h) {
355+
struct Clayterm *init(void *mem, int w, int h, int row) {
356356
struct Clayterm *ct = (struct Clayterm *)mem;
357357
int cell_count = w * h;
358358
int cell_bytes = align8(cell_count * (int)sizeof(Cell));
@@ -369,6 +369,7 @@ struct Clayterm *init(void *mem, int w, int h) {
369369
*ct = (struct Clayterm){
370370
.w = w,
371371
.h = h,
372+
.row = row,
372373
.front = (Cell *)base,
373374
.back = (Cell *)(base + cell_bytes),
374375
.out = {base + cell_bytes * 2, 0, cell_count * OUT_BYTES_PER_CELL},
@@ -557,19 +558,19 @@ char *output(struct Clayterm *ct) { return ct->out.data; }
557558

558559
int length(struct Clayterm *ct) { return ct->out.length; }
559560

560-
int pointer_over_count(void) {
561-
return Clay_GetPointerOverIds().length;
562-
}
561+
int pointer_over_count(void) { return Clay_GetPointerOverIds().length; }
563562

564563
int pointer_over_id_string_length(int index) {
565564
Clay_ElementIdArray ids = Clay_GetPointerOverIds();
566-
if (index >= ids.length) return 0;
565+
if (index >= ids.length)
566+
return 0;
567567
return ids.internalArray[index].stringId.length;
568568
}
569569

570570
int pointer_over_id_string_ptr(int index) {
571571
Clay_ElementIdArray ids = Clay_GetPointerOverIds();
572-
if (index >= ids.length) return 0;
572+
if (index >= ids.length)
573+
return 0;
573574
return (int)ids.internalArray[index].stringId.chars;
574575
}
575576

src/clayterm.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct Clayterm;
1111

1212
/* WASM exports */
1313
int clayterm_size(int w, int h);
14-
struct Clayterm *init(void *mem, int w, int h);
14+
struct Clayterm *init(void *mem, int w, int h, int row);
1515
void reduce(struct Clayterm *ct, uint32_t *buf, int len);
1616
char *output(struct Clayterm *ct);
1717
int length(struct Clayterm *ct);

src/input.c

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,14 @@ static struct InputEvent *emit(struct InputState *st) {
6363

6464
static uint8_t mouse_mods(int b) {
6565
uint8_t m = 0;
66-
if (b & 4) m |= MOD_SHIFT;
67-
if (b & 8) m |= MOD_ALT;
68-
if (b & 16) m |= MOD_CTRL;
69-
if (b & 32) m |= MOD_MOTION;
66+
if (b & 4)
67+
m |= MOD_SHIFT;
68+
if (b & 8)
69+
m |= MOD_ALT;
70+
if (b & 16)
71+
m |= MOD_CTRL;
72+
if (b & 32)
73+
m |= MOD_MOTION;
7074
return m;
7175
}
7276

@@ -485,7 +489,8 @@ static int parse_csi_u(struct InputState *st, struct InputEvent *ev) {
485489
for (int j = off; j < i - 1 && tc < MAX_TEXT_CODEPOINTS; j++) {
486490
char c = st->buf[j];
487491
if (c >= '0' && c <= '9') {
488-
if (val == -1) val = 0;
492+
if (val == -1)
493+
val = 0;
489494
val = val * 10 + (c - '0');
490495
} else if (c == ':') {
491496
if (val >= 0)
@@ -504,38 +509,67 @@ static int parse_csi_u(struct InputState *st, struct InputEvent *ev) {
504509

505510
static uint16_t csi_legacy_key(char term, int number) {
506511
switch (term) {
507-
case 'A': return KEY_ARROW_UP;
508-
case 'B': return KEY_ARROW_DOWN;
509-
case 'C': return KEY_ARROW_RIGHT;
510-
case 'D': return KEY_ARROW_LEFT;
511-
case 'H': return KEY_HOME;
512-
case 'F': return KEY_END;
513-
case 'P': return KEY_F1;
514-
case 'Q': return KEY_F2;
515-
case 'S': return KEY_F4;
512+
case 'A':
513+
return KEY_ARROW_UP;
514+
case 'B':
515+
return KEY_ARROW_DOWN;
516+
case 'C':
517+
return KEY_ARROW_RIGHT;
518+
case 'D':
519+
return KEY_ARROW_LEFT;
520+
case 'H':
521+
return KEY_HOME;
522+
case 'F':
523+
return KEY_END;
524+
case 'P':
525+
return KEY_F1;
526+
case 'Q':
527+
return KEY_F2;
528+
case 'S':
529+
return KEY_F4;
516530
case '~':
517531
switch (number) {
518-
case 2: return KEY_INSERT;
519-
case 3: return KEY_DELETE;
520-
case 5: return KEY_PGUP;
521-
case 6: return KEY_PGDN;
522-
case 7: return KEY_HOME;
523-
case 8: return KEY_END;
524-
case 11: return KEY_F1;
525-
case 12: return KEY_F2;
526-
case 13: return KEY_F3;
527-
case 14: return KEY_F4;
528-
case 15: return KEY_F5;
529-
case 17: return KEY_F6;
530-
case 18: return KEY_F7;
531-
case 19: return KEY_F8;
532-
case 20: return KEY_F9;
533-
case 21: return KEY_F10;
534-
case 23: return KEY_F11;
535-
case 24: return KEY_F12;
536-
default: return 0;
532+
case 2:
533+
return KEY_INSERT;
534+
case 3:
535+
return KEY_DELETE;
536+
case 5:
537+
return KEY_PGUP;
538+
case 6:
539+
return KEY_PGDN;
540+
case 7:
541+
return KEY_HOME;
542+
case 8:
543+
return KEY_END;
544+
case 11:
545+
return KEY_F1;
546+
case 12:
547+
return KEY_F2;
548+
case 13:
549+
return KEY_F3;
550+
case 14:
551+
return KEY_F4;
552+
case 15:
553+
return KEY_F5;
554+
case 17:
555+
return KEY_F6;
556+
case 18:
557+
return KEY_F7;
558+
case 19:
559+
return KEY_F8;
560+
case 20:
561+
return KEY_F9;
562+
case 21:
563+
return KEY_F10;
564+
case 23:
565+
return KEY_F11;
566+
case 24:
567+
return KEY_F12;
568+
default:
569+
return 0;
537570
}
538-
default: return 0;
571+
default:
572+
return 0;
539573
}
540574
}
541575

@@ -578,8 +612,8 @@ static int parse_csi_legacy(struct InputState *st, struct InputEvent *ev) {
578612
mod = cur;
579613
cur = -1;
580614
sub++;
581-
} else if ((c >= 'A' && c <= 'D') || c == 'F' || c == 'H' ||
582-
c == 'P' || c == 'Q' || c == 'S' || c == '~') {
615+
} else if ((c >= 'A' && c <= 'D') || c == 'F' || c == 'H' || c == 'P' ||
616+
c == 'Q' || c == 'S' || c == '~') {
583617
if (param == 0)
584618
number = cur;
585619
else if (param == 1 && sub == 0)

src/input.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@
6969

7070
/* ── Kitty action types ──────────────────────────────────────────── */
7171

72-
#define ACTION_NONE 0
73-
#define ACTION_PRESS 1
74-
#define ACTION_REPEAT 2
72+
#define ACTION_NONE 0
73+
#define ACTION_PRESS 1
74+
#define ACTION_REPEAT 2
7575
#define ACTION_RELEASE 3
7676

7777
/* ── Key codes ────────────────────────────────────────────────────── */

term-native.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ export interface Native {
1111

1212
import { compiled } from "./wasm.ts";
1313

14-
export async function createTermNative(w: number, h: number): Promise<Native> {
14+
export async function createTermNative(
15+
w: number,
16+
h: number,
17+
row: number = 0,
18+
): Promise<Native> {
1519
let memory = new WebAssembly.Memory({ initial: 2 });
1620
let exports: Record<string, CallableFunction> = {};
1721

@@ -43,7 +47,7 @@ export async function createTermNative(w: number, h: number): Promise<Native> {
4347
let ct = exports as unknown as {
4448
__heap_base: WebAssembly.Global;
4549
clayterm_size(w: number, h: number): number;
46-
init(mem: number, w: number, h: number): number;
50+
init(mem: number, w: number, h: number, row: number): number;
4751
reduce(ct: number, buf: number, len: number): void;
4852
output(ct: number): number;
4953
length(ct: number): number;
@@ -64,7 +68,7 @@ export async function createTermNative(w: number, h: number): Promise<Native> {
6468
memory.grow(pages - current);
6569
}
6670

67-
let statePtr = ct.init(heap, w, h);
71+
let statePtr = ct.init(heap, w, h, row);
6872
let opsBuf = (heap + size + 3) & ~3;
6973

7074
return {

term.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createTermNative } from "./term-native.ts";
44
export interface TermOptions {
55
height: number;
66
width: number;
7+
row?: number;
78
}
89

910
export interface RenderOptions {
@@ -29,8 +30,8 @@ export interface Term {
2930
}
3031

3132
export async function createTerm(options: TermOptions): Promise<Term> {
32-
let { width, height } = options;
33-
let native = await createTermNative(width, height);
33+
let { width, height, row = 0 } = options;
34+
let native = await createTermNative(width, height, row);
3435
let { memory, statePtr, opsBuf } = native;
3536

3637
let prev = new Set<string>();

test/term.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,62 @@ describe("term", () => {
8888
│ │
8989
╰──────────────────────────────────────╯`.trim());
9090
});
91+
92+
describe("row offset", () => {
93+
it("renders two frames at the offset position", async () => {
94+
let term = await createTerm({ width: 20, height: 5, row: 5 });
95+
let box = (msg: string) => [
96+
open("root", {
97+
layout: { width: grow(), height: grow(), direction: "ttb" },
98+
}),
99+
open("box", {
100+
layout: {
101+
width: grow(),
102+
height: grow(),
103+
direction: "ttb",
104+
padding: { left: 1, top: 1 },
105+
},
106+
border: {
107+
color: rgba(255, 255, 255),
108+
left: 1,
109+
right: 1,
110+
top: 1,
111+
bottom: 1,
112+
},
113+
}),
114+
text(msg),
115+
close(),
116+
close(),
117+
];
118+
119+
let header = await createTerm({ width: 20, height: 5 });
120+
let banner = decode(header.render(box("hello")).output);
121+
122+
let first = decode(term.render(box("world")).output);
123+
expect(print(banner + first, 20, 10)).toEqual(`\
124+
┌──────────────────┐
125+
│hello │
126+
│ │
127+
│ │
128+
└──────────────────┘
129+
┌──────────────────┐
130+
│world │
131+
│ │
132+
│ │
133+
└──────────────────┘`);
134+
135+
let second = decode(term.render(box("universe")).output);
136+
expect(print(banner + first + second, 20, 10)).toEqual(`\
137+
┌──────────────────┐
138+
│hello │
139+
│ │
140+
│ │
141+
└──────────────────┘
142+
┌──────────────────┐
143+
│universe │
144+
│ │
145+
│ │
146+
└──────────────────┘`);
147+
});
148+
});
91149
});

0 commit comments

Comments
 (0)