Making Codemirror 6 respect indent for wrapped lines

Is there an analogous way to handle line-wrapping with indent in CodeMirror6, similar to how it’s done in the previous versions of CodeMirror? (As shown in this demo: CodeMirror: Indented wrapped line demo). What would be the analogous version of “renderLine”?

I made some progress and attempted to do this by applying the same styling changes as in the previous version to each line in the Viewport.

            EditorView.lineWrapping,
            EditorView.updateListener.of(update => {
              const view = update.view
              var charWidth = view.defaultCharacterWidth
              var basePadding = 4
              view.viewportLines(line => {
                // get the element for each line
                const domAtPos = view.domAtPos(line.from);
                
                // get the actual line
                const lineObj = view.state.doc.lineAt(line.from);

                const tabSize = view.state.tabSize;

                // get the offset width
                const off = countColumn(lineObj.text, 0, tabSize) * charWidth;
                
                // apply the styling changes
                domAtPos.node.style.textIndent = "-" + off + "px";
                domAtPos.node.style.paddingLeft = (basePadding + off) + "px";

                console.log(line);
                console.log("tabSize: " + tabSize)
                console.log("charWidth: " + charWidth)
                console.log(off)
              })
            }),

However, this seems to somehow cause the line-wrapping behavior to break completely. Do you have any suggestions?

Never mind, I’ve realized that the countColumn method from Text is not the same as the countColumn method in CodeMirror from the previous version. Using the old countColumn did the trick.

For people coming here from google for indented wrapped lines, I made a rough plugin that does it using a line decorator:

The imports might be wrong because my project doesn’t do normal bundling, let me know if that’s the case.

3 Likes

I managed to retain the indent while doing line-wrapping by replacing the default break-word css prop on the .cm-lineWrapping class:

 ".cm-lineWrapping": {
      wordBreak: "break-all",
  }

(Heyy I work together with Michiel)

We found out that you can use the CSS unit ch so you don’t need to measure the font width. You can find our updated plugin here:

1 Like

@fonsp Thanks for that! I adapted your version to not use any dependencies other than Codemirror itself. I removed the React bit and replaced the Lodash for loops.

Just in case anyone else comes along looking for a similar solution:

Along with a little bit of (S)CSS. In my case I am using indent and active line markers, which both needed to have the indent size offset set back equal to negative the indent size, to account for the margin.

3 Likes

Hi. I came across this thread upon studying the internals of Lezer Playground made by @Michiel. I discovered that the text-indent trick used by all solutions in this thread interferes with the library’s rectangular selection. The left side will grow outside the text when the first line in the rendered viewport is indented.

I could reproduce this issue in Pluto.jl which implemented this feature. Compare the difference when a tab is inserted in the first line:

The unexpected behavior came from the fact that left boundary is calculated from probing the line element’s text-indent, and querySelector select the first rendered line (not always the document’s very first line), which can be seen in the source code (formatted by hand):

let lineElt = content.querySelector(".cm-line"), 
    lineStyle = lineElt && window.getComputedStyle(lineElt)
let leftSide = contentRect.left +
    (lineStyle ? 
     parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent)) : 
     0)

I am thinking if there is some way to work around this.

I think I have made it. First, use ::before to create pseudo-elements with negative margin-left that mimic the effect of text-indent. Second, set border-left on lines instead of margin-left so that active line background still covers the indentation.

Below is a revised version of @Mitcheljager’s code with the demo text copied from here.
See it in action.

1 Like