Fix wrong cursor posistion (#10838)

* fix unable to use 'del' to delete the last character
if the cursor is at the beginning of the block

* fix right arrow calculate character length incorrect
if the cursor is at the beginning of the block

* fix moving up or down across blocks may enter the middle of a character

* fix moving up or down inside a block may enter the middle of a character
This commit is contained in:
megayu
2024-01-11 16:22:37 +08:00
committed by GitHub
parent a2bb7e4479
commit c25c432755
6 changed files with 96 additions and 36 deletions

View File

@@ -652,10 +652,10 @@
:visibility "hidden"
:top 0
:left 0}}
(let [content (str content "0")]
(for [[idx c] (map-indexed
vector
(string/split content ""))]
(let [content (str content "0")
graphemes (util/split-graphemes content)
graphemes-char-index (reductions #(+ %1 (count %2)) 0 graphemes)]
(for [[idx c] (zipmap graphemes-char-index graphemes)]
(if (= c "\n")
[:span {:id (str "mock-text_" idx)
:key idx} "0" [:br]]

View File

@@ -306,18 +306,6 @@
:own-order-list-index own-order-list-index
:own-order-number-list? (= own-order-list-type "number"))))
(defn- text-range-by-lst-fst-line [content [direction pos]]
(case direction
:up
(let [last-new-line (or (string/last-index-of content \newline) -1)
end (+ last-new-line pos 1)]
(subs content 0 end))
:down
(-> (string/split-lines content)
first
(or "")
(subs 0 pos))))
(defn mark-last-input-time!
[repo]
(when repo
@@ -394,7 +382,7 @@
content-length (count content)
text-range (cond
(vector? pos)
(text-range-by-lst-fst-line content pos)
(util/get-text-range content (last pos) (= (first pos) :down))
(and (> tail-len 0) (>= (count content) tail-len))
(subs content 0 (- (count content) tail-len))

View File

@@ -2586,7 +2586,7 @@
(defn- move-cross-boundary-up-down
[direction move-opts]
(let [input (state/get-input)
line-pos (util/get-first-or-last-line-pos input)
line-pos (util/get-line-pos (.-value input) (util/get-selection-start input))
repo (state/get-current-repo)
f (case direction
:up util/get-prev-block-non-collapsed

View File

@@ -414,12 +414,49 @@
(when input
(.-selectionDirection input)))
(defn get-first-or-last-line-pos
[input]
(let [pos (get-selection-start input)
value (.-value input)
last-newline-pos (or (string/last-index-of value \newline (dec pos)) -1)]
(- pos last-newline-pos 1)))
#?(:cljs
(defn split-graphemes
[s]
(let [^js splitter (GraphemeSplitter.)]
(.splitGraphemes splitter s))))
#?(:cljs
(defn get-graphemes-pos
"Return the length of the substrings in s between start and from-index.
multi-char count as 1, like emoji characters"
[s from-index]
(let [^js splitter (GraphemeSplitter.)]
(.countGraphemes splitter (subs s 0 from-index)))))
#?(:cljs
(defn get-line-pos
"Return the length of the substrings in s between the last index of newline
in s searching backward from from-newline-index and from-newline-index.
multi-char count as 1, like emoji characters"
[s from-newline-index]
(let [^js splitter (GraphemeSplitter.)
last-newline-pos (string/last-index-of s \newline (dec from-newline-index))
before-last-newline-length (or last-newline-pos -1)
last-newline-content (subs s (inc before-last-newline-length) from-newline-index)]
(.countGraphemes splitter last-newline-content))))
#?(:cljs
(defn get-text-range
"Return the substring of the first grapheme-num characters of s if first-line? is true,
otherwise return the substring of s before the last \n and the first grapheme-num characters.
grapheme-num treats multi-char as 1, like emoji characters"
[s grapheme-num first-line?]
(let [newline-pos (if first-line?
0
(inc (or (string/last-index-of s \newline) -1)))
^js splitter (GraphemeSplitter.)
^js newline-graphemes (.splitGraphemes splitter (subs s newline-pos))
^js newline-graphemes (.slice newline-graphemes 0 grapheme-num)
content (.join newline-graphemes "")]
(subs s 0 (+ newline-pos (count content))))))
#?(:cljs
(defn stop [e]
@@ -684,7 +721,7 @@
(defn safe-dec-current-pos-from-end
[input current-pos]
(if-let [len (and (string? input) (.-length input))]
(when-let [input (and (>= len 2) (<= current-pos len)
(if-let [input (and (>= len 2) (<= current-pos len)
(.substring input (max (- current-pos 20) 0) current-pos))]
(try
(let [^js splitter (GraphemeSplitter.)
@@ -692,7 +729,8 @@
(- current-pos (.-length (.pop input))))
(catch :default e
(js/console.error e)
(dec current-pos))))
(dec current-pos)))
(dec current-pos))
(dec current-pos))))
#?(:cljs
@@ -700,7 +738,7 @@
(defn safe-inc-current-pos-from-start
[input current-pos]
(if-let [len (and (string? input) (.-length input))]
(when-let [input (and (>= len 2) (<= current-pos len)
(if-let [input (and (>= len 2) (<= current-pos len)
(.substr input current-pos 20))]
(try
(let [^js splitter (GraphemeSplitter.)
@@ -708,7 +746,8 @@
(+ current-pos (.-length (.shift input))))
(catch :default e
(js/console.error e)
(inc current-pos))))
(inc current-pos)))
(inc current-pos))
(inc current-pos))))
#?(:cljs
@@ -1334,10 +1373,11 @@
scroll-top (.-scrollTop main-node)
current-pos (get-selection-start el)
grapheme-pos (get-graphemes-pos (.-value (.textContent el)) current-pos)
mock-text (some-> (gdom/getElement "mock-text")
gdom/getChildren
array-seq
(nth-safe current-pos))
(nth-safe grapheme-pos))
offset-top (and mock-text (.-offsetTop mock-text))
offset-height (and mock-text (.-offsetHeight mock-text))

View File

@@ -31,12 +31,13 @@
([input] (get-caret-pos input (util/get-selection-start input)))
([input pos]
(when input
(let [rect (bean/->clj (.. input (getBoundingClientRect) (toJSON)))]
(let [rect (bean/->clj (.. input (getBoundingClientRect) (toJSON)))
grapheme-pos (util/get-graphemes-pos (.-value input) pos)]
(try
(some-> (gdom/getElement "mock-text")
gdom/getChildren
array-seq
(util/nth-safe pos)
(util/nth-safe grapheme-pos)
mock-char-pos
(assoc :rect rect))
(catch :default e
@@ -71,9 +72,8 @@
([input n]
(when input
(let [{:keys [pos]} (get-caret-pos input)
pos (if (and (= n 1) (not (zero? pos)))
(or (util/safe-inc-current-pos-from-start (.-value input) pos)
(inc pos))
pos (if (= n 1)
(util/safe-inc-current-pos-from-start (.-value input) pos)
(+ pos n))]
(move-cursor-to input pos)))))
@@ -85,8 +85,7 @@
(let [{:keys [pos]} (get-caret-pos input)
pos (if (= n 1)
(util/safe-dec-current-pos-from-end (.-value input) pos)
(- pos n))
pos (max 0 (or pos (dec pos)))]
(- pos n))]
(move-cursor-to input pos)))))
(defn- get-input-content&pos

View File

@@ -13,6 +13,7 @@
(is (= 3 (util/safe-dec-current-pos-from-end "abc😀d" 5)))
(is (= 3 (util/safe-dec-current-pos-from-end "abc😀" 5)))
(is (= 0 (util/safe-dec-current-pos-from-end "😀" 2)))
(is (= 0 (util/safe-dec-current-pos-from-end "a" 1)))
(is (= 4 (util/safe-dec-current-pos-from-end "abcde" 5)))
(is (= 1 (util/safe-dec-current-pos-from-end "中文" 2))))
@@ -20,8 +21,40 @@
(is (= 5 (util/safe-inc-current-pos-from-start "abc😀d" 3)))
(is (= 2 (util/safe-inc-current-pos-from-start "😀" 0)))
(is (= 2 (util/safe-inc-current-pos-from-start "abcde" 1)))
(is (= 1 (util/safe-inc-current-pos-from-start "a" 0)))
(is (= 1 (util/safe-inc-current-pos-from-start "中文" 0)))))
(deftest test-get-line-pos
(testing "get-line-pos"
(is (= 3 (util/get-line-pos "abcde" 3)))
(is (= 4 (util/get-line-pos "abcd\ne" 4)))
(is (= 0 (util/get-line-pos "abcd\ne" 5)))
(is (= 4 (util/get-line-pos "abc😀d" 5)))
(is (= 1 (util/get-line-pos "abc\nde" 5)))
(is (= 1 (util/get-line-pos "abc\n😀d" 6)))
(is (= 2 (util/get-line-pos "ab\nc😀d" 6)))
(is (= 1 (util/get-line-pos "abc\nde\nf" 5)))
(is (= 1 (util/get-line-pos "abc\n😀d\ne" 6)))
(is (= 2 (util/get-line-pos "ab\nc😀d\ne" 6)))))
(deftest test-get-text-range
(testing "get-text-range"
(is (= "" (util/get-text-range "abcdefg" 0 true)))
(is (= "" (util/get-text-range "abcdefg" 0 false)))
(is (= "abcdefg" (util/get-text-range "abcdefg" 10 true)))
(is (= "abcdefg" (util/get-text-range "abcdefg" 10 false)))
(is (= "abc" (util/get-text-range "abcdefg" 3 true)))
(is (= "abc" (util/get-text-range "abcdefg" 3 false)))
(is (= "abc" (util/get-text-range "abcdefg\nhijklmn" 3 true)))
(is (= "abcdefg\nhij" (util/get-text-range "abcdefg\nhijklmn" 3 false)))
(is (= "abcdefg\nhijklmn" (util/get-text-range "abcdefg\nhijklmn" 10 false)))
(is (= "abcdefg\nhijklmn\nopq" (util/get-text-range "abcdefg\nhijklmn\nopqrst" 3 false)))
(is (= "a😀b" (util/get-text-range "a😀bcdefg" 3 true)))
(is (= "a😀b" (util/get-text-range "a😀bcdefg" 3 false)))
(is (= "a😀b" (util/get-text-range "a😀bcdefg\nhijklmn" 3 true)))
(is (= "a😀bcdefg\nhij" (util/get-text-range "a😀bcdefg\nhijklmn" 3 false)))
(is (= "a😀bcdefg\nh😀i" (util/get-text-range "a😀bcdefg\nh😀ijklmn" 3 false)))))
(deftest test-memoize-last
(testing "memoize-last add test"
(let [actual-ops (atom 0)