From a5d47d81f6ea17197be1bfb35c95e45b9129d886 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Tue, 24 Jan 2023 16:16:54 +0100 Subject: [PATCH] Add commands for selecting next/previous block/paragraph --- src/editor/block/commands.js | 219 ++++++++++++++++++++--------------- src/editor/keymap.js | 15 ++- 2 files changed, 137 insertions(+), 97 deletions(-) diff --git a/src/editor/block/commands.js b/src/editor/block/commands.js index d0ccd6f..28d116b 100644 --- a/src/editor/block/commands.js +++ b/src/editor/block/commands.js @@ -94,114 +94,141 @@ export function changeCurrentBlockLanguage(state, dispatch, language, auto) { changeLanguageTo(state, dispatch, block, language, auto) } -export function gotoPreviousBlock({state, dispatch}) { +function updateSel(sel, by) { + return EditorSelection.create(sel.ranges.map(by), sel.mainIndex); +} +function setSel(state, selection) { + return state.update({ selection, scrollIntoView: true, userEvent: "select" }); +} +function extendSel(state, dispatch, how) { + let selection = updateSel(state.selection, range => { + let head = how(range); + return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined); + }); + if (selection.eq(state.selection)) + return false; + dispatch(setSel(state, selection)); + return true; +} +function moveSel(state, dispatch, how) { + let selection = updateSel(state.selection, how); + if (selection.eq(state.selection)) + return false; + dispatch(setSel(state, selection)); + return true; +} + +function previousBlock(state, range) { const blocks = state.facet(blockState) - const newSelection = EditorSelection.create(state.selection.ranges.map(sel => { - const block = getNoteBlockFromPos(state, sel.head) - if (sel.head === block.content.from) { - const index = blocks.indexOf(block) - const previousBlockIndex = index > 0 ? index - 1 : 0 - return EditorSelection.cursor(blocks[previousBlockIndex].content.from) - } else { - return EditorSelection.cursor(block.content.from) - } - }), state.selection.mainIndex) - dispatch(state.update({ - selection: newSelection, - scrollIntoView: true, - })) - return true + const block = getNoteBlockFromPos(state, range.head) + if (range.head === block.content.from) { + const index = blocks.indexOf(block) + const previousBlockIndex = index > 0 ? index - 1 : 0 + return EditorSelection.cursor(blocks[previousBlockIndex].content.from) + } else { + return EditorSelection.cursor(block.content.from) + } +} + +function nextBlock(state, range) { + const blocks = state.facet(blockState) + const block = getNoteBlockFromPos(state, range.head) + if (range.head === block.content.to) { + const index = blocks.indexOf(block) + const previousBlockIndex = index < blocks.length - 1 ? index + 1 : index + return EditorSelection.cursor(blocks[previousBlockIndex].content.to) + } else { + return EditorSelection.cursor(block.content.to) + } } export function gotoNextBlock({state, dispatch}) { - const blocks = state.facet(blockState) - const newSelection = EditorSelection.create(state.selection.ranges.map(sel => { - const block = getNoteBlockFromPos(state, sel.head) - if (sel.head === block.content.to) { - const index = blocks.indexOf(block) - const previousBlockIndex = index < blocks.length - 1 ? index + 1 : index - return EditorSelection.cursor(blocks[previousBlockIndex].content.to) - } else { - return EditorSelection.cursor(block.content.to) - } - }), state.selection.mainIndex) - dispatch(state.update({ - selection: newSelection, - scrollIntoView: true, - })) - return true + return moveSel(state, dispatch, range => nextBlock(state, range)) +} +export function selectNextBlock({state, dispatch}) { + return extendSel(state, dispatch, range => nextBlock(state, range)) +} +export function gotoPreviousBlock({state, dispatch}) { + return moveSel(state, dispatch, range => previousBlock(state, range)) +} +export function selectPreviousBlock({state, dispatch}) { + return extendSel(state, dispatch, range => previousBlock(state, range)) } -export function gotoPreviousParagraph({state, dispatch}) { + +function previousParagraph(state, range) { const blocks = state.facet(blockState) - const newSelection = EditorSelection.create(state.selection.ranges.map(sel => { - let block = getNoteBlockFromPos(state, sel.head) - const blockIndex = blocks.indexOf(block) + let block = getNoteBlockFromPos(state, range.head) + const blockIndex = blocks.indexOf(block) - let seenContentLine = false - let pos - // if we're on the first row of a block, and it's not the first block, we start from the end of the previous block - if (state.doc.lineAt(sel.head).from === block.content.from && blockIndex > 0) { - block = blocks[blockIndex - 1] - pos = state.doc.lineAt(block.content.to).from - } else { - pos = state.doc.lineAt(sel.head).from - } + let seenContentLine = false + let pos + // if we're on the first row of a block, and it's not the first block, we start from the end of the previous block + if (state.doc.lineAt(range.head).from === block.content.from && blockIndex > 0) { + block = blocks[blockIndex - 1] + pos = state.doc.lineAt(block.content.to).from + } else { + pos = state.doc.lineAt(range.head).from + } - while (pos > block.content.from) { - const line = state.doc.lineAt(pos) - if (line.text.replace(/\s/g, '').length == 0) { - if (seenContentLine) { - return EditorSelection.cursor(line.from) - } - } else { - seenContentLine = true + while (pos > block.content.from) { + const line = state.doc.lineAt(pos) + if (line.text.replace(/\s/g, '').length == 0) { + if (seenContentLine) { + return EditorSelection.cursor(line.from) } - // set position to beginning go previous line - pos = state.doc.lineAt(line.from - 1).from + } else { + seenContentLine = true } - return EditorSelection.cursor(block.content.from) - }), state.selection.mainIndex) - dispatch(state.update({ - selection: newSelection, - scrollIntoView: true, - })) - return true + // set position to beginning go previous line + pos = state.doc.lineAt(line.from - 1).from + } + return EditorSelection.cursor(block.content.from) +} + + +function nextParagraph(state, range) { + const blocks = state.facet(blockState) + let block = getNoteBlockFromPos(state, range.head) + const blockIndex = blocks.indexOf(block) + + let seenContentLine = false + let pos + // if we're at the last line of a block, and it's not the last block, we start from the beginning of the next block + if (state.doc.lineAt(range.head).to === block.content.to && blockIndex < blocks.length - 1) { + block = blocks[blockIndex + 1] + pos = state.doc.lineAt(block.content.from).to + } else { + pos = state.doc.lineAt(range.head).to + } + + while (pos < block.content.to) { + const line = state.doc.lineAt(pos) + if (line.text.replace(/\s/g, '').length == 0) { + if (seenContentLine) { + return EditorSelection.cursor(line.from) + } + } else { + seenContentLine = true + } + // set position to beginning go previous line + pos = state.doc.lineAt(line.to + 1).to + } + return EditorSelection.cursor(block.content.to) } export function gotoNextParagraph({state, dispatch}) { - const blocks = state.facet(blockState) - const newSelection = EditorSelection.create(state.selection.ranges.map(sel => { - let block = getNoteBlockFromPos(state, sel.head) - const blockIndex = blocks.indexOf(block) - - let seenContentLine = false - let pos - // if we're at the last line of a block, and it's not the last block, we start from the beginning of the next block - if (state.doc.lineAt(sel.head).to === block.content.to && blockIndex < blocks.length - 1) { - block = blocks[blockIndex + 1] - pos = state.doc.lineAt(block.content.from).to - } else { - pos = state.doc.lineAt(sel.head).to - } - - while (pos < block.content.to) { - const line = state.doc.lineAt(pos) - if (line.text.replace(/\s/g, '').length == 0) { - if (seenContentLine) { - return EditorSelection.cursor(line.from) - } - } else { - seenContentLine = true - } - // set position to beginning go previous line - pos = state.doc.lineAt(line.to + 1).to - } - return EditorSelection.cursor(block.content.to) - }), state.selection.mainIndex) - dispatch(state.update({ - selection: newSelection, - scrollIntoView: true, - })) - return true + return moveSel(state, dispatch, range => nextParagraph(state, range)) } + +export function selectNextParagraph({state, dispatch}) { + return extendSel(state, dispatch, range => nextParagraph(state, range)) +} + +export function gotoPreviousParagraph({state, dispatch}) { + return moveSel(state, dispatch, range => previousParagraph(state, range)) +} + +export function selectPreviousParagraph({state, dispatch}) { + return extendSel(state, dispatch, range => previousParagraph(state, range)) +} \ No newline at end of file diff --git a/src/editor/keymap.js b/src/editor/keymap.js index bd197b7..bbfab4b 100644 --- a/src/editor/keymap.js +++ b/src/editor/keymap.js @@ -1,7 +1,16 @@ import { EditorView, keymap } from "@codemirror/view" import { EditorSelection } from "@codemirror/state" import { indentWithTab, insertTab, indentLess, indentMore, undo, redo } from "@codemirror/commands" -import { insertNewBlockAtCursor, addNewBlockAfterCurrent, moveLineUp, moveLineDown, selectAll, gotoPreviousBlock, gotoNextBlock, gotoPreviousParagraph, gotoNextParagraph } from "./block/commands.js"; +import { + insertNewBlockAtCursor, + addNewBlockAfterCurrent, + moveLineUp, moveLineDown, + selectAll, + gotoPreviousBlock, gotoNextBlock, + selectNextBlock, selectPreviousBlock, + gotoPreviousParagraph, gotoNextParagraph, + selectNextParagraph, selectPreviousParagraph, +} from "./block/commands.js"; export function heynoteKeymap(editor) { return keymap.of([ @@ -14,8 +23,12 @@ export function heynoteKeymap(editor) { ["Alt-ArrowDown", moveLineDown], ["Mod-ArrowUp", gotoPreviousBlock], ["Mod-ArrowDown", gotoNextBlock], + ["Mod-Shift-ArrowUp", selectPreviousBlock], + ["Mod-Shift-ArrowDown", selectNextBlock], ["Ctrl-ArrowUp", gotoPreviousParagraph], ["Ctrl-ArrowDown", gotoNextParagraph], + ["Ctrl-Shift-ArrowUp", selectPreviousParagraph], + ["Ctrl-Shift-ArrowDown", selectNextParagraph], ["Mod-l", () => editor.openLanguageSelector()], ].map(([key, run]) => { return {