@xterm/xterm 5.4.0-beta.1 → 5.4.0-beta.11

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xterm/xterm",
3
3
  "description": "Full xterm terminal, in your browser",
4
- "version": "5.4.0-beta.1",
4
+ "version": "5.4.0-beta.11",
5
5
  "main": "lib/xterm.js",
6
6
  "style": "css/xterm.css",
7
7
  "types": "typings/xterm.d.ts",
@@ -575,7 +575,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
575
575
  }
576
576
 
577
577
  private _createRenderer(): IRenderer {
578
- return this._instantiationService.createInstance(DomRenderer, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier2);
578
+ return this._instantiationService.createInstance(DomRenderer, this, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier2);
579
579
  }
580
580
 
581
581
  /**
@@ -7,9 +7,10 @@ import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererR
7
7
  import { WidthCache } from 'browser/renderer/dom/WidthCache';
8
8
  import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
9
9
  import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
10
- import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
10
+ import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
11
+ import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types';
11
12
  import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
12
- import { ILinkifier2, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types';
13
+ import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types';
13
14
  import { color } from 'common/Color';
14
15
  import { EventEmitter } from 'common/EventEmitter';
15
16
  import { Disposable, toDisposable } from 'common/Lifecycle';
@@ -25,7 +26,6 @@ const SELECTION_CLASS = 'xterm-selection';
25
26
 
26
27
  let nextTerminalId = 1;
27
28
 
28
-
29
29
  /**
30
30
  * A fallback renderer for when canvas is slow. This is not meant to be
31
31
  * particularly fast or feature complete, more just stable and usable for when
@@ -41,12 +41,14 @@ export class DomRenderer extends Disposable implements IRenderer {
41
41
  private _rowElements: HTMLElement[] = [];
42
42
  private _selectionContainer: HTMLElement;
43
43
  private _widthCache: WidthCache;
44
+ private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
44
45
 
45
46
  public dimensions: IRenderDimensions;
46
47
 
47
48
  public readonly onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>()).event;
48
49
 
49
50
  constructor(
51
+ private readonly _terminal: ITerminal,
50
52
  private readonly _document: Document,
51
53
  private readonly _element: HTMLElement,
52
54
  private readonly _screenElement: HTMLElement,
@@ -291,6 +293,7 @@ export class DomRenderer extends Disposable implements IRenderer {
291
293
  public handleResize(cols: number, rows: number): void {
292
294
  this._refreshRowElements(cols, rows);
293
295
  this._updateDimensions();
296
+ this.handleSelectionChanged(this._selectionRenderModel.selectionStart, this._selectionRenderModel.selectionEnd, this._selectionRenderModel.columnSelectMode);
294
297
  }
295
298
 
296
299
  public handleCharSizeChanged(): void {
@@ -320,11 +323,13 @@ export class DomRenderer extends Disposable implements IRenderer {
320
323
  return;
321
324
  }
322
325
 
326
+ this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode);
327
+
323
328
  // Translate from buffer position to viewport position
324
- const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
325
- const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
326
- const viewportCappedStartRow = Math.max(viewportStartRow, 0);
327
- const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
329
+ const viewportStartRow = this._selectionRenderModel.viewportStartRow;
330
+ const viewportEndRow = this._selectionRenderModel.viewportEndRow;
331
+ const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
332
+ const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;
328
333
 
329
334
  // No need to draw the selection
330
335
  if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
@@ -365,10 +370,16 @@ export class DomRenderer extends Disposable implements IRenderer {
365
370
  */
366
371
  private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
367
372
  const element = this._document.createElement('div');
373
+ const left = colStart * this.dimensions.css.cell.width;
374
+ let width = this.dimensions.css.cell.width * (colEnd - colStart);
375
+ if (left + width > this.dimensions.css.canvas.width) {
376
+ width = this.dimensions.css.canvas.width - left;
377
+ }
378
+
368
379
  element.style.height = `${rowCount * this.dimensions.css.cell.height}px`;
369
380
  element.style.top = `${row * this.dimensions.css.cell.height}px`;
370
- element.style.left = `${colStart * this.dimensions.css.cell.width}px`;
371
- element.style.width = `${this.dimensions.css.cell.width * (colEnd - colStart)}px`;
381
+ element.style.left = `${left}px`;
382
+ element.style.width = `${width}px`;
372
383
  return element;
373
384
  }
374
385
 
@@ -1,8 +1,8 @@
1
1
  import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
2
2
  import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
3
3
  import { ReadonlyColorSet } from 'browser/Types';
4
- import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants';
5
- import { IDecorationService } from 'common/services/Services';
4
+ import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
5
+ import { IDecorationService, IOptionsService } from 'common/services/Services';
6
6
  import { ICellData } from 'common/Types';
7
7
  import { Terminal } from '@xterm/xterm';
8
8
 
@@ -13,6 +13,7 @@ let $hasFg = false;
13
13
  let $hasBg = false;
14
14
  let $isSelected = false;
15
15
  let $colors: ReadonlyColorSet | undefined;
16
+ let $variantOffset = 0;
16
17
 
17
18
  export class CellColorResolver {
18
19
  /**
@@ -27,6 +28,7 @@ export class CellColorResolver {
27
28
 
28
29
  constructor(
29
30
  private readonly _terminal: Terminal,
31
+ private readonly _optionService: IOptionsService,
30
32
  private readonly _selectionRenderModel: ISelectionRenderModel,
31
33
  private readonly _decorationService: IDecorationService,
32
34
  private readonly _coreBrowserService: ICoreBrowserService,
@@ -38,7 +40,7 @@ export class CellColorResolver {
38
40
  * Resolves colors for the cell, putting the result into the shared {@link result}. This resolves
39
41
  * overrides, inverse and selection for the cell which can then be used to feed into the renderer.
40
42
  */
41
- public resolve(cell: ICellData, x: number, y: number): void {
43
+ public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number): void {
42
44
  this.result.bg = cell.bg;
43
45
  this.result.fg = cell.fg;
44
46
  this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0;
@@ -52,6 +54,13 @@ export class CellColorResolver {
52
54
  $hasFg = false;
53
55
  $isSelected = false;
54
56
  $colors = this._themeService.colors;
57
+ $variantOffset = 0;
58
+
59
+ const code = cell.getCode();
60
+ if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.DOTTED) {
61
+ const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15));
62
+ $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2);
63
+ }
55
64
 
56
65
  // Apply decorations on the bottom layer
57
66
  this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => {
@@ -133,5 +142,9 @@ export class CellColorResolver {
133
142
  // Use the override if it exists
134
143
  this.result.bg = $hasBg ? $bg : this.result.bg;
135
144
  this.result.fg = $hasFg ? $fg : this.result.fg;
145
+
146
+ // Reset overrides variantOffset
147
+ this.result.ext &= ~ExtFlags.VARIANT_OFFSET;
148
+ this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET;
136
149
  }
137
150
  }
@@ -56,3 +56,7 @@ function createDimension(): IDimensions {
56
56
  height: 0
57
57
  };
58
58
  }
59
+
60
+ export function computeNextVariantOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number {
61
+ return (cellWidth - (Math.round(lineWidth) * 2 - currentOffset)) % (Math.round(lineWidth) * 2);
62
+ }
@@ -3,6 +3,7 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
+ import { ITerminal } from 'browser/Types';
6
7
  import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
7
8
  import { Terminal } from '@xterm/xterm';
8
9
 
@@ -35,7 +36,7 @@ class SelectionRenderModel implements ISelectionRenderModel {
35
36
  this.selectionEnd = undefined;
36
37
  }
37
38
 
38
- public update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
39
+ public update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
39
40
  this.selectionStart = start;
40
41
  this.selectionEnd = end;
41
42
  // Selection does not exist
@@ -45,8 +46,9 @@ class SelectionRenderModel implements ISelectionRenderModel {
45
46
  }
46
47
 
47
48
  // Translate from buffer position to viewport position
48
- const viewportStartRow = start[1] - terminal.buffer.active.viewportY;
49
- const viewportEndRow = end[1] - terminal.buffer.active.viewportY;
49
+ const viewportY = terminal.buffers.active.ydisp;
50
+ const viewportStartRow = start[1] - viewportY;
51
+ const viewportEndRow = end[1] - viewportY;
50
52
  const viewportCappedStartRow = Math.max(viewportStartRow, 0);
51
53
  const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);
52
54
 
@@ -6,7 +6,7 @@
6
6
  import { IColorContrastCache } from 'browser/Types';
7
7
  import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants';
8
8
  import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
9
- import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
9
+ import { computeNextVariantOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
10
10
  import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types';
11
11
  import { NULL_COLOR, color, rgba } from 'common/Color';
12
12
  import { EventEmitter } from 'common/EventEmitter';
@@ -545,6 +545,7 @@ export class TextureAtlas implements ITextureAtlas {
545
545
  const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
546
546
  const yMid = yTop + lineWidth;
547
547
  const yBot = yTop + lineWidth * 2;
548
+ let nextOffset = this._workAttributeData.getUnderlineVariantOffset();
548
549
 
549
550
  for (let i = 0; i < chWidth; i++) {
550
551
  this._tmpCtx.save();
@@ -594,9 +595,22 @@ export class TextureAtlas implements ITextureAtlas {
594
595
  );
595
596
  break;
596
597
  case UnderlineStyle.DOTTED:
597
- this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
598
- this._tmpCtx.moveTo(xChLeft, yTop);
599
- this._tmpCtx.lineTo(xChRight, yTop);
598
+ const offsetWidth = nextOffset === 0 ? 0 :
599
+ (nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset);
600
+ // a line and a gap.
601
+ const isLineStart = nextOffset >= lineWidth ? false : true;
602
+ if (isLineStart === false || offsetWidth === 0) {
603
+ this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
604
+ this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop);
605
+ this._tmpCtx.lineTo(xChRight, yTop);
606
+ } else {
607
+ this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
608
+ this._tmpCtx.moveTo(xChLeft, yTop);
609
+ this._tmpCtx.lineTo(xChLeft + offsetWidth, yTop);
610
+ this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTop);
611
+ this._tmpCtx.lineTo(xChRight, yTop);
612
+ }
613
+ nextOffset = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset);
600
614
  break;
601
615
  case UnderlineStyle.DASHED:
602
616
  this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]);
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { FontWeight, Terminal } from '@xterm/xterm';
7
- import { IColorSet } from 'browser/Types';
7
+ import { IColorSet, ITerminal } from 'browser/Types';
8
8
  import { IDisposable } from 'common/Types';
9
9
  import { IEvent } from 'common/EventEmitter';
10
10
 
@@ -168,6 +168,6 @@ export interface ISelectionRenderModel {
168
168
  readonly selectionStart: [number, number] | undefined;
169
169
  readonly selectionEnd: [number, number] | undefined;
170
170
  clear(): void;
171
- update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void;
171
+ update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void;
172
172
  isCellSelected(terminal: Terminal, x: number, y: number): boolean;
173
173
  }
@@ -519,7 +519,7 @@ export class InputHandler extends Disposable implements IInputHandler {
519
519
 
520
520
  // handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char
521
521
  if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) {
522
- bufferRow.setCellFromCodePoint(this._activeBuffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
522
+ bufferRow.setCellFromCodepoint(this._activeBuffer.x - 1, 0, 1, curAttr);
523
523
  }
524
524
 
525
525
  let precedingJoinState = this._parser.precedingJoinState;
@@ -581,7 +581,7 @@ export class InputHandler extends Disposable implements IInputHandler {
581
581
  }
582
582
  // clear left over cells to the right
583
583
  while (oldCol < cols) {
584
- oldRow.setCellFromCodePoint(oldCol++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
584
+ oldRow.setCellFromCodepoint(oldCol++, 0, 1, curAttr);
585
585
  }
586
586
  } else {
587
587
  this._activeBuffer.x = cols - 1;
@@ -605,7 +605,7 @@ export class InputHandler extends Disposable implements IInputHandler {
605
605
  bufferRow.addCodepointToCell(this._activeBuffer.x - offset,
606
606
  code, chWidth);
607
607
  for (let delta = chWidth - oldWidth; --delta >= 0; ) {
608
- bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
608
+ bufferRow.setCellFromCodepoint(this._activeBuffer.x++, 0, 0, curAttr);
609
609
  }
610
610
  continue;
611
611
  }
@@ -613,17 +613,17 @@ export class InputHandler extends Disposable implements IInputHandler {
613
613
  // insert mode: move characters to right
614
614
  if (insertMode) {
615
615
  // right shift cells according to the width
616
- bufferRow.insertCells(this._activeBuffer.x, chWidth - oldWidth, this._activeBuffer.getNullCell(curAttr), curAttr);
616
+ bufferRow.insertCells(this._activeBuffer.x, chWidth - oldWidth, this._activeBuffer.getNullCell(curAttr));
617
617
  // test last cell - since the last cell has only room for
618
618
  // a halfwidth char any fullwidth shifted there is lost
619
619
  // and will be set to empty cell
620
620
  if (bufferRow.getWidth(cols - 1) === 2) {
621
- bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg, curAttr.extended);
621
+ bufferRow.setCellFromCodepoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr);
622
622
  }
623
623
  }
624
624
 
625
625
  // write current char to buffer and advance cursor
626
- bufferRow.setCellFromCodePoint(this._activeBuffer.x++, code, chWidth, curAttr.fg, curAttr.bg, curAttr.extended);
626
+ bufferRow.setCellFromCodepoint(this._activeBuffer.x++, code, chWidth, curAttr);
627
627
 
628
628
  // fullwidth char - also set next cell to placeholder stub and advance cursor
629
629
  // for graphemes bigger than fullwidth we can simply loop to zero
@@ -631,7 +631,7 @@ export class InputHandler extends Disposable implements IInputHandler {
631
631
  if (chWidth > 0) {
632
632
  while (--chWidth) {
633
633
  // other than a regular empty cell a cell following a wide char has no width
634
- bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
634
+ bufferRow.setCellFromCodepoint(this._activeBuffer.x++, 0, 0, curAttr);
635
635
  }
636
636
  }
637
637
  }
@@ -640,7 +640,7 @@ export class InputHandler extends Disposable implements IInputHandler {
640
640
 
641
641
  // handle wide chars: reset cell to the right if it is second cell of a wide char
642
642
  if (this._activeBuffer.x < cols && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x) === 0 && !bufferRow.hasContent(this._activeBuffer.x)) {
643
- bufferRow.setCellFromCodePoint(this._activeBuffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
643
+ bufferRow.setCellFromCodepoint(this._activeBuffer.x, 0, 1, curAttr);
644
644
  }
645
645
 
646
646
  this._dirtyRowTracker.markDirty(this._activeBuffer.y);
@@ -1145,7 +1145,6 @@ export class InputHandler extends Disposable implements IInputHandler {
1145
1145
  start,
1146
1146
  end,
1147
1147
  this._activeBuffer.getNullCell(this._eraseAttrData()),
1148
- this._eraseAttrData(),
1149
1148
  respectProtect
1150
1149
  );
1151
1150
  if (clearWrap) {
@@ -1366,8 +1365,7 @@ export class InputHandler extends Disposable implements IInputHandler {
1366
1365
  line.insertCells(
1367
1366
  this._activeBuffer.x,
1368
1367
  params.params[0] || 1,
1369
- this._activeBuffer.getNullCell(this._eraseAttrData()),
1370
- this._eraseAttrData()
1368
+ this._activeBuffer.getNullCell(this._eraseAttrData())
1371
1369
  );
1372
1370
  this._dirtyRowTracker.markDirty(this._activeBuffer.y);
1373
1371
  }
@@ -1393,8 +1391,7 @@ export class InputHandler extends Disposable implements IInputHandler {
1393
1391
  line.deleteCells(
1394
1392
  this._activeBuffer.x,
1395
1393
  params.params[0] || 1,
1396
- this._activeBuffer.getNullCell(this._eraseAttrData()),
1397
- this._eraseAttrData()
1394
+ this._activeBuffer.getNullCell(this._eraseAttrData())
1398
1395
  );
1399
1396
  this._dirtyRowTracker.markDirty(this._activeBuffer.y);
1400
1397
  }
@@ -1461,7 +1458,7 @@ export class InputHandler extends Disposable implements IInputHandler {
1461
1458
  const param = params.params[0] || 1;
1462
1459
  for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
1463
1460
  const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
1464
- line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
1461
+ line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
1465
1462
  line.isWrapped = false;
1466
1463
  }
1467
1464
  this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
@@ -1494,7 +1491,7 @@ export class InputHandler extends Disposable implements IInputHandler {
1494
1491
  const param = params.params[0] || 1;
1495
1492
  for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
1496
1493
  const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
1497
- line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
1494
+ line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
1498
1495
  line.isWrapped = false;
1499
1496
  }
1500
1497
  this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
@@ -1517,7 +1514,7 @@ export class InputHandler extends Disposable implements IInputHandler {
1517
1514
  const param = params.params[0] || 1;
1518
1515
  for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
1519
1516
  const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
1520
- line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
1517
+ line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
1521
1518
  line.isWrapped = false;
1522
1519
  }
1523
1520
  this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
@@ -1540,7 +1537,7 @@ export class InputHandler extends Disposable implements IInputHandler {
1540
1537
  const param = params.params[0] || 1;
1541
1538
  for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
1542
1539
  const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
1543
- line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
1540
+ line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
1544
1541
  line.isWrapped = false;
1545
1542
  }
1546
1543
  this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
@@ -1562,8 +1559,7 @@ export class InputHandler extends Disposable implements IInputHandler {
1562
1559
  line.replaceCells(
1563
1560
  this._activeBuffer.x,
1564
1561
  this._activeBuffer.x + (params.params[0] || 1),
1565
- this._activeBuffer.getNullCell(this._eraseAttrData()),
1566
- this._eraseAttrData()
1562
+ this._activeBuffer.getNullCell(this._eraseAttrData())
1567
1563
  );
1568
1564
  this._dirtyRowTracker.markDirty(this._activeBuffer.y);
1569
1565
  }
@@ -119,6 +119,7 @@ export interface IExtendedAttrs {
119
119
  ext: number;
120
120
  underlineStyle: UnderlineStyle;
121
121
  underlineColor: number;
122
+ underlineVariantOffset: number;
122
123
  urlId: number;
123
124
  clone(): IExtendedAttrs;
124
125
  isEmpty(): boolean;
@@ -209,6 +210,7 @@ export interface IAttributeData {
209
210
  isUnderlineColorPalette(): boolean;
210
211
  isUnderlineColorDefault(): boolean;
211
212
  getUnderlineStyle(): number;
213
+ getUnderlineVariantOffset(): number;
212
214
  }
213
215
 
214
216
  /** Cell data */
@@ -233,11 +235,11 @@ export interface IBufferLine {
233
235
  set(index: number, value: CharData): void;
234
236
  loadCell(index: number, cell: ICellData): ICellData;
235
237
  setCell(index: number, cell: ICellData): void;
236
- setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void;
238
+ setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void;
237
239
  addCodepointToCell(index: number, codePoint: number, width: number): void;
238
- insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void;
239
- deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void;
240
- replaceCells(start: number, end: number, fill: ICellData, eraseAttr?: IAttributeData, respectProtect?: boolean): void;
240
+ insertCells(pos: number, n: number, ch: ICellData): void;
241
+ deleteCells(pos: number, n: number, fill: ICellData): void;
242
+ replaceCells(start: number, end: number, fill: ICellData, respectProtect?: boolean): void;
241
243
  resize(cols: number, fill: ICellData): boolean;
242
244
  cleanupMemory(): number;
243
245
  fill(fillCellData: ICellData, respectProtect?: boolean): void;
@@ -126,6 +126,9 @@ export class AttributeData implements IAttributeData {
126
126
  ? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE)
127
127
  : UnderlineStyle.NONE;
128
128
  }
129
+ public getUnderlineVariantOffset(): number {
130
+ return this.extended.underlineVariantOffset;
131
+ }
129
132
  }
130
133
 
131
134
 
@@ -174,6 +177,18 @@ export class ExtendedAttrs implements IExtendedAttrs {
174
177
  this._urlId = value;
175
178
  }
176
179
 
180
+ public get underlineVariantOffset(): number {
181
+ const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29;
182
+ if (val < 0) {
183
+ return val ^ 0xFFFFFFF8;
184
+ }
185
+ return val;
186
+ }
187
+ public set underlineVariantOffset(value: number) {
188
+ this._ext &= ~ExtFlags.VARIANT_OFFSET;
189
+ this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET;
190
+ }
191
+
177
192
  constructor(
178
193
  ext: number = 0,
179
194
  urlId: number = 0
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { CharData, IAttributeData, IBufferLine, ICellData, IExtendedAttrs } from 'common/Types';
7
- import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
7
+ import { AttributeData } from 'common/buffer/AttributeData';
8
8
  import { CellData } from 'common/buffer/CellData';
9
9
  import { Attributes, BgFlags, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, Content, NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
10
10
  import { stringFromCodePoint } from 'common/input/TextDecoder';
@@ -212,13 +212,13 @@ export class BufferLine implements IBufferLine {
212
212
  * Since the input handler see the incoming chars as UTF32 codepoints,
213
213
  * it gets an optimized access method.
214
214
  */
215
- public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void {
216
- if (bg & BgFlags.HAS_EXTENDED) {
217
- this._extendedAttrs[index] = eAttrs;
215
+ public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {
216
+ if (attrs.bg & BgFlags.HAS_EXTENDED) {
217
+ this._extendedAttrs[index] = attrs.extended;
218
218
  }
219
219
  this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
220
- this._data[index * CELL_SIZE + Cell.FG] = fg;
221
- this._data[index * CELL_SIZE + Cell.BG] = bg;
220
+ this._data[index * CELL_SIZE + Cell.FG] = attrs.fg;
221
+ this._data[index * CELL_SIZE + Cell.BG] = attrs.bg;
222
222
  }
223
223
 
224
224
  /**
@@ -253,12 +253,12 @@ export class BufferLine implements IBufferLine {
253
253
  this._data[index * CELL_SIZE + Cell.CONTENT] = content;
254
254
  }
255
255
 
256
- public insertCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
256
+ public insertCells(pos: number, n: number, fillCellData: ICellData): void {
257
257
  pos %= this.length;
258
258
 
259
259
  // handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
260
260
  if (pos && this.getWidth(pos - 1) === 2) {
261
- this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
261
+ this.setCellFromCodepoint(pos - 1, 0, 1, fillCellData);
262
262
  }
263
263
 
264
264
  if (n < this.length - pos) {
@@ -277,11 +277,11 @@ export class BufferLine implements IBufferLine {
277
277
 
278
278
  // handle fullwidth at line end: reset last cell if it is first cell of a wide char
279
279
  if (this.getWidth(this.length - 1) === 2) {
280
- this.setCellFromCodePoint(this.length - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
280
+ this.setCellFromCodepoint(this.length - 1, 0, 1, fillCellData);
281
281
  }
282
282
  }
283
283
 
284
- public deleteCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
284
+ public deleteCells(pos: number, n: number, fillCellData: ICellData): void {
285
285
  pos %= this.length;
286
286
  if (n < this.length - pos) {
287
287
  const cell = new CellData();
@@ -301,21 +301,21 @@ export class BufferLine implements IBufferLine {
301
301
  // - reset pos-1 if wide char
302
302
  // - reset pos if width==0 (previous second cell of a wide char)
303
303
  if (pos && this.getWidth(pos - 1) === 2) {
304
- this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
304
+ this.setCellFromCodepoint(pos - 1, 0, 1, fillCellData);
305
305
  }
306
306
  if (this.getWidth(pos) === 0 && !this.hasContent(pos)) {
307
- this.setCellFromCodePoint(pos, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
307
+ this.setCellFromCodepoint(pos, 0, 1, fillCellData);
308
308
  }
309
309
  }
310
310
 
311
- public replaceCells(start: number, end: number, fillCellData: ICellData, eraseAttr?: IAttributeData, respectProtect: boolean = false): void {
311
+ public replaceCells(start: number, end: number, fillCellData: ICellData, respectProtect: boolean = false): void {
312
312
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
313
313
  if (respectProtect) {
314
314
  if (start && this.getWidth(start - 1) === 2 && !this.isProtected(start - 1)) {
315
- this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
315
+ this.setCellFromCodepoint(start - 1, 0, 1, fillCellData);
316
316
  }
317
317
  if (end < this.length && this.getWidth(end - 1) === 2 && !this.isProtected(end)) {
318
- this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
318
+ this.setCellFromCodepoint(end, 0, 1, fillCellData);
319
319
  }
320
320
  while (start < end && start < this.length) {
321
321
  if (!this.isProtected(start)) {
@@ -328,11 +328,11 @@ export class BufferLine implements IBufferLine {
328
328
 
329
329
  // handle fullwidth at start: reset cell one to the left if start is second cell of a wide char
330
330
  if (start && this.getWidth(start - 1) === 2) {
331
- this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
331
+ this.setCellFromCodepoint(start - 1, 0, 1, fillCellData);
332
332
  }
333
333
  // handle fullwidth at last cell + 1: reset to empty cell if it is second part of a wide char
334
334
  if (end < this.length && this.getWidth(end - 1) === 2) {
335
- this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
335
+ this.setCellFromCodepoint(end, 0, 1, fillCellData);
336
336
  }
337
337
 
338
338
  while (start < end && start < this.length) {
@@ -134,9 +134,17 @@ export const enum BgFlags {
134
134
 
135
135
  export const enum ExtFlags {
136
136
  /**
137
- * bit 27..32 (upper 3 unused)
137
+ * bit 27..29
138
138
  */
139
- UNDERLINE_STYLE = 0x1C000000
139
+ UNDERLINE_STYLE = 0x1C000000,
140
+
141
+ /**
142
+ * bit 30..32
143
+ *
144
+ * An optional variant for the glyph, this can be used for example to offset underlines by a
145
+ * number of pixels to create a perfect pattern.
146
+ */
147
+ VARIANT_OFFSET = 0xE0000000
140
148
  }
141
149
 
142
150
  export const enum UnderlineStyle {
@@ -83,11 +83,10 @@ export function evaluateKeyboardEvent(
83
83
  break;
84
84
  case 8:
85
85
  // backspace
86
+ result.key = ev.ctrlKey ? '\b' : C0.DEL; // ^H or ^?
86
87
  if (ev.altKey) {
87
- result.key = C0.ESC + C0.DEL; // \e ^?
88
- break;
88
+ result.key = C0.ESC + result.key;
89
89
  }
90
- result.key = C0.DEL; // ^?
91
90
  break;
92
91
  case 9:
93
92
  // tab