openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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 }