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