Created
September 25, 2021 20:25
-
-
Save dralletje/058fe51415fe7dbac4709a65c615b52e to your computer and use it in GitHub Desktop.
Codemirror 6 line wrapping that preserves indentation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from "lodash" | |
import { StateEffect, StateField } from "@codemirror/state" | |
import { EditorView, Decoration } from "@codemirror/view" | |
/** | |
* Plugin that makes line wrapping in the editor respect the identation of the line. | |
* It does this by adding a line decoration that adds padding-left (as much as there is indentation), | |
* and adds the same amount as negative "text-indent". The nice thing about text-indent is that it | |
* applies to the initial line of a wrapped line. | |
* | |
* The identation decorations have to happen in a StateField (without access to the editor), | |
* because they change the layout of the text :( The character width I need however, is in the editor... | |
* So I do this ugly hack where I, in `character_width_listener`, I fire an effect that gets picked up | |
* by another StateField (`extra_cycle_character_width`) that saves the character width into state, | |
* so THEN I can add the markers in the decorations statefield. | |
*/ | |
/** @type {any} */ | |
const CharacterWidthEffect = StateEffect.define({}) | |
const extra_cycle_character_width = StateField.define({ | |
create() { | |
return null | |
}, | |
update(value, tr) { | |
for (let effect of tr.effects) { | |
if (effect.is(CharacterWidthEffect)) return effect.value | |
} | |
return value | |
}, | |
}) | |
let character_width_listener = EditorView.updateListener.of((viewupdate) => { | |
let width = viewupdate.view.defaultCharacterWidth | |
let current_width = viewupdate.view.state.field(extra_cycle_character_width, false) | |
if (current_width !== width) { | |
console.log(`current_width:`, current_width) | |
console.log(`width:`, width) | |
current_width = width | |
viewupdate.view.dispatch({ | |
effects: [CharacterWidthEffect.of(current_width)], | |
}) | |
} | |
}) | |
let ARBITRARY_INDENT_LINE_WRAP_LIMIT = 48 | |
let line_wrapping_decorations = StateField.define({ | |
create() { | |
return Decoration.none | |
}, | |
update(deco, tr) { | |
let tabSize = tr.state.tabSize | |
let charWidth = tr.state.field(extra_cycle_character_width, false) | |
if (charWidth == null) return Decoration.none | |
if (!tr.docChanged && deco !== Decoration.none) return deco | |
let decorations = [] | |
// TODO? Only apply to visible lines? Wouldn't that screw stuff up?? | |
// TODO? Don't create new decorations when a line hasn't changed? | |
for (let i of _.range(0, tr.state.doc.lines)) { | |
let line = tr.state.doc.line(i + 1) | |
if (line.length === 0) continue | |
let indented_chars = 0 | |
for (let ch of line.text) { | |
if (ch === "\t") { | |
indented_chars = indented_chars + tabSize | |
} else if (ch === " ") { | |
indented_chars = indented_chars + 1 | |
} else { | |
break | |
} | |
} | |
// let tabs_in_front = Math.min(line.text.match(/^\t*/)[0].length) * tabSize | |
const offset = Math.min(indented_chars, ARBITRARY_INDENT_LINE_WRAP_LIMIT) * charWidth | |
// TODO? Cache the CSSStyleDeclaration? | |
const rules = document.createElement("span").style | |
rules.setProperty("--idented", `${offset}px`) | |
rules.setProperty("text-indent", "calc(-1 * var(--idented) - 1px)") // I have no idea why, but without the - 1px it behaves weirdly periodically | |
rules.setProperty("padding-left", "calc(var(--idented) + var(--cm-left-padding, 4px))") | |
const linerwapper = Decoration.line({ | |
attributes: { style: rules.cssText }, | |
}) | |
decorations.push(linerwapper.range(line.from, line.from)) | |
} | |
return Decoration.set(decorations, true) | |
}, | |
provide: (f) => EditorView.decorations.from(f), | |
}) | |
export let awesome_line_wrapping_plugin = [extra_cycle_character_width, character_width_listener, line_wrapping_decorations] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment