bunmicro 0.8.3 → 0.9.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.
@@ -8,7 +8,7 @@ const ANSI_COLORS = [
8
8
  ];
9
9
 
10
10
  function blankCell() {
11
- return { ch: " ", fg: "default", bg: "default", bold: false, italic: false, underline: false, reverse: false };
11
+ return { ch: " ", combining: [], filler: false, fg: "default", bg: "default", bold: false, italic: false, underline: false, reverse: false };
12
12
  }
13
13
 
14
14
  function findCSIEnd(str, start) {
@@ -24,6 +24,52 @@ function toHex2(n) {
24
24
  return ((n ?? 0) & 0xFF).toString(16).padStart(2, "0");
25
25
  }
26
26
 
27
+ function isZeroWidthCodePoint(cp) {
28
+ return (
29
+ cp === 0x200D ||
30
+ (cp >= 0x0300 && cp <= 0x036F) ||
31
+ (cp >= 0x1AB0 && cp <= 0x1AFF) ||
32
+ (cp >= 0x1DC0 && cp <= 0x1DFF) ||
33
+ (cp >= 0x20D0 && cp <= 0x20FF) ||
34
+ (cp >= 0xFE00 && cp <= 0xFE0F) ||
35
+ (cp >= 0xFE20 && cp <= 0xFE2F) ||
36
+ (cp >= 0xE0100 && cp <= 0xE01EF)
37
+ );
38
+ }
39
+
40
+ function isWideCodePoint(cp) {
41
+ if (cp < 0x1100) return false;
42
+ return (
43
+ cp <= 0x115F ||
44
+ cp === 0x2329 || cp === 0x232A ||
45
+ (cp >= 0x2E80 && cp <= 0x303E) ||
46
+ (cp >= 0x3040 && cp <= 0x33FF) ||
47
+ (cp >= 0x3400 && cp <= 0x4DBF) ||
48
+ (cp >= 0x4E00 && cp <= 0xA4C6) ||
49
+ (cp >= 0xA960 && cp <= 0xA97C) ||
50
+ (cp >= 0xAC00 && cp <= 0xD7A3) ||
51
+ (cp >= 0xF900 && cp <= 0xFAFF) ||
52
+ (cp >= 0xFE10 && cp <= 0xFE19) ||
53
+ (cp >= 0xFE30 && cp <= 0xFE4F) ||
54
+ (cp >= 0xFF01 && cp <= 0xFF60) ||
55
+ (cp >= 0xFFE0 && cp <= 0xFFE6) ||
56
+ (cp >= 0x1B000 && cp <= 0x1B0FF) ||
57
+ (cp >= 0x1F004 && cp <= 0x1F0CF) ||
58
+ (cp >= 0x1F18F && cp <= 0x1F19A) ||
59
+ (cp >= 0x1F200 && cp <= 0x1F2FF) ||
60
+ (cp >= 0x1F300 && cp <= 0x1FAFF) ||
61
+ (cp >= 0x20000 && cp <= 0x2FFFD) ||
62
+ (cp >= 0x30000 && cp <= 0x3FFFD)
63
+ );
64
+ }
65
+
66
+ function charWidth(ch) {
67
+ if (!ch) return 0;
68
+ const cp = ch.codePointAt(0);
69
+ if (cp < 32 || (cp >= 0x7f && cp < 0xa0) || isZeroWidthCodePoint(cp)) return 0;
70
+ return isWideCodePoint(cp) ? 2 : 1;
71
+ }
72
+
27
73
  export class VT100 {
28
74
  constructor(cols, rows) {
29
75
  this.cols = Math.max(1, cols);
@@ -56,10 +102,7 @@ export class VT100 {
56
102
  return this.cells[this._idx(x, y)];
57
103
  }
58
104
 
59
- _setCell(x, y, ch) {
60
- const cell = this._cell(x, y);
61
- if (!cell) return;
62
- cell.ch = ch;
105
+ _copyStyleTo(cell) {
63
106
  cell.fg = this.sgr.fg;
64
107
  cell.bg = this.sgr.bg;
65
108
  cell.bold = this.sgr.bold;
@@ -68,11 +111,54 @@ export class VT100 {
68
111
  cell.reverse = this.sgr.reverse;
69
112
  }
70
113
 
71
- _clearCell(x, y) {
114
+ _clearCellRaw(x, y) {
72
115
  const cell = this._cell(x, y);
73
116
  if (cell) Object.assign(cell, blankCell());
74
117
  }
75
118
 
119
+ _breakWideAt(x, y) {
120
+ const cell = this._cell(x, y);
121
+ if (!cell) return;
122
+ if (cell.filler) this._clearCellRaw(x - 1, y);
123
+ if (x + 1 < this.cols && this._cell(x + 1, y)?.filler) this._clearCellRaw(x + 1, y);
124
+ }
125
+
126
+ _setCell(x, y, ch, width = 1) {
127
+ if (width === 0) {
128
+ const targetX = this.cx > 0 ? this.cx - 1 : 0;
129
+ const cell = this._cell(targetX, this.cy);
130
+ if (cell && !cell.filler) cell.combining.push(ch);
131
+ return;
132
+ }
133
+ if (width > 1 && x >= this.cols - 1) {
134
+ this.cx = 0;
135
+ this._lineFeed();
136
+ x = this.cx;
137
+ y = this.cy;
138
+ }
139
+ this._breakWideAt(x, y);
140
+ if (width > 1) this._breakWideAt(x + 1, y);
141
+ const cell = this._cell(x, y);
142
+ if (!cell) return;
143
+ cell.ch = ch;
144
+ cell.combining = [];
145
+ cell.filler = false;
146
+ this._copyStyleTo(cell);
147
+ if (width > 1) {
148
+ const filler = this._cell(x + 1, y);
149
+ if (filler) {
150
+ Object.assign(filler, blankCell());
151
+ filler.filler = true;
152
+ this._copyStyleTo(filler);
153
+ }
154
+ }
155
+ }
156
+
157
+ _clearCell(x, y) {
158
+ this._breakWideAt(x, y);
159
+ this._clearCellRaw(x, y);
160
+ }
161
+
76
162
  _clearLineFrom(x, y) {
77
163
  for (let i = x; i < this.cols; i++) this._clearCell(i, y);
78
164
  }
@@ -198,12 +284,15 @@ export class VT100 {
198
284
  i++; // SO/SI charset switch, ignore
199
285
  } else if (code >= 0x20) {
200
286
  // Printable
201
- this._setCell(this.cx, this.cy, ch);
202
- this.cx++;
287
+ const cp = data.codePointAt(i);
288
+ const rune = String.fromCodePoint(cp);
289
+ const width = charWidth(rune);
290
+ this._setCell(this.cx, this.cy, rune, width);
291
+ this.cx += width;
203
292
  if (this.cx >= this.cols) {
204
293
  this.cx = 0; this._lineFeed();
205
294
  }
206
- i++;
295
+ i += cp > 0xFFFF ? 2 : 1;
207
296
  } else {
208
297
  i++; // other control: skip
209
298
  }
package/todo.txt CHANGED
@@ -30,6 +30,10 @@ Current handoff notes
30
30
  - Matchbrace highlights (), {}, [] and statusline column click cycles between matched braces when cursor is on a matched brace.
31
31
  - Ctrl-C/Ctrl-X/Ctrl-V/Ctrl-Y work for line and selection clipboard flows; external clipboard failures fall back to the internal register.
32
32
  - Copy/cut/paste and similar status messages render on a standalone info row above the statusline.
33
+ [x] term pane Unicode input/output support:
34
+ - PTY output now uses a streaming UTF-8 decoder so multi-byte Unicode split across chunks is preserved.
35
+ - VT100 printable handling iterates by code point, stores combining marks on the base cell, and uses filler cells for wide CJK/emoji output.
36
+ - Terminal pane rendering skips filler cells and emits combining marks through Screen.SetContent.
33
37
  [x] replace/replaceall commands aligned with Go behavior:
34
38
  - Flags: -a (replace all at once), -l (literal/no-regex, uses RegExp.escape).
35
39
  - replace without -a shows interactive Y/N prompt per match (Perform replacement (y,n,esc)).