diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 808cf0bc3d9..1de8e59e74a 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -531,12 +531,21 @@ }, "markdown.experimental.updateLinksOnFileMove.externalFileGlobs": { "type": "string", - "default": "**/*.{jpg,jpe,jpeg,png,bmp,gif,ico,webp,avif}", + "default": "**/*.{jpg,jpe,jpeg,png,bmp,gif,ico,webp,avif,tiff,svg,mp4}", "description": "%configuration.markdown.experimental.updateLinksOnFileMove.fileGlobs%", "scope": "resource", "tags": [ "experimental" ] + }, + "markdown.experimental.updateLinksOnFileMove.enableForDirectories": { + "type": "boolean", + "default": true, + "description": "%configuration.markdown.experimental.updateLinksOnFileMove.enableForDirectories%", + "scope": "resource", + "tags": [ + "experimental" + ] } } }, diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 6db1bc6a600..d4cea703cc2 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -42,5 +42,6 @@ "configuration.markdown.experimental.updateLinksOnFileMove.enabled.always": "Always update links automatically.", "configuration.markdown.experimental.updateLinksOnFileMove.enabled.never": "Never try to update link and don't prompt.", "configuration.markdown.experimental.updateLinksOnFileMove.fileGlobs": "A glob that specifies which files besides markdown should trigger a link update.", + "configuration.markdown.experimental.updateLinksOnFileMove.enableForDirectories": "enable/disable updating links when a directory is moved or renamed in the workspace.", "workspaceTrust": "Required for loading styles configured in the workspace." } diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json index 974cad30efa..2738b19188b 100644 --- a/extensions/markdown-language-features/server/package.json +++ b/extensions/markdown-language-features/server/package.json @@ -13,7 +13,7 @@ "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.5", "vscode-languageserver-types": "^3.17.1", - "vscode-markdown-languageservice": "^0.0.0-alpha.14", + "vscode-markdown-languageservice": "^0.0.0-alpha.15", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/markdown-language-features/server/src/config.ts b/extensions/markdown-language-features/server/src/config.ts index 8fb952bb943..6f9d85fbf1d 100644 --- a/extensions/markdown-language-features/server/src/config.ts +++ b/extensions/markdown-language-features/server/src/config.ts @@ -3,17 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export interface LsConfiguration { - /** - * List of file extensions should be considered as markdown. - * - * These should not include the leading `.`. - */ - readonly markdownFileExtensions: readonly string[]; -} +import { LsConfiguration } from 'vscode-markdown-languageservice/out/config'; + +export { LsConfiguration }; const defaultConfig: LsConfiguration = { markdownFileExtensions: ['md'], + knownLinkedToFileExtensions: [ + 'jpg', + 'jpeg', + 'png', + 'gif', + 'webp', + 'bmp', + 'tiff', + ], + excludePaths: [ + '**/.*', + '**/node_modules/**', + ] }; export function getLsConfiguration(overrides: Partial): LsConfiguration { diff --git a/extensions/markdown-language-features/server/src/server.ts b/extensions/markdown-language-features/server/src/server.ts index 0bfeb9e166d..1b46e3712af 100644 --- a/extensions/markdown-language-features/server/src/server.ts +++ b/extensions/markdown-language-features/server/src/server.ts @@ -7,22 +7,24 @@ 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 { IDisposable } from 'vscode-markdown-languageservice/out/util/dispose'; import { URI } from 'vscode-uri'; -import { getLsConfiguration } from './config'; +import { getLsConfiguration, LsConfiguration } from './config'; import { ConfigurationManager } from './configuration'; import { registerValidateSupport } from './languageFeatures/diagnostics'; import { LogFunctionLogger } from './logging'; import * as protocol from './protocol'; +import { IDisposable } from './util/dispose'; import { VsCodeClientWorkspace } from './workspace'; +interface MdServerInitializationOptions extends LsConfiguration { } + export async function startServer(connection: Connection) { const documents = new TextDocuments(TextDocument); const notebooks = new NotebookDocuments(documents); const configurationManager = new ConfigurationManager(connection); - let provider: md.IMdLanguageService | undefined; + let mdLs: md.IMdLanguageService | undefined; let workspace: VsCodeClientWorkspace | undefined; connection.onInitialize((params: InitializeParams): InitializeResult => { @@ -34,21 +36,21 @@ export async function startServer(connection: Connection) { } }; - const config = getLsConfiguration({ - markdownFileExtensions: params.initializationOptions.markdownFileExtensions, - }); + const initOptions = params.initializationOptions as MdServerInitializationOptions | undefined; + const config = getLsConfiguration(initOptions ?? {}); const logger = new LogFunctionLogger(connection.console.log.bind(connection.console)); workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks, logger); - provider = md.createLanguageService({ + mdLs = md.createLanguageService({ workspace, parser, logger, markdownFileExtensions: config.markdownFileExtensions, + excludePaths: config.excludePaths, }); - registerCompletionsSupport(connection, documents, provider, configurationManager); - registerValidateSupport(connection, workspace, provider, configurationManager, logger); + registerCompletionsSupport(connection, documents, mdLs, configurationManager); + registerValidateSupport(connection, workspace, mdLs, configurationManager, logger); workspace.workspaceFolders = (params.workspaceFolders ?? []).map(x => URI.parse(x.uri)); return { @@ -77,139 +79,84 @@ export async function startServer(connection: Connection) { }; }); - connection.onDocumentLinks(async (params, token): Promise => { - try { - const document = documents.get(params.textDocument.uri); - if (document) { - return await provider!.getDocumentLinks(document, token); - } - } catch (e) { - console.error(e.stack); + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; } - return []; + return mdLs!.getDocumentLinks(document, token); }); connection.onDocumentLinkResolve(async (link, token): Promise => { - try { - return await provider!.resolveDocumentLink(link, token); - } catch (e) { - console.error(e.stack); - } - return undefined; + return mdLs!.resolveDocumentLink(link, token); }); connection.onDocumentSymbol(async (params, token): Promise => { - try { - const document = documents.get(params.textDocument.uri); - if (document) { - return await provider!.getDocumentSymbols(document, token); - } - } catch (e) { - console.error(e.stack); + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; } - return []; + return mdLs!.getDocumentSymbols(document, token); }); connection.onFoldingRanges(async (params, token): Promise => { - try { - const document = documents.get(params.textDocument.uri); - if (document) { - return await provider!.getFoldingRanges(document, token); - } - } catch (e) { - console.error(e.stack); + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; } - return []; + return mdLs!.getFoldingRanges(document, token); }); connection.onSelectionRanges(async (params, token): Promise => { - try { - const document = documents.get(params.textDocument.uri); - if (document) { - return await provider!.getSelectionRanges(document, params.positions, token); - } - } catch (e) { - console.error(e.stack); + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; } - return []; + return mdLs!.getSelectionRanges(document, params.positions, token); }); connection.onWorkspaceSymbol(async (params, token): Promise => { - try { - return await provider!.getWorkspaceSymbols(params.query, token); - } catch (e) { - console.error(e.stack); - } - return []; + return mdLs!.getWorkspaceSymbols(params.query, token); }); connection.onReferences(async (params, token): Promise => { - try { - const document = documents.get(params.textDocument.uri); - if (document) { - return await provider!.getReferences(document, params.position, params.context, token); - } - } catch (e) { - console.error(e.stack); + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; } - return []; + return mdLs!.getReferences(document, params.position, params.context, token); }); connection.onDefinition(async (params, token): Promise => { - try { - const document = documents.get(params.textDocument.uri); - if (document) { - return await provider!.getDefinition(document, params.position, token); - } - } catch (e) { - console.error(e.stack); + const document = documents.get(params.textDocument.uri); + if (!document) { + return undefined; } - return undefined; + return mdLs!.getDefinition(document, params.position, token); }); connection.onPrepareRename(async (params, token) => { - try { - const document = documents.get(params.textDocument.uri); - if (document) { - return await provider!.prepareRename(document, params.position, token); - } - } catch (e) { - console.error(e.stack); + const document = documents.get(params.textDocument.uri); + if (!document) { + return undefined; } - return undefined; + return mdLs!.prepareRename(document, params.position, token); }); connection.onRenameRequest(async (params, token) => { - try { - const document = documents.get(params.textDocument.uri); - if (document) { - const edit = await provider!.getRenameEdit(document, params.position, params.newName, token); - console.log(JSON.stringify(edit)); - return edit; - } - } catch (e) { - console.error(e.stack); + const document = documents.get(params.textDocument.uri); + if (!document) { + return undefined; } - return undefined; + return mdLs!.getRenameEdit(document, params.position, params.newName, token); }); connection.onRequest(protocol.getReferencesToFileInWorkspace, (async (params: { uri: string }, token: CancellationToken) => { - try { - return await provider!.getFileReferences(URI.parse(params.uri), token); - } catch (e) { - console.error(e.stack); - } - return undefined; + return mdLs!.getFileReferences(URI.parse(params.uri), token); })); connection.onRequest(protocol.getEditForFileRenames, (async (params, token: CancellationToken) => { - try { - return await provider!.getRenameFilesInWorkspaceEdit(params.map(x => ({ oldUri: URI.parse(x.oldUri), newUri: URI.parse(x.newUri) })), token); - } catch (e) { - console.error(e.stack); - } - return undefined; + return mdLs!.getRenameFilesInWorkspaceEdit(params.map(x => ({ oldUri: URI.parse(x.oldUri), newUri: URI.parse(x.newUri) })), token); })); documents.listen(connection); diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock index 326859f1f5d..56a7c136f27 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.0-alpha.14: - version "0.0.0-alpha.14" - resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.14.tgz#befe2fd1571213db0abbd9c93a4b9adf22f68d5c" - integrity sha512-6rxEZKnYTJfZBOIWfPeUm5cjss7hgnJ7lQ8ZA4b918SjcOlDT0NOCQZ/88vMuxWdKKQCywcD9YoXNMRYsT+N5w== +vscode-markdown-languageservice@^0.0.0-alpha.15: + version "0.0.0-alpha.15" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.15.tgz#f11b18eb2ced8b13d90abc50825d99ce825d88b8" + integrity sha512-OLMd6LlDf3v4/ULU354gwsEcNyuUjEGDFQIYwi78gFXd89K2eWG4KewDR9fl3ip00lOcHcvQWqFBRgkfr72DRg== dependencies: picomatch "^2.3.1" vscode-languageserver-textdocument "^1.0.5" @@ -54,9 +54,9 @@ vscode-markdown-languageservice@^0.0.0-alpha.14: vscode-uri "^3.0.3" vscode-nls@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" - integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/markdown-language-features/src/extension.shared.ts b/extensions/markdown-language-features/src/extension.shared.ts index f679a3be9dd..4f5589e14f3 100644 --- a/extensions/markdown-language-features/src/extension.shared.ts +++ b/extensions/markdown-language-features/src/extension.shared.ts @@ -11,7 +11,7 @@ import { registerPasteSupport } from './languageFeatures/copyPaste'; import { registerDiagnosticSupport } from './languageFeatures/diagnostics'; import { registerDropIntoEditorSupport } from './languageFeatures/dropIntoEditor'; import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences'; -import { registerUpdatePathsOnRename } from './languageFeatures/updatePathsOnRename'; +import { registerUpdateLinksOnRename } from './languageFeatures/updatePathsOnRename'; import { ILogger } from './logging'; import { MarkdownItEngine, MdParsingProvider } from './markdownEngine'; import { MarkdownContributionProvider } from './markdownExtensions'; @@ -63,7 +63,7 @@ function registerMarkdownLanguageFeatures( registerDropIntoEditorSupport(selector), registerFindFileReferenceSupport(commandManager, client), registerPasteSupport(selector), - registerUpdatePathsOnRename(client), + registerUpdateLinksOnRename(client), ); } diff --git a/extensions/markdown-language-features/src/languageFeatures/updatePathsOnRename.ts b/extensions/markdown-language-features/src/languageFeatures/updatePathsOnRename.ts index 911acf6bdf9..8b3536f0c47 100644 --- a/extensions/markdown-language-features/src/languageFeatures/updatePathsOnRename.ts +++ b/extensions/markdown-language-features/src/languageFeatures/updatePathsOnRename.ts @@ -19,7 +19,8 @@ const localize = nls.loadMessageBundle(); const settingNames = Object.freeze({ enabled: 'experimental.updateLinksOnFileMove.enabled', - externalFileGlobs: 'experimental.updateLinksOnFileMove.externalFileGlobs' + externalFileGlobs: 'experimental.updateLinksOnFileMove.externalFileGlobs', + enableForDirectories: 'experimental.updateLinksOnFileMove.enableForDirectories', }); const enum UpdateLinksOnFileMoveSetting { @@ -33,7 +34,7 @@ interface RenameAction { readonly newUri: vscode.Uri; } -class UpdateImportsOnFileRenameHandler extends Disposable { +class UpdateLinksOnFileRenameHandler extends Disposable { private readonly _delayer = new Delayer(50); private readonly _pendingRenames = new Set(); @@ -44,27 +45,23 @@ class UpdateImportsOnFileRenameHandler extends Disposable { super(); this._register(vscode.workspace.onDidRenameFiles(async (e) => { - const [{ newUri, oldUri }] = e.files; // TODO: only handles first file + for (const { newUri, oldUri } of e.files) { + const config = this.getConfiguration(newUri); + if (!await this.shouldParticipateInLinkUpdate(config, newUri)) { + continue; + } - const config = this.getConfiguration(newUri); - - const setting = config.get(settingNames.enabled); - if (setting === UpdateLinksOnFileMoveSetting.Never) { - return; + this._pendingRenames.add({ newUri, oldUri }); } - if (!this.shouldParticipateInLinkUpdate(config, newUri)) { - return; + if (this._pendingRenames.size) { + this._delayer.trigger(() => { + vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: localize('renameProgress.title', "Checking for Markdown links to update") + }, () => this.flushRenames()); + }); } - - this._pendingRenames.add({ oldUri, newUri }); - - this._delayer.trigger(() => { - vscode.window.withProgress({ - location: vscode.ProgressLocation.Window, - title: localize('renameProgress.title', "Checking for Markdown links to update") - }, () => this.flushRenames()); - }); })); } @@ -110,13 +107,27 @@ class UpdateImportsOnFileRenameHandler extends Disposable { return vscode.workspace.getConfiguration('markdown', resource); } - private shouldParticipateInLinkUpdate(config: vscode.WorkspaceConfiguration, newUri: vscode.Uri) { + private async shouldParticipateInLinkUpdate(config: vscode.WorkspaceConfiguration, newUri: vscode.Uri): Promise { + const setting = config.get(settingNames.enabled); + if (setting === UpdateLinksOnFileMoveSetting.Never) { + return false; + } + if (looksLikeMarkdownPath(newUri)) { return true; } const externalGlob = config.get(settingNames.externalFileGlobs); - return !!externalGlob && picomatch.isMatch(newUri.fsPath, externalGlob); + if (!!externalGlob && picomatch.isMatch(newUri.fsPath, externalGlob)) { + return true; + } + + const stat = await vscode.workspace.fs.stat(newUri); + if (stat.type === vscode.FileType.Directory) { + return config.get(settingNames.enableForDirectories, true); + } + + return false; } private async promptUser(newResources: readonly vscode.Uri[]): Promise { @@ -229,6 +240,6 @@ class UpdateImportsOnFileRenameHandler extends Disposable { } } -export function registerUpdatePathsOnRename(client: BaseLanguageClient) { - return new UpdateImportsOnFileRenameHandler(client); +export function registerUpdateLinksOnRename(client: BaseLanguageClient) { + return new UpdateLinksOnFileRenameHandler(client); }