Skip to content

Commit d03f015

Browse files
authored
Clean up structure of markdown extension (microsoft#161148)
- Move things related to the client under `client` - Remove extra abstractions that are no longer used - Add MdLanguageClient type
1 parent f4bf1f3 commit d03f015

File tree

17 files changed

+183
-226
lines changed

17 files changed

+183
-226
lines changed

extensions/markdown-language-features/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,6 @@
604604
"morphdom": "^2.6.1",
605605
"picomatch": "^2.3.1",
606606
"vscode-languageclient": "^8.0.2",
607-
"vscode-languageserver-textdocument": "^1.0.4",
608607
"vscode-nls": "^5.1.0",
609608
"vscode-uri": "^3.0.3"
610609
},

extensions/markdown-language-features/src/client.ts renamed to extensions/markdown-language-features/src/client/client.ts

Lines changed: 33 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,44 @@
55

66
import * as vscode from 'vscode';
77
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';
108
import * as nls from 'vscode-nls';
11-
import { Utils } from 'vscode-uri';
12-
import { IMdParser } from './markdownEngine';
9+
import { IMdParser } from '../markdownEngine';
1310
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';
1715

1816
const localize = nls.loadMessageBundle();
1917

2018
export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient;
2119

20+
export class MdLanguageClient implements IDisposable {
2221

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> {
2446

2547
const mdFileGlob = `**/*.{${markdownFileExtensions.join(',')}}`;
2648

@@ -59,6 +81,8 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
5981
});
6082
}
6183

84+
const workspace = new VsCodeMdWorkspace();
85+
6286
client.onRequest(proto.parse, async (e) => {
6387
const uri = vscode.Uri.parse(e.uri);
6488
const doc = await workspace.getOrLoadMarkdownDocument(uri);
@@ -125,93 +149,5 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
125149

126150
await client.start();
127151

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);
217153
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import { disposeAll } from 'vscode-markdown-languageservice/out/util/dispose';
8+
import { ResourceMap } from 'vscode-markdown-languageservice/out/util/resourceMap';
9+
import { Utils } from 'vscode-uri';
10+
import { IDisposable } from '../util/dispose';
11+
import { Schemes } from '../util/schemes';
12+
13+
type DirWatcherEntry = {
14+
readonly uri: vscode.Uri;
15+
readonly listeners: IDisposable[];
16+
};
17+
18+
19+
export class FileWatcherManager {
20+
21+
private readonly fileWatchers = new Map<number, {
22+
readonly watcher: vscode.FileSystemWatcher;
23+
readonly dirWatchers: DirWatcherEntry[];
24+
}>();
25+
26+
private readonly dirWatchers = new ResourceMap<{
27+
readonly watcher: vscode.FileSystemWatcher;
28+
refCount: number;
29+
}>();
30+
31+
create(id: number, uri: vscode.Uri, watchParentDirs: boolean, listeners: { create?: () => void; change?: () => void; delete?: () => void }): void {
32+
const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'), !listeners.create, !listeners.change, !listeners.delete);
33+
const parentDirWatchers: DirWatcherEntry[] = [];
34+
this.fileWatchers.set(id, { watcher, dirWatchers: parentDirWatchers });
35+
36+
if (listeners.create) { watcher.onDidCreate(listeners.create); }
37+
if (listeners.change) { watcher.onDidChange(listeners.change); }
38+
if (listeners.delete) { watcher.onDidDelete(listeners.delete); }
39+
40+
if (watchParentDirs && uri.scheme !== Schemes.untitled) {
41+
// We need to watch the parent directories too for when these are deleted / created
42+
for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) {
43+
const dirWatcher: DirWatcherEntry = { uri: dirUri, listeners: [] };
44+
45+
let parentDirWatcher = this.dirWatchers.get(dirUri);
46+
if (!parentDirWatcher) {
47+
const glob = new vscode.RelativePattern(Utils.dirname(dirUri), Utils.basename(dirUri));
48+
const parentWatcher = vscode.workspace.createFileSystemWatcher(glob, !listeners.create, true, !listeners.delete);
49+
parentDirWatcher = { refCount: 0, watcher: parentWatcher };
50+
this.dirWatchers.set(dirUri, parentDirWatcher);
51+
}
52+
parentDirWatcher.refCount++;
53+
54+
if (listeners.create) {
55+
dirWatcher.listeners.push(parentDirWatcher.watcher.onDidCreate(async () => {
56+
// Just because the parent dir was created doesn't mean our file was created
57+
try {
58+
const stat = await vscode.workspace.fs.stat(uri);
59+
if (stat.type === vscode.FileType.File) {
60+
listeners.create!();
61+
}
62+
} catch {
63+
// Noop
64+
}
65+
}));
66+
}
67+
68+
if (listeners.delete) {
69+
// When the parent dir is deleted, consider our file deleted too
70+
// TODO: this fires if the file previously did not exist and then the parent is deleted
71+
dirWatcher.listeners.push(parentDirWatcher.watcher.onDidDelete(listeners.delete));
72+
}
73+
74+
parentDirWatchers.push(dirWatcher);
75+
}
76+
}
77+
}
78+
79+
delete(id: number): void {
80+
const entry = this.fileWatchers.get(id);
81+
if (entry) {
82+
for (const dirWatcher of entry.dirWatchers) {
83+
disposeAll(dirWatcher.listeners);
84+
85+
const dirWatcherEntry = this.dirWatchers.get(dirWatcher.uri);
86+
if (dirWatcherEntry) {
87+
if (--dirWatcherEntry.refCount <= 0) {
88+
dirWatcherEntry.watcher.dispose();
89+
this.dirWatchers.delete(dirWatcher.uri);
90+
}
91+
}
92+
}
93+
94+
entry.watcher.dispose();
95+
}
96+
97+
this.fileWatchers.delete(id);
98+
}
99+
}

extensions/markdown-language-features/src/util/inMemoryDocument.ts renamed to extensions/markdown-language-features/src/client/inMemoryDocument.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,17 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7-
import { TextDocument } from 'vscode-languageserver-textdocument';
87
import { ITextDocument } from '../types/textDocument';
98

109
export class InMemoryDocument implements ITextDocument {
1110

12-
private readonly _doc: TextDocument;
13-
1411
constructor(
15-
public readonly uri: vscode.Uri, contents: string,
12+
public readonly uri: vscode.Uri,
13+
private readonly contents: string,
1614
public readonly version = 0,
17-
) {
18-
19-
this._doc = TextDocument.create(uri.toString(), 'markdown', version, contents);
20-
}
21-
22-
get lineCount(): number {
23-
return this._doc.lineCount;
24-
}
25-
26-
positionAt(offset: number): vscode.Position {
27-
const pos = this._doc.positionAt(offset);
28-
return new vscode.Position(pos.line, pos.character);
29-
}
15+
) { }
3016

31-
getText(range?: vscode.Range): string {
32-
return this._doc.getText(range);
17+
getText(): string {
18+
return this.contents;
3319
}
3420
}

extensions/markdown-language-features/src/workspace.ts renamed to extensions/markdown-language-features/src/client/workspace.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,18 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7-
import { ITextDocument } from './types/textDocument';
8-
import { Disposable } from './util/dispose';
9-
import { isMarkdownFile, looksLikeMarkdownPath } from './util/file';
10-
import { InMemoryDocument } from './util/inMemoryDocument';
11-
import { ResourceMap } from './util/resourceMap';
12-
13-
/**
14-
* Provides set of markdown files in the current workspace.
15-
*/
16-
export interface IMdWorkspace {
17-
getOrLoadMarkdownDocument(resource: vscode.Uri): Promise<ITextDocument | undefined>;
18-
}
7+
import { ITextDocument } from '../types/textDocument';
8+
import { Disposable } from '../util/dispose';
9+
import { isMarkdownFile, looksLikeMarkdownPath } from '../util/file';
10+
import { InMemoryDocument } from './inMemoryDocument';
11+
import { ResourceMap } from '../util/resourceMap';
1912

2013
/**
2114
* Provides set of markdown files known to VS Code.
2215
*
2316
* This includes both opened text documents and markdown files in the workspace.
2417
*/
25-
export class VsCodeMdWorkspace extends Disposable implements IMdWorkspace {
18+
export class VsCodeMdWorkspace extends Disposable {
2619

2720
private _watcher: vscode.FileSystemWatcher | undefined;
2821

extensions/markdown-language-features/src/extension.browser.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7-
import { BaseLanguageClient, LanguageClient, LanguageClientOptions } from 'vscode-languageclient/browser';
8-
import { startClient } from './client';
7+
import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/browser';
8+
import { MdLanguageClient, startClient } from './client/client';
99
import { activateShared } from './extension.shared';
1010
import { VsCodeOutputLogger } from './logging';
1111
import { IMdParser, MarkdownItEngine } from './markdownEngine';
1212
import { getMarkdownExtensionContributions } from './markdownExtensions';
1313
import { githubSlugifier } from './slugify';
14-
import { IMdWorkspace, VsCodeMdWorkspace } from './workspace';
1514

1615
export async function activate(context: vscode.ExtensionContext) {
1716
const contributions = getMarkdownExtensionContributions(context);
@@ -22,21 +21,16 @@ export async function activate(context: vscode.ExtensionContext) {
2221

2322
const engine = new MarkdownItEngine(contributions, githubSlugifier, logger);
2423

25-
const workspace = new VsCodeMdWorkspace();
26-
context.subscriptions.push(workspace);
27-
28-
const client = await startServer(context, workspace, engine);
29-
context.subscriptions.push({
30-
dispose: () => client.stop()
31-
});
24+
const client = await startServer(context, engine);
25+
context.subscriptions.push(client);
3226
activateShared(context, client, engine, logger, contributions);
3327
}
3428

35-
function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise<BaseLanguageClient> {
29+
function startServer(context: vscode.ExtensionContext, parser: IMdParser): Promise<MdLanguageClient> {
3630
const serverMain = vscode.Uri.joinPath(context.extensionUri, 'server/dist/browser/main.js');
3731
const worker = new Worker(serverMain.toString());
3832

3933
return startClient((id: string, name: string, clientOptions: LanguageClientOptions) => {
4034
return new LanguageClient(id, name, clientOptions, worker);
41-
}, workspace, parser);
35+
}, parser);
4236
}

0 commit comments

Comments
 (0)