Skip to content

Commit

Permalink
Add "Open remote SSH terminal" in explorer for ssh:// directories + m…
Browse files Browse the repository at this point in the history
…ake root-sensitive
  • Loading branch information
SchoofsKelvin committed Jul 8, 2020
1 parent bbba8b7 commit ffef5c6
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 10 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,16 @@ The configurations for SSH file systems can now also be used to spawn terminals:

![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
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}`, ...

Expand Down
Binary file added media/explorer-terminal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
},
{
"command": "sshfs.settings",
"when": "view == 'sshfs-configs' && !(viewItem)",
"when": "view == 'sshfs-configs'",
"group": "SSH FS@6"
}
],
Expand Down Expand Up @@ -161,6 +161,13 @@
"when": "view == 'sshfs-configs' && viewItem",
"group": "SSH FS@5"
}
],
"explorer/context": [
{
"command": "sshfs.terminal",
"when": "explorerResourceIsFolder && resourceScheme == ssh",
"group": "navigation@30"
}
]
},
"configuration": {
Expand Down
15 changes: 14 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,20 @@ export function activate(context: vscode.ExtensionContext) {
registerCommand('sshfs.connect', (name?: string | FileSystemConfig) => pickAndClick(manager.commandConnect, name, false));
registerCommand('sshfs.disconnect', (name?: string | FileSystemConfig) => pickAndClick(manager.commandDisconnect, 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.reload', loadConfigs);
Expand Down
22 changes: 17 additions & 5 deletions src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Logging } from './logging';
import { createTaskTerminal, SSHPseudoTerminal } from './pseudoTerminal';
import { catchingPromise, toPromise } from './toPromise';
import { Navigation } from './webviewMessages';
import * as path from 'path';

type SSHFileSystem = import('./sshFileSystem').SSHFileSystem;

Expand Down Expand Up @@ -234,14 +235,25 @@ export class Manager implements vscode.TreeDataProvider<string | FileSystemConfi
});
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');
// Create connection (early so we have .actualConfig.root)
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++;
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));
con.terminals.push(pty);
con.pendingUserCount--;
// Create and show the graphical representation
const terminal = vscode.window.createTerminal({ name, pty });
terminal.show();
}
Expand Down Expand Up @@ -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}` });
this.onDidChangeTreeDataEmitter.fire(null);
}
public async commandTerminal(target: string | FileSystemConfig) {
public async commandTerminal(target: string | FileSystemConfig, uri?: vscode.Uri) {
if (typeof target === 'string') {
await this.createTerminal(target);
await this.createTerminal(target, undefined, uri);
} else {
await this.createTerminal(target.label || target.name, target);
await this.createTerminal(target.label || target.name, target, uri);
}
}
public async commandConfigure(target: string | FileSystemConfig) {
Expand Down
4 changes: 3 additions & 1 deletion src/pseudoTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface SSHPseudoTerminal extends vscode.Pseudoterminal {
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));
if (!channel) throw new Error('Could not create remote terminal');
const onDidWrite = new vscode.EventEmitter<string>();
Expand All @@ -28,6 +28,8 @@ export async function createTerminal(client: Client, config: FileSystemConfig):
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) channel.write(`cd "${workingDirectory}"\n`);
const pseudo: SSHPseudoTerminal = {
config, client, channel,
onDidWrite: onDidWrite.event,
Expand Down

0 comments on commit ffef5c6

Please sign in to comment.