From 119f2bdc1e82f9a116113528ac9daa71fda1ebe8 Mon Sep 17 00:00:00 2001 From: Kelvin Schoofs Date: Sat, 13 Apr 2019 17:36:58 +0200 Subject: [PATCH] Initial SearchProvider (very basic FileSearchProvider) --- src/extension.ts | 2 ++ src/searchProvider.ts | 57 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/searchProvider.ts diff --git a/src/extension.ts b/src/extension.ts index eb2a63a..013b94e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,6 +4,7 @@ import { loadConfigs } from './config'; import { FileSystemConfig, invalidConfigName } from './fileSystemConfig'; import * as Logging from './logging'; import { Manager } from './manager'; +import { SearchProvider } from './searchProvider'; function generateDetail(config: FileSystemConfig): string | undefined { const { username, host, putty } = config; @@ -52,6 +53,7 @@ export function activate(context: vscode.ExtensionContext) { subscribe(vscode.commands.registerCommand(command, callback, thisArg)); subscribe(vscode.workspace.registerFileSystemProvider('ssh', manager, { isCaseSensitive: true })); + subscribe(vscode.workspace.registerFileSearchProvider('ssh', new SearchProvider(manager))); async function pickAndClick(func: (name: string) => void, name?: string, activeOrNot?: boolean) { name = name || await pickConfig(manager, activeOrNot); diff --git a/src/searchProvider.ts b/src/searchProvider.ts new file mode 100644 index 0000000..9f2049d --- /dev/null +++ b/src/searchProvider.ts @@ -0,0 +1,57 @@ + +import { posix as path } from 'path'; +import { createInterface } from 'readline'; +import { ClientChannel } from 'ssh2'; +import * as vscode from 'vscode'; +import { Manager } from './manager'; +import { toPromise } from './toPromise'; + +export class SearchProvider implements vscode.FileSearchProvider { + protected cache: [vscode.CancellationToken, Promise][] = []; + constructor(protected manager: Manager) { } + public async provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { + const { folder, session = token } = options; + let cached = this.cache.find(([t]) => session === t); + if (!cached) { + cached = [session, this.buildTree(options, session)] as SearchProvider['cache'][0]; + this.cache.push(cached); + session.onCancellationRequested(() => { + this.cache.splice(this.cache.indexOf(cached!)); + }); + } + const paths = await cached[1]; + console.log('Found', paths.length); + if (token.isCancellationRequested) return []; + const pattern = query.pattern.toLowerCase(); + return paths.map((relative) => { + if (!relative.toLowerCase().includes(pattern)) return null; + return folder.with({ path: path.join(folder.path, relative) }); + }).filter(s => !!s) as vscode.Uri[]; + } + protected async buildTree(options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { + const { folder } = options; + const fs = await this.manager.getFs(folder); + if (!fs || token.isCancellationRequested) return []; + const cmd = `ls -AQ1R "${fs.absoluteFromRelative(folder.path)}"`; + console.log('Creating tree with command:', cmd); + const exec = await toPromise(cb => fs.client.exec(cmd, cb)).catch(() => null); + if (!exec || token.isCancellationRequested) return []; + const res: string[] = []; + const rl = createInterface(exec); + let root = ''; + rl.on('line', (line: string) => { + if (!line) return; + if (line.endsWith(':')) { + root = JSON.parse(line.substr(0, line.length - 1)); + } else { + let relative = JSON.parse(line); + relative = path.join(root, relative); + relative = fs.relativeFromAbsolute(relative); + res.push(relative); + } + }); + token.onCancellationRequested(rl.close, rl); + await toPromise(cb => rl.on('close', cb)); + return res; + } +}