diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fea7a5..ba97155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.7.2 - 2024-02-25 + +### ✨ Introduce new features + +- Support multiple cursors for arrow / shift / ctrl / ctrl-shift + arrow keys + +### 📝 Add or update documentation + +- Add an example using ref + ## 0.7.1 - 2024-02-24 ### ✨ Introduce new features diff --git a/CodeMirror6/CodeMirror6.csproj b/CodeMirror6/CodeMirror6.csproj index 3fad641..68bd8ad 100644 --- a/CodeMirror6/CodeMirror6.csproj +++ b/CodeMirror6/CodeMirror6.csproj @@ -9,7 +9,7 @@ GaelJ.BlazorCodeMirror6 true GaelJ.BlazorCodeMirror6 - 0.7.1 + 0.7.2 true snupkg true diff --git a/CodeMirror6/NodeLib/src/CmColumns.ts b/CodeMirror6/NodeLib/src/CmColumns.ts index 4547248..f6ed22c 100644 --- a/CodeMirror6/NodeLib/src/CmColumns.ts +++ b/CodeMirror6/NodeLib/src/CmColumns.ts @@ -114,11 +114,11 @@ export function columnStylingPlugin(separator: string): Extension { if (e.ctrlKey === true || e.metaKey === true || e.altKey === true || e.shiftKey === true) return if (e.key === "ArrowLeft") { - moveCursors(view, true, separator) + moveCursorsByColumn(view, true, separator) e.preventDefault() } else if (e.key === "ArrowRight") { - moveCursors(view, false, separator) + moveCursorsByColumn(view, false, separator) e.preventDefault() } } @@ -128,12 +128,12 @@ export function columnStylingPlugin(separator: string): Extension { export const getColumnStylingKeymap = (separator: string): KeyBinding[] => [ { key: 'Tab', run: (view) => { - moveCursors(view, false, separator) + moveCursorsByColumn(view, false, separator) insertTabulationAtEndOfDocumentIfSelectionAtEnd(view) return true }}, { key: 'Shift-Tab', run: (view) => { - moveCursors(view, true, separator) + moveCursorsByColumn(view, true, separator) return true }}, ] @@ -167,7 +167,7 @@ export async function columnLintSource(id: string, view: EditorView, separator: } } -function moveCursors(view: EditorView, previous: boolean, separator: string) { +function moveCursorsByColumn(view: EditorView, previous: boolean, separator: string) { const { state } = view const newSelectionRanges: SelectionRange[] = [] for (const range of state.selection.ranges) { diff --git a/CodeMirror6/NodeLib/src/CmKeymap.ts b/CodeMirror6/NodeLib/src/CmKeymap.ts index 311ede3..d5ec61a 100644 --- a/CodeMirror6/NodeLib/src/CmKeymap.ts +++ b/CodeMirror6/NodeLib/src/CmKeymap.ts @@ -1,6 +1,11 @@ import { toggleMarkdownBold, toggleMarkdownItalic } from "./CmCommands" -import { KeyBinding } from '@codemirror/view'; -import { deleteCharBackward, deleteCharForward, deleteGroupBackward, deleteGroupForward } from '@codemirror/commands' +import { KeyBinding, EditorView } from '@codemirror/view' +import { Extension, RangeSetBuilder, Transaction, EditorSelection, SelectionRange, Text } from "@codemirror/state" +import { + deleteCharBackward, deleteCharForward, deleteGroupBackward, deleteGroupForward, + cursorGroupLeft, cursorGroupRight, selectGroupLeft, selectGroupRight, + cursorCharLeft, cursorCharRight, selectCharLeft, selectCharRight, +} from '@codemirror/commands' export const customMarkdownKeymap: KeyBinding[] = [ { key: 'Mod-b', run: toggleMarkdownBold }, // Cmd/Ctrl + B for bold @@ -13,3 +18,86 @@ export const customDeleteKeymap = [ { key: "Mod-Delete", run: deleteGroupForward }, { key: "Mod-Backspace", run: deleteGroupBackward }, ] + +export const customArrowKeymap: KeyBinding[] = [ + { + key: "ArrowLeft", + run: (view) => moveCursorsByCharacter(view, true, false), + shift: (view) => moveCursorsByCharacter(view, true, true), + }, + { + key: "ArrowRight", + run: (view) => moveCursorsByCharacter(view, false, false), + shift: (view) => moveCursorsByCharacter(view, false, true), + }, + { + key: "Mod-ArrowLeft", + run: (view) => moveCursorsByWord(view, true, false), + shift: (view) => moveCursorsByWord(view, true, true), + }, + { + key: "Mod-ArrowRight", + run: (view) => moveCursorsByWord(view, false, false), + shift: (view) => moveCursorsByWord(view, false, true), + }, +] + +function moveCursorsByCharacter(view: EditorView, previous: boolean, headOnly: boolean) { + const { state } = view + const newSelectionRanges: SelectionRange[] = [] + for (const range of state.selection.ranges) { + const offset = previous ? -1 : 1 + const newAnchor = headOnly ? range.anchor : Math.max(Math.min(state.doc.length, range.head + offset), 0) + const newHead = !headOnly ? newAnchor : Math.max(Math.min(state.doc.length, range.head + offset), 0) + newSelectionRanges.push(EditorSelection.range(newAnchor, newHead)) + } + view.dispatch(state.update({ + selection: EditorSelection.create(newSelectionRanges), + scrollIntoView: true, + userEvent: 'input' + })) + return true +} + +function moveCursorsByWord(view: EditorView, previous: boolean, headOnly: boolean): boolean { + const { state } = view + const newSelectionRanges: SelectionRange[] = [] + + for (const range of state.selection.ranges) { + const currentPos = range.head + const wordBoundary = findWordBoundary(state.doc, currentPos, previous, true) + + const newAnchor = headOnly ? range.anchor : wordBoundary + const newHead = !headOnly ? newAnchor : wordBoundary + + newSelectionRanges.push(EditorSelection.range(newAnchor, newHead)) + } + view.dispatch(state.update({ + selection: EditorSelection.create(newSelectionRanges), + scrollIntoView: true, + userEvent: 'input' + })) + return true +} + +function findWordBoundary(doc: Text, pos: number, previous: boolean, firstRun: boolean): number { + if (previous && pos === 0) return 0 + if (!previous && pos === doc.length) return doc.length + if (isWordBoundary(doc, pos) && firstRun) { + pos += previous ? -1 : 1 + return findWordBoundary(doc, pos, previous, false) + } + for (let i = pos; previous ? i >= 0 : i < doc.length; i += (previous ? -1 : 1)) { + if (isWordBoundary(doc, i)) { + return i + } + } + return previous ? 0 : doc.length +} + +function isWordBoundary(doc: Text, pos: number): boolean { + if (pos < 0 || pos >= doc.length) return true + const charBefore = doc.sliceString(pos - 1, pos) + const charAfter = doc.sliceString(pos, pos + 1) + return /\s/.test(charBefore) !== /\s/.test(charAfter) +} diff --git a/CodeMirror6/NodeLib/src/index.ts b/CodeMirror6/NodeLib/src/index.ts index 0eabf39..b4d1c5d 100644 --- a/CodeMirror6/NodeLib/src/index.ts +++ b/CodeMirror6/NodeLib/src/index.ts @@ -61,7 +61,7 @@ import { getColumnStylingKeymap, columnStylingPlugin, columnLintSource, getSepar import { consoleLog } from "./CmLogging" import { createEditorWithId } from "./CmId" import { hyperLink } from './CmHyperlink' -import { customDeleteKeymap } from "./CmKeymap" +import { customArrowKeymap, customDeleteKeymap } from "./CmKeymap" export { csvToMarkdownTable, getCmInstance, cut, copy, paste } @@ -181,6 +181,7 @@ export async function initCodeMirror( ...completionKeymap, ...lintKeymap, ...customDeleteKeymap, + ...customArrowKeymap, ]) ] diff --git a/Examples.BlazorServer/Examples.BlazorServer.csproj b/Examples.BlazorServer/Examples.BlazorServer.csproj index df8cd6a..aeb8832 100644 --- a/Examples.BlazorServer/Examples.BlazorServer.csproj +++ b/Examples.BlazorServer/Examples.BlazorServer.csproj @@ -4,7 +4,7 @@ enable false enable - 0.7.1 + 0.7.2 diff --git a/Examples.BlazorServerInteractive/Examples.BlazorServerInteractive.csproj b/Examples.BlazorServerInteractive/Examples.BlazorServerInteractive.csproj index 5317e9e..fbc9bc4 100644 --- a/Examples.BlazorServerInteractive/Examples.BlazorServerInteractive.csproj +++ b/Examples.BlazorServerInteractive/Examples.BlazorServerInteractive.csproj @@ -4,7 +4,7 @@ enable enable false - 0.7.1 + 0.7.2 diff --git a/Examples.BlazorWasm/Examples.BlazorWasm.csproj b/Examples.BlazorWasm/Examples.BlazorWasm.csproj index 572aec0..3d4547a 100644 --- a/Examples.BlazorWasm/Examples.BlazorWasm.csproj +++ b/Examples.BlazorWasm/Examples.BlazorWasm.csproj @@ -4,7 +4,7 @@ enable enable false - 0.7.1 + 0.7.2 diff --git a/Examples.Common/Example.razor b/Examples.Common/Example.razor index 570f316..9d9410d 100644 --- a/Examples.Common/Example.razor +++ b/Examples.Common/Example.razor @@ -106,7 +106,15 @@ }) >Change local storage key + + enable enable false - 0.7.1 + 0.7.2 diff --git a/NEW_CHANGELOG.md b/NEW_CHANGELOG.md index 82258d9..575b317 100644 --- a/NEW_CHANGELOG.md +++ b/NEW_CHANGELOG.md @@ -1,15 +1,7 @@ ### ✨ Introduce new features -- Add DeleteTrailingWhitespace command -- Export clipboard functions (#144) -- Tab inserts a tabulation when selection is at the end of document, in csv / tsv modes +- Support multiple cursors for arrow / shift / ctrl / ctrl-shift + arrow keys -### 🐛 Fix a bug +### 📝 Add or update documentation -- Fix backspace and delete with multiple cursors (#148) -- Fix multi cursor clipboard operations (#148) -- Fix multiple selections and tab switching in csv / tsv - -### ⬆️ Upgrade dependencies - -- Update js dependencies: (uiw) +- Add an example using ref