This repository was archived by the owner on Nov 26, 2019. It is now read-only.
forked from sourcegraph/javascript-typescript-langserver
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfs.ts
More file actions
200 lines (184 loc) · 6.57 KB
/
fs.ts
File metadata and controls
200 lines (184 loc) · 6.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import { Glob } from 'glob'
import * as fs from 'mz/fs'
import { Span } from 'opentracing'
import { Observable } from 'rxjs'
import Semaphore from 'semaphore-async-await'
import { InMemoryFileSystem } from './memfs'
import { traceObservable } from './tracing'
import { normalizeUri, uri2path } from './util'
export interface FileSystem {
/**
* Returns all files in the workspace under base
*
* @param base A URI under which to search, resolved relative to the rootUri
* @return An Observable that emits URIs
*/
getWorkspaceFiles(base?: string, childOf?: Span): Observable<string>
/**
* Returns the content of a text document
*
* @param uri The URI of the text document, resolved relative to the rootUri
* @return An Observable that emits the text document content
*/
getTextDocumentContent(uri: string, childOf?: Span): Observable<string>
}
export class LocalFileSystem implements FileSystem {
/**
* @param rootUri The root URI that is used if `base` is not specified
*/
constructor(private rootUri: string) {}
/**
* Converts the URI to an absolute path on the local disk
*/
protected resolveUriToPath(uri: string): string {
return uri2path(uri)
}
public getWorkspaceFiles(base = this.rootUri): Observable<string> {
if (!base.endsWith('/')) {
base += '/'
}
const cwd = this.resolveUriToPath(base)
return new Observable<string>(subscriber => {
const globber = new Glob('*', {
cwd,
nodir: true,
matchBase: true,
follow: true,
})
globber.on('match', (file: string) => {
subscriber.next(file)
})
globber.on('error', (err: any) => {
subscriber.error(err)
})
globber.on('end', () => {
subscriber.complete()
})
return () => {
globber.abort()
}
}).map(file => {
const encodedPath = file
.split('/')
.map(encodeURIComponent)
.join('/')
return normalizeUri(base + encodedPath)
})
}
public getTextDocumentContent(uri: string): Observable<string> {
const filePath = this.resolveUriToPath(uri)
return Observable.fromPromise(fs.readFile(filePath, 'utf8'))
}
}
/**
* Synchronizes a remote file system to an in-memory file system
*
* TODO: Implement Disposable with Disposer
*/
export class FileSystemUpdater {
/**
* Observable for a pending or completed structure fetch
*/
private structureFetch?: Observable<never>
/**
* Map from URI to Observable of pending or completed content fetch
*/
private fetches = new Map<string, Observable<never>>()
/**
* Limits concurrent fetches to not fetch thousands of files in parallel
*/
private concurrencyLimit = new Semaphore(100)
constructor(private remoteFs: FileSystem, private inMemoryFs: InMemoryFileSystem) {}
/**
* 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
*/
public fetch(uri: string, childOf = new Span()): Observable<never> {
// Limit concurrent fetches
const observable = 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() as Observable<never>
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
*/
public ensure(uri: string, childOf = new Span()): Observable<never> {
return 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
*/
public fetchStructure(childOf = new Span()): Observable<never> {
const observable = traceObservable(
'Fetch workspace structure',
childOf,
span =>
this.remoteFs
.getWorkspaceFiles(undefined, span)
.do(
uri => {
this.inMemoryFs.add(uri)
},
err => {
this.structureFetch = undefined
}
)
.ignoreElements()
.publishReplay()
.refCount() as Observable<never>
)
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
*/
public ensureStructure(childOf = new Span()): Observable<never> {
return 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
*/
public invalidate(uri: string): void {
this.fetches.delete(uri)
}
/**
* Invalidates the structure fetch cache.
* The next call to `ensureStructure` will do a refetch.
*/
public invalidateStructure(): void {
this.structureFetch = undefined
}
}