From fa505111a210333b3aa6887ab1bc4f9831aeefb3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 08:51:23 -0700 Subject: [PATCH 1/2] Replace winptyCompat addon with windowsMode option --- demo/client.ts | 16 +++----- src/Buffer.ts | 2 +- src/Terminal.ts | 25 +++++++++++- src/WindowsMode.ts | 30 ++++++++++++++ src/addons/winptyCompat/Interfaces.ts | 14 ------- src/addons/winptyCompat/package.json | 5 --- src/addons/winptyCompat/tsconfig.json | 21 ---------- src/addons/winptyCompat/winptyCompat.test.ts | 19 --------- src/addons/winptyCompat/winptyCompat.ts | 43 -------------------- typings/xterm.d.ts | 12 ++++++ 10 files changed, 73 insertions(+), 114 deletions(-) create mode 100644 src/WindowsMode.ts delete mode 100644 src/addons/winptyCompat/Interfaces.ts delete mode 100644 src/addons/winptyCompat/package.json delete mode 100644 src/addons/winptyCompat/tsconfig.json delete mode 100644 src/addons/winptyCompat/winptyCompat.test.ts delete mode 100644 src/addons/winptyCompat/winptyCompat.ts diff --git a/demo/client.ts b/demo/client.ts index aaaf282947..fa98ee4ea1 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -13,12 +13,11 @@ import * as fit from '../lib/addons/fit/fit'; import * as fullscreen from '../lib/addons/fullscreen/fullscreen'; import * as search from '../lib/addons/search/search'; import * as webLinks from '../lib/addons/webLinks/webLinks'; -import * as winptyCompat from '../lib/addons/winptyCompat/winptyCompat'; import { ISearchOptions } from '../lib/addons/search/Interfaces'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module -import { Terminal as TerminalType } from 'xterm'; +import { Terminal as TerminalType, ITerminalOptions } from 'xterm'; export interface IWindowWithTerminal extends Window { term: TerminalType; @@ -30,10 +29,6 @@ Terminal.applyAddon(fit); Terminal.applyAddon(fullscreen); Terminal.applyAddon(search); Terminal.applyAddon(webLinks); -const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; -if (isWindows) { - Terminal.applyAddon(winptyCompat); -} let term; @@ -86,7 +81,10 @@ function createTerminal(): void { while (terminalContainer.children.length) { terminalContainer.removeChild(terminalContainer.children[0]); } - term = new Terminal({}); + const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; + term = new Terminal({ + windowsMode: isWindows + } as ITerminalOptions); window.term = term; // Expose `term` to window for debugging purposes term.on('resize', (size: { cols: number, rows: number }) => { if (!pid) { @@ -102,9 +100,7 @@ function createTerminal(): void { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); - if (isWindows) { - term.winptyCompatInit(); - } + term.webLinksInit(); term.fit(); term.focus(); diff --git a/src/Buffer.ts b/src/Buffer.ts index 9cc1adba88..4d844ea172 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -252,7 +252,7 @@ export class Buffer implements IBuffer { } private get _isReflowEnabled(): boolean { - return this._hasScrollback && !(this._terminal as any).isWinptyCompatEnabled; + return this._hasScrollback && !this._terminal.options.windowsMode; } private _reflow(newCols: number, newRows: number): void { diff --git a/src/Terminal.ts b/src/Terminal.ts index db696bd6f6..5ed997290c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,6 +52,7 @@ import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; import { clone } from './common/Clone'; +import { applyWindowsMode } from './WindowsMode'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -110,7 +111,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = { tabStopWidth: 8, theme: null, rightClickSelectsWord: Browser.isMac, - rendererType: 'canvas' + rendererType: 'canvas', + windowsMode: false }; export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal { @@ -210,6 +212,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _accessibilityManager: AccessibilityManager; private _screenDprMonitor: ScreenDprMonitor; private _theme: ITheme; + private _windowsMode: IDisposable | undefined; // bufferline to clone/copy from for new blank lines private _blankLine: IBufferLine = null; @@ -239,6 +242,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public dispose(): void { super.dispose(); + if (this._windowsMode) { + this._windowsMode.dispose(); + this._windowsMode = undefined; + } this._customKeyEventHandler = null; removeTerminalFromCache(this); this.handler = () => {}; @@ -321,6 +328,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.selectionManager.clearSelection(); this.selectionManager.initBuffersListeners(); } + + if (this.options.windowsMode) { + this._windowsMode = applyWindowsMode(this); + } } /** @@ -501,6 +512,18 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } break; case 'tabStopWidth': this.buffers.setupTabStops(); break; + case 'windowsMode': + if (value) { + if (!this._windowsMode) { + this._windowsMode = applyWindowsMode(this); + } + } else { + if (this._windowsMode) { + this._windowsMode.dispose(); + this._windowsMode = undefined; + } + } + break; } // Inform renderer of changes if (this.renderer) { diff --git a/src/WindowsMode.ts b/src/WindowsMode.ts new file mode 100644 index 0000000000..33a9bed5e6 --- /dev/null +++ b/src/WindowsMode.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable } from 'xterm'; +import { ITerminal } from './Types'; +import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from './Buffer'; + +export function applyWindowsMode(terminal: ITerminal): IDisposable { + // Winpty does not support wraparound mode which means that lines will never + // be marked as wrapped. This causes issues for things like copying a line + // retaining the wrapped new line characters or if consumers are listening + // in on the data stream. + // + // The workaround for this is to listen to every incoming line feed and mark + // the line as wrapped if the last character in the previous line is not a + // space. This is certainly not without its problems, but generally on + // Windows when text reaches the end of the terminal it's likely going to be + // wrapped. + return terminal.addDisposableListener('linefeed', () => { + const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1); + const lastChar = line.get(terminal.cols - 1); + + if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) { + const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y); + nextLine.isWrapped = true; + } + }); +} diff --git a/src/addons/winptyCompat/Interfaces.ts b/src/addons/winptyCompat/Interfaces.ts deleted file mode 100644 index 6217c8607a..0000000000 --- a/src/addons/winptyCompat/Interfaces.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Terminal } from 'xterm'; - -export interface ITerminalCore { - buffer: any; -} - -export interface IWinptyCompatAddonTerminal extends Terminal { - _core: ITerminalCore; -} diff --git a/src/addons/winptyCompat/package.json b/src/addons/winptyCompat/package.json deleted file mode 100644 index fc929497ce..0000000000 --- a/src/addons/winptyCompat/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "xterm.winptycompat", - "main": "winptyCompat.js", - "private": true -} diff --git a/src/addons/winptyCompat/tsconfig.json b/src/addons/winptyCompat/tsconfig.json deleted file mode 100644 index fa48c963e2..0000000000 --- a/src/addons/winptyCompat/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "lib": [ - "es5" - ], - "rootDir": ".", - "outDir": "../../../lib/addons/winptyCompat/", - "sourceMap": true, - "removeComments": true, - "declaration": true, - "types": [ - "../../node_modules/@types/mocha" - ] - }, - "include": [ - "**/*.ts", - "../../../typings/xterm.d.ts" - ] -} diff --git a/src/addons/winptyCompat/winptyCompat.test.ts b/src/addons/winptyCompat/winptyCompat.test.ts deleted file mode 100644 index c3a7e479e6..0000000000 --- a/src/addons/winptyCompat/winptyCompat.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; - -import * as winptyCompat from './winptyCompat'; - -class MockTerminal {} - -describe('winptyCompat addon', () => { - describe('apply', () => { - it('should do register the `winptyCompatInit` method', () => { - winptyCompat.apply(MockTerminal); - assert.equal(typeof (MockTerminal).prototype.winptyCompatInit, 'function'); - }); - }); -}); diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts deleted file mode 100644 index 58f59fd907..0000000000 --- a/src/addons/winptyCompat/winptyCompat.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Terminal } from 'xterm'; -import { IWinptyCompatAddonTerminal } from './Interfaces'; - -const CHAR_DATA_CODE_INDEX = 3; -const NULL_CELL_CODE = 0; -const WHITESPACE_CELL_CODE = 32; - -export function winptyCompatInit(terminal: Terminal): void { - const addonTerminal = terminal; - - (addonTerminal._core as any).isWinptyCompatEnabled = true; - - // Winpty does not support wraparound mode which means that lines will never - // be marked as wrapped. This causes issues for things like copying a line - // retaining the wrapped new line characters or if consumers are listening - // in on the data stream. - // - // The workaround for this is to listen to every incoming line feed and mark - // the line as wrapped if the last character in the previous line is not a - // space. This is certainly not without its problems, but generally on - // Windows when text reaches the end of the terminal it's likely going to be - // wrapped. - addonTerminal.on('linefeed', () => { - const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1); - const lastChar = line.get(addonTerminal.cols - 1); - - if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) { - const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y); - nextLine.isWrapped = true; - } - }); -} - -export function apply(terminalConstructor: typeof Terminal): void { - (terminalConstructor.prototype).winptyCompatInit = function (): void { - winptyCompatInit(this); - }; -} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d813bc5f7b..ec647a10f5 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -188,6 +188,18 @@ declare module 'xterm' { * The color theme of the terminal. */ theme?: ITheme; + + /** + * Whether "Windows mode" is enabled. Because Windows backends winpty and + * conpty operate by doing line wrapping on their side, xterm.js does not + * have access to wrapped lines. When Windows mode is enabled the following + * changes will be in effect: + * + * - Reflow is disabled. + * - Lines are assumed to be wrapped if the last character of the line is + * not whitespace. + */ + windowsMode?: boolean; } /** From b12d2c218eab24ecb981be861fd086c6379f7b84 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:03:24 -0700 Subject: [PATCH 2/2] Remove reference to winptyCompat tsconfig --- src/tsconfig.all.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tsconfig.all.json b/src/tsconfig.all.json index bee5df3237..2a53ab8994 100644 --- a/src/tsconfig.all.json +++ b/src/tsconfig.all.json @@ -9,8 +9,6 @@ { "path": "./addons/search" }, { "path": "./addons/terminado" }, { "path": "./addons/webLinks" }, - { "path": "./addons/winptyCompat" }, { "path": "./addons/zmodem" } ] } - \ No newline at end of file