                        (update-in [:lines line-no] remove-str-range start end)
                        (assoc :backup backup :stack stack))
 
-
             insert-line? (= (:line-no insert) line-no)
             result (cond-> result
                      insert-line? (update-in [:insert :x-pos] min start))]

                   (assoc :backup backup)
                   (update-in [:lines line-no] str ch))

        ;; Add potential insert point for closing delimiters if required.
        insert (when (and (not= "" ch)

                          ;; only allow whitespace as an insert if we have already found the indentation point
                          ;; (i.e. a whitespace-only line)
                          (or (and (not track-indent?) (whitespace? ch))
                              (not (whitespace? ch)))
                          (not (isa? char-hierarchy ch :close))
                          (seq stack)
                          (in-code? stack))
                 {:line-no line-no
                  :x-pos (inc x-pos)})
        result (cond-> result insert (assoc :insert insert))]

(defn process-line
  "Update the given result by processing the given line of text."
  ([line] (process-line empty-result line))
  ([{:keys [stack lines line-no cursor-line] :as result} line]
   (let [line-no (inc line-no)
         result (assoc result
                  :backup []
                  :delim-trail {:start nil :end nil}
                  :track-indent? (and (seq stack) (not (in-str? stack)))
                  :lines (conj lines "")
                  :line-no line-no)
         result (reduce pre-process-char result (str line "\n"))
         result (cond-> result
                  (not= line-no cursor-line) remove-delim-trail)
         ]
     result)))

(defn process-text
  "Update the given result by processing the given text."
  [result text & {:keys [cursor-line cursor-x]}]
  (let [result (assoc result
                      :cursor-line cursor-line
                      :cursor-x cursor-x)
        result (reduce process-line result (split-lines text))
        result (cond-> result (seq (:stack result)) close-delims)]
    result))

;;------------------------------------------------------------------------
;; Editor
;;------------------------------------------------------------------------

(defn update-text!
  [cursor-index text]
  (let [lines (split-lines (subs text 0 cursor-index))
        cursor-x (count (last lines))
        cursor-line (dec (count lines))
        {:keys [stack lines]} (process-text empty-result text
                                :cursor-line cursor-line
                                :cursor-x cursor-x)]
    (swap! app-state assoc :stack stack)
    (swap! app-state assoc :text text)
    (swap! app-state assoc :full-text (join "\n" lines))))

(defn trigger-change!
  "should be called when text or cursor position changes."
  [evt]
  (let [target (.-target evt)]
    (update-text! (.-selectionStart target) (.-value target))))

(defn root-comp
  [data owner]
