mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 05:19:39 +00:00
Documenting markdown LS (#155789)
This commit is contained in:
parent
e7bf4c6a75
commit
924dde5c1e
120
extensions/markdown-language-features/server/README.md
Normal file
120
extensions/markdown-language-features/server/README.md
Normal file
|
@ -0,0 +1,120 @@
|
|||
# Markdown Language Server
|
||||
|
||||
> **❗ Import** This is still in development. While the language server is being used by VS Code, it has not yet been tested with other clients.
|
||||
|
||||
The Markdown language server powers VS Code's built-in markdown support, providing tools for writing and browsing Markdown files. It runs as a separate executable and implements the [language server protocol](https://microsoft.github.io/language-server-protocol/overview).
|
||||
|
||||
This server uses the [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) to implement almost all of the language features. You can use that library if you need a library for working with Markdown instead of a full language server.
|
||||
|
||||
|
||||
## Server capabilities
|
||||
|
||||
- [Completions](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for Markdown links.
|
||||
|
||||
- [Folding](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) of Markdown regions, block elements, and header sections.
|
||||
|
||||
- [Smart selection](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange) for inline elements, block elements, and header sections.
|
||||
|
||||
- [Document Symbols](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for quick navigation to headers in a document.
|
||||
|
||||
- [Workspace Symbols](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) for quick navigation to headers in the workspace
|
||||
|
||||
- [Document links](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink) for making Markdown links in a document clickable.
|
||||
|
||||
- [Find all references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) to headers and links across all Markdown files in the workspace.
|
||||
|
||||
- [Rename](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename) of headers and links across all Markdown files in the workspace.
|
||||
|
||||
- [Go to definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) from links to headers or link definitions.
|
||||
|
||||
- (experimental) [Pull diagnostics (validation)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics) for links.
|
||||
|
||||
|
||||
|
||||
## Client requirements
|
||||
|
||||
### Initialization options
|
||||
|
||||
The client can send the following initialization options to the server:
|
||||
|
||||
- `markdownFileExtensions` Array file extensions that should be considered as Markdown. These should not include the leading `.`. For example: `['md', 'mdown', 'markdown']`.
|
||||
|
||||
### Settings
|
||||
|
||||
Clients may send a `workspace/didChangeConfiguration` notification to notify the server of settings changes.
|
||||
The server supports the following settings:
|
||||
|
||||
- `markdown`
|
||||
- `suggest`
|
||||
- `paths`
|
||||
- `enabled` — Enable/disable path suggestions.
|
||||
- `experimental`
|
||||
- `validate`
|
||||
- `enabled` — Enable/disable all validation.
|
||||
- `referenceLinks`
|
||||
- `enabled` — Enable/disable validation of reference links: `[text][ref]`
|
||||
- `fragmentLinks`
|
||||
- `enabled` — Enable/disable validation of links to fragments in the current files: `[text](#head)`
|
||||
- `fileLinks`
|
||||
- `enabled` — Enable/disable validation of links to file in the workspace.
|
||||
- `markdownFragmentLinks` — Enable/disable validation of links to headers in other Markdown files.
|
||||
- `ignoreLinks` — Array of glob patterns for files that should not be validated.
|
||||
|
||||
### Custom requests
|
||||
|
||||
To support all of the features of the language server, the client needs to implement a few custom request types. The definitions of these request types can be found in [`protocol.ts`](./src/protocol.ts)
|
||||
|
||||
#### `markdown/parse`
|
||||
|
||||
Get the tokens for a Markdown file. Clients are expected to use [Markdown-it](https://github.com/markdown-it/markdown-it) for this.
|
||||
|
||||
We require that clients bring their own version of Markdown-it so that they can customize/extend Markdown-it.
|
||||
|
||||
#### `markdown/fs/readFile`
|
||||
|
||||
Read the contents of a file in the workspace.
|
||||
|
||||
#### `markdown/fs/readDirectory`
|
||||
|
||||
Read the contents of a directory in the workspace.
|
||||
|
||||
#### `markdown/fs/stat`
|
||||
|
||||
Check if a given file/directory exists in the workspace.
|
||||
|
||||
#### `markdown/fs/watcher/create`
|
||||
|
||||
Create a file watcher. This is needed for diagnostics support.
|
||||
|
||||
#### `markdown/fs/watcher/delete`
|
||||
|
||||
Delete a previously created file watcher.
|
||||
|
||||
#### `markdown/findMarkdownFilesInWorkspace`
|
||||
|
||||
Get a list of all markdown files in the workspace.
|
||||
|
||||
|
||||
## Contribute
|
||||
|
||||
The source code of the Markdown language server can be found in the [VSCode repository](https://github.com/microsoft/vscode) at [extensions/markdown-language-features/server](https://github.com/microsoft/vscode/tree/master/extensions/markdown-language-features/server).
|
||||
|
||||
File issues and pull requests in the [VSCode GitHub Issues](https://github.com/microsoft/vscode/issues). See the document [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) on how to build and run from source.
|
||||
|
||||
Most of the functionality of the server is located in libraries:
|
||||
|
||||
- [vscode-markdown-languageservice](https://github.com/microsoft/vscode-markdown-languageservice) contains the implementation of all features as a reusable library.
|
||||
- [vscode-languageserver-node](https://github.com/microsoft/vscode-languageserver-node) contains the implementation of language server for NodeJS.
|
||||
|
||||
Help on any of these projects is very welcome.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Licensed under the [MIT](https://github.com/microsoft/vscode/blob/master/LICENSE.txt) License.
|
||||
|
|
@ -4,20 +4,24 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RequestType } from 'vscode-languageserver';
|
||||
import * as lsp from 'vscode-languageserver-types';
|
||||
import * as md from 'vscode-markdown-languageservice';
|
||||
import type * as lsp from 'vscode-languageserver-types';
|
||||
import type * as md from 'vscode-markdown-languageservice';
|
||||
|
||||
// From server
|
||||
export const parseRequestType: RequestType<{ uri: string }, md.Token[], any> = new RequestType('markdown/parse');
|
||||
export const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile');
|
||||
export const statFileRequestType: RequestType<{ uri: string }, md.FileStat | undefined, any> = new RequestType('markdown/statFile');
|
||||
export const readDirectoryRequestType: RequestType<{ uri: string }, [string, md.FileStat][], any> = new RequestType('markdown/readDirectory');
|
||||
export const findFilesRequestTypes: RequestType<{}, string[], any> = new RequestType('markdown/findFiles');
|
||||
//#region From server
|
||||
export const parse = new RequestType<{ uri: string }, md.Token[], any>('markdown/parse');
|
||||
|
||||
export const createFileWatcher: RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any> = new RequestType('markdown/createFileWatcher');
|
||||
export const deleteFileWatcher: RequestType<{ id: number }, void, any> = new RequestType('markdown/deleteFileWatcher');
|
||||
export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile');
|
||||
export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory');
|
||||
export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat');
|
||||
|
||||
// To server
|
||||
export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any>('markdown/fs/watcher/create');
|
||||
export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete');
|
||||
|
||||
export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace');
|
||||
//#endregion
|
||||
|
||||
//#region To server
|
||||
export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace');
|
||||
|
||||
export const onWatcherChange: RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any> = new RequestType('markdown/onWatcherChange');
|
||||
export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange');
|
||||
//#endregion
|
||||
|
|
|
@ -30,7 +30,7 @@ export async function startServer(connection: Connection) {
|
|||
slugifier = md.githubSlugifier;
|
||||
|
||||
async tokenize(document: md.ITextDocument): Promise<md.Token[]> {
|
||||
return await connection.sendRequest(protocol.parseRequestType, { uri: document.uri.toString() });
|
||||
return await connection.sendRequest(protocol.parse, { uri: document.uri.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
|||
}
|
||||
});
|
||||
|
||||
connection.onRequest(protocol.onWatcherChange, params => {
|
||||
connection.onRequest(protocol.fs_watcher_onChange, params => {
|
||||
const watcher = this._watchers.get(params.id);
|
||||
if (!watcher) {
|
||||
return;
|
||||
|
@ -130,7 +130,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
|||
const limiter = new Limiter<md.ITextDocument | undefined>(maxConcurrent);
|
||||
|
||||
// Add files on disk
|
||||
const resources = await this.connection.sendRequest(protocol.findFilesRequestTypes, {});
|
||||
const resources = await this.connection.sendRequest(protocol.findMarkdownFilesInWorkspace, {});
|
||||
const onDiskResults = await Promise.all(resources.map(strResource => {
|
||||
return limiter.queue(async () => {
|
||||
const resource = URI.parse(strResource);
|
||||
|
@ -170,7 +170,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await this.connection.sendRequest(protocol.readFileRequestType, { uri: resource.toString() });
|
||||
const response = await this.connection.sendRequest(protocol.fs_readFile, { uri: resource.toString() });
|
||||
// TODO: LSP doesn't seem to handle Array buffers well
|
||||
const bytes = new Uint8Array(response);
|
||||
|
||||
|
@ -189,12 +189,12 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
|||
if (this._documentCache.has(resource) || this.documents.get(resource.toString())) {
|
||||
return { isDirectory: false };
|
||||
}
|
||||
return this.connection.sendRequest(protocol.statFileRequestType, { uri: resource.toString() });
|
||||
return this.connection.sendRequest(protocol.fs_stat, { uri: resource.toString() });
|
||||
}
|
||||
|
||||
async readDirectory(resource: URI): Promise<[string, md.FileStat][]> {
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: readDir', `${resource}`);
|
||||
return this.connection.sendRequest(protocol.readDirectoryRequestType, { uri: resource.toString() });
|
||||
return this.connection.sendRequest(protocol.fs_readDirectory, { uri: resource.toString() });
|
||||
}
|
||||
|
||||
getContainingDocument(resource: URI): ContainingDocumentContext | undefined {
|
||||
|
@ -221,7 +221,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
|||
const id = this._watcherPool++;
|
||||
this._watchers.set(id, entry);
|
||||
|
||||
this.connection.sendRequest(protocol.createFileWatcher, {
|
||||
this.connection.sendRequest(protocol.fs_watcher_create, {
|
||||
id,
|
||||
uri: resource.toString(),
|
||||
options,
|
||||
|
@ -232,7 +232,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
|||
onDidChange: entry.onDidChange.event,
|
||||
onDidDelete: entry.onDidDelete.event,
|
||||
dispose: () => {
|
||||
this.connection.sendRequest(protocol.deleteFileWatcher, { id });
|
||||
this.connection.sendRequest(protocol.fs_watcher_delete, { id });
|
||||
this._watchers.delete(id);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -57,7 +57,7 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
|
|||
});
|
||||
}
|
||||
|
||||
client.onRequest(proto.parseRequestType, async (e) => {
|
||||
client.onRequest(proto.parse, async (e) => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
const doc = await workspace.getOrLoadMarkdownDocument(uri);
|
||||
if (doc) {
|
||||
|
@ -67,12 +67,12 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
|
|||
}
|
||||
});
|
||||
|
||||
client.onRequest(proto.readFileRequestType, async (e): Promise<number[]> => {
|
||||
client.onRequest(proto.fs_readFile, async (e): Promise<number[]> => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
return Array.from(await vscode.workspace.fs.readFile(uri));
|
||||
});
|
||||
|
||||
client.onRequest(proto.statFileRequestType, async (e): Promise<{ isDirectory: boolean } | undefined> => {
|
||||
client.onRequest(proto.fs_stat, async (e): Promise<{ isDirectory: boolean } | undefined> => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
try {
|
||||
const stat = await vscode.workspace.fs.stat(uri);
|
||||
|
@ -82,28 +82,28 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
|
|||
}
|
||||
});
|
||||
|
||||
client.onRequest(proto.readDirectoryRequestType, async (e): Promise<[string, { isDirectory: boolean }][]> => {
|
||||
client.onRequest(proto.fs_readDirectory, async (e): Promise<[string, { isDirectory: boolean }][]> => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
const result = await vscode.workspace.fs.readDirectory(uri);
|
||||
return result.map(([name, type]) => [name, { isDirectory: type === vscode.FileType.Directory }]);
|
||||
});
|
||||
|
||||
client.onRequest(proto.findFilesRequestTypes, async (): Promise<string[]> => {
|
||||
client.onRequest(proto.findMarkdownFilesInWorkspace, async (): Promise<string[]> => {
|
||||
return (await vscode.workspace.findFiles(mdFileGlob, '**/node_modules/**')).map(x => x.toString());
|
||||
});
|
||||
|
||||
const watchers = new Map<number, vscode.FileSystemWatcher>();
|
||||
|
||||
client.onRequest(proto.createFileWatcher, async (params): Promise<void> => {
|
||||
client.onRequest(proto.fs_watcher_create, async (params): Promise<void> => {
|
||||
const id = params.id;
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(vscode.Uri.parse(params.uri), '*'), params.options.ignoreCreate, params.options.ignoreChange, params.options.ignoreDelete);
|
||||
watchers.set(id, watcher);
|
||||
watcher.onDidCreate(() => { client.sendRequest(proto.onWatcherChange, { id, uri: params.uri, kind: 'create' }); });
|
||||
watcher.onDidChange(() => { client.sendRequest(proto.onWatcherChange, { id, uri: params.uri, kind: 'change' }); });
|
||||
watcher.onDidDelete(() => { client.sendRequest(proto.onWatcherChange, { id, uri: params.uri, kind: 'delete' }); });
|
||||
watcher.onDidCreate(() => { client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind: 'create' }); });
|
||||
watcher.onDidChange(() => { client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind: 'change' }); });
|
||||
watcher.onDidDelete(() => { client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind: 'delete' }); });
|
||||
});
|
||||
|
||||
client.onRequest(proto.deleteFileWatcher, async (params): Promise<void> => {
|
||||
client.onRequest(proto.fs_watcher_delete, async (params): Promise<void> => {
|
||||
watchers.get(params.id)?.dispose();
|
||||
watchers.delete(params.id);
|
||||
});
|
||||
|
|
|
@ -8,17 +8,21 @@ import { RequestType } from 'vscode-languageclient';
|
|||
import type * as lsp from 'vscode-languageserver-types';
|
||||
import type * as md from 'vscode-markdown-languageservice';
|
||||
|
||||
// From server
|
||||
export const parseRequestType: RequestType<{ uri: string }, Token[], any> = new RequestType('markdown/parse');
|
||||
export const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile');
|
||||
export const statFileRequestType: RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any> = new RequestType('markdown/statFile');
|
||||
export const readDirectoryRequestType: RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any> = new RequestType('markdown/readDirectory');
|
||||
export const findFilesRequestTypes = new RequestType<{}, string[], any>('markdown/findFiles');
|
||||
//#region From server
|
||||
export const parse = new RequestType<{ uri: string }, Token[], any>('markdown/parse');
|
||||
|
||||
export const createFileWatcher: RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any> = new RequestType('markdown/createFileWatcher');
|
||||
export const deleteFileWatcher: RequestType<{ id: number }, void, any> = new RequestType('markdown/deleteFileWatcher');
|
||||
export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile');
|
||||
export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory');
|
||||
export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat');
|
||||
|
||||
// To server
|
||||
export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any>('markdown/fs/watcher/create');
|
||||
export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete');
|
||||
|
||||
export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace');
|
||||
//#endregion
|
||||
|
||||
//#region To server
|
||||
export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace');
|
||||
|
||||
export const onWatcherChange: RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any> = new RequestType('markdown/onWatcherChange');
|
||||
export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange');
|
||||
//#endregion
|
||||
|
|
Loading…
Reference in a new issue