Add "Open remote SSH terminal" in explorer for ssh:// directories + make root-sensitive

pull/208/head
Kelvin Schoofs 4 years ago
parent bbba8b7280
commit ffef5c64fa

@ -47,12 +47,16 @@ The configurations for SSH file systems can now also be used to spawn terminals:
![Terminals](./media/terminals.png) ![Terminals](./media/terminals.png)
Currently the working directory will always be the user's home directory, similar to directly connecting to the server over ssh. I'm thinking about making it automatically move to the `root` directory if possible. Opening a terminal automatically sets the working directory to the `root` directory, unless a directory was explicitly selected to open the terminal in:
![Explorer Terminal](./media/explorer-terminal.png)
This replaces the built-in "Open terminal" context menu option that isn't provided for remote field systems. For non-ssh:// file systems, the original "Open terminal" menu item is still displayed, the remote version only affects ssh:// file systems.
### New task type ### New task type
This extension adds a new task type `ssh-shell` which can be used to run commands on a configured remote host: This extension adds a new task type `ssh-shell` which can be used to run commands on a configured remote host:
![Terminals](./media/tasks.png) ![Tasks](./media/tasks.png)
Currently only the `command` field is supported. The goal is to replicate part of the `shell` task structure, e.g. an `args` array, support for `${workspaceFolder}`, ... Currently only the `command` field is supported. The goal is to replicate part of the `shell` task structure, e.g. an `args` array, support for `${workspaceFolder}`, ...

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

@ -131,7 +131,7 @@
}, },
{ {
"command": "sshfs.settings", "command": "sshfs.settings",
"when": "view == 'sshfs-configs' && !(viewItem)", "when": "view == 'sshfs-configs'",
"group": "SSH FS@6" "group": "SSH FS@6"
} }
], ],
@ -161,6 +161,13 @@
"when": "view == 'sshfs-configs' && viewItem", "when": "view == 'sshfs-configs' && viewItem",
"group": "SSH FS@5" "group": "SSH FS@5"
} }
],
"explorer/context": [
{
"command": "sshfs.terminal",
"when": "explorerResourceIsFolder && resourceScheme == ssh",
"group": "navigation@30"
}
] ]
}, },
"configuration": { "configuration": {

@ -67,7 +67,20 @@ export function activate(context: vscode.ExtensionContext) {
registerCommand('sshfs.connect', (name?: string | FileSystemConfig) => pickAndClick(manager.commandConnect, name, false)); registerCommand('sshfs.connect', (name?: string | FileSystemConfig) => pickAndClick(manager.commandConnect, name, false));
registerCommand('sshfs.disconnect', (name?: string | FileSystemConfig) => pickAndClick(manager.commandDisconnect, name, true)); registerCommand('sshfs.disconnect', (name?: string | FileSystemConfig) => pickAndClick(manager.commandDisconnect, name, true));
registerCommand('sshfs.reconnect', (name?: string | FileSystemConfig) => pickAndClick(manager.commandReconnect, name, true)); registerCommand('sshfs.reconnect', (name?: string | FileSystemConfig) => pickAndClick(manager.commandReconnect, name, true));
registerCommand('sshfs.terminal', (name?: string | FileSystemConfig) => pickAndClick(manager.commandTerminal, name)); registerCommand('sshfs.terminal', async (configOrUri?: string | FileSystemConfig | vscode.Uri) => {
// SSH FS view context menu: [ config, null ]
// Explorer context menu: [ uri, [uri] ]
// Command: [ ]
// And just in case, supporting [ configName ] too
let config = configOrUri;
let uri: vscode.Uri | undefined;
if (config instanceof vscode.Uri) {
uri = config;
config = config.authority;
}
config = config || await pickConfig(manager);
if (config) manager.commandTerminal(config, uri);
});
registerCommand('sshfs.configure', (name?: string | FileSystemConfig) => pickAndClick(manager.commandConfigure, name)); registerCommand('sshfs.configure', (name?: string | FileSystemConfig) => pickAndClick(manager.commandConfigure, name));
registerCommand('sshfs.reload', loadConfigs); registerCommand('sshfs.reload', loadConfigs);

@ -7,6 +7,7 @@ import { Logging } from './logging';
import { createTaskTerminal, SSHPseudoTerminal } from './pseudoTerminal'; import { createTaskTerminal, SSHPseudoTerminal } from './pseudoTerminal';
import { catchingPromise, toPromise } from './toPromise'; import { catchingPromise, toPromise } from './toPromise';
import { Navigation } from './webviewMessages'; import { Navigation } from './webviewMessages';
import * as path from 'path';
type SSHFileSystem = import('./sshFileSystem').SSHFileSystem; type SSHFileSystem = import('./sshFileSystem').SSHFileSystem;
@ -234,14 +235,25 @@ export class Manager implements vscode.TreeDataProvider<string | FileSystemConfi
}); });
return this.creatingFileSystems[name] = promise; return this.creatingFileSystems[name] = promise;
} }
public async createTerminal(name: string, config?: FileSystemConfig): Promise<void> { public async createTerminal(name: string, config?: FileSystemConfig, uri?: vscode.Uri): Promise<void> {
const { createTerminal } = await import('./pseudoTerminal'); const { createTerminal } = await import('./pseudoTerminal');
// Create connection (early so we have .actualConfig.root)
const con = await this.createConnection(name, config); const con = await this.createConnection(name, config);
// Calculate working directory if applicable
let workingDirectory: string | undefined = uri ? uri.path : con.actualConfig.root || '/';
if (workingDirectory) {
// Normally there should be a fs, as (currently) workingDirectory is only provided
// when the user uses "Open remote SSH terminal" on a directory in the explorer view
const fs = this.fileSystems.find(fs => fs.config.name === name);
workingDirectory = fs ? fs.relative(workingDirectory) : undefined;
}
// Create pseudo terminal
con.pendingUserCount++; con.pendingUserCount++;
const pty = await createTerminal(con.client, con.actualConfig); const pty = await createTerminal(con.client, 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--;
// Create and show the graphical representation
const terminal = vscode.window.createTerminal({ name, pty }); const terminal = vscode.window.createTerminal({ name, pty });
terminal.show(); terminal.show();
} }
@ -355,11 +367,11 @@ export class Manager implements vscode.TreeDataProvider<string | FileSystemConfi
vscode.workspace.updateWorkspaceFolders(folders ? folders.length : 0, 0, { uri: vscode.Uri.parse(`ssh://${target}/`), name: `SSH FS - ${target}` }); vscode.workspace.updateWorkspaceFolders(folders ? folders.length : 0, 0, { uri: vscode.Uri.parse(`ssh://${target}/`), name: `SSH FS - ${target}` });
this.onDidChangeTreeDataEmitter.fire(null); this.onDidChangeTreeDataEmitter.fire(null);
} }
public async commandTerminal(target: string | FileSystemConfig) { public async commandTerminal(target: string | FileSystemConfig, uri?: vscode.Uri) {
if (typeof target === 'string') { if (typeof target === 'string') {
await this.createTerminal(target); await this.createTerminal(target, undefined, uri);
} else { } else {
await this.createTerminal(target.label || target.name, target); await this.createTerminal(target.label || target.name, target, uri);
} }
} }
public async commandConfigure(target: string | FileSystemConfig) { public async commandConfigure(target: string | FileSystemConfig) {

@ -17,7 +17,7 @@ export interface SSHPseudoTerminal extends vscode.Pseudoterminal {
channel?: ClientChannel; channel?: ClientChannel;
} }
export async function createTerminal(client: Client, config: FileSystemConfig): Promise<SSHPseudoTerminal> { 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)); const channel = await toPromise<ClientChannel | undefined>(cb => client.shell(PSEUDO_TTY_OPTIONS, cb));
if (!channel) throw new Error('Could not create remote terminal'); if (!channel) throw new Error('Could not create remote terminal');
const onDidWrite = new vscode.EventEmitter<string>(); const onDidWrite = new vscode.EventEmitter<string>();
@ -28,6 +28,8 @@ export async function createTerminal(client: Client, config: FileSystemConfig):
channel.on('exit', onDidClose.fire); channel.on('exit', onDidClose.fire);
// Hopefully the exit event fires first // Hopefully the exit event fires first
channel.on('close', () => onDidClose.fire(0)); 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) channel.write(`cd "${workingDirectory}"\n`);
const pseudo: SSHPseudoTerminal = { const pseudo: SSHPseudoTerminal = {
config, client, channel, config, client, channel,
onDidWrite: onDidWrite.event, onDidWrite: onDidWrite.event,

Loading…
Cancel
Save