mirror of
https://github.com/logseq/logseq.git
synced 2026-04-25 14:44:53 +00:00
273 lines
8.5 KiB
Clojure
273 lines
8.5 KiB
Clojure
(ns ^:no-doc frontend.util.cursor
|
|
(:require [cljs-bean.core :as bean]
|
|
[clojure.string :as string]
|
|
[frontend.util :as util]
|
|
[goog.dom :as gdom]
|
|
[goog.object :as gobj]))
|
|
|
|
(defn- closer [a b c]
|
|
(let [a-left (or (:left a) 0)
|
|
b-left (:left b)
|
|
c-left (or (:left c) js/Number.MAX_SAFE_INTEGER)]
|
|
(if (< (- b-left a-left) (- c-left b-left))
|
|
a
|
|
c)))
|
|
|
|
(defn mock-char-pos [e]
|
|
{:left (.-offsetLeft e)
|
|
:top (.-offsetTop e)
|
|
:pos (-> (.-id e)
|
|
(string/split "_")
|
|
second
|
|
int)})
|
|
|
|
(defn get-caret-pos
|
|
"Get caret offset position as well as input element rect.
|
|
|
|
This function is only used by autocomplete command or up/down command
|
|
where offset position is needed.
|
|
|
|
If you only need character position, use `pos` instead. Do NOT call this."
|
|
([input] (get-caret-pos input (util/get-selection-start input)))
|
|
([input pos]
|
|
(when input
|
|
(let [rect (bean/->clj (.. input (getBoundingClientRect) (toJSON)))]
|
|
(try
|
|
(some-> (gdom/getElement "mock-text")
|
|
gdom/getChildren
|
|
array-seq
|
|
(util/nth-safe pos)
|
|
mock-char-pos
|
|
(assoc :rect rect))
|
|
(catch :default e
|
|
(js/console.log "index error" e)
|
|
{:pos pos
|
|
:rect rect
|
|
:left js/Number.MAX_SAFE_INTEGER
|
|
:top js/Number.MAX_SAFE_INTEGER}))))))
|
|
|
|
|
|
(defn pos [input]
|
|
(when input
|
|
(util/get-selection-start input)))
|
|
|
|
(defn start? [input]
|
|
(and input (zero? (util/get-selection-start input))))
|
|
|
|
(defn end? [input]
|
|
(and input
|
|
(= (count (.-value input))
|
|
(util/get-selection-start input))))
|
|
|
|
(defn set-selection-to [input n m]
|
|
(.setSelectionRange input n m))
|
|
|
|
(defn move-cursor-to [input n]
|
|
(.setSelectionRange input n n))
|
|
|
|
(defn move-cursor-forward
|
|
([input]
|
|
(move-cursor-forward input 1))
|
|
([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 n))]
|
|
(move-cursor-to input pos)))))
|
|
|
|
(defn move-cursor-backward
|
|
([input]
|
|
(move-cursor-backward input 1))
|
|
([input n]
|
|
(when input
|
|
(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)))]
|
|
(move-cursor-to input pos)))))
|
|
|
|
(defn- get-input-content&pos
|
|
[input]
|
|
[(gobj/get input "value")
|
|
(pos input)])
|
|
|
|
(defn line-beginning-pos
|
|
[input]
|
|
(let [[content pos] (get-input-content&pos input)]
|
|
(if (zero? pos) 0
|
|
(let [last-newline-pos (string/last-index-of content \newline (dec pos))]
|
|
(if (= nil last-newline-pos) 0 ;; no newline found (first line)
|
|
(inc last-newline-pos))))))
|
|
|
|
(defn line-end-pos
|
|
[input]
|
|
(let [[content pos] (get-input-content&pos input)]
|
|
(or (string/index-of content \newline pos)
|
|
(count content))))
|
|
|
|
(defn beginning-of-line?
|
|
[input]
|
|
(let [[content pos] (get-input-content&pos input)]
|
|
(when content
|
|
(or (zero? pos)
|
|
(when-let [pre-char (subs content (dec pos) pos)]
|
|
(= pre-char \newline))))))
|
|
|
|
(defn move-cursor-to-line-end
|
|
[input]
|
|
(move-cursor-to input (line-end-pos input)))
|
|
|
|
;; (defn move-cursor-to-line-beginning
|
|
;; [input]
|
|
;; (move-cursor-to input (line-beginning-pos input)))
|
|
|
|
(defn move-cursor-to-end
|
|
[input]
|
|
(let [pos (count (gobj/get input "value"))]
|
|
(move-cursor-to input pos)))
|
|
|
|
(defn move-cursor-to-thing
|
|
([input thing]
|
|
(move-cursor-to-thing input thing (pos input)))
|
|
([input thing from]
|
|
(let [[content _pos] (get-input-content&pos input)
|
|
pos (string/index-of content thing from)]
|
|
(move-cursor-to input pos))))
|
|
|
|
(defn move-cursor-forward-by-word
|
|
[input]
|
|
(let [val (.-value input)
|
|
current (util/get-selection-start input)
|
|
current (loop [idx current]
|
|
(if (#{\space \newline} (util/nth-safe val idx))
|
|
(recur (inc idx))
|
|
idx))
|
|
idx (or (->> [(string/index-of val \space current)
|
|
(string/index-of val \newline current)]
|
|
(remove nil?)
|
|
(apply min))
|
|
(count val))]
|
|
(move-cursor-to input idx)))
|
|
|
|
(defn move-cursor-backward-by-word
|
|
[input]
|
|
(let [val (.-value input)
|
|
current (util/get-selection-start input)
|
|
prev (or
|
|
(->> [(string/last-index-of val \space (dec current))
|
|
(string/last-index-of val \newline (dec current))]
|
|
(remove nil?)
|
|
(apply max))
|
|
0)
|
|
idx (if (zero? prev)
|
|
0
|
|
(->
|
|
(loop [idx prev]
|
|
(if (#{\space \newline} (util/nth-safe val idx))
|
|
(recur (dec idx))
|
|
idx))
|
|
inc))]
|
|
(move-cursor-to input idx)))
|
|
|
|
(defn textarea-cursor-rect-first-row? [cursor]
|
|
(let [elms (-> (gdom/getElement "mock-text")
|
|
gdom/getChildren
|
|
array-seq)
|
|
tops (->> elms
|
|
(map mock-char-pos)
|
|
(map :top)
|
|
(distinct))]
|
|
(= (first tops) (:top cursor))))
|
|
|
|
(defn textarea-cursor-first-row? [input]
|
|
(textarea-cursor-rect-first-row? (get-caret-pos input)))
|
|
|
|
|
|
(defn textarea-cursor-rect-last-row? [cursor]
|
|
(let [elms (-> (gdom/getElement "mock-text")
|
|
gdom/getChildren
|
|
array-seq)
|
|
tops (->> elms
|
|
(map mock-char-pos)
|
|
(map :top)
|
|
(distinct))]
|
|
(= (last tops) (:top cursor))))
|
|
|
|
(defn textarea-cursor-last-row? [input]
|
|
(textarea-cursor-rect-last-row? (get-caret-pos input)))
|
|
|
|
(defn- next-cursor-pos-up-down [direction cursor]
|
|
(let [elms (-> (gdom/getElement "mock-text")
|
|
gdom/getChildren
|
|
array-seq)
|
|
chars (->> elms
|
|
(map mock-char-pos)
|
|
(group-by :top))
|
|
tops (sort (keys chars))
|
|
tops-p (partition-by #(== (:top cursor) %) tops)
|
|
line-next
|
|
(if (= :up direction)
|
|
(-> tops-p first last)
|
|
(-> tops-p last first))
|
|
lefts
|
|
(->> (get chars line-next)
|
|
(partition-by (fn [char-pos]
|
|
(<= (:left char-pos) (:left cursor)))))
|
|
left-a (-> lefts first last)
|
|
left-c (-> lefts last first)
|
|
closer
|
|
(if (> 2 (count lefts))
|
|
left-a
|
|
(closer left-a cursor left-c))]
|
|
(:pos closer)))
|
|
|
|
(defn- move-cursor-up-down
|
|
[input direction]
|
|
(move-cursor-to input (next-cursor-pos-up-down direction (get-caret-pos input))))
|
|
|
|
(defn move-cursor-up [input]
|
|
(move-cursor-up-down input :up))
|
|
|
|
(defn move-cursor-down [input]
|
|
(move-cursor-up-down input :down))
|
|
|
|
(defn select-up-down [input direction anchor cursor-rect]
|
|
(let [next-cursor (next-cursor-pos-up-down direction cursor-rect)]
|
|
(if (<= anchor next-cursor)
|
|
(.setSelectionRange input anchor next-cursor "forward")
|
|
(.setSelectionRange input next-cursor anchor "backward"))))
|
|
|
|
(comment
|
|
;; previous implementation of up/down
|
|
(defn move-cursor-up
|
|
"Move cursor up. If EOL, always move cursor to previous EOL."
|
|
[input]
|
|
(let [val (gobj/get input "value")
|
|
pos (util/get-selection-start input)
|
|
prev-idx (string/last-index-of val \newline pos)
|
|
pprev-idx (or (string/last-index-of val \newline (dec prev-idx)) -1)
|
|
cal-idx (+ pprev-idx pos (- prev-idx))]
|
|
(if (or (== pos (count val))
|
|
(> (- pos prev-idx) (- prev-idx pprev-idx)))
|
|
(move-cursor-to input prev-idx)
|
|
(move-cursor-to input cal-idx))))
|
|
|
|
(defn move-cursor-down
|
|
"Move cursor down by calculating current cursor line pos.
|
|
If EOL, always move cursor to next EOL."
|
|
[input]
|
|
(let [val (gobj/get input "value")
|
|
pos (util/get-selection-start input)
|
|
prev-idx (or (string/last-index-of val \newline pos) -1)
|
|
next-idx (or (string/index-of val \newline (inc pos))
|
|
(count val))
|
|
nnext-idx (or (string/index-of val \newline (inc next-idx))
|
|
(count val))
|
|
cal-idx (+ next-idx pos (- prev-idx))]
|
|
(if (> (- pos prev-idx) (- nnext-idx next-idx))
|
|
(move-cursor-to input nnext-idx)
|
|
(move-cursor-to input cal-idx)))))
|