From 1ca3f1d73e960fa76277ff4c1914ac0383d4a784 Mon Sep 17 00:00:00 2001 From: Nikhil Benesch Date: Tue, 22 Jan 2019 14:48:30 -0500 Subject: [PATCH] Implement file index provider This powers VSCode's built-in fuzzy file finder. --- package.json | 1 + src/connect.ts | 2 +- src/extension.ts | 1 + src/manager.ts | 7 +++++-- src/sshFileSystem.ts | 48 +++++++++++++++++++++++++++++++++++++++----- 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 9a1d7b6..06de125 100644 --- a/package.json +++ b/package.json @@ -430,6 +430,7 @@ "vscode": "^1.1.21" }, "dependencies": { + "binary-split": "^1.0.5", "jsonc-parser": "^2.0.0", "socks": "^2.2.0", "ssh2": "^0.6.0", diff --git a/src/connect.ts b/src/connect.ts index 1d52367..ba2d756 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -31,7 +31,7 @@ export async function calculateActualConfig(config: FileSystemConfig): Promise void, name?: string, activeOrNot?: boolean) { name = name || await pickConfig(manager, activeOrNot); diff --git a/src/manager.ts b/src/manager.ts index bdae47e..6d7e1f1 100644 --- a/src/manager.ts +++ b/src/manager.ts @@ -117,7 +117,7 @@ async function tryGetHome(ssh: Client): Promise { return mat[1]; } -export class Manager implements vscode.FileSystemProvider, vscode.TreeDataProvider { +export class Manager implements vscode.FileSystemProvider, vscode.FileIndexProvider, vscode.TreeDataProvider { public onDidChangeTreeData: vscode.Event; public onDidChangeFile: vscode.Event; protected fileSystems: SSHFileSystem[] = []; @@ -194,7 +194,7 @@ export class Manager implements vscode.FileSystemProvider, vscode.TreeDataProvid root = root.replace(/^~/, home.replace(/\/$/, '')); } const sftp = await getSFTP(client, config); - const fs = new SSHFileSystem(name, sftp, root, config!); + const fs = new SSHFileSystem(name, client, sftp, root, config!); try { const rootUri = vscode.Uri.parse(`ssh://${name}/`); const stat = await fs.stat(rootUri); @@ -289,6 +289,9 @@ export class Manager implements vscode.FileSystemProvider, vscode.TreeDataProvid if (fs !== (await assertFs(this, newUri))) throw new Error(`Can't copy between different SSH filesystems`); return fs.rename(oldUri, newUri, options); } + public async provideFileIndex(options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { + return (await assertFs(this, options.folder)).provideFileIndex(options, token); + } /* TreeDataProvider */ public getTreeItem(element: string): vscode.TreeItem | Thenable { return createTreeItem(this, element); diff --git a/src/sshFileSystem.ts b/src/sshFileSystem.ts index 7519861..10687ca 100644 --- a/src/sshFileSystem.ts +++ b/src/sshFileSystem.ts @@ -3,9 +3,10 @@ import * as path from 'path'; import * as ssh2 from 'ssh2'; import * as ssh2s from 'ssh2-streams'; import * as vscode from 'vscode'; +import * as split from 'binary-split'; import { FileSystemConfig } from './manager'; -export class SSHFileSystem implements vscode.FileSystemProvider { +export class SSHFileSystem implements vscode.FileSystemProvider, vscode.FileIndexProvider { public waitForContinue = false; public closed = false; public closing = false; @@ -13,15 +14,19 @@ export class SSHFileSystem implements vscode.FileSystemProvider { public onDidChangeFile: vscode.Event; protected onDidChangeFileEmitter = new vscode.EventEmitter(); - constructor(public readonly authority: string, protected sftp: ssh2.SFTPWrapper, - public readonly root: string, public readonly config: FileSystemConfig) { + constructor( + public readonly authority: string, + protected ssh: ssh2.Client, + protected sftp: ssh2.SFTPWrapper, + public readonly root: string, + public readonly config: FileSystemConfig) { this.onDidChangeFile = this.onDidChangeFileEmitter.event; - this.sftp.on('end', () => this.closed = true); + this.ssh.on('end', () => this.closed = true); } public disconnect() { this.closing = true; - this.sftp.end(); + this.ssh.end(); } public relative(relPath: string) { @@ -138,6 +143,39 @@ export class SSHFileSystem implements vscode.FileSystemProvider { public rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Promise { return this.continuePromise(cb => this.sftp.rename(this.relative(oldUri.path), this.relative(newUri.path), cb)); } + + public async provideFileIndex(options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { + const name = this.config.label || this.config.name; + const command = `find ${this.relative(options.folder.path)} -print0`; + return new Promise((resolve, reject) => { + const stream = this.ssh.exec(command, (err, stream) => { + if (err) return reject(err); + stream.stderr.on('data', (d) => { + console.log("Stderr from directory listing:", d.toString()) + }) + const uris: vscode.Uri[] = []; + stream.pipe(split("\x00")).on('data', (d) => { + const p = path.posix.relative(this.root, d.toString()); + uris.push(vscode.Uri.parse(`ssh://${name}/${p}`)) + }); + stream.on('error', reject); + stream.on('close', (exitCode, signal) => { + if (exitCode || signal) { + return reject(new Error("error listing directory")); + } + resolve(uris); + }); + }); + }); + } + + public async provideTextSearchResults( + query: vscode.TextSearchQuery, + options: vscode.TextSearchOptions, + progress: vscode.Progress, token: vscode.CancellationToken + ): Promise { + + } } export default SSHFileSystem;