Skip to content

Commit bec36ce

Browse files
authored
Move md path completions and document links to language server (microsoft#155100)
1 parent 06443bc commit bec36ce

File tree

14 files changed

+153
-1345
lines changed

14 files changed

+153
-1345
lines changed

extensions/markdown-language-features/server/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@
1010
"main": "./out/node/main",
1111
"browser": "./dist/browser/main",
1212
"dependencies": {
13-
"vscode-languageserver": "^8.0.2-next.4",
14-
"vscode-uri": "^3.0.3",
13+
"vscode-languageserver": "^8.0.2-next.5`",
1514
"vscode-languageserver-textdocument": "^1.0.5",
1615
"vscode-languageserver-types": "^3.17.1",
17-
"vscode-markdown-languageservice": "microsoft/vscode-markdown-languageservice"
16+
"vscode-markdown-languageservice": "^0.0.0-alpha.5",
17+
"vscode-uri": "^3.0.3"
1818
},
1919
"devDependencies": {
2020
"@types/node": "16.x"
2121
},
2222
"scripts": {
23-
"postinstall": "cd node_modules/vscode-markdown-languageservice && yarn run compile-ext",
2423
"compile": "gulp compile-extension:markdown-language-features-server",
2524
"watch": "gulp watch-extension:markdown-language-features-server"
2625
}

extensions/markdown-language-features/server/src/protocol.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import { RequestType } from 'vscode-languageserver';
77
import * as md from 'vscode-markdown-languageservice';
88

9-
declare const TextDecoder: any;
10-
119
export const parseRequestType: RequestType<{ uri: string }, md.Token[], any> = new RequestType('markdown/parse');
1210

1311
export const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile');
1412

13+
export const statFileRequestType: RequestType<{ uri: string }, md.FileStat | undefined, any> = new RequestType('markdown/statFile');
14+
15+
export const readDirectoryRequestType: RequestType<{ uri: string }, [string, md.FileStat][], any> = new RequestType('markdown/readDirectory');
16+
1517
export const findFilesRequestTypes: RequestType<{}, string[], any> = new RequestType('markdown/findFiles');

extensions/markdown-language-features/server/src/server.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,35 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Connection, InitializeParams, InitializeResult, TextDocuments } from 'vscode-languageserver';
6+
import { Connection, InitializeParams, InitializeResult, NotebookDocuments, TextDocuments } from 'vscode-languageserver';
77
import { TextDocument } from 'vscode-languageserver-textdocument';
88
import * as lsp from 'vscode-languageserver-types';
99
import * as md from 'vscode-markdown-languageservice';
10+
import { URI } from 'vscode-uri';
1011
import { LogFunctionLogger } from './logging';
1112
import { parseRequestType } from './protocol';
1213
import { VsCodeClientWorkspace } from './workspace';
1314

14-
declare const TextDecoder: any;
15-
16-
export function startServer(connection: Connection) {
15+
export async function startServer(connection: Connection) {
1716
const documents = new TextDocuments(TextDocument);
18-
documents.listen(connection);
17+
const notebooks = new NotebookDocuments(documents);
1918

20-
connection.onInitialize((_params: InitializeParams): InitializeResult => {
19+
connection.onInitialize((params: InitializeParams): InitializeResult => {
20+
workspace.workspaceFolders = (params.workspaceFolders ?? []).map(x => URI.parse(x.uri));
2121
return {
2222
capabilities: {
23+
documentLinkProvider: { resolveProvider: true },
2324
documentSymbolProvider: true,
25+
completionProvider: { triggerCharacters: ['.', '/', '#'] },
2426
foldingRangeProvider: true,
2527
selectionRangeProvider: true,
2628
workspaceSymbolProvider: true,
29+
workspace: {
30+
workspaceFolders: {
31+
supported: true,
32+
changeNotifications: true,
33+
},
34+
}
2735
}
2836
};
2937
});
@@ -36,15 +44,36 @@ export function startServer(connection: Connection) {
3644
}
3745
};
3846

39-
const workspace = new VsCodeClientWorkspace(connection, documents);
47+
const workspace = new VsCodeClientWorkspace(connection, documents, notebooks);
4048
const logger = new LogFunctionLogger(connection.console.log.bind(connection.console));
4149
const provider = md.createLanguageService({ workspace, parser, logger });
4250

51+
connection.onDocumentLinks(async (params, token): Promise<lsp.DocumentLink[]> => {
52+
try {
53+
const document = documents.get(params.textDocument.uri);
54+
if (document) {
55+
return await provider.getDocumentLinks(document, token);
56+
}
57+
} catch (e) {
58+
console.error(e.stack);
59+
}
60+
return [];
61+
});
62+
63+
connection.onDocumentLinkResolve(async (link, token): Promise<lsp.DocumentLink | undefined> => {
64+
try {
65+
return await provider.resolveDocumentLink(link, token);
66+
} catch (e) {
67+
console.error(e.stack);
68+
}
69+
return undefined;
70+
});
71+
4372
connection.onDocumentSymbol(async (params, token): Promise<lsp.DocumentSymbol[]> => {
4473
try {
4574
const document = documents.get(params.textDocument.uri);
4675
if (document) {
47-
return await provider.provideDocumentSymbols(document, token);
76+
return await provider.getDocumentSymbols(document, token);
4877
}
4978
} catch (e) {
5079
console.error(e.stack);
@@ -56,7 +85,7 @@ export function startServer(connection: Connection) {
5685
try {
5786
const document = documents.get(params.textDocument.uri);
5887
if (document) {
59-
return await provider.provideFoldingRanges(document, token);
88+
return await provider.getFoldingRanges(document, token);
6089
}
6190
} catch (e) {
6291
console.error(e.stack);
@@ -68,7 +97,7 @@ export function startServer(connection: Connection) {
6897
try {
6998
const document = documents.get(params.textDocument.uri);
7099
if (document) {
71-
return await provider.provideSelectionRanges(document, params.positions, token);
100+
return await provider.getSelectionRanges(document, params.positions, token);
72101
}
73102
} catch (e) {
74103
console.error(e.stack);
@@ -78,13 +107,26 @@ export function startServer(connection: Connection) {
78107

79108
connection.onWorkspaceSymbol(async (params, token): Promise<lsp.WorkspaceSymbol[]> => {
80109
try {
81-
return await provider.provideWorkspaceSymbols(params.query, token);
110+
return await provider.getWorkspaceSymbols(params.query, token);
82111
} catch (e) {
83112
console.error(e.stack);
84113
}
85114
return [];
86115
});
87116

117+
connection.onCompletion(async (params, token): Promise<lsp.CompletionItem[]> => {
118+
try {
119+
const document = documents.get(params.textDocument.uri);
120+
if (document) {
121+
return await provider.getCompletionItems(document, params.position, params.context!, token);
122+
}
123+
} catch (e) {
124+
console.error(e.stack);
125+
}
126+
return [];
127+
});
128+
129+
documents.listen(connection);
130+
notebooks.listen(connection);
88131
connection.listen();
89132
}
90-
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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+
export const Schemes = Object.freeze({
7+
notebookCell: 'vscode-notebook-cell',
8+
});

extensions/markdown-language-features/server/src/workspace.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Connection, Emitter, FileChangeType, TextDocuments } from 'vscode-languageserver';
6+
import { Connection, Emitter, FileChangeType, NotebookDocuments, TextDocuments } from 'vscode-languageserver';
77
import { TextDocument } from 'vscode-languageserver-textdocument';
88
import * as md from 'vscode-markdown-languageservice';
9+
import { ContainingDocumentContext } from 'vscode-markdown-languageservice/out/workspace';
910
import { URI } from 'vscode-uri';
1011
import * as protocol from './protocol';
1112
import { coalesce } from './util/arrays';
1213
import { isMarkdownDocument, looksLikeMarkdownPath } from './util/file';
1314
import { Limiter } from './util/limiter';
1415
import { ResourceMap } from './util/resourceMap';
16+
import { Schemes } from './util/schemes';
1517

1618
declare const TextDecoder: any;
1719

@@ -33,6 +35,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
3335
constructor(
3436
private readonly connection: Connection,
3537
private readonly documents: TextDocuments<TextDocument>,
38+
private readonly notebooks: NotebookDocuments<TextDocument>,
3639
) {
3740
documents.onDidOpen(e => {
3841
this._documentCache.delete(URI.parse(e.document.uri));
@@ -57,14 +60,14 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
5760
switch (change.type) {
5861
case FileChangeType.Changed: {
5962
this._documentCache.delete(resource);
60-
const document = await this.getOrLoadMarkdownDocument(resource);
63+
const document = await this.openMarkdownDocument(resource);
6164
if (document) {
6265
this._onDidChangeMarkdownDocument.fire(document);
6366
}
6467
break;
6568
}
6669
case FileChangeType.Created: {
67-
const document = await this.getOrLoadMarkdownDocument(resource);
70+
const document = await this.openMarkdownDocument(resource);
6871
if (document) {
6972
this._onDidCreateMarkdownDocument.fire(document);
7073
}
@@ -80,6 +83,22 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
8083
});
8184
}
8285

86+
public listen() {
87+
this.connection.workspace.onDidChangeWorkspaceFolders(async () => {
88+
this.workspaceFolders = (await this.connection.workspace.getWorkspaceFolders() ?? []).map(x => URI.parse(x.uri));
89+
});
90+
}
91+
92+
private _workspaceFolders: readonly URI[] = [];
93+
94+
get workspaceFolders(): readonly URI[] {
95+
return this._workspaceFolders;
96+
}
97+
98+
set workspaceFolders(value: readonly URI[]) {
99+
this._workspaceFolders = value;
100+
}
101+
83102
async getAllMarkdownDocuments(): Promise<Iterable<md.ITextDocument>> {
84103
const maxConcurrent = 20;
85104

@@ -91,7 +110,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
91110
const onDiskResults = await Promise.all(resources.map(strResource => {
92111
return limiter.queue(async () => {
93112
const resource = URI.parse(strResource);
94-
const doc = await this.getOrLoadMarkdownDocument(resource);
113+
const doc = await this.openMarkdownDocument(resource);
95114
if (doc) {
96115
foundFiles.set(resource);
97116
}
@@ -110,7 +129,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
110129
return !!this.documents.get(resource.toString());
111130
}
112131

113-
async getOrLoadMarkdownDocument(resource: URI): Promise<md.ITextDocument | undefined> {
132+
async openMarkdownDocument(resource: URI): Promise<md.ITextDocument | undefined> {
114133
const existing = this._documentCache.get(resource);
115134
if (existing) {
116135
return existing;
@@ -141,12 +160,25 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
141160
}
142161
}
143162

144-
async pathExists(_resource: URI): Promise<boolean> {
145-
return false;
163+
stat(resource: URI): Promise<md.FileStat | undefined> {
164+
return this.connection.sendRequest(protocol.statFileRequestType, { uri: resource.toString() });
165+
}
166+
167+
async readDirectory(resource: URI): Promise<[string, md.FileStat][]> {
168+
return this.connection.sendRequest(protocol.readDirectoryRequestType, { uri: resource.toString() });
146169
}
147170

148-
async readDirectory(_resource: URI): Promise<[string, { isDir: boolean }][]> {
149-
return [];
171+
getContainingDocument(resource: URI): ContainingDocumentContext | undefined {
172+
if (resource.scheme === Schemes.notebookCell) {
173+
const nb = this.notebooks.findNotebookDocumentForCell(resource.toString());
174+
if (nb) {
175+
return {
176+
uri: URI.parse(nb.uri),
177+
children: nb.cells.map(cell => ({ uri: URI.parse(cell.document) })),
178+
};
179+
}
180+
}
181+
return undefined;
150182
}
151183

152184
private isRelevantMarkdownDocument(doc: TextDocument) {

extensions/markdown-language-features/server/yarn.lock

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,19 @@ vscode-languageserver-types@^3.17.1:
3535
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16"
3636
integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ==
3737

38-
vscode-languageserver@^8.0.2-next.4:
38+
vscode-languageserver@^8.0.2-next.5`:
3939
version "8.0.2-next.5"
4040
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.2-next.5.tgz#39a2dd4c504fb88042375e7ac706a714bdaab4e5"
4141
integrity sha512-2ZDb7O/4atS9mJKufPPz637z+51kCyZfgnobFW5eSrUdS3c0UB/nMS4Ng1EavYTX84GVaVMKCrmP0f2ceLmR0A==
4242
dependencies:
4343
vscode-languageserver-protocol "3.17.2-next.6"
4444

45-
vscode-markdown-languageservice@microsoft/vscode-markdown-languageservice:
46-
version "0.0.0-alpha.2"
47-
resolved "https://codeload.github.com/microsoft/vscode-markdown-languageservice/tar.gz/db497ada376aae9a335519dbfb406c6a1f873446"
45+
vscode-markdown-languageservice@^0.0.0-alpha.5:
46+
version "0.0.0-alpha.5"
47+
resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.5.tgz#fb3042f3ee79589606154c19b15565541337bceb"
48+
integrity sha512-vy8UVa1jtm3CwkifRn3fEWM710JC4AYEECNd5KQthSCoFSfT5pOshJNFWs5yzBeVrohiy4deOdhSrfbDMg/Hyg==
4849
dependencies:
50+
vscode-languageserver-textdocument "^1.0.5"
4951
vscode-languageserver-types "^3.17.1"
5052
vscode-uri "^3.0.3"
5153

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

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import Token = require('markdown-it/lib/token');
77
import * as vscode from 'vscode';
8-
import { BaseLanguageClient, LanguageClientOptions, RequestType } from 'vscode-languageclient';
8+
import { BaseLanguageClient, LanguageClientOptions, NotebookDocumentSyncRegistrationType, RequestType } from 'vscode-languageclient';
99
import * as nls from 'vscode-nls';
1010
import { IMdParser } from './markdownEngine';
1111
import { markdownFileExtensions } from './util/file';
@@ -14,9 +14,9 @@ import { IMdWorkspace } from './workspace';
1414
const localize = nls.loadMessageBundle();
1515

1616
const parseRequestType: RequestType<{ uri: string }, Token[], any> = new RequestType('markdown/parse');
17-
1817
const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile');
19-
18+
const statFileRequestType: RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any> = new RequestType('markdown/statFile');
19+
const readDirectoryRequestType: RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any> = new RequestType('markdown/readDirectory');
2020
const findFilesRequestTypes: RequestType<{}, string[], any> = new RequestType('markdown/findFiles');
2121

2222
export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient;
@@ -33,13 +33,25 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
3333
configurationSection: ['markdown'],
3434
fileEvents: vscode.workspace.createFileSystemWatcher(mdFileGlob),
3535
},
36-
initializationOptions: {}
3736
};
3837

3938
const client = factory('markdown', localize('markdownServer.name', 'Markdown Language Server'), clientOptions);
4039

4140
client.registerProposedFeatures();
4241

42+
const notebookFeature = client.getFeature(NotebookDocumentSyncRegistrationType.method);
43+
if (notebookFeature !== undefined) {
44+
notebookFeature.register({
45+
id: String(Date.now()),
46+
registerOptions: {
47+
notebookSelector: [{
48+
notebook: '*',
49+
cells: [{ language: 'markdown' }]
50+
}]
51+
}
52+
});
53+
}
54+
4355
client.onRequest(parseRequestType, async (e) => {
4456
const uri = vscode.Uri.parse(e.uri);
4557
const doc = await workspace.getOrLoadMarkdownDocument(uri);
@@ -55,6 +67,22 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
5567
return Array.from(await vscode.workspace.fs.readFile(uri));
5668
});
5769

70+
client.onRequest(statFileRequestType, async (e): Promise<{ isDirectory: boolean } | undefined> => {
71+
const uri = vscode.Uri.parse(e.uri);
72+
try {
73+
const stat = await vscode.workspace.fs.stat(uri);
74+
return { isDirectory: stat.type === vscode.FileType.Directory };
75+
} catch {
76+
return undefined;
77+
}
78+
});
79+
80+
client.onRequest(readDirectoryRequestType, async (e): Promise<[string, { isDirectory: boolean }][]> => {
81+
const uri = vscode.Uri.parse(e.uri);
82+
const result = await vscode.workspace.fs.readDirectory(uri);
83+
return result.map(([name, type]) => [name, { isDirectory: type === vscode.FileType.Directory }]);
84+
});
85+
5886
client.onRequest(findFilesRequestTypes, async (): Promise<string[]> => {
5987
return (await vscode.workspace.findFiles(mdFileGlob, '**/node_modules/**')).map(x => x.toString());
6088
});

0 commit comments

Comments
 (0)