Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a node.js target for xterm.js #3212

Merged
merged 39 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
157055c
Original files
joyceerhl Jan 10, 2021
5e6e65c
Get dupe of src/browser/public/Terminal compiling
joyceerhl Jan 11, 2021
2f88498
Add index.js used to test xterm-core
joyceerhl Jan 11, 2021
c015379
Merge branch 'master' into nodetarget
Tyriar Jan 11, 2021
238f6ab
Merge branch 'master' into nodetarget
Tyriar Jul 22, 2021
91c7032
Use core terminal in common, remove duplicate classes
Tyriar Jul 22, 2021
625e17e
Fix some errors, move TerminalCore to common/
Tyriar Jul 22, 2021
df17288
Create headless project, fix compile
Tyriar Jul 22, 2021
61ede1c
Update instructions to run
Tyriar Jul 22, 2021
7117951
Output headless to out/headless
Tyriar Jul 22, 2021
9b2e511
Fix webpack headless
Tyriar Jul 22, 2021
2191d1a
Output commonjs for headless
Tyriar Jul 22, 2021
01babd1
Do a pass of headless/Terminal members
Tyriar Jul 22, 2021
40992a7
Update node test
Tyriar Jul 22, 2021
a6e6c07
Add first headless test
Tyriar Jul 22, 2021
f43fcaf
Move addon manager to common and use in xterm-core
Tyriar Aug 10, 2021
468f419
Merge branch 'master' into nodetarget
Tyriar Aug 10, 2021
f769d03
xterm-core -> xterm-headless
Tyriar Aug 10, 2021
4a200e5
Create headless package script
Tyriar Aug 10, 2021
15bd827
Improve headless packaging
Tyriar Aug 10, 2021
6aa0782
Publish dry run in package_headless
Tyriar Aug 10, 2021
83ddf0f
Force publish of headless even in PR
Tyriar Aug 11, 2021
f685d8e
Force publish
Tyriar Aug 11, 2021
97fa4a5
Package headless in release step
Tyriar Aug 11, 2021
b6e5430
Revert "Force publish"
Tyriar Aug 11, 2021
5affa70
Undo headless force publish changes
Tyriar Aug 11, 2021
3e9b1ba
Remove force publish changes
Tyriar Aug 11, 2021
370d0c5
Copy logo-full.png
Tyriar Aug 11, 2021
be16429
Remove unused compile script
Tyriar Aug 11, 2021
454c272
Add npmignore
Tyriar Aug 11, 2021
dda4904
Fix xterm-headless lib file
Tyriar Aug 11, 2021
6ac7e18
Merge branch 'master' into nodetarget
Tyriar Aug 12, 2021
7e7dcd9
Move node-test into headless folder
Tyriar Aug 12, 2021
ec80fc9
Remove alpha from package.json version
Tyriar Aug 12, 2021
e96b472
Start on headless unit tests
Tyriar Aug 12, 2021
df48e9f
Headless event tests
Tyriar Aug 12, 2021
f8066d6
Full tests for xterm-headless
Tyriar Aug 12, 2021
f45f5b4
Remove unneeded manual headless test
Tyriar Aug 12, 2021
d694feb
Polish readme
Tyriar Aug 12, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions core-webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
Tyriar marked this conversation as resolved.
Show resolved Hide resolved
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/

const path = require('path');

/**
* This webpack config does a production build for xterm-core.js. It works by taking the output from tsc
* (via `yarn watch` or `yarn prebuild`) which are put into `xterm-core/` and webpacks them into a
* production mode commonjs library module in `lib/`. The aliases are used fix up the absolute paths
* output by tsc (because of `baseUrl` and `paths` in `tsconfig.json`.
*/
module.exports = {
entry: './xterm-core/common/public/Terminal.js',
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre",
exclude: /node_modules/
}
]
},
resolve: {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: path.resolve('./xterm-core/common')
}
},
output: {
filename: 'xterm-core.js',
path: path.resolve('./lib'),
libraryTarget: 'commonjs'
},
mode: 'production',
target: 'node',
};
10 changes: 10 additions & 0 deletions node-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Cursory test that 'xterm-core' works:

```
# From root of this repo
npm run compile # Outputs to xterm-core
npx webpack --config core-webpack.config.js # Outputs to lib
cd node-test
npm link ../lib/
node index.js
```
12 changes: 12 additions & 0 deletions node-test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const xterm = require('xterm-core');

console.log('Creating xterm-core terminal...');
const terminal = new xterm.Terminal();
console.log('Writing `ls` to terminal...')
terminal.write('ls', () => {
const bufferLine = terminal.buffer.normal.getLine(terminal.buffer.normal.cursorY);
const contents = bufferLine.translateToString();
console.log(`Contents of terminal active buffer are: ${contents}`); // ls
});
11 changes: 11 additions & 0 deletions node-test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": ""
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"scripts": {
"prepackage": "npm run build",
"package": "webpack",
"compile": "tsc -b ./src/common/public/tsconfig.json",
Tyriar marked this conversation as resolved.
Show resolved Hide resolved
"start": "node demo/start",
"lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/",
"test": "npm run test-unit",
Expand Down
239 changes: 239 additions & 0 deletions src/common/public/Terminal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/

import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm-core';
import { IBufferLine, ICellData } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
import { CellData } from 'common/buffer/CellData';
import { Terminal as TerminalCore } from './TerminalCore';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { IParams } from 'common/parser/Types';
import { ITerminal } from 'common/public/types';

export class Terminal implements ITerminalApi {
private _core: ITerminal;
private _parser: IParser | undefined;
private _buffer: BufferNamespaceApi | undefined;

constructor(options?: ITerminalOptions) {
this._core = new TerminalCore(options);
}

private _checkProposedApi(): void {
if (!this._core.optionsService.options.allowProposedApi) {
throw new Error('You must set the allowProposedApi option to true to use proposed API');
}
}

public get onCursorMove(): IEvent<void> { return this._core.onCursorMove; }
public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }
public get onData(): IEvent<string> { return this._core.onData; }
public get onBinary(): IEvent<string> { return this._core.onBinary; }
public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }

public get parser(): IParser {
this._checkProposedApi();
if (!this._parser) {
this._parser = new ParserApi(this._core);
}
return this._parser;
}
public get unicode(): IUnicodeHandling {
this._checkProposedApi();
return new UnicodeApi(this._core);
}
public get rows(): number { return this._core.rows; }
public get cols(): number { return this._core.cols; }
public get buffer(): IBufferNamespaceApi {
this._checkProposedApi();
if (!this._buffer) {
this._buffer = new BufferNamespaceApi(this._core);
}
return this._buffer;
}
public get markers(): ReadonlyArray<IMarker> {
this._checkProposedApi();
return this._core.markers;
}
public resize(columns: number, rows: number): void {
this._verifyIntegers(columns, rows);
this._core.resize(columns, rows);
}
public registerMarker(cursorYOffset: number): IMarker | undefined {
this._checkProposedApi();
this._verifyIntegers(cursorYOffset);
return this._core.addMarker(cursorYOffset);
}
public addMarker(cursorYOffset: number): IMarker | undefined {
return this.registerMarker(cursorYOffset);
}
public dispose(): void {
this._core.dispose();
}
public clear(): void {
this._core.clear();
}
public write(data: string | Uint8Array, callback?: () => void): void {
this._core.write(data, callback);
}
public writeUtf8(data: Uint8Array, callback?: () => void): void {
this._core.write(data, callback);
}
public writeln(data: string | Uint8Array, callback?: () => void): void {
this._core.write(data);
this._core.write('\r\n', callback);
}
public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean;
public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
public getOption(key: string): any;
public getOption(key: any): any {
return this._core.optionsService.getOption(key);
}
public setOption(key: 'bellSound' | 'fontFamily' | 'termName' | 'wordSeparator', value: string): void;
public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void;
public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void;
public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void;
public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void;
public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell', value: boolean): void;
public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
public setOption(key: 'cols' | 'rows', value: number): void;
public setOption(key: string, value: any): void;
public setOption(key: any, value: any): void {
this._core.optionsService.setOption(key, value);
}
public reset(): void {
this._core.reset();
}

private _verifyIntegers(...values: number[]): void {
for (const value of values) {
if (value === Infinity || isNaN(value) || value % 1 !== 0) {
throw new Error('This API only accepts integers');
}
}
}
}

class BufferApiView implements IBufferApi {
Tyriar marked this conversation as resolved.
Show resolved Hide resolved
constructor(
private _buffer: IBuffer,
public readonly type: 'normal' | 'alternate'
) { }

public init(buffer: IBuffer): BufferApiView {
this._buffer = buffer;
return this;
}

public get cursorY(): number { return this._buffer.y; }
public get cursorX(): number { return this._buffer.x; }
public get viewportY(): number { return this._buffer.ydisp; }
public get baseY(): number { return this._buffer.ybase; }
public get length(): number { return this._buffer.lines.length; }
public getLine(y: number): IBufferLineApi | undefined {
const line = this._buffer.lines.get(y);
if (!line) {
return undefined;
}
return new BufferLineApiView(line);
}
public getNullCell(): IBufferCellApi { return new CellData(); }
}

class BufferNamespaceApi implements IBufferNamespaceApi {
private _normal: BufferApiView;
private _alternate: BufferApiView;
private _onBufferChange = new EventEmitter<IBufferApi>();
public get onBufferChange(): IEvent<IBufferApi> { return this._onBufferChange.event; }

constructor(private _core: ITerminal) {
this._normal = new BufferApiView(this._core.buffers.normal, 'normal');
this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate');
this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active));
}
public get active(): IBufferApi {
if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; }
if (this._core.buffers.active === this._core.buffers.alt) { return this.alternate; }
throw new Error('Active buffer is neither normal nor alternate');
}
public get normal(): IBufferApi {
return this._normal.init(this._core.buffers.normal);
}
public get alternate(): IBufferApi {
return this._alternate.init(this._core.buffers.alt);
}
}

class BufferLineApiView implements IBufferLineApi {
constructor(private _line: IBufferLine) { }

public get isWrapped(): boolean { return this._line.isWrapped; }
public get length(): number { return this._line.length; }
public getCell(x: number, cell?: IBufferCellApi): IBufferCellApi | undefined {
if (x < 0 || x >= this._line.length) {
return undefined;
}

if (cell) {
this._line.loadCell(x, <ICellData>cell);
return cell;
}
return this._line.loadCell(x, new CellData());
}
public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {
return this._line.translateToString(trimRight, startColumn, endColumn);
}
}

class ParserApi implements IParser {
constructor(private _core: ITerminal) { }

public registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray()));
}
public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
return this.registerCsiHandler(id, callback);
}
public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable {
return this._core.addDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray()));
}
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable {
return this.registerDcsHandler(id, callback);
}
public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
return this._core.addEscHandler(id, handler);
}
public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
return this.registerEscHandler(id, handler);
}
public registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this._core.addOscHandler(ident, callback);
}
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this.registerOscHandler(ident, callback);
}
}

class UnicodeApi implements IUnicodeHandling {
constructor(private _core: ITerminal) { }

public register(provider: IUnicodeVersionProvider): void {
this._core.unicodeService.register(provider);
}

public get versions(): string[] {
return this._core.unicodeService.versions;
}

public get activeVersion(): string {
return this._core.unicodeService.activeVersion;
}

public set activeVersion(version: string) {
this._core.unicodeService.activeVersion = version;
}
}
Loading