An efficient way of decorating all parenthesis in a line

What is the best way of stylizing all parentheses?

I have some objects, which increase the total height of the line in the editor

And it would be nice to automatically stretch () to match the height of the parent DOM element. Should it be a good job for mark decorations? I am afraid of the extra load given by spawning many small widgets.

Thanks

You can either use a MatchDecorator (as in this example), or use the syntax tree to locate the parentheses. You’ll want to find some way to set the height without querying the parent DOM geometry though, to avoid extra layouts and heights getting ‘stuck’ because the parens themselves keep the element high.

1 Like

Thanks!

Then I should probably apply mark-decoration (wrap into a span with a specific class with a defined y-scale).

I tried couple of techniques, and it looks like I anyway have to query the parents height using JS. As a compromise one could do it periodically for groups of matched parens once per n transactions or milliseconds after users input…

If someone is still interested, it turned out to be not too slow.

I mainly check only PointDecorations offsetheight and then adjust the scaling of brackets/parenthesis:

{  
  getBracketDecorations(view) {
    const { doc } = view.state;
    const decorations = [];
    const stack = [];
    const rangeSets = view.state.facet(EditorView.atomicRanges).map((f)=>f(view));
    let height = -1;

    for (let pos = 0; pos < doc.length; pos += 1) {
      const char = doc.sliceString(pos, pos + 1);
      if (char === '(' || char === '[' || char === '{') {
        stack.push({ type: char, from: pos });
      } else if (char === ')' || char === ']' || char === '}') {
        const open = stack.pop();
        if (open && open.type === this.getMatchingBracket(char)) {
            height = -1;
            rangeSets.forEach((rangeSet) => {
            if (rangeSet.size > 0) {
                const cursor = rangeSet.iter();
                let v;
                do {
                  v = cursor.value;
                  if (!v) break;
                  const start = v.widget.visibleValue.pos;
                  const end   = v.widget.visibleValue.pos + v.widget.visibleValue.length;
                  if (start > open.from && end < pos+1 && v.widget.DOMElement) {
                    height = Math.max(height, v.widget.DOMElement.offsetHeight)
                  }
                  cursor.next();
                } while(v);
              }
          });

          if (height > 14) {
            decorations.push(
              Decoration.mark({ attributes: {style: `transform: scaleY(${height/14}); display:inline-block`} }).range(open.from, open.from + 1),
              Decoration.mark({ attributes: {style: `transform: scaleY(${height/14}); display:inline-block`} }).range(pos, pos + 1),
            );
          }
        }
      }
    }

    decorations.sort((a, b) => a.from - b.from || a.startSide - b.startSide);

    return Decoration.set(decorations);
  }

  getMatchingBracket(closingBracket) {
    switch (closingBracket) {
      case ')': return '(';
      case ']': return '[';
      case '}': return '{';
      default: return null;
    }
  }

}

However now there is another issue: scaled glyphs are horrible.

Therefore, I believe I need to recreate them using SVG/CSS magic with ReplacingDecorations instead of MarkDecorations. But this is another story…