From 74a6dfb19779ecc13b52483e15634b0e5f49b416 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 29 Jan 2018 11:28:48 -0800 Subject: [PATCH 01/33] Send arrow events on mouse wheel in alt buffer Fixes #426 --- src/Terminal.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 6e501d03..e1452f44 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -991,7 +991,20 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // } on(el, 'wheel', (ev: WheelEvent) => { - if (!this.mouseEvents) return; + if (!this.mouseEvents) { + // Convert wheel events into up/down events when the buffer does not have scrollback, this + // enables scrolling in apps hosted in the alt buffer such as vim or tmux. + if (!this.buffer.hasScrollback) { + let sequence = C0.ESC + (this.applicationCursor ? 'O' : '['); + if (ev.wheelDeltaY > 0) { + sequence += 'A'; + } else { + sequence += 'B'; + } + this.send(sequence); + } + return; + } if (this.x10Mouse || this.vt300Mouse || this.decLocator) return; sendButton(ev); ev.preventDefault(); From ae2881404116d8ee655da7ffa2074f22cfe68fed Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Feb 2018 21:34:57 -0800 Subject: [PATCH 02/33] Check for valid coords before getting word at coords Fixes #1287 --- src/SelectionManager.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index dc1d0682..5aa145e6 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -634,6 +634,11 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param coords The coordinates to get the word at. */ private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): IWordPosition { + // Ensure coords are within viewport (eg. not within scroll bar) + if (coords[0] >= this._terminal.cols) { + return null; + } + const bufferLine = this._buffer.lines.get(coords[1]); if (!bufferLine) { return null; From f8f135515517f5e68276e7a59a4c4449f9c934ac Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Mon, 19 Feb 2018 22:54:09 +0000 Subject: [PATCH 03/33] Validate colors --- src/renderer/ColorManager.ts | 72 +++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 90ef0431..43a41f56 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -66,6 +66,8 @@ function toPaddedHex(c: number): string { * Manages the source of truth for a terminal's colors. */ export class ColorManager implements IColorManager { + private VALID_NON_NAMED_COLORS = new RegExp('^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d\.]+%?\))$', 'g'); + public colors: IColorSet; constructor() { @@ -85,26 +87,54 @@ export class ColorManager implements IColorManager { * colors will be used where colors are not defined. */ public setTheme(theme: ITheme): void { - this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND; - this.colors.background = theme.background || DEFAULT_BACKGROUND; - this.colors.cursor = theme.cursor || DEFAULT_CURSOR; - this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT; - this.colors.selection = theme.selection || DEFAULT_SELECTION; - this.colors.ansi[0] = theme.black || DEFAULT_ANSI_COLORS[0]; - this.colors.ansi[1] = theme.red || DEFAULT_ANSI_COLORS[1]; - this.colors.ansi[2] = theme.green || DEFAULT_ANSI_COLORS[2]; - this.colors.ansi[3] = theme.yellow || DEFAULT_ANSI_COLORS[3]; - this.colors.ansi[4] = theme.blue || DEFAULT_ANSI_COLORS[4]; - this.colors.ansi[5] = theme.magenta || DEFAULT_ANSI_COLORS[5]; - this.colors.ansi[6] = theme.cyan || DEFAULT_ANSI_COLORS[6]; - this.colors.ansi[7] = theme.white || DEFAULT_ANSI_COLORS[7]; - this.colors.ansi[8] = theme.brightBlack || DEFAULT_ANSI_COLORS[8]; - this.colors.ansi[9] = theme.brightRed || DEFAULT_ANSI_COLORS[9]; - this.colors.ansi[10] = theme.brightGreen || DEFAULT_ANSI_COLORS[10]; - this.colors.ansi[11] = theme.brightYellow || DEFAULT_ANSI_COLORS[11]; - this.colors.ansi[12] = theme.brightBlue || DEFAULT_ANSI_COLORS[12]; - this.colors.ansi[13] = theme.brightMagenta || DEFAULT_ANSI_COLORS[13]; - this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14]; - this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15]; + this.colors.foreground = this._validateColor(theme.foreground, DEFAULT_FOREGROUND); + this.colors.background = this._validateColor(theme.background, DEFAULT_BACKGROUND); + this.colors.cursor = this._validateColor(theme.cursor, DEFAULT_CURSOR); + this.colors.cursorAccent = this._validateColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); + this.colors.selection = this._validateColor(theme.selection, DEFAULT_SELECTION); + this.colors.ansi[0] = this._validateColor(theme.black, DEFAULT_ANSI_COLORS[0]); + this.colors.ansi[1] = this._validateColor(theme.red, DEFAULT_ANSI_COLORS[1]); + this.colors.ansi[2] = this._validateColor(theme.green, DEFAULT_ANSI_COLORS[2]); + this.colors.ansi[3] = this._validateColor(theme.yellow, DEFAULT_ANSI_COLORS[3]); + this.colors.ansi[4] = this._validateColor(theme.blue, DEFAULT_ANSI_COLORS[4]); + this.colors.ansi[5] = this._validateColor(theme.magenta, DEFAULT_ANSI_COLORS[5]); + this.colors.ansi[6] = this._validateColor(theme.cyan, DEFAULT_ANSI_COLORS[6]); + this.colors.ansi[7] = this._validateColor(theme.white, DEFAULT_ANSI_COLORS[7]); + this.colors.ansi[8] = this._validateColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]); + this.colors.ansi[9] = this._validateColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]); + this.colors.ansi[10] = this._validateColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]); + this.colors.ansi[11] = this._validateColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]); + this.colors.ansi[12] = this._validateColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]); + this.colors.ansi[13] = this._validateColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]); + this.colors.ansi[14] = this._validateColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]); + this.colors.ansi[15] = this._validateColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); + } + + private _validateColor(color: string, fallback: string): string { + if (!color) { + return fallback; + } + + const isColorValid = this.VALID_NON_NAMED_COLORS.exec(color) || this._validateNamedColor(color); + + if (!isColorValid) { + console.warn(`Color: ${color} is invalid using fallback ${fallback}`); + } + + return isColorValid ? color : fallback; + } + + private _validateNamedColor(color: string): boolean { + const litmus = 'red'; + const d = document.createElement('div'); + d.style.color = litmus; + d.style.color = color; + + // Element's style.color will be reverted to litmus or set to '' if an invalid color is given + if (color !== litmus && (d.style.color === litmus || d.style.color === '')) { + return false; + } + + return true; } } From 99aa142cefe8f1da1c04cc07425d26ec50ed3c08 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Mon, 19 Feb 2018 23:55:25 +0000 Subject: [PATCH 04/33] Fix test -> wrong Regex flag --- src/renderer/ColorManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 43a41f56..ee9c9eac 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -66,7 +66,7 @@ function toPaddedHex(c: number): string { * Manages the source of truth for a terminal's colors. */ export class ColorManager implements IColorManager { - private VALID_NON_NAMED_COLORS = new RegExp('^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d\.]+%?\))$', 'g'); + private VALID_NON_NAMED_COLORS = new RegExp('^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d\.]+%?\))$', 'i'); public colors: IColorSet; From a9d6686e14b4f54350561ec5be37861161953525 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Tue, 20 Feb 2018 20:38:12 +0000 Subject: [PATCH 05/33] Regex is not needed for color validation --- src/renderer/ColorManager.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index ee9c9eac..33d69e26 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -66,8 +66,6 @@ function toPaddedHex(c: number): string { * Manages the source of truth for a terminal's colors. */ export class ColorManager implements IColorManager { - private VALID_NON_NAMED_COLORS = new RegExp('^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d\.]+%?\))$', 'i'); - public colors: IColorSet; constructor() { @@ -115,7 +113,7 @@ export class ColorManager implements IColorManager { return fallback; } - const isColorValid = this.VALID_NON_NAMED_COLORS.exec(color) || this._validateNamedColor(color); + const isColorValid = this._isColorValid(color); if (!isColorValid) { console.warn(`Color: ${color} is invalid using fallback ${fallback}`); @@ -124,7 +122,7 @@ export class ColorManager implements IColorManager { return isColorValid ? color : fallback; } - private _validateNamedColor(color: string): boolean { + private _isColorValid(color: string): boolean { const litmus = 'red'; const d = document.createElement('div'); d.style.color = litmus; From 1114977a2194db5457b4f9f304defa3d4d7d480e Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Wed, 21 Feb 2018 23:35:37 +0000 Subject: [PATCH 06/33] Fix tests --- src/renderer/ColorManager.test.ts | 9 ++++++++- src/renderer/ColorManager.ts | 6 ++++-- src/renderer/Renderer.ts | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/renderer/ColorManager.test.ts b/src/renderer/ColorManager.test.ts index 22407604..2dc60408 100644 --- a/src/renderer/ColorManager.test.ts +++ b/src/renderer/ColorManager.test.ts @@ -3,14 +3,21 @@ * @license MIT */ +import jsdom = require('jsdom'); import { assert } from 'chai'; import { ColorManager } from './ColorManager'; describe('ColorManager', () => { let cm: ColorManager; + let dom: jsdom.JSDOM; + let document: Document; + let window: Window; beforeEach(() => { - cm = new ColorManager(); + dom = new jsdom.JSDOM(''); + window = dom.window; + document = window.document; + cm = new ColorManager(document); }); describe('constructor', () => { diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 33d69e26..ddb928a5 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -67,8 +67,10 @@ function toPaddedHex(c: number): string { */ export class ColorManager implements IColorManager { public colors: IColorSet; + private _document: Document; - constructor() { + constructor(document: Document) { + this._document = document; this.colors = { foreground: DEFAULT_FOREGROUND, background: DEFAULT_BACKGROUND, @@ -124,7 +126,7 @@ export class ColorManager implements IColorManager { private _isColorValid(color: string): boolean { const litmus = 'red'; - const d = document.createElement('div'); + const d = this._document.createElement('div'); d.style.color = litmus; d.style.color = color; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2408f808..ca5fca6a 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -31,7 +31,7 @@ export class Renderer extends EventEmitter implements IRenderer { constructor(private _terminal: ITerminal, theme: ITheme) { super(); - this.colorManager = new ColorManager(); + this.colorManager = new ColorManager(document); if (theme) { this.colorManager.setTheme(theme); } From bc35f28f512ec5eadb2cf1bff107686b4bbeed36 Mon Sep 17 00:00:00 2001 From: Philip Olson Date: Thu, 22 Feb 2018 17:07:35 -0600 Subject: [PATCH 07/33] Paste newlines as \r Fix issue where non-windows OS terminals don't paste newlines correctly into misbehaving programs. As noted in JetBrains/jediterm#136, the most practical solution is for terminals to handle this in their paste logic. In a perfect world, all terminal programs would implement bracketed paste mode properly, but that's a lot to ask. - translate newlines to \r which is what terminal programs expect --- src/handlers/Clipboard.test.ts | 18 ++++++++++++------ src/handlers/Clipboard.ts | 9 +++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/handlers/Clipboard.test.ts b/src/handlers/Clipboard.test.ts index d35e5fa4..174f6efb 100644 --- a/src/handlers/Clipboard.test.ts +++ b/src/handlers/Clipboard.test.ts @@ -8,13 +8,19 @@ import * as Terminal from '../Terminal'; import * as Clipboard from './Clipboard'; describe('evaluatePastedTextProcessing', () => { - it('should replace carriage return + line feed with line feed on windows', () => { - const pastedText = 'foo\r\nbar\r\n'; - const processedText = Clipboard.prepareTextForTerminal(pastedText, false); - const windowsProcessedText = Clipboard.prepareTextForTerminal(pastedText, true); + it('should replace carriage return and/or line feed with carriage return', () => { + const pastedText = { + unix: 'foo\nbar\n', + windows: 'foo\r\nbar\r\n' + }; - assert.equal(processedText, 'foo\r\nbar\r\n'); - assert.equal(windowsProcessedText, 'foo\rbar\r'); + const processedText = { + unix: Clipboard.prepareTextForTerminal(pastedText.unix), + windows: Clipboard.prepareTextForTerminal(pastedText.windows) + }; + + assert.equal(processedText.unix, 'foo\rbar\r'); + assert.equal(processedText.windows, 'foo\rbar\r'); }); it('should bracket pasted text in bracketedPasteMode', () => { const pastedText = 'foo bar'; diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts index 7ac97714..23925007 100644 --- a/src/handlers/Clipboard.ts +++ b/src/handlers/Clipboard.ts @@ -18,11 +18,8 @@ declare var window: IWindow; * Prepares text to be pasted into the terminal by normalizing the line endings * @param text The pasted text that needs processing before inserting into the terminal */ -export function prepareTextForTerminal(text: string, isMSWindows: boolean): string { - if (isMSWindows) { - return text.replace(/\r?\n/g, '\r'); - } - return text; +export function prepareTextForTerminal(text: string): string { + return text.replace(/\r?\n/g, '\r'); } /** @@ -62,7 +59,7 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { let text: string; let dispatchPaste = function(text: string): void { - text = prepareTextForTerminal(text, term.browser.isMSWindows); + text = prepareTextForTerminal(text); text = bracketTextForPaste(text, term.bracketedPasteMode); term.handler(text); term.textarea.value = ''; From 5011e629b1f0d5c6ad0009ddc3439a459a4726a4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Feb 2018 11:08:11 -0800 Subject: [PATCH 08/33] Build addons by scanning addon dir --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 4353e46d..af4fdce0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -23,7 +23,7 @@ const tsProject = ts.createProject('tsconfig.json'); const srcDir = tsProject.config.compilerOptions.rootDir; let outDir = tsProject.config.compilerOptions.outDir; -const addons = ['attach', 'fit', 'fullscreen', 'search', 'terminado', 'winptyCompat', 'zmodem']; +const addons = fs.readdirSync(`${__dirname}/src/addons`); // Under some environments like TravisCI, this comes out at absolute which can // break the build. This ensures that the outDir is absolute. From dc822787d0869d16ce0173562155e130b3b7db44 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Feb 2018 11:11:23 -0800 Subject: [PATCH 09/33] Move http links to the new webLinks addon Fixes #1297 --- demo/main.js | 3 ++ src/Linkifier.ts | 47 ++-------------------------- src/Terminal.ts | 45 +++----------------------- src/Types.ts | 4 +-- src/addons/webLinks/package.json | 5 +++ src/addons/webLinks/tsconfig.json | 11 +++++++ src/addons/webLinks/webLinks.ts | 41 ++++++++++++++++++++++++ src/addons/winptyCompat/package.json | 2 +- typings/xterm.d.ts | 2 +- 9 files changed, 70 insertions(+), 90 deletions(-) create mode 100644 src/addons/webLinks/package.json create mode 100644 src/addons/webLinks/tsconfig.json create mode 100644 src/addons/webLinks/webLinks.ts diff --git a/demo/main.js b/demo/main.js index 7feb7939..d9c0151a 100644 --- a/demo/main.js +++ b/demo/main.js @@ -3,6 +3,7 @@ import * as attach from '../build/addons/attach/attach'; import * as fit from '../build/addons/fit/fit'; import * as fullscreen from '../build/addons/fullscreen/fullscreen'; import * as search from '../build/addons/search/search'; +import * as webLinks from '../build/addons/webLinks/webLinks'; import * as winptyCompat from '../build/addons/winptyCompat/winptyCompat'; @@ -10,6 +11,7 @@ Terminal.applyAddon(attach); Terminal.applyAddon(fit); Terminal.applyAddon(fullscreen); Terminal.applyAddon(search); +Terminal.applyAddon(webLinks); Terminal.applyAddon(winptyCompat); @@ -121,6 +123,7 @@ function createTerminal() { term.open(terminalContainer); term.winptyCompatInit(); + term.webLinksInit(); term.fit(); term.focus(); diff --git a/src/Linkifier.ts b/src/Linkifier.ts index a9617952..f5e59f25 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -8,30 +8,6 @@ import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkMatcherValidatio import { MouseZone } from './input/MouseZoneManager'; import { EventEmitter } from './EventEmitter'; -const protocolClause = '(https?:\\/\\/)'; -const domainCharacterSet = '[\\da-z\\.-]+'; -const negatedDomainCharacterSet = '[^\\da-z\\.-]+'; -const domainBodyClause = '(' + domainCharacterSet + ')'; -const tldClause = '([a-z\\.]{2,6})'; -const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; -const localHostClause = '(localhost)'; -const portClause = '(:\\d{1,5})'; -const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*'; -const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; -const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; -const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; -const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+'; -const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause; -const start = '(?:^|' + negatedDomainCharacterSet + ')('; -const end = ')($|' + negatedPathCharacterSet + ')'; -const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); - -/** - * The ID of the built in http(s) link matcher. - */ -const HYPERTEXT_LINK_MATCHER_ID = 0; - /** * The Linkifier applies links to rows shortly after they have been refreshed. */ @@ -47,7 +23,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { private _mouseZoneManager: IMouseZoneManager; private _rowsTimeoutId: number; - private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; + private _nextLinkMatcherId = 0; private _rowsToLinkify: {start: number, end: number}; constructor( @@ -58,7 +34,6 @@ export class Linkifier extends EventEmitter implements ILinkifier { start: null, end: null }; - this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 }); } /** @@ -111,23 +86,6 @@ export class Linkifier extends EventEmitter implements ILinkifier { this._rowsToLinkify.end = null; } - /** - * Attaches a handler for hypertext links, overriding default behavior for - * tandard http(s) links. - * @param handler The handler to use, this can be cleared with null. - */ - public setHypertextLinkHandler(handler: LinkMatcherHandler): void { - this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler; - } - - /** - * Attaches a validation callback for hypertext links. - * @param callback The callback to use, this can be cleared with null. - */ - public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void { - this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback; - } - /** * Registers a link matcher, allowing custom link patterns to be matched and * handled. @@ -139,7 +97,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { * @return The ID of the new matcher, this can be used to deregister. */ public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number { - if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) { + if (!handler) { throw new Error('handler must be defined'); } const matcher: ILinkMatcher = { @@ -222,7 +180,6 @@ export class Linkifier extends EventEmitter implements ILinkifier { private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher, offset: number = 0): void { // Iterate over nodes as we want to consider text nodes let result = []; - const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID; // Find the first match let match = text.match(matcher.regex); diff --git a/src/Terminal.ts b/src/Terminal.ts index 721996c9..fe9a1b9c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1326,36 +1326,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.customKeyEventHandler = customKeyEventHandler; } - /** - * Attaches a http(s) link handler, forcing web links to behave differently to - * regular tags. This will trigger a refresh as links potentially need to be - * reconstructed. Calling this with null will remove the handler. - * @param handler The handler callback function. - */ - public setHypertextLinkHandler(handler: LinkMatcherHandler): void { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); - } - this.linkifier.setHypertextLinkHandler(handler); - // Refresh to force links to refresh - this.refresh(0, this.rows - 1); - } - - /** - * Attaches a validation callback for hypertext links. This is useful to use - * validation logic or to do something with the link's element and url. - * @param callback The callback to use, this can - * be cleared with null. - */ - public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); - } - this.linkifier.setHypertextValidationCallback(callback); - // // Refresh to force links to refresh - this.refresh(0, this.rows - 1); - } - /** * Registers a link matcher, allowing custom link patterns to be matched and * handled. @@ -1367,12 +1337,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @return The ID of the new matcher, this can be used to deregister. */ public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number { - if (this.linkifier) { - const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); - this.refresh(0, this.rows - 1); - return matcherId; - } - return 0; + const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); + this.refresh(0, this.rows - 1); + return matcherId; } /** @@ -1380,10 +1347,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param matcherId The link matcher's ID (returned after register) */ public deregisterLinkMatcher(matcherId: number): void { - if (this.linkifier) { - if (this.linkifier.deregisterLinkMatcher(matcherId)) { - this.refresh(0, this.rows - 1); - } + if (this.linkifier.deregisterLinkMatcher(matcherId)) { + this.refresh(0, this.rows - 1); } } diff --git a/src/Types.ts b/src/Types.ts index be292dbf..b09397d7 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -14,7 +14,7 @@ export type XtermListener = (...args: any[]) => void; export type CharData = [number, string, number, number]; export type LineData = CharData[]; -export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; +export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void; export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; export enum LinkHoverEventTypes { @@ -298,8 +298,6 @@ export interface ISelectionManager { export interface ILinkifier extends IEventEmitter { attachToDom(mouseZoneManager: IMouseZoneManager): void; linkifyRows(start: number, end: number): void; - setHypertextLinkHandler(handler: LinkMatcherHandler): void; - setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void; registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): boolean; } diff --git a/src/addons/webLinks/package.json b/src/addons/webLinks/package.json new file mode 100644 index 00000000..f200cab4 --- /dev/null +++ b/src/addons/webLinks/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.weblinks", + "main": "weblinks.js", + "private": true +} diff --git a/src/addons/webLinks/tsconfig.json b/src/addons/webLinks/tsconfig.json new file mode 100644 index 00000000..7549370b --- /dev/null +++ b/src/addons/webLinks/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "rootDir": ".", + "outDir": "../../../lib/addons/webLinks/", + "sourceMap": true, + "removeComments": true, + "declaration": true + } +} diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts new file mode 100644 index 00000000..5e4ac029 --- /dev/null +++ b/src/addons/webLinks/webLinks.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/// + +import { Terminal } from 'xterm'; + +const protocolClause = '(https?:\\/\\/)'; +const domainCharacterSet = '[\\da-z\\.-]+'; +const negatedDomainCharacterSet = '[^\\da-z\\.-]+'; +const domainBodyClause = '(' + domainCharacterSet + ')'; +const tldClause = '([a-z\\.]{2,6})'; +const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; +const localHostClause = '(localhost)'; +const portClause = '(:\\d{1,5})'; +const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; +const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*'; +const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; +const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; +const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; +const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+'; +const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause; +const start = '(?:^|' + negatedDomainCharacterSet + ')('; +const end = ')($|' + negatedPathCharacterSet + ')'; +const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); + +function handleLink(event: MouseEvent, uri: string): void { + window.open(uri, '_blank'); +} + +export function webLinksInit(term: Terminal): void { + term.registerLinkMatcher(strictUrlRegex, handleLink, { matchIndex: 1 }); +} + +export function apply(terminalConstructor: typeof Terminal): void { + (terminalConstructor.prototype).webLinksInit = function (): void { + webLinksInit(this); + }; +} diff --git a/src/addons/winptyCompat/package.json b/src/addons/winptyCompat/package.json index 2e9fc164..fc929497 100644 --- a/src/addons/winptyCompat/package.json +++ b/src/addons/winptyCompat/package.json @@ -1,5 +1,5 @@ { - "name": "xterm.winptyCompat", + "name": "xterm.winptycompat", "main": "winptyCompat.js", "private": true } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index dd08b55e..4de9e4ed 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -395,7 +395,7 @@ declare module 'xterm' { * @param options Options for the link matcher. * @return The ID of the new matcher, this can be used to deregister. */ - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number; + registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; /** * (EXPERIMENTAL) Deregisters a link matcher if it has been registered. From 6a717177513ad81662247ed580ae2023a4e1da13 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Feb 2018 11:28:14 -0800 Subject: [PATCH 10/33] Allow customization of the web links handler and options --- src/addons/webLinks/webLinks.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index 5e4ac029..7ff87c60 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -5,7 +5,7 @@ /// -import { Terminal } from 'xterm'; +import { Terminal, ILinkMatcherOptions } from 'xterm'; const protocolClause = '(https?:\\/\\/)'; const domainCharacterSet = '[\\da-z\\.-]+'; @@ -30,12 +30,19 @@ function handleLink(event: MouseEvent, uri: string): void { window.open(uri, '_blank'); } -export function webLinksInit(term: Terminal): void { - term.registerLinkMatcher(strictUrlRegex, handleLink, { matchIndex: 1 }); +/** + * Initialize the web links addon, registering the link matcher. + * @param term The terminal to use web links within. + * @param handler A custom handler to use. + * @param options Custom options to use, matchIndex will always be ignored. + */ +export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { + options.matchIndex = 1; + term.registerLinkMatcher(strictUrlRegex, handler, options); } export function apply(terminalConstructor: typeof Terminal): void { - (terminalConstructor.prototype).webLinksInit = function (): void { - webLinksInit(this); + (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void { + webLinksInit(this, handler, options); }; } From 0d2cc7489ea0888e81f793c7fc1fff1d12ce25c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 24 Feb 2018 10:36:34 -0800 Subject: [PATCH 11/33] Fix tests, add webLinks addon test --- src/Linkifier.test.ts | 12 ++------ src/Linkifier.ts | 3 +- src/addons/webLinks/webLinks.test.ts | 42 ++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 src/addons/webLinks/webLinks.test.ts diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 14b1ac0f..ce1635a3 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -101,12 +101,6 @@ describe('Linkifier', () => { linkifier.attachToDom(mouseZoneManager); }); - describe('http links', () => { - it('should allow ~ character in URI path', (done) => { - assertLinkifiesEntireRow('http://foo.com/a~b#c~d?e~f', done); - }); - }); - describe('link matcher', () => { it('should match a single link', done => { assertLinkifiesRow('foo', /foo/, [{x: 0, length: 3}], done); @@ -200,19 +194,19 @@ describe('Linkifier', () => { it('should order the list from highest priority to lowest #1', () => { const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 1 }); const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: -1 }); - assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, 0, bId]); + assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, bId]); }); it('should order the list from highest priority to lowest #2', () => { const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: -1 }); const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 1 }); - assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [bId, 0, aId]); + assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [bId, aId]); }); it('should order items of equal priority in the order they are added', () => { const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 0 }); const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 0 }); - assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [0, aId, bId]); + assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, bId]); }); }); }); diff --git a/src/Linkifier.ts b/src/Linkifier.ts index f5e59f25..92a6ca9f 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -143,8 +143,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { * @return Whether a link matcher was found and deregistered. */ public deregisterLinkMatcher(matcherId: number): boolean { - // ID 0 is the hypertext link matcher which cannot be deregistered - for (let i = 1; i < this._linkMatchers.length; i++) { + for (let i = 0; i < this._linkMatchers.length; i++) { if (this._linkMatchers[i].id === matcherId) { this._linkMatchers.splice(i, 1); return true; diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts new file mode 100644 index 00000000..b3c76703 --- /dev/null +++ b/src/addons/webLinks/webLinks.test.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert, expect } from 'chai'; + +import * as webLinks from './webLinks'; + +class MockTerminal { + public regex: RegExp; + public handler: (event: MouseEvent, uri: string) => void; + public options?: any; + + public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: any): number { + this.regex = regex; + this.handler = handler; + this.options = options; + return 0; + } +} + +describe('webLinks addon', () => { + describe('apply', () => { + it('should do register the `webLinksInit` method', () => { + webLinks.apply(MockTerminal); + assert.equal(typeof (MockTerminal).prototype.webLinksInit, 'function'); + }); + }); + + it('should allow ~ character in URI path', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://foo.com/a~b#c~d?e~f '; + + let match = row.match(term.regex); + let uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); + }); +}); From 454fd4bfb1ab5c9ecea60a8db102d786dcbb8452 Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Sun, 4 Mar 2018 18:50:46 -0800 Subject: [PATCH 12/33] Split CharAtlas types and utility functions off I'm starting to pull some changes off of my large WIP branch/commit that adds an alternative dynamic character atlas. We're going to need to support multiple CharAtlas implementations, and this should make that easier. --- src/renderer/CharAtlas.ts | 51 ++-------------------------- src/renderer/atlas/CharAtlasUtils.ts | 47 +++++++++++++++++++++++++ src/renderer/atlas/Types.ts | 22 ++++++++++++ 3 files changed, 71 insertions(+), 49 deletions(-) create mode 100644 src/renderer/atlas/CharAtlasUtils.ts create mode 100644 src/renderer/atlas/Types.ts diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index ff8990b3..0c8e90ca 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -5,22 +5,13 @@ import { ITerminal } from '../Types'; import { IColorSet } from './Types'; +import { ICharAtlasConfig } from './atlas/Types'; import { isFirefox } from '../shared/utils/Browser'; import { generateCharAtlas, ICharAtlasRequest } from '../shared/CharAtlasGenerator'; +import { generateConfig, configEquals } from './atlas/CharAtlasUtils'; export const CHAR_ATLAS_CELL_SPACING = 1; -interface ICharAtlasConfig { - fontSize: number; - fontFamily: string; - fontWeight: string; - fontWeightBold: string; - scaledCharWidth: number; - scaledCharHeight: number; - allowTransparency: boolean; - colors: IColorSet; -} - interface ICharAtlasCacheEntry { bitmap: HTMLCanvasElement | Promise; config: ICharAtlasConfig; @@ -96,41 +87,3 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC charAtlasCache.push(newEntry); return newEntry.bitmap; } - -function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { - const clonedColors = { - foreground: colors.foreground, - background: colors.background, - cursor: null, - cursorAccent: null, - selection: null, - ansi: colors.ansi.slice(0, 16) - }; - return { - scaledCharWidth, - scaledCharHeight, - fontFamily: terminal.options.fontFamily, - fontSize: terminal.options.fontSize, - fontWeight: terminal.options.fontWeight, - fontWeightBold: terminal.options.fontWeightBold, - allowTransparency: terminal.options.allowTransparency, - colors: clonedColors - }; -} - -function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { - for (let i = 0; i < a.colors.ansi.length; i++) { - if (a.colors.ansi[i] !== b.colors.ansi[i]) { - return false; - } - } - return a.fontFamily === b.fontFamily && - a.fontSize === b.fontSize && - a.fontWeight === b.fontWeight && - a.fontWeightBold === b.fontWeightBold && - a.allowTransparency === b.allowTransparency && - a.scaledCharWidth === b.scaledCharWidth && - a.scaledCharHeight === b.scaledCharHeight && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; -} diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts new file mode 100644 index 00000000..57e362af --- /dev/null +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal } from '../../Types'; +import { ITheme } from 'xterm'; +import { IColorSet } from '../Types'; +import { ICharAtlasConfig } from './Types'; + +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { + const clonedColors = { + foreground: colors.foreground, + background: colors.background, + cursor: null, + cursorAccent: null, + selection: null, + ansi: colors.ansi.slice(0, 16) + }; + return { + scaledCharWidth, + scaledCharHeight, + fontFamily: terminal.options.fontFamily, + fontSize: terminal.options.fontSize, + fontWeight: terminal.options.fontWeight, + fontWeightBold: terminal.options.fontWeightBold, + allowTransparency: terminal.options.allowTransparency, + colors: clonedColors + }; +} + +export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { + for (let i = 0; i < a.colors.ansi.length; i++) { + if (a.colors.ansi[i] !== b.colors.ansi[i]) { + return false; + } + } + return a.fontFamily === b.fontFamily && + a.fontSize === b.fontSize && + a.fontWeight === b.fontWeight && + a.fontWeightBold === b.fontWeightBold && + a.allowTransparency === b.allowTransparency && + a.scaledCharWidth === b.scaledCharWidth && + a.scaledCharHeight === b.scaledCharHeight && + a.colors.foreground === b.colors.foreground && + a.colors.background === b.colors.background; +} diff --git a/src/renderer/atlas/Types.ts b/src/renderer/atlas/Types.ts new file mode 100644 index 00000000..3142ce3a --- /dev/null +++ b/src/renderer/atlas/Types.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { FontWeight } from 'xterm'; +import { IColorSet } from '../Types'; + +export const CHAR_ATLAS_CELL_SPACING = 1; +export const INVERTED_DEFAULT_COLOR = -1; +export const DIM_OPACITY = 0.5; + +export interface ICharAtlasConfig { + fontSize: number; + fontFamily: string; + fontWeight: FontWeight; + fontWeightBold: FontWeight; + scaledCharWidth: number; + scaledCharHeight: number; + allowTransparency: boolean; + colors: IColorSet; +} From 18fb1349105a3de4e5520d748c4a0258913b1204 Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Sun, 4 Mar 2018 20:55:42 -0800 Subject: [PATCH 13/33] Remove didCharSizeChange logic from renderer The only thing `didCharSizeChange` was used for was to figure out if we need to refresh the character atlas or not. However, `acquireCharAtlas` already inspects the atlas owned by this terminal, and if it matches, it avoids generating a new atlas. So if `didCharSizeChange` is true and it's not needed, it quickly bails out as a no-op. But if `didCharSizeChange` is false, and it was needed (possible given the complexity of testing all of these edge cases), it could introduce a bug. I think it makes sense to just get rid of `didCharSizeChange`, and just always call `acquireCharAtlas`. --- src/Terminal.ts | 8 +++----- src/renderer/BaseRenderLayer.ts | 6 ++---- src/renderer/CursorRenderLayer.ts | 4 ++-- src/renderer/LinkRenderLayer.ts | 4 ++-- src/renderer/Renderer.ts | 8 ++++---- src/renderer/SelectionRenderLayer.ts | 4 ++-- src/renderer/TextRenderLayer.ts | 4 ++-- src/renderer/Types.ts | 4 ++-- src/utils/TestUtils.test.ts | 2 +- 9 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 721996c9..fdf16830 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -472,11 +472,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT case 'lineHeight': case 'fontWeight': case 'fontWeightBold': - const didCharSizeChange = (key === 'fontWeight' || key === 'fontWeightBold' || key === 'enableBold'); - // When the font changes the size of the cells may change which requires a renderer clear this.renderer.clear(); - this.renderer.onResize(this.cols, this.rows, didCharSizeChange); + this.renderer.onResize(this.cols, this.rows); this.refresh(0, this.rows - 1); case 'scrollback': this.buffers.resize(this.cols, this.rows); @@ -702,14 +700,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport.onThemeChanged(this.renderer.colorManager.colors); this.on('cursormove', () => this.renderer.onCursorMove()); - this.on('resize', () => this.renderer.onResize(this.cols, this.rows, false)); + this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); this.on('blur', () => this.renderer.onBlur()); this.on('focus', () => this.renderer.onFocus()); this.on('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio)); // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. window.addEventListener('resize', () => this.renderer.onWindowResize(window.devicePixelRatio)); - this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows, true)); + this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows)); this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea()); this.selectionManager = new SelectionManager(this, this.charMeasure); diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 9423f65d..f977d4c5 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -93,7 +93,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } - public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { + public resize(terminal: ITerminal, dim: IRenderDimensions): void { this._scaledCellWidth = dim.scaledCellWidth; this._scaledCellHeight = dim.scaledCellHeight; this._scaledCharWidth = dim.scaledCharWidth; @@ -110,9 +110,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this.clearAll(); } - if (charSizeChanged) { - this._refreshCharAtlas(terminal, this._colors); - } + this._refreshCharAtlas(terminal, this._colors); } public abstract reset(terminal: ITerminal): void; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index d430b81d..8db861d5 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -45,8 +45,8 @@ export class CursorRenderLayer extends BaseRenderLayer { // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open? } - public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { - super.resize(terminal, dim, charSizeChanged); + public resize(terminal: ITerminal, dim: IRenderDimensions): void { + super.resize(terminal, dim); // Resizing the canvas discards the contents of the canvas so clear state this._state = { x: null, diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 61352f15..4071523a 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -18,8 +18,8 @@ export class LinkRenderLayer extends BaseRenderLayer { terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e)); } - public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { - super.resize(terminal, dim, charSizeChanged); + public resize(terminal: ITerminal, dim: IRenderDimensions): void { + super.resize(terminal, dim); // Resizing the canvas discards the contents of the canvas so clear state this._state = null; } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index fa1e34e6..bd0b688a 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -84,7 +84,7 @@ export class Renderer extends EventEmitter implements IRenderer { // and the terminal needs to refreshed if (this._devicePixelRatio !== devicePixelRatio) { this._devicePixelRatio = devicePixelRatio; - this.onResize(this._terminal.cols, this._terminal.rows, true); + this.onResize(this._terminal.cols, this._terminal.rows); } } @@ -106,12 +106,12 @@ export class Renderer extends EventEmitter implements IRenderer { return this.colorManager.colors; } - public onResize(cols: number, rows: number, didCharSizeChange: boolean): void { + public onResize(cols: number, rows: number): void { // Update character and canvas dimensions this._updateDimensions(); // Resize all render layers - this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions, didCharSizeChange)); + this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); // Force a refresh if (this._isPaused) { @@ -131,7 +131,7 @@ export class Renderer extends EventEmitter implements IRenderer { } public onCharSizeChanged(): void { - this.onResize(this._terminal.cols, this._terminal.rows, true); + this.onResize(this._terminal.cols, this._terminal.rows); } public onBlur(): void { diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 53fc9b39..7a5557fa 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -20,8 +20,8 @@ export class SelectionRenderLayer extends BaseRenderLayer { }; } - public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { - super.resize(terminal, dim, charSizeChanged); + public resize(terminal: ITerminal, dim: IRenderDimensions): void { + super.resize(terminal, dim); // Resizing the canvas discards the contents of the canvas so clear state this._state = { start: null, diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index a2ab9f19..3ab1fdf0 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -27,8 +27,8 @@ export class TextRenderLayer extends BaseRenderLayer { this._state = new GridCache(); } - public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { - super.resize(terminal, dim, charSizeChanged); + public resize(terminal: ITerminal, dim: IRenderDimensions): void { + super.resize(terminal, dim); // Clear the character width cache if the font or width has changed const terminalFont = this._getFont(terminal, false); diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 6f6e5f0e..1885d059 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -24,7 +24,7 @@ export interface IRenderer extends IEventEmitter { setTheme(theme: ITheme): IColorSet; onWindowResize(devicePixelRatio: number): void; - onResize(cols: number, rows: number, didCharSizeChange: boolean): void; + onResize(cols: number, rows: number): void; onCharSizeChanged(): void; onBlur(): void; onFocus(): void; @@ -103,7 +103,7 @@ export interface IRenderLayer { /** * Resize the render layer. */ - resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void; + resize(terminal: ITerminal, dim: IRenderDimensions): void; /** * Clear the state of the render layer. diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 8ba16179..0c53c9a0 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -310,7 +310,7 @@ export class MockRenderer implements IRenderer { } dimensions: IRenderDimensions; setTheme(theme: ITheme): IColorSet { return {}; } - onResize(cols: number, rows: number, didCharSizeChange: boolean): void {} + onResize(cols: number, rows: number): void {} onCharSizeChanged(): void {} onBlur(): void {} onFocus(): void {} From 9e62c8692f5a85c42c4c3aee316cc495c7ab84ba Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 5 Mar 2018 11:11:54 -0800 Subject: [PATCH 14/33] Only move horizontally on alt click in normal buffer Fixes #1305 --- src/Types.ts | 1 + src/handlers/AltClickHandler.ts | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Types.ts b/src/Types.ts index be292dbf..f0185123 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -252,6 +252,7 @@ export interface IBuffer { tabs: any; scrollBottom: number; scrollTop: number; + hasScrollback: boolean; savedY: number; savedX: number; isCursorInViewport: boolean; diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index c9c51cbe..7af32ee6 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -56,9 +56,13 @@ export class AltClickHandler { * then moves to requested col. */ private _arrowSequences(): string { - return this._resetStartingRow() + - this._moveToRequestedRow() + - this._moveToRequestedCol(); + // The alt buffer should try to navigate between rows + if (!this._terminal.buffer.hasScrollback) { + return this._resetStartingRow() + this._moveToRequestedRow() + this._moveToRequestedCol(); + } + + // Only move horizontally for the normal buffer + return this._moveHorizontallyOnly(); } /** @@ -113,6 +117,11 @@ export class AltClickHandler { ).length, this._sequence(direction)); } + private _moveHorizontallyOnly(): string { + let direction = this._horizontalDirection(); + return repeat(Math.abs(this._startCol - this._endCol), this._sequence(direction)); + } + /** * Utility functions */ From 7c471f5389cd43365aad3f6972df06b2cf7b56ba Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 5 Mar 2018 11:32:05 -0800 Subject: [PATCH 15/33] Fix test compile --- src/utils/TestUtils.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 8ba16179..8d12120b 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -276,6 +276,7 @@ export class MockBuffer implements IBuffer { lines: ICircularList<[number, string, number, number][]>; ydisp: number; ybase: number; + hasScrollback: boolean; y: number; x: number; tabs: any; From e137530581511a15fb060b9a838be8a59072336e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 5 Mar 2018 14:22:20 -0800 Subject: [PATCH 16/33] Scroll by exact line amount in alt buffer, unify scroll logic Fixes #1304 --- src/Terminal.ts | 21 +++++++++----- src/Types.ts | 1 + src/Viewport.ts | 58 ++++++++++++++++++++++++++++++------- src/utils/TestUtils.test.ts | 3 ++ 4 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index ec96d4ff..d234cbd4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1056,15 +1056,22 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT on(el, 'wheel', (ev: WheelEvent) => { if (!this.mouseEvents) { // Convert wheel events into up/down events when the buffer does not have scrollback, this - // enables scrolling in apps hosted in the alt buffer such as vim or tmux. + // enables scrolling in apps hosted in the alt buffer such as vim or tmux. if (!this.buffer.hasScrollback) { - let sequence = C0.ESC + (this.applicationCursor ? 'O' : '['); - if (ev.wheelDeltaY > 0) { - sequence += 'A'; - } else { - sequence += 'B'; + const amount = this.viewport.getLinesScrolled(ev); + + // Do nothing if there's no vertical scroll + if (amount === 0) { + return; } - this.send(sequence); + + // Construct and send sequences + const sequence = C0.ESC + (this.applicationCursor ? 'O' : '[') + ( ev.deltaY < 0 ? 'A' : 'B'); + let data = ''; + for (let i = 0; i < Math.abs(amount); i++) { + data += sequence; + } + this.send(data); } return; } diff --git a/src/Types.ts b/src/Types.ts index be292dbf..dd50ce06 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -89,6 +89,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { export interface IViewport { scrollBarWidth: number; syncScrollArea(): void; + getLinesScrolled(ev: WheelEvent): number; onWheel(ev: WheelEvent): void; onTouchStart(ev: TouchEvent): void; onTouchMove(ev: TouchEvent): void; diff --git a/src/Viewport.ts b/src/Viewport.ts index 35a2aa68..c64bfbb7 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -21,6 +21,11 @@ export class Viewport implements IViewport { private lastRecordedBufferHeight: number = 0; private lastTouchY: number; + // Stores a partial line amount when scrolling, this is used to keep track of how much of a line + // is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a + // quick fix and could have a more robust solution in place that reset the value when needed. + private _wheelPartialScroll: number; + /** * Creates a new Viewport. * @param terminal The terminal this viewport belongs to. @@ -113,22 +118,55 @@ export class Viewport implements IViewport { * @param ev The mouse wheel event. */ public onWheel(ev: WheelEvent): void { - if (ev.deltaY === 0) { - // Do nothing if it's not a vertical scroll event + const amount = this._getPixelsScrolled(ev); + if (amount === 0) { return; } - // Fallback to WheelEvent.DOM_DELTA_PIXEL - let multiplier = 1; - if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { - multiplier = this.currentRowHeight; - } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { - multiplier = this.currentRowHeight * this.terminal.rows; - } - this.viewportElement.scrollTop += ev.deltaY * multiplier; + this.viewportElement.scrollTop += amount; // Prevent the page from scrolling when the terminal scrolls ev.preventDefault(); } + private _getPixelsScrolled(ev: WheelEvent): number { + // Do nothing if it's not a vertical scroll event + if (ev.deltaY === 0) { + return 0; + } + + // Fallback to WheelEvent.DOM_DELTA_PIXEL + let amount = ev.deltaY; + if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { + amount *= this.currentRowHeight; + } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + amount *= this.currentRowHeight * this.terminal.rows; + } + return amount; + } + + /** + * Gets the number of pixels scrolled by the mouse event taking into account what type of delta + * is being used. + * @param ev The mouse wheel event. + */ + public getLinesScrolled(ev: WheelEvent): number { + // Do nothing if it's not a vertical scroll event + if (ev.deltaY === 0) { + return 0; + } + + // Fallback to WheelEvent.DOM_DELTA_LINE + let amount = ev.deltaY; + if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) { + amount /= this.currentRowHeight + 0.0; // Prevent integer division + this._wheelPartialScroll += amount; + amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1); + this._wheelPartialScroll %= 1; + } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + amount *= this.terminal.rows; + } + return amount; + } + /** * Handles the touchstart event, recording the touch occurred. * @param ev The touch event. diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 8ba16179..88b375e0 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -337,6 +337,9 @@ export class MockViewport implements IViewport { throw new Error('Method not implemented.'); } syncScrollArea(): void { } + getLinesScrolled(ev: WheelEvent): number { + throw new Error('Method not implemented.'); + } } export class MockCompositionHelper implements ICompositionHelper { From ff6ef1b63a3996906191142c98b1dd277d6ce24f Mon Sep 17 00:00:00 2001 From: Thomas Zilz Date: Tue, 6 Mar 2018 11:39:14 +0100 Subject: [PATCH 17/33] Initialise _wheelPartialScroll to 0 to prevent NaN --- src/Viewport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Viewport.ts b/src/Viewport.ts index c64bfbb7..e276d113 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -24,7 +24,7 @@ export class Viewport implements IViewport { // Stores a partial line amount when scrolling, this is used to keep track of how much of a line // is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a // quick fix and could have a more robust solution in place that reset the value when needed. - private _wheelPartialScroll: number; + private _wheelPartialScroll: number = 0; /** * Creates a new Viewport. From 1f113b1d29c507bfc40ee9f747f5037d6dc3cfff Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Wed, 7 Mar 2018 12:28:44 +0200 Subject: [PATCH 18/33] Add xterm.js Docker image to documentation Closes #1295 --- README.md | 14 +++++++++++++- docker-compose.yml | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cdaf0d71..a2a50372 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,19 @@ Then open your project's [Public URL](https://help.sourcelair.com/projects/the-p ### Docker -First, make sure you have Docker Engine 1.13.0 (or newer) and Docker Compose 1.10.0 (or newer). To run the demo and builder in parallel, run the following command in your terminal: +First, make sure you have Docker Engine 1.13.0 (or newer) and Docker Compose 1.10.0 (or newer). + +Xterm.js [provides a pre-built Docker image](https://hub.docker.com/r/xtermjs/xterm.js/) to help run the demo easily (Git tags are built as [tagged Docker images](https://hub.docker.com/r/xtermjs/xterm.js/tags/) too). + +To run the just demo (with no editing access). run the following command in your terminal: + +``` +docker run -p 3000:3000 xtermjs/xterm.js +``` + +Then open http://0.0.0.0:3000 in a web browser to access the demo. + +To run the demo and builder in parallel, run the following command in your terminal: ``` docker-compose up diff --git a/docker-compose.yml b/docker-compose.yml index effd975c..8e6a2f46 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,7 @@ version: "3" services: web: + image: xtermjs/xterm.js:latest build: . volumes: - ./:/usr/src/app From 31ce5c182697d7988e2ac1abe68f3dfde6667df8 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Wed, 7 Mar 2018 12:38:29 +0200 Subject: [PATCH 19/33] Run the webpack build in the docker image too --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1c72e679..1e3a262c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ RUN npm install COPY . /usr/src/app # Run the tests and build, to make sure everything is working nicely -RUN npm run build && npm run test +RUN npm run build && npm run webpack && npm run test From 70771430b55c085bc5c632b8cfaab7285dc63de3 Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Wed, 7 Mar 2018 22:13:41 -0800 Subject: [PATCH 20/33] Clean up char atlas constant imports/exports - Ensures that everything is only defined in one place, and everything imports from that place. - Moves CHAR_ATLAS_CELL_SPACING into a subdirectory of shared/ so that CharAtlasGenerator can pull from it. This addresses the comments on https://github.com/xtermjs/xterm.js/pull/1307/files/454fd4bfb1ab5c9ece --- src/renderer/BaseRenderLayer.ts | 7 +++---- src/renderer/CharAtlas.ts | 2 -- src/renderer/LinkRenderLayer.ts | 3 ++- src/renderer/TextRenderLayer.ts | 3 ++- src/renderer/atlas/Types.ts | 1 - src/shared/CharAtlasGenerator.ts | 3 +-- src/shared/atlas/Types.ts | 1 + 7 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 src/shared/atlas/Types.ts diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 9423f65d..8a236ada 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -5,12 +5,11 @@ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; import { CharData, ITerminal, ITerminalOptions } from '../Types'; -import { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { CHAR_ATLAS_CELL_SPACING } from '../shared/atlas/Types'; +import { acquireCharAtlas } from './CharAtlas'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; -export const INVERTED_DEFAULT_COLOR = -1; -const DIM_OPACITY = 0.5; - export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx: CanvasRenderingContext2D; diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index 0c8e90ca..c157ff3d 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -10,8 +10,6 @@ import { isFirefox } from '../shared/utils/Browser'; import { generateCharAtlas, ICharAtlasRequest } from '../shared/CharAtlasGenerator'; import { generateConfig, configEquals } from './atlas/CharAtlasUtils'; -export const CHAR_ATLAS_CELL_SPACING = 1; - interface ICharAtlasCacheEntry { bitmap: HTMLCanvasElement | Promise; config: ICharAtlasConfig; diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 61352f15..16d788b5 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -7,7 +7,8 @@ import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, IBuffer, ICharMeasure, import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; import { FLAGS, IColorSet, IRenderDimensions } from './Types'; -import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; +import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { BaseRenderLayer } from './BaseRenderLayer'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkHoverEvent = null; diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index a2ab9f19..6e82a05a 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -6,8 +6,9 @@ import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { FLAGS, IColorSet, IRenderDimensions } from './Types'; import { CharData, IBuffer, ICharMeasure, ITerminal } from '../Types'; +import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { GridCache } from './GridCache'; -import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; +import { BaseRenderLayer } from './BaseRenderLayer'; /** * This CharData looks like a null character, which will forc a clear and render diff --git a/src/renderer/atlas/Types.ts b/src/renderer/atlas/Types.ts index 3142ce3a..a79cb327 100644 --- a/src/renderer/atlas/Types.ts +++ b/src/renderer/atlas/Types.ts @@ -6,7 +6,6 @@ import { FontWeight } from 'xterm'; import { IColorSet } from '../Types'; -export const CHAR_ATLAS_CELL_SPACING = 1; export const INVERTED_DEFAULT_COLOR = -1; export const DIM_OPACITY = 0.5; diff --git a/src/shared/CharAtlasGenerator.ts b/src/shared/CharAtlasGenerator.ts index 96cf6cc6..fbf66ac3 100644 --- a/src/shared/CharAtlasGenerator.ts +++ b/src/shared/CharAtlasGenerator.ts @@ -4,6 +4,7 @@ */ import { FontWeight } from 'xterm'; +import { CHAR_ATLAS_CELL_SPACING } from './atlas/Types'; import { isFirefox } from './utils/Browser'; declare const Promise: any; @@ -29,8 +30,6 @@ export interface ICharAtlasRequest { allowTransparency: boolean; } -export const CHAR_ATLAS_CELL_SPACING = 1; - /** * Generates a char atlas. * @param context The window or worker context. diff --git a/src/shared/atlas/Types.ts b/src/shared/atlas/Types.ts new file mode 100644 index 00000000..b69dfe0d --- /dev/null +++ b/src/shared/atlas/Types.ts @@ -0,0 +1 @@ +export const CHAR_ATLAS_CELL_SPACING = 1; From ab69bfbb3260a791e65ea59e00bd935bcab03a2c Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Wed, 7 Mar 2018 22:23:19 -0800 Subject: [PATCH 21/33] Move rest of atlas implementations to atlas dirs Moves CharAtlas into src/renderer/atlas/ and CharAtlasGenerator into src/shared/atlas/. --- src/renderer/BaseRenderLayer.ts | 2 +- src/renderer/{ => atlas}/CharAtlas.ts | 12 ++++++------ src/shared/{ => atlas}/CharAtlasGenerator.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) rename src/renderer/{ => atlas}/CharAtlas.ts (88%) rename src/shared/{ => atlas}/CharAtlasGenerator.ts (97%) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 8a236ada..7957eed0 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; import { CharData, ITerminal, ITerminalOptions } from '../Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { CHAR_ATLAS_CELL_SPACING } from '../shared/atlas/Types'; -import { acquireCharAtlas } from './CharAtlas'; +import { acquireCharAtlas } from './atlas/CharAtlas'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; export abstract class BaseRenderLayer implements IRenderLayer { diff --git a/src/renderer/CharAtlas.ts b/src/renderer/atlas/CharAtlas.ts similarity index 88% rename from src/renderer/CharAtlas.ts rename to src/renderer/atlas/CharAtlas.ts index c157ff3d..d688d328 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/atlas/CharAtlas.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { ITerminal } from '../Types'; -import { IColorSet } from './Types'; -import { ICharAtlasConfig } from './atlas/Types'; -import { isFirefox } from '../shared/utils/Browser'; -import { generateCharAtlas, ICharAtlasRequest } from '../shared/CharAtlasGenerator'; -import { generateConfig, configEquals } from './atlas/CharAtlasUtils'; +import { ITerminal } from '../../Types'; +import { IColorSet } from '../Types'; +import { ICharAtlasConfig } from './Types'; +import { isFirefox } from '../../shared/utils/Browser'; +import { generateCharAtlas, ICharAtlasRequest } from '../../shared/atlas/CharAtlasGenerator'; +import { generateConfig, configEquals } from './CharAtlasUtils'; interface ICharAtlasCacheEntry { bitmap: HTMLCanvasElement | Promise; diff --git a/src/shared/CharAtlasGenerator.ts b/src/shared/atlas/CharAtlasGenerator.ts similarity index 97% rename from src/shared/CharAtlasGenerator.ts rename to src/shared/atlas/CharAtlasGenerator.ts index fbf66ac3..cf17bbb8 100644 --- a/src/shared/CharAtlasGenerator.ts +++ b/src/shared/atlas/CharAtlasGenerator.ts @@ -4,8 +4,8 @@ */ import { FontWeight } from 'xterm'; -import { CHAR_ATLAS_CELL_SPACING } from './atlas/Types'; -import { isFirefox } from './utils/Browser'; +import { CHAR_ATLAS_CELL_SPACING } from './Types'; +import { isFirefox } from '../utils/Browser'; declare const Promise: any; From 735dfb4710bac33fae32ae8faae7c02269fb8d38 Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Wed, 7 Mar 2018 23:19:38 -0800 Subject: [PATCH 22/33] Add copyright header to shared/atlas/Types.ts --- src/shared/atlas/Types.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/shared/atlas/Types.ts b/src/shared/atlas/Types.ts index b69dfe0d..e8bb6b0a 100644 --- a/src/shared/atlas/Types.ts +++ b/src/shared/atlas/Types.ts @@ -1 +1,6 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + export const CHAR_ATLAS_CELL_SPACING = 1; From 606c68dd0c24127fc29a3b0511fc847ee9ff3b4b Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Thu, 8 Mar 2018 08:59:46 +0000 Subject: [PATCH 23/33] Bump version to 3.2.0 Signed-off-by: Paris Kasidiaris --- AUTHORS | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 47328bc9..bcf1c5b8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -32,6 +32,7 @@ Christopher Jeffrey coderaiser Damien Tournoud Dan Brown +Daniel Griffen Daniel Griffen Daniel Imms Daniel Risacher @@ -74,6 +75,7 @@ Martin Chloride Martin Koppehel Martin Wang Matt Bierner +Matthew James Michael Irwin Mikko Karvonen mofux diff --git a/package.json b/package.json index 079333de..594c31a7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "3.1.0", + "version": "3.2.0", "ignore": [ "demo", "test", From 7639131c12823e23768053682b31ab23d9cc1576 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 8 Mar 2018 06:09:46 -0800 Subject: [PATCH 24/33] Ensure underscore is used for private vars Variables changed based on regex: "private [^(_|get)]" --- src/CompositionHelper.ts | 122 +++++++------- src/SoundManager.ts | 6 +- src/Terminal.ts | 272 +++++++++++++++--------------- src/Viewport.ts | 92 +++++----- src/renderer/CursorRenderLayer.ts | 8 +- 5 files changed, 250 insertions(+), 250 deletions(-) diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index 2aa0449f..387d3f8f 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -20,43 +20,43 @@ export class CompositionHelper { * Whether input composition is currently happening, eg. via a mobile keyboard, speech input or * IME. This variable determines whether the compositionText should be displayed on the UI. */ - private isComposing: boolean; + private _isComposing: boolean; /** * The position within the input textarea's value of the current composition. */ - private compositionPosition: IPosition; + private _compositionPosition: IPosition; /** * Whether a composition is in the process of being sent, setting this to false will cancel any * in-progress composition. */ - private isSendingComposition: boolean; + private _isSendingComposition: boolean; /** * Creates a new CompositionHelper. - * @param textarea The textarea that xterm uses for input. - * @param compositionView The element to display the in-progress composition in. - * @param terminal The Terminal to forward the finished composition to. + * @param _textarea The textarea that xterm uses for input. + * @param _compositionView The element to display the in-progress composition in. + * @param _terminal The Terminal to forward the finished composition to. */ constructor( - private textarea: HTMLTextAreaElement, - private compositionView: HTMLElement, - private terminal: ITerminal + private _textarea: HTMLTextAreaElement, + private _compositionView: HTMLElement, + private _terminal: ITerminal ) { - this.isComposing = false; - this.isSendingComposition = false; - this.compositionPosition = { start: null, end: null }; + this._isComposing = false; + this._isSendingComposition = false; + this._compositionPosition = { start: null, end: null }; } /** * Handles the compositionstart event, activating the composition view. */ public compositionstart(): void { - this.isComposing = true; - this.compositionPosition.start = this.textarea.value.length; - this.compositionView.textContent = ''; - this.compositionView.classList.add('active'); + this._isComposing = true; + this._compositionPosition.start = this._textarea.value.length; + this._compositionView.textContent = ''; + this._compositionView.classList.add('active'); } /** @@ -64,10 +64,10 @@ export class CompositionHelper { * @param {CompositionEvent} ev The event. */ public compositionupdate(ev: CompositionEvent): void { - this.compositionView.textContent = ev.data; + this._compositionView.textContent = ev.data; this.updateCompositionElements(); setTimeout(() => { - this.compositionPosition.end = this.textarea.value.length; + this._compositionPosition.end = this._textarea.value.length; }, 0); } @@ -76,7 +76,7 @@ export class CompositionHelper { * the handler. */ public compositionend(): void { - this.finalizeComposition(true); + this._finalizeComposition(true); } /** @@ -85,7 +85,7 @@ export class CompositionHelper { * @return Whether the Terminal should continue processing the keydown event. */ public keydown(ev: KeyboardEvent): boolean { - if (this.isComposing || this.isSendingComposition) { + if (this._isComposing || this._isSendingComposition) { if (ev.keyCode === 229) { // Continue composing if the keyCode is the "composition character" return false; @@ -95,14 +95,14 @@ export class CompositionHelper { } else { // Finish composition immediately. This is mainly here for the case where enter is // pressed and the handler needs to be triggered before the command is executed. - this.finalizeComposition(false); + this._finalizeComposition(false); } } if (ev.keyCode === 229) { // If the "composition character" is used but gets to this point it means a non-composition // character (eg. numbers and punctuation) was pressed when the IME was active. - this.handleAnyTextareaChanges(); + this._handleAnyTextareaChanges(); return false; } @@ -117,22 +117,22 @@ export class CompositionHelper { * compositionend event is triggered, such as enter, so that the composition is send before * the command is executed. */ - private finalizeComposition(waitForPropogation: boolean): void { - this.compositionView.classList.remove('active'); - this.isComposing = false; - this.clearTextareaPosition(); + private _finalizeComposition(waitForPropogation: boolean): void { + this._compositionView.classList.remove('active'); + this._isComposing = false; + this._clearTextareaPosition(); if (!waitForPropogation) { // Cancel any delayed composition send requests and send the input immediately. - this.isSendingComposition = false; - const input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end); - this.terminal.handler(input); + this._isSendingComposition = false; + const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end); + this._terminal.handler(input); } else { // Make a deep copy of the composition position here as a new compositionstart event may // fire before the setTimeout executes. const currentCompositionPosition = { - start: this.compositionPosition.start, - end: this.compositionPosition.end, + start: this._compositionPosition.start, + end: this._compositionPosition.end, }; // Since composition* events happen before the changes take place in the textarea on most @@ -143,22 +143,22 @@ export class CompositionHelper { // - The last compositionupdate event's data property does not always accurately describe // the character, a counter example being Korean where an ending consonsant can move to // the following character if the following input is a vowel. - this.isSendingComposition = true; + this._isSendingComposition = true; setTimeout(() => { // Ensure that the input has not already been sent - if (this.isSendingComposition) { - this.isSendingComposition = false; + if (this._isSendingComposition) { + this._isSendingComposition = false; let input; - if (this.isComposing) { + if (this._isComposing) { // Use the end position to get the string if a new composition has started. - input = this.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end); + input = this._textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end); } else { // Don't use the end position here in order to pick up any characters after the // composition has finished, for example when typing a non-composition character // (eg. 2) after a composition character. - input = this.textarea.value.substring(currentCompositionPosition.start); + input = this._textarea.value.substring(currentCompositionPosition.start); } - this.terminal.handler(input); + this._terminal.handler(input); } }, 0); } @@ -170,15 +170,15 @@ export class CompositionHelper { * character" (229) is triggered, in order to allow non-composition text to be entered when an * IME is active. */ - private handleAnyTextareaChanges(): void { - const oldValue = this.textarea.value; + private _handleAnyTextareaChanges(): void { + const oldValue = this._textarea.value; setTimeout(() => { // Ignore if a composition has started since the timeout - if (!this.isComposing) { - const newValue = this.textarea.value; + if (!this._isComposing) { + const newValue = this._textarea.value; const diff = newValue.replace(oldValue, ''); if (diff.length > 0) { - this.terminal.handler(diff); + this._terminal.handler(diff); } } }, 0); @@ -191,27 +191,27 @@ export class CompositionHelper { * necessary as the IME events across browsers are not consistently triggered. */ public updateCompositionElements(dontRecurse?: boolean): void { - if (!this.isComposing) { + if (!this._isComposing) { return; } - if (this.terminal.buffer.isCursorInViewport) { - const cellHeight = Math.ceil(this.terminal.charMeasure.height * this.terminal.options.lineHeight); - const cursorTop = this.terminal.buffer.y * cellHeight; - const cursorLeft = this.terminal.buffer.x * this.terminal.charMeasure.width; + if (this._terminal.buffer.isCursorInViewport) { + const cellHeight = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight); + const cursorTop = this._terminal.buffer.y * cellHeight; + const cursorLeft = this._terminal.buffer.x * this._terminal.charMeasure.width; - this.compositionView.style.left = cursorLeft + 'px'; - this.compositionView.style.top = cursorTop + 'px'; - this.compositionView.style.height = cellHeight + 'px'; - this.compositionView.style.lineHeight = cellHeight + 'px'; + this._compositionView.style.left = cursorLeft + 'px'; + this._compositionView.style.top = cursorTop + 'px'; + this._compositionView.style.height = cellHeight + 'px'; + this._compositionView.style.lineHeight = cellHeight + 'px'; // Sync the textarea to the exact position of the composition view so the IME knows where the // text is. - const compositionViewBounds = this.compositionView.getBoundingClientRect(); - this.textarea.style.left = cursorLeft + 'px'; - this.textarea.style.top = cursorTop + 'px'; - this.textarea.style.width = compositionViewBounds.width + 'px'; - this.textarea.style.height = compositionViewBounds.height + 'px'; - this.textarea.style.lineHeight = compositionViewBounds.height + 'px'; + const compositionViewBounds = this._compositionView.getBoundingClientRect(); + this._textarea.style.left = cursorLeft + 'px'; + this._textarea.style.top = cursorTop + 'px'; + this._textarea.style.width = compositionViewBounds.width + 'px'; + this._textarea.style.height = compositionViewBounds.height + 'px'; + this._textarea.style.lineHeight = compositionViewBounds.height + 'px'; } if (!dontRecurse) { @@ -223,8 +223,8 @@ export class CompositionHelper { * Clears the textarea's position so that the cursor does not blink on IE. * @private */ - private clearTextareaPosition(): void { - this.textarea.style.left = ''; - this.textarea.style.top = ''; + private _clearTextareaPosition(): void { + this._textarea.style.left = ''; + this._textarea.style.top = ''; } } diff --git a/src/SoundManager.ts b/src/SoundManager.ts index 1c50cbbc..4139c207 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -28,7 +28,7 @@ export class SoundManager implements ISoundManager { if (this._audioContext) { const bellAudioSource = this._audioContext.createBufferSource(); const context = this._audioContext; - this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), (buffer) => { + this._audioContext.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => { bellAudioSource.buffer = buffer; bellAudioSource.connect(context.destination); bellAudioSource.start(0); @@ -38,7 +38,7 @@ export class SoundManager implements ISoundManager { } } - private base64ToArrayBuffer(base64: string): ArrayBuffer { + private _base64ToArrayBuffer(base64: string): ArrayBuffer { const binaryString = window.atob(base64); const len = binaryString.length; const bytes = new Uint8Array(len); @@ -50,7 +50,7 @@ export class SoundManager implements ISoundManager { return bytes.buffer; } - private removeMimeType(dataURI: string): string { + private _removeMimeType(dataURI: string): string { // Split the input to get the mime-type and the data itself const splitUri = dataURI.split(','); diff --git a/src/Terminal.ts b/src/Terminal.ts index 51d53193..68128ff3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -133,30 +133,30 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * The HTMLElement that the terminal is created in, set by Terminal.open. */ - private parent: HTMLElement; - private context: Window; - private document: Document; - private body: HTMLBodyElement; - private viewportScrollArea: HTMLElement; - private viewportElement: HTMLElement; - private helperContainer: HTMLElement; - private compositionView: HTMLElement; - private charSizeStyleElement: HTMLStyleElement; + private _parent: HTMLElement; + private _context: Window; + private _document: Document; + private _body: HTMLBodyElement; + private _viewportScrollArea: HTMLElement; + private _viewportElement: HTMLElement; + private _helperContainer: HTMLElement; + private _compositionView: HTMLElement; + private _charSizeStyleElement: HTMLStyleElement; - private visualBellTimer: number; + private _visualBellTimer: number; public browser: IBrowser = Browser; public options: ITerminalOptions; - private colors: any; + private _colors: any; // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options public cursorState: number; public cursorHidden: boolean; public convertEol: boolean; - private sendDataQueue: string; - private customKeyEventHandler: CustomKeyEventHandler; + private _sendDataQueue: string; + private _customKeyEventHandler: CustomKeyEventHandler; // modes public applicationKeypad: boolean; @@ -174,10 +174,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public charsets: ICharset[]; // mouse properties - private decLocator: boolean; // This is unstable and never set + private _decLocator: boolean; // This is unstable and never set public x10Mouse: boolean; public vt200Mouse: boolean; - private vt300Mouse: boolean; // This is unstable and never set + private _vt300Mouse: boolean; // This is unstable and never set public normalMouse: boolean; public mouseEvents: boolean; public sendFocus: boolean; @@ -186,13 +186,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public urxvtMouse: boolean; // misc - private refreshStart: number; - private refreshEnd: number; + private _refreshStart: number; + private _refreshEnd: number; public savedCols: number; // stream - private readable: boolean; - private writable: boolean; + private _readable: boolean; + private _writable: boolean; public defAttr: number; public curAttr: number; @@ -204,7 +204,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // user input states public writeBuffer: string[]; - private writeInProgress: boolean; + private _writeInProgress: boolean; /** * Whether _xterm.js_ sent XOFF in order to catch up with the pty process. @@ -212,26 +212,26 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * XOFF via ^S that it will not automatically resume when the writeBuffer goes * below threshold. */ - private xoffSentToCatchUp: boolean; + private _xoffSentToCatchUp: boolean; /** Whether writing has been stopped as a result of XOFF */ - private writeStopped: boolean; + private _writeStopped: boolean; // leftover surrogate high from previous write invocation - private surrogateHigh: string; + private _surrogateHigh: string; // Store if user went browsing history in scrollback - private userScrolling: boolean; + private _userScrolling: boolean; - private inputHandler: InputHandler; + private _inputHandler: InputHandler; public soundManager: SoundManager; - private parser: Parser; + private _parser: Parser; public renderer: IRenderer; public selectionManager: SelectionManager; public linkifier: ILinkifier; public buffers: BufferSet; public viewport: IViewport; - private compositionHelper: ICompositionHelper; + private _compositionHelper: ICompositionHelper; public charMeasure: CharMeasure; private _mouseZoneManager: IMouseZoneManager; public mouseHelper: MouseHelper; @@ -258,10 +258,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT ) { super(); this.options = options; - this.setup(); + this._setup(); } - private setup(): void { + private _setup(): void { Object.keys(DEFAULT_OPTIONS).forEach((key) => { if (this.options[key] == null) { this.options[key] = DEFAULT_OPTIONS[key]; @@ -273,7 +273,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // this.context = options.context || window; // this.document = options.document || document; // TODO: WHy not document.body? - this.parent = document ? document.body : null; + this._parent = document ? document.body : null; this.cols = this.options.cols; this.rows = this.options.rows; @@ -284,8 +284,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.cursorState = 0; this.cursorHidden = false; - this.sendDataQueue = ''; - this.customKeyEventHandler = null; + this._sendDataQueue = ''; + this._customKeyEventHandler = null; // modes this.applicationKeypad = false; @@ -302,8 +302,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // TODO: Can this be just []? this.charsets = [null]; - this.readable = true; - this.writable = true; + this._readable = true; + this._writable = true; this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); this.curAttr = (0 << 18) | (257 << 9) | (256 << 0); @@ -315,15 +315,15 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // user input states this.writeBuffer = []; - this.writeInProgress = false; + this._writeInProgress = false; - this.xoffSentToCatchUp = false; - this.writeStopped = false; - this.surrogateHigh = ''; - this.userScrolling = false; + this._xoffSentToCatchUp = false; + this._writeStopped = false; + this._surrogateHigh = ''; + this._userScrolling = false; - this.inputHandler = new InputHandler(this); - this.parser = new Parser(this.inputHandler, this); + this._inputHandler = new InputHandler(this); + this._parser = new Parser(this._inputHandler, this); // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; @@ -538,8 +538,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Initialize default behavior */ - private initGlobal(): void { - this.bindKeys(); + private _initGlobal(): void { + this._bindKeys(); // Bind clipboard functionality on(this.element, 'copy', (event: ClipboardEvent) => { @@ -585,7 +585,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Apply key handling to the terminal */ - private bindKeys(): void { + private _bindKeys(): void { const self = this; on(this.element, 'keydown', function (ev: KeyboardEvent): void { if (document.activeElement !== this) { @@ -609,11 +609,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT on(this.textarea, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true); on(this.textarea, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true); - on(this.textarea, 'compositionstart', () => this.compositionHelper.compositionstart()); - on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this.compositionHelper.compositionupdate(e)); - on(this.textarea, 'compositionend', () => this.compositionHelper.compositionend()); - this.on('refresh', () => this.compositionHelper.updateCompositionElements()); - this.on('refresh', (data) => this.queueLinkification(data.start, data.end)); + on(this.textarea, 'compositionstart', () => this._compositionHelper.compositionstart()); + on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper.compositionupdate(e)); + on(this.textarea, 'compositionend', () => this._compositionHelper.compositionend()); + this.on('refresh', () => this._compositionHelper.updateCompositionElements()); + this.on('refresh', (data) => this._queueLinkification(data.start, data.end)); } /** @@ -625,44 +625,44 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT let i = 0; let div; - this.parent = parent || this.parent; + this._parent = parent || this._parent; - if (!this.parent) { + if (!this._parent) { throw new Error('Terminal requires a parent element.'); } // Grab global elements - this.context = this.parent.ownerDocument.defaultView; - this.document = this.parent.ownerDocument; - this.body = this.document.body; + this._context = this._parent.ownerDocument.defaultView; + this._document = this._parent.ownerDocument; + this._body = this._document.body; this._screenDprMonitor = new ScreenDprMonitor(); this._screenDprMonitor.setListener(() => this.emit('dprchange', window.devicePixelRatio)); // Create main element container - this.element = this.document.createElement('div'); + this.element = this._document.createElement('div'); this.element.classList.add('terminal'); this.element.classList.add('xterm'); this.element.setAttribute('tabindex', '0'); - this.parent.appendChild(this.element); + this._parent.appendChild(this.element); // Performance: Use a document fragment to build the terminal // viewport and helper elements detached from the DOM const fragment = document.createDocumentFragment(); - this.viewportElement = document.createElement('div'); - this.viewportElement.classList.add('xterm-viewport'); - fragment.appendChild(this.viewportElement); - this.viewportScrollArea = document.createElement('div'); - this.viewportScrollArea.classList.add('xterm-scroll-area'); - this.viewportElement.appendChild(this.viewportScrollArea); + this._viewportElement = document.createElement('div'); + this._viewportElement.classList.add('xterm-viewport'); + fragment.appendChild(this._viewportElement); + this._viewportScrollArea = document.createElement('div'); + this._viewportScrollArea.classList.add('xterm-scroll-area'); + this._viewportElement.appendChild(this._viewportScrollArea); this.screenElement = document.createElement('div'); this.screenElement.classList.add('xterm-screen'); // Create the container that will hold helpers like the textarea for // capturing DOM Events. Then produce the helpers. - this.helperContainer = document.createElement('div'); - this.helperContainer.classList.add('xterm-helpers'); - this.screenElement.appendChild(this.helperContainer); + this._helperContainer = document.createElement('div'); + this._helperContainer.classList.add('xterm-helpers'); + this.screenElement.appendChild(this._helperContainer); fragment.appendChild(this.screenElement); this._mouseZoneManager = new MouseZoneManager(this); @@ -680,23 +680,23 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.textarea.tabIndex = 0; this.textarea.addEventListener('focus', () => this._onTextAreaFocus()); this.textarea.addEventListener('blur', () => this._onTextAreaBlur()); - this.helperContainer.appendChild(this.textarea); + this._helperContainer.appendChild(this.textarea); - this.compositionView = document.createElement('div'); - this.compositionView.classList.add('composition-view'); - this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this); - this.helperContainer.appendChild(this.compositionView); + this._compositionView = document.createElement('div'); + this._compositionView.classList.add('composition-view'); + this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this); + this._helperContainer.appendChild(this._compositionView); - this.charSizeStyleElement = document.createElement('style'); - this.helperContainer.appendChild(this.charSizeStyleElement); - this.charMeasure = new CharMeasure(document, this.helperContainer); + this._charSizeStyleElement = document.createElement('style'); + this._helperContainer.appendChild(this._charSizeStyleElement); + this.charMeasure = new CharMeasure(document, this._helperContainer); // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); this.renderer = new Renderer(this, this.options.theme); this.options.theme = null; - this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); + this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure); this.viewport.onThemeChanged(this.renderer.colorManager.colors); this.on('cursormove', () => this.renderer.onCursorMove()); @@ -725,7 +725,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport.syncScrollArea(); this.selectionManager.refresh(); }); - this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh()); + this._viewportElement.addEventListener('scroll', () => this.selectionManager.refresh()); this.mouseHelper = new MouseHelper(this.renderer); @@ -742,7 +742,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refresh(0, this.rows - 1); // Initialize global actions that need to be taken on the document. - this.initGlobal(); + this._initGlobal(); // Listen for mouse events and translate // them into terminal mouse protocols. @@ -869,7 +869,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // button: button // }); - if (self.vt300Mouse) { + if (self._vt300Mouse) { // NOTE: Unstable. // http://www.vt100.net/docs/vt3xx-gp/chapter15.html button &= 3; @@ -886,7 +886,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return; } - if (self.decLocator) { + if (self._decLocator) { // NOTE: Unstable. button &= 3; pos.x -= 32; @@ -1029,19 +1029,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } // bind events - if (this.normalMouse) on(this.document, 'mousemove', sendMove); + if (this.normalMouse) on(this._document, 'mousemove', sendMove); // x10 compatibility mode can't send button releases if (!this.x10Mouse) { const handler = (ev: MouseEvent) => { sendButton(ev); // TODO: Seems dangerous calling this on document? - if (this.normalMouse) off(this.document, 'mousemove', sendMove); - off(this.document, 'mouseup', handler); + if (this.normalMouse) off(this._document, 'mousemove', sendMove); + off(this._document, 'mouseup', handler); return this.cancel(ev); }; // TODO: Seems dangerous calling this on document? - on(this.document, 'mouseup', handler); + on(this._document, 'mouseup', handler); } return this.cancel(ev); @@ -1053,7 +1053,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT on(el, 'wheel', (ev: WheelEvent) => { if (!this.mouseEvents) return; - if (this.x10Mouse || this.vt300Mouse || this.decLocator) return; + if (this.x10Mouse || this._vt300Mouse || this._decLocator) return; sendButton(ev); ev.preventDefault(); }); @@ -1084,8 +1084,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public destroy(): void { super.destroy(); - this.readable = false; - this.writable = false; + this._readable = false; + this._writable = false; this.handler = () => {}; this.write = () => {}; if (this.element && this.element.parentNode) { @@ -1111,7 +1111,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {number} start The row to start from (between 0 and this.rows - 1). * @param {number} end The row to end at (between start and this.rows - 1). */ - private queueLinkification(start: number, end: number): void { + private _queueLinkification(start: number, end: number): void { if (this.linkifier) { this.linkifier.linkifyRows(start, end); } @@ -1151,13 +1151,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (!willBufferBeTrimmed) { this.buffer.ybase++; // Only scroll the ydisp with ybase if the user has not scrolled up - if (!this.userScrolling) { + if (!this._userScrolling) { this.buffer.ydisp++; } } else { // When the buffer is full and the user has scrolled up, keep the text // stable unless ydisp is right at the top - if (this.userScrolling) { + if (this._userScrolling) { this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); } } @@ -1171,7 +1171,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Move the viewport to the bottom of the buffer unless the user is // scrolling. - if (!this.userScrolling) { + if (!this._userScrolling) { this.buffer.ydisp = this.buffer.ybase; } @@ -1200,9 +1200,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (this.buffer.ydisp === 0) { return; } - this.userScrolling = true; + this._userScrolling = true; } else if (disp + this.buffer.ydisp >= this.buffer.ybase) { - this.userScrolling = false; + this._userScrolling = false; } const oldYdisp = this.buffer.ydisp; @@ -1252,54 +1252,54 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Send XOFF to pause the pty process if the write buffer becomes too large so // xterm.js can catch up before more data is sent. This is necessary in order // to keep signals such as ^C responsive. - if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { + if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { // XOFF - stop pty pipe // XON will be triggered by emulator before processing data chunk this.send(C0.DC3); - this.xoffSentToCatchUp = true; + this._xoffSentToCatchUp = true; } - if (!this.writeInProgress && this.writeBuffer.length > 0) { + if (!this._writeInProgress && this.writeBuffer.length > 0) { // Kick off a write which will write all data in sequence recursively - this.writeInProgress = true; + this._writeInProgress = true; // Kick off an async innerWrite so more writes can come in while processing data setTimeout(() => { - this.innerWrite(); + this._innerWrite(); }); } } - private innerWrite(): void { + private _innerWrite(): void { const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); while (writeBatch.length > 0) { const data = writeBatch.shift(); // If XOFF was sent in order to catch up with the pty process, resume it if // the writeBuffer is empty to allow more data to come in. - if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { + if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { this.send(C0.DC1); - this.xoffSentToCatchUp = false; + this._xoffSentToCatchUp = false; } - this.refreshStart = this.buffer.y; - this.refreshEnd = this.buffer.y; + this._refreshStart = this.buffer.y; + this._refreshEnd = this.buffer.y; // HACK: Set the parser state based on it's state at the time of return. // This works around the bug #662 which saw the parser state reset in the // middle of parsing escape sequence in two chunks. For some reason the // state of the parser resets to 0 after exiting parser.parse. This change // just sets the state back based on the correct return statement. - const state = this.parser.parse(data); - this.parser.setState(state); + const state = this._parser.parse(data); + this._parser.setState(state); this.updateRange(this.buffer.y); - this.refresh(this.refreshStart, this.refreshEnd); + this.refresh(this._refreshStart, this._refreshEnd); } if (this.writeBuffer.length > 0) { // Allow renderer to catch up before processing the next batch - setTimeout(() => this.innerWrite(), 0); + setTimeout(() => this._innerWrite(), 0); } else { - this.writeInProgress = false; + this._writeInProgress = false; } } @@ -1321,7 +1321,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * the event should be processed by xterm.js. */ public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void { - this.customKeyEventHandler = customKeyEventHandler; + this._customKeyEventHandler = customKeyEventHandler; } /** @@ -1390,11 +1390,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {KeyboardEvent} ev The keydown event to be handled. */ protected _keyDown(ev: KeyboardEvent): boolean { - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { + if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) { return false; } - if (!this.compositionHelper.keydown(ev)) { + if (!this._compositionHelper.keydown(ev)) { if (this.buffer.ybase !== this.buffer.ydisp) { this.scrollToBottom(); } @@ -1404,9 +1404,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT const result = this._evaluateKeyEscapeSequence(ev); if (result.key === C0.DC3) { // XOFF - this.writeStopped = true; + this._writeStopped = true; } else if (result.key === C0.DC1) { // XON - this.writeStopped = false; + this._writeStopped = false; } if (result.scrollLines) { @@ -1801,7 +1801,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT protected _keyPress(ev: KeyboardEvent): boolean { let key; - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { + if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) { return false; } @@ -1838,14 +1838,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {string} data */ public send(data: string): void { - if (!this.sendDataQueue) { + if (!this._sendDataQueue) { setTimeout(() => { - this.handler(this.sendDataQueue); - this.sendDataQueue = ''; + this.handler(this._sendDataQueue); + this._sendDataQueue = ''; }, 1); } - this.sendDataQueue += data; + this._sendDataQueue += data; } /** @@ -1854,14 +1854,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public bell(): void { this.emit('bell'); - if (this.soundBell()) { + if (this._soundBell()) { this.soundManager.playBellSound(); } - if (this.visualBell()) { + if (this._visualBell()) { this.element.classList.add('visual-bell-active'); - clearTimeout(this.visualBellTimer); - this.visualBellTimer = window.setTimeout(() => { + clearTimeout(this._visualBellTimer); + this._visualBellTimer = window.setTimeout(() => { this.element.classList.remove('visual-bell-active'); }, 200); } @@ -1872,8 +1872,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public log(text: string, data?: any): void { if (!this.options.debug) return; - if (!this.context.console || !this.context.console.log) return; - this.context.console.log(text, data); + if (!this._context.console || !this._context.console.log) return; + this._context.console.log(text, data); } /** @@ -1881,8 +1881,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public error(text: string, data?: any): void { if (!this.options.debug) return; - if (!this.context.console || !this.context.console.error) return; - this.context.console.error(text, data); + if (!this._context.console || !this._context.console.error) return; + this._context.console.error(text, data); } /** @@ -1926,8 +1926,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {number} y The number of rows to refresh next. */ public updateRange(y: number): void { - if (y < this.refreshStart) this.refreshStart = y; - if (y > this.refreshEnd) this.refreshEnd = y; + if (y < this._refreshStart) this._refreshStart = y; + if (y > this._refreshEnd) this._refreshEnd = y; // if (y > this.refreshEnd) { // this.refreshEnd = y; // if (y > this.rows - 1) { @@ -1940,8 +1940,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Set the range of refreshing to the maximum value */ public maxRange(): void { - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this._refreshStart = 0; + this._refreshEnd = this.rows - 1; } /** @@ -2079,7 +2079,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Emit the 'title' event and populate the given title. * @param {string} title The title to populate in the event. */ - private handleTitle(title: string): void { + private _handleTitle(title: string): void { /** * This event is emitted when the title of the terminal is changed * from inside the terminal. The parameter is the new title. @@ -2134,11 +2134,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public reset(): void { this.options.rows = this.rows; this.options.cols = this.cols; - const customKeyEventHandler = this.customKeyEventHandler; - const inputHandler = this.inputHandler; - this.setup(); - this.customKeyEventHandler = customKeyEventHandler; - this.inputHandler = inputHandler; + const customKeyEventHandler = this._customKeyEventHandler; + const inputHandler = this._inputHandler; + this._setup(); + this._customKeyEventHandler = customKeyEventHandler; + this._inputHandler = inputHandler; this.refresh(0, this.rows - 1); this.viewport.syncScrollArea(); } @@ -2166,13 +2166,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return matchColor_(r1, g1, b1); } - private visualBell(): boolean { + private _visualBell(): boolean { return false; // return this.options.bellStyle === 'visual' || // this.options.bellStyle === 'both'; } - private soundBell(): boolean { + private _soundBell(): boolean { return this.options.bellStyle === 'sound'; // return this.options.bellStyle === 'sound' || // this.options.bellStyle === 'both'; diff --git a/src/Viewport.ts b/src/Viewport.ts index 35a2aa68..59d95406 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -15,51 +15,51 @@ const FALLBACK_SCROLL_BAR_WIDTH = 15; */ export class Viewport implements IViewport { public scrollBarWidth: number = 0; - private currentRowHeight: number = 0; - private lastRecordedBufferLength: number = 0; - private lastRecordedViewportHeight: number = 0; - private lastRecordedBufferHeight: number = 0; - private lastTouchY: number; + private _currentRowHeight: number = 0; + private _lastRecordedBufferLength: number = 0; + private _lastRecordedViewportHeight: number = 0; + private _lastRecordedBufferHeight: number = 0; + private _lastTouchY: number; /** * Creates a new Viewport. - * @param terminal The terminal this viewport belongs to. - * @param viewportElement The DOM element acting as the viewport. - * @param scrollArea The DOM element acting as the scroll area. - * @param charMeasure A DOM element used to measure the character size of. the terminal. + * @param _terminal The terminal this viewport belongs to. + * @param _viewportElement The DOM element acting as the viewport. + * @param _scrollArea The DOM element acting as the scroll area. + * @param _charMeasure A DOM element used to measure the character size of. the terminal. */ constructor( - private terminal: ITerminal, - private viewportElement: HTMLElement, - private scrollArea: HTMLElement, - private charMeasure: CharMeasure + private _terminal: ITerminal, + private _viewportElement: HTMLElement, + private _scrollArea: HTMLElement, + private _charMeasure: CharMeasure ) { // Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar. // Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case, // therefore we account for a standard amount to make it visible - this.scrollBarWidth = (this.viewportElement.offsetWidth - this.scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; - this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); + this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; + this._viewportElement.addEventListener('scroll', this._onScroll.bind(this)); // Perform this async to ensure the CharMeasure is ready. setTimeout(() => this.syncScrollArea(), 0); } public onThemeChanged(colors: IColorSet): void { - this.viewportElement.style.backgroundColor = colors.background; + this._viewportElement.style.backgroundColor = colors.background; } /** * Refreshes row height, setting line-height, viewport height and scroll area height if * necessary. */ - private refresh(): void { - if (this.charMeasure.height > 0) { - this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio; - this.lastRecordedViewportHeight = this.viewportElement.offsetHeight; - const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength) + (this.lastRecordedViewportHeight - this.terminal.renderer.dimensions.canvasHeight); - if (this.lastRecordedBufferHeight !== newBufferHeight) { - this.lastRecordedBufferHeight = newBufferHeight; - this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px'; + private _refresh(): void { + if (this._charMeasure.height > 0) { + this._currentRowHeight = this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio; + this._lastRecordedViewportHeight = this._viewportElement.offsetHeight; + const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._terminal.renderer.dimensions.canvasHeight); + if (this._lastRecordedBufferHeight !== newBufferHeight) { + this._lastRecordedBufferHeight = newBufferHeight; + this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px'; } } } @@ -68,24 +68,24 @@ export class Viewport implements IViewport { * Updates dimensions and synchronizes the scroll area if necessary. */ public syncScrollArea(): void { - if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) { + if (this._lastRecordedBufferLength !== this._terminal.buffer.lines.length) { // If buffer height changed - this.lastRecordedBufferLength = this.terminal.buffer.lines.length; - this.refresh(); - } else if (this.lastRecordedViewportHeight !== (this.terminal).renderer.dimensions.canvasHeight) { + this._lastRecordedBufferLength = this._terminal.buffer.lines.length; + this._refresh(); + } else if (this._lastRecordedViewportHeight !== (this._terminal).renderer.dimensions.canvasHeight) { // If viewport height changed - this.refresh(); + this._refresh(); } else { // If size has changed, refresh viewport - if (this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this.currentRowHeight) { - this.refresh(); + if (this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) { + this._refresh(); } } // Sync scrollTop - const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight; - if (this.viewportElement.scrollTop !== scrollTop) { - this.viewportElement.scrollTop = scrollTop; + const scrollTop = this._terminal.buffer.ydisp * this._currentRowHeight; + if (this._viewportElement.scrollTop !== scrollTop) { + this._viewportElement.scrollTop = scrollTop; } } @@ -94,16 +94,16 @@ export class Viewport implements IViewport { * terminal to scroll to it. * @param ev The scroll event. */ - private onScroll(ev: Event): void { + private _onScroll(ev: Event): void { // Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt // which causes the terminal to scroll the buffer to the top - if (!this.viewportElement.offsetParent) { + if (!this._viewportElement.offsetParent) { return; } - const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); - const diff = newRow - this.terminal.buffer.ydisp; - this.terminal.scrollLines(diff, true); + const newRow = Math.round(this._viewportElement.scrollTop / this._currentRowHeight); + const diff = newRow - this._terminal.buffer.ydisp; + this._terminal.scrollLines(diff, true); } /** @@ -120,11 +120,11 @@ export class Viewport implements IViewport { // Fallback to WheelEvent.DOM_DELTA_PIXEL let multiplier = 1; if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { - multiplier = this.currentRowHeight; + multiplier = this._currentRowHeight; } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { - multiplier = this.currentRowHeight * this.terminal.rows; + multiplier = this._currentRowHeight * this._terminal.rows; } - this.viewportElement.scrollTop += ev.deltaY * multiplier; + this._viewportElement.scrollTop += ev.deltaY * multiplier; // Prevent the page from scrolling when the terminal scrolls ev.preventDefault(); } @@ -134,7 +134,7 @@ export class Viewport implements IViewport { * @param ev The touch event. */ public onTouchStart(ev: TouchEvent): void { - this.lastTouchY = ev.touches[0].pageY; + this._lastTouchY = ev.touches[0].pageY; } /** @@ -142,12 +142,12 @@ export class Viewport implements IViewport { * @param ev The touch event. */ public onTouchMove(ev: TouchEvent): void { - let deltaY = this.lastTouchY - ev.touches[0].pageY; - this.lastTouchY = ev.touches[0].pageY; + let deltaY = this._lastTouchY - ev.touches[0].pageY; + this._lastTouchY = ev.touches[0].pageY; if (deltaY === 0) { return; } - this.viewportElement.scrollTop += deltaY; + this._viewportElement.scrollTop += deltaY; ev.preventDefault(); } } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 8db861d5..691f17d8 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -237,7 +237,7 @@ class CursorBlinkStateManager { constructor( terminal: ITerminal, - private renderCallback: () => void + private _renderCallback: () => void ) { this.isCursorVisible = true; if (terminal.isFocused) { @@ -272,7 +272,7 @@ class CursorBlinkStateManager { this.isCursorVisible = true; if (!this._animationFrame) { this._animationFrame = window.requestAnimationFrame(() => { - this.renderCallback(); + this._renderCallback(); this._animationFrame = null; }); } @@ -303,7 +303,7 @@ class CursorBlinkStateManager { // Hide the cursor this.isCursorVisible = false; this._animationFrame = window.requestAnimationFrame(() => { - this.renderCallback(); + this._renderCallback(); this._animationFrame = null; }); @@ -322,7 +322,7 @@ class CursorBlinkStateManager { // Invert visibility and render this.isCursorVisible = !this.isCursorVisible; this._animationFrame = window.requestAnimationFrame(() => { - this.renderCallback(); + this._renderCallback(); this._animationFrame = null; }); }, BLINK_INTERVAL); From b9cbdf6a0de068631f15edb8a8ed1c7433000f77 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 8 Mar 2018 06:19:17 -0800 Subject: [PATCH 25/33] Fix tests --- src/Terminal.integration.ts | 2 +- src/Terminal.test.ts | 4 ++-- src/Terminal.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 283c5bc0..fdf27acf 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -125,7 +125,7 @@ if (os.platform() !== 'win32') { // Perform a synchronous .write(data) xterm.writeBuffer.push(fromPty); - xterm.innerWrite(); + xterm._innerWrite(); let fromEmulator = terminalToString(xterm); console.log = CONSOLE_LOG; diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 12467d84..0db9545f 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -29,11 +29,11 @@ describe('term.js addons', () => { term.refresh = () => {}; (term).renderer = new MockRenderer(); term.viewport = new MockViewport(); - (term).compositionHelper = new MockCompositionHelper(); + (term)._compositionHelper = new MockCompositionHelper(); // Force synchronous writes term.write = (data) => { term.writeBuffer.push(data); - (term).innerWrite(); + (term)._innerWrite(); }; (term).element = { classList: { diff --git a/src/Terminal.ts b/src/Terminal.ts index 68128ff3..02dc261d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -2079,7 +2079,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Emit the 'title' event and populate the given title. * @param {string} title The title to populate in the event. */ - private _handleTitle(title: string): void { + public handleTitle(title: string): void { /** * This event is emitted when the title of the terminal is changed * from inside the terminal. The parameter is the new title. From 4443ed69c7947d3623b7eafc0faa7fb06c523399 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 8 Mar 2018 10:56:10 -0800 Subject: [PATCH 26/33] Add noUnusedLocals tsconfig flag, fix issues Two parts were commented out: - _writeStopped: This is because _xoffSentToCatchUp references this variable and the feature is sort of half implemented/disabled. - _clearChar: This is because some comments still reference this, it's part of the work needed to do better line redrawing. --- package-lock.json | 2 +- src/AccessibilityManager.ts | 2 -- src/InputHandler.ts | 2 +- src/Linkifier.test.ts | 13 +--------- src/Linkifier.ts | 5 +--- src/Parser.ts | 5 ++-- src/SelectionManager.test.ts | 5 +--- src/SelectionManager.ts | 3 +-- src/SelectionModel.test.ts | 3 --- src/Terminal.ts | 39 +++++++--------------------- src/Types.ts | 3 ++- src/handlers/AltClickHandler.ts | 3 --- src/handlers/Clipboard.test.ts | 1 - src/renderer/BaseRenderLayer.ts | 4 +-- src/renderer/CursorRenderLayer.ts | 8 +++--- src/renderer/LinkRenderLayer.ts | 7 ++--- src/renderer/Renderer.ts | 2 -- src/renderer/SelectionRenderLayer.ts | 6 ++--- src/renderer/TextRenderLayer.ts | 22 ++++++++-------- src/renderer/atlas/CharAtlas.ts | 1 - src/renderer/atlas/CharAtlasUtils.ts | 1 - src/utils/CharMeasure.test.ts | 2 +- src/utils/CharMeasure.ts | 2 +- src/utils/CircularList.test.ts | 4 --- src/utils/CircularList.ts | 1 - src/utils/TestUtils.test.ts | 3 +++ tsconfig.json | 3 ++- 27 files changed, 46 insertions(+), 106 deletions(-) diff --git a/package-lock.json b/package-lock.json index 03bbd115..59522f3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "xterm", - "version": "3.1.0-master", + "version": "3.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index cb915516..67031cb7 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -11,7 +11,6 @@ import { addDisposableListener } from './utils/Dom'; import { IDisposable } from 'xterm'; const MAX_ROWS_TO_READ = 20; -const ACTIVE_ITEM_ID_PREFIX = 'xterm-active-item-'; enum BoundaryPosition { Top, @@ -265,7 +264,6 @@ export class AccessibilityManager implements IDisposable { if (!this._terminal.renderer.dimensions.actualCellHeight) { return; } - const buffer: IBuffer = this._terminal.buffer; for (let i = 0; i < this._terminal.rows; i++) { this._refreshRowDimensions(this._rowElements[i]); } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 18338ae7..acf7af1f 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { CharData, IInputHandler, IInputHandlingTerminal, ITerminal } from './Types'; +import { CharData, IInputHandler, IInputHandlingTerminal } from './Types'; import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index ce1635a3..1aaf02c9 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -5,7 +5,7 @@ import { assert } from 'chai'; import { IMouseZoneManager, IMouseZone } from './input/Types'; -import { ILinkMatcher, LineData, ITerminal, ILinkifier, IBuffer, IBufferAccessor, IElementAccessor } from './Types'; +import { ILinkMatcher, LineData, IBufferAccessor, IElementAccessor } from './Types'; import { Linkifier } from './Linkifier'; import { MockBuffer } from './utils/TestUtils.test'; import { CircularList } from './utils/CircularList'; @@ -59,17 +59,6 @@ describe('Linkifier', () => { terminal.buffer.lines.push(stringToRow(text)); } - function assertLinkifiesEntireRow(uri: string, done: MochaDone): void { - addRow(uri); - linkifier.linkifyRows(); - setTimeout(() => { - assert.equal(mouseZoneManager.zones[0].x1, 1); - assert.equal(mouseZoneManager.zones[0].x2, uri.length + 1); - assert.equal(mouseZoneManager.zones[0].y, terminal.buffer.lines.length); - done(); - }, 0); - } - function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void { addRow(rowText); linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 92a6ca9f..8dc27cc2 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -4,7 +4,7 @@ */ import { IMouseZoneManager } from './input/Types'; -import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEventTypes, ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Types'; +import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, IBufferAccessor, ILinkifier, IElementAccessor } from './Types'; import { MouseZone } from './input/MouseZoneManager'; import { EventEmitter } from './EventEmitter'; @@ -177,9 +177,6 @@ export class Linkifier extends EventEmitter implements ILinkifier { * @return The link element(s) that were added. */ private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher, offset: number = 0): void { - // Iterate over nodes as we want to consider text nodes - let result = []; - // Find the first match let match = text.match(matcher.regex); if (!match || match.length === 0) { diff --git a/src/Parser.ts b/src/Parser.ts index 03b4a39e..21e5a612 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -5,7 +5,7 @@ */ import { C0 } from './EscapeSequences'; -import { IInputHandler } from './Types'; +import { IInputHandler, IInputHandlingTerminal } from './Types'; import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {}; @@ -185,7 +185,6 @@ export class Parser { */ public parse(data: string): ParserState { const l = data.length; - let j; let cs; let ch; let code; @@ -346,7 +345,7 @@ export class Parser { // ESC H Tab Set (HTS is 0x88). case 'H': - this._terminal.tabSet(); + (this._terminal).tabSet(); this._state = ParserState.NORMAL; break; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index a76947e5..4738bc02 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -6,11 +6,10 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; import { CharMeasure } from './utils/CharMeasure'; -import { CircularList } from './utils/CircularList'; import { SelectionManager } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { LineData, CharData, ITerminal, ICircularList, IBuffer } from './Types'; +import { LineData, CharData, ITerminal, IBuffer } from './Types'; import { MockTerminal } from './utils/TestUtils.test'; class TestMockTerminal extends MockTerminal { @@ -39,11 +38,9 @@ class TestSelectionManager extends SelectionManager { describe('SelectionManager', () => { let dom: jsdom.JSDOM; let window: Window; - let document: Document; let terminal: ITerminal; let buffer: IBuffer; - let rowContainer: HTMLElement; let selectionManager: TestSelectionManager; beforeEach(() => { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 93957fa8..506e87d4 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -3,11 +3,10 @@ * @license MIT */ -import { ITerminal, ICircularList, ISelectionManager, IBuffer, LineData, CharData, XtermListener } from './Types'; +import { ITerminal, ISelectionManager, IBuffer, CharData, XtermListener } from './Types'; import { MouseHelper } from './utils/MouseHelper'; import * as Browser from './shared/utils/Browser'; import { CharMeasure } from './utils/CharMeasure'; -import { CircularList } from './utils/CircularList'; import { EventEmitter } from './EventEmitter'; import { SelectionModel } from './SelectionModel'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index ed483dbe..85486bab 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -18,9 +18,6 @@ class TestSelectionModel extends SelectionModel { } describe('SelectionManager', () => { - let window: Window; - let document: Document; - let terminal: ITerminal; let model: TestSelectionModel; diff --git a/src/Terminal.ts b/src/Terminal.ts index 735711d0..924260ca 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; +import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData } from './Types'; import { IMouseZoneManager } from './input/Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; @@ -30,7 +30,6 @@ import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './EventEmitter'; import { Viewport } from './Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard'; -import { CircularList } from './utils/CircularList'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; import { Parser } from './Parser'; @@ -41,7 +40,6 @@ import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './shared/utils/Browser'; import * as Strings from './Strings'; import { MouseHelper } from './utils/MouseHelper'; -import { CHARSETS } from './Charsets'; import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { MouseZoneManager } from './input/MouseZoneManager'; @@ -136,7 +134,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private _parent: HTMLElement; private _context: Window; private _document: Document; - private _body: HTMLBodyElement; private _viewportScrollArea: HTMLElement; private _viewportElement: HTMLElement; private _helperContainer: HTMLElement; @@ -148,7 +145,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public browser: IBrowser = Browser; public options: ITerminalOptions; - private _colors: any; // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options public cursorState: number; @@ -190,10 +186,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private _refreshEnd: number; public savedCols: number; - // stream - private _readable: boolean; - private _writable: boolean; - public defAttr: number; public curAttr: number; @@ -215,10 +207,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private _xoffSentToCatchUp: boolean; /** Whether writing has been stopped as a result of XOFF */ - private _writeStopped: boolean; - - // leftover surrogate high from previous write invocation - private _surrogateHigh: string; + // private _writeStopped: boolean; // Store if user went browsing history in scrollback private _userScrolling: boolean; @@ -302,9 +291,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // TODO: Can this be just []? this.charsets = [null]; - this._readable = true; - this._writable = true; - this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); this.curAttr = (0 << 18) | (257 << 9) | (256 << 0); @@ -318,8 +304,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this._writeInProgress = false; this._xoffSentToCatchUp = false; - this._writeStopped = false; - this._surrogateHigh = ''; + // this._writeStopped = false; this._userScrolling = false; this._inputHandler = new InputHandler(this); @@ -622,9 +607,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {HTMLElement} parent The element to create the terminal within. */ public open(parent: HTMLElement): void { - let i = 0; - let div; - this._parent = parent || this._parent; if (!this._parent) { @@ -634,7 +616,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Grab global elements this._context = this._parent.ownerDocument.defaultView; this._document = this._parent.ownerDocument; - this._body = this._document.body; this._screenDprMonitor = new ScreenDprMonitor(); this._screenDprMonitor.setListener(() => this.emit('dprchange', window.devicePixelRatio)); @@ -1104,8 +1085,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public destroy(): void { super.destroy(); - this._readable = false; - this._writable = false; this.handler = () => {}; this.write = () => {}; if (this.element && this.element.parentNode) { @@ -1423,11 +1402,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT const result = this._evaluateKeyEscapeSequence(ev); - if (result.key === C0.DC3) { // XOFF - this._writeStopped = true; - } else if (result.key === C0.DC1) { // XON - this._writeStopped = false; - } + // if (result.key === C0.DC3) { // XOFF + // this._writeStopped = true; + // } else if (result.key === C0.DC1) { // XON + // this._writeStopped = false; + // } if (result.scrollLines) { this.scrollLines(result.scrollLines); @@ -2167,7 +2146,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * ESC H Tab Set (HTS is 0x88). */ - private tabSet(): void { + public tabSet(): void { this.buffer.tabs[this.buffer.x] = true; } diff --git a/src/Types.ts b/src/Types.ts index 2751d266..8f0cf380 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter as IPublicEventEmitter, IEventEmitter } from 'xterm'; +import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; import { IMouseZoneManager } from './input/Types'; @@ -84,6 +84,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { matchColor(r1: number, g1: number, b1: number): number; error(text: string, data?: any): void; setOption(key: string, value: any): void; + tabSet(): void; } export interface IViewport { diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 7af32ee6..f77637ea 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -71,9 +71,6 @@ export class AltClickHandler { * positioning. */ private _resetStartingRow(): string { - let startRow = this._endRow - this._wrappedRowsForRow(this._endRow); - let endRow = this._endRow; - if (this._moveToRequestedRow().length === 0) { return ''; } else { diff --git a/src/handlers/Clipboard.test.ts b/src/handlers/Clipboard.test.ts index 174f6efb..07c0f66e 100644 --- a/src/handlers/Clipboard.test.ts +++ b/src/handlers/Clipboard.test.ts @@ -4,7 +4,6 @@ */ import { assert } from 'chai'; -import * as Terminal from '../Terminal'; import * as Clipboard from './Clipboard'; describe('evaluatePastedTextProcessing', () => { diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 4d626e4b..281b3ee9 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -4,11 +4,11 @@ */ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; -import { CharData, ITerminal, ITerminalOptions } from '../Types'; +import { CharData, ITerminal } from '../Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { CHAR_ATLAS_CELL_SPACING } from '../shared/atlas/Types'; import { acquireCharAtlas } from './atlas/CharAtlas'; -import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; +import { CHAR_DATA_CHAR_INDEX } from '../Buffer'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 691f17d8..bfd215ad 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -3,11 +3,10 @@ * @license MIT */ -import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; -import { GridCache } from './GridCache'; -import { FLAGS, IColorSet, IRenderDimensions } from './Types'; +import { CHAR_DATA_WIDTH_INDEX } from '../Buffer'; +import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { CharData, IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Types'; +import { CharData, ITerminal } from '../Types'; interface ICursorState { x: number; @@ -26,7 +25,6 @@ export class CursorRenderLayer extends BaseRenderLayer { private _state: ICursorState; private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; - private _isFocused: boolean; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { super(container, 'cursor', zIndex, true, colors); diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 10a33d11..f94a47f8 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -3,11 +3,8 @@ * @license MIT */ -import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, IBuffer, ICharMeasure, LinkHoverEventTypes } from '../Types'; -import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; -import { GridCache } from './GridCache'; -import { FLAGS, IColorSet, IRenderDimensions } from './Types'; -import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } from '../Types'; +import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; export class LinkRenderLayer extends BaseRenderLayer { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index db639c66..1ce2c9b4 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -3,12 +3,10 @@ * @license MIT */ -import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { TextRenderLayer } from './TextRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; import { ColorManager } from './ColorManager'; -import { BaseRenderLayer } from './BaseRenderLayer'; import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Types'; import { ITerminal } from '../Types'; import { LinkRenderLayer } from './LinkRenderLayer'; diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 7a5557fa..7a6a5af8 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -3,10 +3,8 @@ * @license MIT */ -import { IBuffer, ICharMeasure, ITerminal } from '../Types'; -import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; -import { GridCache } from './GridCache'; -import { FLAGS, IColorSet, IRenderDimensions } from './Types'; +import { ITerminal } from '../Types'; +import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; export class SelectionRenderLayer extends BaseRenderLayer { diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index df65ef8b..27ce9f79 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -5,7 +5,7 @@ import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { FLAGS, IColorSet, IRenderDimensions } from './Types'; -import { CharData, IBuffer, ICharMeasure, ITerminal } from '../Types'; +import { CharData, ITerminal } from '../Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; @@ -15,7 +15,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; * when the character changes (a regular space ' ' character may not as it's * drawn state is a cleared cell). */ -const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1]; +// const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1]; export class TextRenderLayer extends BaseRenderLayer { private _state: GridCache; @@ -239,13 +239,13 @@ export class TextRenderLayer extends BaseRenderLayer { * @param x The column of the char. * @param y The row of the char. */ - private _clearChar(x: number, y: number): void { - let colsToClear = 1; - // Clear the adjacent character if it was wide - const state = this._state.cache[x][y]; - if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) { - colsToClear = 2; - } - this.clearCells(x, y, colsToClear, 1); - } + // private _clearChar(x: number, y: number): void { + // let colsToClear = 1; + // // Clear the adjacent character if it was wide + // const state = this._state.cache[x][y]; + // if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) { + // colsToClear = 2; + // } + // this.clearCells(x, y, colsToClear, 1); + // } } diff --git a/src/renderer/atlas/CharAtlas.ts b/src/renderer/atlas/CharAtlas.ts index d688d328..8c529242 100644 --- a/src/renderer/atlas/CharAtlas.ts +++ b/src/renderer/atlas/CharAtlas.ts @@ -6,7 +6,6 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; import { ICharAtlasConfig } from './Types'; -import { isFirefox } from '../../shared/utils/Browser'; import { generateCharAtlas, ICharAtlasRequest } from '../../shared/atlas/CharAtlasGenerator'; import { generateConfig, configEquals } from './CharAtlasUtils'; diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index 57e362af..13a75dc1 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -4,7 +4,6 @@ */ import { ITerminal } from '../../Types'; -import { ITheme } from 'xterm'; import { IColorSet } from '../Types'; import { ICharAtlasConfig } from './Types'; diff --git a/src/utils/CharMeasure.test.ts b/src/utils/CharMeasure.test.ts index e4f4e1d6..a3cb3b3b 100644 --- a/src/utils/CharMeasure.test.ts +++ b/src/utils/CharMeasure.test.ts @@ -4,7 +4,7 @@ */ import jsdom = require('jsdom'); -import { ICharMeasure, ITerminal } from '../Types'; +import { ICharMeasure } from '../Types'; import { assert } from 'chai'; import { CharMeasure } from './CharMeasure'; diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts index 91cfce5f..b9f267e8 100644 --- a/src/utils/CharMeasure.ts +++ b/src/utils/CharMeasure.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ICharMeasure, ITerminal, ITerminalOptions } from '../Types'; +import { ICharMeasure, ITerminalOptions } from '../Types'; import { EventEmitter } from '../EventEmitter'; /** diff --git a/src/utils/CircularList.test.ts b/src/utils/CircularList.test.ts index 6bb35113..4c07b16c 100644 --- a/src/utils/CircularList.test.ts +++ b/src/utils/CircularList.test.ts @@ -6,10 +6,6 @@ import { assert } from 'chai'; import { CircularList } from './CircularList'; -class TestCircularList extends CircularList { - public get array(): T[] { return this._array; } -} - describe('CircularList', () => { describe('push', () => { it('should push values onto the array', () => { diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts index 6b74971b..7fc5dcbf 100644 --- a/src/utils/CircularList.ts +++ b/src/utils/CircularList.ts @@ -60,7 +60,6 @@ export class CircularList extends EventEmitter implements ICircularList { public get forEach(): (callbackfn: (value: T, index: number) => void) => void { return (callbackfn: (value: T, index: number) => void) => { - let i = 0; let length = this.length; for (let i = 0; i < length; i++) { callbackfn(this.get(i), i); diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index c1ab2115..3c60d695 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -269,6 +269,9 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { addDisposableListener(type: string, handler: XtermListener): IDisposable { throw new Error('Method not implemented.'); } + tabSet(): void { + throw new Error('Method not implemented.'); + } } export class MockBuffer implements IBuffer { diff --git a/tsconfig.json b/tsconfig.json index 38bfd2fa..ffd9ab68 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "rootDir": "src", "outDir": "lib", "sourceMap": true, - "removeComments": true + "removeComments": true, + "noUnusedLocals": true }, "include": [ "src/**/*" From b794dc017ddb304f830a5b770b6ebecb97ac555d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 8 Mar 2018 11:08:51 -0800 Subject: [PATCH 27/33] Fix test --- src/SelectionManager.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 4738bc02..3dae2c31 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -3,7 +3,6 @@ * @license MIT */ -import jsdom = require('jsdom'); import { assert } from 'chai'; import { CharMeasure } from './utils/CharMeasure'; import { SelectionManager } from './SelectionManager'; @@ -36,17 +35,11 @@ class TestSelectionManager extends SelectionManager { } describe('SelectionManager', () => { - let dom: jsdom.JSDOM; - let window: Window; - let terminal: ITerminal; let buffer: IBuffer; let selectionManager: TestSelectionManager; beforeEach(() => { - dom = new jsdom.JSDOM(''); - window = dom.window; - document = window.document; terminal = new TestMockTerminal(); terminal.cols = 80; terminal.rows = 2; From cfe936a0b486c32735fbc6edd50d7b80b88102d4 Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Wed, 7 Mar 2018 23:15:37 -0800 Subject: [PATCH 28/33] Merge ICharAtlasRequest with ICharAtlasConfig ICharAtlasRequest and ICharAtlasConfig need almost exactly the same set of information, so it's simpler if we just merge the two types. As an added bonus, this also adds devicePixelRatio to the config, which helps guarantee that we won't ever accidentally end up with an atlas using a different pixel ratio than we need. --- src/renderer/Types.ts | 11 ++---- src/renderer/atlas/CharAtlas.ts | 20 ++--------- src/renderer/atlas/CharAtlasUtils.ts | 6 ++-- src/renderer/atlas/Types.ts | 14 -------- src/shared/Types.ts | 13 +++++++ src/shared/atlas/CharAtlasGenerator.ts | 48 +++++++++----------------- src/shared/atlas/Types.ts | 15 ++++++++ 7 files changed, 55 insertions(+), 72 deletions(-) create mode 100644 src/shared/Types.ts diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 1885d059..8c464bec 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -5,6 +5,7 @@ import { ITerminal } from '../Types'; import { IEventEmitter, ITheme } from 'xterm'; +import { IColorSet } from '../shared/Types'; /** * Flags used to render terminal text properly. @@ -39,14 +40,8 @@ export interface IColorManager { colors: IColorSet; } -export interface IColorSet { - foreground: string; - background: string; - cursor: string; - cursorAccent: string; - selection: string; - ansi: string[]; -} +// TODO: We should probably rewrite the imports for IColorSet, but there's a lot of them +export { IColorSet }; export interface IRenderDimensions { scaledCharWidth: number; diff --git a/src/renderer/atlas/CharAtlas.ts b/src/renderer/atlas/CharAtlas.ts index d688d328..2ad6c6fc 100644 --- a/src/renderer/atlas/CharAtlas.ts +++ b/src/renderer/atlas/CharAtlas.ts @@ -5,9 +5,9 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; -import { ICharAtlasConfig } from './Types'; +import { ICharAtlasConfig } from '../../shared/atlas/Types'; import { isFirefox } from '../../shared/utils/Browser'; -import { generateCharAtlas, ICharAtlasRequest } from '../../shared/atlas/CharAtlasGenerator'; +import { generateCharAtlas } from '../../shared/atlas/CharAtlasGenerator'; import { generateConfig, configEquals } from './CharAtlasUtils'; interface ICharAtlasCacheEntry { @@ -63,22 +63,8 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC return canvas; }; - const charAtlasConfig: ICharAtlasRequest = { - scaledCharWidth, - scaledCharHeight, - fontSize: terminal.options.fontSize, - fontFamily: terminal.options.fontFamily, - fontWeight: terminal.options.fontWeight, - fontWeightBold: terminal.options.fontWeightBold, - background: colors.background, - foreground: colors.foreground, - ansiColors: colors.ansi, - devicePixelRatio: window.devicePixelRatio, - allowTransparency: terminal.options.allowTransparency - }; - const newEntry: ICharAtlasCacheEntry = { - bitmap: generateCharAtlas(window, canvasFactory, charAtlasConfig), + bitmap: generateCharAtlas(window, canvasFactory, newConfig), config: newConfig, ownedBy: [terminal] }; diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index 57e362af..5b9a2838 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -6,7 +6,7 @@ import { ITerminal } from '../../Types'; import { ITheme } from 'xterm'; import { IColorSet } from '../Types'; -import { ICharAtlasConfig } from './Types'; +import { ICharAtlasConfig } from '../../shared/atlas/Types'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { const clonedColors = { @@ -18,6 +18,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number ansi: colors.ansi.slice(0, 16) }; return { + devicePixelRatio: window.devicePixelRatio, scaledCharWidth, scaledCharHeight, fontFamily: terminal.options.fontFamily, @@ -35,7 +36,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean return false; } } - return a.fontFamily === b.fontFamily && + return a.devicePixelRatio === b.devicePixelRatio && + a.fontFamily === b.fontFamily && a.fontSize === b.fontSize && a.fontWeight === b.fontWeight && a.fontWeightBold === b.fontWeightBold && diff --git a/src/renderer/atlas/Types.ts b/src/renderer/atlas/Types.ts index a79cb327..34f01d39 100644 --- a/src/renderer/atlas/Types.ts +++ b/src/renderer/atlas/Types.ts @@ -3,19 +3,5 @@ * @license MIT */ -import { FontWeight } from 'xterm'; -import { IColorSet } from '../Types'; - export const INVERTED_DEFAULT_COLOR = -1; export const DIM_OPACITY = 0.5; - -export interface ICharAtlasConfig { - fontSize: number; - fontFamily: string; - fontWeight: FontWeight; - fontWeightBold: FontWeight; - scaledCharWidth: number; - scaledCharHeight: number; - allowTransparency: boolean; - colors: IColorSet; -} diff --git a/src/shared/Types.ts b/src/shared/Types.ts new file mode 100644 index 00000000..0407c2ef --- /dev/null +++ b/src/shared/Types.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export interface IColorSet { + foreground: string; + background: string; + cursor: string; + cursorAccent: string; + selection: string; + ansi: string[]; +} diff --git a/src/shared/atlas/CharAtlasGenerator.ts b/src/shared/atlas/CharAtlasGenerator.ts index cf17bbb8..10112efa 100644 --- a/src/shared/atlas/CharAtlasGenerator.ts +++ b/src/shared/atlas/CharAtlasGenerator.ts @@ -4,7 +4,7 @@ */ import { FontWeight } from 'xterm'; -import { CHAR_ATLAS_CELL_SPACING } from './Types'; +import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from './Types'; import { isFirefox } from '../utils/Browser'; declare const Promise: any; @@ -16,41 +16,27 @@ export interface IOffscreenCanvas { transferToImageBitmap(): ImageBitmap; } -export interface ICharAtlasRequest { - scaledCharWidth: number; - scaledCharHeight: number; - fontSize: number; - fontFamily: string; - fontWeight: FontWeight; - fontWeightBold: FontWeight; - background: string; - foreground: string; - ansiColors: string[]; - devicePixelRatio: number; - allowTransparency: boolean; -} - /** * Generates a char atlas. * @param context The window or worker context. * @param canvasFactory A function to generate a canvas with a width or height. * @param request The config for the new char atlas. */ -export function generateCharAtlas(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, request: ICharAtlasRequest): HTMLCanvasElement | Promise { - const cellWidth = request.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; - const cellHeight = request.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; +export function generateCharAtlas(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, config: ICharAtlasConfig): HTMLCanvasElement | Promise { + const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; + const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; const canvas = canvasFactory( /*255 ascii chars*/255 * cellWidth, (/*default+default bold*/2 + /*0-15*/16) * cellHeight ); - const ctx = canvas.getContext('2d', {alpha: request.allowTransparency}); + const ctx = canvas.getContext('2d', {alpha: config.allowTransparency}); - ctx.fillStyle = request.background; + ctx.fillStyle = config.colors.background; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.save(); - ctx.fillStyle = request.foreground; - ctx.font = getFont(request.fontWeight, request); + ctx.fillStyle = config.colors.foreground; + ctx.font = getFont(config.fontWeight, config); ctx.textBaseline = 'top'; // Default color @@ -64,7 +50,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number } // Default color bold ctx.save(); - ctx.font = getFont(request.fontWeightBold, request); + ctx.font = getFont(config.fontWeightBold, config); for (let i = 0; i < 256; i++) { ctx.save(); ctx.beginPath(); @@ -76,11 +62,11 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number ctx.restore(); // Colors 0-15 - ctx.font = getFont(request.fontWeight, request); + ctx.font = getFont(config.fontWeight, config); for (let colorIndex = 0; colorIndex < 16; colorIndex++) { // colors 8-15 are bold if (colorIndex === 8) { - ctx.font = getFont(request.fontWeightBold, request); + ctx.font = getFont(config.fontWeightBold, config); } const y = (colorIndex + 2) * cellHeight; // Draw ascii characters @@ -89,7 +75,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number ctx.beginPath(); ctx.rect(i * cellWidth, y, cellWidth, cellHeight); ctx.clip(); - ctx.fillStyle = request.ansiColors[colorIndex]; + ctx.fillStyle = config.colors.ansi[colorIndex]; ctx.fillText(String.fromCharCode(i), i * cellWidth, y); ctx.restore(); } @@ -114,9 +100,9 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); // Remove the background color from the image so characters may overlap - const r = parseInt(request.background.substr(1, 2), 16); - const g = parseInt(request.background.substr(3, 2), 16); - const b = parseInt(request.background.substr(5, 2), 16); + const r = parseInt(config.colors.background.substr(1, 2), 16); + const g = parseInt(config.colors.background.substr(3, 2), 16); + const b = parseInt(config.colors.background.substr(5, 2), 16); clearColor(charAtlasImageData, r, g, b); return context.createImageBitmap(charAtlasImageData); @@ -135,6 +121,6 @@ function clearColor(imageData: ImageData, r: number, g: number, b: number): void } } -function getFont(fontWeight: FontWeight, request: ICharAtlasRequest): string { - return `${fontWeight} ${request.fontSize * request.devicePixelRatio}px ${request.fontFamily}`; +function getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string { + return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`; } diff --git a/src/shared/atlas/Types.ts b/src/shared/atlas/Types.ts index e8bb6b0a..4a66d554 100644 --- a/src/shared/atlas/Types.ts +++ b/src/shared/atlas/Types.ts @@ -3,4 +3,19 @@ * @license MIT */ +import { FontWeight } from 'xterm'; +import { IColorSet } from '../Types'; + export const CHAR_ATLAS_CELL_SPACING = 1; + +export interface ICharAtlasConfig { + devicePixelRatio: number; + fontSize: number; + fontFamily: string; + fontWeight: FontWeight; + fontWeightBold: FontWeight; + scaledCharWidth: number; + scaledCharHeight: number; + allowTransparency: boolean; + colors: IColorSet; +} From efb67edc36d4f3baa2bc5bd6901dba745a555dc7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 9 Mar 2018 07:11:53 -0800 Subject: [PATCH 29/33] Remove unused CSS rule With #1316, this will resolve Microsoft/vscode#45145 --- src/xterm.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/xterm.css b/src/xterm.css index 16eb283e..3d2e9b62 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -144,10 +144,6 @@ color: transparent; } -.xterm .xterm-accessibility-tree:focus [id^="xterm-active-item-"] { - outline: 1px solid #F80; -} - .xterm .live-region { position: absolute; left: -9999px; From 4f0cc01485247f3376cfab8d2dbe3626202ecc90 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 9 Mar 2018 11:23:45 -0800 Subject: [PATCH 30/33] Fix strictNullChecks errors in AccessibilityManager Part of #1319 --- src/AccessibilityManager.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 67031cb7..1df6a7d2 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -20,7 +20,7 @@ enum BoundaryPosition { export class AccessibilityManager implements IDisposable { private _accessibilityTreeRoot: HTMLElement; private _rowContainer: HTMLElement; - private _rowElements: HTMLElement[] = []; + private _rowElements: HTMLElement[]; private _liveRegion: HTMLElement; private _liveRegionLineCount: number = 0; @@ -48,6 +48,7 @@ export class AccessibilityManager implements IDisposable { this._rowContainer = document.createElement('div'); this._rowContainer.classList.add('xterm-accessibility-tree'); + this._rowElements = []; for (let i = 0; i < this._terminal.rows; i++) { this._rowElements[i] = this._createAccessibilityTreeNode(); this._rowContainer.appendChild(this._rowElements[i]); @@ -92,14 +93,10 @@ export class AccessibilityManager implements IDisposable { } public dispose(): void { - this._terminal.element.removeChild(this._accessibilityTreeRoot); this._disposables.forEach(d => d.dispose()); - this._disposables = null; - this._accessibilityTreeRoot = null; - this._rowContainer = null; - this._liveRegion = null; - this._rowContainer = null; - this._rowElements = null; + this._disposables.length = 0; + this._terminal.element.removeChild(this._accessibilityTreeRoot); + this._rowElements.length = 0; } private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { @@ -124,10 +121,10 @@ export class AccessibilityManager implements IDisposable { let bottomBoundaryElement: HTMLElement; if (position === BoundaryPosition.Top) { topBoundaryElement = boundaryElement; - bottomBoundaryElement = this._rowElements.pop(); + bottomBoundaryElement = this._rowElements.pop(); this._rowContainer.removeChild(bottomBoundaryElement); } else { - topBoundaryElement = this._rowElements.shift(); + topBoundaryElement = this._rowElements.shift(); bottomBoundaryElement = boundaryElement; this._rowContainer.removeChild(topBoundaryElement); } @@ -173,7 +170,7 @@ export class AccessibilityManager implements IDisposable { } // Shrink rows as required while (this._rowElements.length > rows) { - this._rowContainer.removeChild(this._rowElements.pop()); + this._rowContainer.removeChild(this._rowElements.pop()); } // Add bottom boundary listener @@ -217,7 +214,7 @@ export class AccessibilityManager implements IDisposable { // Only detach/attach on mac as otherwise messages can go unaccounced if (isMac) { - if (this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { + if (this._liveRegion.textContent && this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { setTimeout(() => { this._accessibilityTreeRoot.appendChild(this._liveRegion); }, 0); From 2f861602c8b2eb3ffedbe85eb3375aab65e661c9 Mon Sep 17 00:00:00 2001 From: Felix <30559812+felixse@users.noreply.github.com> Date: Sat, 10 Mar 2018 23:27:59 +0100 Subject: [PATCH 31/33] Add Fluent Terminal to the list of real-world uses --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a2a50372..26f98382 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ computational environment for Jupyter, supporting interactive data science and s - [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker. - [**Microsoft SQL Operations Studio**](https://github.com/Microsoft/sqlopsstudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux - [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users +- [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies. Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. From 9905e2aa1de32ce684f03a6f85c082b5509e67cf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 13 Mar 2018 11:19:51 -0700 Subject: [PATCH 32/33] Add note about third party dependencies Fixes #1328 --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b33d183..4aebdf20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,3 +50,7 @@ By contributing code to xterm.js you holder has explicitly granted the right to use it like this, through a compatible open source license or through a direct agreement with you.) + +### Third party dependencies + +We prefer to not include any non-dev third party dependencies in order to keep our code minimal, performant and secure. If you plan on adding a dependency on a third party library it's a good idea to discuss the need in an issue with the maintainers first. From b67b65cd1e16e1f2b014e1b33bc9f2b4a4b6374f Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 16 Mar 2018 13:42:32 +0100 Subject: [PATCH 33/33] Replace inline-styles by external CSS Selected inline-styles were externalized to xterm.css. The `Terminal._charSizeStyleElement` is removed since it was not used anymore. Fixes: https://github.com/xtermjs/xterm.js/issues/1335 --- src/Linkifier.ts | 4 ++-- src/Terminal.ts | 5 +---- src/utils/CharMeasure.ts | 8 ++------ src/xterm.css | 8 +++++++- typings/xterm.d.ts | 3 ++- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 8dc27cc2..93eb9063 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -230,7 +230,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { }, e => { this.emit(LinkHoverEventTypes.HOVER, { x, y, length: uri.length}); - this._terminal.element.style.cursor = 'pointer'; + this._terminal.element.classList.add('xterm-cursor-pointer'); }, e => { this.emit(LinkHoverEventTypes.TOOLTIP, { x, y, length: uri.length}); @@ -240,7 +240,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { }, () => { this.emit(LinkHoverEventTypes.LEAVE, { x, y, length: uri.length}); - this._terminal.element.style.cursor = ''; + this._terminal.element.classList.remove('xterm-cursor-pointer'); if (matcher.hoverLeaveCallback) { matcher.hoverLeaveCallback(); } diff --git a/src/Terminal.ts b/src/Terminal.ts index 924260ca..a0d6ed6f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -118,7 +118,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { allowTransparency: false, tabStopWidth: 8, theme: null, - rightClickSelectsWord: Browser.isMac + rightClickSelectsWord: Browser.isMac, // programFeatures: false, // focusKeys: false, }; @@ -138,7 +138,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private _viewportElement: HTMLElement; private _helperContainer: HTMLElement; private _compositionView: HTMLElement; - private _charSizeStyleElement: HTMLStyleElement; private _visualBellTimer: number; @@ -668,8 +667,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this); this._helperContainer.appendChild(this._compositionView); - this._charSizeStyleElement = document.createElement('style'); - this._helperContainer.appendChild(this._charSizeStyleElement); this.charMeasure = new CharMeasure(document, this._helperContainer); // Performance: Add viewport and helper elements from the fragment diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts index b9f267e8..5ad1de76 100644 --- a/src/utils/CharMeasure.ts +++ b/src/utils/CharMeasure.ts @@ -23,10 +23,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure { this._document = document; this._parentElement = parentElement; this._measureElement = this._document.createElement('span'); - this._measureElement.style.position = 'absolute'; - this._measureElement.style.top = '0'; - this._measureElement.style.left = '-9999em'; - this._measureElement.style.lineHeight = 'normal'; + this._measureElement.classList.add('xterm-char-measure-element'); this._measureElement.textContent = 'W'; this._measureElement.setAttribute('aria-hidden', 'true'); this._parentElement.appendChild(this._measureElement); @@ -41,7 +38,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure { } public measure(options: ITerminalOptions): void { - this._measureElement.style.fontFamily = options.fontFamily; + this._measureElement.style.fontFamily = options.fontFamily; this._measureElement.style.fontSize = `${options.fontSize}px`; const geometry = this._measureElement.getBoundingClientRect(); // The element is likely currently display:none, we should retain the @@ -55,5 +52,4 @@ export class CharMeasure extends EventEmitter implements ICharMeasure { this.emit('charsizechanged'); } } - } diff --git a/src/xterm.css b/src/xterm.css index 3d2e9b62..eec41a05 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -117,11 +117,13 @@ visibility: hidden; } -.xterm .xterm-char-measure-element { +.xterm-char-measure-element { display: inline-block; visibility: hidden; position: absolute; + top: 0; left: -9999em; + line-height: normal; } .xterm.enable-mouse-events { @@ -151,3 +153,7 @@ height: 1px; overflow: hidden; } + +.xterm-cursor-pointer { + cursor: pointer; +} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0e8fdcc7..1e5999c9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -23,6 +23,7 @@ declare module 'xterm' { * Warning: Enabling this option can reduce performances somewhat. */ allowTransparency?: boolean; + /** * A data uri of the sound to use for the bell (needs bellStyle = 'sound'). */ @@ -55,7 +56,7 @@ declare module 'xterm' { /** * Whether to enable the rendering of bold text. - * + * * @deprecated Use fontWeight and fontWeightBold instead. */ enableBold?: boolean;