mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05: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:
parent
e09a5f3adf
commit
4b2cc907e2
6 changed files with 214 additions and 9 deletions
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -8189,8 +8189,8 @@
|
|||
},
|
||||
"node_modules/@replit/codemirror-emacs": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "git+ssh://git@github.com/overleaf/codemirror-emacs.git#3f235870d4fa83069df855d6588569865c31e220",
|
||||
"integrity": "sha512-Fqnj4HO25LRqlvgS2g/FEP+w2LpmjAN+y+nED2eLk6ezaE8VpFc7DJyd0Or7FqwqonJv1ARMJUDOKQVTkKcEjA==",
|
||||
"resolved": "git+ssh://git@github.com/overleaf/codemirror-emacs.git#cea6eaefe2301bf07e7dec54f028537c3fdc4982",
|
||||
"integrity": "sha512-1dW1RZX6yaZ31N2KqQ7XgYAy44yhXOf3LBZjpoODoVnJzEX5b003mejygoVCrHr6GpjBeInAx7ggx2wRWXiLXA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.2",
|
||||
|
@ -35131,7 +35131,7 @@
|
|||
"@pollyjs/core": "^4.2.1",
|
||||
"@pollyjs/persister-fs": "^4.2.1",
|
||||
"@reach/tabs": "^0.15.0",
|
||||
"@replit/codemirror-emacs": "overleaf/codemirror-emacs#3f235870d4fa83069df855d6588569865c31e220",
|
||||
"@replit/codemirror-emacs": "overleaf/codemirror-emacs#cea6eaefe2301bf07e7dec54f028537c3fdc4982",
|
||||
"@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#1b1f93c0bcd04293aea6986aa2275185b2c56803",
|
||||
"@replit/codemirror-vim": "overleaf/codemirror-vim#77876ac3390f5a6642e04348c0afbe9f0878ec25",
|
||||
"@sentry/browser": "^7.8.1",
|
||||
|
@ -44810,7 +44810,7 @@
|
|||
"@pollyjs/core": "^4.2.1",
|
||||
"@pollyjs/persister-fs": "^4.2.1",
|
||||
"@reach/tabs": "^0.15.0",
|
||||
"@replit/codemirror-emacs": "overleaf/codemirror-emacs#3f235870d4fa83069df855d6588569865c31e220",
|
||||
"@replit/codemirror-emacs": "overleaf/codemirror-emacs#cea6eaefe2301bf07e7dec54f028537c3fdc4982",
|
||||
"@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#1b1f93c0bcd04293aea6986aa2275185b2c56803",
|
||||
"@replit/codemirror-vim": "overleaf/codemirror-vim#77876ac3390f5a6642e04348c0afbe9f0878ec25",
|
||||
"@sentry/browser": "^7.8.1",
|
||||
|
@ -46990,9 +46990,9 @@
|
|||
"integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg=="
|
||||
},
|
||||
"@replit/codemirror-emacs": {
|
||||
"version": "git+ssh://git@github.com/overleaf/codemirror-emacs.git#3f235870d4fa83069df855d6588569865c31e220",
|
||||
"integrity": "sha512-Fqnj4HO25LRqlvgS2g/FEP+w2LpmjAN+y+nED2eLk6ezaE8VpFc7DJyd0Or7FqwqonJv1ARMJUDOKQVTkKcEjA==",
|
||||
"from": "@replit/codemirror-emacs@overleaf/codemirror-emacs#3f235870d4fa83069df855d6588569865c31e220",
|
||||
"version": "git+ssh://git@github.com/overleaf/codemirror-emacs.git#cea6eaefe2301bf07e7dec54f028537c3fdc4982",
|
||||
"integrity": "sha512-1dW1RZX6yaZ31N2KqQ7XgYAy44yhXOf3LBZjpoODoVnJzEX5b003mejygoVCrHr6GpjBeInAx7ggx2wRWXiLXA==",
|
||||
"from": "@replit/codemirror-emacs@overleaf/codemirror-emacs#cea6eaefe2301bf07e7dec54f028537c3fdc4982",
|
||||
"requires": {}
|
||||
},
|
||||
"@replit/codemirror-indentation-markers": {
|
||||
|
|
|
@ -62,6 +62,12 @@ const ignoredDefaultKeybindings = new Set([
|
|||
'Mod-Alt-\\',
|
||||
])
|
||||
|
||||
const ignoredDefaultMacKeybindings = new Set([
|
||||
// We replace these with our custom visual-line versions
|
||||
'Mod-Backspace',
|
||||
'Mod-Delete',
|
||||
])
|
||||
|
||||
const moduleExtensions: Array<() => Extension> = importOverleafModules(
|
||||
'sourceEditorExtensions'
|
||||
).map((item: { import: { extension: Extension } }) => item.import.extension)
|
||||
|
@ -92,7 +98,15 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
|
|||
...defaultKeymap.filter(
|
||||
// We only filter on keys, so if the keybinding doesn't have a key,
|
||||
// allow it
|
||||
item => !item.key || !ignoredDefaultKeybindings.has(item.key)
|
||||
item => {
|
||||
if (item.key && ignoredDefaultKeybindings.has(item.key)) {
|
||||
return false
|
||||
}
|
||||
if (item.mac && ignoredDefaultMacKeybindings.has(item.mac)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
),
|
||||
...historyKeymap,
|
||||
...lintKeymap,
|
||||
|
|
|
@ -8,6 +8,13 @@ import {
|
|||
import { EmacsHandler } from '@replit/codemirror-emacs'
|
||||
import { CodeMirror } from '@replit/codemirror-vim'
|
||||
import { foldCode, toggleFold, unfoldCode } from '@codemirror/language'
|
||||
import {
|
||||
cursorToBeginningOfVisualLine,
|
||||
cursorToEndOfVisualLine,
|
||||
selectRestOfVisualLine,
|
||||
selectToBeginningOfVisualLine,
|
||||
selectToEndOfVisualLine,
|
||||
} from './visual-line-selection'
|
||||
|
||||
const hasNonEmptySelection = (cm: CodeMirror): boolean => {
|
||||
const selections = cm.getSelections()
|
||||
|
@ -132,6 +139,18 @@ const customiseEmacsOnce = () => {
|
|||
})
|
||||
EmacsHandler.bindKey('C-s', 'openSearch')
|
||||
EmacsHandler.bindKey('C-r', 'openSearch')
|
||||
EmacsHandler.bindKey('C-a', {
|
||||
command: 'goOrSelect',
|
||||
args: [cursorToBeginningOfVisualLine, selectToBeginningOfVisualLine],
|
||||
})
|
||||
EmacsHandler.bindKey('C-e', {
|
||||
command: 'goOrSelect',
|
||||
args: [cursorToEndOfVisualLine, selectToEndOfVisualLine],
|
||||
})
|
||||
EmacsHandler.bindKey('C-k', {
|
||||
command: 'killLine',
|
||||
args: selectRestOfVisualLine,
|
||||
})
|
||||
}
|
||||
|
||||
const options = [
|
||||
|
|
|
@ -19,6 +19,10 @@ import { changeCase, duplicateSelection } from '../commands/ranges'
|
|||
import { selectOccurrence } from '../commands/select'
|
||||
import { cloneSelectionVertically } from '../commands/cursor'
|
||||
import { dispatchEditorEvent } from './changes/change-manager'
|
||||
import {
|
||||
deleteToVisualLineEnd,
|
||||
deleteToVisualLineStart,
|
||||
} from './visual-line-selection'
|
||||
|
||||
export const shortcuts = () => {
|
||||
const toggleReviewPanel = () => {
|
||||
|
@ -168,6 +172,14 @@ export const shortcuts = () => {
|
|||
run: cursorSyntaxRight,
|
||||
shift: selectSyntaxRight,
|
||||
},
|
||||
{
|
||||
mac: 'Mod-Backspace',
|
||||
run: deleteToVisualLineStart,
|
||||
},
|
||||
{
|
||||
mac: 'Mod-Delete',
|
||||
run: deleteToVisualLineEnd,
|
||||
},
|
||||
]
|
||||
|
||||
return Prec.high(keymap.of(keyBindings))
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -111,7 +111,7 @@
|
|||
"@pollyjs/core": "^4.2.1",
|
||||
"@pollyjs/persister-fs": "^4.2.1",
|
||||
"@reach/tabs": "^0.15.0",
|
||||
"@replit/codemirror-emacs": "overleaf/codemirror-emacs#3f235870d4fa83069df855d6588569865c31e220",
|
||||
"@replit/codemirror-emacs": "overleaf/codemirror-emacs#cea6eaefe2301bf07e7dec54f028537c3fdc4982",
|
||||
"@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#1b1f93c0bcd04293aea6986aa2275185b2c56803",
|
||||
"@replit/codemirror-vim": "overleaf/codemirror-vim#77876ac3390f5a6642e04348c0afbe9f0878ec25",
|
||||
"@sentry/browser": "^7.8.1",
|
||||
|
|
Loading…
Reference in a new issue