Folding functions

Please ignore. I somehow managed to miss completely the fold module – apologies

Right, you can fold with foldEffect, and find foldable ranges with foldable from the language package.

Yeah. Sorry for my total ineptitude. I am very new to CodeMirror 6 and I am clumsy at best.

I am actually trying to use foldAll which is part of the API. But… I am having a timing problem.

I need to set the value for my editor. Once the code is viewed, I want it to be totally folded.
So, I am doing this:

  set value (v) {
    const state = this.view.state

    this.view.dispatch({
      changes: { from: 0, to: state.doc.length, insert: v || '' }
    })

   foldAll(this.view)
  }

However, this only folds the first portion of the document (which is quite long).
In order to get it all folded, I have to do this:

  set value (v) {
    const state = this.view.state

    this.view.dispatch({
      changes: { from: 0, to: state.doc.length, insert: v || '' }
    })

    setTimeout(() => {
      foldAll(this.view)
    }, 5000)
  }

Which is obviously NOT a solution – it’s only to show that it’s a timing issue.
I assumed that the changes dispatched would be executed in order, in a somewhat sync fashion. So:

Is this a problem with CodeMirror? OR

How do I apply the folding to the whole document without a timeout?

Thank you!

What you’re seeing is the parsing happening in a background process, which hasn’t finished yet. Folding is based on the parse tree. You could either synchronously force a full parse with ensureSyntaxTree, or wait for updates until syntaxParserRunning returns false.

Thank you Marijn. You must be the most helpful developer on the planet!

I am sorry, I don’t mean to be the guy who wants to be spoon-fed code, and I realise you are busy. But…
How do I use ensureSyntaxTree?

I mean, my aim would be to set the value and then run foldAll() once the tree is fully parsed: From ensureSyntaxTree(), I can’t figure out how to run something once the tree is done: the call doesn’t take a callback and doesn’t seem to return a promise?

Wait you wrote synchronously! Sorry again.
So I tried this:

import { ensureSyntaxTree } from '@codemirror/language'

  set value (v) {
    const state = this.view.state

    this.view.dispatch({
      changes: { from: 0, to: state.doc.length, insert: v || '' }
    })

    ensureSyntaxTree(this.view.state, state.doc.length, 5000)
    foldAll(this.view)
  }

but no luck.

I am not sure listening to updates will work for me, since I need to trigger the folding only when I set the editor to a new value (for example when you navigate onto that particular page in an SPA).

(I tried to figure out how to go this route, and I realise that UpdateListener is the key, but I haven’t managed to figure out how to actually use it).

Oh, right, ensureSyntaxTree won’t actually change the editor state until the next editor update (since it’s immutable), so that doesn’t work straightforwardly.

Yeah, the idea would be to have an update listener that somehow recognizes the ‘right after load, didn’t fold yet, but syntax parser no longer running’ situation, and does the folding (and then sets the ‘after load’ flag to false).

I am afraid that didn’t work either.
My code is very straightforward:


  set value (v) {
    const state = this.view.state

    this.view.dispatch({
      changes: { from: 0, to: state.doc.length, insert: v || '' }
    })

    // This will tell us if the value was just set
    this._valueJustSet = true
  }
...
        this.view = new EditorView({
          state: EditorState.create({
            extensions: [
              ...
              EditorView.updateListener.of(update => this._maybeFoldAll(update))
...
  _maybeFoldAll (update) {
    console.log(`_maybeFoldAll: docChanged: ${update.docChanged}, flags: ${update.flags}, _valueJustSet: ${!!this._valueJustSet}, syntaxTreeAvailable: ${!!syntaxTreeAvailable(this.view.state)}, syntaxParserRunning: ${!!syntaxParserRunning(this.view)}`)
    if (!this._valueJustSet) return
    if (syntaxParserRunning(this.view)) return
    // if (!syntaxTreeAvailable(this.view.state) || ) return
    console.log('Folding!')
    this._valueJustSet = false
    foldAll(this.view)
  }

The result is not what was hoped:

Value just set!
co-codemirror.js:150 _maybeFoldAll: docChanged: false, flags: 14, _valueJustSet: true, syntaxTreeAvailable: false, syntaxParserRunning: true
co-codemirror.js:150 _maybeFoldAll: docChanged: false, flags: 0, _valueJustSet: true, syntaxTreeAvailable: false, syntaxParserRunning: true
co-codemirror.js:150 _maybeFoldAll: docChanged: false, flags: 0, _valueJustSet: true, syntaxTreeAvailable: false, syntaxParserRunning: false
co-codemirror.js:154 Folding!
co-codemirror.js:150 _maybeFoldAll: docChanged: false, flags: 6, _valueJustSet: false, syntaxTreeAvailable: false, syntaxParserRunning: false
4co-codemirror.js:150 _maybeFoldAll: docChanged: false, flags: 4, _valueJustSet: false, syntaxTreeAvailable: false, syntaxParserRunning: false
co-codemirror.js:150 _maybeFoldAll: docChanged: false, flags: 4, _valueJustSet: false, syntaxTreeAvailable: false, syntaxParserRunning: true
co-codemirror.js:150 _maybeFoldAll: docChanged: false, flags: 0, _valueJustSet: false, syntaxTreeAvailable: false, syntaxParserRunning: false
co-codemirror.js:150 _maybeFoldAll: docChanged: false, flags: 0, _valueJustSet: false, syntaxTreeAvailable: true, syntaxParserRunning: false
3co-codemirror.js:150 _maybeFoldAll: docChanged: false, flags: 4, _valueJustSet: false, syntaxTreeAvailable: true, syntaxParserRunning: false

Interestingly, only about three quarters of the file was folded. As you can see, the parser stopped running for a while, and then it restarted. I read in the documentation that the parser can, and will, do that if it’s taking too much time.

I looked everywhere for hints – I even checked if the flags might mean something (although they were just Focus, Height, Viewport, Geometry).

Is there anything, at all, that will say “parsing is done”?

Right, if the document is big enough the parser will just stop doing work below the viewport. Lazy parsing may make foldAll fundamentally unreliable.

@codemirror/language 0.19.9 fixes an issue so that can do view.dispatch({}) after ensureSyntaxTree to flush the new parsing work to the view, making foldAll work, which might address your use case here, though it’s still all somewhat dodgy.

Ah, so this should work now?

set value (v) {
    const state = this.view.state

    this.view.dispatch({
      changes: { from: 0, to: state.doc.length, insert: v || '' }
    })

    ensureSyntaxTree(this.view.state, state.doc.length, 5000)
    foldAll(this.view)
  }

…?

No. Try reading my response a bit more closely.

Oh, you mean actually view.dispatch({}) with the empty object as the argument?

Yes, that’ll create an empty transaction, allowing the state to move forward to the new parse tree.

Thanks for that. I read your answer a little too closely I think! I thought that empty object meant “put the transaction here” – but I was obviously reading too much into it.

(Unrelated, but I will avoid opening a new ticket about it) Did you know that https://replit.com/ uses CodeMirror now? CodeMirror 6 is INCREDIBLE – and if you open two tabs, they will update in real time. Impressive stuff.

Yes! They did some blog posts about it.

I think this is absolutely huge. It’s a really big project, and they are using the cooperative editing – and it works. It feels like Google Docs. It’s insane.

Just letting you know that I can finally reliably open the file with everything folded for the first time.
Thanks for your help.
I think you will see interest in Codemirror exploding in the coming weeks. I followed your blog as you were writing it, I read about your “tearing my hair out” moments (yes, I can relate), and I am just so impressed it’s come along so immensely nicely.

good news! my congratulations!