How to implement ruler?

Hi. Is there a possibility to add ruler (vertical line) in v6? In v5 it’s possible. Didn’t find anything similar in the documentation.

That’s not part of the library for 6.x, but the way you’d implement it would be something like this:

  • Create a view plugin that adds an absolutely positioned element to view.scrollDOM. Give it some kind of left border so that it’s visible

  • On init, and whenever the plugin gets an update that has the heightChanged flag set, set the element’s left to X * view.defaultCharacterWidth, and its height to view.contentHeight.

Would it be possible to give a few more details?

I assume such a plugin would be passed to the extensions list when initializing CodeMirror. Is there a function that should be used to create the plugin? Also, could you point me to the documentation about how to check the events that trigger a plugin? Thanks!

To help those interested in this feature, here is my basic implementation in TypeScript:

import { EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";

function generateRulerPlugin() {
    let width: number = 0;
    let dom: HTMLDivElement;
    let defaultCharacterWidth: number = 1;

    class RulerPlugin {
        containerDom: HTMLDivElement;
        constructor(view: EditorView) {
            this.containerDom = view.dom.appendChild(document.createElement("div"));
            this.containerDom.style.cssText=`
                position: absolute;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                pointer-events: none;
                overflow: hidden;
            `
            dom = this.containerDom.appendChild(document.createElement("div"));
            dom.style.cssText = `
                position: absolute; 
                border-right: 1px dotted gray; 
                height: 100%; 
                opacity: 0.7;
            `;
            // XXX: This should be equal to the amount of padding on a line.
            // This value should be extracted from CodeMirror rather than hardcoded.
            dom.style.width = "4px";
            defaultCharacterWidth = view.defaultCharacterWidth;
            updateRulerWidth(80);
        }

        update(update: ViewUpdate) {
            defaultCharacterWidth = update.view.defaultCharacterWidth;
            if (update.viewportChanged) {
                updateRulerWidth(width, true);
            }
        }

        destroy() {
            dom.remove();
        }
    }
    
    function updateRulerWidth(newWidth: number, force = false) {
        if ((newWidth !== width || force) && dom) {
            width = newWidth;
            dom.style.left = `${width * defaultCharacterWidth}px`;
        }
    }

    return { rulerPlugin: ViewPlugin.fromClass(RulerPlugin), updateRulerWidth };
}

const { rulerPlugin, updateRulerWidth } = generateRulerPlugin();

Add rulerPlugin to the list of plugins and then call updateRulerWidth when needed to change the ruler width. The only issue I see is that I don’t know how to get the line padding from CodeMirror, so I have hardcoded 4px, which appears to be the default line padding… @marijn is there a way to extract the line padding so that we can compute the exact pixel location of a character?

Not really—the editor doesn’t track horizontal padding. But you should be able to read it from the DOM.

Will there always be an element with the correct amount of padding (even if there are 0 lines of code displayed)? Any suggestions for a reliable query-selector?

It is possible to replace the entire document with a block widget, but that would be very exceptional, and I guess falling back to a default value in that case won’t cause any trouble. Lines have a cm-line class and are children of view.contentDOM.