codemirror-wrapped-line-indent
Version:
A plugin for CodeMirror to handle wrapped line indentation
104 lines (103 loc) • 3.81 kB
JavaScript
import { getIndentUnit } from "@codemirror/language";
import { RangeSetBuilder } from "@codemirror/state";
import { Decoration, ViewPlugin, } from "@codemirror/view";
class WrappedLineIndent {
constructor(view) {
this.view = view;
this.indentUnit = getIndentUnit(view.state);
this.initialPaddingLeft = null;
this.isChrome = window === null || window === void 0 ? void 0 : window.navigator.userAgent.includes("Chrome");
this.generate(view.state);
}
update(update) {
const indentUnit = getIndentUnit(update.state);
if (indentUnit !== this.indentUnit ||
update.docChanged ||
update.viewportChanged) {
this.indentUnit = indentUnit;
this.generate(update.state);
}
}
generate(state) {
const builder = new RangeSetBuilder();
if (this.initialPaddingLeft) {
this.addStyleToBuilder(builder, state, this.initialPaddingLeft);
}
else {
this.view.requestMeasure({
read: (view) => {
const lineElement = view.contentDOM.querySelector(".cm-line");
if (lineElement) {
this.initialPaddingLeft = window
.getComputedStyle(lineElement)
.getPropertyValue("padding-left");
this.addStyleToBuilder(builder, view.state, this.initialPaddingLeft);
}
this.decorations = builder.finish();
},
});
}
this.decorations = builder.finish();
}
addStyleToBuilder(builder, state, initialPaddingLeft) {
const visibleLines = this.getVisibleLines(state);
for (const line of visibleLines) {
const { numColumns, containsTab } = this.numColumns(line.text, state.tabSize);
const paddingValue = `calc(${numColumns + this.indentUnit}ch + ${initialPaddingLeft})`;
const textIndentValue = this.isChrome
? `calc(-${numColumns + this.indentUnit}ch - ${containsTab ? 1 : 0}px)`
: `-${numColumns + this.indentUnit}ch`;
builder.add(line.from, line.from, Decoration.line({
attributes: {
style: `padding-left: ${paddingValue}; text-indent: ${textIndentValue};`,
},
}));
}
}
// Get all lines that are currently visible in the viewport.
getVisibleLines(state) {
const lines = new Set();
let lastLine = null;
for (const { from, to } of this.view.visibleRanges) {
let pos = from;
while (pos <= to) {
const line = state.doc.lineAt(pos);
if (lastLine !== line) {
lines.add(line);
lastLine = line;
}
pos = line.to + 1;
}
}
return lines;
}
numColumns(str, tabSize) {
let cols = 0;
let containsTab = false;
loop: for (let i = 0; i < str.length; i++) {
switch (str[i]) {
case " ": {
cols += 1;
continue loop;
}
case "\t": {
cols += tabSize - (cols % tabSize);
containsTab = true;
continue loop;
}
case "\r": {
continue loop;
}
default: {
break loop;
}
}
}
return { numColumns: cols, containsTab };
}
}
export const wrappedLineIndent = [
ViewPlugin.fromClass(WrappedLineIndent, {
decorations: (v) => v.decorations,
}),
];