diff --git a/AUTHORS b/AUTHORS index 99f2bc59..482e0309 100644 --- a/AUTHORS +++ b/AUTHORS @@ -61,6 +61,7 @@ irokas Jakob Gillich Jan Kuri Jean Bruenn +Jeff Principe Jeremy Danyow Jianhui Zhao Joao Moreno @@ -70,6 +71,7 @@ Jon Austin Jon Kohler Jon Masters Jörg Breitbart +Jose Anton jpoth Justin Luk Justin Mecham @@ -103,6 +105,7 @@ Peng Xiao Peter Baumgarten Philip Olson PowerHat <31401273+7PH@users.noreply.github.com> +PowerHat pro-src <34285059+pro-src@users.noreply.github.com> pro-src Rick Baker @@ -111,6 +114,7 @@ Saad Malik Samuel Williams Saswat Das Saul Costa +Segev Finer Shuanglei Tao sitzmar Steven Silvester diff --git a/LICENSE b/LICENSE index 648a90ec..28adbdad 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ +Copyright (c) 2017-2018, The xterm.js authors (https://github.com/xtermjs/xterm.js) Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com) Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/) diff --git a/README.md b/README.md index 512f4dce..583faf7f 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ computational environment for Jupyter, supporting interactive data science and s - [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice. - [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js - [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP. +- [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere. [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) diff --git a/fixtures/typings-test/tsconfig.json b/fixtures/typings-test/tsconfig.json index 2dcff2ea..699a055a 100644 --- a/fixtures/typings-test/tsconfig.json +++ b/fixtures/typings-test/tsconfig.json @@ -4,6 +4,7 @@ ], "compilerOptions": { "module": "commonjs", - "target": "es5" + "target": "es5", + "noEmit": true } } diff --git a/package.json b/package.json index 1d35405f..db566c68 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "3.5.0", + "version": "3.6.0", "main": "lib/public/Terminal.js", "types": "typings/xterm.d.ts", "repository": "https://github.com/xtermjs/xterm.js", diff --git a/src/Buffer.ts b/src/Buffer.ts index 5d45645f..5c843808 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -283,7 +283,7 @@ export class Buffer implements IBuffer { * @param i The index to start setting up tab stops from. */ public setupTabStops(i?: number): void { - if (i != null) { + if (i !== null && i !== undefined) { if (!this.tabs[i]) { i = this.prevStop(i); } @@ -302,7 +302,7 @@ export class Buffer implements IBuffer { * @param x The position to move the cursor to the previous tab stop. */ public prevStop(x?: number): number { - if (x == null) { + if (x === null || x === undefined) { x = this.x; } while (!this.tabs[--x] && x > 0); @@ -314,7 +314,7 @@ export class Buffer implements IBuffer { * @param x The position to move the cursor one tab stop forward. */ public nextStop(x?: number): number { - if (x == null) { + if (x === null || x === undefined) { x = this.x; } while (!this.tabs[++x] && x < this._terminal.cols); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 0419c318..42b1b095 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1631,6 +1631,9 @@ export class InputHandler extends Disposable implements IInputHandler { // not bold nor faint flags &= ~FLAGS.BOLD; flags &= ~FLAGS.DIM; + } else if (p === 23) { + // not italic + flags &= ~FLAGS.ITALIC; } else if (p === 24) { // not underlined flags &= ~FLAGS.UNDERLINE; diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 9ab770d9..32504cc2 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -7,6 +7,7 @@ import { IMouseZoneManager } from './ui/Types'; import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, LineData } from './Types'; import { MouseZone } from './ui/MouseZoneManager'; import { EventEmitter } from './EventEmitter'; +import { CHAR_DATA_ATTR_INDEX } from './Buffer'; /** * The Linkifier applies links to rows shortly after they have been refreshed. @@ -24,7 +25,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { private _mouseZoneManager: IMouseZoneManager; private _rowsTimeoutId: number; private _nextLinkMatcherId = 0; - private _rowsToLinkify: {start: number, end: number}; + private _rowsToLinkify: { start: number, end: number }; constructor( protected _terminal: ITerminal @@ -187,7 +188,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { let text = this._terminal.buffer.translateBufferLineToString(absoluteRowIndex, false); let currentIndex = absoluteRowIndex + 1; while (currentIndex < this._terminal.buffer.lines.length && - (this._terminal.buffer.lines.get(currentIndex)).isWrapped) { + (this._terminal.buffer.lines.get(currentIndex)).isWrapped) { text += this._terminal.buffer.translateBufferLineToString(currentIndex++, false); } @@ -216,6 +217,12 @@ export class Linkifier extends EventEmitter implements ILinkifier { // Get index, match.index is for the outer match which includes negated chars const index = text.indexOf(uri); + // Get cell color + const line = this._terminal.buffer.lines.get(this._terminal.buffer.ydisp + rowIndex); + const char = line[index]; + const attr: number = char[CHAR_DATA_ATTR_INDEX]; + const fg = (attr >> 9) & 0x1ff; + // Ensure the link is valid before registering if (matcher.validationCallback) { matcher.validationCallback(uri, isValid => { @@ -224,11 +231,11 @@ export class Linkifier extends EventEmitter implements ILinkifier { return; } if (isValid) { - this._addLink(offset + index, rowIndex, uri, matcher); + this._addLink(offset + index, rowIndex, uri, matcher, fg); } }); } else { - this._addLink(offset + index, rowIndex, uri, matcher); + this._addLink(offset + index, rowIndex, uri, matcher, fg); } // Recursively check for links in the rest of the text @@ -245,8 +252,9 @@ export class Linkifier extends EventEmitter implements ILinkifier { * @param y The row the link is on. * @param uri The URI of the link. * @param matcher The link matcher for the link. + * @param fg The link color for hover event. */ - private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher): void { + private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher, fg: number): void { const x1 = x % this._terminal.cols; const y1 = y + Math.floor(x / this._terminal.cols); let x2 = (x1 + uri.length) % this._terminal.cols; @@ -268,17 +276,17 @@ export class Linkifier extends EventEmitter implements ILinkifier { window.open(uri, '_blank'); }, e => { - this.emit(LinkHoverEventTypes.HOVER, this._createLinkHoverEvent(x1, y1, x2, y2)); + this.emit(LinkHoverEventTypes.HOVER, this._createLinkHoverEvent(x1, y1, x2, y2, fg)); this._terminal.element.classList.add('xterm-cursor-pointer'); }, e => { - this.emit(LinkHoverEventTypes.TOOLTIP, this._createLinkHoverEvent(x1, y1, x2, y2)); + this.emit(LinkHoverEventTypes.TOOLTIP, this._createLinkHoverEvent(x1, y1, x2, y2, fg)); if (matcher.hoverTooltipCallback) { matcher.hoverTooltipCallback(e, uri); } }, () => { - this.emit(LinkHoverEventTypes.LEAVE, this._createLinkHoverEvent(x1, y1, x2, y2)); + this.emit(LinkHoverEventTypes.LEAVE, this._createLinkHoverEvent(x1, y1, x2, y2, fg)); this._terminal.element.classList.remove('xterm-cursor-pointer'); if (matcher.hoverLeaveCallback) { matcher.hoverLeaveCallback(); @@ -293,7 +301,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { )); } - private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number): ILinkHoverEvent { - return { x1, y1, x2, y2, cols: this._terminal.cols }; + private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkHoverEvent { + return { x1, y1, x2, y2, cols: this._terminal.cols, fg }; } } diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index f37d7840..ee38133b 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -163,7 +163,5 @@ describe('typings', () => { const fixtureDir = path.join(__dirname, '..', 'fixtures', 'typings-test'); const result = cp.spawnSync(tsc, { cwd: fixtureDir }); assert.equal(result.status, 0, `build did not succeed:\nstdout: ${result.stdout.toString()}\nstderr: ${result.stderr.toString()}\n`); - // Clean up - fs.unlinkSync(path.join(fixtureDir, 'typings-test.js')); }); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index 21ab78c3..99b5859d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -251,7 +251,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _setup(): void { Object.keys(DEFAULT_OPTIONS).forEach((key) => { - if (this.options[key] == null) { + if (this.options[key] === null || this.options[key] === undefined) { this.options[key] = DEFAULT_OPTIONS[key]; } }); @@ -503,10 +503,11 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Binds the desired focus behavior on a given terminal object. */ - private _onTextAreaFocus(): void { + private _onTextAreaFocus(ev: KeyboardEvent): void { if (this.sendFocus) { this.handler(C0.ESC + '[I'); } + this.updateCursorStyle(ev); this.element.classList.add('focus'); this.showCursor(); this.emit('focus'); @@ -679,7 +680,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.textarea.setAttribute('autocapitalize', 'off'); this.textarea.setAttribute('spellcheck', 'false'); this.textarea.tabIndex = 0; - this.register(addDisposableDomListener(this.textarea, 'focus', () => this._onTextAreaFocus())); + this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._onTextAreaFocus(ev))); this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur())); this._helperContainer.appendChild(this.textarea); @@ -960,9 +961,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // 1, and 2 - with 64 added switch ((ev).overrideType || ev.type) { case 'mousedown': - button = ev.button != null + button = ev.button !== null && ev.button !== undefined ? +ev.button - : ev.which != null + : ev.which !== null && ev.which !== undefined ? ev.which - 1 : null; @@ -1587,7 +1588,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (ev.charCode) { key = ev.charCode; - } else if (ev.which == null) { + } else if (ev.which === null || ev.which === undefined) { key = ev.keyCode; } else if (ev.which !== 0 && ev.charCode !== 0) { key = ev.which; @@ -1932,7 +1933,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public matchColor(r1: number, g1: number, b1: number): number { const hash = (r1 << 16) | (g1 << 8) | b1; - if (matchColorCache[hash] != null) { + if (matchColorCache[hash] !== null && matchColorCache[hash] !== undefined) { return matchColorCache[hash]; } diff --git a/src/Types.ts b/src/Types.ts index ba1a7990..4917c513 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -204,6 +204,7 @@ export interface ILinkHoverEvent { x2: number; y2: number; cols: number; + fg: number; } export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts index 98a0bfaa..f121e2e2 100644 --- a/src/addons/attach/attach.ts +++ b/src/addons/attach/attach.ts @@ -53,7 +53,7 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean const fileReader = new FileReader(); fileReader.addEventListener('load', () => { - str = myTextDecoder.decode(this.result); + str = myTextDecoder.decode(fileReader.result); displayData(str); }); fileReader.readAsArrayBuffer(ev.data); diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 1e9731f1..8679939a 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -6,6 +6,7 @@ import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } from '../Types'; import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; +import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkHoverEvent = null; @@ -39,7 +40,15 @@ export class LinkRenderLayer extends BaseRenderLayer { } private _onLinkHover(e: ILinkHoverEvent): void { - this._ctx.fillStyle = this._colors.foreground.css; + if (e.fg === INVERTED_DEFAULT_COLOR) { + this._ctx.fillStyle = this._colors.background.css; + } else if (e.fg < 256) { + // 256 color support + this._ctx.fillStyle = this._colors.ansi[e.fg].css; + } else { + this._ctx.fillStyle = this._colors.foreground.css; + } + if (e.y1 === e.y2) { // Single line link this.fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1); diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index e919e362..f6df365e 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -94,7 +94,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas { ): boolean { const glyphKey = getGlyphCacheKey(glyph); const cacheValue = this._cacheMap.get(glyphKey); - if (cacheValue != null) { + if (cacheValue !== null && cacheValue !== undefined) { this._drawFromCache(ctx, cacheValue, x, y); return true; } else if (this._canCache(glyph) && this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) { diff --git a/src/renderer/atlas/StaticCharAtlas.ts b/src/renderer/atlas/StaticCharAtlas.ts index b6de82fc..c0d8a814 100644 --- a/src/renderer/atlas/StaticCharAtlas.ts +++ b/src/renderer/atlas/StaticCharAtlas.ts @@ -53,7 +53,7 @@ export default class StaticCharAtlas extends BaseCharAtlas { y: number ): boolean { // we're not warmed up yet - if (this._texture == null) { + if (this._texture === null || this._texture === undefined) { return false; } diff --git a/src/ui/RenderDebouncer.ts b/src/ui/RenderDebouncer.ts index ab88fb5f..775b7f74 100644 --- a/src/ui/RenderDebouncer.ts +++ b/src/ui/RenderDebouncer.ts @@ -22,11 +22,16 @@ export class RenderDebouncer implements IDisposable { } } - public refresh(rowStart?: number, rowEnd?: number): void { - rowStart = rowStart || 0; - rowEnd = rowEnd || this._terminal.rows - 1; - this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart; - this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd; + public refresh(rowStart: number, rowEnd: number): void { + // Get the min/max row start/end for the arg values + rowStart = rowStart !== null && rowStart !== undefined ? rowStart : 0; + rowEnd = rowEnd !== null && rowEnd !== undefined ? rowEnd : this._terminal.rows - 1; + // Check whether the row start/end values have already been set + const isRowStartSet = this._rowStart !== undefined && this._rowStart !== null; + const isRowEndSet = this._rowEnd !== undefined && this._rowEnd !== null; + // Set the properties to the updated values + this._rowStart = isRowStartSet ? Math.min(this._rowStart, rowStart) : rowStart; + this._rowEnd = isRowEndSet ? Math.max(this._rowEnd, rowEnd) : rowEnd; if (this._animationFrame) { return; diff --git a/src/utils/MouseHelper.ts b/src/utils/MouseHelper.ts index f62593eb..15f05742 100644 --- a/src/utils/MouseHelper.ts +++ b/src/utils/MouseHelper.ts @@ -11,7 +11,7 @@ export class MouseHelper { public static getCoordsRelativeToElement(event: {pageX: number, pageY: number}, element: HTMLElement): [number, number] { // Ignore browsers that don't support MouseEvent.pageX - if (event.pageX == null) { + if (event.pageX === null || event.pageX === undefined) { return null; } diff --git a/tslint.json b/tslint.json index 2ae39796..5dac55d6 100644 --- a/tslint.json +++ b/tslint.json @@ -111,6 +111,7 @@ "prefer-const-enum": [ true ], - "prefer-const": true + "prefer-const": true, + "triple-equals": true } }