|
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