Merge branch 'master' into benchmark_integration
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
@@ -2,3 +2,4 @@
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = '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'
|
||||
};
|
||||
@@ -2,3 +2,4 @@
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
@@ -2,3 +2,4 @@
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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;
|
||||
|
||||
Vendored
+4
-6
@@ -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
@@ -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
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Vendored
+5
@@ -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 }>;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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,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 {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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()];
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Vendored
+1
-1
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user