Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Folding Markdown headings #5423

Open
deathaxe opened this issue May 27, 2022 · 2 comments
Open

Folding Markdown headings #5423

deathaxe opened this issue May 27, 2022 · 2 comments

Comments

@deathaxe
Copy link
Collaborator

Description of the bug

A general question about syntax based folding is how to achieve something like folding markdown headings of certain levels via fold buttons.

MarkdownEditing can already fold sections via key bindings. It would be cool to bind those functions to the fold buttons of a heading or achieve same behavior using syntax based folding.

A PoC of a Fold.tmPreferences is attached below, but it seems not to work as expected.

Steps to reproduce

  1. Open ST
  2. Create Packages/Markdown/Fold.tmPreferences using snippet below
  3. Paste test content to new buffer and assign Markdown syntax
  4. try folding sections using fold buttons

Expected behavior

Folding a Heading of leval x should fold everything until a heading of level x or x-n is found.

Seems to work correctly for headings of level 3.

Animation

Actual behavior

Behavior fails for heandings of level x if followed by headings of level x+n | n>0.

See how heading 2 and heading 3 fold as expected, but heading 1 or heading 1.1 don't.

Animation

Sublime Text build number

4133

Operating system & version

Windows 11

(Linux) Desktop environment and/or window manager

No response

Additional information

Markdown/Fold.tmPreferences

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
    <key>scope</key>
    <string>text.html.markdown</string>
    <key>settings</key>
    <dict>
        <key>foldScopes</key>
        <array>
            <dict>
                <key>begin</key>
                <string>markup.heading.1 meta.whitespace.newline</string>
                <key>end</key>
                <string>markup.heading.1 & punctuation.definition.heading</string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.2 meta.whitespace.newline</string>
                <key>end</key>
                <string>(markup.heading.2 | markup.heading.1) & punctuation.definition.heading</string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.3 meta.whitespace.newline</string>
                <key>end</key>
                <string>(markup.heading.3 | markup.heading.2 | markup.heading.1) & punctuation.definition.heading</string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.4 meta.whitespace.newline</string>
                <key>end</key>
                <string>(markup.heading.4 | markup.heading.3 | markup.heading.2 | markup.heading.1) & punctuation.definition.heading</string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.5 meta.whitespace.newline</string>
                <key>end</key>
                <string>(markup.heading.5 | markup.heading.4 | markup.heading.3 | markup.heading.2 | markup.heading.1) & punctuation.definition.heading</string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.6 meta.whitespace.newline</string>
                <key>end</key>
                <string>(markup.heading.6 | markup.heading.5 | markup.heading.4 | markup.heading.3 | markup.heading.2 | markup.heading.1) & punctuation.definition.heading</string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
        </array>
    </dict>
</dict>
</plist>

Test File

# 1 Heading

para

## 1.1 Heading

para

### 1.1.1 Heading

para

## 1.2 Heading

para

### 1.2.1 Heading

para

# 2 Heading

para

# 3 Heading

para

# 4 Heading

para

OpenGL context information

No response

@mnemnion
Copy link

mnemnion commented Aug 5, 2022

I'm glad I found this, I just finished leaving a comment on #101 with this same puzzling behavior.

The explanation seems to be that the region-end detection has no memory, so when it uses (in my syntax) markup.section.3.begin and runs across a markup.section.4.begin, it gets confused and folds everything to the end of the file

This looks like a design flaw with the foldScopes approach, because while it could maintain a stack for a well-behaved system like you and I are trying to describe (nested subsections), the begin and end keys aren't guaranteed to nest, if we have an a scope and a b scope, these can appear as abab, not just the desirable abba.

A proposal would be to have a foldContexts property, which works with contexts. Contexts already work on a stack and it's tractable to set up contexts which mirror a tree structure exactly.

@deathaxe
Copy link
Collaborator Author

deathaxe commented Jun 3, 2023

Here's another attempt to fold markdown headings per level.

Folding a level 2 heading works, if level 3 headings are already folded.

Otherwise excludeTrailingNewlines doesn't apply and the next heading is placed at the same line as the folded heading.

Animation

Fold.tmPreferences

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
    <key>scope</key>
    <string>text.html.markdown</string>
    <key>settings</key>
    <dict>
        <key>foldScopes</key>
        <array>
            <dict>
                <key>begin</key>
                <string>markup.heading.1 meta.whitespace.newline - markup.quote - meta.list</string>
                <key>end</key>
                <string>
                    markup.heading.1 - markup.quote - meta.list
                </string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.2 meta.whitespace.newline - markup.quote - meta.list</string>
                <key>end</key>
                <string>
                    markup.heading.2 - markup.quote - meta.list,
                    markup.heading.1 - markup.quote - meta.list
                </string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.3 meta.whitespace.newline - markup.quote - meta.list</string>
                <key>end</key>
                <string>
                    markup.heading.3 - markup.quote - meta.list,
                    markup.heading.2 - markup.quote - meta.list,
                    markup.heading.1 - markup.quote - meta.list
                </string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.4 meta.whitespace.newline - markup.quote - meta.list</string>
                <key>end</key>
                <string>
                    markup.heading.4 - markup.quote - meta.list,
                    markup.heading.3 - markup.quote - meta.list,
                    markup.heading.2 - markup.quote - meta.list,
                    markup.heading.1 - markup.quote - meta.list
                </string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.5 meta.whitespace.newline - markup.quote - meta.list</string>
                <key>end</key>
                <string>
                    markup.heading.5 - markup.quote - meta.list,
                    markup.heading.4 - markup.quote - meta.list,
                    markup.heading.3 - markup.quote - meta.list,
                    markup.heading.2 - markup.quote - meta.list,
                    markup.heading.1 - markup.quote - meta.list
                </string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>markup.heading.6 meta.whitespace.newline - markup.quote - meta.list</string>
                <key>end</key>
                <string>
                    markup.heading.6 - markup.quote - meta.list,
                    markup.heading.5 - markup.quote - meta.list,
                    markup.heading.4 - markup.quote - meta.list,
                    markup.heading.3 - markup.quote - meta.list,
                    markup.heading.2 - markup.quote - meta.list,
                    markup.heading.1 - markup.quote - meta.list
                </string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
            <dict>
                <key>begin</key>
                <string>meta.code-fence.definition.begin</string>
                <key>end</key>
                <string>meta.code-fence.definition.end</string>
                <key>excludeTrailingNewlines</key>
                <true/>
            </dict>
        </array>
    </dict>
</dict>
</plist>

deathaxe added a commit to sublimehq/Packages that referenced this issue Jul 10, 2023
This commit enables scope based folding of headings and fenced code blocks.

Note: It does not add folding by heading level due to sublimehq/sublime_text#5423
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants