visual-keymap.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { keymap } from '@codemirror/view'
  2. import { EditorSelection, Prec } from '@codemirror/state'
  3. import { ancestorNodeOfType } from '../../utils/tree-query'
  4. import { toggleRanges } from '../../commands/ranges'
  5. import {
  6. getIndentation,
  7. IndentContext,
  8. indentString,
  9. } from '@codemirror/language'
  10. import {
  11. cursorIsAtStartOfListItem,
  12. indentDecrease,
  13. indentIncrease,
  14. } from '../toolbar/commands'
  15. export const visualKeymap = Prec.highest(
  16. keymap.of([
  17. // create a new list item with the same indentation
  18. {
  19. key: 'Enter',
  20. run: view => {
  21. const { state } = view
  22. let handled = false
  23. const changes = state.changeByRange(range => {
  24. if (range.empty) {
  25. const { from } = range
  26. const listNode = ancestorNodeOfType(state, from, 'ListEnvironment')
  27. if (listNode) {
  28. const line = state.doc.lineAt(range.from)
  29. const endLine = state.doc.lineAt(listNode.to)
  30. if (line.number === endLine.number - 1) {
  31. // last item line
  32. if (line.text.trim() === '\\item') {
  33. // no content on this line
  34. // delete this line
  35. const changes = state.changes({
  36. from: line.from,
  37. to: line.to + 1,
  38. insert: '',
  39. })
  40. // the start of the line after the list environment
  41. const range = EditorSelection.cursor(endLine.to + 1).map(
  42. changes
  43. )
  44. handled = true
  45. return { changes, range }
  46. }
  47. }
  48. // create a new list item
  49. const cx = new IndentContext(state)
  50. const columns = getIndentation(cx, from) ?? 0
  51. const indent = indentString(state, columns)
  52. const insert = `\n${indent}\\item `
  53. handled = true
  54. return {
  55. changes: { from, insert },
  56. range: EditorSelection.cursor(from + insert.length),
  57. }
  58. }
  59. const sectioningNode = ancestorNodeOfType(
  60. state,
  61. from,
  62. 'SectioningCommand'
  63. )
  64. if (sectioningNode) {
  65. // jump out of a section heading to the start of the next line
  66. const nextLineNumber = state.doc.lineAt(from).number + 1
  67. if (nextLineNumber <= state.doc.lines) {
  68. const line = state.doc.line(nextLineNumber)
  69. handled = true
  70. return {
  71. range: EditorSelection.cursor(line.from),
  72. }
  73. }
  74. }
  75. }
  76. return { range }
  77. })
  78. if (handled) {
  79. view.dispatch(changes, {
  80. scrollIntoView: true,
  81. userEvent: 'input',
  82. })
  83. }
  84. return handled
  85. },
  86. },
  87. // Increase list indent
  88. {
  89. key: 'Mod-]',
  90. preventDefault: true,
  91. run: indentIncrease,
  92. },
  93. // Decrease list indent
  94. {
  95. key: 'Mod-[',
  96. preventDefault: true,
  97. run: indentDecrease,
  98. },
  99. // Increase list indent
  100. {
  101. key: 'Tab',
  102. preventDefault: true,
  103. run: view =>
  104. cursorIsAtStartOfListItem(view.state) && indentIncrease(view),
  105. },
  106. // Decrease list indent
  107. {
  108. key: 'Shift-Tab',
  109. preventDefault: true,
  110. run: indentDecrease,
  111. },
  112. // Override bolding in RT mode
  113. {
  114. key: 'Ctrl-b',
  115. mac: 'Mod-b',
  116. preventDefault: true,
  117. run: toggleRanges('\\textbf'),
  118. },
  119. {
  120. key: 'Ctrl-i',
  121. mac: 'Mod-i',
  122. preventDefault: true,
  123. run: toggleRanges('\\textit'),
  124. },
  125. ])
  126. )