Merge createTerminal and createTaskTerminal

feature/ssh-config
Kelvin Schoofs 4 years ago
parent f0ee09a795
commit e158bf251c

@ -249,7 +249,7 @@ export class Manager implements vscode.TreeDataProvider<string | FileSystemConfi
} }
// Create pseudo terminal // Create pseudo terminal
con.pendingUserCount++; con.pendingUserCount++;
const pty = await createTerminal(con.client, con.actualConfig, workingDirectory); const pty = await createTerminal({ client: con.client, config: con.actualConfig, workingDirectory });
pty.onDidClose(() => con.terminals = con.terminals.filter(t => t !== pty)); pty.onDidClose(() => con.terminals = con.terminals.filter(t => t !== pty));
con.terminals.push(pty); con.terminals.push(pty);
con.pendingUserCount--; con.pendingUserCount--;
@ -312,8 +312,8 @@ export class Manager implements vscode.TreeDataProvider<string | FileSystemConfi
new vscode.CustomExecution(async () => { new vscode.CustomExecution(async () => {
const connection = await this.createConnection(host); const connection = await this.createConnection(host);
connection.pendingUserCount++; connection.pendingUserCount++;
const { createTaskTerminal } = await import('./pseudoTerminal'); const { createTerminal } = await import('./pseudoTerminal');
const psy = await createTaskTerminal({ const psy = await createTerminal({
command, command,
client: connection.client, client: connection.client,
config: connection.actualConfig, config: connection.actualConfig,

@ -11,106 +11,89 @@ const PSEUDO_TTY_OPTIONS: PseudoTtyOptions = {
export interface SSHPseudoTerminal extends vscode.Pseudoterminal { export interface SSHPseudoTerminal extends vscode.Pseudoterminal {
onDidClose: vscode.Event<number>; // Redeclaring that it isn't undefined onDidClose: vscode.Event<number>; // Redeclaring that it isn't undefined
onDidOpen: vscode.Event<void>;
handleInput(data: string): void; // We don't support/need read-only terminals for now
status: 'opening' | 'open' | 'closed';
config: FileSystemConfig; config: FileSystemConfig;
client: Client; client: Client;
/** Could be undefined if it only gets created during psy.open() instead of beforehand */ /** Could be undefined if it only gets created during psy.open() instead of beforehand */
channel?: ClientChannel; channel?: ClientChannel;
} }
export async function createTerminal(client: Client, config: FileSystemConfig, workingDirectory?: string): Promise<SSHPseudoTerminal> { export interface TerminalOptions {
const channel = await toPromise<ClientChannel | undefined>(cb => client.shell(PSEUDO_TTY_OPTIONS, cb));
if (!channel) throw new Error('Could not create remote terminal');
const onDidWrite = new vscode.EventEmitter<string>();
onDidWrite.fire(`Connecting to ${config.label || config.name}...\n`);
(channel as Readable).on('data', chunk => onDidWrite.fire(chunk.toString()));
channel.stderr.on('data', chunk => onDidWrite.fire(chunk.toString()));
const onDidClose = new vscode.EventEmitter<number>();
channel.on('exit', onDidClose.fire);
// Hopefully the exit event fires first
channel.on('close', () => onDidClose.fire(0));
// There isn't a proper way of setting the working directory, but this should work in most cases
if (workingDirectory) {
if (workingDirectory.startsWith('~')) {
// So `cd "~/a/b/..." apparently doesn't work, but `~/"a/b/..."` does
// `"~"` would also fail but `~/""` works fine it seems
workingDirectory = `~/"${workingDirectory.substr(2)}"`;
} else {
workingDirectory = `"${workingDirectory}"`;
}
channel.write(`cd ${workingDirectory}\n`);
}
const pseudo: SSHPseudoTerminal = {
config, client, channel,
onDidWrite: onDidWrite.event,
onDidClose: onDidClose.event,
close() {
channel.signal('INT');
channel.signal('SIGINT');
channel.write('\x03');
channel.close();
},
open(dims) {
if (!dims) return;
channel.setWindow(dims.rows, dims.columns, HEIGHT, WIDTH);
},
setDimensions(dims) {
channel.setWindow(dims.rows, dims.columns, HEIGHT, WIDTH);
},
handleInput(data) {
channel.write(data);
},
};
return pseudo;
}
export interface TaskTerminalOptions {
client: Client; client: Client;
config: FileSystemConfig; config: FileSystemConfig;
command: string; workingDirectory?: string;
/** The command to run in the remote shell. If undefined, a (regular interactive) shell is started instead */
command?: string;
} }
export async function createTaskTerminal(options: TaskTerminalOptions): Promise<SSHPseudoTerminal> { export async function createTerminal(options: TerminalOptions): Promise<SSHPseudoTerminal> {
const { client, config, command } = options; const { client, config, command } = options;
const onDidWrite = new vscode.EventEmitter<string>(); const onDidWrite = new vscode.EventEmitter<string>();
onDidWrite.fire(`Connecting to ${config.label || config.name}...\n`);
const onDidClose = new vscode.EventEmitter<number>(); const onDidClose = new vscode.EventEmitter<number>();
let channel: ClientChannel; const onDidOpen = new vscode.EventEmitter<void>();
// Won't actually open the remote terminal until pseudo.open(dims) is called
const pseudo: SSHPseudoTerminal = { const pseudo: SSHPseudoTerminal = {
status: 'opening',
config, client, config, client,
onDidWrite: onDidWrite.event, onDidWrite: onDidWrite.event,
onDidClose: onDidClose.event, onDidClose: onDidClose.event,
onDidOpen: onDidOpen.event,
close() { close() {
channel?.signal('INT'); const { channel } = pseudo;
channel?.signal('SIGINT'); if (!channel) return;
channel?.write('\x03'); pseudo.status = 'closed';
channel?.close(); channel.signal('INT');
channel.signal('SIGINT');
channel.write('\x03');
channel.close();
pseudo.channel = undefined;
}, },
open(dims) { async open(dims) {
onDidWrite.fire(`Running command: ${command}\n`); console.log('Called pseudo.open');
(async () => { onDidWrite.fire(`Connecting to ${config.label || config.name}...\r\n`);
const ch = await toPromise<ClientChannel | undefined>(cb => client.exec(command, { try {
pty: { ...PSEUDO_TTY_OPTIONS, cols: dims?.columns, rows: dims?.rows } let setupCommand: string | undefined;
}, cb)); // There isn't a proper way of setting the working directory, but this should work in most cases
if (!ch) { let { workingDirectory } = options;
onDidWrite.fire(`Could not create SSH channel, running task failed\n`); if (workingDirectory) {
onDidClose.fire(1); if (workingDirectory.startsWith('~')) {
return; // So `cd "~/a/b/..." apparently doesn't work, but `~/"a/b/..."` does
// `"~"` would also fail but `~/""` works fine it seems
workingDirectory = `~/"${workingDirectory.substr(2)}"`;
} else {
workingDirectory = `"${workingDirectory}"`;
}
setupCommand = `cd ${workingDirectory}`;
} }
pseudo.channel = channel = ch; const pseudoTtyOptions: PseudoTtyOptions = { ...PSEUDO_TTY_OPTIONS, cols: dims?.columns, rows: dims?.rows };
const channel = await toPromise<ClientChannel | undefined>(cb => command ?
client.exec(setupCommand ? `${setupCommand}; ${command}` : command, { pty: pseudoTtyOptions }, cb) :
client.shell(pseudoTtyOptions, cb));
if (!channel) throw new Error('Could not create remote terminal');
if (!command && setupCommand) channel.write(setupCommand + '\n');
pseudo.channel = channel;
channel.on('exit', onDidClose.fire); channel.on('exit', onDidClose.fire);
channel.on('close', () => onDidClose.fire(0)); channel.on('close', () => onDidClose.fire(0));
(channel as Readable).on('data', chunk => onDidWrite.fire(chunk.toString())); (channel as Readable).on('data', chunk => onDidWrite.fire(chunk.toString()));
// TODO: Keep track of stdout's color, switch to red, output, then switch back?
channel.stderr.on('data', chunk => onDidWrite.fire(chunk.toString())); channel.stderr.on('data', chunk => onDidWrite.fire(chunk.toString()));
})().catch(e => { // Inform others (e.g. createTaskTerminal) that the terminal is ready to be used
onDidWrite.fire(`Error starting process over SSH:\n${e}\n`); pseudo.status = 'open';
onDidOpen.fire();
} catch (e) {
onDidWrite.fire(`Error starting SSH terminal:\r\n${e}\r\n`);
onDidClose.fire(1); onDidClose.fire(1);
}); pseudo.status = 'closed';
pseudo.channel?.destroy();
}
}, },
setDimensions(dims) { setDimensions(dims) {
channel?.setWindow(dims.rows, dims.columns, HEIGHT, WIDTH); pseudo.channel?.setWindow(dims.rows, dims.columns, HEIGHT, WIDTH);
}, },
handleInput(data) { handleInput(data) {
channel?.write(data); pseudo.channel?.write(data);
}, },
}; };
return pseudo; return pseudo;

@ -1,5 +1,5 @@
export type toPromiseCallback<T> = (err: Error | null | undefined, res?: T) => void; export type toPromiseCallback<T> = (err?: Error | null, res?: T) => void;
export async function toPromise<T>(func: (cb: toPromiseCallback<T>) => void): Promise<T> { export async function toPromise<T>(func: (cb: toPromiseCallback<T>) => void): Promise<T> {
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
try { try {

Loading…
Cancel
Save