Merge branch 'master' into clusters

This commit is contained in:
Per Bothner
2023-08-21 07:59:15 -07:00
committed by GitHub
17 changed files with 1258 additions and 455 deletions
+133
View File
@@ -0,0 +1,133 @@
name: CI
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Build
run: yarn setup
- name: Zip artifacts
run: |
zip -r compressed-build \
./out/* \
./out-test/* \
./addons/xterm-addon-attach/out/* \
./addons/xterm-addon-attach/out-test/* \
./addons/xterm-addon-canvas/out/* \
./addons/xterm-addon-canvas/out-test/* \
./addons/xterm-addon-fit/out/* \
./addons/xterm-addon-fit/out-test/* \
./addons/xterm-addon-image/inwasm-builds/out/* \
./addons/xterm-addon-image/out/* \
./addons/xterm-addon-image/out-test/* \
./addons/xterm-addon-ligatures/out/* \
./addons/xterm-addon-ligatures/out-test/* \
./addons/xterm-addon-search/out/* \
./addons/xterm-addon-search/out-test/* \
./addons/xterm-addon-serialize/out/* \
./addons/xterm-addon-serialize/out-test/* \
./addons/xterm-addon-unicode11/out/* \
./addons/xterm-addon-unicode11/out-test/* \
./addons/xterm-addon-web-links/out/* \
./addons/xterm-addon-web-links/out-test/* \
./addons/xterm-addon-webgl/out/* \
./addons/xterm-addon-webgl/out-test/*
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: compressed-build.zip
if-no-files-found: error
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Lint code
run: yarn lint
- name: Lint API
run: yarn lint-api
unit-tests:
needs: build
strategy:
matrix:
node-version: [16, 18]
runs-on: [ubuntu, macos, windows]
runs-on: ${{ matrix.runs-on }}-latest
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Unzip artifacts (Linux, macOS)
if: runner.os != 'Windows'
run: unzip -o compressed-build.zip
- name: Unzip artifacts (Windows)
if: runner.os == 'Windows'
run: 7z x compressed-build.zip -aoa -o${{ github.workspace }}
- name: Print directory structure
run: ls -R
- name: Use Node.js ${{ matrix.node-version }}.x
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}.x
cache: 'yarn'
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Unit tests
run: yarn test-unit --forbid-only
integration-tests:
needs: build
strategy:
matrix:
node-version: [18]
runs-on: [ubuntu, windows] # macos is flaky
browser: [chromium, firefox]
runs-on: ${{ matrix.runs-on }}-latest
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Unzip artifacts (Linux, macOS)
if: runner.os != 'Windows'
run: unzip -o compressed-build.zip
- name: Unzip artifacts (Windows)
if: runner.os == 'Windows'
run: 7z x compressed-build.zip -aoa -o${{ github.workspace }}
- name: Print directory structure
run: ls -R
- name: Use Node.js ${{ matrix.node-version }}.x
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}.x
cache: 'yarn'
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Install playwright
run: npx playwright install
- name: Integration tests (${{ matrix.browser }})
run: yarn test-api-${{ matrix.browser }} --headless --forbid-only
-37
View File
@@ -1,21 +1,9 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '41 17 * * 0'
@@ -33,40 +21,15 @@ jobs:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+30
View File
@@ -0,0 +1,30 @@
name: Release
on:
push:
# If a commit reaches master, assume it has passed CI via PR and publish
# without running tests to save time to publish
branches: [ "master" ]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Build
run: yarn setup
- name: Package headless
run: |
yarn package-headless
node ./bin/package_headless.js
- name: Publish to npm
env:
NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: node ./bin/publish.js
-2
View File
@@ -1,7 +1,5 @@
# [![xterm.js logo](logo-full.png)](https://xtermjs.org)
[![Build Status](https://dev.azure.com/xtermjs/xterm.js/_apis/build/status/xtermjs.xterm.js)](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3)
Xterm.js is a front-end component written in TypeScript that lets applications bring fully-featured terminals to their users in the browser. It's used by popular projects such as VS Code, Hyper and Theia.
## Features
+15 -18
View File
@@ -45,10 +45,9 @@ describe('Base64Decoder', () => {
assert.deepEqual(dec.data8, inp);
}
});
it('1+2 bytes', function() {
this.timeout(20000);
const dec = new Base64Decoder(0);
for (let a = 0; a < 256; ++a) {
for (let a = 0; a < 256; ++a) {
it(`1+2 bytes (${a})`, function() {
const dec = new Base64Decoder(0);
for (let b = 0; b < 256; ++b) {
dec.init(2);
const inp = new Uint8Array([a, b]);
@@ -57,12 +56,11 @@ describe('Base64Decoder', () => {
assert.strictEqual(dec.end(), 0);
assert.deepEqual(dec.data8, inp);
}
}
});
it('2+3 bytes', function() {
this.timeout(20000);
const dec = new Base64Decoder(0);
for (let a = 0; a < 256; ++a) {
});
}
for (let a = 0; a < 256; ++a) {
it(`2+3 bytes (${a})`, function() {
const dec = new Base64Decoder(0);
for (let b = 0; b < 256; ++b) {
dec.init(3);
const inp = new Uint8Array([0, a, b]);
@@ -71,12 +69,11 @@ describe('Base64Decoder', () => {
assert.strictEqual(dec.end(), 0);
assert.deepEqual(dec.data8, inp);
}
}
});
it('3+4 bytes', function() {
this.timeout(20000);
const dec = new Base64Decoder(0);
for (let a = 0; a < 256; ++a) {
});
}
for (let a = 0; a < 256; ++a) {
it(`3+4 bytes (${a})`, function() {
const dec = new Base64Decoder(0);
for (let b = 0; b < 256; ++b) {
dec.init(4);
const inp = new Uint8Array([0, 0, a, b]);
@@ -85,8 +82,8 @@ describe('Base64Decoder', () => {
assert.strictEqual(dec.end(), 0);
assert.deepEqual(dec.data8, inp);
}
}
});
});
}
it('padding', () => {
const dec = new Base64Decoder(0);
const d = fromBs('Hello, here comes the mouse');
@@ -35,26 +35,57 @@ describe('WebLinksAddon', () => {
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal(page, { cols: 40 });
});
after(async () => await browser.close());
beforeEach(async () => await page.goto(APP));
it('.com', async function(): Promise<any> {
await testHostName('foo.com');
beforeEach(async () => {
await page.evaluate(`
window._linkaddon?.dispose();
window.term.reset();
window._linkaddon = new window.WebLinksAddon();
window.term.loadAddon(window._linkaddon);
`);
});
it('.com.au', async function(): Promise<any> {
await testHostName('foo.com.au');
});
it('.io', async function(): Promise<any> {
await testHostName('foo.io');
});
const countryTlds = [
'.ac', '.ad', '.ae', '.af', '.ag', '.ai', '.al', '.am', '.ao', '.aq', '.ar', '.as', '.at',
'.au', '.aw', '.ax', '.az', '.ba', '.bb', '.bd', '.be', '.bf', '.bg', '.bh', '.bi', '.bj',
'.bm', '.bn', '.bo', '.bq', '.br', '.bs', '.bt', '.bw', '.by', '.bz', '.ca', '.cc', '.cd',
'.cf', '.cg', '.ch', '.ci', '.ck', '.cl', '.cm', '.cn', '.co', '.cr', '.cu', '.cv', '.cw',
'.cx', '.cy', '.cz', '.de', '.dj', '.dk', '.dm', '.do', '.dz', '.ec', '.ee', '.eg', '.eh',
'.er', '.es', '.et', '.eu', '.fi', '.fj', '.fk', '.fm', '.fo', '.fr', '.ga', '.gd', '.ge',
'.gf', '.gg', '.gh', '.gi', '.gl', '.gm', '.gn', '.gp', '.gq', '.gr', '.gs', '.gt', '.gu',
'.gw', '.gy', '.hk', '.hm', '.hn', '.hr', '.ht', '.hu', '.id', '.ie', '.il', '.im', '.in',
'.io', '.iq', '.ir', '.is', '.it', '.je', '.jm', '.jo', '.jp', '.ke', '.kg', '.kh', '.ki',
'.km', '.kn', '.kp', '.kr', '.kw', '.ky', '.kz', '.la', '.lb', '.lc', '.li', '.lk', '.lr',
'.ls', '.lt', '.lu', '.lv', '.ly', '.ma', '.mc', '.md', '.me', '.mg', '.mh', '.mk', '.ml',
'.mm', '.mn', '.mo', '.mp', '.mq', '.mr', '.ms', '.mt', '.mu', '.mv', '.mw', '.mx', '.my',
'.mz', '.na', '.nc', '.ne', '.nf', '.ng', '.ni', '.nl', '.no', '.np', '.nr', '.nu', '.nz',
'.om', '.pa', '.pe', '.pf', '.pg', '.ph', '.pk', '.pl', '.pm', '.pn', '.pr', '.ps', '.pt',
'.pw', '.py', '.qa', '.re', '.ro', '.rs', '.ru', '.rw', '.sa', '.sb', '.sc', '.sd', '.se',
'.sg', '.sh', '.si', '.sk', '.sl', '.sm', '.sn', '.so', '.sr', '.ss', '.st', '.su', '.sv',
'.sx', '.sy', '.sz', '.tc', '.td', '.tf', '.tg', '.th', '.tj', '.tk', '.tl', '.tm', '.tn',
'.to', '.tr', '.tt', '.tv', '.tw', '.tz', '.ua', '.ug', '.uk', '.us', '.uy', '.uz', '.va',
'.vc', '.ve', '.vg', '.vi', '.vn', '.vu', '.wf', '.ws', '.ye', '.yt', '.za', '.zm', '.zw'
];
for (const tld of countryTlds) {
it(tld, async () => await testHostName(`foo${tld}`));
}
it(`.com`, async () => await testHostName(`foo.com`));
for (const tld of countryTlds) {
it(`.com${tld}`, async () => await testHostName(`foo.com${tld}`));
}
describe('correct buffer offsets & uri', () => {
beforeEach(async () => {
await page.evaluate(`
window._linkStateData = {uri:''};
window._linkaddon._options.hover = (event, uri, range) => { window._linkStateData = { uri, range }; };
`);
});
it('all half width', async () => {
setupCustom();
await writeSync(page, 'aaa http://example.com aaa http://example.com aaa');
await resetAndHover(5, 0);
await evalLinkStateData('http://example.com', { start: { x: 5, y: 1 }, end: { x: 22, y: 1 } });
@@ -62,7 +93,6 @@ describe('WebLinksAddon', () => {
await evalLinkStateData('http://example.com', { start: { x: 28, y: 1 }, end: { x: 5, y: 2 } });
});
it('url after full width', async () => {
setupCustom();
await writeSync(page, '¥¥¥ http://example.com ¥¥¥ http://example.com aaa');
await resetAndHover(8, 0);
await evalLinkStateData('http://example.com', { start: { x: 8, y: 1 }, end: { x: 25, y: 1 } });
@@ -70,7 +100,6 @@ describe('WebLinksAddon', () => {
await evalLinkStateData('http://example.com', { start: { x: 34, y: 1 }, end: { x: 11, y: 2 } });
});
it('full width within url and before', async () => {
setupCustom();
await writeSync(page, '¥¥¥ https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 ¥¥¥');
await resetAndHover(8, 0);
await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 8, y: 1 }, end: { x: 11, y: 2 } });
@@ -80,7 +109,6 @@ describe('WebLinksAddon', () => {
await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 17, y: 2 }, end: { x: 19, y: 3 } });
});
it('name + password url after full width and combining', async () => {
setupCustom();
await writeSync(page, '¥¥¥cafe\u0301 http://test:password@example.com/some_path');
await resetAndHover(12, 0);
await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } });
@@ -91,8 +119,6 @@ describe('WebLinksAddon', () => {
});
async function testHostName(hostname: string): Promise<void> {
await openTerminal(page, { cols: 40 });
await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`);
const data = ` http://${hostname} \\r\\n` +
` http://${hostname}/a~b#c~d?e~f \\r\\n` +
` http://${hostname}/colon:test \\r\\n` +
@@ -117,14 +143,6 @@ async function pollForLinkAtCell(col: number, row: number, value: string): Promi
assert.deepEqual(text, value);
}
async function setupCustom(): Promise<void> {
await openTerminal(page, { cols: 40 });
await page.evaluate(`window._linkStateData = {uri:''};
window._linkaddon = new window.WebLinksAddon();
window._linkaddon._options.hover = (event, uri, range) => { window._linkStateData = { uri, range }; };
window.term.loadAddon(window._linkaddon);`);
}
async function resetAndHover(col: number, row: number): Promise<void> {
await page.mouse.move(0, 0);
await page.evaluate(`window._linkStateData = {uri:''};`);
+2 -3
View File
@@ -4,6 +4,7 @@
*/
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { CursorInactiveStyle, CursorStyle } from 'common/Types';
export interface IRenderModel {
cells: Uint32Array;
@@ -16,13 +17,11 @@ export interface ICursorRenderModel {
x: number;
y: number;
width: number;
style: CursorStyle;
style: CursorStyle | CursorInactiveStyle;
cursorWidth: number;
dpr: number;
}
export type CursorStyle = 'outline' | 'block' | 'bar' | 'underline' | 'none';
export interface IWebGL2RenderingContext extends WebGLRenderingContext {
vertexAttribDivisor(index: number, divisor: number): void;
createVertexArray(): IWebGLVertexArrayObject;
-182
View File
@@ -1,182 +0,0 @@
pr:
branches:
include: ["main"]
trigger:
branches:
include: ["main"]
jobs:
- job: Linux
pool:
vmImage: 'ubuntu-20.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
inputs:
versionSpec: '1.x'
displayName: 'Install Yarn'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn test-unit-coverage --forbid-only
EXIT_CODE=$?
./node_modules/.bin/nyc report --reporter=cobertura
exit $EXIT_CODE
displayName: 'Unit tests'
- script: yarn lint
displayName: 'Lint code'
- script: yarn lint-api
displayName: 'Lint API'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml'
displayName: 'Publish coverage'
- job: macOS
pool:
vmImage: 'macOS-11'
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: yarn test-unit --forbid-only
displayName: 'Unit tests'
- script: yarn lint
displayName: 'Lint code'
- script: yarn lint-api
displayName: 'Lint API'
- job: Windows
pool:
vmImage: 'windows-2019'
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: yarn test-unit --forbid-only
displayName: 'Unit tests'
- script: yarn lint
displayName: 'Lint code'
- script: yarn lint-api
displayName: 'Lint API'
- job: Linux_IntegrationTests
pool:
vmImage: 'ubuntu-20.04'
steps:
- script: |
# source: https://github.com/microsoft/playwright/issues/1041
sudo apt update
sudo apt install libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libnotify4 libxslt1.1 libevent-2.1-6 libgles2 libgl1 libegl1 libvpx5
# for chromium
sudo apt install libnss3 libxss1 libasound2
# for firefox
sudo apt install libdbus-glib-1-2 libxt6
displayName: Install required packages
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
inputs:
versionSpec: '1.x'
displayName: 'Install Yarn'
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: xvfb-run --auto-servernum -- bash -c "yarn test-api-firefox --headless --forbid-only"
displayName: 'Integration tests (Firefox)'
# Integration tests are too flaky on macOS https://github.com/xtermjs/xterm.js/issues/3590
# - job: macOS_IntegrationTests
# pool:
# vmImage: 'macOS-11'
# steps:
# - task: NodeTool@0
# inputs:
# versionSpec: '18.x'
# displayName: 'Install Node.js'
# - script: yarn --frozen-lockfile
# displayName: 'Install dependencies and build'
# - script: yarn test-api-chromium --headless --forbid-only
# displayName: 'Integration tests (Chromium)'
# - script: yarn test-api-firefox --headless --forbid-only
# displayName: 'Integration tests (Firefox)'
# - script: yarn test-api-webkit --headless --forbid-only
# displayName: 'Integration tests (Webkit)'
- job: Windows_IntegrationTests
pool:
vmImage: 'windows-2019'
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: yarn test-api-firefox --headless --forbid-only
displayName: 'Integration tests (Firefox)'
- job: Release
dependsOn:
- Linux
- macOS
- Windows
- Linux_IntegrationTests
# - macOS_IntegrationTests
- Windows_IntegrationTests
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
pool:
vmImage: 'ubuntu-20.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
inputs:
versionSpec: '1.x'
displayName: 'Install Yarn'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn package-headless
node ./bin/package_headless.js
displayName: 'Package xterm-headless'
- script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
displayName: 'Package and publish to npm'
+11 -12
View File
@@ -67,25 +67,24 @@ function checkAndPublishPackage(packageDir) {
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));
}
fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));
// Publish
const args = ['publish'];
if (!isStableRelease) {
args.push('--tag', 'beta');
}
if (isDryRun) {
args.push('--dry-run');
}
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);
}
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();
+30 -13
View File
@@ -60,6 +60,7 @@ let protocol;
let socketURL;
let socket;
let pid;
let autoResize: boolean = true;
type AddonType = 'attach' | 'canvas' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'unicode-graphemes' | 'web-links' | 'webgl' | 'ligatures';
@@ -325,6 +326,13 @@ function createTerminal(): void {
term.focus();
const resizeObserver = new ResizeObserver(entries => {
if (autoResize) {
addons.fit.instance.fit();
}
});
resizeObserver.observe(terminalContainer);
addDomListener(paddingElement, 'change', setPadding);
addDomListener(actionElements.findNext, 'keydown', (e) => {
@@ -355,9 +363,6 @@ function createTerminal(): void {
// fit is called within a setTimeout, cols and rows need this.
setTimeout(async () => {
initOptions(term);
// TODO: Clean this up, opt-cols/rows doesn't exist anymore
(document.getElementById(`opt-cols`) as HTMLInputElement).value = term.cols;
(document.getElementById(`opt-rows`) as HTMLInputElement).value = term.rows;
paddingElement.value = '0';
// Set terminal size again to set the specific dimensions on the demo
@@ -422,6 +427,7 @@ function initOptions(term: TerminalType): void {
'cancelEvents',
'convertEol',
'termName',
'cols', 'rows', // subsumed by "size" (cols_rows) option
// Complex option
'theme',
'windowOptions'
@@ -435,7 +441,8 @@ function initOptions(term: TerminalType): void {
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
logLevel: ['trace', 'debug', 'info', 'warn', 'error', 'off'],
theme: ['default', 'xtermjs', 'sapphire', 'light'],
wordSeparator: null
wordSeparator: null,
cols_rows: null
};
const options = Object.getOwnPropertyNames(term.options);
const booleanOptions = [];
@@ -468,7 +475,9 @@ function initOptions(term: TerminalType): void {
});
html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => {
if (stringOptions[o]) {
if (o === 'cols_rows') {
html += `<div class="option"><label>size (<var>cols</var><code>x</code><var>rows</var> or <code>auto</code>) <input id="opt-${o}" type="text" value="auto"/></label></div>`;
} else if (stringOptions[o]) {
const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o];
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${v === selectedOption ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
} else {
@@ -492,11 +501,7 @@ function initOptions(term: TerminalType): void {
const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
if (o === 'rows') {
term.resize(term.cols, parseInt(input.value));
} else if (o === 'cols') {
term.resize(parseInt(input.value), term.rows);
} else if (o === 'lineHeight') {
if (o === 'lineHeight') {
term.options.lineHeight = parseFloat(input.value);
} else if (o === 'scrollSensitivity') {
term.options.scrollSensitivity = parseFloat(input.value);
@@ -515,7 +520,17 @@ function initOptions(term: TerminalType): void {
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
let value: any = input.value;
if (o === 'theme') {
if (o === 'cols_rows') {
let m = input.value.match(/^([0-9]+)x([0-9]+)$/);
if (m) {
autoResize = false;
term.resize(parseInt(m[1]), parseInt(m[2]));
} else {
autoResize = true;
input.value = 'auto';
updateTerminalSize();
}
} else if (o === 'theme') {
switch (input.value) {
case 'default':
value = undefined;
@@ -687,8 +702,10 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a
}
function updateTerminalSize(): void {
const width = (term._core._renderService.dimensions.css.canvas.width + term._core.viewport.scrollBarWidth).toString() + 'px';
const height = (term._core._renderService.dimensions.css.canvas.height).toString() + 'px';
const width = autoResize ? '100%'
: (term._core._renderService.dimensions.css.canvas.width + term._core.viewport.scrollBarWidth).toString() + 'px';
const height = autoResize ? '100%'
: (term._core._renderService.dimensions.css.canvas.height).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
addons.fit.instance.fit();
+12 -10
View File
@@ -14,16 +14,16 @@
<body>
<h1 style="color: #2D2E2C">xterm.js: A terminal for the <em style="color: #5DA5D5">web</em></h1>
<div id="container">
<div id="grid">
<div class="grid">
<div id="terminal-container"></div>
</div>
<div id="grid">
<div class="grid">
<div class="tab">
<button id= "optionsbutton" class="tabLinks" onclick="openSection(event, 'options')">Options</button>
<button id= "addonsbutton" class="tabLinks" onclick="openSection(event, 'addons')">Addons</button>
<button id= "stylebutton" class="tabLinks" onclick="openSection(event, 'style')">Style</button>
<button id= "testbutton" class="tabLinks" onclick="openSection(event, 'test')">Test</button>
<button id= "vtbutton" class="tabLinks" onclick="openSection(event, 'vt')">VT</button>
<button id="optionsbutton" class="tabLinks" onclick="openSection(event, 'options')">Options</button>
<button id="addonsbutton" class="tabLinks" onclick="openSection(event, 'addons')">Addons</button>
<button id="stylebutton" class="tabLinks" onclick="openSection(event, 'style')">Style</button>
<button id="testbutton" class="tabLinks" onclick="openSection(event, 'test')">Test</button>
<button id="vtbutton" class="tabLinks" onclick="openSection(event, 'vt')">VT</button>
</div>
<div id="options" class="tabContent">
<h3>Options</h3>
@@ -119,9 +119,11 @@
</div>
</div>
</div>
<input type="checkbox" id="texture-atlas-zoom"/>
<label for="texture-atlas-zoom">Zoom texture atlas</label>
<div id="texture-atlas"></div>
<div id="texture-atlas-container">
<input type="checkbox" id="texture-atlas-zoom"/>
<label for="texture-atlas-zoom">Zoom texture atlas</label>
<div id="texture-atlas"></div>
</div>
<script src="dist/client-bundle.js" defer ></script>
<script>
var tab = localStorage.getItem("tab");
+10 -5
View File
@@ -9,8 +9,7 @@ h1 {
}
#terminal-container {
width: 800px;
height: 450px;
height: 60%;
margin: 0 auto;
padding: 2px;
}
@@ -46,11 +45,16 @@ pre {
#container {
display: flex;
}
#grid {
.grid {
flex: 1;
/* max-height: 80vh;
overflow-y: auto; */
width: 100%;
min-width: 100px;
}
div:first-of-type.grid {
flex: 2;
height: 60vh;
}
.tab {
overflow: hidden;
@@ -86,8 +90,6 @@ pre {
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
max-height: 100vh;
overflow-y: auto;
}
#texture-atlas-zoom:checked + label + #texture-atlas canvas {
@@ -106,3 +108,6 @@ pre {
.vt-button * {
margin-right: 1em;
}
input#opt-cols_rows {
width: 6em;
}
+1 -2
View File
@@ -41,9 +41,8 @@
"test-unit-coverage": "node ./bin/test.js --coverage",
"test-unit-dev": "cross-env NODE_PATH='./out' mocha",
"build": "tsc -b ./tsconfig.all.json",
"prepare": "npm run setup",
"setup": "npm run build",
"presetup": "node ./bin/install-addons.js",
"postinstall": "node ./bin/install-addons.js",
"postsetup": "npm run inwasm",
"prepublishOnly": "npm run package",
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
+43 -36
View File
@@ -29,6 +29,8 @@ function fromByteString(s: string): Uint8Array {
return result;
}
const BATCH_SIZE = 2048;
const TEST_STRINGS = [
'Лорем ипсум долор сит амет, ех сеа аццусам диссентиет. Ан еос стет еирмод витуперата. Иус дицерет урбанитас ет. Ан при алтера долорес сплендиде, цу яуо интегре денияуе, игнота волуптариа инструцтиор цу вим.',
'ლორემ იფსუმ დოლორ სით ამეთ, ფაცერ მუციუს ცონსეთეთურ ყუო იდ, ფერ ვივენდუმ ყუაერენდუმ ეა, ესთ ამეთ მოვეთ სუავითათე ცუ. ვითაე სენსიბუს ან ვიხ. ეხერცი დეთერრუისსეთ უთ ყუი. ვოცენთ დებითის ადიფისცი ეთ ფერ. ნეც ან ფეუგაით ფორენსიბუს ინთერესსეთ. იდ დიცო რიდენს იუს. დისსენთიეთ ცონსეყუუნთურ სედ ნე, ნოვუმ მუნერე ეუმ ათ, ნე ეუმ ნიჰილ ირაცუნდია ურბანითას.',
@@ -54,36 +56,40 @@ describe('text encodings', () => {
describe('StringToUtf32 decoder', () => {
describe('full codepoint test', () => {
it('0..65535', () => {
const decoder = new StringToUtf32();
const target = new Uint32Array(5);
for (let i = 0; i < 65536; ++i) {
// skip surrogate pairs and a BOM
if ((i >= 0xD800 && i <= 0xDFFF) || i === 0xFEFF) {
continue;
for (let min = 0; min < 65535; min += BATCH_SIZE) {
const max = Math.min(min + BATCH_SIZE, 65536);
it(`${formatRange(min, max)}`, () => {
const decoder = new StringToUtf32();
const target = new Uint32Array(5);
for (let i = min; i < max; ++i) {
// skip surrogate pairs and a BOM
if ((i >= 0xD800 && i <= 0xDFFF) || i === 0xFEFF) {
continue;
}
const length = decoder.decode(String.fromCharCode(i), target);
assert.equal(length, 1);
assert.equal(target[0], i);
assert.equal(utf32ToString(target, 0, length), String.fromCharCode(i));
decoder.clear();
}
const length = decoder.decode(String.fromCharCode(i), target);
assert.equal(length, 1);
assert.equal(target[0], i);
assert.equal(utf32ToString(target, 0, length), String.fromCharCode(i));
decoder.clear();
}
});
it('65536..0x10FFFF (surrogates)', function (): void {
this.timeout(20000);
const decoder = new StringToUtf32();
const target = new Uint32Array(5);
for (let i = 65536; i < 0x10FFFF; ++i) {
const codePoint = i - 0x10000;
const s = String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00);
const length = decoder.decode(s, target);
assert.equal(length, 1);
assert.equal(target[0], i);
assert.equal(utf32ToString(target, 0, length), s);
decoder.clear();
}
});
});
}
for (let min = 65536; min < 0x10FFFF; min += BATCH_SIZE) {
const max = Math.min(min + BATCH_SIZE, 0x10FFFF);
it(`${formatRange(min, max)} (surrogates)`, () => {
const decoder = new StringToUtf32();
const target = new Uint32Array(5);
for (let i = min; i < max; ++i) {
const codePoint = i - 0x10000;
const s = String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00);
const length = decoder.decode(s, target);
assert.equal(length, 1);
assert.equal(target[0], i);
assert.equal(utf32ToString(target, 0, length), s);
decoder.clear();
}
});
}
it('0xFEFF(BOM)', () => {
const decoder = new StringToUtf32();
@@ -121,11 +127,8 @@ describe('text encodings', () => {
describe('Utf8ToUtf32 decoder', () => {
describe('full codepoint test', () => {
function formatRange(min: number, max: number): string {
return `${min}..${max} (0x${min.toString(16).toUpperCase()}..0x${max.toString(16).toUpperCase()})`;
}
for (let min = 0; min < 65535; min += 10000) {
const max = Math.min(min + 10000, 65536);
for (let min = 0; min < 65535; min += BATCH_SIZE) {
const max = Math.min(min + BATCH_SIZE, 65536);
it(`${formatRange(min, max)} (1/2/3 byte sequences)`, () => {
const decoder = new Utf8ToUtf32();
const target = new Uint32Array(5);
@@ -142,9 +145,9 @@ describe('text encodings', () => {
}
});
}
for (let minRaw = 60000; minRaw < 0x10FFFF; minRaw += 10000) {
for (let minRaw = 60000; minRaw < 0x10FFFF; minRaw += BATCH_SIZE) {
const min = Math.max(minRaw, 65536);
const max = Math.min(minRaw + 10000, 0x10FFFF);
const max = Math.min(minRaw + BATCH_SIZE, 0x10FFFF);
it(`${formatRange(min, max)} (4 byte sequences)`, function (): void {
const decoder = new Utf8ToUtf32();
const target = new Uint32Array(5);
@@ -265,3 +268,7 @@ describe('text encodings', () => {
});
});
});
function formatRange(min: number, max: number): string {
return `${min}..${max} (0x${min.toString(16).toUpperCase()}..0x${max.toString(16).toUpperCase()})`;
}
+2 -2
View File
@@ -227,7 +227,7 @@ describe('DcsParser', () => {
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'Here comes the mouse!']]);
});
it('should work up to payload limit', function(): void {
this.timeout(10000);
this.timeout(30000);
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; }));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
const data = toUtf32('A'.repeat(1000));
@@ -238,7 +238,7 @@ describe('DcsParser', () => {
assert.deepEqual(reports, [[[1, 2, 3], 'A'.repeat(PAYLOAD_LIMIT)]]);
});
it('should abort for payload limit +1', function(): void {
this.timeout(10000);
this.timeout(30000);
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; }));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('A'.repeat(1000));
+2 -2
View File
@@ -221,7 +221,7 @@ describe('OscParser', () => {
assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'Here comes the mouse!']]);
});
it('should work up to payload limit', function(): void {
this.timeout(10000);
this.timeout(30000);
parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; }));
parser.start();
let data = toUtf32('1234;');
@@ -234,7 +234,7 @@ describe('OscParser', () => {
assert.deepEqual(reports, [[1234, 'A'.repeat(PAYLOAD_LIMIT)]]);
});
it('should abort for payload limit +1', function(): void {
this.timeout(10000);
this.timeout(30000);
parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; }));
parser.start();
let data = toUtf32('1234;');
File diff suppressed because it is too large Load Diff