From d8a93b757ad4fd4c3945a99f1e1e8833772a524f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 26 Dec 2025 11:06:55 -0800 Subject: [PATCH] Support reporting XTVERSION Fixes #5342 --- bin/publish.js | 16 ++++++++++++++-- src/common/InputHandler.test.ts | 22 ++++++++++++++++++++++ src/common/InputHandler.ts | 18 ++++++++++++++++++ src/common/Version.ts | 9 +++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/common/Version.ts diff --git a/bin/publish.js b/bin/publish.js index af4cc508..fd676b3a 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -30,7 +30,7 @@ const changedFiles = getChangedFilesInCommit('HEAD'); // Always publish xterm, technically this isn't needed if // `changedFiles.some(e => e.search(/^addons\//)`, but it's here for convenience to get the right // peer dependencies for addons. -const result = checkAndPublishPackage(path.resolve(__dirname, '..'), repoCommit); +const result = checkAndPublishPackage(path.resolve(__dirname, '..'), repoCommit, undefined, true); const isStableRelease = result.isStableRelease; const peerDependencies = result.nextVersion.includes('beta') ? { '@xterm/xterm': `^${result.nextVersion}`, @@ -67,7 +67,7 @@ if (isStableRelease) { updateWebsite(); } -function checkAndPublishPackage(packageDir, repoCommit, peerDependencies) { +function checkAndPublishPackage(packageDir, repoCommit, peerDependencies, updateVersionTs = false) { const packageJson = require(path.join(packageDir, 'package.json')); // Determine if this is a stable or beta release @@ -78,6 +78,18 @@ function checkAndPublishPackage(packageDir, repoCommit, peerDependencies) { let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion(packageJson); log(`Publishing version: ${nextVersion}`, 'green'); + // Update Version.ts with the new version + if (updateVersionTs) { + const versionTsPath = path.join(packageDir, 'src/common/Version.ts'); + const versionTsContent = fs.readFileSync(versionTsPath, 'utf8'); + const updatedVersionTs = versionTsContent.replace( + /export const XTERM_VERSION = '[^']+';/, + `export const XTERM_VERSION = '${nextVersion}';` + ); + fs.writeFileSync(versionTsPath, updatedVersionTs); + log(`Updated ${versionTsPath} to version ${nextVersion}`); + } + // Set the version in package.json const packageJsonFile = path.join(packageDir, 'package.json'); packageJson.version = nextVersion; diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 1325cf0f..9de6c11c 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1582,6 +1582,28 @@ describe('InputHandler', () => { assert.equal(bufferService.cols, 132); }); }); + describe('XTVERSION (CSI > q, CSI > 0 q)', () => { + it('should report xterm.js version', async () => { + const stack: string[] = []; + coreService.onData(data => stack.push(data)); + await inputHandler.parseP('\x1b[>q'); + assert.strictEqual(stack.length, 1); + assert.ok(stack[0].match(/^\x1bP>\|xterm\.js\(\d+\.\d+\.\d+(-beta\.\d+)?\)\x1b\\/)); + }); + it('should report xterm.js version for CSI > 0 q', async () => { + const stack: string[] = []; + coreService.onData(data => stack.push(data)); + await inputHandler.parseP('\x1b[>0q'); + assert.strictEqual(stack.length, 1); + assert.ok(stack[0].match(/^\x1bP>\|xterm\.js\(\d+\.\d+\.\d+(-beta\.\d+)?\)\x1b\\/)); + }); + it('should not report for CSI > 1 q', async () => { + const stack: string[] = []; + coreService.onData(data => stack.push(data)); + await inputHandler.parseP('\x1b[>1q'); + assert.strictEqual(stack.length, 0); + }); + }); describe('should correctly reset cells taken by wide chars', () => { beforeEach(async () => { bufferService.resize(10, 5); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index ba53941a..44c0275b 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -22,6 +22,7 @@ import { DcsHandler } from 'common/parser/DcsParser'; import { IBuffer } from 'common/buffer/Types'; import { parseColor } from 'common/input/XParseColor'; import { Emitter } from 'vs/base/common/event'; +import { XTERM_VERSION } from 'common/Version'; /** * Map collect to glevel. Used in `selectCharset`. @@ -256,6 +257,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.registerCsiHandler({ final: 'n' }, params => this.deviceStatus(params)); this._parser.registerCsiHandler({ prefix: '?', final: 'n' }, params => this.deviceStatusPrivate(params)); this._parser.registerCsiHandler({ intermediates: '!', final: 'p' }, params => this.softReset(params)); + this._parser.registerCsiHandler({ prefix: '>', final: 'q' }, params => this.sendXtVersion(params)); this._parser.registerCsiHandler({ intermediates: ' ', final: 'q' }, params => this.setCursorStyle(params)); this._parser.registerCsiHandler({ final: 'r' }, params => this.setScrollRegion(params)); this._parser.registerCsiHandler({ final: 's' }, params => this.saveCursor(params)); @@ -1721,6 +1723,22 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } + /** + * CSI > Ps q + * Ps = 0 => Report xterm name and version (XTVERSION). + * + * The response is a DCS sequence identifying the version: DCS > | text ST + * + * @vt: #Y CSI XTVERSION "Report Xterm Version" "CSI > q" "Report the terminal name and version." + */ + public sendXtVersion(params: IParams): boolean { + if (params.params[0] > 0) { + return true; + } + this._coreService.triggerDataEvent(`${C0.ESC}P>|xterm.js(${XTERM_VERSION})${C0.ESC}\\`); + return true; + } + /** * Evaluate if the current terminal is the given argument. * @param term The terminal name to evaluate diff --git a/src/common/Version.ts b/src/common/Version.ts new file mode 100644 index 00000000..5f4a25b3 --- /dev/null +++ b/src/common/Version.ts @@ -0,0 +1,9 @@ +/** + * Copyright (c) 2025 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * The xterm.js version. This is updated by the publish script from package.json. + */ +export const XTERM_VERSION = '6.0.0';