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 }