Repository: marp-team/marp-vscode
Pull request #367 (2022-06-10 03:30:46)

Added experimental setting `markdown.marp.strictPathResolutionDuringExport`
`markdown.marp.strictPathResolutionDuringExport` enables strict path resolution during export.

If enabled, the export command tries to resolve relative paths from VS Code workspace that a Markdown file belongs. If not, or the Markdown does not belong to any workspace, the export command resolves paths based on the local file system.

It's useful for getting more consistent result between preview and export.

- An inline image `![]()` that refers the resource in outside of the workspace has not rendered in the preview but rendered in the export result. By enabling this option, the export command tries to resolve images from the workspace proxy so images in the outside won't be rendered as same as the preview.
- If the Markdown file is located in `[workspace-root]/markdown/abc.md`, `![](/images/xyz.jpg)` is referring to `[workspace-root]/images/xyz.jpg` at the workspace root in the preview. However, the export command could not resolve this image because the export process has not known any about of VS Code workspace. In this case, the provided option will make resolvable the image by setting correct base path during export.
- According to [experimental Markdown link validation in VS Code 1.68](https://code.visualstudio.com/updates/v1_68#_markdown-link-validation), the absolute path for local file like `/users/xxx/abc.jpg` and `C:\xxx\abc.jpg` should not be rendered, even if the referrenced image has located in the workspace. The preview is following this rule, but Marp's export command is not following. The added option can opt in strict path resolutions like this, and avoid unexpected resource sniffing by inline scripts in Markdown.

This option is experimental and disabled by default, to avoid breaking exist user's workflow.

### Related

- #359
- #354 (The second example would be useful to resolve this case)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a737d1..3231fec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
 ### Added
 
 - `deprecated-color-setting-shorthand` auto-fixable diagnostic for replacing [deprecated shorthands for setting colors](https://github.com/marp-team/marpit/issues/331) ([#358](https://github.com/marp-team/marp-vscode/issues/358), [#366](https://github.com/marp-team/marp-vscode/pull/366))
+- Experimental `markdown.marp.strictPathResolutionDuringExport` setting ([#367](https://github.com/marp-team/marp-vscode/pull/367))
 - Sponsor button for Visual Studio Marketplace ([#365](https://github.com/marp-team/marp-vscode/pull/365))
 
 ## v2.0.1 - 2022-06-06
diff --git a/package.json b/package.json
index 0bb7cfd..46e90c3 100644
--- a/package.json
+++ b/package.json
@@ -172,6 +172,11 @@
           "default": false,
           "markdownDescription": "Adds [presenter notes](https://marpit.marp.app/usage?id=presenter-notes) to exported PDF as note annotations."
         },
+        "markdown.marp.strictPathResolutionDuringExport": {
+          "type": "boolean",
+          "default": false,
+          "markdownDescription": "_[Experimental]_ Enables strict path resolution during export. If enabled, the export command tries to resolve relative paths from VS Code workspace that a Markdown file belongs. If not, or the Markdown does not belong to any workspace, the export command resolves paths based on the local file system."
+        },
         "markdown.marp.themes": {
           "type": "array",
           "default": [],
diff --git a/src/commands/export.ts b/src/commands/export.ts
index 69a578c..161753b 100644
--- a/src/commands/export.ts
+++ b/src/commands/export.ts
@@ -65,13 +65,23 @@ export const doExport = async (uri: Uri, document: TextDocument) => {
     const ext = path.extname(uri.path).replace(/^\./, '')
 
     if (chromiumRequiredExtensions.includes(ext)) {
-      // VS Code's Markdown preview may show local resources placed at the
-      // outside of workspace, and using the proxy server in that case may too
-      // much prevent file accesses.
-      //
-      // So leave handling local files to Marp CLI if the current document was
-      // assumed to use local file system.
-      return !['file', 'untitled'].includes(document.uri.scheme)
+      if (document.uri.scheme === 'untitled') return false
+      if (document.uri.scheme === 'file') {
+        if (
+          marpConfiguration().get<boolean>('strictPathResolutionDuringExport')
+        )
+          return true
+
+        // VS Code's Markdown preview may show local resources placed at the
+        // outside of workspace, and using the proxy server in that case may too
+        // much prevent file accesses.
+        //
+        // So leave handling local files to Marp CLI if the current document was
+        // assumed to use local file system.
+        return false
+      }
+
+      return true
     }
 
     return false


Test patch:
diff --git a/src/commands/export.test.ts b/src/commands/export.test.ts
index c9c7792..e24e685 100644
--- a/src/commands/export.test.ts
+++ b/src/commands/export.test.ts
@@ -175,6 +175,7 @@ describe('#doExport', () => {
     await exportModule.doExport(uri, document)
     expect(runMarpCLI).toHaveBeenCalled()
     expect(env.openExternal).toHaveBeenCalledWith(uri)
+    expect(createWorkspaceProxyServer).not.toHaveBeenCalled()
   })
 
   it('shows error when Marp CLI throws error', async () => {
@@ -213,6 +214,42 @@ describe('#doExport', () => {
     )
   })
 
+  describe('when enabled markdown.marp.strictPathResolutionDuringExport', () => {
+    beforeEach(() => {
+      setConfiguration({
+        'markdown.marp.strictPathResolutionDuringExport': true,
+      })
+      jest.spyOn(marpCli, 'default').mockImplementation()
+    })
+
+    it('opens workspace proxy server during export if the document has file scheme', async () => {
+      const fileWorkspace: any = {
+        name: 'file',
+        index: 0,
+        uri: {
+          scheme: 'file',
+          path: 'file:///tmp',
+          fsPath: '/tmp',
+        },
+      }
+      jest.spyOn(workspace, 'getWorkspaceFolder').mockReturnValue(fileWorkspace)
+
+      await exportModule.doExport(saveURI(), document)
+      expect(createWorkspaceProxyServer).toHaveBeenCalledWith(fileWorkspace)
+    })
+
+    it('does not open workspace proxy server if the document is untitled', async () => {
+      const untitledDocument: any = {
+        uri: { scheme: 'untitled', path: 'untitled.md', fsPath: 'untitled.md' },
+        isUntitled: true,
+        getText: () => '',
+      }
+
+      await exportModule.doExport(saveURI(), untitledDocument)
+      expect(createWorkspaceProxyServer).not.toHaveBeenCalled()
+    })
+  })
+
   describe('when enabled markdown.marp.pdf.noteAnnotations', () => {
     beforeEach(() => {
       setConfiguration({ 'markdown.marp.pdf.noteAnnotations': true })