'use strict'; import * as paths from 'path'; import { Disposable, Event, EventEmitter, FileChangeEvent, FileStat, FileSystemError, FileSystemProvider, FileType, Uri, workspace } from 'vscode'; import { DocumentSchemes } from '../constants'; import { Container } from '../container'; import { GitService, GitTree, GitUri } from '../git/gitService'; import { Iterables, Strings, TernarySearchTree } from '../system'; export function fromGitLensFSUri(uri: Uri): { path: string; ref: string; repoPath: string } { const gitUri = uri instanceof GitUri ? uri : GitUri.fromRevisionUri(uri); return { path: gitUri.getRelativePath(), ref: gitUri.sha!, repoPath: gitUri.repoPath! }; } export function toGitLensFSUri(ref: string, repoPath: string): Uri { return GitUri.toRevisionUri(ref, repoPath, repoPath); } const emptyArray = new Uint8Array(0); export class GitFileSystemProvider implements FileSystemProvider, Disposable { private readonly _disposable: Disposable; private readonly _searchTreeMap = new Map<string, Promise<TernarySearchTree<GitTree>>>(); constructor() { this._disposable = Disposable.from( workspace.registerFileSystemProvider(DocumentSchemes.GitLens, this, { isCaseSensitive: true, isReadonly: true }) ); } dispose() { this._disposable && this._disposable.dispose(); } private _onDidChangeFile = new EventEmitter<FileChangeEvent[]>(); get onDidChangeFile(): Event<FileChangeEvent[]> { return this._onDidChangeFile.event; } copy?(): void | Thenable<void> { throw FileSystemError.NoPermissions; } createDirectory(): void | Thenable<void> { throw FileSystemError.NoPermissions; } delete(): void | Thenable<void> { throw FileSystemError.NoPermissions; } async readDirectory(uri: Uri): Promise<[string, FileType][]> { const { path, ref, repoPath } = fromGitLensFSUri(uri); const tree = await this.getTree(path, ref, repoPath); if (tree === undefined) { debugger; throw FileSystemError.FileNotFound(uri); } const items = [ ...Iterables.map<GitTree, [string, FileType]>(tree, t => [ path != null && path.length !== 0 ? Strings.normalizePath(paths.relative(path, t.path)) : t.path, typeToFileType(t.type) ]) ]; return items; } async readFile(uri: Uri): Promise<Uint8Array> { const { path, ref, repoPath } = fromGitLensFSUri(uri); if (ref === GitService.deletedOrMissingSha) return emptyArray; const buffer = await Container.git.getVersionedFileBuffer(repoPath, path, ref); if (buffer === undefined) return emptyArray; return buffer; } rename(): void | Thenable<void> { throw FileSystemError.NoPermissions; } async stat(uri: Uri): Promise<FileStat> { const { path, ref, repoPath } = fromGitLensFSUri(uri); if (ref === GitService.deletedOrMissingSha) { return { type: FileType.File, size: 0, ctime: 0, mtime: 0 }; } let treeItem; const searchTree = this._searchTreeMap.get(ref); if (searchTree !== undefined) { // Add the fake root folder to the path treeItem = (await searchTree).get(`/~/${path}`); } else { treeItem = await Container.git.getTreeFileForRevision(repoPath, path, ref); } if (treeItem === undefined) { throw FileSystemError.FileNotFound(uri); } return { type: typeToFileType(treeItem.type), size: treeItem.size, ctime: 0, mtime: 0 }; } watch(): Disposable { return { dispose: () => {} }; } writeFile(): void | Thenable<void> { throw FileSystemError.NoPermissions; } private async createSearchTree(ref: string, repoPath: string) { const searchTree = TernarySearchTree.forPaths() as TernarySearchTree<GitTree>; const trees = await Container.git.getTreeForRevision(repoPath, ref); // Add a fake root folder so that searches will work searchTree.set(`~`, { commitSha: '', path: '~', size: 0, type: 'tree' }); for (const item of trees) { searchTree.set(`~/${item.path}`, item); } return searchTree; } private async getOrCreateSearchTree(ref: string, repoPath: string) { let searchTree = this._searchTreeMap.get(ref); if (searchTree === undefined) { searchTree = this.createSearchTree(ref, repoPath); this._searchTreeMap.set(ref, searchTree); } return searchTree; } private async getTree(path: string, ref: string, repoPath: string) { const searchTree = await this.getOrCreateSearchTree(ref, repoPath); // Add the fake root folder to the path return searchTree!.findSuperstr(`/~/${path}`, true); } } function typeToFileType(type: 'blob' | 'tree' | undefined | null) { switch (type) { case 'blob': return FileType.File; case 'tree': return FileType.Directory; default: return FileType.Unknown; } }