Merge branch 'master' into benchmark_integration

This commit is contained in:
Daniel Imms
2019-06-15 22:47:33 -07:00
committed by GitHub
76 changed files with 892 additions and 635 deletions
+2 -6
View File
@@ -85,13 +85,9 @@ The xterm.js team maintains the following addons but they can be built by anyone
## Browser Support
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support:
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox* and *Safari*.
- Chrome latest
- Edge latest
- Firefox latest
- Safari latest
- IE11
We also partially support *Intenet Explorer 11*, meaning xterm.js should work for the most part, but we reserve the right to not provide workarounds specifically for it unless it's absolutely necessary to get the basic input/output flow working.
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers, these are the versions we strive to keep working.
+1
View File
@@ -2,3 +2,4 @@
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
+6 -3
View File
@@ -1,15 +1,18 @@
{
"name": "xterm-addon-attach",
"version": "0.1.0-beta10",
"version": "0.1.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/AttachAddon.js",
"main": "lib/xterm-addon-attach.js",
"types": "typings/xterm-addon-attach.d.ts",
"license": "MIT",
"scripts": {
"prepublishOnly": "../../node_modules/.bin/tsc -p src"
"build": "../../node_modules/.bin/tsc -p src",
"prepackage": "npm run build",
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"xterm": "^3.14.0"
+1 -1
View File
@@ -7,7 +7,7 @@
"es2015"
],
"rootDir": ".",
"outDir": "../lib",
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
const path = require('path');
const addonName = 'AttachAddon';
const mainFile = 'xterm-addon-attach.js';
module.exports = {
entry: `./out/${addonName}.js`,
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre",
exclude: /node_modules/
}
]
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
},
mode: 'production'
};
+1
View File
@@ -2,3 +2,4 @@
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
+6 -3
View File
@@ -1,15 +1,18 @@
{
"name": "xterm-addon-fit",
"version": "0.1.0-beta2",
"version": "0.1.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/FitAddon.js",
"main": "lib/xterm-addon-fit.js",
"types": "typings/xterm-addon-fit.d.ts",
"license": "MIT",
"scripts": {
"prepublishOnly": "../../node_modules/.bin/tsc -p src"
"build": "../../node_modules/.bin/tsc -p src",
"prepackage": "npm run build",
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"xterm": "^3.14.0"
+1 -1
View File
@@ -7,7 +7,7 @@
"es2015"
],
"rootDir": ".",
"outDir": "../lib",
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true
+31
View File
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
const path = require('path');
const addonName = 'FitAddon';
const mainFile = 'xterm-addon-fit.js';
module.exports = {
entry: `./out/${addonName}.js`,
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre",
exclude: /node_modules/
}
]
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
},
mode: 'production'
};
+1
View File
@@ -2,3 +2,4 @@
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
+6 -3
View File
@@ -1,15 +1,18 @@
{
"name": "xterm-addon-search",
"version": "0.1.0-beta5",
"version": "0.1.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/SearchAddon.js",
"main": "lib/xterm-addon-search.js",
"types": "typings/xterm-addon-search.d.ts",
"license": "MIT",
"scripts": {
"prepublishOnly": "../../node_modules/.bin/tsc -p src"
"build": "../../node_modules/.bin/tsc -p src",
"prepackage": "npm run build",
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"xterm": "^3.14.0"
+1 -1
View File
@@ -7,7 +7,7 @@
"es6",
],
"rootDir": ".",
"outDir": "../lib",
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
const path = require('path');
const addonName = 'SearchAddon';
const mainFile = 'xterm-addon-search.js';
module.exports = {
entry: `./out/${addonName}.js`,
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre",
exclude: /node_modules/
}
]
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
},
mode: 'production'
};
+1
View File
@@ -2,3 +2,4 @@
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
+6 -3
View File
@@ -1,15 +1,18 @@
{
"name": "xterm-addon-web-links",
"version": "0.1.0-beta9",
"version": "0.1.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/WebLinksAddon.js",
"main": "lib/xterm-addon-web-links.js",
"types": "typings/xterm-addon-web-links.d.ts",
"license": "MIT",
"scripts": {
"prepublishOnly": "../../node_modules/.bin/tsc -p src"
"build": "../../node_modules/.bin/tsc -p src",
"prepackage": "npm run build",
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"xterm": "^3.14.0"
@@ -7,7 +7,7 @@
"es2015"
],
"rootDir": ".",
"outDir": "../lib",
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
const path = require('path');
const addonName = 'WebLinksAddon';
const mainFile = 'xterm-addon-web-links.js';
module.exports = {
entry: `./out/${addonName}.js`,
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre",
exclude: /node_modules/
}
]
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
},
mode: 'production'
};
+74 -23
View File
@@ -6,33 +6,76 @@
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']}`);
// Determine if this is a stable or beta release
const publishedVersions = getPublishedVersions();
const isStableRelease = publishedVersions.indexOf(packageJson.version) === -1;
// Get the next version
let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion();
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 args = ['publish'];
if (!isStableRelease) {
args.push('--tag', 'beta');
const isDryRun = process.argv.indexOf('--dry') !== -1;
if (isDryRun) {
console.log('Publish dry run');
}
const result = cp.spawn('npm', args, { stdio: 'inherit' });
result.on('exit', code => process.exit(code));
function getNextBetaVersion() {
const changedFiles = getChangedFilesInCommit('HEAD');
// Publish xterm if any files were changed outside of the addons directory
if (changedFiles.some(e => e.search(/^addons\//) === -1)) {
checkAndPublishPackage(path.resolve(__dirname, '..'));
}
// Publish addons if any files were changed inside of the addon
const addonPackageDirs = [
path.resolve(__dirname, '../addons/xterm-addon-attach'),
path.resolve(__dirname, '../addons/xterm-addon-fit'),
path.resolve(__dirname, '../addons/xterm-addon-search'),
path.resolve(__dirname, '../addons/xterm-addon-web-links')
];
addonPackageDirs.forEach(p => {
const addon = path.basename(p);
if (changedFiles.some(e => e.indexOf(addon) !== -1)) {
checkAndPublishPackage(p);
}
});
function checkAndPublishPackage(packageDir) {
const packageJson = require(path.join(packageDir, 'package.json'));
// Determine if this is a stable or beta release
const publishedVersions = getPublishedVersions(packageJson);
const isStableRelease = publishedVersions.indexOf(packageJson.version) === -1;
// Get the next version
let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion(packageJson);
console.log(`Publishing version: ${nextVersion}`);
// Set the version in package.json
const packageJsonFile = path.join(packageDir, 'package.json');
packageJson.version = nextVersion;
console.log(`Set version of ${packageJsonFile} to ${nextVersion}`);
if (!isDryRun) {
fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));
}
// Publish
const args = ['publish'];
if (!isStableRelease) {
args.push('--tag', 'beta');
}
console.log(`Spawn: npm ${args.join(' ')}`);
if (!isDryRun) {
const result = cp.spawnSync('npm', args, {
cwd: packageDir,
stdio: 'inherit'
});
if (result.status) {
console.error(`Spawn exited with code ${result.status}`);
process.exit(result.status);
}
}
console.groupEnd();
}
function getNextBetaVersion(packageJson) {
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);
@@ -40,7 +83,7 @@ function getNextBetaVersion() {
const tag = 'beta';
const stableVersion = packageJson.version.split('.');
const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`;
const publishedVersions = getPublishedVersions(nextStableVersion, tag);
const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag);
if (publishedVersions.length === 0) {
return `${nextStableVersion}-${tag}1`;
}
@@ -53,7 +96,7 @@ function getNextBetaVersion() {
return `${nextStableVersion}-${tag}${latestTagVersion + 1}`;
}
function getPublishedVersions(version, tag) {
function getPublishedVersions(packageJson, version, tag) {
const versionsProcess = cp.spawnSync('npm', ['view', packageJson.name, 'versions', '--json']);
const versionsJson = JSON.parse(versionsProcess.stdout);
if (tag) {
@@ -61,3 +104,11 @@ function getPublishedVersions(version, tag) {
}
return versionsJson;
}
function getChangedFilesInCommit(commit) {
const args = ['log', '-m', '-1', '--name-only', `--pretty=format:`, commit];
const result = cp.spawnSync('git', args);
const output = result.stdout.toString();
const changedFiles = output.split('\n').filter(e => e.length > 0);
return changedFiles;
}
+9 -5
View File
@@ -9,13 +9,17 @@
// Use tsc version (yarn watch)
import { Terminal } from '../out/public/Terminal';
import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon';
import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon';
import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon';
import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon';
// Use webpacked version (yarn package)
// import { Terminal } from '../lib/xterm';
import { AttachAddon } from 'xterm-addon-attach';
import { FitAddon } from 'xterm-addon-fit';
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
import { WebLinksAddon } from 'xterm-addon-web-links';
// import { AttachAddon } from 'xterm-addon-attach';
// import { FitAddon } from 'xterm-addon-fit';
// import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
// import { WebLinksAddon } from 'xterm-addon-web-links';
// Pulling in the module's types relies on the <reference> above, it's looks a
// little weird here as we're importing "this" module
+1
View File
@@ -3,6 +3,7 @@
"description": "Full xterm terminal, in your browser",
"version": "3.14.0",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
+4 -25
View File
@@ -5,15 +5,6 @@
import { ITerminal, ISelectionManager } from './Types';
interface IWindow extends Window {
clipboardData?: {
getData(format: string): string;
setData(format: string, data: string): void;
};
}
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
@@ -38,12 +29,7 @@ export function bracketTextForPaste(text: string, bracketedPasteMode: boolean):
* @param ev The original copy event to be handled
*/
export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void {
if (term.browser.isMSIE) {
window.clipboardData.setData('Text', selectionManager.selectionText);
} else {
ev.clipboardData.setData('text/plain', selectionManager.selectionText);
}
ev.clipboardData.setData('text/plain', selectionManager.selectionText);
// Prevent or the original text will be copied.
ev.preventDefault();
}
@@ -66,16 +52,9 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {
term.cancel(ev);
};
if (term.browser.isMSIE) {
if (window.clipboardData) {
text = window.clipboardData.getData('Text');
dispatchPaste(text);
}
} else {
if (ev.clipboardData) {
text = ev.clipboardData.getData('text/plain');
dispatchPaste(text);
}
if (ev.clipboardData) {
text = ev.clipboardData.getData('text/plain');
dispatchPaste(text);
}
}
+5 -1
View File
@@ -8,13 +8,17 @@ import { InputHandler } from './InputHandler';
import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test';
import { Terminal } from './Terminal';
import { IBufferLine } from 'common/Types';
import { CellData, Attributes, AttributeData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { Attributes } from 'common/buffer/Constants';
import { AttributeData } from 'common/buffer/AttributeData';
describe('InputHandler', () => {
describe('save and restore cursor', () => {
const terminal = new MockInputHandlingTerminal();
terminal.buffer.x = 1;
terminal.buffer.y = 2;
terminal.buffer.ybase = 0;
terminal.curAttrData.fg = 3;
const inputHandler = new InputHandler(terminal);
// Save cursor position
+6 -3
View File
@@ -13,9 +13,12 @@ import { IDisposable } from 'xterm';
import { Disposable } from 'common/Lifecycle';
import { concat } from 'common/TypedArrayUtils';
import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'common/input/TextDecoder';
import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types';
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData';
/**
* Map collect to glevel. Used in `selectCharset`.
@@ -1916,7 +1919,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public saveCursor(params: number[]): void {
this._terminal.buffer.savedX = this._terminal.buffer.x;
this._terminal.buffer.savedY = this._terminal.buffer.y;
this._terminal.buffer.savedY = this._terminal.buffer.ybase + this._terminal.buffer.y;
this._terminal.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg;
this._terminal.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg;
}
@@ -1929,7 +1932,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public restoreCursor(params: number[]): void {
this._terminal.buffer.x = this._terminal.buffer.savedX || 0;
this._terminal.buffer.y = this._terminal.buffer.savedY || 0;
this._terminal.buffer.y = Math.max(this._terminal.buffer.savedY - this._terminal.buffer.ybase, 0);
this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg;
this._terminal.curAttrData.bg = this._terminal.buffer.savedCurAttrData.bg;
}
+2 -1
View File
@@ -9,7 +9,8 @@ import { IBufferLine } from 'common/Types';
import { Linkifier } from './Linkifier';
import { MockBuffer, MockTerminal, TestTerminal } from './TestUtils.test';
import { CircularList } from 'common/CircularList';
import { BufferLine, CellData } from 'common/buffer/BufferLine';
import { BufferLine } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
class TestLinkifier extends Linkifier {
constructor(terminal: ITerminal) {
+4 -2
View File
@@ -6,6 +6,7 @@
import { ITerminal, IMouseZoneManager, IMouseZone } from './Types';
import { Disposable } from 'common/Lifecycle';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IMouseService } from 'browser/services/Services';
const HOVER_DURATION = 500;
@@ -31,7 +32,8 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private _initialSelectionLength: number;
constructor(
private _terminal: ITerminal
private _terminal: ITerminal,
private _mouseService: IMouseService
) {
super();
@@ -203,7 +205,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
private _findZoneEventAt(e: MouseEvent): IMouseZone {
const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.cols, this._terminal.rows);
const coords = this._mouseService.getCoords(e, this._terminal.screenElement, this._terminal.cols, this._terminal.rows);
if (!coords) {
return null;
}
+4 -3
View File
@@ -12,9 +12,10 @@ import { IBuffer } from 'common/buffer/Types';
import { IBufferLine } from 'common/Types';
import { MockTerminal } from './TestUtils.test';
import { MockOptionsService, MockBufferService } from 'common/TestUtils.test';
import { BufferLine, CellData } from 'common/buffer/BufferLine';
import { BufferLine } from 'common/buffer/BufferLine';
import { IBufferService } from 'common/services/Services';
import { MockCharSizeService } from 'browser/TestUtils.test';
import { MockCharSizeService, MockMouseService } from 'browser/TestUtils.test';
import { CellData } from 'common/buffer/CellData';
class TestMockTerminal extends MockTerminal {
emit(event: string, data: any): void {}
@@ -25,7 +26,7 @@ class TestSelectionManager extends SelectionManager {
terminal: ITerminal,
bufferService: IBufferService
) {
super(terminal, new MockCharSizeService(10, 10), bufferService);
super(terminal, new MockCharSizeService(10, 10), bufferService, new MockMouseService());
}
public get model(): SelectionModel { return this._model; }
+10 -9
View File
@@ -6,15 +6,15 @@
import { ITerminal, ISelectionManager, ISelectionRedrawRequestEvent } from './Types';
import { IBuffer } from 'common/buffer/Types';
import { IBufferLine } from 'common/Types';
import { MouseHelper } from 'browser/input/MouseHelper';
import * as Browser from 'common/Platform';
import { SelectionModel } from './SelectionModel';
import { AltClickHandler } from './handlers/AltClickHandler';
import { CellData } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { IDisposable } from 'xterm';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { ICharSizeService } from 'browser/services/Services';
import { ICharSizeService, IMouseService } from 'browser/services/Services';
import { IBufferService } from 'common/services/Services';
import { getCoordsRelativeToElement } from 'browser/input/Mouse';
/**
* The number of pixels the mouse needs to be above or below the viewport in
@@ -118,9 +118,10 @@ export class SelectionManager implements ISelectionManager {
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
constructor(
private _terminal: ITerminal,
private _charSizeService: ICharSizeService,
bufferService: IBufferService
private readonly _terminal: ITerminal,
private readonly _charSizeService: ICharSizeService,
readonly bufferService: IBufferService,
private readonly _mouseService: IMouseService
) {
this._initListeners();
this.enable();
@@ -357,7 +358,7 @@ export class SelectionManager implements ISelectionManager {
* @param event The mouse event.
*/
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._terminal.cols, this._terminal.rows, true);
const coords = this._mouseService.getCoords(event, this._terminal.screenElement, this._terminal.cols, this._terminal.rows, true);
if (!coords) {
return null;
}
@@ -377,7 +378,7 @@ export class SelectionManager implements ISelectionManager {
* @param event The mouse event.
*/
private _getMouseEventScrollAmount(event: MouseEvent): number {
let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1];
let offset = getCoordsRelativeToElement(event, this._terminal.screenElement)[1];
const terminalHeight = this._terminal.rows * Math.ceil(this._charSizeService.height * this._terminal.options.lineHeight);
if (offset >= 0 && offset <= terminalHeight) {
return 0;
@@ -654,7 +655,7 @@ export class SelectionManager implements ISelectionManager {
this._removeMouseDownListeners();
if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) {
(new AltClickHandler(event, this._terminal)).move();
(new AltClickHandler(event, this._terminal, this._mouseService)).move();
} else if (this.hasSelection) {
this._onSelectionChange.fire();
}
+2 -1
View File
@@ -6,7 +6,8 @@
import { assert, expect } from 'chai';
import { Terminal } from './Terminal';
import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test';
import { CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
const INIT_COLS = 80;
const INIT_ROWS = 24;
+19 -19
View File
@@ -34,7 +34,6 @@ import { SelectionManager } from './SelectionManager';
import * as Browser from 'common/Platform';
import { addDisposableDomListener } from 'browser/Lifecycle';
import * as Strings from './browser/LocalizableStrings';
import { MouseHelper } from 'browser/input/MouseHelper';
import { SoundManager } from './SoundManager';
import { MouseZoneManager } from './MouseZoneManager';
import { AccessibilityManager } from './AccessibilityManager';
@@ -44,17 +43,19 @@ import { DomRenderer } from './renderer/dom/DomRenderer';
import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Attributes, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { applyWindowsMode } from './WindowsMode';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { OptionsService } from 'common/services/OptionsService';
import { ICharSizeService } from 'browser/services/Services';
import { ICharSizeService, IRenderService, IMouseService } from 'browser/services/Services';
import { CharSizeService } from 'browser/services/CharSizeService';
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
import { Disposable } from 'common/Lifecycle';
import { IBufferSet, IBuffer } from 'common/buffer/Types';
import { Attributes } from 'common/buffer/Constants';
import { MouseService } from 'browser/services/MouseService';
// Let it work inside Node.js for automated testing purposes.
const document = (typeof window !== 'undefined') ? window.document : null;
@@ -110,7 +111,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
// browser services
private _charSizeService: ICharSizeService;
private _renderService: RenderService;
private _renderService: IRenderService;
private _mouseService: IMouseService;
// modes
public applicationKeypad: boolean;
@@ -176,7 +178,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
public viewport: IViewport;
private _compositionHelper: ICompositionHelper;
private _mouseZoneManager: IMouseZoneManager;
public mouseHelper: MouseHelper;
private _accessibilityManager: AccessibilityManager;
private _colorManager: ColorManager;
private _theme: ITheme;
@@ -590,11 +591,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.screenElement.appendChild(this._helperContainer);
fragment.appendChild(this.screenElement);
this._mouseZoneManager = new MouseZoneManager(this);
this.register(this._mouseZoneManager);
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
this.linkifier.attachToDom(this._mouseZoneManager);
this.textarea = document.createElement('textarea');
this.textarea.classList.add('xterm-helper-textarea');
this.textarea.setAttribute('aria-label', Strings.promptLabel);
@@ -627,6 +623,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._renderService.onRender(e => this._onRender.fire(e));
this.onResize(e => this._renderService.resize(e.cols, e.rows));
this._mouseService = new MouseService(this._renderService, this._charSizeService);
this._mouseZoneManager = new MouseZoneManager(this, this._mouseService);
this.register(this._mouseZoneManager);
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
this.linkifier.attachToDom(this._mouseZoneManager);
this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this._renderService.dimensions, this._charSizeService);
this.viewport.onThemeChange(this._colorManager.colors);
this.register(this.viewport);
@@ -637,7 +640,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.register(this.onFocus(() => this._renderService.onFocus()));
this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea()));
this.selectionManager = new SelectionManager(this, this._charSizeService, this._bufferService);
this.selectionManager = new SelectionManager(this, this._charSizeService, this._bufferService, this._mouseService);
this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire()));
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e)));
this.register(this.selectionManager.onRedrawRequest(e => this._renderService.onSelectionChanged(e.start, e.end, e.columnSelectMode)));
@@ -655,7 +658,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}));
this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this.selectionManager.refresh()));
this.mouseHelper = new MouseHelper(this._renderService, this._charSizeService);
// apply mouse event classes set by escape codes before terminal was attached
this.element.classList.toggle('enable-mouse-events', this.mouseEvents);
if (this.mouseEvents) {
@@ -700,7 +702,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
*/
private _setTheme(theme: ITheme): void {
this._theme = theme;
this._colorManager.setTheme(theme);
if (this._colorManager) {
this._colorManager.setTheme(theme);
}
if (this._renderService) {
this._renderService.setColors(this._colorManager.colors);
}
@@ -735,7 +739,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
button = getButton(ev);
// get mouse coordinates
pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.cols, self.rows);
pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows);
if (!pos) return;
sendEvent(button, pos);
@@ -761,7 +765,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
function sendMove(ev: MouseEvent): void {
let button = pressed;
const pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.cols, self.rows);
const pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows);
if (!pos) return;
// buttons marked as motions
@@ -890,10 +894,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
: ev.which !== null && ev.which !== undefined
? ev.which - 1
: null;
if (Browser.isMSIE) {
button = button === 1 ? 0 : button === 4 ? 1 : button;
}
break;
case 'mouseup':
button = 3;
+2 -1
View File
@@ -12,7 +12,8 @@ import * as path from 'path';
import * as pty from 'node-pty';
import { Terminal } from './Terminal';
import { IViewport } from './Types';
import { CellData, WHITESPACE_CELL_CHAR } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
class TestTerminal extends Terminal {
innerWrite(): void { this._innerWrite(); }
+2 -3
View File
@@ -11,8 +11,8 @@ import { Buffer } from 'common/buffer/Buffer';
import * as Browser from 'common/Platform';
import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm';
import { Terminal } from './Terminal';
import { AttributeData } from 'common/buffer/BufferLine';
import { IColorManager, IColorSet, IMouseHelper } from 'browser/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { IColorManager, IColorSet } from 'browser/Types';
import { IOptionsService } from 'common/services/Services';
import { EventEmitter } from 'common/EventEmitter';
@@ -122,7 +122,6 @@ export class MockTerminal implements ITerminal {
throw new Error('Method not implemented.');
}
bracketedPasteMode: boolean;
mouseHelper: IMouseHelper;
renderer: IRenderer;
linkifier: ILinkifier;
isFocused: boolean;
+4 -6
View File
@@ -5,8 +5,8 @@
import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm';
import { ICharset, IAttributeData, CharData } from 'common/Types';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { IColorSet, IMouseHelper } from 'browser/Types';
import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IColorSet } from 'browser/Types';
import { IOptionsService } from 'common/services/Services';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
@@ -54,8 +54,8 @@ export interface IInputHandlingTerminal {
viewport: IViewport;
selectionManager: ISelectionManager;
onA11yCharEmitter: EventEmitter<string>;
onA11yTabEmitter: EventEmitter<number>;
onA11yCharEmitter: IEventEmitter<string>;
onA11yTabEmitter: IEventEmitter<number>;
bell(): void;
focus(): void;
@@ -204,7 +204,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc
buffer: IBuffer;
buffers: IBufferSet;
isFocused: boolean;
mouseHelper: IMouseHelper;
viewport: IViewport;
bracketedPasteMode: boolean;
applicationCursor: boolean;
@@ -365,7 +364,6 @@ export interface IBrowser {
userAgent: string;
platform: string;
isFirefox: boolean;
isMSIE: boolean;
isMac: boolean;
isIpad: boolean;
isIphone: boolean;
+1 -1
View File
@@ -5,7 +5,7 @@
import { IDisposable } from 'xterm';
import { ITerminal } from './Types';
import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/BufferLine';
import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
export function applyWindowsMode(terminal: ITerminal): IDisposable {
// Winpty does not support wraparound mode which means that lines will never
+11 -1
View File
@@ -4,7 +4,7 @@
*/
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { ICharSizeService } from 'browser/services/Services';
import { ICharSizeService, IMouseService } from 'browser/services/Services';
export class MockCharSizeService implements ICharSizeService {
get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
@@ -12,3 +12,13 @@ export class MockCharSizeService implements ICharSizeService {
constructor(public width: number, public height: number) {}
measure(): void {}
}
export class MockMouseService implements IMouseService {
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
throw new Error('Not implemented');
}
public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined {
throw new Error('Not implemented');
}
}
-5
View File
@@ -20,8 +20,3 @@ export interface IColorSet {
selection: IColor;
ansi: IColor[];
}
export interface IMouseHelper {
getCoords(event: { clientX: number, clientY: number }, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined;
getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number | undefined, y: number | undefined };
}
+40
View File
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { getCoords } from 'browser/input/Mouse';
const CHAR_WIDTH = 10;
const CHAR_HEIGHT = 20;
describe('Mouse getCoords', () => {
let document: Document;
beforeEach(() => {
document = new jsdom.JSDOM('').window.document;
});
it('should return the cell that was clicked', () => {
let coords: [number, number] | undefined;
coords = getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [1, 1]);
coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [1, 1]);
coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [1, 2]);
coords = getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [2, 1]);
});
it('should ensure the coordinates are returned within the terminal bounds', () => {
let coords: [number, number] | undefined;
coords = getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [1, 1]);
// Event are double the cols/rows
coords = getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal');
});
});
+58
View File
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
export function getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
const rect = element.getBoundingClientRect();
return [event.clientX - rect.left, event.clientY - rect.top];
}
/**
* Gets coordinates within the terminal for a particular mouse event. The result
* is returned as an array in the form [x, y] instead of an object as it's a
* little faster and this function is used in some low level code.
* @param event The mouse event.
* @param element The terminal's container element.
* @param colCount The number of columns in the terminal.
* @param rowCount The number of rows n the terminal.
* @param isSelection Whether the request is for the selection or not. This will
* apply an offset to the x value such that the left half of the cell will
* select that cell and the right half will select the next cell.
*/
export function getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined {
// Coordinates cannot be measured if there are no valid
if (!hasValidCharSize) {
return undefined;
}
const coords = getCoordsRelativeToElement(event, element);
if (!coords) {
return undefined;
}
coords[0] = Math.ceil((coords[0] + (isSelection ? actualCellWidth / 2 : 0)) / actualCellWidth);
coords[1] = Math.ceil(coords[1] / actualCellHeight);
// Ensure coordinates are within the terminal viewport. Note that selections
// need an addition point of precision to cover the end point (as characters
// cover half of one char and half of the next).
coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));
coords[1] = Math.min(Math.max(coords[1], 1), rowCount);
return coords;
}
/**
* Gets coordinates within the terminal for a particular mouse event, wrapping
* them to the bounds of the terminal and adding 32 to both the x and y values
* as expected by xterm.
*/
export function getRawByteCoords(coords: [number, number] | undefined): { x: number, y: number } | undefined {
if (!coords) {
return undefined;
}
// xterm sends raw bytes and starts at 32 (SP) for each.
return { x: coords[0] + 32, y: coords[1] + 32 };
}
-49
View File
@@ -1,49 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { MouseHelper } from 'browser/input/MouseHelper';
import { MockCharSizeService } from 'browser/TestUtils.test';
const CHAR_WIDTH = 10;
const CHAR_HEIGHT = 20;
describe('MouseHelper.getCoords', () => {
let document: Document;
let mouseHelper: MouseHelper;
beforeEach(() => {
document = new jsdom.JSDOM('').window.document;
const mockRenderService = {
dimensions: {
actualCellWidth: CHAR_WIDTH,
actualCellHeight: CHAR_HEIGHT
}
};
mouseHelper = new MouseHelper(mockRenderService as any, new MockCharSizeService(CHAR_WIDTH, CHAR_HEIGHT));
});
it('should return the cell that was clicked', () => {
let coords: [number, number] | undefined;
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10);
assert.deepEqual(coords, [1, 1]);
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10);
assert.deepEqual(coords, [1, 1]);
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10);
assert.deepEqual(coords, [1, 2]);
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10);
assert.deepEqual(coords, [2, 1]);
});
it('should ensure the coordinates are returned within the terminal bounds', () => {
let coords: [number, number] | undefined;
coords = mouseHelper.getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10);
assert.deepEqual(coords, [1, 1]);
// Event are double the cols/rows
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10);
assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal');
});
});
-75
View File
@@ -1,75 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IMouseHelper } from 'browser/Types';
import { RenderService } from 'browser/services/RenderService';
import { ICharSizeService } from 'browser/services/Services';
export class MouseHelper implements IMouseHelper {
constructor(
private _renderService: RenderService,
private _charSizeService: ICharSizeService
) {
}
public static getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
const rect = element.getBoundingClientRect();
return [event.clientX - rect.left, event.clientY - rect.top];
}
/**
* Gets coordinates within the terminal for a particular mouse event. The result
* is returned as an array in the form [x, y] instead of an object as it's a
* little faster and this function is used in some low level code.
* @param event The mouse event.
* @param element The terminal's container element.
* @param colCount The number of columns in the terminal.
* @param rowCount The number of rows n the terminal.
* @param isSelection Whether the request is for the selection or not. This will
* apply an offset to the x value such that the left half of the cell will
* select that cell and the right half will select the next cell.
*/
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
// Coordinates cannot be measured if there are no valid
if (!this._charSizeService.hasValidSize) {
return undefined;
}
const coords = MouseHelper.getCoordsRelativeToElement(event, element);
if (!coords) {
return undefined;
}
coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderService.dimensions.actualCellWidth / 2 : 0)) / this._renderService.dimensions.actualCellWidth);
coords[1] = Math.ceil(coords[1] / this._renderService.dimensions.actualCellHeight);
// Ensure coordinates are within the terminal viewport. Note that selections
// need an addition point of precision to cover the end point (as characters
// cover half of one char and half of the next).
coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));
coords[1] = Math.min(Math.max(coords[1], 1), rowCount);
return coords;
}
/**
* Gets coordinates within the terminal for a particular mouse event, wrapping
* them to the bounds of the terminal and adding 32 to both the x and y values
* as expected by xterm.
* @param event The mouse event.
* @param element The terminal's container element.
* @param colCount The number of columns in the terminal.
* @param rowCount The number of rows in the terminal.
*/
public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number | undefined, y: number | undefined } {
const coords = this.getCoords(event, element, colCount, rowCount);
// xterm sends raw bytes and starts at 32 (SP) for each.
const x = coords ? coords[0] + 32 : undefined;
const y = coords ? coords[1] + 32 : undefined;
return { x, y };
}
}
+33
View File
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ICharSizeService, IRenderService, IMouseService } from './Services';
import { getCoords, getRawByteCoords } from 'browser/input/Mouse';
export class MouseService implements IMouseService {
constructor(
private readonly _renderService: IRenderService,
private readonly _charSizeService: ICharSizeService
) {
}
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
return getCoords(
event,
element,
colCount,
rowCount,
this._charSizeService.hasValidSize,
this._renderService.dimensions.actualCellWidth,
this._renderService.dimensions.actualCellHeight,
isSelection
);
}
public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined {
const coords = this.getCoords(event, element, colCount, rowCount);
return getRawByteCoords(coords);
}
}
+5 -3
View File
@@ -43,7 +43,7 @@ export class RenderService extends Disposable implements IRenderService {
this.register(this._renderDebouncer);
this._screenDprMonitor = new ScreenDprMonitor();
this._screenDprMonitor.setListener(() => this._renderer.onDevicePixelRatioChange());
this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange());
this.register(this._screenDprMonitor);
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
@@ -51,7 +51,7 @@ export class RenderService extends Disposable implements IRenderService {
// dprchange should handle this case, we need this as well for browsers that don't support the
// matchMedia query.
this.register(addDisposableDomListener(window, 'resize', () => this._renderer.onDevicePixelRatioChange()));
this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange()));
// Detect whether IntersectionObserver is detected and enable renderer pause
// and resume based on terminal visibility if so
@@ -105,13 +105,14 @@ export class RenderService extends Disposable implements IRenderService {
// TODO: RenderCoordinator should be the only one to dispose the renderer
this._renderer.dispose();
this._renderer = renderer;
this.refreshRows(0, this._rowCount - 1);
}
private _fullRefresh(): void {
if (this._isPaused) {
this._needsFullRefresh = true;
} else {
this.refreshRows(0, this._rowCount);
this.refreshRows(0, this._rowCount - 1);
}
}
@@ -122,6 +123,7 @@ export class RenderService extends Disposable implements IRenderService {
public onDevicePixelRatioChange(): void {
this._renderer.onDevicePixelRatioChange();
this.refreshRows(0, this._rowCount - 1);
}
public onResize(cols: number, rows: number): void {
+5
View File
@@ -17,6 +17,11 @@ export interface ICharSizeService {
measure(): void;
}
export interface IMouseService {
getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined;
getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined;
}
export interface IRenderService {
onDimensionsChange: IEvent<IRenderDimensions>;
onRender: IEvent<{ start: number, end: number }>;
+6 -1
View File
@@ -13,7 +13,12 @@ export interface IEvent<T> {
(listener: (e: T) => any): IDisposable;
}
export class EventEmitter<T> {
export interface IEventEmitter<T> {
event: IEvent<T>;
fire(data: T): void;
}
export class EventEmitter<T> implements IEventEmitter<T> {
private _listeners: IListener<T>[] = [];
private _event?: IEvent<T>;
-1
View File
@@ -19,7 +19,6 @@ const platform = (isNode) ? 'node' : navigator.platform;
export const isFirefox = !!~userAgent.indexOf('Firefox');
export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
export const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');
// Find the users platform. We use this to interpret the meta key
// and ISO third level shifts.
+4 -13
View File
@@ -3,22 +3,13 @@
* @license MIT
*/
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IDeleteEvent, IInsertEvent } from 'common/CircularList';
export const DEFAULT_COLOR = 256;
export interface IDisposable {
dispose(): void;
}
export interface IEventEmitter {
on(type: string, listener: (...args: any[]) => void): void;
off(type: string, listener: (...args: any[]) => void): void;
emit(type: string, data?: any): void;
addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable;
}
export type XtermListener = (...args: any[]) => void;
/**
@@ -40,11 +31,11 @@ export interface ICircularList<T> {
maxLength: number;
isFull: boolean;
onDeleteEmitter: EventEmitter<IDeleteEvent>;
onDeleteEmitter: IEventEmitter<IDeleteEvent>;
onDelete: IEvent<IDeleteEvent>;
onInsertEmitter: EventEmitter<IInsertEvent>;
onInsertEmitter: IEventEmitter<IInsertEvent>;
onInsert: IEvent<IInsertEvent>;
onTrimEmitter: EventEmitter<number>;
onTrimEmitter: IEventEmitter<number>;
onTrim: IEvent<number>;
get(index: number): T | undefined;
+68
View File
@@ -0,0 +1,68 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IAttributeData, IColorRGB } from 'common/Types';
import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
export class AttributeData implements IAttributeData {
static toColorRGB(value: number): IColorRGB {
return [
value >>> Attributes.RED_SHIFT & 255,
value >>> Attributes.GREEN_SHIFT & 255,
value & 255
];
}
static fromColorRGB(value: IColorRGB): number {
return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255;
}
public clone(): IAttributeData {
const newObj = new AttributeData();
newObj.fg = this.fg;
newObj.bg = this.bg;
return newObj;
}
// data
public fg: number = 0;
public bg: number = 0;
// flags
public isInverse(): number { return this.fg & FgFlags.INVERSE; }
public isBold(): number { return this.fg & FgFlags.BOLD; }
public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; }
public isBlink(): number { return this.fg & FgFlags.BLINK; }
public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; }
public isItalic(): number { return this.bg & BgFlags.ITALIC; }
public isDim(): number { return this.bg & BgFlags.DIM; }
// color modes
public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; }
public getBgColorMode(): number { return this.bg & Attributes.CM_MASK; }
public isFgRGB(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; }
public isBgRGB(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; }
public isFgPalette(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; }
public isBgPalette(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.bg & Attributes.CM_MASK) === Attributes.CM_P256; }
public isFgDefault(): boolean { return (this.fg & Attributes.CM_MASK) === 0; }
public isBgDefault(): boolean { return (this.bg & Attributes.CM_MASK) === 0; }
// colors
public getFgColor(): number {
switch (this.fg & Attributes.CM_MASK) {
case Attributes.CM_P16:
case Attributes.CM_P256: return this.fg & Attributes.PCOLOR_MASK;
case Attributes.CM_RGB: return this.fg & Attributes.RGB_MASK;
default: return -1; // CM_DEFAULT defaults to -1
}
}
public getBgColor(): number {
switch (this.bg & Attributes.CM_MASK) {
case Attributes.CM_P16:
case Attributes.CM_P256: return this.bg & Attributes.PCOLOR_MASK;
case Attributes.CM_RGB: return this.bg & Attributes.RGB_MASK;
default: return -1; // CM_DEFAULT defaults to -1
}
}
}
+2 -1
View File
@@ -7,7 +7,8 @@ import { assert } from 'chai';
import { Buffer } from 'common/buffer/Buffer';
import { CircularList } from 'common/CircularList';
import { MockOptionsService, MockBufferService } from 'common/TestUtils.test';
import { BufferLine, CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
const INIT_COLS = 80;
const INIT_ROWS = 24;
+6 -2
View File
@@ -6,7 +6,9 @@
import { CircularList, IInsertEvent } from 'common/CircularList';
import { IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from 'common/buffer/Types';
import { IBufferLine, ICellData, IAttributeData } from 'common/Types';
import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from 'common/buffer/Constants';
import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from 'common/buffer/BufferReflow';
import { Marker } from 'common/buffer/Marker';
import { IOptionsService, IBufferService } from 'common/services/Services';
@@ -203,6 +205,7 @@ export class Buffer implements IBuffer {
this.lines.trimStart(amountToTrim);
this.ybase = Math.max(this.ybase - amountToTrim, 0);
this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
this.savedY = Math.max(this.savedY - amountToTrim, 0);
}
this.lines.maxLength = newMaxLength;
}
@@ -213,7 +216,6 @@ export class Buffer implements IBuffer {
if (addToY) {
this.y += addToY;
}
this.savedY = Math.min(this.savedY, newRows - 1);
this.savedX = Math.min(this.savedX, newCols - 1);
this.scrollTop = 0;
@@ -282,6 +284,7 @@ export class Buffer implements IBuffer {
this.ybase--;
}
}
this.savedY = Math.max(this.savedY - countRemoved, 0);
}
private _reflowSmaller(newCols: number, newRows: number): void {
@@ -393,6 +396,7 @@ export class Buffer implements IBuffer {
}
}
}
this.savedY = Math.min(this.savedY + linesToAdd, this.ybase + newRows - 1);
}
// Rearrange lines in the buffer if there are any insertions, this is done at the end rather
+3 -1
View File
@@ -3,7 +3,9 @@
* @license MIT
*/
import * as chai from 'chai';
import { BufferLine, CellData, Content, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './BufferLine';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR, Content } from 'common/buffer/Constants';
import { BufferLine } from 'common/buffer//BufferLine';
import { CellData } from 'common/buffer/CellData';
import { CharData, IBufferLine } from '../Types';
class TestBufferLine extends BufferLine {
+5 -276
View File
@@ -2,33 +2,12 @@
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { DEFAULT_COLOR, CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from 'common/Types';
import { CharData, IBufferLine, ICellData } from 'common/Types';
import { stringFromCodePoint } from 'common/input/TextDecoder';
export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
export const CHAR_DATA_ATTR_INDEX = 0;
export const CHAR_DATA_CHAR_INDEX = 1;
export const CHAR_DATA_WIDTH_INDEX = 2;
export const CHAR_DATA_CODE_INDEX = 3;
/**
* Null cell - a real empty cell (containing nothing).
* Note that code should always be 0 for a null cell as
* several test condition of the buffer line rely on this.
*/
export const NULL_CELL_CHAR = '';
export const NULL_CELL_WIDTH = 1;
export const NULL_CELL_CODE = 0;
/**
* Whitespace cell.
* This is meant as a replacement for empty cells when needed
* during rendering lines to preserve correct aligment.
*/
export const WHITESPACE_CELL_CHAR = ' ';
export const WHITESPACE_CELL_WIDTH = 1;
export const WHITESPACE_CELL_CODE = 32;
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData';
/**
* buffer memory layout:
@@ -56,258 +35,8 @@ const enum Cell {
BG = 2 // currently unused
}
/**
* Bitmasks for accessing data in `content`.
*/
export const enum Content {
/**
* bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)
* read: `codepoint = content & Content.codepointMask;`
* write: `content |= codepoint & Content.codepointMask;`
* shortcut if precondition `codepoint <= 0x10FFFF` is met:
* `content |= codepoint;`
*/
CODEPOINT_MASK = 0x1FFFFF,
/**
* bit 22 flag indication whether a cell contains combined content
* read: `isCombined = content & Content.isCombined;`
* set: `content |= Content.isCombined;`
* clear: `content &= ~Content.isCombined;`
*/
IS_COMBINED_MASK = 0x200000, // 1 << 21
/**
* bit 1..22 mask to check whether a cell contains any string data
* we need to check for codepoint and isCombined bits to see
* whether a cell contains anything
* read: `isEmpty = !(content & Content.hasContent)`
*/
HAS_CONTENT_MASK = 0x3FFFFF,
/**
* bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)
* read: `width = (content & Content.widthMask) >> Content.widthShift;`
* `hasWidth = content & Content.widthMask;`
* as long as wcwidth is highest value in `content`:
* `width = content >> Content.widthShift;`
* write: `content |= (width << Content.widthShift) & Content.widthMask;`
* shortcut if precondition `0 <= width <= 3` is met:
* `content |= width << Content.widthShift;`
*/
WIDTH_MASK = 0xC00000, // 3 << 22
WIDTH_SHIFT = 22
}
export const enum Attributes {
/**
* bit 1..8 blue in RGB, color in P256 and P16
*/
BLUE_MASK = 0xFF,
BLUE_SHIFT = 0,
PCOLOR_MASK = 0xFF,
PCOLOR_SHIFT = 0,
/**
* bit 9..16 green in RGB
*/
GREEN_MASK = 0xFF00,
GREEN_SHIFT = 8,
/**
* bit 17..24 red in RGB
*/
RED_MASK = 0xFF0000,
RED_SHIFT = 16,
/**
* bit 25..26 color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3)
*/
CM_MASK = 0x3000000,
CM_DEFAULT = 0,
CM_P16 = 0x1000000,
CM_P256 = 0x2000000,
CM_RGB = 0x3000000,
/**
* bit 1..24 RGB room
*/
RGB_MASK = 0xFFFFFF
}
export const enum FgFlags {
/**
* bit 27..31 (32th bit unused)
*/
INVERSE = 0x4000000,
BOLD = 0x8000000,
UNDERLINE = 0x10000000,
BLINK = 0x20000000,
INVISIBLE = 0x40000000
}
export const enum BgFlags {
/**
* bit 27..32 (upper 4 unused)
*/
ITALIC = 0x4000000,
DIM = 0x8000000
}
export class AttributeData implements IAttributeData {
static toColorRGB(value: number): IColorRGB {
return [
value >>> Attributes.RED_SHIFT & 255,
value >>> Attributes.GREEN_SHIFT & 255,
value & 255
];
}
static fromColorRGB(value: IColorRGB): number {
return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255;
}
public clone(): IAttributeData {
const newObj = new AttributeData();
newObj.fg = this.fg;
newObj.bg = this.bg;
return newObj;
}
// data
public fg: number = 0;
public bg: number = 0;
// flags
public isInverse(): number { return this.fg & FgFlags.INVERSE; }
public isBold(): number { return this.fg & FgFlags.BOLD; }
public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; }
public isBlink(): number { return this.fg & FgFlags.BLINK; }
public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; }
public isItalic(): number { return this.bg & BgFlags.ITALIC; }
public isDim(): number { return this.bg & BgFlags.DIM; }
// color modes
public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; }
public getBgColorMode(): number { return this.bg & Attributes.CM_MASK; }
public isFgRGB(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; }
public isBgRGB(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; }
public isFgPalette(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; }
public isBgPalette(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.bg & Attributes.CM_MASK) === Attributes.CM_P256; }
public isFgDefault(): boolean { return (this.fg & Attributes.CM_MASK) === 0; }
public isBgDefault(): boolean { return (this.bg & Attributes.CM_MASK) === 0; }
// colors
public getFgColor(): number {
switch (this.fg & Attributes.CM_MASK) {
case Attributes.CM_P16:
case Attributes.CM_P256: return this.fg & Attributes.PCOLOR_MASK;
case Attributes.CM_RGB: return this.fg & Attributes.RGB_MASK;
default: return -1; // CM_DEFAULT defaults to -1
}
}
public getBgColor(): number {
switch (this.bg & Attributes.CM_MASK) {
case Attributes.CM_P16:
case Attributes.CM_P256: return this.bg & Attributes.PCOLOR_MASK;
case Attributes.CM_RGB: return this.bg & Attributes.RGB_MASK;
default: return -1; // CM_DEFAULT defaults to -1
}
}
}
export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
/**
* CellData - represents a single Cell in the terminal buffer.
*/
export class CellData extends AttributeData implements ICellData {
/** Helper to create CellData from CharData. */
public static fromCharData(value: CharData): CellData {
const obj = new CellData();
obj.setFromCharData(value);
return obj;
}
/** Primitives from terminal buffer. */
public content: number = 0;
public fg: number = 0;
public bg: number = 0;
public combinedData: string = '';
/** Whether cell contains a combined string. */
public isCombined(): number {
return this.content & Content.IS_COMBINED_MASK;
}
/** Width of the cell. */
public getWidth(): number {
return this.content >> Content.WIDTH_SHIFT;
}
/** JS string of the content. */
public getChars(): string {
if (this.content & Content.IS_COMBINED_MASK) {
return this.combinedData;
}
if (this.content & Content.CODEPOINT_MASK) {
return stringFromCodePoint(this.content & Content.CODEPOINT_MASK);
}
return '';
}
/**
* Codepoint of cell
* Note this returns the UTF32 codepoint of single chars,
* if content is a combined string it returns the codepoint
* of the last char in string to be in line with code in CharData.
* */
public getCode(): number {
return (this.isCombined())
? this.combinedData.charCodeAt(this.combinedData.length - 1)
: this.content & Content.CODEPOINT_MASK;
}
/** Set data from CharData */
public setFromCharData(value: CharData): void {
this.fg = value[CHAR_DATA_ATTR_INDEX];
this.bg = 0;
let combined = false;
// surrogates and combined strings need special treatment
if (value[CHAR_DATA_CHAR_INDEX].length > 2) {
combined = true;
} else if (value[CHAR_DATA_CHAR_INDEX].length === 2) {
const code = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0);
// if the 2-char string is a surrogate create single codepoint
// everything else is combined
if (0xD800 <= code && code <= 0xDBFF) {
const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1);
if (0xDC00 <= second && second <= 0xDFFF) {
this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
} else {
combined = true;
}
} else {
combined = true;
}
} else {
this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
}
if (combined) {
this.combinedData = value[CHAR_DATA_CHAR_INDEX];
this.content = Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
}
}
/** Get data as CharData. */
public getAsCharData(): CharData {
return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
}
}
/**
* Typed array based bufferline implementation.
*
+2 -1
View File
@@ -3,7 +3,8 @@
* @license MIT
*/
import { assert } from 'chai';
import { BufferLine, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from 'common/buffer/BufferLine';
import { BufferLine } from 'common/buffer/BufferLine';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from 'common/buffer/Constants';
import { reflowSmallerGetNewLineLengths } from 'common/buffer/BufferReflow';
describe('BufferReflow', () => {
+93
View File
@@ -0,0 +1,93 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { CharData, ICellData } from 'common/Types';
import { stringFromCodePoint } from 'common/input/TextDecoder';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, Content } from 'common/buffer/Constants';
import { AttributeData } from 'common/buffer/AttributeData';
/**
* CellData - represents a single Cell in the terminal buffer.
*/
export class CellData extends AttributeData implements ICellData {
/** Helper to create CellData from CharData. */
public static fromCharData(value: CharData): CellData {
const obj = new CellData();
obj.setFromCharData(value);
return obj;
}
/** Primitives from terminal buffer. */
public content: number = 0;
public fg: number = 0;
public bg: number = 0;
public combinedData: string = '';
/** Whether cell contains a combined string. */
public isCombined(): number {
return this.content & Content.IS_COMBINED_MASK;
}
/** Width of the cell. */
public getWidth(): number {
return this.content >> Content.WIDTH_SHIFT;
}
/** JS string of the content. */
public getChars(): string {
if (this.content & Content.IS_COMBINED_MASK) {
return this.combinedData;
}
if (this.content & Content.CODEPOINT_MASK) {
return stringFromCodePoint(this.content & Content.CODEPOINT_MASK);
}
return '';
}
/**
* Codepoint of cell
* Note this returns the UTF32 codepoint of single chars,
* if content is a combined string it returns the codepoint
* of the last char in string to be in line with code in CharData.
* */
public getCode(): number {
return (this.isCombined())
? this.combinedData.charCodeAt(this.combinedData.length - 1)
: this.content & Content.CODEPOINT_MASK;
}
/** Set data from CharData */
public setFromCharData(value: CharData): void {
this.fg = value[CHAR_DATA_ATTR_INDEX];
this.bg = 0;
let combined = false;
// surrogates and combined strings need special treatment
if (value[CHAR_DATA_CHAR_INDEX].length > 2) {
combined = true;
}
else if (value[CHAR_DATA_CHAR_INDEX].length === 2) {
const code = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0);
// if the 2-char string is a surrogate create single codepoint
// everything else is combined
if (0xD800 <= code && code <= 0xDBFF) {
const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1);
if (0xDC00 <= second && second <= 0xDFFF) {
this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
}
else {
combined = true;
}
}
else {
combined = true;
}
}
else {
this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
}
if (combined) {
this.combinedData = value[CHAR_DATA_CHAR_INDEX];
this.content = Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
}
}
/** Get data as CharData. */
public getAsCharData(): CharData {
return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
}
}
+128
View File
@@ -0,0 +1,128 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
export const DEFAULT_COLOR = 256;
export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
export const CHAR_DATA_ATTR_INDEX = 0;
export const CHAR_DATA_CHAR_INDEX = 1;
export const CHAR_DATA_WIDTH_INDEX = 2;
export const CHAR_DATA_CODE_INDEX = 3;
/**
* Null cell - a real empty cell (containing nothing).
* Note that code should always be 0 for a null cell as
* several test condition of the buffer line rely on this.
*/
export const NULL_CELL_CHAR = '';
export const NULL_CELL_WIDTH = 1;
export const NULL_CELL_CODE = 0;
/**
* Whitespace cell.
* This is meant as a replacement for empty cells when needed
* during rendering lines to preserve correct aligment.
*/
export const WHITESPACE_CELL_CHAR = ' ';
export const WHITESPACE_CELL_WIDTH = 1;
export const WHITESPACE_CELL_CODE = 32;
/**
* Bitmasks for accessing data in `content`.
*/
export const enum Content {
/**
* bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)
* read: `codepoint = content & Content.codepointMask;`
* write: `content |= codepoint & Content.codepointMask;`
* shortcut if precondition `codepoint <= 0x10FFFF` is met:
* `content |= codepoint;`
*/
CODEPOINT_MASK = 0x1FFFFF,
/**
* bit 22 flag indication whether a cell contains combined content
* read: `isCombined = content & Content.isCombined;`
* set: `content |= Content.isCombined;`
* clear: `content &= ~Content.isCombined;`
*/
IS_COMBINED_MASK = 0x200000, // 1 << 21
/**
* bit 1..22 mask to check whether a cell contains any string data
* we need to check for codepoint and isCombined bits to see
* whether a cell contains anything
* read: `isEmpty = !(content & Content.hasContent)`
*/
HAS_CONTENT_MASK = 0x3FFFFF,
/**
* bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)
* read: `width = (content & Content.widthMask) >> Content.widthShift;`
* `hasWidth = content & Content.widthMask;`
* as long as wcwidth is highest value in `content`:
* `width = content >> Content.widthShift;`
* write: `content |= (width << Content.widthShift) & Content.widthMask;`
* shortcut if precondition `0 <= width <= 3` is met:
* `content |= width << Content.widthShift;`
*/
WIDTH_MASK = 0xC00000, // 3 << 22
WIDTH_SHIFT = 22
}
export const enum Attributes {
/**
* bit 1..8 blue in RGB, color in P256 and P16
*/
BLUE_MASK = 0xFF,
BLUE_SHIFT = 0,
PCOLOR_MASK = 0xFF,
PCOLOR_SHIFT = 0,
/**
* bit 9..16 green in RGB
*/
GREEN_MASK = 0xFF00,
GREEN_SHIFT = 8,
/**
* bit 17..24 red in RGB
*/
RED_MASK = 0xFF0000,
RED_SHIFT = 16,
/**
* bit 25..26 color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3)
*/
CM_MASK = 0x3000000,
CM_DEFAULT = 0,
CM_P16 = 0x1000000,
CM_P256 = 0x2000000,
CM_RGB = 0x3000000,
/**
* bit 1..24 RGB room
*/
RGB_MASK = 0xFFFFFF
}
export const enum FgFlags {
/**
* bit 27..31 (32th bit unused)
*/
INVERSE = 0x4000000,
BOLD = 0x8000000,
UNDERLINE = 0x10000000,
BLINK = 0x20000000,
INVISIBLE = 0x40000000
}
export const enum BgFlags {
/**
* bit 27..32 (upper 4 unused)
*/
ITALIC = 0x4000000,
DIM = 0x8000000
}
+45
View File
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
/**
* Internal states of EscapeSequenceParser.
*/
export const enum ParserState {
GROUND = 0,
ESCAPE = 1,
ESCAPE_INTERMEDIATE = 2,
CSI_ENTRY = 3,
CSI_PARAM = 4,
CSI_INTERMEDIATE = 5,
CSI_IGNORE = 6,
SOS_PM_APC_STRING = 7,
OSC_STRING = 8,
DCS_ENTRY = 9,
DCS_PARAM = 10,
DCS_IGNORE = 11,
DCS_INTERMEDIATE = 12,
DCS_PASSTHROUGH = 13
}
/**
* Internal actions of EscapeSequenceParser.
*/
export const enum ParserAction {
IGNORE = 0,
ERROR = 1,
PRINT = 2,
EXECUTE = 3,
OSC_START = 4,
OSC_PUT = 5,
OSC_END = 6,
CSI_DISPATCH = 7,
PARAM = 8,
COLLECT = 9,
ESC_DISPATCH = 10,
CLEAR = 11,
DCS_HOOK = 12,
DCS_PUT = 13,
DCS_UNHOOK = 14
}
@@ -3,10 +3,11 @@
* @license MIT
*/
import { ParserState, IDcsHandler, IParsingState } from 'common/parser/Types';
import { IDcsHandler, IParsingState } from 'common/parser/Types';
import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser';
import * as chai from 'chai';
import { StringToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { ParserState } from 'common/parser/Constants';
function r(a: number, b: number): string[] {
let c = b - a;
+2 -1
View File
@@ -3,7 +3,8 @@
* @license MIT
*/
import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types';
import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types';
import { ParserState, ParserAction } from 'common/parser/Constants';
import { Disposable } from 'common/Lifecycle';
import { utf32ToString } from 'common/input/TextDecoder';
import { IDisposable } from 'common/Types';
+1 -41
View File
@@ -4,47 +4,7 @@
*/
import { IDisposable } from 'common/Types';
/**
* Internal states of EscapeSequenceParser.
*/
export const enum ParserState {
GROUND = 0,
ESCAPE = 1,
ESCAPE_INTERMEDIATE = 2,
CSI_ENTRY = 3,
CSI_PARAM = 4,
CSI_INTERMEDIATE = 5,
CSI_IGNORE = 6,
SOS_PM_APC_STRING = 7,
OSC_STRING = 8,
DCS_ENTRY = 9,
DCS_PARAM = 10,
DCS_IGNORE = 11,
DCS_INTERMEDIATE = 12,
DCS_PASSTHROUGH = 13
}
/**
* Internal actions of EscapeSequenceParser.
*/
export const enum ParserAction {
IGNORE = 0,
ERROR = 1,
PRINT = 2,
EXECUTE = 3,
OSC_START = 4,
OSC_PUT = 5,
OSC_END = 6,
CSI_DISPATCH = 7,
PARAM = 8,
COLLECT = 9,
ESC_DISPATCH = 10,
CLEAR = 11,
DCS_HOOK = 12,
DCS_PUT = 13,
DCS_UNHOOK = 14
}
import { ParserState } from 'common/parser/Constants';
/**
* Internal state of EscapeSequenceParser.
+4 -2
View File
@@ -6,6 +6,7 @@
import { ITerminal } from '../Types';
import { IBufferLine, ICircularList } from 'common/Types';
import { C0 } from 'common/data/EscapeSequences';
import { IMouseService } from 'browser/services/Services';
const enum Direction {
UP = 'A',
@@ -23,13 +24,14 @@ export class AltClickHandler {
constructor(
private _mouseEvent: MouseEvent,
private _terminal: ITerminal
private _terminal: ITerminal,
private readonly _mouseService: IMouseService
) {
this._lines = this._terminal.buffer.lines;
this._startCol = this._terminal.buffer.x;
this._startRow = this._terminal.buffer.y;
const coordinates = this._terminal.mouseHelper.getCoords(
const coordinates = this._mouseService.getCoords(
this._mouseEvent,
this._terminal.element,
this._terminal.cols,
+6 -3
View File
@@ -6,12 +6,15 @@
import { IRenderLayer } from './Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { ITerminal } from '../Types';
import { ICellData, DEFAULT_COLOR } from 'common/Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types';
import { ICellData } from 'common/Types';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
import { IGlyphIdentifier } from './atlas/Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from './atlas/Constants';
import { BaseCharAtlas } from './atlas/BaseCharAtlas';
import { acquireCharAtlas } from './atlas/CharAtlasCache';
import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/BufferLine';
import { AttributeData } from 'common/buffer/AttributeData';
import { IColorSet } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
+2 -1
View File
@@ -10,8 +10,9 @@ import { CircularList } from 'common/CircularList';
import { ICharacterJoinerRegistry } from './Types';
import { CharacterJoinerRegistry } from './CharacterJoinerRegistry';
import { BufferLine, CellData } from 'common/buffer/BufferLine';
import { BufferLine } from 'common/buffer/BufferLine';
import { IBufferLine } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
describe('CharacterJoinerRegistry', () => {
let registry: ICharacterJoinerRegistry;
+3 -1
View File
@@ -6,7 +6,9 @@
import { ITerminal } from '../Types';
import { IBufferLine, ICellData, CharData } from 'common/Types';
import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types';
import { CellData, Content, AttributeData, WHITESPACE_CELL_CHAR } from 'common/buffer/BufferLine';
import { AttributeData } from 'common/buffer/AttributeData';
import { WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
export class JoinedCellData extends AttributeData implements ICellData {
private _width: number;
+1 -1
View File
@@ -7,7 +7,7 @@ import { IRenderDimensions } from 'browser/renderer/Types';
import { BaseRenderLayer } from './BaseRenderLayer';
import { ITerminal } from '../Types';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { IColorSet } from 'browser/Types';
interface ICursorState {
+1 -1
View File
@@ -6,7 +6,7 @@
import { ILinkifierEvent, ITerminal, ILinkifierAccessor } from '../Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { BaseRenderLayer } from './BaseRenderLayer';
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
import { INVERTED_DEFAULT_COLOR } from './atlas/Constants';
import { is256Color } from './atlas/CharAtlasUtils';
import { IColorSet } from 'browser/Types';
+3 -1
View File
@@ -9,9 +9,11 @@ import { ITerminal } from '../Types';
import { CharData, ICellData } from 'common/Types';
import { GridCache } from './GridCache';
import { BaseRenderLayer } from './BaseRenderLayer';
import { CellData, AttributeData, Content, NULL_CELL_CODE } from 'common/buffer/BufferLine';
import { AttributeData } from 'common/buffer/AttributeData';
import { NULL_CELL_CODE, Content } from 'common/buffer/Constants';
import { JoinedCellData } from './CharacterJoinerRegistry';
import { IColorSet } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
/**
* This CharData looks like a null character, which will forc a clear and render
-13
View File
@@ -8,19 +8,6 @@ import { IDisposable } from 'xterm';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
/**
* Flags used to render terminal text properly.
*/
export const enum FLAGS {
BOLD = 1,
UNDERLINE = 2,
BLINK = 4,
INVERSE = 8,
INVISIBLE = 16,
DIM = 32,
ITALIC = 64
}
export interface IRenderLayer extends IDisposable {
/**
* Called when the terminal loses focus.
+1 -1
View File
@@ -5,7 +5,7 @@
import { ITerminal } from '../../Types';
import { ICharAtlasConfig } from './Types';
import { DEFAULT_COLOR } from 'common/Types';
import { DEFAULT_COLOR } from 'common/buffer/Constants';
import { IColorSet } from 'browser/Types';
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
+9
View File
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
export const INVERTED_DEFAULT_COLOR = 257;
export const DIM_OPACITY = 0.5;
export const CHAR_ATLAS_CELL_SPACING = 1;
+2 -1
View File
@@ -3,7 +3,8 @@
* @license MIT
*/
import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types';
import { IGlyphIdentifier, ICharAtlasConfig } from './Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from './Constants';
import { BaseCharAtlas } from './BaseCharAtlas';
import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager';
import { LRUMap } from './LRUMap';
@@ -6,11 +6,6 @@
import { FontWeight } from 'xterm';
import { IColorSet } from 'browser/Types';
export const INVERTED_DEFAULT_COLOR = 257;
export const DIM_OPACITY = 0.5;
export const CHAR_ATLAS_CELL_SPACING = 1;
export interface IGlyphIdentifier {
chars: string;
code: number;
+9 -4
View File
@@ -6,7 +6,7 @@
import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
import { ILinkifierEvent, ITerminal } from '../../Types';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Constants';
import { Disposable } from 'common/Lifecycle';
import { IColorSet } from 'browser/Types';
import { ICharSizeService } from 'browser/services/Services';
@@ -167,9 +167,14 @@ export class DomRenderer extends Disposable implements IRenderer {
// Blink animation
styles +=
`@keyframes blink {` +
` 0% { opacity: 1.0; }` +
` 50% { opacity: 0.0; }` +
` 100% { opacity: 1.0; }` +
` 0% {` +
` background-color: ${this._colors.cursor.css};` +
` color: ${this._colors.cursorAccent.css};` +
` }` +
` 50% {` +
` background-color: ${this._colors.cursorAccent.css};` +
` color: ${this._colors.cursor.css};` +
` }` +
`}`;
// Cursor
styles +=
@@ -6,9 +6,11 @@
import jsdom = require('jsdom');
import { assert } from 'chai';
import { DomRendererRowFactory } from './DomRendererRowFactory';
import { BufferLine, CellData, FgFlags, BgFlags, Attributes, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, BgFlags, Attributes } from 'common/buffer/Constants';
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { ITerminalOptions } from '../../Types';
import { IBufferLine } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
+4 -2
View File
@@ -5,8 +5,10 @@
import { ITerminalOptions } from '../../Types';
import { IBufferLine } from 'common/Types';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
import { CellData, AttributeData, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/BufferLine';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Constants';
import { AttributeData } from 'common/buffer/AttributeData';
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
export const BOLD_CLASS = 'xterm-bold';
export const DIM_CLASS = 'xterm-dim';
+17
View File
@@ -292,6 +292,23 @@ describe('InputHandler Integration Tests', function(): void {
assert.deepEqual(await getLinesAsArray(3), ['#', ' #', 'abcd####']);
});
});
describe('ESC', () => {
describe('DECRC: Save cursor, ESC 7', () => {
it('should save the absolute cursor position so resizing restores to the correct position', async () => {
await page.evaluate(`
window.term.resize(10, 2);
window.term.write('1\\n\\r2\\n\\r3\\n\\r4\\n\\r5');
window.term.write('\\x1b7\\x1b[?47h');
`);
await page.evaluate(`
window.term.resize(10, 4);
window.term.write('\\x1b[?47l\\x1b8');
`);
assert.deepEqual(await getCursor(), {col: 1, row: 3});
});
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
+1 -1
View File
@@ -191,7 +191,7 @@ declare module 'xterm' {
cursor?: string,
/** The accent color of the cursor (fg color for a block cursor) */
cursorAccent?: string,
/** The selection color (can be transparent) */
/** The selection background color (can be transparent) */
selection?: string,
/** ANSI black (eg. `\x1b[30m`) */
black?: string,