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/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/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.ts b/src/Terminal.ts index c00e6df9..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]; } }); @@ -961,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; @@ -1588,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; @@ -1933,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/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/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 } }