Skip to content

Commit

Permalink
Almost finished extracting backspace handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaydek Michels-Gualtieri committed Jun 25, 2020
1 parent 3b70aaa commit 4bae799
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 50 deletions.
27 changes: 13 additions & 14 deletions src/Editor/backspace/backspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import * as Spans from "../Spans"
import dropCursors from "./dropCursors"
import must from "lib/must"

// Returns the next right-to-left (RTL) cursor for a
// boundary.
// Returns the next right-to-left cursor for a boundary.
function nextRTLCursor(elements, { ...rtl }, boundary) {
const y = must(elements.findIndex(each => each.key === rtl.key))
const substr = Spans.textContent(elements[y].props.children).slice(0, rtl.offset)
Expand All @@ -16,12 +15,13 @@ function nextRTLCursor(elements, { ...rtl }, boundary) {
})
return rtl
}
const runes = Iterators.RTL[boundary](substr)
rtl.offset -= runes.length
// const runes = Iterators.RTL[boundary](substr) // DEBUG
// rtl.offset -= runes.length // DEBUG
rtl.offset -= Iterators.RTL[boundary](substr).length
return rtl
}

// Returns the next left-to-right (LTR) cursor.
// Returns the next left-to-right cursor for a boundary.
function nextLTRCursor(elements, { ...ltr }, boundary) {
const y = must(elements.findIndex(each => each.key === ltr.key))
const substr = Spans.textContent(elements[y].props.children).slice(ltr.offset)
Expand All @@ -32,13 +32,13 @@ function nextLTRCursor(elements, { ...ltr }, boundary) {
})
return ltr
}
const runes = Iterators.LTR[boundary](substr)
ltr.offset += runes.length
// const runes = Iterators.LTR[boundary](substr) // DEBUG
// ltr.offset += runes.length // DEBUG
ltr.offset += Iterators.LTR[boundary](substr).length
return ltr
}

// Returns the next set of cursors for a direction and a
// boundary.
// Returns the next set of cursors.
function nextCursors(elements, cursors, dir, boundary) {
if (!cursors.collapsed) {
return cursors
Expand All @@ -60,13 +60,12 @@ function nextCursors(elements, cursors, dir, boundary) {
return next
}

// Backspace handler. Returns a collapsed set of cursors.
// dir maps to "rtl" or "ltr" and boundary maps to "rune",
// "word", or "line".
function backspace(elements, cursors, dir, boundary) {
// Backspace handler for both directions and all boundaries.
// Returns the next set of collapsed cursors.
function backspaceHandler(elements, cursors, dir, boundary) {
const next = nextCursors(elements, cursors, dir, boundary)
dropCursors(elements, next)
return Cursors.collapse(next)
}

export default backspace
export default backspaceHandler
107 changes: 78 additions & 29 deletions src/Editor/backspace/dropCursors.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,104 @@
import * as Cursors from "../Cursors"
import * as Spans from "../Spans"
import JSONClone from "lib/JSONClone"
import must from "lib/must"

// Drops up to n-bytes from an array of spans at an offset.
// Returns the number of bytes dropped.
function dropBytes({ spans, offset, nbytes }) {
// Compute the span and character offsets (offset):
// // Returns the span and character offsets for an array of
// // spans at an offset.
// function computeSpanOffsets(spans, offset) {
// const offsets = {
// span: 0,
// char: 0,
// }
//
// let x = 0
// for (; x < spans.length; x++) {
// const children = spans[x].props.children
// if (offset - children.length <= 0) {
// Object.assign(offsets, {
// span: x,
// char: offset,
// })
// return offsets
// }
// offset -= children.length
// }
// return null
// }

// Returns the span and character offsets for an array of
// spans at an offset.
function computeSpanOffsets(spans, offset) {
let x = 0
for (; x < spans.length; x++) {
if (offset - spans[x].props.children.length <= 0) {
// No-op
break
const children = spans[x].props.children
if (offset - children.length <= 0) {
return { span: x, char: offset }
}
offset -= spans[x].props.children.length
offset -= children.length
}
// Drop up to n-bytes:
nbytes = Math.min(nbytes, offset)
spans[x].props.children = (
spans[x].props.children.slice(0, offset - nbytes) +
spans[x].props.children.slice(offset)
return null
}

// Drops a number of characters from an array of spans at an
// offset. Returns the number of characters dropped.
function dropChars(spans, offset, nchars) {
const offsets = must(computeSpanOffsets(spans, offset))
if (nchars > offsets.char) {
nchars = offsets.char
}
spans[offsets.span].props.children = (
spans[offsets.span].props.children.slice(0, offsets.char - nchars) +
spans[offsets.span].props.children.slice(offsets.char)
)
if (!spans[x].props.children) {
spans.splice(x, 1)
if (!spans[offsets.span].props.children) {
spans.splice(offsets.span, 1)
}
Spans.defer(spans)
return nbytes
return nchars
}

// Drops bytes between cursors.
// Counts the number of characters between the offsets of a
// set of cursors.
function nchars(cursors) {
const nchars = 0
if (cursors[0].key === cursors[1].key) {
return cursors[1].offset - cursors[0].offset
}
return cursors[1].offset
}

// Drops the characters between a set of cursors.
function dropCursors(elements, cursors) {
cursors = JSONClone(cursors) // Do not mutate references

let y = must(elements.findIndex(each => each.key === cursors[1].key))
while (!Cursors.areEqual(cursors[0], cursors[1])) {
let nbytes = cursors[1].offset - (cursors[0].key === cursors[1].key && cursors[0].offset)
if (!nbytes && y) {
// Read the current span (for cursors[1].offset):
const textContent = Spans.textContent(elements[y - 1].props.children)
// Push and defer spans:
elements[y - 1].props.children.push(...elements[y].props.children)
elements.splice(y, 1)
if (!cursors[1].offset && y) {
// const offset = Spans.textContent(elements[y - 1].props.children).length // Precompute
// elements[y - 1].props.children.push(...elements.splice(y, 1)[0].props.children)
// Spans.defer(elements[y - 1].props.children)
const offset = Spans.textContent(elements[y - 1].props.children).length
elements.splice(y - 1, 2, {
...elements[y - 1],
props: {
...elements[y - 1].props,
children: [
...elements[y - 1].props.children,
...elements[y].props.children,
],
},
})
Spans.defer(elements[y - 1].props.children)
// Reset cursor[1]:
Object.assign(cursors[1], {
key: elements[y - 1].key,
offset: textContent.length,
offset,
})
y--
continue
}
const ref = { spans: elements[y].props.children, offset: cursors[1].offset, nbytes }
nbytes = dropBytes(ref)
cursors[1].offset -= nbytes
const dropped = dropChars(elements[y].props.children, cursors[1].offset, nchars(cursors))
cursors[1].offset -= dropped
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/Editor/useEditor.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Cursors from "./Cursors"
import * as Elements from "./Elements"
import backspace from "./backspace"
import backspaceHandler from "./backspace"
import must from "lib/must"
import newHashID from "lib/newHashID"
import React from "react"
Expand Down Expand Up @@ -107,31 +107,31 @@ const methods = state => ({

backspaceRTLRune() {
const { elements, cursors } = state
const collapsed = backspace(elements, cursors, "rtl", "rune")
const collapsed = backspaceHandler(elements, cursors, "rtl", "rune")
this.select(collapsed)
this.render()
},
backspaceRTLWord() {
const { elements, cursors } = state
const collapsed = backspace(elements, cursors, "rtl", "word")
const collapsed = backspaceHandler(elements, cursors, "rtl", "word")
this.select(collapsed)
this.render()
},
backspaceRTLLine() {
const { elements, cursors } = state
const collapsed = backspace(elements, cursors, "rtl", "line")
const collapsed = backspaceHandler(elements, cursors, "rtl", "line")
this.select(collapsed)
this.render()
},
backspaceLTRRune() {
const { elements, cursors } = state
const collapsed = backspace(elements, cursors, "ltr", "rune")
const collapsed = backspaceHandler(elements, cursors, "ltr", "rune")
this.select(collapsed)
this.render()
},
backspaceLTRWord() {
const { elements, cursors } = state
const collapsed = backspace(elements, cursors, "ltr", "word")
const collapsed = backspaceHandler(elements, cursors, "ltr", "word")
this.select(collapsed)
this.render()
},
Expand Down
2 changes: 1 addition & 1 deletion src/lib/must/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//
function must(value) {
if (value === undefined || value === false || value === -1 || value === "" || value === null) {
throw new Error(`must: value=${JSON.stringify(value)} must not be undefined, false, -1, "", or null`)
throw new Error(`FIXME: value=${JSON.stringify(value)}`)
}
return value
}
Expand Down

0 comments on commit 4bae799

Please sign in to comment.