openrat-cms

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

selection_updates.min.js (7567B)


      1 import { signalLater } from "../util/operation_group.js"
      2 import { ensureCursorVisible } from "../display/scrolling.js"
      3 import { clipPos, cmp, Pos } from "../line/pos.js"
      4 import { getLine } from "../line/utils_line.js"
      5 import { hasHandler, signal, signalCursorActivity } from "../util/event.js"
      6 import { lst, sel_dontScroll } from "../util/misc.js"
      7 
      8 import { addSelectionToHistory } from "./history.js"
      9 import { normalizeSelection, Range, Selection, simpleSelection } from "./selection.js"
     10 
     11 // The 'scroll' parameter given to many of these indicated whether
     12 // the new cursor position should be scrolled into view after
     13 // modifying the selection.
     14 
     15 // If shift is held or the extend flag is set, extends a range to
     16 // include a given position (and optionally a second position).
     17 // Otherwise, simply returns the range between the given positions.
     18 // Used for cursor motion and such.
     19 export function extendRange(range, head, other, extend) {
     20   if (extend) {
     21     let anchor = range.anchor
     22     if (other) {
     23       let posBefore = cmp(head, anchor) < 0
     24       if (posBefore != (cmp(other, anchor) < 0)) {
     25         anchor = head
     26         head = other
     27       } else if (posBefore != (cmp(head, other) < 0)) {
     28         head = other
     29       }
     30     }
     31     return new Range(anchor, head)
     32   } else {
     33     return new Range(other || head, head)
     34   }
     35 }
     36 
     37 // Extend the primary selection range, discard the rest.
     38 export function extendSelection(doc, head, other, options, extend) {
     39   if (extend == null) extend = doc.cm && (doc.cm.display.shift || doc.extend)
     40   setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options)
     41 }
     42 
     43 // Extend all selections (pos is an array of selections with length
     44 // equal the number of selections)
     45 export function extendSelections(doc, heads, options) {
     46   let out = []
     47   let extend = doc.cm && (doc.cm.display.shift || doc.extend)
     48   for (let i = 0; i < doc.sel.ranges.length; i++)
     49     out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend)
     50   let newSel = normalizeSelection(out, doc.sel.primIndex)
     51   setSelection(doc, newSel, options)
     52 }
     53 
     54 // Updates a single range in the selection.
     55 export function replaceOneSelection(doc, i, range, options) {
     56   let ranges = doc.sel.ranges.slice(0)
     57   ranges[i] = range
     58   setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options)
     59 }
     60 
     61 // Reset the selection to a single range.
     62 export function setSimpleSelection(doc, anchor, head, options) {
     63   setSelection(doc, simpleSelection(anchor, head), options)
     64 }
     65 
     66 // Give beforeSelectionChange handlers a change to influence a
     67 // selection update.
     68 function filterSelectionChange(doc, sel, options) {
     69   let obj = {
     70     ranges: sel.ranges,
     71     update: function(ranges) {
     72       this.ranges = []
     73       for (let i = 0; i < ranges.length; i++)
     74         this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
     75                                    clipPos(doc, ranges[i].head))
     76     },
     77     origin: options && options.origin
     78   }
     79   signal(doc, "beforeSelectionChange", doc, obj)
     80   if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj)
     81   if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1)
     82   else return sel
     83 }
     84 
     85 export function setSelectionReplaceHistory(doc, sel, options) {
     86   let done = doc.history.done, last = lst(done)
     87   if (last && last.ranges) {
     88     done[done.length - 1] = sel
     89     setSelectionNoUndo(doc, sel, options)
     90   } else {
     91     setSelection(doc, sel, options)
     92   }
     93 }
     94 
     95 // Set a new selection.
     96 export function setSelection(doc, sel, options) {
     97   setSelectionNoUndo(doc, sel, options)
     98   addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options)
     99 }
    100 
    101 export function setSelectionNoUndo(doc, sel, options) {
    102   if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
    103     sel = filterSelectionChange(doc, sel, options)
    104 
    105   let bias = options && options.bias ||
    106     (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1)
    107   setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true))
    108 
    109   if (!(options && options.scroll === false) && doc.cm)
    110     ensureCursorVisible(doc.cm)
    111 }
    112 
    113 function setSelectionInner(doc, sel) {
    114   if (sel.equals(doc.sel)) return
    115 
    116   doc.sel = sel
    117 
    118   if (doc.cm) {
    119     doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true
    120     signalCursorActivity(doc.cm)
    121   }
    122   signalLater(doc, "cursorActivity", doc)
    123 }
    124 
    125 // Verify that the selection does not partially select any atomic
    126 // marked ranges.
    127 export function reCheckSelection(doc) {
    128   setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false))
    129 }
    130 
    131 // Return a selection that does not partially select any atomic
    132 // ranges.
    133 function skipAtomicInSelection(doc, sel, bias, mayClear) {
    134   let out
    135   for (let i = 0; i < sel.ranges.length; i++) {
    136     let range = sel.ranges[i]
    137     let old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]
    138     let newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear)
    139     let newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear)
    140     if (out || newAnchor != range.anchor || newHead != range.head) {
    141       if (!out) out = sel.ranges.slice(0, i)
    142       out[i] = new Range(newAnchor, newHead)
    143     }
    144   }
    145   return out ? normalizeSelection(out, sel.primIndex) : sel
    146 }
    147 
    148 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
    149   let line = getLine(doc, pos.line)
    150   if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) {
    151     let sp = line.markedSpans[i], m = sp.marker
    152     if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
    153         (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
    154       if (mayClear) {
    155         signal(m, "beforeCursorEnter")
    156         if (m.explicitlyCleared) {
    157           if (!line.markedSpans) break
    158           else {--i; continue}
    159         }
    160       }
    161       if (!m.atomic) continue
    162 
    163       if (oldPos) {
    164         let near = m.find(dir < 0 ? 1 : -1), diff
    165         if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft)
    166           near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null)
    167         if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
    168           return skipAtomicInner(doc, near, pos, dir, mayClear)
    169       }
    170 
    171       let far = m.find(dir < 0 ? -1 : 1)
    172       if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight)
    173         far = movePos(doc, far, dir, far.line == pos.line ? line : null)
    174       return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null
    175     }
    176   }
    177   return pos
    178 }
    179 
    180 // Ensure a given position is not inside an atomic range.
    181 export function skipAtomic(doc, pos, oldPos, bias, mayClear) {
    182   let dir = bias || 1
    183   let found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
    184       (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
    185       skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
    186       (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true))
    187   if (!found) {
    188     doc.cantEdit = true
    189     return Pos(doc.first, 0)
    190   }
    191   return found
    192 }
    193 
    194 function movePos(doc, pos, dir, line) {
    195   if (dir < 0 && pos.ch == 0) {
    196     if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1))
    197     else return null
    198   } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
    199     if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0)
    200     else return null
    201   } else {
    202     return new Pos(pos.line, pos.ch + dir)
    203   }
    204 }
    205 
    206 export function selectAll(cm) {
    207   cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll)
    208 }