diff --git a/.npmignore b/.npmignore index 517ba0bd..03030ba5 100644 --- a/.npmignore +++ b/.npmignore @@ -42,6 +42,7 @@ lib/test/ docs/ /.idea/ .vscode/ +bin/ build/ fixtures/ coverage/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..3c8a2dc2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.preferences.quoteStyle": "single" +} diff --git a/README.md b/README.md index 1fa8ad41..b2e9f02f 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,16 @@ Xterm.js follows a monthly release cycle roughly. All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones). +### Beta builds + +Our CI releases beta builds to npm for every change that goes into master, install the latest beta build with: + +``` +npm install -S xterm@beta +``` + +These should generally be stable but some bugs may slip in, we recommend using the beta build primarily to test out new features and for verifying bug fixes. + ## Contributing You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and setup xterm.js for development. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a26bd3d8..db74a4ea 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -66,3 +66,28 @@ jobs: - script: | yarn lint displayName: 'Lint' + +- job: Release + dependsOn: + - Linux + - macOS + - Windows + condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) + pool: + vmImage: 'ubuntu-16.04' + steps: + - task: NodeTool@0 + inputs: + versionSpec: '8.x' + displayName: 'Install Node.js' + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.9.4" + displayName: 'Install Yarn' + - script: | + yarn + BUILD_DIR=dist npm run build + displayName: 'Install dependencies and build' + - script: | + NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js + displayName: 'Publish to npm' diff --git a/bin/publish.js b/bin/publish.js new file mode 100644 index 00000000..ec7bf173 --- /dev/null +++ b/bin/publish.js @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const packageJson = require('../package.json'); + +// Setup auth +fs.writeFileSync(`${process.env['HOME']}/.npmrc`, `//registry.npmjs.org/:_authToken=${process.env['NPM_AUTH_TOKEN']}`); + +// Get the version +const tag = 'beta' +const nextVersion = getNextVersion(tag); +console.log(`Publishing version: ${nextVersion}`); + +// Set the version in package.json +const packageJsonFile = path.resolve(__dirname, '..', 'package.json'); +packageJson.version = nextVersion; +fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2)); + +// Publish +const result = cp.spawn('npm', ['publish', '--tag', tag], { + stdio: 'inherit' +}); +result.on('exit', code => process.exit(code)); + +function getNextVersion(tag) { + if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.exec(packageJson.version)) { + console.error('The package.json version must be of the form x.y.z'); + process.exit(1); + } + const stableVersion = packageJson.version.split('.'); + const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.${stableVersion[2]}`; + const publishedVersions = getPublishedVersions(nextStableVersion, tag); + if (publishedVersions.length === 0) { + return `${packageJson.version}-${tag}1`; + } + const latestPublishedVersion = publishedVersions.sort((a, b) => b.localeCompare(a))[0]; + const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10); + return `${nextStableVersion}-${tag}${latestTagVersion + 1}`; +} + +function getPublishedVersions(version, tag) { + const versionsProcess = cp.spawnSync('npm', ['view', 'xterm', 'versions', '--json']); + const versionsJson = JSON.parse(versionsProcess.stdout); + return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`))); +} diff --git a/package.json b/package.json index 539b96da..d58a6a5d 100644 --- a/package.json +++ b/package.json @@ -61,4 +61,4 @@ "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" } -} +} \ No newline at end of file diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index e2115797..1f44c132 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -61,7 +61,7 @@ export class AccessibilityManager extends Disposable { this._refreshRowsDimensions(); this._accessibilityTreeRoot.appendChild(this._rowContainer); - this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._renderRowsDebouncer = new RenderDebouncer(this._renderRows.bind(this)); this._refreshRows(); this._liveRegion = document.createElement('div'); @@ -239,7 +239,7 @@ export class AccessibilityManager extends Disposable { } private _refreshRows(start?: number, end?: number): void { - this._renderRowsDebouncer.refresh(start, end); + this._renderRowsDebouncer.refresh(start, end, this._terminal.rows); } private _renderRows(start: number, end: number): void { diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index d5a27bf1..d866fd56 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -7,7 +7,7 @@ import { assert, expect } from 'chai'; import { ITerminal } from './Types'; import { Buffer, DEFAULT_ATTR_DATA } from './Buffer'; import { CircularList } from './common/CircularList'; -import { MockTerminal, TestTerminal } from './ui/TestUtils.test'; +import { MockTerminal, TestTerminal } from './TestUtils.test'; import { BufferLine, CellData } from './BufferLine'; const INIT_COLS = 80; diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index 576a8ca4..cdc220b3 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { BufferSet } from './BufferSet'; import { Buffer } from './Buffer'; -import { MockTerminal } from './ui/TestUtils.test'; +import { MockTerminal } from './TestUtils.test'; describe('BufferSet', () => { let terminal: ITerminal; diff --git a/src/ui/CharMeasure.test.ts b/src/CharMeasure.test.ts similarity index 97% rename from src/ui/CharMeasure.test.ts rename to src/CharMeasure.test.ts index a3cb3b3b..5fd17eb2 100644 --- a/src/ui/CharMeasure.test.ts +++ b/src/CharMeasure.test.ts @@ -4,7 +4,7 @@ */ import jsdom = require('jsdom'); -import { ICharMeasure } from '../Types'; +import { ICharMeasure } from './Types'; import { assert } from 'chai'; import { CharMeasure } from './CharMeasure'; diff --git a/src/ui/CharMeasure.ts b/src/CharMeasure.ts similarity index 93% rename from src/ui/CharMeasure.ts rename to src/CharMeasure.ts index c37df312..efee8972 100644 --- a/src/ui/CharMeasure.ts +++ b/src/CharMeasure.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { ICharMeasure, ITerminalOptions } from '../Types'; -import { EventEmitter2, IEvent } from '../common/EventEmitter2'; +import { ICharMeasure, ITerminalOptions } from './Types'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * Utility class that measures the size of a character. Measurements are done in diff --git a/src/CharWidth.test.ts b/src/CharWidth.test.ts index 8608c6fa..ff6f17ed 100644 --- a/src/CharWidth.test.ts +++ b/src/CharWidth.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { TestTerminal } from './ui/TestUtils.test'; +import { TestTerminal } from './TestUtils.test'; import { assert } from 'chai'; import { getStringCellWidth, wcwidth } from './CharWidth'; import { IBuffer } from './Types'; diff --git a/src/ui/Clipboard.test.ts b/src/Clipboard.test.ts similarity index 100% rename from src/ui/Clipboard.test.ts rename to src/Clipboard.test.ts diff --git a/src/ui/Clipboard.ts b/src/Clipboard.ts similarity index 98% rename from src/ui/Clipboard.ts rename to src/Clipboard.ts index 2570a8b1..7a32badc 100644 --- a/src/ui/Clipboard.ts +++ b/src/Clipboard.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminal, ISelectionManager } from '../Types'; +import { ITerminal, ISelectionManager } from './Types'; interface IWindow extends Window { clipboardData?: { diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 01596f80..dcd4d1b4 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -5,7 +5,7 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; -import { MockInputHandlingTerminal, TestTerminal } from './ui/TestUtils.test'; +import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from './Buffer'; import { Terminal } from './Terminal'; import { IBufferLine } from './Types'; diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index c7bbbeb8..8f734bac 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -4,10 +4,9 @@ */ import { assert } from 'chai'; -import { IMouseZoneManager, IMouseZone } from './ui/Types'; -import { ILinkMatcher, ITerminal, IBufferLine } from './Types'; +import { IMouseZoneManager, IMouseZone, ILinkMatcher, ITerminal, IBufferLine } from './Types'; import { Linkifier } from './Linkifier'; -import { MockBuffer, MockTerminal, TestTerminal } from './ui/TestUtils.test'; +import { MockBuffer, MockTerminal, TestTerminal } from './TestUtils.test'; import { CircularList } from './common/CircularList'; import { BufferLine, CellData } from './BufferLine'; diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 8c57e1b2..9744929e 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -3,9 +3,8 @@ * @license MIT */ -import { IMouseZoneManager } from './ui/Types'; -import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types'; -import { MouseZone } from './ui/MouseZoneManager'; +import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult, IMouseZoneManager } from './Types'; +import { MouseZone } from './MouseZoneManager'; import { getStringCellWidth } from './CharWidth'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; diff --git a/src/ui/MouseHelper.test.ts b/src/MouseHelper.test.ts similarity index 100% rename from src/ui/MouseHelper.test.ts rename to src/MouseHelper.test.ts diff --git a/src/ui/MouseHelper.ts b/src/MouseHelper.ts similarity index 96% rename from src/ui/MouseHelper.ts rename to src/MouseHelper.ts index e36e7f17..fb91c9ef 100644 --- a/src/ui/MouseHelper.ts +++ b/src/MouseHelper.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { ICharMeasure, IMouseHelper } from '../Types'; -import { IRenderer } from '../renderer/Types'; +import { ICharMeasure, IMouseHelper } from './Types'; +import { IRenderer } from './renderer/Types'; export class MouseHelper implements IMouseHelper { constructor(private _renderer: IRenderer) {} diff --git a/src/ui/MouseZoneManager.ts b/src/MouseZoneManager.ts similarity index 97% rename from src/ui/MouseZoneManager.ts rename to src/MouseZoneManager.ts index 372dccc5..2f293df1 100644 --- a/src/ui/MouseZoneManager.ts +++ b/src/MouseZoneManager.ts @@ -3,10 +3,9 @@ * @license MIT */ -import { ITerminal } from '../Types'; -import { IMouseZoneManager, IMouseZone } from './Types'; -import { Disposable } from '../common/Lifecycle'; -import { addDisposableDomListener } from './Lifecycle'; +import { ITerminal, IMouseZoneManager, IMouseZone } from './Types'; +import { Disposable } from './common/Lifecycle'; +import { addDisposableDomListener } from './ui/Lifecycle'; const HOVER_DURATION = 500; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index d65f9716..9a2ac405 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -4,12 +4,12 @@ */ import { assert } from 'chai'; -import { CharMeasure } from './ui/CharMeasure'; +import { CharMeasure } from './CharMeasure'; import { SelectionManager, SelectionMode } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { ITerminal, IBuffer, IBufferLine } from './Types'; -import { MockTerminal } from './ui/TestUtils.test'; +import { MockTerminal } from './TestUtils.test'; import { BufferLine, CellData } from './BufferLine'; class TestMockTerminal extends MockTerminal { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index eed206cc..dcd60068 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -4,9 +4,9 @@ */ import { ITerminal, ISelectionManager, IBuffer, IBufferLine, ISelectionRedrawRequestEvent } from './Types'; -import { MouseHelper } from './ui/MouseHelper'; +import { MouseHelper } from './MouseHelper'; import * as Browser from './common/Platform'; -import { CharMeasure } from './ui/CharMeasure'; +import { CharMeasure } from './CharMeasure'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from './BufferLine'; diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index d49f41d0..c2b261a9 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { MockTerminal } from './ui/TestUtils.test'; +import { MockTerminal } from './TestUtils.test'; class TestSelectionModel extends SelectionModel { constructor( diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index dd8e32e1..69fbb8e2 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -5,7 +5,7 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; -import { MockViewport, MockCompositionHelper, MockRenderer } from './ui/TestUtils.test'; +import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from './Buffer'; import { CellData } from './BufferLine'; diff --git a/src/Terminal.ts b/src/Terminal.ts index 93a37813..1038ea8b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,27 +21,26 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IBufferLine, IAttributeData } from './Types'; -import { IMouseZoneManager } from './ui/Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IBufferLine, IAttributeData, IMouseZoneManager } from './Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR_DATA } from './Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './common/EventEmitter'; import { Viewport } from './Viewport'; -import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './ui/Clipboard'; +import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './Clipboard'; import { C0 } from './common/data/EscapeSequences'; import { InputHandler } from './InputHandler'; import { Renderer } from './renderer/Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; -import { CharMeasure } from './ui/CharMeasure'; +import { CharMeasure } from './CharMeasure'; import * as Browser from './common/Platform'; import { addDisposableDomListener } from './ui/Lifecycle'; import * as Strings from './Strings'; -import { MouseHelper } from './ui/MouseHelper'; +import { MouseHelper } from './MouseHelper'; import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; -import { MouseZoneManager } from './ui/MouseZoneManager'; +import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; import { ITheme, IMarker, IDisposable } from 'xterm'; diff --git a/src/ui/TestUtils.test.ts b/src/TestUtils.test.ts similarity index 97% rename from src/ui/TestUtils.test.ts rename to src/TestUtils.test.ts index 5ee0f9d5..92018d1f 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -3,14 +3,14 @@ * @license MIT */ -import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator, ICellData, IAttributeData } from '../Types'; -import { ICircularList, XtermListener } from '../common/Types'; -import { Buffer } from '../Buffer'; -import * as Browser from '../common/Platform'; +import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from './renderer/Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator, ICellData, IAttributeData } from './Types'; +import { ICircularList, XtermListener } from './common/Types'; +import { Buffer } from './Buffer'; +import * as Browser from './common/Platform'; import { ITheme, IDisposable, IMarker, IEvent } from 'xterm'; -import { Terminal } from '../Terminal'; -import { AttributeData } from '../BufferLine'; +import { Terminal } from './Terminal'; +import { AttributeData } from './BufferLine'; export class TestTerminal extends Terminal { writeSync(data: string): void { diff --git a/src/Types.ts b/src/Types.ts index 6930d878..1e9ac642 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -5,7 +5,6 @@ import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; -import { IMouseZoneManager } from './ui/Types'; import { ICharset } from './core/Types'; import { ICircularList } from './common/Types'; import { IEvent } from './common/EventEmitter2'; @@ -605,3 +604,20 @@ export interface IBufferLine { isCombined(index: number): number; getString(index: number): string; } + +export interface IMouseZoneManager extends IDisposable { + add(zone: IMouseZone): void; + clearAll(start?: number, end?: number): void; +} + +export interface IMouseZone { + x1: number; + x2: number; + y1: number; + y2: number; + clickCallback: (e: MouseEvent) => any; + hoverCallback: (e: MouseEvent) => any | undefined; + tooltipCallback: (e: MouseEvent) => any | undefined; + leaveCallback: () => any | undefined; + willLinkActivate: (e: MouseEvent) => boolean; +} diff --git a/src/Viewport.ts b/src/Viewport.ts index 50d04457..6bf92531 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -5,7 +5,7 @@ import { IColorSet } from './renderer/Types'; import { ITerminal, IViewport } from './Types'; -import { CharMeasure } from './ui/CharMeasure'; +import { CharMeasure } from './CharMeasure'; import { Disposable } from './common/Lifecycle'; import { addDisposableDomListener } from './ui/Lifecycle'; diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 549a8449..7db1ed43 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -21,6 +21,7 @@ export class SearchHelper implements ISearchHelper { private _linesCache: string[] = null; private _linesCacheTimeoutId = 0; private _cursorMoveListener: IDisposable | undefined; + private _resizeListener: IDisposable | undefined; constructor(private _terminal: ISearchAddonTerminal) { this._destroyLinesCache = this._destroyLinesCache.bind(this); @@ -185,6 +186,7 @@ export class SearchHelper implements ISearchHelper { if (!this._linesCache) { this._linesCache = new Array(this._terminal._core.buffer.length); this._cursorMoveListener = this._terminal.onCursorMove(() => this._destroyLinesCache()); + this._resizeListener = this._terminal.onResize(() => this._destroyLinesCache()); } window.clearTimeout(this._linesCacheTimeoutId); @@ -197,6 +199,10 @@ export class SearchHelper implements ISearchHelper { this._cursorMoveListener.dispose(); this._cursorMoveListener = undefined; } + if (this._resizeListener) { + this._resizeListener.dispose(); + this._resizeListener = undefined; + } if (this._linesCacheTimeoutId) { window.clearTimeout(this._linesCacheTimeoutId); this._linesCacheTimeoutId = 0; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 008b216a..d851b2f2 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -322,14 +322,18 @@ export abstract class BaseRenderLayer implements IRenderLayer { } else { this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; } - } else if (cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else if (cell.isFgPalette()) { - let fg = cell.getFgColor(); - if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { - fg += 8; + } else { + if (cell.isFgDefault()) { + this._ctx.fillStyle = this._colors.foreground.css; + } else if (cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } else { + let fg = cell.getFgColor(); + if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + fg += 8; + } + this._ctx.fillStyle = this._colors.ansi[fg].css; } - this._ctx.fillStyle = this._colors.ansi[fg].css; } this._clipRow(terminal, y); diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts index effdbfaa..2ccfd197 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { MockTerminal, MockBuffer } from '../ui/TestUtils.test'; +import { MockTerminal, MockBuffer } from '../TestUtils.test'; import { CircularList } from '../common/CircularList'; import { ICharacterJoinerRegistry } from './Types'; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2a205be3..14ce72b3 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -68,7 +68,7 @@ export class Renderer extends Disposable implements IRenderer { this._updateDimensions(); this.onOptionsChanged(); - this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); this._screenDprMonitor = new ScreenDprMonitor(); this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); this.register(this._screenDprMonitor); @@ -194,7 +194,7 @@ export class Renderer extends Disposable implements IRenderer { this._needsFullRefresh = true; return; } - this._renderDebouncer.refresh(start, end); + this._renderDebouncer.refresh(start, end, this._terminal.rows); } /** diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 2547ecb2..c4bdf19f 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -217,10 +217,18 @@ export class TextRenderLayer extends BaseRenderLayer { } else { this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; } - } else if (cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else if (cell.isFgPalette()) { - this._ctx.fillStyle = this._colors.ansi[cell.getFgColor()].css; + } else { + if (cell.isFgDefault()) { + this._ctx.fillStyle = this._colors.foreground.css; + } else if (cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } else { + let fg = cell.getFgColor(); + if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + fg += 8; + } + this._ctx.fillStyle = this._colors.ansi[fg].css; + } } this.fillBottomLineAtCells(x, y, cell.getWidth()); diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 1d879fcd..ea3ac5d9 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -80,7 +80,7 @@ export class DomRenderer extends Disposable implements IRenderer { }; this._updateDimensions(); - this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); this._rowFactory = new DomRendererRowFactory(_terminal.options, document); this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); @@ -340,7 +340,7 @@ export class DomRenderer extends Disposable implements IRenderer { } public refreshRows(start: number, end: number): void { - this._renderDebouncer.refresh(start, end); + this._renderDebouncer.refresh(start, end, this._terminal.rows); } private _renderRows(start: number, end: number): void { diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 4402943c..076f5d6a 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -100,6 +100,16 @@ describe('DomRendererRowFactory', () => { ); }); + it('should add class for underline', () => { + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE; + lineData.setCell(0, cell); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + assert.equal(getFragmentHtml(fragment), + 'a' + ); + }); + it('should add classes for 256 foreground colors', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg |= Attributes.CM_P256; diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index a6cbeb78..60e2d509 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -11,6 +11,7 @@ import { CellData, AttributeData } from '../../BufferLine'; export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; export const ITALIC_CLASS = 'xterm-italic'; +export const UNDERLINE_CLASS = 'xterm-underline'; export const CURSOR_CLASS = 'xterm-cursor'; export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink'; export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block'; @@ -88,6 +89,10 @@ export class DomRendererRowFactory { charElement.classList.add(DIM_CLASS); } + if (this._workCell.isUnderline()) { + charElement.classList.add(UNDERLINE_CLASS); + } + charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; const swapColor = this._workCell.isInverse(); diff --git a/src/tsconfig.json b/src/tsconfig.json index f0b1c749..1e8230a2 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -24,6 +24,7 @@ ], "references": [ { "path": "./common" }, - { "path": "./core" } + { "path": "./core" }, + { "path": "./ui" } ] } diff --git a/src/ui/Lifecycle.ts b/src/ui/Lifecycle.ts index 9f058106..4d36a1f1 100644 --- a/src/ui/Lifecycle.ts +++ b/src/ui/Lifecycle.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable } from 'xterm'; +import { IDisposable } from '../common/Types'; /** * Adds a disposable listener to a node in the DOM, returning the disposable. @@ -24,8 +24,6 @@ export function addDisposableDomListener( return; } node.removeEventListener(type, handler, useCapture); - node = null; - handler = null; } }; } diff --git a/src/ui/RenderDebouncer.ts b/src/ui/RenderDebouncer.ts index 775b7f74..ee2ab16e 100644 --- a/src/ui/RenderDebouncer.ts +++ b/src/ui/RenderDebouncer.ts @@ -1,37 +1,39 @@ -import { ITerminal } from '../Types'; -import { IDisposable } from 'xterm'; +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable } from '../common/Types'; /** * Debounces calls to render terminal rows using animation frames. */ export class RenderDebouncer implements IDisposable { - private _rowStart: number; - private _rowEnd: number; - private _animationFrame: number = null; + private _rowStart: number | undefined; + private _rowEnd: number | undefined; + private _rowCount: number | undefined; + private _animationFrame: number | undefined; constructor( - private _terminal: ITerminal, - private _callback: (start: number, end: number) => void + private _renderCallback: (start: number, end: number) => void ) { } public dispose(): void { if (this._animationFrame) { window.cancelAnimationFrame(this._animationFrame); - this._animationFrame = null; + this._animationFrame = undefined; } } - public refresh(rowStart: number, rowEnd: number): void { + public refresh(rowStart: number, rowEnd: number, rowCount: number): void { + this._rowCount = rowCount; // 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; + rowStart = rowStart !== undefined ? rowStart : 0; + rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1; // 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; + this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart; + this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd; if (this._animationFrame) { return; @@ -41,16 +43,21 @@ export class RenderDebouncer implements IDisposable { } private _innerRefresh(): void { + // Make sure values are set + if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) { + return; + } + // Clamp values this._rowStart = Math.max(this._rowStart, 0); - this._rowEnd = Math.min(this._rowEnd, this._terminal.rows - 1); + this._rowEnd = Math.min(this._rowEnd, this._rowCount - 1); // Run render callback - this._callback(this._rowStart, this._rowEnd); + this._renderCallback(this._rowStart, this._rowEnd); // Reset debouncer - this._rowStart = null; - this._rowEnd = null; - this._animationFrame = null; + this._rowStart = undefined; + this._rowEnd = undefined; + this._animationFrame = undefined; } } diff --git a/src/ui/ScreenDprMonitor.ts b/src/ui/ScreenDprMonitor.ts index d66eeb64..37c6a0da 100644 --- a/src/ui/ScreenDprMonitor.ts +++ b/src/ui/ScreenDprMonitor.ts @@ -18,10 +18,10 @@ export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRat * monitor with a different DPI. */ export class ScreenDprMonitor extends Disposable { - private _currentDevicePixelRatio: number; - private _outerListener: (this: MediaQueryList, ev: MediaQueryListEvent) => any; - private _listener: ScreenDprListener; - private _resolutionMediaMatchList: MediaQueryList; + private _currentDevicePixelRatio: number = window.devicePixelRatio; + private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; + private _listener: ScreenDprListener | undefined; + private _resolutionMediaMatchList: MediaQueryList | undefined; public setListener(listener: ScreenDprListener): void { if (this._listener) { @@ -29,6 +29,9 @@ export class ScreenDprMonitor extends Disposable { } this._listener = listener; this._outerListener = () => { + if (!this._listener) { + return; + } this._listener(window.devicePixelRatio, this._currentDevicePixelRatio); this._updateDpr(); }; @@ -41,10 +44,13 @@ export class ScreenDprMonitor extends Disposable { } private _updateDpr(): void { - // Clear listeners for old DPR - if (this._resolutionMediaMatchList) { - this._resolutionMediaMatchList.removeListener(this._outerListener); + if (!this._resolutionMediaMatchList || !this._outerListener) { + return; } + + // Clear listeners for old DPR + this._resolutionMediaMatchList.removeListener(this._outerListener); + // Add listeners for new DPR this._currentDevicePixelRatio = window.devicePixelRatio; this._resolutionMediaMatchList = window.matchMedia(`screen and (resolution: ${window.devicePixelRatio}dppx)`); @@ -52,11 +58,12 @@ export class ScreenDprMonitor extends Disposable { } public clearListener(): void { - if (!this._listener) { + if (!this._resolutionMediaMatchList || !this._listener || !this._outerListener) { return; } this._resolutionMediaMatchList.removeListener(this._outerListener); - this._listener = null; - this._outerListener = null; + this._resolutionMediaMatchList = undefined; + this._listener = undefined; + this._outerListener = undefined; } } diff --git a/src/ui/Types.ts b/src/ui/Types.ts deleted file mode 100644 index 89dfa1a4..00000000 --- a/src/ui/Types.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable } from 'xterm'; - -export interface IMouseZoneManager extends IDisposable { - add(zone: IMouseZone): void; - clearAll(start?: number, end?: number): void; -} - -export interface IMouseZone { - x1: number; - x2: number; - y1: number; - y2: number; - clickCallback: (e: MouseEvent) => any; - hoverCallback: (e: MouseEvent) => any | undefined; - tooltipCallback: (e: MouseEvent) => any | undefined; - leaveCallback: () => any | undefined; - willLinkActivate: (e: MouseEvent) => boolean; -} diff --git a/src/ui/tsconfig.json b/src/ui/tsconfig.json new file mode 100644 index 00000000..b3613eef --- /dev/null +++ b/src/ui/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../tsconfig-library-base", + "compilerOptions": { + "lib": [ + "dom", + "es5", + ], + "outDir": "../../lib", + "types": [ + "../../node_modules/@types/mocha" + ] + }, + "include": [ + "./Lifecycle.ts", + "./RenderDebouncer.ts", + "./ScreenDprMonitor.ts" + ], + "references": [ + { "path": "../common" } + ] +} \ No newline at end of file diff --git a/src/xterm.css b/src/xterm.css index e80c2524..5448b5a5 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -165,3 +165,7 @@ .xterm-dim { opacity: 0.5; } + +.xterm-underline { + text-decoration: underline; +}