Summary
The outline panel does not preserve its collapse state. Every time the document body is edited, the entire outline re-expands, discarding whatever the user had folded. For long documents this is disruptive: the outline jumps around, the user loses their place, and they have to re-fold sections repeatedly while writing.
Environment
- Extension:
zaaack.markdown-editor v0.1.21
- IDE: VS Code-compatible desktop IDE (VS Code fork), macOS arm64
- Mode: instant rendering (IR)
- Opened via
workbench.editorAssociations -> markdown-editor.customEditor
Repro
- Open a markdown file with several nested headings.
- Collapse a few outline branches using the arrow toggles.
- Edit any part of the document body (a single character is enough).
- Expected: collapsed branches stay collapsed. Actual: the whole outline re-expands; all previously folded branches are lost.
Root cause
This is a Vditor implementation detail, not something configurable from the extension side. Three facts from the bundled media/dist/main.js:
1. Collapse state lives only in the DOM - there is no persistence. Folding is represented by the CSS class vditor-outline__action--close on the toggle span, plus display:none on the sibling <ul>. The click handler just flips that class:
S.classList.contains("vditor-outline__action--close")
? (remove class, sibling.style.display = "block")
: (add class, sibling.style.display = "none")
2. The outline is rebuilt wholesale on every render. outlineRender replaces the entire subtree:
U.innerHTML = F.HTML2VditorDOM("<p>[ToC]</p>" + R)
Assigning innerHTML destroys every --close class, so all branches necessarily come back expanded.
3. outline.render() is called on high-frequency paths. At least six call sites in the bundle; the one that matters here is in the content-change handler, immediately after HTML2VditorDOM / Md2VditorIRDOM:
The others are in setValue(), insertMD(), preview.render(), and the mode-switch path. So any keystroke that changes rendered content wipes the outline state.
Why this matters
It makes the outline unusable for navigation while writing. The outline's whole purpose is to let you jump between sections of a long document. If it re-expands on every edit, it becomes a constantly reshuffling list rather than a stable map. Users who set markdown-editor.defaultOpenOutline: true - i.e. the ones relying on the outline most - hit this constantly.
It destroys deliberate user state, not just a transient view. Folding is an explicit choice the user made. Losing it silently on every keystroke is worse than never having the feature, because it looks broken rather than absent.
There is no user-side workaround. None of the five existing configuration options (imageSaveFolder, useVscodeThemeColor, showLineNumbers, defaultOpenOutline, customCss) affect this. customCss cannot help either, since folding is driven by JS-applied classes, not static styling.
Suggested fix
Capture the collapsed set before re-rendering and restore it afterwards, keyed by heading text. Heading text is a stable identifier across re-renders, so matching is reliable.
function renderOutlinePreservingState(vditor) {
const outlineEl = vditor.outline.element;
// 1. Snapshot which branches are currently collapsed, keyed by heading text.
const collapsed = new Set();
outlineEl.querySelectorAll('.vditor-outline__action--close').forEach(function (action) {
const span = action.parentElement.querySelector('span:not(.vditor-outline__action)');
if (span) collapsed.add(span.textContent.trim());
});
// 2. Let Vditor rebuild the outline as usual.
vditor.outline.render(vditor);
// 3. Re-apply the collapse state to branches that still exist.
outlineEl.querySelectorAll('li > span[data-target-id]').forEach(function (span) {
const label = span.querySelector('span:not(.vditor-outline__action)');
const sibling = span.nextElementSibling;
if (label && sibling && sibling.tagName === 'UL'
&& collapsed.has(label.textContent.trim())) {
span.querySelector('.vditor-outline__action')
.classList.add('vditor-outline__action--close');
sibling.style.display = 'none';
}
});
}
Then use renderOutlinePreservingState(vditor) at the call sites instead of vditor.outline.render(vditor).
Two notes on edge cases:
- Duplicate heading text. If two sections share the same heading, both would be restored to the same state. Using the heading text plus its depth level as the key would reduce this; a fully robust key would need positional identity, but text + depth covers the common case.
- Renamed headings. A heading edited during the change will not match its old key, so it stays expanded. That is the correct behavior - the user just touched it.
Happy to send a PR if this direction looks acceptable.
Related
microsoft/vscode#197251 ("Outline doesn't remember expand/collapse states after restart/recreate", closed as COMPLETED) fixed the equivalent problem for VS Code's own Outline view. VS Code's native Outline panel is driven by DocumentSymbol and is unaffected by Vditor re-renders, so it currently serves as a workaround - but users who prefer this extension's integrated outline have no equivalent option.
Summary
The outline panel does not preserve its collapse state. Every time the document body is edited, the entire outline re-expands, discarding whatever the user had folded. For long documents this is disruptive: the outline jumps around, the user loses their place, and they have to re-fold sections repeatedly while writing.
Environment
zaaack.markdown-editorv0.1.21workbench.editorAssociations->markdown-editor.customEditorRepro
Root cause
This is a Vditor implementation detail, not something configurable from the extension side. Three facts from the bundled
media/dist/main.js:1. Collapse state lives only in the DOM - there is no persistence. Folding is represented by the CSS class
vditor-outline__action--closeon the toggle span, plusdisplay:noneon the sibling<ul>. The click handler just flips that class:2. The outline is rebuilt wholesale on every render.
outlineRenderreplaces the entire subtree:Assigning
innerHTMLdestroys every--closeclass, so all branches necessarily come back expanded.3.
outline.render()is called on high-frequency paths. At least six call sites in the bundle; the one that matters here is in the content-change handler, immediately afterHTML2VditorDOM/Md2VditorIRDOM:The others are in
setValue(),insertMD(),preview.render(), and the mode-switch path. So any keystroke that changes rendered content wipes the outline state.Why this matters
It makes the outline unusable for navigation while writing. The outline's whole purpose is to let you jump between sections of a long document. If it re-expands on every edit, it becomes a constantly reshuffling list rather than a stable map. Users who set
markdown-editor.defaultOpenOutline: true- i.e. the ones relying on the outline most - hit this constantly.It destroys deliberate user state, not just a transient view. Folding is an explicit choice the user made. Losing it silently on every keystroke is worse than never having the feature, because it looks broken rather than absent.
There is no user-side workaround. None of the five existing configuration options (
imageSaveFolder,useVscodeThemeColor,showLineNumbers,defaultOpenOutline,customCss) affect this.customCsscannot help either, since folding is driven by JS-applied classes, not static styling.Suggested fix
Capture the collapsed set before re-rendering and restore it afterwards, keyed by heading text. Heading text is a stable identifier across re-renders, so matching is reliable.
Then use
renderOutlinePreservingState(vditor)at the call sites instead ofvditor.outline.render(vditor).Two notes on edge cases:
Happy to send a PR if this direction looks acceptable.
Related
microsoft/vscode#197251 ("Outline doesn't remember expand/collapse states after restart/recreate", closed as COMPLETED) fixed the equivalent problem for VS Code's own Outline view. VS Code's native Outline panel is driven by
DocumentSymboland is unaffected by Vditor re-renders, so it currently serves as a workaround - but users who prefer this extension's integrated outline have no equivalent option.