Merge branch 'master' into left_right_scroll

This commit is contained in:
jerch
2019-10-24 18:07:35 +02:00
committed by GitHub
44 changed files with 490 additions and 128 deletions
+9 -2
View File
@@ -8,12 +8,19 @@ RUN apt-get update \
# Verify git and process tools are installed
RUN apt-get install -y git procps
# Install yarn
# Install yarn, puppeteer deps
RUN apt-get install -y curl apt-transport-https lsb-release \
&& curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \
&& echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends yarn
&& apt-get -y install --no-install-recommends \
yarn fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
# https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322921352
gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
# Clean up
RUN apt-get autoremove -y \
+2
View File
@@ -154,6 +154,8 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**PHP App Server**](https://github.com/cubiclesoft/php-app-server/): Create lightweight, installable almost-native applications for desktop OSes. ExecTerminal (nicely wraps the xterm.js Terminal), TerminalManager, and RunProcessSDK are self-contained, reusable ES5+ compliant Javascript components.
- [**NgTerminal**](https://github.com/qwefgh90/ng-terminal): NgTerminal is a web terminal that leverages xterm.js on Angular 7+. You can easily add it into your application by adding `<ng-terminal></ng-terminal>` into your component.
- [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet.
- [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks.
- [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-attach",
"version": "0.2.1",
"version": "0.3.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -21,7 +21,7 @@ describe('AttachAddon', () => {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
+119
View File
@@ -0,0 +1,119 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
const width = 1024;
const height = 768;
describe('FitAddon', () => {
before(async function(): Promise<any> {
this.timeout(20000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
await page.goto(APP);
await openTerminal();
});
after(async () => {
await browser.close();
});
it('no terminal', async function(): Promise<any> {
await page.evaluate(`window.fit = new FitAddon();`);
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
});
describe('proposeDimensions', () => {
afterEach(async () => {
return unloadFit();
});
it('default', async function(): Promise<any> {
await loadFit();
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 87,
rows: 26
});
});
it('width', async function(): Promise<any> {
await loadFit(1008);
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 110,
rows: 26
});
});
it('small', async function(): Promise<any> {
await loadFit(1, 1);
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 2,
rows: 1
});
});
});
describe('fit', () => {
afterEach(async () => {
return unloadFit();
});
it('default', async function(): Promise<any> {
await loadFit();
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 87);
assert.equal(await page.evaluate(`window.term.rows`), 26);
});
it('width', async function(): Promise<any> {
await loadFit(1008);
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 110);
assert.equal(await page.evaluate(`window.term.rows`), 26);
});
it('small', async function(): Promise<any> {
await loadFit(1, 1);
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 2);
assert.equal(await page.evaluate(`window.term.rows`), 1);
});
});
});
async function loadFit(width: number = 800, height: number = 450): Promise<void> {
await page.evaluate(`
window.fit = new FitAddon();
window.term.loadAddon(window.fit);
document.querySelector('#terminal-container').style.width='${width}px';
document.querySelector('#terminal-container').style.height='${height}px';
`);
}
async function unloadFit(): Promise<void> {
await page.evaluate(`window.fit.dispose();`);
}
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
if (options.rendererType === 'dom') {
await page.waitForSelector('.xterm-rows');
} else {
await page.waitForSelector('.xterm-text-layer');
}
}
+6 -3
View File
@@ -17,6 +17,9 @@ interface ITerminalDimensions {
cols: number;
}
const MINIMUM_COLS = 2;
const MINIMUM_ROWS = 1;
export class FitAddon implements ITerminalAddon {
private _terminal: Terminal | undefined;
@@ -49,7 +52,7 @@ export class FitAddon implements ITerminalAddon {
return undefined;
}
if (!this._terminal.element.parentElement) {
if (!this._terminal.element || !this._terminal.element.parentElement) {
return undefined;
}
@@ -71,8 +74,8 @@ export class FitAddon implements ITerminalAddon {
const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth;
const geometry = {
cols: Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight)
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth)),
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight))
};
return geometry;
}
@@ -15,13 +15,13 @@ const width = 800;
const height = 600;
describe('Search Tests', function (): void {
this.timeout(200000);
this.timeout(20000);
before(async function (): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
@@ -98,6 +98,14 @@ describe('Search Tests', function (): void {
await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'ABCD');
});
it('Search for single result twice should not unselect it', async () => {
await writeSync('abc def');
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
+13 -6
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm';
export interface ISearchOptions {
regex?: boolean;
@@ -59,12 +59,12 @@ export class SearchAddon implements ITerminalAddon {
let startCol = 0;
let startRow = 0;
let currentSelection: ISelectionPosition | undefined;
if (this._terminal.hasSelection()) {
const incremental = searchOptions ? searchOptions.incremental : false;
// Start from the selection end if there is a selection
// For incremental search, use existing row
const currentSelection = this._terminal.getSelectionPosition()!;
currentSelection = this._terminal.getSelectionPosition()!;
startRow = incremental ? currentSelection.startRow : currentSelection.endRow;
startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn;
}
@@ -97,6 +97,9 @@ export class SearchAddon implements ITerminalAddon {
}
}
// If there is only one result, return true.
if (!result && currentSelection) return true;
// Set selection and scroll if a result was found
return this._selectResult(result);
}
@@ -121,10 +124,11 @@ export class SearchAddon implements ITerminalAddon {
const isReverseSearch = true;
let startRow = this._terminal.buffer.baseY + this._terminal.rows;
let startCol = this._terminal.cols;
let result: ISearchResult | undefined = undefined;
let result: ISearchResult | undefined;
const incremental = searchOptions ? searchOptions.incremental : false;
let currentSelection: ISelectionPosition | undefined;
if (this._terminal.hasSelection()) {
const currentSelection = this._terminal.getSelectionPosition()!;
currentSelection = this._terminal.getSelectionPosition()!;
// Start from selection start if there is a selection
startRow = currentSelection.startRow;
startCol = currentSelection.startColumn;
@@ -161,6 +165,9 @@ export class SearchAddon implements ITerminalAddon {
}
}
// If there is only one result, return true.
if (!result && currentSelection) return true;
// Set selection and scroll if a result was found
return this._selectResult(result);
}
@@ -344,7 +351,7 @@ export class SearchAddon implements ITerminalAddon {
}
terminal.select(result.col, result.row, result.term.length);
// If it is not in the viewport then we scroll else it just gets selected
if (result.row > (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
if (result.row >= (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
let scroll = result.row - terminal.buffer.viewportY;
scroll = scroll - Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll);
@@ -20,7 +20,7 @@ describe('WebLinksAddon', () => {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
@@ -105,9 +105,6 @@ export class GlyphRenderer {
const gl = this._gl;
const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
if (program === undefined) {
throw new Error('Could not create WebGL program');
}
this._program = program;
// Uniform locations
@@ -22,7 +22,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
@@ -14,7 +14,7 @@ import { IWebGL2RenderingContext } from './Types';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel';
import { Disposable } from 'common/Lifecycle';
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants';
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal } from 'xterm';
import { getLuminance } from './ColorUtils';
import { IRenderLayer } from './renderLayer/Types';
@@ -92,10 +92,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
}
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
}
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
}
public onCursorMove(terminal: Terminal): void {
@@ -139,12 +139,17 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._clearCursor();
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
const cursorStyle = terminal.getOption('cursorStyle');
if (cursorStyle && cursorStyle !== 'block') {
this._cursorRenderers[cursorStyle](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
} else {
this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
}
this._ctx.restore();
this._state.x = terminal.buffer.cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = terminal.getOption('cursorStyle');
this._state.style = cursorStyle;
this._state.width = this._cell.getWidth();
return;
}
+6 -6
View File
@@ -20,7 +20,7 @@ jobs:
yarn
displayName: 'Install dependencies and build'
- script: |
yarn test-unit
yarn test-unit --forbid-only
displayName: 'Unit tests'
- script: |
yarn lint
@@ -38,7 +38,7 @@ jobs:
yarn
displayName: 'Install dependencies and build'
- script: |
yarn test-unit
yarn test-unit --forbid-only
displayName: 'Unit tests'
- script: |
yarn lint
@@ -56,7 +56,7 @@ jobs:
yarn
displayName: 'Install dependencies and build'
- script: |
yarn test-unit
yarn test-unit --forbid-only
displayName: 'Unit tests'
- script: |
yarn lint
@@ -80,7 +80,7 @@ jobs:
- script: |
yarn start &
sleep 10
yarn test-api --headless
yarn test-api --headless --forbid-only
displayName: 'Linux Integration tests'
- job: macOS_IntegrationTests
@@ -97,7 +97,7 @@ jobs:
- script: |
yarn start &
sleep 10
yarn test-api --headless
yarn test-api --headless --forbid-only
displayName: 'MacOS Integration tests'
- job: Release
@@ -107,7 +107,7 @@ jobs:
- Windows
- Linux_IntegrationTests
- macOS_IntegrationTests
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/release/*')))
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
pool:
vmImage: 'ubuntu-16.04'
steps:
+19 -1
View File
@@ -5,6 +5,7 @@
const cp = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
// Setup auth
@@ -18,8 +19,9 @@ if (isDryRun) {
const changedFiles = getChangedFilesInCommit('HEAD');
// Publish xterm if any files were changed outside of the addons directory
let isStableRelease = false;
if (changedFiles.some(e => e.search(/^addons\//) === -1)) {
checkAndPublishPackage(path.resolve(__dirname, '..'));
isStableRelease = checkAndPublishPackage(path.resolve(__dirname, '..'));
}
// Publish addons if any files were changed inside of the addon
@@ -39,6 +41,11 @@ addonPackageDirs.forEach(p => {
}
});
// Publish website if it's a stable release
if (isStableRelease) {
updateWebsite();
}
function checkAndPublishPackage(packageDir) {
const packageJson = require(path.join(packageDir, 'package.json'));
@@ -76,6 +83,8 @@ function checkAndPublishPackage(packageDir) {
}
console.groupEnd();
return isStableRelease;
}
function getNextBetaVersion(packageJson) {
@@ -115,3 +124,12 @@ function getChangedFilesInCommit(commit) {
const changedFiles = output.split('\n').filter(e => e.length > 0);
return changedFiles;
}
function updateWebsite() {
console.log('Updating website');
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'website-'));
const packageJson = require(path.join(path.resolve(__dirname, '..'), 'package.json'));
if (!isDryRun) {
cp.spawnSync('sh', [path.join(__dirname, 'update-website.sh'), packageJson.version], { cwd, stdio: [process.stdin, process.stdout, process.stderr] });
}
}
+12 -5
View File
@@ -15,15 +15,22 @@ let testFiles = [
'./out/**/*test.js'
];
// ability to inject particular test files via
// yarn test [testFileA testFileB ...]
let flagArgs = [];
if (process.argv.length > 2) {
testFiles = process.argv.slice(2);
const args = process.argv.slice(2);
flagArgs = args.filter(e => e.startsWith('--'));
// ability to inject particular test files via
// yarn test [testFileA testFileB ...]
files = args.filter(e => !e.startsWith('--'));
if (files.length) {
testFiles = files;
}
}
const run = cp.spawnSync(
path.resolve(__dirname, '../node_modules/.bin/mocha'),
testFiles,
[...testFiles, ...flagArgs],
{
cwd: path.resolve(__dirname, '..'),
env,
@@ -31,4 +38,4 @@ const run = cp.spawnSync(
}
);
process.exit(run.status);
process.exit(run.status);
+41
View File
@@ -0,0 +1,41 @@
#!/bin/sh
# Name the arguments
VERSION=$1
# Clone docs repo and update the documentation
git clone https://github.com/xtermjs/xtermjs.org
cd xtermjs.org
yarn
./bin/update-docs
# Add changes to index and only proceed if there are changes to commit
touch test-file
git add .
if ! git diff-index --quiet HEAD --; then
# Delete the upstream branch if it exists for some reason
export BRANCH_NAME=update-$VERSION
git branch -D $BRANCH_NAME || true
git push origin :$BRANCH_NAME || true
# Create commit and push it to update-x.y.z
git checkout -b $BRANCH_NAME
git config --global user.name Daniel Imms
git config --global user.email tyriar@tyriar.com
git commit -m 'Update docs for v$VERSION'
git push --set-upstream origin update-4.2.0
git push -f
# Create a PR in the GitHub repo
curl \
-H "Authorization: token $GITHUB_TOKEN" \
-X POST \
-d "{\"title\":\"Update docs for v$VERSION\",\"base\":\"master\",\"head\":\"xtermjs:$BRANCH_NAME\"}" \
https://api.github.com/repos/xtermjs/xtermjs.org/pulls
else
echo "No changes to commit"
fi
+3 -2
View File
@@ -221,6 +221,7 @@ function initOptions(term: TerminalType): void {
bellSound: null,
bellStyle: ['none', 'sound'],
cursorStyle: ['block', 'underline', 'bar'],
fastScrollModifier: ['alt', 'ctrl', 'shift', undefined],
fontFamily: null,
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
@@ -253,7 +254,7 @@ function initOptions(term: TerminalType): void {
});
html += '</div><div class="option-group">';
numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' ? '0.1' : '1'}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
});
html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => {
@@ -282,7 +283,7 @@ function initOptions(term: TerminalType): void {
console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') {
updateTerminalSize();
} else if (o === 'lineHeight') {
} else if (o === 'lineHeight' || o === 'scrollSensitivity') {
term.setOption(o, parseFloat(input.value));
updateTerminalSize();
} else {
+12 -10
View File
@@ -14,24 +14,26 @@ function startServer() {
logs = {};
app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css'));
app.get('/logo.png', (req, res) => res.sendFile(__dirname + '/logo.png'));
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
app.get('/logo.png', (req, res) => {
res.sendFile(__dirname + '/logo.png'); // lgtm [js/missing-rate-limiting]
});
app.get('/test', function(req, res){
res.sendFile(__dirname + '/test.html');
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html'); // lgtm [js/missing-rate-limiting]
});
app.get('/style.css', function(req, res){
res.sendFile(__dirname + '/style.css');
app.get('/test', (req, res) => {
res.sendFile(__dirname + '/test.html'); // lgtm [js/missing-rate-limiting]
});
app.get('/style.css', (req, res) => {
res.sendFile(__dirname + '/style.css'); // lgtm [js/missing-rate-limiting]
});
app.use('/dist', express.static(__dirname + '/dist'));
app.use('/src', express.static(__dirname + '/src'));
app.post('/terminals', function (req, res) {
app.post('/terminals', (req, res) => {
const env = Object.assign({}, process.env);
env['COLORTERM'] = 'truecolor';
var cols = parseInt(req.query.cols),
@@ -55,7 +57,7 @@ function startServer() {
res.end();
});
app.post('/terminals/:pid/size', function (req, res) {
app.post('/terminals/:pid/size', (req, res) => {
var pid = parseInt(req.params.pid),
cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.0.0",
"version": "4.1.0",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
@@ -47,7 +47,7 @@
"ts-loader": "^6.0.4",
"tslint": "^5.18.0",
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "3.5",
"typescript": "3.6",
"utf8": "^3.0.0",
"webpack": "^4.35.3",
"webpack-cli": "^3.1.0",

Some files were not shown because too many files have changed in this diff Show More