mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 14:43:46 +01:00
[cm6] Change Emacs commands to visual-line-mode (#12523)
* [cm6] Change Emacs commands to visual-line-mode * [cm6] Change line deletion commands to visual line mode GitOrigin-RevId: 7a4f3d66bec611de410b6c1fbafbfe33b974e37b
This commit is contained in:
committed by
Copybot
parent
e09a5f3adf
commit
4b2cc907e2
@@ -0,0 +1,160 @@
|
||||
import {
|
||||
SelectionRange,
|
||||
EditorSelection,
|
||||
EditorState,
|
||||
Transaction,
|
||||
} from '@codemirror/state'
|
||||
import { Command, EditorView } from '@codemirror/view'
|
||||
|
||||
const getNextLineBoundary = (
|
||||
selection: SelectionRange,
|
||||
forward: boolean,
|
||||
view: EditorView,
|
||||
includeWrappingCharacter = false
|
||||
) => {
|
||||
const newSelection = view.moveToLineBoundary(
|
||||
EditorSelection.cursor(
|
||||
selection.head,
|
||||
1,
|
||||
selection.bidiLevel || undefined,
|
||||
selection.goalColumn
|
||||
),
|
||||
forward
|
||||
)
|
||||
// Adjust to be "before" the simulated line break
|
||||
let offset = 0
|
||||
if (
|
||||
forward &&
|
||||
!includeWrappingCharacter &&
|
||||
view.lineBlockAt(selection.head).to !== newSelection.head
|
||||
) {
|
||||
offset = 1
|
||||
}
|
||||
return EditorSelection.cursor(
|
||||
newSelection.head - offset,
|
||||
selection.assoc,
|
||||
selection.bidiLevel || undefined,
|
||||
newSelection.goalColumn
|
||||
)
|
||||
}
|
||||
|
||||
const changeSelection = (
|
||||
view: EditorView,
|
||||
how: (selection: SelectionRange) => SelectionRange,
|
||||
extend = false
|
||||
) => {
|
||||
view.dispatch({
|
||||
selection: EditorSelection.create(
|
||||
view.state.selection.ranges.map(start => {
|
||||
const newSelection = how(start)
|
||||
const anchor = extend ? start.anchor : newSelection.head
|
||||
return EditorSelection.range(
|
||||
anchor,
|
||||
newSelection.head,
|
||||
newSelection.goalColumn,
|
||||
newSelection.bidiLevel || undefined
|
||||
)
|
||||
}),
|
||||
view.state.selection.mainIndex
|
||||
),
|
||||
scrollIntoView: true,
|
||||
userEvent: 'select',
|
||||
})
|
||||
}
|
||||
|
||||
export const cursorToEndOfVisualLine = (view: EditorView) =>
|
||||
changeSelection(view, range => getNextLineBoundary(range, true, view), false)
|
||||
|
||||
export const selectToEndOfVisualLine = (view: EditorView) =>
|
||||
changeSelection(view, range => getNextLineBoundary(range, true, view), true)
|
||||
|
||||
export const selectRestOfVisualLine = (view: EditorView) =>
|
||||
changeSelection(
|
||||
view,
|
||||
range => getNextLineBoundary(range, true, view, true),
|
||||
true
|
||||
)
|
||||
|
||||
export const cursorToBeginningOfVisualLine = (view: EditorView) =>
|
||||
changeSelection(view, range => getNextLineBoundary(range, false, view), false)
|
||||
|
||||
export const selectToBeginningOfVisualLine = (view: EditorView) =>
|
||||
changeSelection(view, range => getNextLineBoundary(range, false, view), true)
|
||||
|
||||
export const deleteToVisualLineEnd: Command = view =>
|
||||
deleteBy(view, pos => {
|
||||
const lineEnd = getNextLineBoundary(
|
||||
EditorSelection.cursor(pos),
|
||||
true,
|
||||
view,
|
||||
true
|
||||
).to
|
||||
return pos < lineEnd ? lineEnd : Math.min(view.state.doc.length, pos + 1)
|
||||
})
|
||||
|
||||
export const deleteToVisualLineStart: Command = view =>
|
||||
deleteBy(view, pos => {
|
||||
const lineStart = getNextLineBoundary(
|
||||
EditorSelection.cursor(pos),
|
||||
false,
|
||||
view
|
||||
).to
|
||||
return pos > lineStart ? lineStart : Math.max(0, pos - 1)
|
||||
})
|
||||
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* The following definitions are from CodeMirror 6, licensed under the MIT license:
|
||||
* https://github.com/codemirror/commands/blob/main/src/commands.ts
|
||||
*/
|
||||
type CommandTarget = { state: EditorState; dispatch: (tr: Transaction) => void }
|
||||
|
||||
function deleteBy(target: CommandTarget, by: (start: number) => number) {
|
||||
if (target.state.readOnly) return false
|
||||
let event = 'delete.selection',
|
||||
{ state } = target
|
||||
let changes = state.changeByRange(range => {
|
||||
let { from, to } = range
|
||||
if (from == to) {
|
||||
let towards = by(from)
|
||||
if (towards < from) {
|
||||
event = 'delete.backward'
|
||||
towards = skipAtomic(target, towards, false)
|
||||
} else if (towards > from) {
|
||||
event = 'delete.forward'
|
||||
towards = skipAtomic(target, towards, true)
|
||||
}
|
||||
from = Math.min(from, towards)
|
||||
to = Math.max(to, towards)
|
||||
} else {
|
||||
from = skipAtomic(target, from, false)
|
||||
to = skipAtomic(target, to, true)
|
||||
}
|
||||
return from == to
|
||||
? { range }
|
||||
: { changes: { from, to }, range: EditorSelection.cursor(from) }
|
||||
})
|
||||
if (changes.changes.empty) return false
|
||||
target.dispatch(
|
||||
state.update(changes, {
|
||||
scrollIntoView: true,
|
||||
userEvent: event,
|
||||
effects:
|
||||
event == 'delete.selection'
|
||||
? EditorView.announce.of(state.phrase('Selection deleted'))
|
||||
: undefined,
|
||||
})
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
function skipAtomic(target: CommandTarget, pos: number, forward: boolean) {
|
||||
if (target instanceof EditorView)
|
||||
for (let ranges of target.state
|
||||
.facet(EditorView.atomicRanges)
|
||||
.map(f => f(target)))
|
||||
ranges.between(pos, pos, (from, to) => {
|
||||
if (from < pos && to > pos) pos = forward ? to : from
|
||||
})
|
||||
return pos
|
||||
}
|
||||
Reference in New Issue
Block a user