diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f6f892c7..e641e716 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -27,7 +27,9 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn test-unit-coverage --forbid-only + EXIT_CODE=$? ./node_modules/.bin/nyc report --reporter=cobertura + exit $EXIT_CODE displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' diff --git a/src/browser/input/MoveToCell.test.ts b/src/browser/input/MoveToCell.test.ts index bc4012c0..61e5f90b 100644 --- a/src/browser/input/MoveToCell.test.ts +++ b/src/browser/input/MoveToCell.test.ts @@ -19,19 +19,40 @@ describe('MoveToCell', () => { describe('normal buffer', () => { it('should use the right directional escape sequences', () => { + assert.equal(moveToCellSequence(1, 3, bufferService, false), '\x1b[D\x1b[D'); assert.equal(moveToCellSequence(2, 3, bufferService, false), '\x1b[D'); assert.equal(moveToCellSequence(4, 3, bufferService, false), '\x1b[C'); + assert.equal(moveToCellSequence(5, 3, bufferService, false), '\x1b[C\x1b[C'); }); - it('should ignore the Y value', () => { - assert.equal(moveToCellSequence(1, 1, bufferService, false), '\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(1, 2, bufferService, false), '\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(1, 3, bufferService, false), '\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(1, 4, bufferService, false), '\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[D\x1b[D'); + it('should wrap around entire row instead of doing up and down when the Y value differs', () => { + assert.equal(moveToCellSequence(1, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(3, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(4, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(5, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(2, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(3, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(4, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(5, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(2, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(3, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(4, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(5, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(2, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(3, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(4, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(5, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); }); it('should use the correct character for application cursor', () => { - assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D'); - assert.equal(moveToCellSequence(2, 1, bufferService, true), '\x1bOD'); + assert.equal(moveToCellSequence(3, 1, bufferService, true), '\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD'); + assert.equal(moveToCellSequence(3, 2, bufferService, true), '\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD'); + assert.equal(moveToCellSequence(2, 3, bufferService, true), '\x1bOD'); + assert.equal(moveToCellSequence(4, 3, bufferService, true), '\x1bOC'); + assert.equal(moveToCellSequence(3, 4, bufferService, true), '\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC'); + assert.equal(moveToCellSequence(3, 5, bufferService, true), '\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC'); }); }); diff --git a/src/browser/input/MoveToCell.ts b/src/browser/input/MoveToCell.ts index 406ec807..4a7b55aa 100644 --- a/src/browser/input/MoveToCell.ts +++ b/src/browser/input/MoveToCell.ts @@ -30,7 +30,31 @@ export function moveToCellSequence(targetX: number, targetY: number, bufferServi } // Only move horizontally for the normal buffer - return moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor); + let direction; + if (startY === targetY) { + direction = startX > targetX ? Direction.LEFT : Direction.RIGHT; + return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); + } + direction = startY > targetY ? Direction.LEFT : Direction.RIGHT; + const rowDifference = Math.abs(startY - targetY); + const cellsToMove = colsFromRowEnd(startY > targetY ? targetX : startX, bufferService) + + (rowDifference - 1) * bufferService.cols + 1 /*wrap around 1 row*/ + + colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService); + return repeat(cellsToMove, sequence(direction, applicationCursor)); +} + +/** + * Find the number of cols from a row beginning to a col. + */ +function colsFromRowBeginning(currX: number, bufferService: IBufferService): number { + return currX - 1; +} + +/** + * Find the number of cols from a col to row end. + */ +function colsFromRowEnd(currX: number, bufferService: IBufferService): number { + return bufferService.cols - currX; } /** diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 5fbcfdc9..e8679026 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -173,13 +173,13 @@ export class DomRenderer extends Disposable implements IRenderer { `}`; // Blink animation styles += - `@keyframes blink_box_shadow {` + + `@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` + ` 50% {` + ` box-shadow: none;` + ` }` + `}`; styles += - `@keyframes blink_block {` + + `@keyframes blink_block` + `_` + this._terminalClass + ` {` + ` 0% {` + ` background-color: ${this._colors.cursor.css};` + ` color: ${this._colors.cursorAccent.css};` + @@ -196,10 +196,10 @@ export class DomRenderer extends Disposable implements IRenderer { ` outline-offset: -1px;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` + - ` animation: blink_box_shadow 1s step-end infinite;` + + ` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + - ` animation: blink_block 1s step-end infinite;` + + ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + ` background-color: ${this._colors.cursor.css};` + diff --git a/src/common/data/Charsets.ts b/src/common/data/Charsets.ts index 56ca6799..56513b8a 100644 --- a/src/common/data/Charsets.ts +++ b/src/common/data/Charsets.ts @@ -30,14 +30,14 @@ export const DEFAULT_CHARSET: ICharset | null = CHARSETS['B']; CHARSETS['0'] = { '`': '\u25c6', // '◆' 'a': '\u2592', // '▒' - 'b': '\u0009', // '\t' - 'c': '\u000c', // '\f' - 'd': '\u000d', // '\r' - 'e': '\u000a', // '\n' + 'b': '\u2409', // '␉' (HT) + 'c': '\u240c', // '␌' (FF) + 'd': '\u240d', // '␍' (CR) + 'e': '\u240a', // '␊' (LF) 'f': '\u00b0', // '°' 'g': '\u00b1', // '±' - 'h': '\u2424', // '\u2424' (NL) - 'i': '\u000b', // '\v' + 'h': '\u2424', // '␤' (NL) + 'i': '\u240b', // '␋' (VT) 'j': '\u2518', // '┘' 'k': '\u2510', // '┐' 'l': '\u250c', // '┌' diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 7cd111ac..b855974a 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -52,7 +52,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, cancelEvents: false, useFlowControl: false, - wordSeparator: ' ()[]{}\',:;"`' + wordSeparator: ' ()[]{}\',"`' }); /**