diff --git a/extensions/markdown-language-features/server/README.md b/extensions/markdown-language-features/server/README.md index de4e33926c3..49670fc800d 100644 --- a/extensions/markdown-language-features/server/README.md +++ b/extensions/markdown-language-features/server/README.md @@ -23,12 +23,20 @@ This server uses the [Markdown Language Service](https://github.com/microsoft/vs - [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. +- [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. +- Find all references to a file. Uses a custom `markdown/getReferencesToFileInWorkspace` message. + +- [Code Actions](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction) + + - Organize link definitions source action. + - Extract link to definition refactoring. + +- (experimental) Updating links when a file is moved / renamed. Uses a custom `markdown/getEditForFileRenames` message. + +- (experimental) [Pull diagnostics (validation)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics) for links. ## Client requirements diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json index 1211948600a..1e4a7831f8e 100644 --- a/extensions/markdown-language-features/server/package.json +++ b/extensions/markdown-language-features/server/package.json @@ -13,7 +13,8 @@ "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.5", "vscode-languageserver-types": "^3.17.1", - "vscode-markdown-languageservice": "^0.0.1", + "vscode-markdown-languageservice": "^0.1.0-alpha.1", + "vscode-nls": "^5.0.1", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/markdown-language-features/server/src/server.ts b/extensions/markdown-language-features/server/src/server.ts index bbd8468ec02..bf918945d87 100644 --- a/extensions/markdown-language-features/server/src/server.ts +++ b/extensions/markdown-language-features/server/src/server.ts @@ -7,6 +7,7 @@ import { CancellationToken, Connection, InitializeParams, InitializeResult, Note import { TextDocument } from 'vscode-languageserver-textdocument'; import * as lsp from 'vscode-languageserver-types'; import * as md from 'vscode-markdown-languageservice'; +import * as nls from 'vscode-nls'; import { URI } from 'vscode-uri'; import { getLsConfiguration, LsConfiguration } from './config'; import { ConfigurationManager } from './configuration'; @@ -16,8 +17,11 @@ import * as protocol from './protocol'; import { IDisposable } from './util/dispose'; import { VsCodeClientWorkspace } from './workspace'; +const localize = nls.loadMessageBundle(); + interface MdServerInitializationOptions extends LsConfiguration { } +const organizeLinkDefKind = 'source.organizeLinkDefinitions'; export async function startServer(connection: Connection) { const documents = new TextDocuments(TextDocument); const notebooks = new NotebookDocuments(documents); @@ -61,6 +65,7 @@ export async function startServer(connection: Connection) { interFileDependencies: true, workspaceDiagnostics: false, }, + codeActionProvider: { resolveProvider: true }, completionProvider: { triggerCharacters: ['.', '/', '#'] }, definitionProvider: true, documentLinkProvider: { resolveProvider: true }, @@ -97,7 +102,7 @@ export async function startServer(connection: Connection) { if (!document) { return []; } - return mdLs!.getDocumentSymbols(document, token); + return mdLs!.getDocumentSymbols(document, { includeLinkDefinitions: true }, token); }); connection.onFoldingRanges(async (params, token): Promise => { @@ -152,6 +157,48 @@ export async function startServer(connection: Connection) { return mdLs!.getRenameEdit(document, params.position, params.newName, token); }); + interface OrganizeLinkActionData { + readonly uri: string; + } + + connection.onCodeAction(async (params, token) => { + const document = documents.get(params.textDocument.uri); + if (!document) { + return undefined; + } + + if (params.context.only?.some(kind => kind === 'source' || kind.startsWith('source.'))) { + const action: lsp.CodeAction = { + title: localize('organizeLinkDefAction.title', "Organize link definitions"), + kind: organizeLinkDefKind, + data: { uri: document.uri } + }; + return [action]; + } + + return mdLs!.getCodeActions(document, params.range, params.context, token); + }); + + connection.onCodeActionResolve(async (codeAction, token) => { + if (codeAction.kind === organizeLinkDefKind) { + const data = codeAction.data as OrganizeLinkActionData; + const document = documents.get(data.uri); + if (!document) { + return codeAction; + } + + const edits = (await mdLs?.organizeLinkDefinitions(document, { removeUnused: true }, token)) || []; + codeAction.edit = { + changes: { + [data.uri]: edits + } + }; + return codeAction; + } + + return codeAction; + }); + connection.onRequest(protocol.getReferencesToFileInWorkspace, (async (params: { uri: string }, token: CancellationToken) => { return mdLs!.getFileReferences(URI.parse(params.uri), token); })); diff --git a/extensions/markdown-language-features/server/src/workspace.ts b/extensions/markdown-language-features/server/src/workspace.ts index 3cd75cf5e84..0389055807a 100644 --- a/extensions/markdown-language-features/server/src/workspace.ts +++ b/extensions/markdown-language-features/server/src/workspace.ts @@ -10,7 +10,6 @@ import { ContainingDocumentContext, FileWatcherOptions, IFileSystemWatcher } fro import { URI } from 'vscode-uri'; import { LsConfiguration } from './config'; import * as protocol from './protocol'; -import { coalesce } from './util/arrays'; import { isMarkdownFile, looksLikeMarkdownPath } from './util/file'; import { Limiter } from './util/limiter'; import { ResourceMap } from './util/resourceMap'; @@ -133,29 +132,35 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { } async getAllMarkdownDocuments(): Promise> { + // Add opened files (such as untitled files) + const openTextDocumentResults = this.documents.all() + .filter(doc => this.isRelevantMarkdownDocument(doc)); + + const allDocs = new ResourceMap(); + for (const doc of openTextDocumentResults) { + allDocs.set(URI.parse(doc.uri), doc); + } + + // And then add files on disk const maxConcurrent = 20; - - const foundFiles = new ResourceMap(); const limiter = new Limiter(maxConcurrent); - - // Add files on disk const resources = await this.connection.sendRequest(protocol.findMarkdownFilesInWorkspace, {}); - const onDiskResults = await Promise.all(resources.map(strResource => { + await Promise.all(resources.map(strResource => { return limiter.queue(async () => { const resource = URI.parse(strResource); + if (allDocs.has(resource)) { + return; + } + const doc = await this.openMarkdownDocument(resource); if (doc) { - foundFiles.set(resource); + allDocs.set(resource, doc); } return doc; }); })); - // Add opened files (such as untitled files) - const openTextDocumentResults = await Promise.all(this.documents.all() - .filter(doc => !foundFiles.has(URI.parse(doc.uri)) && this.isRelevantMarkdownDocument(doc))); - - return coalesce([...onDiskResults, ...openTextDocumentResults]); + return allDocs.values(); } hasMarkdownDocument(resource: URI): boolean { diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock index 7294c3084f4..62275748baa 100644 --- a/extensions/markdown-language-features/server/yarn.lock +++ b/extensions/markdown-language-features/server/yarn.lock @@ -42,10 +42,10 @@ vscode-languageserver@^8.0.2: dependencies: vscode-languageserver-protocol "3.17.2" -vscode-markdown-languageservice@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.1.tgz#40398c7cb2d169359e690bc76bd6618dc377e58a" - integrity sha512-zQuAJXLY3Wmu7LknHaes8dDZt0TWwgJQlLocbHgBtnMp6Tdv7zgNToIkW4k4OReqpiOLt0llIamM29e6ober0Q== +vscode-markdown-languageservice@^0.1.0-alpha.1: + version "0.1.0-alpha.1" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.1.0-alpha.1.tgz#60a9b445240eb2f90b5f2cfe203f9cdf1773d674" + integrity sha512-2detAtQRLGdc6MgdQI/8/+Bypa3enw6SA/ia4PCBctwO422kvYjBlyICnqP12ju6DVUNxfLQg5aNqa90xO1H2A== dependencies: picomatch "^2.3.1" vscode-languageserver-textdocument "^1.0.5"