46f5661aac17b006be052053540cb5389f58105ce96eacc7d4d2600915d3e45435d0b242f8fe2955ee88cdb95edcb9f3b49701ad0819e451b65d483fc8bf5f 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import { deleteNearSelection } from "./deleteNearSelection.js"
  2. import { runInOp } from "../display/operations.js"
  3. import { ensureCursorVisible } from "../display/scrolling.js"
  4. import { endOfLine } from "../input/movement.js"
  5. import { clipPos, Pos } from "../line/pos.js"
  6. import { visualLine, visualLineEnd } from "../line/spans.js"
  7. import { getLine, lineNo } from "../line/utils_line.js"
  8. import { Range } from "../model/selection.js"
  9. import { selectAll } from "../model/selection_updates.js"
  10. import { countColumn, sel_dontScroll, sel_move, spaceStr } from "../util/misc.js"
  11. import { getOrder } from "../util/bidi.js"
  12. // Commands are parameter-less actions that can be performed on an
  13. // editor, mostly used for keybindings.
  14. export let commands = {
  15. selectAll: selectAll,
  16. singleSelection: cm => cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll),
  17. killLine: cm => deleteNearSelection(cm, range => {
  18. if (range.empty()) {
  19. let len = getLine(cm.doc, range.head.line).text.length
  20. if (range.head.ch == len && range.head.line < cm.lastLine())
  21. return {from: range.head, to: Pos(range.head.line + 1, 0)}
  22. else
  23. return {from: range.head, to: Pos(range.head.line, len)}
  24. } else {
  25. return {from: range.from(), to: range.to()}
  26. }
  27. }),
  28. deleteLine: cm => deleteNearSelection(cm, range => ({
  29. from: Pos(range.from().line, 0),
  30. to: clipPos(cm.doc, Pos(range.to().line + 1, 0))
  31. })),
  32. delLineLeft: cm => deleteNearSelection(cm, range => ({
  33. from: Pos(range.from().line, 0), to: range.from()
  34. })),
  35. delWrappedLineLeft: cm => deleteNearSelection(cm, range => {
  36. let top = cm.charCoords(range.head, "div").top + 5
  37. let leftPos = cm.coordsChar({left: 0, top: top}, "div")
  38. return {from: leftPos, to: range.from()}
  39. }),
  40. delWrappedLineRight: cm => deleteNearSelection(cm, range => {
  41. let top = cm.charCoords(range.head, "div").top + 5
  42. let rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
  43. return {from: range.from(), to: rightPos }
  44. }),
  45. undo: cm => cm.undo(),
  46. redo: cm => cm.redo(),
  47. undoSelection: cm => cm.undoSelection(),
  48. redoSelection: cm => cm.redoSelection(),
  49. goDocStart: cm => cm.extendSelection(Pos(cm.firstLine(), 0)),
  50. goDocEnd: cm => cm.extendSelection(Pos(cm.lastLine())),
  51. goLineStart: cm => cm.extendSelectionsBy(range => lineStart(cm, range.head.line),
  52. {origin: "+move", bias: 1}
  53. ),
  54. goLineStartSmart: cm => cm.extendSelectionsBy(range => lineStartSmart(cm, range.head),
  55. {origin: "+move", bias: 1}
  56. ),
  57. goLineEnd: cm => cm.extendSelectionsBy(range => lineEnd(cm, range.head.line),
  58. {origin: "+move", bias: -1}
  59. ),
  60. goLineRight: cm => cm.extendSelectionsBy(range => {
  61. let top = cm.cursorCoords(range.head, "div").top + 5
  62. return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
  63. }, sel_move),
  64. goLineLeft: cm => cm.extendSelectionsBy(range => {
  65. let top = cm.cursorCoords(range.head, "div").top + 5
  66. return cm.coordsChar({left: 0, top: top}, "div")
  67. }, sel_move),
  68. goLineLeftSmart: cm => cm.extendSelectionsBy(range => {
  69. let top = cm.cursorCoords(range.head, "div").top + 5
  70. let pos = cm.coordsChar({left: 0, top: top}, "div")
  71. if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head)
  72. return pos
  73. }, sel_move),
  74. goLineUp: cm => cm.moveV(-1, "line"),
  75. goLineDown: cm => cm.moveV(1, "line"),
  76. goPageUp: cm => cm.moveV(-1, "page"),
  77. goPageDown: cm => cm.moveV(1, "page"),
  78. goCharLeft: cm => cm.moveH(-1, "char"),
  79. goCharRight: cm => cm.moveH(1, "char"),
  80. goColumnLeft: cm => cm.moveH(-1, "column"),
  81. goColumnRight: cm => cm.moveH(1, "column"),
  82. goWordLeft: cm => cm.moveH(-1, "word"),
  83. goGroupRight: cm => cm.moveH(1, "group"),
  84. goGroupLeft: cm => cm.moveH(-1, "group"),
  85. goWordRight: cm => cm.moveH(1, "word"),
  86. delCharBefore: cm => cm.deleteH(-1, "codepoint"),
  87. delCharAfter: cm => cm.deleteH(1, "char"),
  88. delWordBefore: cm => cm.deleteH(-1, "word"),
  89. delWordAfter: cm => cm.deleteH(1, "word"),
  90. delGroupBefore: cm => cm.deleteH(-1, "group"),
  91. delGroupAfter: cm => cm.deleteH(1, "group"),
  92. indentAuto: cm => cm.indentSelection("smart"),
  93. indentMore: cm => cm.indentSelection("add"),
  94. indentLess: cm => cm.indentSelection("subtract"),
  95. insertTab: cm => cm.replaceSelection("\t"),
  96. insertSoftTab: cm => {
  97. let spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize
  98. for (let i = 0; i < ranges.length; i++) {
  99. let pos = ranges[i].from()
  100. let col = countColumn(cm.getLine(pos.line), pos.ch, tabSize)
  101. spaces.push(spaceStr(tabSize - col % tabSize))
  102. }
  103. cm.replaceSelections(spaces)
  104. },
  105. defaultTab: cm => {
  106. if (cm.somethingSelected()) cm.indentSelection("add")
  107. else cm.execCommand("insertTab")
  108. },
  109. // Swap the two chars left and right of each selection's head.
  110. // Move cursor behind the two swapped characters afterwards.
  111. //
  112. // Doesn't consider line feeds a character.
  113. // Doesn't scan more than one line above to find a character.
  114. // Doesn't do anything on an empty line.
  115. // Doesn't do anything with non-empty selections.
  116. transposeChars: cm => runInOp(cm, () => {
  117. let ranges = cm.listSelections(), newSel = []
  118. for (let i = 0; i < ranges.length; i++) {
  119. if (!ranges[i].empty()) continue
  120. let cur = ranges[i].head, line = getLine(cm.doc, cur.line).text
  121. if (line) {
  122. if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1)
  123. if (cur.ch > 0) {
  124. cur = new Pos(cur.line, cur.ch + 1)
  125. cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
  126. Pos(cur.line, cur.ch - 2), cur, "+transpose")
  127. } else if (cur.line > cm.doc.first) {
  128. let prev = getLine(cm.doc, cur.line - 1).text
  129. if (prev) {
  130. cur = new Pos(cur.line, 1)
  131. cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
  132. prev.charAt(prev.length - 1),
  133. Pos(cur.line - 1, prev.length - 1), cur, "+transpose")
  134. }
  135. }
  136. }
  137. newSel.push(new Range(cur, cur))
  138. }
  139. cm.setSelections(newSel)
  140. }),
  141. newlineAndIndent: cm => runInOp(cm, () => {
  142. let sels = cm.listSelections()
  143. for (let i = sels.length - 1; i >= 0; i--)
  144. cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input")
  145. sels = cm.listSelections()
  146. for (let i = 0; i < sels.length; i++)
  147. cm.indentLine(sels[i].from().line, null, true)
  148. ensureCursorVisible(cm)
  149. }),
  150. openLine: cm => cm.replaceSelection("\n", "start"),
  151. toggleOverwrite: cm => cm.toggleOverwrite()
  152. }
  153. function lineStart(cm, lineN) {
  154. let line = getLine(cm.doc, lineN)
  155. let visual = visualLine(line)
  156. if (visual != line) lineN = lineNo(visual)
  157. return endOfLine(true, cm, visual, lineN, 1)
  158. }
  159. function lineEnd(cm, lineN) {
  160. let line = getLine(cm.doc, lineN)
  161. let visual = visualLineEnd(line)
  162. if (visual != line) lineN = lineNo(visual)
  163. return endOfLine(true, cm, line, lineN, -1)
  164. }
  165. function lineStartSmart(cm, pos) {
  166. let start = lineStart(cm, pos.line)
  167. let line = getLine(cm.doc, start.line)
  168. let order = getOrder(line, cm.doc.direction)
  169. if (!order || order[0].level == 0) {
  170. let firstNonWS = Math.max(start.ch, line.text.search(/\S/))
  171. let inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch
  172. return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky)
  173. }
  174. return start
  175. }