Skip to content

Commit

Permalink
File Explorer: Add rename (#163)
Browse files Browse the repository at this point in the history
Part of ENG-1932

Signed-off-by: Tyler Smalley <tyler@tailscale.com>
  • Loading branch information
tylersmalley committed Aug 3, 2023
1 parent 7cf6559 commit 389e384
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 47 deletions.
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@
"group": "3_dirActions@1",
"when": "view == node-explorer-view && viewItem =~ /^peer-file-explorer/"
},
{
"command": "tailscale.node.fs.rename",
"group": "4_fileAction@1",
"when": "view == node-explorer-view && viewItem =~ /^peer-file-explorer-child/"
},
{
"command": "tailscale.node.fs.delete",
"group": "4_fileAction@2",
Expand Down Expand Up @@ -277,6 +282,10 @@
"command": "tailscale.node.fs.delete",
"title": "Delete"
},
{
"command": "tailscale.node.fs.rename",
"title": "Rename"
},
{
"command": "tailscale.node.fs.createDirectory",
"title": "Create directory"
Expand Down
58 changes: 40 additions & 18 deletions src/filesystem-provider-sftp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,44 @@ export class FileSystemProviderSFTP implements vscode.FileSystemProvider {
}

readDirectory = withFileSystemErrorHandling(
'readDirectory',
async (uri: vscode.Uri): Promise<[string, vscode.FileType][]> => {
const { resourcePath, sftp } = await this.getParsedUriAndSftp(uri);
const files = await sftp.readDirectory(resourcePath);
return files.sort(fileSorter);
}
);

stat = withFileSystemErrorHandling(async (uri: vscode.Uri): Promise<vscode.FileStat> => {
stat = withFileSystemErrorHandling('stat', async (uri: vscode.Uri): Promise<vscode.FileStat> => {
const { resourcePath, sftp } = await this.getParsedUriAndSftp(uri);
return await sftp.stat(resourcePath);
});

createDirectory = withFileSystemErrorHandling(async (uri: vscode.Uri): Promise<void> => {
const { resourcePath, sftp } = await this.getParsedUriAndSftp(uri);
return await sftp.createDirectory(resourcePath);
});
createDirectory = withFileSystemErrorHandling(
'createDirectory',
async (uri: vscode.Uri): Promise<void> => {
const { resourcePath, sftp } = await this.getParsedUriAndSftp(uri);
return await sftp.createDirectory(resourcePath);
}
);

readFile = withFileSystemErrorHandling(async (uri: vscode.Uri): Promise<Uint8Array> => {
const { resourcePath, sftp } = await this.getParsedUriAndSftp(uri);
return await sftp.readFile(resourcePath);
});
readFile = withFileSystemErrorHandling(
'readFile',
async (uri: vscode.Uri): Promise<Uint8Array> => {
const { resourcePath, sftp } = await this.getParsedUriAndSftp(uri);
return await sftp.readFile(resourcePath);
}
);

writeFile = withFileSystemErrorHandling(async (uri: vscode.Uri, content: Uint8Array) => {
const { resourcePath, sftp } = await this.getParsedUriAndSftp(uri);
return await sftp.writeFile(resourcePath, content);
});
writeFile = withFileSystemErrorHandling(
'writeFile',
async (uri: vscode.Uri, content: Uint8Array) => {
const { resourcePath, sftp } = await this.getParsedUriAndSftp(uri);
return await sftp.writeFile(resourcePath, content);
}
);

delete = withFileSystemErrorHandling(async (uri: vscode.Uri): Promise<void> => {
delete = withFileSystemErrorHandling('delete', async (uri: vscode.Uri): Promise<void> => {
const { resourcePath, sftp } = await this.getParsedUriAndSftp(uri);

const deleteRecursively = async (path: string) => {
Expand All @@ -72,7 +82,12 @@ export class FileSystemProviderSFTP implements vscode.FileSystemProvider {
return await deleteRecursively(resourcePath);
});

async rename(): Promise<void> {}
rename = withFileSystemErrorHandling('rename', async (source: vscode.Uri, target: vscode.Uri) => {
const { resourcePath: sourcePath, sftp } = await this.getParsedUriAndSftp(source);
const { resourcePath: targetPath } = parseTsUri(target);

return await sftp.rename(sourcePath, targetPath);
});

async getHomeDirectory(address: string): Promise<string> {
const sftp = await this.manager.getSftp(address);
Expand All @@ -99,16 +114,20 @@ type FileSystemMethod<TArgs extends unknown[], TResult> = (
) => Promise<TResult>;

function withFileSystemErrorHandling<TArgs extends unknown[], TResult>(
actionName: string,
fn: FileSystemMethod<TArgs, TResult>
): FileSystemMethod<TArgs, TResult> {
return async (uri: vscode.Uri, ...args: TArgs): Promise<TResult> => {
Logger.info(`${fn.name}: ${uri}`, 'tsFs-sftp');
Logger.info(`${actionName}: ${uri}`, 'tsFs-sftp');

try {
return await fn(uri, ...args);
} catch (error) {
const message = getErrorMessage(error);
Logger.error(`${fn.name}: ${error}`, 'tsFs-sftp');

if (error instanceof vscode.FileSystemError) {
throw error;
}

if (message.includes('no such file or directory')) {
throw vscode.FileSystemError.FileNotFound();
Expand All @@ -119,7 +138,8 @@ function withFileSystemErrorHandling<TArgs extends unknown[], TResult>(
}

if (message.includes('file already exists')) {
throw vscode.FileSystemError.FileExists();
const message = `Unable to move/copy`;
throw vscode.FileSystemError.FileExists(message);
}

if (message.includes('EISDIR')) {
Expand All @@ -138,6 +158,8 @@ function withFileSystemErrorHandling<TArgs extends unknown[], TResult>(
throw vscode.FileSystemError.Unavailable();
}

Logger.error(`${actionName}: ${error}`, 'tsFs-sftp');

throw error;
}
};
Expand Down
75 changes: 46 additions & 29 deletions src/node-explorer-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { Peer, PeerGroup } from './types';
import { Utils } from 'vscode-uri';
import { Tailscale } from './tailscale/cli';
import { ConfigManager } from './config-manager';
import { Logger } from './logger';
Expand Down Expand Up @@ -43,6 +44,7 @@ export class NodeExplorerProvider implements vscode.TreeDataProvider<PeerBaseTre
this.registerCopyIPv6Command();
this.registerCreateDirectoryCommand();
this.registerDeleteCommand();
this.registerRenameCommand();
this.registerOpenNodeDetailsCommand();
this.registerOpenRemoteCodeCommand();
this.registerOpenRemoteCodeLocationCommand();
Expand Down Expand Up @@ -215,7 +217,50 @@ export class NodeExplorerProvider implements vscode.TreeDataProvider<PeerBaseTre
}

registerDeleteCommand() {
vscode.commands.registerCommand('tailscale.node.fs.delete', this.delete.bind(this));
vscode.commands.registerCommand('tailscale.node.fs.delete', async (file: FileExplorer) => {
try {
const msg = `Are you sure you want to delete ${
file.type === vscode.FileType.Directory ? 'this directory' : 'this file'
}? This action cannot be undone.`;

const answer = await vscode.window.showInformationMessage(msg, { modal: true }, 'Yes');

if (answer !== 'Yes') {
return;
}

await vscode.workspace.fs.delete(file.uri);
vscode.window.showInformationMessage(`${file.label} deleted successfully.`);

this._onDidChangeTreeData.fire([undefined]);
} catch (e) {
vscode.window.showErrorMessage(`Could not delete ${file.label}: ${e}`);
}
});
}

registerRenameCommand() {
vscode.commands.registerCommand('tailscale.node.fs.rename', async (node: FileExplorer) => {
const source = node.uri;

const newName = await vscode.window.showInputBox({
prompt: 'Enter a new name for the file',
value: path.basename(source.path),
});

if (!newName) {
return;
}

try {
const target = Utils.joinPath(source, '..', newName);
await vscode.workspace.fs.rename(source, target);

this._onDidChangeTreeData.fire([undefined]);
} catch (e) {
vscode.window.showErrorMessage(`Could not rename: ${e}`);
}
});
}

registerCreateDirectoryCommand() {
Expand Down Expand Up @@ -343,34 +388,6 @@ export class NodeExplorerProvider implements vscode.TreeDataProvider<PeerBaseTre
{ forceNewWindow: !reuseWindow }
);
}

async delete(file: FileExplorer) {
try {
const msg = `Are you sure you want to delete ${
file.type === vscode.FileType.Directory ? 'this directory' : 'this file'
}? This action cannot be undone.`;
const answer = await vscode.window.showInformationMessage(msg, { modal: true }, 'Yes');
if (answer !== 'Yes') {
return;
}
await vscode.workspace.fs.delete(file.uri);
vscode.window.showInformationMessage(`${file.label} deleted successfully.`);

const normalizedPath = path.normalize(file.uri.toString());
const parentDir = path.dirname(normalizedPath);
const dirName = path.basename(parentDir);

const parentFileExplorerItem = new FileExplorer(
dirName,
vscode.Uri.parse(parentDir),
vscode.FileType.Directory
);

this._onDidChangeTreeData.fire([parentFileExplorerItem]);
} catch (e) {
vscode.window.showErrorMessage(`Could not delete ${file.label}: ${e}`);
}
}
}

export class PeerBaseTreeItem extends vscode.TreeItem {
Expand Down

0 comments on commit 389e384

Please sign in to comment.