openrat-cms

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

movement.min.js (5044B)


      1 import { Pos } from "../line/pos.js"
      2 import { prepareMeasureForLine, measureCharPrepared, wrappedLineExtentChar } from "../measurement/position_measurement.js"
      3 import { getBidiPartAt, getOrder } from "../util/bidi.js"
      4 import { findFirst, lst, skipExtendingChars } from "../util/misc.js"
      5 
      6 function moveCharLogically(line, ch, dir) {
      7   let target = skipExtendingChars(line.text, ch + dir, dir)
      8   return target < 0 || target > line.text.length ? null : target
      9 }
     10 
     11 export function moveLogically(line, start, dir) {
     12   let ch = moveCharLogically(line, start.ch, dir)
     13   return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before")
     14 }
     15 
     16 export function endOfLine(visually, cm, lineObj, lineNo, dir) {
     17   if (visually) {
     18     let order = getOrder(lineObj, cm.doc.direction)
     19     if (order) {
     20       let part = dir < 0 ? lst(order) : order[0]
     21       let moveInStorageOrder = (dir < 0) == (part.level == 1)
     22       let sticky = moveInStorageOrder ? "after" : "before"
     23       let ch
     24       // With a wrapped rtl chunk (possibly spanning multiple bidi parts),
     25       // it could be that the last bidi part is not on the last visual line,
     26       // since visual lines contain content order-consecutive chunks.
     27       // Thus, in rtl, we are looking for the first (content-order) character
     28       // in the rtl chunk that is on the last line (that is, the same line
     29       // as the last (content-order) character).
     30       if (part.level > 0 || cm.doc.direction == "rtl") {
     31         let prep = prepareMeasureForLine(cm, lineObj)
     32         ch = dir < 0 ? lineObj.text.length - 1 : 0
     33         let targetTop = measureCharPrepared(cm, prep, ch).top
     34         ch = findFirst(ch => measureCharPrepared(cm, prep, ch).top == targetTop, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch)
     35         if (sticky == "before") ch = moveCharLogically(lineObj, ch, 1)
     36       } else ch = dir < 0 ? part.to : part.from
     37       return new Pos(lineNo, ch, sticky)
     38     }
     39   }
     40   return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after")
     41 }
     42 
     43 export function moveVisually(cm, line, start, dir) {
     44   let bidi = getOrder(line, cm.doc.direction)
     45   if (!bidi) return moveLogically(line, start, dir)
     46   if (start.ch >= line.text.length) {
     47     start.ch = line.text.length
     48     start.sticky = "before"
     49   } else if (start.ch <= 0) {
     50     start.ch = 0
     51     start.sticky = "after"
     52   }
     53   let partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos]
     54   if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) {
     55     // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines,
     56     // nothing interesting happens.
     57     return moveLogically(line, start, dir)
     58   }
     59 
     60   let mv = (pos, dir) => moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir)
     61   let prep
     62   let getWrappedLineExtent = ch => {
     63     if (!cm.options.lineWrapping) return {begin: 0, end: line.text.length}
     64     prep = prep || prepareMeasureForLine(cm, line)
     65     return wrappedLineExtentChar(cm, line, prep, ch)
     66   }
     67   let wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch)
     68 
     69   if (cm.doc.direction == "rtl" || part.level == 1) {
     70     let moveInStorageOrder = (part.level == 1) == (dir < 0)
     71     let ch = mv(start, moveInStorageOrder ? 1 : -1)
     72     if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) {
     73       // Case 2: We move within an rtl part or in an rtl editor on the same visual line
     74       let sticky = moveInStorageOrder ? "before" : "after"
     75       return new Pos(start.line, ch, sticky)
     76     }
     77   }
     78 
     79   // Case 3: Could not move within this bidi part in this visual line, so leave
     80   // the current bidi part
     81 
     82   let searchInVisualLine = (partPos, dir, wrappedLineExtent) => {
     83     let getRes = (ch, moveInStorageOrder) => moveInStorageOrder
     84       ? new Pos(start.line, mv(ch, 1), "before")
     85       : new Pos(start.line, ch, "after")
     86 
     87     for (; partPos >= 0 && partPos < bidi.length; partPos += dir) {
     88       let part = bidi[partPos]
     89       let moveInStorageOrder = (dir > 0) == (part.level != 1)
     90       let ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1)
     91       if (part.from <= ch && ch < part.to) return getRes(ch, moveInStorageOrder)
     92       ch = moveInStorageOrder ? part.from : mv(part.to, -1)
     93       if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) return getRes(ch, moveInStorageOrder)
     94     }
     95   }
     96 
     97   // Case 3a: Look for other bidi parts on the same visual line
     98   let res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent)
     99   if (res) return res
    100 
    101   // Case 3b: Look for other bidi parts on the next visual line
    102   let nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1)
    103   if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) {
    104     res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh))
    105     if (res) return res
    106   }
    107 
    108   // Case 4: Nowhere to move
    109   return null
    110 }