installed pty
[VSoRC/.git] / node_modules / node-pty / src / windowsTerminal.ts
1 /**
2  * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
3  * Copyright (c) 2016, Daniel Imms (MIT License).
4  * Copyright (c) 2018, Microsoft Corporation (MIT License).
5  */
6
7 import { Socket } from 'net';
8 import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
9 import { WindowsPtyAgent } from './windowsPtyAgent';
10 import { IPtyOpenOptions, IWindowsPtyForkOptions } from './interfaces';
11 import { ArgvOrCommandLine } from './types';
12 import { assign } from './utils';
13
14 const DEFAULT_FILE = 'cmd.exe';
15 const DEFAULT_NAME = 'Windows Shell';
16
17 export class WindowsTerminal extends Terminal {
18   private _isReady: boolean;
19   private _deferreds: any[];
20   private _agent: WindowsPtyAgent;
21
22   constructor(file?: string, args?: ArgvOrCommandLine, opt?: IWindowsPtyForkOptions) {
23     super(opt);
24
25     // Initialize arguments
26     args = args || [];
27     file = file || DEFAULT_FILE;
28     opt = opt || {};
29     opt.env = opt.env || process.env;
30
31     if (opt.encoding) {
32       console.warn('Setting encoding on Windows is not supported');
33     }
34
35     const env = assign({}, opt.env);
36     this._cols = opt.cols || DEFAULT_COLS;
37     this._rows = opt.rows || DEFAULT_ROWS;
38     const cwd = opt.cwd || process.cwd();
39     const name = opt.name || env.TERM || DEFAULT_NAME;
40     const parsedEnv = this._parseEnv(env);
41
42     // If the terminal is ready
43     this._isReady = false;
44
45     // Functions that need to run after `ready` event is emitted.
46     this._deferreds = [];
47
48     // Create new termal.
49     this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.conptyInheritCursor);
50     this._socket = this._agent.outSocket;
51
52     // Not available until `ready` event emitted.
53     this._pid = this._agent.innerPid;
54     this._fd = this._agent.fd;
55     this._pty = this._agent.pty;
56
57     // The forked windows terminal is not available until `ready` event is
58     // emitted.
59     this._socket.on('ready_datapipe', () => {
60
61       // These events needs to be forwarded.
62       ['connect', 'data', 'end', 'timeout', 'drain'].forEach(event => {
63         this._socket.on(event, () => {
64
65           // Wait until the first data event is fired then we can run deferreds.
66           if (!this._isReady && event === 'data') {
67
68             // Terminal is now ready and we can avoid having to defer method
69             // calls.
70             this._isReady = true;
71
72             // Execute all deferred methods
73             this._deferreds.forEach(fn => {
74               // NB! In order to ensure that `this` has all its references
75               // updated any variable that need to be available in `this` before
76               // the deferred is run has to be declared above this forEach
77               // statement.
78               fn.run();
79             });
80
81             // Reset
82             this._deferreds = [];
83
84           }
85         });
86       });
87
88       // Shutdown if `error` event is emitted.
89       this._socket.on('error', err => {
90         // Close terminal session.
91         this._close();
92
93         // EIO, happens when someone closes our child process: the only process
94         // in the terminal.
95         // node < 0.6.14: errno 5
96         // node >= 0.6.14: read EIO
97         if ((<any>err).code) {
98           if (~(<any>err).code.indexOf('errno 5') || ~(<any>err).code.indexOf('EIO')) return;
99         }
100
101         // Throw anything else.
102         if (this.listeners('error').length < 2) {
103           throw err;
104         }
105       });
106
107       // Cleanup after the socket is closed.
108       this._socket.on('close', () => {
109         this.emit('exit', this._agent.exitCode);
110         this._close();
111       });
112
113     });
114
115     this._file = file;
116     this._name = name;
117
118     this._readable = true;
119     this._writable = true;
120
121     this._forwardEvents();
122   }
123
124   protected _write(data: string): void {
125     this._defer(this._doWrite, data);
126   }
127
128   private _doWrite(data: string): void {
129     this._agent.inSocket.write(data);
130   }
131
132   /**
133    * openpty
134    */
135
136   public static open(options?: IPtyOpenOptions): void {
137     throw new Error('open() not supported on windows, use Fork() instead.');
138   }
139
140   /**
141    * TTY
142    */
143
144   public resize(cols: number, rows: number): void {
145     if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
146       throw new Error('resizing must be done using positive cols and rows');
147     }
148     this._defer(() => {
149       this._agent.resize(cols, rows);
150       this._cols = cols;
151       this._rows = rows;
152     });
153   }
154
155   public destroy(): void {
156     this._defer(() => {
157       this.kill();
158     });
159   }
160
161   public kill(signal?: string): void {
162     this._defer(() => {
163       if (signal) {
164         throw new Error('Signals not supported on windows.');
165       }
166       this._close();
167       this._agent.kill();
168     });
169   }
170
171   private _defer<A extends any>(deferredFn: (arg?: A) => void, arg?: A): void {
172     // If the terminal is ready, execute.
173     if (this._isReady) {
174       deferredFn.call(this, arg);
175       return;
176     }
177
178     // Queue until terminal is ready.
179     this._deferreds.push({
180       run: () => deferredFn.call(this, arg)
181     });
182   }
183
184   public get process(): string { return this._name; }
185   public get master(): Socket { throw new Error('master is not supported on Windows'); }
186   public get slave(): Socket { throw new Error('slave is not supported on Windows'); }
187 }