diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..eaf22d67 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f8c8362a..3ba91f7e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..1e26e438 --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/README.md b/README.md index 2eeba51b..0abcd32c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/addons/xterm-addon-image/src/base64.test.ts b/addons/xterm-addon-image/src/base64.test.ts index 6b82893a..2d4e6d76 100644 --- a/addons/xterm-addon-image/src/base64.test.ts +++ b/addons/xterm-addon-image/src/base64.test.ts @@ -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'); diff --git a/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts index 366d8160..a086e1d3 100644 --- a/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts @@ -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 { - 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 { - await testHostName('foo.com.au'); - }); - - it('.io', async function(): Promise { - 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 { - 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 { - 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 { await page.mouse.move(0, 0); await page.evaluate(`window._linkStateData = {uri:''};`); diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index 3eb3b300..fe6e10c7 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -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; diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index f96a0e8a..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -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' diff --git a/bin/publish.js b/bin/publish.js index e65048db..ec089e7b 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -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(); diff --git a/demo/client.ts b/demo/client.ts index 7c586528..7b295c37 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -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 += '
'; Object.keys(stringOptions).forEach(o => { - if (stringOptions[o]) { + if (o === 'cols_rows') { + html += `
`; + } else if (stringOptions[o]) { const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o]; html += `
`; } 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(); diff --git a/demo/index.html b/demo/index.html index e6d7eba4..43522101 100644 --- a/demo/index.html +++ b/demo/index.html @@ -14,16 +14,16 @@

xterm.js: A terminal for the web

-
+
-
+
- - - - - + + + + +

Options

@@ -119,9 +119,11 @@
- - -
+
+ + +
+