|
5 | 5 |
|
6 | 6 | import * as vscode from 'vscode'; |
7 | 7 | import { BaseLanguageClient, LanguageClientOptions, NotebookDocumentSyncRegistrationType } from 'vscode-languageclient'; |
8 | | -import { disposeAll, IDisposable } from 'vscode-markdown-languageservice/out/util/dispose'; |
9 | | -import { ResourceMap } from 'vscode-markdown-languageservice/out/util/resourceMap'; |
10 | 8 | import * as nls from 'vscode-nls'; |
11 | | -import { Utils } from 'vscode-uri'; |
12 | | -import { IMdParser } from './markdownEngine'; |
| 9 | +import { IMdParser } from '../markdownEngine'; |
13 | 10 | import * as proto from './protocol'; |
14 | | -import { looksLikeMarkdownPath, markdownFileExtensions } from './util/file'; |
15 | | -import { Schemes } from './util/schemes'; |
16 | | -import { IMdWorkspace } from './workspace'; |
| 11 | +import { looksLikeMarkdownPath, markdownFileExtensions } from '../util/file'; |
| 12 | +import { VsCodeMdWorkspace } from './workspace'; |
| 13 | +import { FileWatcherManager } from './fileWatchingManager'; |
| 14 | +import { IDisposable } from '../util/dispose'; |
17 | 15 |
|
18 | 16 | const localize = nls.loadMessageBundle(); |
19 | 17 |
|
20 | 18 | export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; |
21 | 19 |
|
| 20 | +export class MdLanguageClient implements IDisposable { |
22 | 21 |
|
23 | | -export async function startClient(factory: LanguageClientConstructor, workspace: IMdWorkspace, parser: IMdParser): Promise<BaseLanguageClient> { |
| 22 | + constructor( |
| 23 | + private readonly _client: BaseLanguageClient, |
| 24 | + private readonly _workspace: VsCodeMdWorkspace, |
| 25 | + ) { } |
| 26 | + |
| 27 | + dispose(): void { |
| 28 | + this._client.stop(); |
| 29 | + this._workspace.dispose(); |
| 30 | + } |
| 31 | + |
| 32 | + resolveLinkTarget(linkText: string, uri: vscode.Uri): Promise<proto.ResolvedDocumentLinkTarget> { |
| 33 | + return this._client.sendRequest(proto.resolveLinkTarget, { linkText, uri: uri.toString() }); |
| 34 | + } |
| 35 | + |
| 36 | + getEditForFileRenames(files: ReadonlyArray<{ oldUri: string; newUri: string }>, token: vscode.CancellationToken) { |
| 37 | + return this._client.sendRequest(proto.getEditForFileRenames, files, token); |
| 38 | + } |
| 39 | + |
| 40 | + getReferencesToFileInWorkspace(resource: vscode.Uri, token: vscode.CancellationToken) { |
| 41 | + return this._client.sendRequest(proto.getReferencesToFileInWorkspace, { uri: resource.toString() }, token); |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +export async function startClient(factory: LanguageClientConstructor, parser: IMdParser): Promise<MdLanguageClient> { |
24 | 46 |
|
25 | 47 | const mdFileGlob = `**/*.{${markdownFileExtensions.join(',')}}`; |
26 | 48 |
|
@@ -59,6 +81,8 @@ export async function startClient(factory: LanguageClientConstructor, workspace: |
59 | 81 | }); |
60 | 82 | } |
61 | 83 |
|
| 84 | + const workspace = new VsCodeMdWorkspace(); |
| 85 | + |
62 | 86 | client.onRequest(proto.parse, async (e) => { |
63 | 87 | const uri = vscode.Uri.parse(e.uri); |
64 | 88 | const doc = await workspace.getOrLoadMarkdownDocument(uri); |
@@ -125,93 +149,5 @@ export async function startClient(factory: LanguageClientConstructor, workspace: |
125 | 149 |
|
126 | 150 | await client.start(); |
127 | 151 |
|
128 | | - return client; |
129 | | -} |
130 | | - |
131 | | -type DirWatcherEntry = { |
132 | | - readonly uri: vscode.Uri; |
133 | | - readonly listeners: IDisposable[]; |
134 | | -}; |
135 | | - |
136 | | -class FileWatcherManager { |
137 | | - |
138 | | - private readonly fileWatchers = new Map<number, { |
139 | | - readonly watcher: vscode.FileSystemWatcher; |
140 | | - readonly dirWatchers: DirWatcherEntry[]; |
141 | | - }>(); |
142 | | - |
143 | | - private readonly dirWatchers = new ResourceMap<{ |
144 | | - readonly watcher: vscode.FileSystemWatcher; |
145 | | - refCount: number; |
146 | | - }>(); |
147 | | - |
148 | | - create(id: number, uri: vscode.Uri, watchParentDirs: boolean, listeners: { create?: () => void; change?: () => void; delete?: () => void }): void { |
149 | | - const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'), !listeners.create, !listeners.change, !listeners.delete); |
150 | | - const parentDirWatchers: DirWatcherEntry[] = []; |
151 | | - this.fileWatchers.set(id, { watcher, dirWatchers: parentDirWatchers }); |
152 | | - |
153 | | - if (listeners.create) { watcher.onDidCreate(listeners.create); } |
154 | | - if (listeners.change) { watcher.onDidChange(listeners.change); } |
155 | | - if (listeners.delete) { watcher.onDidDelete(listeners.delete); } |
156 | | - |
157 | | - if (watchParentDirs && uri.scheme !== Schemes.untitled) { |
158 | | - // We need to watch the parent directories too for when these are deleted / created |
159 | | - for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) { |
160 | | - const dirWatcher: DirWatcherEntry = { uri: dirUri, listeners: [] }; |
161 | | - |
162 | | - let parentDirWatcher = this.dirWatchers.get(dirUri); |
163 | | - if (!parentDirWatcher) { |
164 | | - const glob = new vscode.RelativePattern(Utils.dirname(dirUri), Utils.basename(dirUri)); |
165 | | - const parentWatcher = vscode.workspace.createFileSystemWatcher(glob, !listeners.create, true, !listeners.delete); |
166 | | - parentDirWatcher = { refCount: 0, watcher: parentWatcher }; |
167 | | - this.dirWatchers.set(dirUri, parentDirWatcher); |
168 | | - } |
169 | | - parentDirWatcher.refCount++; |
170 | | - |
171 | | - if (listeners.create) { |
172 | | - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidCreate(async () => { |
173 | | - // Just because the parent dir was created doesn't mean our file was created |
174 | | - try { |
175 | | - const stat = await vscode.workspace.fs.stat(uri); |
176 | | - if (stat.type === vscode.FileType.File) { |
177 | | - listeners.create!(); |
178 | | - } |
179 | | - } catch { |
180 | | - // Noop |
181 | | - } |
182 | | - })); |
183 | | - } |
184 | | - |
185 | | - if (listeners.delete) { |
186 | | - // When the parent dir is deleted, consider our file deleted too |
187 | | - |
188 | | - // TODO: this fires if the file previously did not exist and then the parent is deleted |
189 | | - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); |
190 | | - } |
191 | | - |
192 | | - parentDirWatchers.push(dirWatcher); |
193 | | - } |
194 | | - } |
195 | | - } |
196 | | - |
197 | | - delete(id: number): void { |
198 | | - const entry = this.fileWatchers.get(id); |
199 | | - if (entry) { |
200 | | - for (const dirWatcher of entry.dirWatchers) { |
201 | | - disposeAll(dirWatcher.listeners); |
202 | | - |
203 | | - const dirWatcherEntry = this.dirWatchers.get(dirWatcher.uri); |
204 | | - if (dirWatcherEntry) { |
205 | | - if (--dirWatcherEntry.refCount <= 0) { |
206 | | - dirWatcherEntry.watcher.dispose(); |
207 | | - this.dirWatchers.delete(dirWatcher.uri); |
208 | | - } |
209 | | - } |
210 | | - } |
211 | | - |
212 | | - entry.watcher.dispose(); |
213 | | - } |
214 | | - |
215 | | - this.fileWatchers.delete(id); |
216 | | - } |
| 152 | + return new MdLanguageClient(client, workspace); |
217 | 153 | } |
0 commit comments