Skip to content

Commit

Permalink
merge upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
oznu committed May 7, 2019
2 parents 8d4eac9 + 3f4ef58 commit a608744
Show file tree
Hide file tree
Showing 18 changed files with 179 additions and 40 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ Copyright (c) 2012-2015, Christopher Jeffrey (MIT License).
Copyright (c) 2016, Daniel Imms (MIT License).
Copyright (c) 2018, Microsoft Corporation (MIT License).
Copyright (c) 2018, David Wilson (MIT License).
Copyright (c) 2018, oznu (MIT License).
Copyright (c) 2018, oznu (MIT License).
2 changes: 1 addition & 1 deletion examples/electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
"dependencies": {
"electron": "^4.0.1",
"node-pty": "^0.8.0",
"xterm": "3.12.0"
"xterm": "^3.12.1"
}
}
25 changes: 9 additions & 16 deletions examples/fork/index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
var os = require('os');
var pty = require('../..');

var shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
var isWindows = os.platform() === 'win32';
var shell = isWindows ? 'powershell.exe' : 'bash';

var ptyProcess = pty.spawn(shell, [], {
name: 'xterm-256color',
cols: 80,
rows: 26,
cwd: os.platform() === 'win32' ? process.env.USERPROFILE : process.env.HOME,
env: Object.assign({ TEST: "abc" }, process.env),
cwd: isWindows ? process.env.USERPROFILE : process.env.HOME,
env: Object.assign({ TEST: "Environment vars work" }, process.env),
experimentalUseConpty: true
});

ptyProcess.on('data', function(data) {
// console.log(data);
process.stdout.write(data);
});
ptyProcess.onData(data => process.stdout.write(data));

ptyProcess.write('dir\r');
// ptyProcess.write('ls\r');
ptyProcess.write(isWindows ? 'dir\r' : 'ls\r');

setTimeout(() => {
ptyProcess.resize(30, 19);
ptyProcess.write(shell === 'powershell.exe' ? '$Env:TEST\r' : 'echo %TEST%\r');
ptyProcess.write(isWindows ? '$Env:TEST\r' : 'echo $TEST\r');
}, 2000);

process.on('exit', () => {
ptyProcess.kill();
});
process.on('exit', () => ptyProcess.kill());

setTimeout(() => {
process.exit();
}, 4000);
setTimeout(() => process.exit(), 4000);
4 changes: 1 addition & 3 deletions examples/killDeepTree/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ var ptyProcess = pty.spawn(shell, [], {
env: process.env
});

ptyProcess.on('data', function(data) {
process.stdout.write(data);
});
ptyProcess.onData((data) => process.stdout.write(data));

ptyProcess.write('start notepad\r');
ptyProcess.write('npm start\r');
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"devDependencies": {
"@types/mocha": "^5.0.0",
"@types/node": "^6.0.104",
"@types/ps-list": "^6.0.0",
"cross-env": "^5.1.4",
"mocha": "^5.0.5",
"pollUntil": "^1.0.3",
Expand Down
8 changes: 6 additions & 2 deletions src/conpty_console_list_agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
* single console attached to a process.
*/

import { loadNative } from './utils';
let getConsoleProcessList: any;
try {
getConsoleProcessList = require('../build/Release/conpty_console_list.node').getConsoleProcessList;
} catch (err) {
getConsoleProcessList = require('../build/Debug/conpty_console_list.node').getConsoleProcessList;
}

const getConsoleProcessList = loadNative('conpty_console_list').getConsoleProcessList;
const shellPid = parseInt(process.argv[2], 10);
const consoleProcessList = getConsoleProcessList(shellPid);
process.send({ consoleProcessList });
Expand Down
30 changes: 30 additions & 0 deletions src/eventEmitter2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2019, Microsoft Corporation (MIT License).
*/

import * as assert from 'assert';
import { EventEmitter2 } from './eventEmitter2';

describe('EventEmitter2', () => {
it('should fire listeners multiple times', () => {
const order: string[] = [];
const emitter = new EventEmitter2<number>();
emitter.event(data => order.push(data + 'a'));
emitter.event(data => order.push(data + 'b'));
emitter.fire(1);
emitter.fire(2);
assert.deepEqual(order, [ '1a', '1b', '2a', '2b' ]);
});

it('should not fire listeners once disposed', () => {
const order: string[] = [];
const emitter = new EventEmitter2<number>();
emitter.event(data => order.push(data + 'a'));
const disposeB = emitter.event(data => order.push(data + 'b'));
emitter.event(data => order.push(data + 'c'));
emitter.fire(1);
disposeB.dispose();
emitter.fire(2);
assert.deepEqual(order, [ '1a', '1b', '1c', '2a', '2c' ]);
});
});
48 changes: 48 additions & 0 deletions src/eventEmitter2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) 2019, Microsoft Corporation (MIT License).
*/

import { IDisposable } from './types';

interface IListener<T> {
(e: T): void;
}

export interface IEvent<T> {
(listener: (e: T) => any): IDisposable;
}

export class EventEmitter2<T> {
private _listeners: IListener<T>[] = [];
private _event?: IEvent<T>;

public get event(): IEvent<T> {
if (!this._event) {
this._event = (listener: (e: T) => any) => {
this._listeners.push(listener);
const disposable = {
dispose: () => {
for (let i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
}
}
}
};
return disposable;
};
}
return this._event;
}

public fire(data: T): void {
const queue: IListener<T>[] = [];
for (let i = 0; i < this._listeners.length; i++) {
queue.push(this._listeners[i]);
}
for (let i = 0; i < queue.length; i++) {
queue[i].call(undefined, data);
}
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ export function open(options: IPtyOpenOptions): ITerminal {
* Expose the native API when not Windows, note that this is not public API and
* could be removed at any time.
*/
export const native = (process.platform !== 'win32' ? require(path.join('..', 'build', 'Release', 'pty.node')) : null);
export const native = (process.platform !== 'win32' ? require('../build/Release/pty.node') : null);
13 changes: 12 additions & 1 deletion src/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/

import * as path from 'path';
import { Socket } from 'net';
import { EventEmitter } from 'events';
import { ITerminal, IPtyForkOptions } from './interfaces';
import { EventEmitter2, IEvent } from './eventEmitter2';
import { IExitEvent } from './types';

export const DEFAULT_COLS: number = 80;
export const DEFAULT_ROWS: number = 24;
Expand All @@ -28,6 +29,11 @@ export abstract class Terminal implements ITerminal {

protected _internalee: EventEmitter;

private _onData = new EventEmitter2<string>();
public get onData(): IEvent<string> { return this._onData.event; }
private _onExit = new EventEmitter2<IExitEvent>();
public get onExit(): IEvent<IExitEvent> { return this._onExit.event; }

public get pid(): number { return this._pid; }

constructor(opt?: IPtyForkOptions) {
Expand All @@ -50,6 +56,11 @@ export abstract class Terminal implements ITerminal {
this._checkType('encoding', opt.encoding ? opt.encoding : null, 'string');
}

protected _forwardEvents(): void {
this.on('data', e => this._onData.fire(e));
this.on('exit', (exitCode, signal) => this._onExit.fire({ exitCode, signal }));
}

private _checkType(name: string, value: any, type: string): void {
if (value && typeof value !== type) {
throw new Error(`${name} must be a ${type} (not a ${typeof value})`);
Expand Down
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@
*/

export type ArgvOrCommandLine = string[] | string;

export interface IExitEvent {
exitCode: number;
signal: number | undefined;
}

export interface IDisposable {
dispose(): void;
}
8 changes: 7 additions & 1 deletion src/unix/pty.cc
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,13 @@ NAN_METHOD(PtyResize) {
winp.ws_ypixel = 0;

if (ioctl(fd, TIOCSWINSZ, &winp) == -1) {
return Nan::ThrowError("ioctl(2) failed.");
switch (errno) {
case EBADF: return Nan::ThrowError("ioctl(2) failed, EBADF");
case EFAULT: return Nan::ThrowError("ioctl(2) failed, EFAULT");
case EINVAL: return Nan::ThrowError("ioctl(2) failed, EINVAL");
case ENOTTY: return Nan::ThrowError("ioctl(2) failed, ENOTTY");
}
return Nan::ThrowError("ioctl(2) failed");
}

return info.GetReturnValue().SetUndefined();
Expand Down
11 changes: 9 additions & 2 deletions src/unixTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import * as net from 'net';
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
import { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from './interfaces';
import { ArgvOrCommandLine } from './types';
import { assign, loadNative } from './utils';
import { assign } from './utils';

const pty: IUnixNative = loadNative('pty');
let pty: IUnixNative;
try {
pty = require('../build/Release/pty.node');
} catch {
pty = require('../build/Debug/pty.node');
}

const DEFAULT_FILE = 'sh';
const DEFAULT_NAME = 'xterm';
Expand Down Expand Up @@ -150,6 +155,8 @@ export class UnixTerminal extends Terminal {
this._close();
this.emit('close');
});

this._forwardEvents();
}

/**
Expand Down
8 changes: 0 additions & 8 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,3 @@ export function assign(target: any, ...sources: any[]): any {
sources.forEach(source => Object.keys(source).forEach(key => target[key] = source[key]));
return target;
}

export function loadNative(moduleName: string): any {
try {
return require(path.join('..', 'build', 'Release', `${moduleName}.node`));
} catch {
return require(path.join('..', 'build', 'Debug', `${moduleName}.node`));
}
}
3 changes: 3 additions & 0 deletions src/win/conpty.cc
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ static NAN_METHOD(PtyStartProcess) {
HPCON hpc;
HRESULT hr = CreateNamedPipesAndPseudoConsole({cols, rows}, 0, &hIn, &hOut, &hpc, inName, outName, pipeName);

// Restore default handling of ctrl+c
SetConsoleCtrlHandler(NULL, FALSE);

// Set return values
marshal = Nan::New<v8::Object>();

Expand Down
13 changes: 10 additions & 3 deletions src/windowsPtyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import * as os from 'os';
import * as path from 'path';
import { Socket } from 'net';
import { ArgvOrCommandLine } from './types';
import { loadNative } from './utils';
import { fork } from 'child_process';

let conptyNative: IConptyNative;
Expand Down Expand Up @@ -59,11 +58,19 @@ export class WindowsPtyAgent {
}
if (this._useConpty) {
if (!conptyNative) {
conptyNative = loadNative('conpty');
try {
conptyNative = require('../build/Release/conpty.node');
} catch (err) {
conptyNative = require('../build/Debug/conpty.node');
}
}
} else {
if (!winptyNative) {
winptyNative = loadNative('pty');
try {
winptyNative = require('../build/Release/pty.node');
} catch (err) {
winptyNative = require('../build/Debug/pty.node');
}
}
}
this._ptyNative = this._useConpty ? conptyNative : winptyNative;
Expand Down
2 changes: 2 additions & 0 deletions src/windowsTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ export class WindowsTerminal extends Terminal {

this._readable = true;
this._writable = true;

this._forwardEvents();
}

/**
Expand Down
30 changes: 30 additions & 0 deletions typings/node-pty.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,24 @@ declare module 'node-pty-prebuilt-multiarch' {
*/
process: string;

/**
* Adds an event listener for when a data event fires. This happens when data is returned from
* the pty.
* @returns an `IDisposable` to stop listening.
*/
onData: IEvent<string>;

/**
* Adds an event listener for when an exit event fires. This happens when the pty exits.
* @returns an `IDisposable` to stop listening.
*/
onExit: IEvent<{ exitCode: number, signal?: number }>;

/**
* Adds a listener to the data event, fired when data is returned from the pty.
* @param event The name of the event.
* @param listener The callback function.
* @deprecated Use IPty.onData
*/
on(event: 'data', listener: (data: string) => void): void;

Expand All @@ -62,6 +76,7 @@ declare module 'node-pty-prebuilt-multiarch' {
* @param event The name of the event.
* @param listener The callback function, exitCode is the exit code of the process and signal is
* the signal that triggered the exit. signal is not supported on Windows.
* @deprecated Use IPty.onExit
*/
on(event: 'exit', listener: (exitCode: number, signal?: number) => void): void;

Expand All @@ -86,4 +101,19 @@ declare module 'node-pty-prebuilt-multiarch' {
*/
kill(signal?: string): void;
}

/**
* An object that can be disposed via a dispose function.
*/
export interface IDisposable {
dispose(): void;
}

/**
* An event that can be listened to.
* @returns an `IDisposable` to stop listening.
*/
export interface IEvent<T> {
(listener: (e: T) => any): IDisposable;
}
}

0 comments on commit a608744

Please sign in to comment.