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

@ -11,106 +11,89 @@ const PSEUDO_TTY_OPTIONS: PseudoTtyOptions = {
export interface SSHPseudoTerminal extends vscode.Pseudoterminal {
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;
client: Client;
/** Could be undefined if it only gets created during psy.open() instead of beforehand */
channel?: ClientChannel;
}
export async function createTerminal(client: Client, config: FileSystemConfig, workingDirectory?: string): Promise<SSHPseudoTerminal> {
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 {
export interface TerminalOptions {
client: Client;
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 onDidWrite = new vscode.EventEmitter<string>();
onDidWrite.fire(`Connecting to ${config.label || config.name}...\n`);
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 = {
status: 'opening',
config, client,
onDidWrite: onDidWrite.event,
onDidClose: onDidClose.event,
onDidOpen: onDidOpen.event,
close() {
channel?.signal('INT');
channel?.signal('SIGINT');
channel?.write('\x03');
channel?.close();
const { channel } = pseudo;
if (!channel) return;
pseudo.status = 'closed';
channel.signal('INT');
channel.signal('SIGINT');
channel.write('\x03');
channel.close();
pseudo.channel = undefined;
},
open(dims) {
onDidWrite.fire(`Running command: ${command}\n`);
(async () => {
const ch = await toPromise<ClientChannel | undefined>(cb => client.exec(command, {
pty: { ...PSEUDO_TTY_OPTIONS, cols: dims?.columns, rows: dims?.rows }
}, cb));
if (!ch) {
onDidWrite.fire(`Could not create SSH channel, running task failed\n`);
onDidClose.fire(1);
return;
async open(dims) {
console.log('Called pseudo.open');
onDidWrite.fire(`Connecting to ${config.label || config.name}...\r\n`);
try {
let setupCommand: string | undefined;
// There isn't a proper way of setting the working directory, but this should work in most cases
let { workingDirectory } = options;
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}"`;
}
pseudo.channel = channel = ch;
setupCommand = `cd ${workingDirectory}`;
}
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('close', () => onDidClose.fire(0));
(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()));
})().catch(e => {
onDidWrite.fire(`Error starting process over SSH:\n${e}\n`);
// Inform others (e.g. createTaskTerminal) that the terminal is ready to be used
pseudo.status = 'open';
onDidOpen.fire();
} catch (e) {
onDidWrite.fire(`Error starting SSH terminal:\r\n${e}\r\n`);
onDidClose.fire(1);
});
pseudo.status = 'closed';
pseudo.channel?.destroy();
}
},
setDimensions(dims) {
channel?.setWindow(dims.rows, dims.columns, HEIGHT, WIDTH);
pseudo.channel?.setWindow(dims.rows, dims.columns, HEIGHT, WIDTH);
},
handleInput(data) {
channel?.write(data);
pseudo.channel?.write(data);
},
};
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> {
return new Promise<T>((resolve, reject) => {
try {

Loading…
Cancel
Save