Repository: marp-team/marp-vscode (ts)

Issue:
Outline view : option to have the native markdown outline
Supporting slide pages in the Outline view is sometimes great, but for long presentation, structured with markdown titles, i think it's inefficient and it would be better to have the option to go back to the native outline view in VS Code.
Thanks again for your great app!

Pull request description:
Add `markdown.marp.outlineExtension` setting
Added the setting to enable or disable the extension for outline view.

Close #212.

### ToDo

- [x] Add test case
- [x] Update documentation

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index de7339e..6d04e2e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,8 @@
 
 ### Added
 
-- Set up automated publication into Open VSX ([#211](https://github.com/marp-team/marp-vscode/issues/211), [#218](https://github.com/marp-team/marp-vscode/pull/218))
+- Set up automated publication into [Open VSX](https://open-vsx.org/extension/marp-team/marp-vscode) ([#211](https://github.com/marp-team/marp-vscode/issues/211), [#218](https://github.com/marp-team/marp-vscode/pull/218))
+- `markdown.marp.outlineExtension` preference to enable or disable the outline extension ([#212](https://github.com/marp-team/marp-vscode/issues/212), [#221](https://github.com/marp-team/marp-vscode/pull/221))
 
 ### Changed
 
diff --git a/README.md b/README.md
index 2f9fe29..2ddc597 100644
--- a/README.md
+++ b/README.md
@@ -113,9 +113,13 @@ Markdown preview will reload updated theme CSS automatically when you edited the
   <img src="https://raw.githubusercontent.com/marp-team/marp-vscode/main/docs/custom-theme.gif" alt="Use custom theme" width="600" />
 </p>
 
-### Outline view for each slide
+### Outline extension
 
-We extend [a native outline view](https://code.visualstudio.com/docs/languages/markdown#_outline-view) to support slide pages in Marp Markdown.
+When Marp Markdown is enabled, you can use the extended [outline view](https://code.visualstudio.com/docs/languages/markdown#_outline-view) like following. They are enabled by default but you may disable by `markdown.marp.outlineExtension` preference.
+
+#### Outline view for each slide
+
+We extend the outline view to support slide pages in Marp Markdown.
 
 <p align="center">
   <img src="https://raw.githubusercontent.com/marp-team/marp-vscode/main/docs/outline.png" alt="Outline view for each slide" width="400" />
@@ -123,7 +127,7 @@ We extend [a native outline view](https://code.visualstudio.com/docs/languages/m
 
 > ℹ️ Please choose `Sort By: Position` from context menu of its panel if you see incorrect slide order.
 
-### Slide folding in editor
+#### Slide folding in editor
 
 You can fold the content of slide in editor while editing Marp Markdown.
 
diff --git a/package.json b/package.json
index 2f33928..ef6dc2c 100644
--- a/package.json
+++ b/package.json
@@ -127,6 +127,11 @@
             "KaTeX (https://katex.org/): The default library in Marp Core"
           ]
         },
+        "markdown.marp.outlineExtension": {
+          "type": "boolean",
+          "default": true,
+          "description": "Enables the outline extension for Marp Markdown. If enabled, VS Code's outline view will reflect slide splitters, and you can fold regions of the slide content in the editor."
+        },
         "markdown.marp.themes": {
           "type": "array",
           "default": [],
diff --git a/src/__mocks__/vscode.ts b/src/__mocks__/vscode.ts
index ab3c1a5..e5ca7a0 100644
--- a/src/__mocks__/vscode.ts
+++ b/src/__mocks__/vscode.ts
@@ -6,6 +6,7 @@ const defaultConf: MockedConf = {
   'markdown.marp.chromePath': '',
   'markdown.marp.enableHtml': false,
   'markdown.marp.exportType': 'pdf',
+  'markdown.marp.outlineExtension': true,
   'window.zoomLevel': 0,
 }
 
diff --git a/src/extension.ts b/src/extension.ts
index e5ebaa0..dd373fd 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -9,14 +9,15 @@ import diagnostics from './diagnostics/'
 import { marpCoreOptionForPreview, clearMarpCoreOptionCache } from './option'
 import customTheme from './plugins/custom-theme'
 import lineNumber from './plugins/line-number'
-import outline from './plugins/outline'
+import outline, { rule as outlineRule } from './plugins/outline'
 import themes from './themes'
-import { detectMarpFromMarkdown } from './utils'
+import { detectMarpFromMarkdown, marpConfiguration } from './utils'
 
 const shouldRefreshConfs = [
   'markdown.marp.breaks',
   'markdown.marp.enableHtml',
   'markdown.marp.mathTypesetting',
+  'markdown.marp.outlineExtension',
   'markdown.marp.themes',
   'markdown.preview.breaks',
 ]
@@ -52,6 +53,10 @@ export function extendMarkdownIt(md: any) {
         .use(outline)
         .use(lineNumber)
 
+      if (!(marpConfiguration().get<boolean>('outlineExtension') ?? true)) {
+        marp.markdown.disable(outlineRule)
+      }
+
       // Load custom themes
       Promise.all(
         themes.loadStyles(baseFolder).map((p) =>
diff --git a/src/plugins/outline.ts b/src/plugins/outline.ts
index 600b8a9..17badae 100644
--- a/src/plugins/outline.ts
+++ b/src/plugins/outline.ts
@@ -1,5 +1,7 @@
+export const rule = 'marp_vscode_outline'
+
 export default function marpVSCodeOutline(instance) {
-  instance.core.ruler.push('marp_vscode_outline', (state) => {
+  instance.core.ruler.push(rule, (state) => {
     if (state.inlineMode) return
 
     const tokens: any[] = []


Test patch:
diff --git a/src/extension.test.ts b/src/extension.test.ts
index 697d954..f1948a1 100644
--- a/src/extension.test.ts
+++ b/src/extension.test.ts
@@ -287,5 +287,67 @@ describe('#extendMarkdownIt', () => {
         ).not.toContain('@custom theme')
       })
     })
+
+    describe('markdown.marp.outlineExtension', () => {
+      const markdown = dedent`
+        ---
+        marp: true
+        ---
+
+        1
+
+        ---
+
+        2
+      `
+
+      it('adds hidden heading token marked as zero-level if enabled', () => {
+        setConfiguration({ 'markdown.marp.outlineExtension': true })
+
+        const parsed = md().parse(markdown)
+        const hiddenHeadings = parsed.filter(
+          (t) => t.type === 'heading_open' && t.level === 0 && t.hidden
+        )
+
+        expect(hiddenHeadings).toHaveLength(2)
+        expect(hiddenHeadings[0].map[0]).toBe(0)
+        expect(hiddenHeadings[1].map[0]).toBe(6)
+
+        // headingDivider directive
+        const headingDivider = md().parse(dedent`
+          ---
+          marp: true
+          headingDivider: 2
+          ---
+
+          # 1
+
+          ## 2
+
+          ### 3
+
+          ## 4
+        `)
+        const hiddenHeadingDividers = headingDivider.filter(
+          (t) => t.type === 'heading_open' && t.level === 0 && t.hidden
+        )
+
+        expect(hiddenHeadingDividers).toHaveLength(3)
+        expect(hiddenHeadingDividers[0].map[0]).toBe(0)
+        expect(hiddenHeadingDividers[1].map[0]).toBe(7)
+        expect(hiddenHeadingDividers[2].map[0]).toBe(11)
+      })
+
+      it('does not add zero-level heading token if disabled', () => {
+        setConfiguration({ 'markdown.marp.outlineExtension': false })
+
+        const parsed = md().parse(markdown)
+        const hiddenHeadings = parsed.filter(
+          (t) => t.type === 'heading_open' && t.level === 0 && t.hidden
+        )
+
+        expect(hiddenHeadings).toHaveLength(0)
+      })
+    })
   })
 })