UNPKG

javascript-typescript-langserver

Version:

Implementation of the Language Server Protocol for JavaScript and TypeScript

188 lines 6.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const glob_1 = require("glob"); const fs = require("mz/fs"); const opentracing_1 = require("opentracing"); const rxjs_1 = require("rxjs"); const semaphore_async_await_1 = require("semaphore-async-await"); const tracing_1 = require("./tracing"); const util_1 = require("./util"); class RemoteFileSystem { constructor(client) { this.client = client; } /** * The files request is sent from the server to the client to request a list of all files in the workspace or inside the directory of the base parameter, if given. * A language server can use the result to index files by filtering and doing a content request for each text document of interest. */ getWorkspaceFiles(base, childOf = new opentracing_1.Span()) { return this.client .workspaceXfiles({ base }, childOf) .mergeMap(textDocuments => textDocuments) .map(textDocument => util_1.normalizeUri(textDocument.uri)); } /** * The content request is sent from the server to the client to request the current content of * any text document. This allows language servers to operate without accessing the file system * directly. */ getTextDocumentContent(uri, childOf = new opentracing_1.Span()) { return this.client .textDocumentXcontent({ textDocument: { uri } }, childOf) .map(textDocument => textDocument.text); } } exports.RemoteFileSystem = RemoteFileSystem; class LocalFileSystem { /** * @param rootUri The root URI that is used if `base` is not specified */ constructor(rootUri) { this.rootUri = rootUri; } /** * Converts the URI to an absolute path on the local disk */ resolveUriToPath(uri) { return util_1.uri2path(uri); } getWorkspaceFiles(base = this.rootUri) { if (!base.endsWith('/')) { base += '/'; } const cwd = this.resolveUriToPath(base); return new rxjs_1.Observable(subscriber => { const globber = new glob_1.Glob('*', { cwd, nodir: true, matchBase: true, follow: true, }); globber.on('match', (file) => { subscriber.next(file); }); globber.on('error', (err) => { subscriber.error(err); }); globber.on('end', () => { subscriber.complete(); }); return () => { globber.abort(); }; }).map(file => { const encodedPath = file .split('/') .map(encodeURIComponent) .join('/'); return util_1.normalizeUri(base + encodedPath); }); } getTextDocumentContent(uri) { const filePath = this.resolveUriToPath(uri); return rxjs_1.Observable.fromPromise(fs.readFile(filePath, 'utf8')); } } exports.LocalFileSystem = LocalFileSystem; /** * Synchronizes a remote file system to an in-memory file system * * TODO: Implement Disposable with Disposer */ class FileSystemUpdater { constructor(remoteFs, inMemoryFs) { this.remoteFs = remoteFs; this.inMemoryFs = inMemoryFs; /** * Map from URI to Observable of pending or completed content fetch */ this.fetches = new Map(); /** * Limits concurrent fetches to not fetch thousands of files in parallel */ this.concurrencyLimit = new semaphore_async_await_1.default(100); } /** * Fetches the file content for the given URI and adds the content to the in-memory file system * * @param uri URI of the file to fetch * @param childOf A parent span for tracing * @return Observable that completes when the fetch is finished */ fetch(uri, childOf = new opentracing_1.Span()) { // Limit concurrent fetches const observable = rxjs_1.Observable.fromPromise(this.concurrencyLimit.wait()) .mergeMap(() => this.remoteFs.getTextDocumentContent(uri)) .do(content => { this.concurrencyLimit.signal(); this.inMemoryFs.add(uri, content); }, err => { this.fetches.delete(uri); }) .ignoreElements() .publishReplay() .refCount(); this.fetches.set(uri, observable); return observable; } /** * Returns a promise that is resolved when the given URI has been fetched (at least once) to the in-memory file system. * This function cannot be cancelled because multiple callers get the result of the same operation. * * @param uri URI of the file to ensure * @param childOf An OpenTracing span for tracing * @return Observable that completes when the file was fetched */ ensure(uri, childOf = new opentracing_1.Span()) { return tracing_1.traceObservable('Ensure content', childOf, span => { span.addTags({ uri }); return this.fetches.get(uri) || this.fetch(uri, span); }); } /** * Fetches the file/directory structure for the given directory from the remote file system and saves it in the in-memory file system * * @param childOf A parent span for tracing */ fetchStructure(childOf = new opentracing_1.Span()) { const observable = tracing_1.traceObservable('Fetch workspace structure', childOf, span => this.remoteFs .getWorkspaceFiles(undefined, span) .do(uri => { this.inMemoryFs.add(uri); }, err => { this.structureFetch = undefined; }) .ignoreElements() .publishReplay() .refCount()); this.structureFetch = observable; return observable; } /** * Returns a promise that is resolved as soon as the file/directory structure for the given directory has been synced * from the remote file system to the in-memory file system (at least once) * * @param span An OpenTracing span for tracing */ ensureStructure(childOf = new opentracing_1.Span()) { return tracing_1.traceObservable('Ensure structure', childOf, span => this.structureFetch || this.fetchStructure(span)); } /** * Invalidates the content fetch cache of a file. * The next call to `ensure` will do a refetch. * * @param uri URI of the file that changed */ invalidate(uri) { this.fetches.delete(uri); } /** * Invalidates the structure fetch cache. * The next call to `ensureStructure` will do a refetch. */ invalidateStructure() { this.structureFetch = undefined; } } exports.FileSystemUpdater = FileSystemUpdater; //# sourceMappingURL=fs.js.map