diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index d5b8d9481c..acff5f1698 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -99,6 +99,9 @@ export enum WindowsOptionsReportType { GET_CELL_SIZE_PIXELS = 1 } +// FIXME: add to terminal settings somewhere +let is_protected = false; + // create a warning log if an async handler takes longer than the limit (in ms) const SLOW_ASYNC_LIMIT = 5000; @@ -335,9 +338,9 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.registerCsiHandler({ final: 'H' }, params => this.cursorPosition(params)); this._parser.registerCsiHandler({ final: 'I' }, params => this.cursorForwardTab(params)); this._parser.registerCsiHandler({ final: 'J' }, params => this.eraseInDisplay(params)); - this._parser.registerCsiHandler({ prefix: '?', final: 'J' }, params => this.eraseInDisplay(params)); + this._parser.registerCsiHandler({ prefix: '?', final: 'J' }, params => this.eraseInDisplayProtected(params)); this._parser.registerCsiHandler({ final: 'K' }, params => this.eraseInLine(params)); - this._parser.registerCsiHandler({ prefix: '?', final: 'K' }, params => this.eraseInLine(params)); + this._parser.registerCsiHandler({ prefix: '?', final: 'K' }, params => this.eraseInLineProtected(params)); this._parser.registerCsiHandler({ final: 'L' }, params => this.insertLines(params)); this._parser.registerCsiHandler({ final: 'M' }, params => this.deleteLines(params)); this._parser.registerCsiHandler({ final: 'P' }, params => this.deleteChars(params)); @@ -370,6 +373,21 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.registerCsiHandler({ intermediates: '\'', final: '}' }, params => this.insertColumns(params)); this._parser.registerCsiHandler({ intermediates: '\'', final: '~' }, params => this.deleteColumns(params)); + // DECSCA quick hack + this._parser.registerCsiHandler({ intermediates: '"', final: 'q' }, params => { + switch (params.params[0]) { + case 1: + is_protected = true; + break; + case 0: + case 2: + is_protected = false; + break; + } + return true; + }); + + /** * execute handler */ @@ -612,9 +630,11 @@ export class InputHandler extends Disposable implements IInputHandler { this._dirtyRowService.markDirty(this._activeBuffer.y); + const _is_protected = is_protected ? 1 : 0; // FIXME: pull from terminal settings + // handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) { - bufferRow.setCellFromCodePoint(this._activeBuffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended); + bufferRow.setCellFromCodePoint(this._activeBuffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended, _is_protected); } for (let pos = start; pos < end; ++pos) { @@ -663,7 +683,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (wraparoundMode) { // clear left over cells to the right while (this._activeBuffer.x < cols) { - bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended); + bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended, _is_protected); } this._activeBuffer.x = 0; this._activeBuffer.y++; @@ -703,7 +723,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // write current char to buffer and advance cursor - bufferRow.setCellFromCodePoint(this._activeBuffer.x++, code, chWidth, curAttr.fg, curAttr.bg, curAttr.extended); + bufferRow.setCellFromCodePoint(this._activeBuffer.x++, code, chWidth, curAttr.fg, curAttr.bg, curAttr.extended, _is_protected); // fullwidth char - also set next cell to placeholder stub and advance cursor // for graphemes bigger than fullwidth we can simply loop to zero @@ -711,7 +731,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (chWidth > 0) { while (--chWidth) { // other than a regular empty cell a cell following a wide char has no width - bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended); + bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended, _is_protected); } } } @@ -1223,6 +1243,21 @@ export class InputHandler extends Disposable implements IInputHandler { } } + private _eraseInBufferLineProtected(y: number, start: number, end: number, clearWrap: boolean = false): void { + // TODO: also apply fullwidth edge cases from Bufferline.replaceCells + const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; + const fillCellData = this._activeBuffer.getNullCell(this._eraseAttrData()); + while (start < end && start < line.length) { + if (!line.getProtected(start)) { + line.setCell(start, fillCellData); + } + start++; + } + if (clearWrap) { + line.isWrapped = false; + } + } + /** * Helper method to reset cells in a terminal row. * The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false. @@ -1234,6 +1269,17 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase + y); line.isWrapped = false; } + private _resetBufferLineProtected(y: number): void { + const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; + const fillCellData = this._activeBuffer.getNullCell(this._eraseAttrData()); + for (let i = 0; i < line.length; ++i) { + if (!line.getProtected(i)) { + line.setCell(i, fillCellData); + } + } + this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase + y); + line.isWrapped = false; + } /** * CSI Ps J Erase in Display (ED). @@ -1308,6 +1354,55 @@ export class InputHandler extends Disposable implements IInputHandler { } return true; } + public eraseInDisplayProtected(params: IParams): boolean { + this._restrictCursor(this._bufferService.cols); + let j; + switch (params.params[0]) { + case 0: + j = this._activeBuffer.y; + this._dirtyRowService.markDirty(j); + this._eraseInBufferLineProtected(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0); + for (; j < this._bufferService.rows; j++) { + this._resetBufferLineProtected(j); + } + this._dirtyRowService.markDirty(j); + break; + case 1: + j = this._activeBuffer.y; + this._dirtyRowService.markDirty(j); + // Deleted front part of line and everything before. This line will no longer be wrapped. + this._eraseInBufferLineProtected(j, 0, this._activeBuffer.x + 1, true); + if (this._activeBuffer.x + 1 >= this._bufferService.cols) { + // Deleted entire previous line. This next line can no longer be wrapped. + this._activeBuffer.lines.get(j + 1)!.isWrapped = false; + } + while (j--) { + this._resetBufferLineProtected(j); + } + this._dirtyRowService.markDirty(0); + break; + case 2: + j = this._bufferService.rows; + this._dirtyRowService.markDirty(j - 1); + while (j--) { + this._resetBufferLineProtected(j); + } + this._dirtyRowService.markDirty(0); + break; + case 3: + // Clear scrollback (everything not in viewport) + const scrollBackSize = this._activeBuffer.lines.length - this._bufferService.rows; + if (scrollBackSize > 0) { + this._activeBuffer.lines.trimStart(scrollBackSize); + this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0); + this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0); + // Force a scroll event to refresh viewport + this._onScroll.fire(0); + } + break; + } + return true; + } /** * CSI Ps K Erase in Line (EL). @@ -1347,6 +1442,22 @@ export class InputHandler extends Disposable implements IInputHandler { this._dirtyRowService.markDirty(this._activeBuffer.y); return true; } + public eraseInLineProtected(params: IParams): boolean { + this._restrictCursor(this._bufferService.cols); + switch (params.params[0]) { + case 0: + this._eraseInBufferLineProtected(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0); + break; + case 1: + this._eraseInBufferLineProtected(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false); + break; + case 2: + this._eraseInBufferLineProtected(this._activeBuffer.y, 0, this._bufferService.cols, true); + break; + } + this._dirtyRowService.markDirty(this._activeBuffer.y); + return true; + } /** * CSI Ps L diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index fee426e132..9d4b2d5528 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -177,7 +177,7 @@ export interface IBufferLine { set(index: number, value: CharData): void; loadCell(index: number, cell: ICellData): ICellData; setCell(index: number, cell: ICellData): void; - setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void; + setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs, is_protected?: number): void; addCodepointToCell(index: number, codePoint: number): void; insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void; deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void; @@ -191,6 +191,7 @@ export interface IBufferLine { /* direct access to cell attrs */ getWidth(index: number): number; + getProtected(index: number): number; hasWidth(index: number): number; getFg(index: number): number; getBg(index: number): number; diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index f0bf4fcb67..fda7c50cbc 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -12,9 +12,9 @@ import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData'; /** * buffer memory layout: * - * | uint32_t | uint32_t | uint32_t | - * | `content` | `FG` | `BG` | - * | wcwidth(2) comb(1) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) | + * | uint32_t | uint32_t | uint32_t | + * | `content` | `FG` | `BG` | + * | wcwidth(2) protected(1) comb(1) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) | */ @@ -107,6 +107,9 @@ export class BufferLine implements IBufferLine { public getWidth(index: number): number { return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT; } + public getProtected(index: number): number { + return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_PROTECTED_MASK; + } /** Test whether content has width. */ public hasWidth(index: number): number { @@ -201,11 +204,11 @@ export class BufferLine implements IBufferLine { * Since the input handler see the incoming chars as UTF32 codepoints, * it gets an optimized access method. */ - public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void { + public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs, is_protected: number = 0): void { if (bg & BgFlags.HAS_EXTENDED) { this._extendedAttrs[index] = eAttrs; } - this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT); + this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT) | (is_protected << 22); this._data[index * CELL_SIZE + Cell.FG] = fg; this._data[index * CELL_SIZE + Cell.BG] = bg; } diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index a2c1b884c8..3b44d8be41 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -58,6 +58,8 @@ export const enum Content { */ HAS_CONTENT_MASK = 0x3FFFFF, + IS_PROTECTED_MASK = 0x400000, // 1 << 22 + /** * bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2) * read: `width = (content & Content.widthMask) >> Content.widthShift;` @@ -68,8 +70,10 @@ export const enum Content { * shortcut if precondition `0 <= width <= 3` is met: * `content |= width << Content.widthShift;` */ - WIDTH_MASK = 0xC00000, // 3 << 22 - WIDTH_SHIFT = 22 + //WIDTH_MASK = 0xC00000, // 3 << 22 + //WIDTH_SHIFT = 22 + WIDTH_MASK = 0x1800000, // 3 << 23 + WIDTH_SHIFT = 23 } export const enum Attributes {