@opentui/core 0.5.3 → 0.5.6

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/index.node.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  mergeKeyAliases,
44
44
  mergeKeyBindings,
45
45
  wrapWithDelegates
46
- } from "./chunk-node-kq7as74d.js";
46
+ } from "./chunk-node-ks0581vk.js";
47
47
  import {
48
48
  ASCIIFontSelectionHelper,
49
49
  ATTRIBUTE_BASE_BITS,
@@ -203,7 +203,7 @@ import {
203
203
  visualizeRenderableTree,
204
204
  white,
205
205
  yellow
206
- } from "./chunk-node-aj3n20gq.js";
206
+ } from "./chunk-node-2h23nsbj.js";
207
207
  // src/post/effects.ts
208
208
  function toU8(value) {
209
209
  return Math.round(Math.max(0, Math.min(1, Number.isFinite(value) ? value : 0)) * 255);
@@ -8222,6 +8222,378 @@ class DiffRenderable extends Renderable {
8222
8222
  return offsets;
8223
8223
  }
8224
8224
  }
8225
+ // src/renderables/EmbeddedTerminal.ts
8226
+ var MOD_SHIFT = 1 << 0;
8227
+ var MOD_CTRL = 1 << 1;
8228
+ var MOD_ALT = 1 << 2;
8229
+ var MOD_SUPER = 1 << 3;
8230
+ var MOD_CAPS_LOCK = 1 << 4;
8231
+ var MOD_NUM_LOCK = 1 << 5;
8232
+
8233
+ class EmbeddedTerminalRenderable extends Renderable {
8234
+ selectable = true;
8235
+ lib;
8236
+ handle = null;
8237
+ _onData;
8238
+ _onTerminalResize;
8239
+ _onScreenChange;
8240
+ keyreleaseHandler = null;
8241
+ hadRenderHooks = false;
8242
+ selection = false;
8243
+ constructor(ctx, options) {
8244
+ const cols = options.cols ?? (typeof options.width === "number" ? options.width : 80);
8245
+ const rows = options.rows ?? (typeof options.height === "number" ? options.height : 24);
8246
+ super(ctx, {
8247
+ ...options,
8248
+ width: options.width ?? cols,
8249
+ height: options.height ?? rows,
8250
+ buffered: true
8251
+ });
8252
+ this._focusable = true;
8253
+ this._onData = options.onData;
8254
+ this._onTerminalResize = options.onTerminalResize;
8255
+ this._onScreenChange = options.onScreenChange;
8256
+ this.selectable = options.selectable ?? true;
8257
+ this.lib = resolveRenderLib();
8258
+ try {
8259
+ this.handle = this.lib.createEmbeddedTerminal({ cols, rows, maxScrollback: options.maxScrollback });
8260
+ this.setupMouse(options);
8261
+ } catch (error) {
8262
+ this.destroy();
8263
+ throw error;
8264
+ }
8265
+ }
8266
+ get onData() {
8267
+ return this._onData;
8268
+ }
8269
+ set onData(value) {
8270
+ this._onData = value;
8271
+ }
8272
+ get onTerminalResize() {
8273
+ return this._onTerminalResize;
8274
+ }
8275
+ set onTerminalResize(value) {
8276
+ this._onTerminalResize = value;
8277
+ }
8278
+ get onScreenChange() {
8279
+ return this._onScreenChange;
8280
+ }
8281
+ set onScreenChange(value) {
8282
+ this._onScreenChange = value;
8283
+ }
8284
+ screen() {
8285
+ const cursor = this.handle ? this.lib.embeddedTerminalCursor(this.handle) : { x: 0, y: 0, visible: false, hasValue: false };
8286
+ const lines = this.frameBuffer ? new TextDecoder().decode(this.frameBuffer.getRealCharBytes(true)).split(`
8287
+ `).slice(0, this.height).map((line) => line.trimEnd()) : [];
8288
+ while (lines.at(-1) === "")
8289
+ lines.pop();
8290
+ return {
8291
+ text: lines.join(`
8292
+ `),
8293
+ lines,
8294
+ columns: this.width,
8295
+ rows: this.height,
8296
+ cursor: {
8297
+ x: cursor.x,
8298
+ y: cursor.y,
8299
+ visible: cursor.hasValue && cursor.visible
8300
+ }
8301
+ };
8302
+ }
8303
+ write(data) {
8304
+ if (!this.handle)
8305
+ return;
8306
+ this.lib.embeddedTerminalWrite(this.handle, data);
8307
+ try {
8308
+ this.flushResponses();
8309
+ } finally {
8310
+ this.requestRender();
8311
+ }
8312
+ }
8313
+ invalidate() {
8314
+ if (!this.handle)
8315
+ return;
8316
+ this.lib.embeddedTerminalInvalidate(this.handle);
8317
+ this.requestRender();
8318
+ }
8319
+ encodeKey(key) {
8320
+ if (!this.handle)
8321
+ return new Uint8Array;
8322
+ const text = textualKey(key);
8323
+ return this.lib.embeddedTerminalEncodeKey(this.handle, {
8324
+ action: key.eventType === "release" ? "release" : key.repeated ? "repeat" : "press",
8325
+ key: physicalKey(key),
8326
+ mods: modifiers(key),
8327
+ text,
8328
+ unshiftedCodepoint: key.baseCode ?? physicalUnshiftedCodepoint(key.code)
8329
+ });
8330
+ }
8331
+ encodePaste(bytes) {
8332
+ if (!this.handle)
8333
+ return new Uint8Array;
8334
+ return this.lib.embeddedTerminalEncodePaste(this.handle, bytes);
8335
+ }
8336
+ shouldStartSelection(x, y) {
8337
+ if (!this.selectable)
8338
+ return false;
8339
+ const localX = x - this.x;
8340
+ const localY = y - this.y;
8341
+ return localX >= 0 && localX < this.width && localY >= 0 && localY < this.height;
8342
+ }
8343
+ onSelectionChanged(selection) {
8344
+ if (!this.handle)
8345
+ return false;
8346
+ const local = convertGlobalToLocalSelection(selection, this.x, this.y);
8347
+ if (!local?.isActive) {
8348
+ if (!this.selection)
8349
+ return false;
8350
+ this.lib.embeddedTerminalClearSelection(this.handle);
8351
+ this.selection = false;
8352
+ this.requestRender();
8353
+ return false;
8354
+ }
8355
+ const point = (x, y) => {
8356
+ if (y < 0)
8357
+ return { x: 0, y: 0 };
8358
+ if (y >= this.height)
8359
+ return { x: this.width - 1, y: this.height - 1 };
8360
+ return { x: Math.max(0, Math.min(this.width - 1, x)), y };
8361
+ };
8362
+ this.lib.embeddedTerminalSetSelection(this.handle, point(local.anchorX, local.anchorY), point(local.focusX, local.focusY));
8363
+ this.selection = true;
8364
+ this.requestRender();
8365
+ return true;
8366
+ }
8367
+ hasSelection() {
8368
+ return this.selection;
8369
+ }
8370
+ getSelectedText() {
8371
+ if (!this.handle || !this.selection)
8372
+ return "";
8373
+ return new TextDecoder().decode(this.lib.embeddedTerminalGetSelectedText(this.handle));
8374
+ }
8375
+ focus() {
8376
+ if (this.focused)
8377
+ return;
8378
+ super.focus();
8379
+ if (!this.focused)
8380
+ return;
8381
+ this.keyreleaseHandler = (key) => this.handleKeyPress(key);
8382
+ this.ctx._internalKeyInput.onInternal("keyrelease", this.keyreleaseHandler);
8383
+ try {
8384
+ this.send(this.handle ? this.lib.embeddedTerminalEncodeFocus(this.handle, true) : new Uint8Array, "input");
8385
+ } catch (error) {
8386
+ this.removeKeyreleaseHandler();
8387
+ super.blur();
8388
+ throw error;
8389
+ }
8390
+ }
8391
+ blur() {
8392
+ if (!this.focused)
8393
+ return;
8394
+ try {
8395
+ this.send(this.handle ? this.lib.embeddedTerminalEncodeFocus(this.handle, false) : new Uint8Array, "input");
8396
+ } catch {} finally {
8397
+ this.removeKeyreleaseHandler();
8398
+ super.blur();
8399
+ this._ctx.setCursorPosition(0, 0, false);
8400
+ }
8401
+ }
8402
+ handleKeyPress(key) {
8403
+ const output = this.encodeKey(key);
8404
+ this.send(output, "input");
8405
+ return output.byteLength > 0;
8406
+ }
8407
+ handlePaste(event) {
8408
+ this.send(this.encodePaste(event.bytes), "input");
8409
+ }
8410
+ render(buffer, deltaTime) {
8411
+ const hasRenderHooks = Boolean(this.renderBefore || this.renderAfter);
8412
+ if (this.handle && (hasRenderHooks || this.hadRenderHooks))
8413
+ this.lib.embeddedTerminalInvalidate(this.handle);
8414
+ this.hadRenderHooks = hasRenderHooks;
8415
+ super.render(buffer, deltaTime);
8416
+ }
8417
+ onResize(width, height) {
8418
+ super.onResize(width, height);
8419
+ if (!this.handle || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
8420
+ return;
8421
+ const cols = Math.min(Math.floor(width), 65535);
8422
+ const rows = Math.min(Math.floor(height), 65535);
8423
+ this.lib.embeddedTerminalResize(this.handle, cols, rows);
8424
+ this.lib.embeddedTerminalInvalidate(this.handle);
8425
+ this.flushResponses();
8426
+ this._onTerminalResize?.(cols, rows);
8427
+ }
8428
+ renderSelf(buffer) {
8429
+ if (!this.handle || !this.frameBuffer || !this.visible || this.isDestroyed)
8430
+ return;
8431
+ this.lib.embeddedTerminalCompose(this.handle, buffer.ptr, 0, 0);
8432
+ this._onScreenChange?.();
8433
+ if (!this.focused)
8434
+ return;
8435
+ const cursor = this.lib.embeddedTerminalCursor(this.handle);
8436
+ const visible = cursor.visible && cursor.hasValue;
8437
+ const cursorX = cursor.wideTail && cursor.x > 0 ? cursor.x - 1 : cursor.x;
8438
+ this._ctx.setCursorPosition(this._screenX + cursorX + 1, this._screenY + cursor.y + 1, visible);
8439
+ if (!visible)
8440
+ return;
8441
+ this._ctx.setCursorStyle({
8442
+ style: cursor.style === "bar" ? "line" : cursor.style === "underline" ? "underline" : "block",
8443
+ blinking: cursor.blinking
8444
+ });
8445
+ if (cursor.color)
8446
+ this._ctx.setCursorColor(RGBA.fromInts(cursor.color.r, cursor.color.g, cursor.color.b, 255));
8447
+ }
8448
+ destroySelf() {
8449
+ if (this.handle) {
8450
+ this.lib.destroyEmbeddedTerminal(this.handle);
8451
+ this.handle = null;
8452
+ }
8453
+ this._ctx.setCursorPosition(0, 0, false);
8454
+ super.destroySelf();
8455
+ }
8456
+ onRemove() {
8457
+ if (this.focused)
8458
+ this.blur();
8459
+ }
8460
+ setupMouse(options) {
8461
+ const { onMouseDown, onMouseUp, onMouseMove, onMouseDrag, onMouseScroll } = options;
8462
+ this.onMouseDown = (event) => {
8463
+ this.forwardMouse(event, "press");
8464
+ onMouseDown?.call(this, event);
8465
+ };
8466
+ this.onMouseUp = (event) => {
8467
+ this.forwardMouse(event, "release");
8468
+ onMouseUp?.call(this, event);
8469
+ };
8470
+ this.onMouseMove = (event) => {
8471
+ this.forwardMouse(event, "motion");
8472
+ onMouseMove?.call(this, event);
8473
+ };
8474
+ this.onMouseDrag = (event) => {
8475
+ this.forwardMouse(event, "motion");
8476
+ onMouseDrag?.call(this, event);
8477
+ };
8478
+ this.onMouseScroll = (event) => {
8479
+ this.forwardMouse(event, "press");
8480
+ onMouseScroll?.call(this, event);
8481
+ };
8482
+ }
8483
+ forwardMouse(event, action) {
8484
+ if (!this.handle)
8485
+ return;
8486
+ if (event.type === "down" && event.button === 0)
8487
+ this.focus();
8488
+ const output = this.lib.embeddedTerminalEncodeMouse(this.handle, {
8489
+ action,
8490
+ button: event.type === "move" && !event.isDragging ? undefined : mouseButton(event),
8491
+ mods: modifiers(event.modifiers),
8492
+ x: event.x - this._screenX,
8493
+ y: event.y - this._screenY,
8494
+ anyButtonPressed: event.isDragging === true || event.type === "down"
8495
+ });
8496
+ if (event.type === "scroll" && output.byteLength === 0) {
8497
+ const direction = event.scroll?.direction;
8498
+ if (direction !== "up" && direction !== "down")
8499
+ return;
8500
+ this.lib.embeddedTerminalScroll(this.handle, direction === "up" ? -3 : 3);
8501
+ this.requestRender();
8502
+ event.preventDefault();
8503
+ event.stopPropagation();
8504
+ return;
8505
+ }
8506
+ if (output.byteLength === 0)
8507
+ return;
8508
+ event.preventDefault();
8509
+ event.stopPropagation();
8510
+ this.send(output, "input");
8511
+ }
8512
+ flushResponses() {
8513
+ if (!this.handle)
8514
+ return;
8515
+ this.send(this.lib.embeddedTerminalDrainResponses(this.handle), "response");
8516
+ }
8517
+ removeKeyreleaseHandler() {
8518
+ if (!this.keyreleaseHandler)
8519
+ return;
8520
+ this.ctx._internalKeyInput.offInternal("keyrelease", this.keyreleaseHandler);
8521
+ this.keyreleaseHandler = null;
8522
+ }
8523
+ send(data, source) {
8524
+ if (data.byteLength > 0)
8525
+ this._onData?.(data, source);
8526
+ }
8527
+ }
8528
+ function modifiers(input) {
8529
+ let value = 0;
8530
+ if (input.shift)
8531
+ value |= MOD_SHIFT;
8532
+ if (input.ctrl)
8533
+ value |= MOD_CTRL;
8534
+ if (input.alt || input.option)
8535
+ value |= MOD_ALT;
8536
+ if (input.meta || input.super)
8537
+ value |= MOD_SUPER;
8538
+ if (input.capsLock)
8539
+ value |= MOD_CAPS_LOCK;
8540
+ if (input.numLock)
8541
+ value |= MOD_NUM_LOCK;
8542
+ return value;
8543
+ }
8544
+ function physicalKey(key) {
8545
+ if (key.code)
8546
+ return key.code;
8547
+ return {
8548
+ backspace: "Backspace",
8549
+ enter: "Enter",
8550
+ return: "Enter",
8551
+ space: "Space",
8552
+ tab: "Tab",
8553
+ delete: "Delete",
8554
+ end: "End",
8555
+ home: "Home",
8556
+ insert: "Insert",
8557
+ pagedown: "PageDown",
8558
+ pageup: "PageUp",
8559
+ down: "ArrowDown",
8560
+ left: "ArrowLeft",
8561
+ right: "ArrowRight",
8562
+ up: "ArrowUp",
8563
+ escape: "Escape"
8564
+ }[key.name.toLowerCase()] ?? "";
8565
+ }
8566
+ function textualKey(key) {
8567
+ if (key.sequence.length > 0 && !/[\p{Cc}]/u.test(key.sequence))
8568
+ return key.sequence;
8569
+ if (key.name === "space")
8570
+ return " ";
8571
+ if (key.name.length === 0 || /[\p{Cc}]/u.test(key.name))
8572
+ return;
8573
+ if ([...key.name].length === 1 || /[^\x00-\x7f]/.test(key.name))
8574
+ return key.name;
8575
+ }
8576
+ function physicalUnshiftedCodepoint(code) {
8577
+ if (code?.startsWith("Key") && code.length === 4)
8578
+ return code.charCodeAt(3) + 32;
8579
+ if (code?.startsWith("Digit") && code.length === 6)
8580
+ return code.charCodeAt(5);
8581
+ return 0;
8582
+ }
8583
+ function mouseButton(event) {
8584
+ if (event.type === "scroll") {
8585
+ if (event.scroll?.direction === "up")
8586
+ return "four";
8587
+ if (event.scroll?.direction === "down")
8588
+ return "five";
8589
+ if (event.scroll?.direction === "left")
8590
+ return "six";
8591
+ if (event.scroll?.direction === "right")
8592
+ return "seven";
8593
+ return;
8594
+ }
8595
+ return { 0: "left", 1: "middle", 2: "right", 4: "four", 5: "five" }[event.button];
8596
+ }
8225
8597
  // src/renderables/Textarea.ts
8226
8598
  var defaultTextareaKeyBindings = [
8227
8599
  { name: "left", action: "move-left" },
@@ -14830,6 +15202,30 @@ class TabSelectRenderable extends Renderable {
14830
15202
  }
14831
15203
  }
14832
15204
  // src/renderables/TimeToFirstDraw.ts
15205
+ var graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
15206
+ function measureCellWidth(buffer, text) {
15207
+ const encoded = buffer.encodeUnicode(text);
15208
+ if (!encoded)
15209
+ return 0;
15210
+ try {
15211
+ return encoded.data.reduce((width, glyph) => width + glyph.width, 0);
15212
+ } finally {
15213
+ buffer.freeUnicode(encoded);
15214
+ }
15215
+ }
15216
+ function truncateToCellWidth(buffer, text, maxWidth) {
15217
+ let visibleText = "";
15218
+ let visibleWidth = 0;
15219
+ for (const { segment } of graphemeSegmenter.segment(text)) {
15220
+ const segmentWidth = measureCellWidth(buffer, segment);
15221
+ if (visibleWidth + segmentWidth > maxWidth)
15222
+ break;
15223
+ visibleText += segment;
15224
+ visibleWidth += segmentWidth;
15225
+ }
15226
+ return { text: visibleText, width: visibleWidth };
15227
+ }
15228
+
14833
15229
  class TimeToFirstDrawRenderable extends Renderable {
14834
15230
  _runtimeMs = null;
14835
15231
  textColor;
@@ -14882,9 +15278,9 @@ class TimeToFirstDrawRenderable extends Renderable {
14882
15278
  }
14883
15279
  const content = `${this.label}: ${this._runtimeMs.toFixed(this.precision)}ms`;
14884
15280
  const maxWidth = Math.max(this.width, 1);
14885
- const visibleContent = content.length > maxWidth ? content.slice(0, maxWidth) : content;
14886
- const centeredX = this.x + Math.max(0, Math.floor((maxWidth - visibleContent.length) / 2));
14887
- buffer.drawText(visibleContent, centeredX, this.y, this.textColor);
15281
+ const visibleContent = truncateToCellWidth(buffer, content, maxWidth);
15282
+ const centeredX = this.x + Math.max(0, Math.floor((maxWidth - visibleContent.width) / 2));
15283
+ buffer.drawText(visibleContent.text, centeredX, this.y, this.textColor);
14888
15284
  }
14889
15285
  normalizePrecision(value) {
14890
15286
  if (!Number.isFinite(value)) {
@@ -15128,6 +15524,7 @@ export {
15128
15524
  FrameBuffer,
15129
15525
  FlamesEffect,
15130
15526
  ExtmarksController,
15527
+ EmbeddedTerminalRenderable,
15131
15528
  EditorView,
15132
15529
  EditBufferRenderableEvents,
15133
15530
  EditBufferRenderable,
@@ -15172,5 +15569,5 @@ export {
15172
15569
  ACHROMATOPSIA_MATRIX
15173
15570
  };
15174
15571
 
15175
- //# debugId=461568FAFCC40A8864756E2164756E21
15572
+ //# debugId=ED15438BE5BDAF1564756E2164756E21
15176
15573
  //# sourceMappingURL=index.node.js.map