How to update start and end positions in an eventListener?

Hi!

Sorry for my amateur questions again. I want to insert a widget above codeblocks, and when this widget is clicked then it should collapse the codelines below it. If clicked again, then it should uncollapse the lines. I got it working (there is probably a much easier way to do this), but the problem is, that when the code is edited, and more code is added then the end position is increased. When I created my widget, I set the start and end parameters of the codelines, which should be collapsed.
So, my question is:

  • How can I update these start and end position if the code was edited, so it matches the current content?
  • When enabling the plugin, how can I immediately collapse specific blocks?
  • Is it possible to combine CodeblockHeader and markField?

Thanks in advance!

Here is my code:

export const CodeblockHeader = StateField.define<DecorationSet>({
  create(state): DecorationSet {
    return Decoration.none;
  },
  update(oldState: DecorationSet, transaction: Transaction): DecorationSet {
    const builder = new RangeSetBuilder<Decoration>();
    let CollapseStart = null;
    let CollapseEnd = null;
    let WidgetStart;
    syntaxTree(transaction.state).iterate({
      enter(node) {
        const lineText = transaction.state.doc.slice(node.from, node.to).toString();
        if (node.type.id === 3 ) {
          CollapseStart = node.from
          WidgetStart = node.from;
        }
        if (node.type.id === 6 ) {
          CollapseEnd = node.to;
          if (CollapseStart != null && CollapseEnd!= null ){
            builder.add(WidgetStart, WidgetStart, Decoration.widget({
              widget: new TextAboveCodeblockWidget("Testing", CollapseStart, CollapseEnd),
              block: true
              })
            );// builder          
            CollapseStart = null;
            CollapseEnd = null;
          }
        }
      },// enter
    });// syntaxTree 
    return builder.finish();
  },// update
  provide(field: StateField<DecorationSet>): Extension {
    return EditorView.decorations.from(field);
  },// provide
});// CodeblockHeader

const Collapse = StateEffect.define(), UnCollapse = StateEffect.define()

const markField = StateField.define({
  create() { return Decoration.none },
  update(value, tr) {
    value = value.map(tr.changes)
    for (let effect of tr.effects) {
      if (effect.is(Collapse)) value = value.update({add: effect.value, sort: true})
      else if (effect.is(UnCollapse)) value = value.update({filter: effect.value})
    }
    return value
  },
  provide: f => EditorView.decorations.from(f)
})

const doFold = Decoration.replace({block: true})

class TextAboveCodeblockWidget extends WidgetType {
  constructor(private text: string,private CollapseStart: number, private CollapseEnd: number, private isCollapsed: boolean) {
    super();
     this.folded = isCollapsed;
  }
  
  eq(other: TextAboveCodeblockWidget) { return other.folded == this.folded }
    
  toDOM(view: EditorView): HTMLElement {
    const container = document.createElement("div");
    container.classList.add("codeblock-header-container");
    container.addEventListener('click', function() {
      if (this.folded) {
        console.log("UnCollapse");
        view.dispatch({
          effects: UnCollapse.of((from, to) => to <= this.CollapseStart || from >= this.CollapseEnd)
        })
      } else {
        console.log("Collapse");
        view.dispatch({
          effects: Collapse.of([doFold.range(this.CollapseStart, this.CollapseEnd)])
        })
      }
      this.folded = !this.folded;
    }.bind(this));
    //});
    
    const span = document.createElement("div");
    span.innerText = this.text;
    span.classList.add("codeblock-header-text");
    
    const line = document.createElement("hr");
    line.classList.add("codeblock-header-line");
    line.style.border = '1px solid #46cced';
    
    container.appendChild(span);
    container.appendChild(line);
    
    return container;
  }
  
  ignoreEvent() { return false }
  
}// TextAboveCodeblockWidget

export default class LineNumberingPlugin extends Plugin  {
  async onload() {    
    this.registerEditorExtension(CodeblockHeader);
    this.registerEditorExtension(markField);
    console.log("Plugin is registered");
  }
}

I figured out the first question. If I create a Viewplugin, I can set up an Eventhandler there, and from there I can dispatch the correct values.

The easiest way to do this is probably to map the range set holding your decorations through update.changes in your plugin’s update method.

Thank you for your help! I figured it out. I didn’t do it the way you suggested, because I didn’t really understand, but it works now. Thank you!