Skip to content

Commit

Permalink
Implement by-page motion and by-group deletion
Browse files Browse the repository at this point in the history
FEATURE: New commands `movePageUp/Down`, `extendPageUp/Down`, `deleteGroupForward/Backward`.
  • Loading branch information
marijnh committed May 28, 2020
1 parent 2548bf1 commit da87836
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 16 deletions.
20 changes: 16 additions & 4 deletions commands/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@

@moveCharRight

@moveWordLeft
@moveGroupLeft

@moveWordRight
@moveGroupRight

@moveLineUp

@moveLineDown

@movePageUp

@movePageDown

@moveLineStart

@moveLineEnd
Expand All @@ -34,14 +38,18 @@

@extendCharRight

@extendWordLeft
@extendGroupLeft

@extendWordRight
@extendGroupRight

@extendLineUp

@extendLineDown

@extendPageUp

@extendPageDown

@extendLineStart

@extendLineEnd
Expand All @@ -52,6 +60,10 @@

@deleteCharForward

@deleteGroupBackward

@deleteGroupForward

### Indentation

@insertNewlineAndIndent
Expand Down
74 changes: 62 additions & 12 deletions commands/src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {EditorState, StateCommand, EditorSelection, SelectionRange, IndentContext, ChangeSpec} from "@codemirror/next/state"
import {EditorState, StateCommand, EditorSelection, SelectionRange,
IndentContext, ChangeSpec, CharCategory} from "@codemirror/next/state"
import {Text, Line, countColumn} from "@codemirror/next/text"
import {EditorView, Command, Direction} from "@codemirror/next/view"

Expand Down Expand Up @@ -43,7 +44,14 @@ export const moveLineUp: Command = view => moveByLine(view, false)
/// Move the selection one line down.
export const moveLineDown: Command = view => moveByLine(view, true)

// FIXME movePageUp/Down
function moveByPage(view: EditorView, forward: boolean) {
return moveSel(view, range => view.moveVertically(range, forward, view.dom.clientHeight))
}

/// Move the selection one page up.
export const movePageUp: Command = view => moveByPage(view, false)
/// Move the selection one page down.
export const movePageDown: Command = view => moveByPage(view, true)

function moveLineBoundary(view: EditorView, forward: boolean) {
return moveSel(view, range => {
Expand Down Expand Up @@ -93,7 +101,16 @@ function extendByLine(view: EditorView, forward: boolean) {
/// Move the selection head one line up.
export const extendLineUp: Command = view => extendByLine(view, false)
/// Move the selection head one line down.
export const extendLineDown: Command = view => extendByLine(view, false)
export const extendLineDown: Command = view => extendByLine(view, true)

function extendByPage(view: EditorView, forward: boolean) {
return extendSel(view, range => view.moveVertically(range, forward, view.dom.clientHeight))
}

/// Move the selection head one page up.
export const extendPageUp: Command = view => extendByPage(view, false)
/// Move the selection head one page down.
export const extendPageDown: Command = view => extendByPage(view, true)

function extendByLineBoundary(view: EditorView, forward: boolean) {
return extendSel(view, range => {
Expand Down Expand Up @@ -125,12 +142,32 @@ export const selectAll: StateCommand = ({state, dispatch}) => {
return true
}

function deleteText(view: EditorView, forward: boolean) {
function deleteText(view: EditorView, forward: boolean, group: boolean) {
let {state} = view, changes = state.changeByRange(range => {
let {from, to} = range
if (from == to) {
let line = state.doc.lineAt(from), before
if (!forward && from > line.start && from < line.start + 200 &&
if (group) {
let categorize = view.state.charCategorizer(from)
let head = from
for (let cat: CharCategory | null = null;;) {
let next, nextChar
if (head == (forward ? line.end : line.start)) {
if (line.number == (forward ? state.doc.lines : 1)) break
line = state.doc.line(line.number + (forward ? 1 : -1))
next = forward ? line.start : line.end
nextChar = "\n"
} else {
next = line.findClusterBreak(head - line.start, forward) + line.start
nextChar = line.slice(Math.min(head, next) - line.start, Math.max(head, next) - line.start)
}
let nextCat = categorize(nextChar)
if (cat != null && nextCat != cat) break
if (nextCat != CharCategory.Space) cat = nextCat
head = next
}
if (forward) to = head; else from = head
} else if (!forward && from > line.start && from < line.start + 200 &&
!/[^ \t]/.test(before = line.slice(0, from - line.start))) {
if (before[before.length - 1] == "\t") {
from--
Expand All @@ -140,6 +177,8 @@ function deleteText(view: EditorView, forward: boolean) {
}
} else {
let target = line.findClusterBreak(from - line.start, forward) + line.start
if (target == from && line.number != (forward ? state.doc.lines : 0))
target += forward ? 1 : -1
if (forward) to = target; else from = target
}
}
Expand All @@ -152,12 +191,17 @@ function deleteText(view: EditorView, forward: boolean) {
return true
}

/// Delete the character before the cursor (which is the one to left
/// in left-to-right text, but the one to the right in right-to-left
/// text).
export const deleteCharBackward: Command = view => deleteText(view, false)
/// Delete the character after the cursor.
export const deleteCharForward: Command = view => deleteText(view, true)
/// Delete the selection, or, for cursor selections, the character
/// before the cursor.
export const deleteCharBackward: Command = view => deleteText(view, false, false)
/// Delete the selection or the character after the cursor.
export const deleteCharForward: Command = view => deleteText(view, true, false)

/// Delete the selection or backward until the end of the next
/// [group](#view.EditorView.moveByGroup).
export const deleteGroupBackward: Command = view => deleteText(view, false, true)
/// Delete the selection or forward until the end of the next group.
export const deleteGroupForward: Command = view => deleteText(view, true, true)

function indentString(state: EditorState, n: number) {
let result = ""
Expand Down Expand Up @@ -260,7 +304,7 @@ export const indentLess: StateCommand = ({state, dispatch}) => {

/// The default keymap for Linux/Windows/non-Mac platforms. Binds the
/// arrows for cursor motion, shift-arrow for selection extension,
/// ctrl-arrows for by-word motion, home/end for line start/end,
/// ctrl-arrows for by-group motion, home/end for line start/end,
/// ctrl-home/end for document start/end, ctrl-a to select all,
/// backspace/delete for deletion, and enter for newline-and-indent.
export const pcBaseKeymap: {[key: string]: Command} = {
Expand All @@ -276,6 +320,10 @@ export const pcBaseKeymap: {[key: string]: Command} = {
"ArrowDown": moveLineDown,
"Shift-ArrowUp": extendLineUp,
"Shift-ArrowDown": extendLineDown,
"PageUp": movePageUp,
"PageDown": movePageDown,
"Shift-PageUp": extendPageUp,
"Shift-PageDown": extendPageDown,
"Home": moveLineStart,
"End": moveLineEnd,
"Shift-Home": extendLineStart,
Expand All @@ -285,6 +333,8 @@ export const pcBaseKeymap: {[key: string]: Command} = {
"Mod-a": selectAll,
"Backspace": deleteCharBackward,
"Delete": deleteCharForward,
"Mod-Backspace": deleteGroupBackward,
"Mod-Delete": deleteGroupForward,
"Enter": insertNewlineAndIndent
}

Expand Down

0 comments on commit da87836

Please sign in to comment.