diff .cms/lib/codemirror/src/edit/commands.js @ 0:78edf6b517a0 draft

24.10
author Coffee CMS <info@coffee-cms.ru>
date Fri, 11 Oct 2024 22:40:23 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.cms/lib/codemirror/src/edit/commands.js	Fri Oct 11 22:40:23 2024 +0000
@@ -0,0 +1,178 @@
+import { deleteNearSelection } from "./deleteNearSelection.js"
+import { runInOp } from "../display/operations.js"
+import { ensureCursorVisible } from "../display/scrolling.js"
+import { endOfLine } from "../input/movement.js"
+import { clipPos, Pos } from "../line/pos.js"
+import { visualLine, visualLineEnd } from "../line/spans.js"
+import { getLine, lineNo } from "../line/utils_line.js"
+import { Range } from "../model/selection.js"
+import { selectAll } from "../model/selection_updates.js"
+import { countColumn, sel_dontScroll, sel_move, spaceStr } from "../util/misc.js"
+import { getOrder } from "../util/bidi.js"
+
+// Commands are parameter-less actions that can be performed on an
+// editor, mostly used for keybindings.
+export let commands = {
+  selectAll: selectAll,
+  singleSelection: cm => cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll),
+  killLine: cm => deleteNearSelection(cm, range => {
+    if (range.empty()) {
+      let len = getLine(cm.doc, range.head.line).text.length
+      if (range.head.ch == len && range.head.line < cm.lastLine())
+        return {from: range.head, to: Pos(range.head.line + 1, 0)}
+      else
+        return {from: range.head, to: Pos(range.head.line, len)}
+    } else {
+      return {from: range.from(), to: range.to()}
+    }
+  }),
+  deleteLine: cm => deleteNearSelection(cm, range => ({
+    from: Pos(range.from().line, 0),
+    to: clipPos(cm.doc, Pos(range.to().line + 1, 0))
+  })),
+  delLineLeft: cm => deleteNearSelection(cm, range => ({
+    from: Pos(range.from().line, 0), to: range.from()
+  })),
+  delWrappedLineLeft: cm => deleteNearSelection(cm, range => {
+    let top = cm.charCoords(range.head, "div").top + 5
+    let leftPos = cm.coordsChar({left: 0, top: top}, "div")
+    return {from: leftPos, to: range.from()}
+  }),
+  delWrappedLineRight: cm => deleteNearSelection(cm, range => {
+    let top = cm.charCoords(range.head, "div").top + 5
+    let rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
+    return {from: range.from(), to: rightPos }
+  }),
+  undo: cm => cm.undo(),
+  redo: cm => cm.redo(),
+  undoSelection: cm => cm.undoSelection(),
+  redoSelection: cm => cm.redoSelection(),
+  goDocStart: cm => cm.extendSelection(Pos(cm.firstLine(), 0)),
+  goDocEnd: cm => cm.extendSelection(Pos(cm.lastLine())),
+  goLineStart: cm => cm.extendSelectionsBy(range => lineStart(cm, range.head.line),
+    {origin: "+move", bias: 1}
+  ),
+  goLineStartSmart: cm => cm.extendSelectionsBy(range => lineStartSmart(cm, range.head),
+    {origin: "+move", bias: 1}
+  ),
+  goLineEnd: cm => cm.extendSelectionsBy(range => lineEnd(cm, range.head.line),
+    {origin: "+move", bias: -1}
+  ),
+  goLineRight: cm => cm.extendSelectionsBy(range => {
+    let top = cm.cursorCoords(range.head, "div").top + 5
+    return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
+  }, sel_move),
+  goLineLeft: cm => cm.extendSelectionsBy(range => {
+    let top = cm.cursorCoords(range.head, "div").top + 5
+    return cm.coordsChar({left: 0, top: top}, "div")
+  }, sel_move),
+  goLineLeftSmart: cm => cm.extendSelectionsBy(range => {
+    let top = cm.cursorCoords(range.head, "div").top + 5
+    let pos = cm.coordsChar({left: 0, top: top}, "div")
+    if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head)
+    return pos
+  }, sel_move),
+  goLineUp: cm => cm.moveV(-1, "line"),
+  goLineDown: cm => cm.moveV(1, "line"),
+  goPageUp: cm => cm.moveV(-1, "page"),
+  goPageDown: cm => cm.moveV(1, "page"),
+  goCharLeft: cm => cm.moveH(-1, "char"),
+  goCharRight: cm => cm.moveH(1, "char"),
+  goColumnLeft: cm => cm.moveH(-1, "column"),
+  goColumnRight: cm => cm.moveH(1, "column"),
+  goWordLeft: cm => cm.moveH(-1, "word"),
+  goGroupRight: cm => cm.moveH(1, "group"),
+  goGroupLeft: cm => cm.moveH(-1, "group"),
+  goWordRight: cm => cm.moveH(1, "word"),
+  delCharBefore: cm => cm.deleteH(-1, "codepoint"),
+  delCharAfter: cm => cm.deleteH(1, "char"),
+  delWordBefore: cm => cm.deleteH(-1, "word"),
+  delWordAfter: cm => cm.deleteH(1, "word"),
+  delGroupBefore: cm => cm.deleteH(-1, "group"),
+  delGroupAfter: cm => cm.deleteH(1, "group"),
+  indentAuto: cm => cm.indentSelection("smart"),
+  indentMore: cm => cm.indentSelection("add"),
+  indentLess: cm => cm.indentSelection("subtract"),
+  insertTab: cm => cm.replaceSelection("\t"),
+  insertSoftTab: cm => {
+    let spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize
+    for (let i = 0; i < ranges.length; i++) {
+      let pos = ranges[i].from()
+      let col = countColumn(cm.getLine(pos.line), pos.ch, tabSize)
+      spaces.push(spaceStr(tabSize - col % tabSize))
+    }
+    cm.replaceSelections(spaces)
+  },
+  defaultTab: cm => {
+    if (cm.somethingSelected()) cm.indentSelection("add")
+    else cm.execCommand("insertTab")
+  },
+  // Swap the two chars left and right of each selection's head.
+  // Move cursor behind the two swapped characters afterwards.
+  //
+  // Doesn't consider line feeds a character.
+  // Doesn't scan more than one line above to find a character.
+  // Doesn't do anything on an empty line.
+  // Doesn't do anything with non-empty selections.
+  transposeChars: cm => runInOp(cm, () => {
+    let ranges = cm.listSelections(), newSel = []
+    for (let i = 0; i < ranges.length; i++) {
+      if (!ranges[i].empty()) continue
+      let cur = ranges[i].head, line = getLine(cm.doc, cur.line).text
+      if (line) {
+        if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1)
+        if (cur.ch > 0) {
+          cur = new Pos(cur.line, cur.ch + 1)
+          cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
+                          Pos(cur.line, cur.ch - 2), cur, "+transpose")
+        } else if (cur.line > cm.doc.first) {
+          let prev = getLine(cm.doc, cur.line - 1).text
+          if (prev) {
+            cur = new Pos(cur.line, 1)
+            cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
+                            prev.charAt(prev.length - 1),
+                            Pos(cur.line - 1, prev.length - 1), cur, "+transpose")
+          }
+        }
+      }
+      newSel.push(new Range(cur, cur))
+    }
+    cm.setSelections(newSel)
+  }),
+  newlineAndIndent: cm => runInOp(cm, () => {
+    let sels = cm.listSelections()
+    for (let i = sels.length - 1; i >= 0; i--)
+      cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input")
+    sels = cm.listSelections()
+    for (let i = 0; i < sels.length; i++)
+      cm.indentLine(sels[i].from().line, null, true)
+    ensureCursorVisible(cm)
+  }),
+  openLine: cm => cm.replaceSelection("\n", "start"),
+  toggleOverwrite: cm => cm.toggleOverwrite()
+}
+
+
+function lineStart(cm, lineN) {
+  let line = getLine(cm.doc, lineN)
+  let visual = visualLine(line)
+  if (visual != line) lineN = lineNo(visual)
+  return endOfLine(true, cm, visual, lineN, 1)
+}
+function lineEnd(cm, lineN) {
+  let line = getLine(cm.doc, lineN)
+  let visual = visualLineEnd(line)
+  if (visual != line) lineN = lineNo(visual)
+  return endOfLine(true, cm, line, lineN, -1)
+}
+function lineStartSmart(cm, pos) {
+  let start = lineStart(cm, pos.line)
+  let line = getLine(cm.doc, start.line)
+  let order = getOrder(line, cm.doc.direction)
+  if (!order || order[0].level == 0) {
+    let firstNonWS = Math.max(start.ch, line.text.search(/\S/))
+    let inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch
+    return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky)
+  }
+  return start
+}