Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions frontend/components/CellInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { markdown, html as htmlLang, javascript, sqlLang, python, julia_mixed }
import { julia } from "../imports/CodemirrorPlutoSetup.js"
import { pluto_autocomplete } from "./CellInput/pluto_autocomplete.js"
import { NotebookpackagesFacet, pkgBubblePlugin } from "./CellInput/pkg_bubble_plugin.js"
import { awesome_line_wrapping, get_start_tabs } from "./CellInput/awesome_line_wrapping.js"
import { ARBITRARY_INDENT_LINE_WRAP_LIMIT, awesome_line_wrapping, get_leading_indent } from "./CellInput/awesome_line_wrapping.js"
import { cell_movement_plugin, prevent_holding_a_key_from_doing_things_across_cells } from "./CellInput/cell_movement_plugin.js"
import { pluto_paste_plugin } from "./CellInput/pluto_paste_plugin.js"
import { bracketMatching } from "./CellInput/block_matcher_plugin.js"
Expand Down Expand Up @@ -1130,17 +1130,29 @@ const InputContextMenuItem = ({ contents, title, onClick, setOpen, tag }) =>
</button>
</li>`

const generate_fake_deco_indent_text = (width) => {
const tab_size = 4
const max_indent_ch = ARBITRARY_INDENT_LINE_WRAP_LIMIT * tab_size
if (width <= max_indent_ch) return " ".repeat(width)

const left = width - max_indent_ch
return " ".repeat(max_indent_ch) + "⇥ ".repeat(Math.floor(left / 4)) + " ".repeat(left % 4)
}

const StaticCodeMirrorFaker = ({ value }) => {
const tab_size = 4
const lines = value.split("\n").map((line, i) => {
const start_tabs = get_start_tabs(line)
const { text: indent_text, width: indent_width } = get_leading_indent(line, tab_size)
const max_indent_ch = ARBITRARY_INDENT_LINE_WRAP_LIMIT * tab_size
const offset = Math.min(indent_width, max_indent_ch)

const tabbed_line =
start_tabs.length == 0
indent_text.length == 0
? line
: html`<span class="awesome-wrapping-plugin-the-tabs"><span class="ͼo">${start_tabs}</span></span
>${line.substring(start_tabs.length)}`
: html`<span class="awesome-wrapping-plugin-the-tabs"><span class="ͼo">${generate_fake_deco_indent_text(indent_width)}</span></span
>${line.substring(indent_text.length)}`

return html`<div class="awesome-wrapping-plugin-the-line cm-line" style="--indented: ${4 * start_tabs.length}ch;">
return html`<div class="awesome-wrapping-plugin-the-line cm-line" style="--indented: ${offset}ch;">
${line.length === 0 ? html`<br />` : tabbed_line}
</div>`
})
Expand Down
60 changes: 41 additions & 19 deletions frontend/components/CellInput/awesome_line_wrapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@ import { StateField, EditorView, Decoration } from "../../imports/CodemirrorPlut
import { ReactWidget } from "./ReactWidget.js"
import { html } from "../../imports/Preact.js"

const ARBITRARY_INDENT_LINE_WRAP_LIMIT = 12
export const ARBITRARY_INDENT_LINE_WRAP_LIMIT = 5

export const get_start_tabs = (line) => /^\t*/.exec(line)?.[0] ?? ""
export const get_leading_indent = (line, tabSize) => {
const text = /^[\t ]*/.exec(line)?.[0] ?? ""
let width = 0
for (const c of text) width += c === "\t" ? tabSize : 1
return { text, width }
}

const get_decorations = (/** @type {import("../../imports/CodemirrorPlutoSetup.js").EditorState} */ state) => {
let decorations = []
const max_indent_ch = ARBITRARY_INDENT_LINE_WRAP_LIMIT * state.tabSize

// TODO? Don't create new decorations when a line hasn't changed?
for (let i of _.range(0, state.doc.lines)) {
let line = state.doc.line(i + 1)
const num_tabs = get_start_tabs(line.text).length
if (num_tabs === 0) continue
const { text: indent_text, width: indent_width } = get_leading_indent(line.text, state.tabSize)
if (indent_width === 0) continue

const how_much_to_indent = Math.min(num_tabs, ARBITRARY_INDENT_LINE_WRAP_LIMIT)
const offset = how_much_to_indent * state.tabSize
const offset = Math.min(indent_width, max_indent_ch)

const linerwapper = Decoration.line({
attributes: {
Expand All @@ -28,21 +33,38 @@ const get_decorations = (/** @type {import("../../imports/CodemirrorPlutoSetup.j
// Need to push before the tabs one else codemirror gets madddd
decorations.push(linerwapper.range(line.from, line.from))

if (how_much_to_indent > 0) {
decorations.push(
Decoration.mark({
class: "awesome-wrapping-plugin-the-tabs",
}).range(line.from, line.from + how_much_to_indent)
)
}
if (num_tabs > how_much_to_indent) {
for (let i of _.range(how_much_to_indent, num_tabs)) {
decorations.push(
Decoration.replace({
decorations.push(
Decoration.mark({
class: "awesome-wrapping-plugin-the-tabs",
}).range(line.from, line.from + indent_text.length)
)

// For indent past the cap, replace remaining tabs with a faded ⇥ widget.
if (indent_width > max_indent_ch) {
let acc = 0

for (let j = 0; j < indent_text.length; j++) {
const c = indent_text[j]
const w = c === "\t" ? state.tabSize : 1
if (acc >= max_indent_ch) {
const deco = Decoration.replace({
widget: new ReactWidget(html`<span style=${{ opacity: 0.2 }}>⇥ </span>`),
block: false,
}).range(line.from + i, line.from + i + 1)
)
})

if (c === "\t") {
decorations.push(deco.range(line.from + j, line.from + j + 1))
} else if (c === " ") {
// If 4 spaces are coming up...
if (" ".repeat(state.tabSize) === indent_text.slice(j, j + state.tabSize)) {
// ...then replace with single deco.
decorations.push(deco.range(line.from + j, line.from + j + state.tabSize))
// Skip to next indent unit
j += state.tabSize - 1
}
}
}
acc += w
}
}
}
Expand Down
Loading