 
 ;; Push operations
 
-(defmulti push-char
-  (fn [stack [i ch]] ch)
+(defmulti push-char*
+  (fn [stack [x-pos ch]] ch)
   :hierarchy #'char-hierarchy)
 
-(defmethod push-char :open
-  [stack [i ch]]
-  (cond
-    (in-escape? stack) (pop stack)
-    (in-code? stack) (conj stack [i ch])
-    :else stack))
-
-(defmethod push-char :close
-  [stack [i ch]]
-  (println (valid-closer? stack ch) (in-code? stack))
+(defmethod push-char* "\t"
+  [stack [x-pos ch]]
+  (cond
+    (not (in-str? stack)) (throw (str "no tabs allowed outside strings"))
+    :else stack))
+
+(defmethod push-char* :open
+  [stack [x-pos ch]]
+  (cond
+    (in-escape? stack) (pop stack)
+    (in-code? stack) (conj stack [x-pos ch])
+    :else stack))
+
+(defmethod push-char* :close
+  [stack [x-pos ch]]
   (cond
     (in-escape? stack) (pop stack)
     (in-code? stack) (if (valid-closer? stack ch)


(defn update-text!
  [cursor-index text]
  (println cursor-index)
  (try
    (let [stack (parse-text text)]
      (swap! app-state assoc :stack stack)
      (swap! app-state assoc :text text))
    (catch :default e
      nil ;; don't update the text
      )))


                         (throw (str "no matching open delimiter for " ch))))
    :else stack))

(defmethod push-char* ";"
  [stack [x-pos ch]]
  (cond
    (in-escape? stack) (pop stack)
    (in-code? stack) (conj stack [x-pos ch])
    :else stack))

(defmethod push-char* "\n"
  [stack [x-pos ch]]
  (let [s0 (cond-> stack (in-escape? stack) pop)
        s1 (cond-> s0 (in-comment? s0) pop)]
    s1))

(defmethod push-char* "\\"
  [stack [x-pos ch]]
  (cond
    (in-escape? stack) (pop stack)
    :else (conj stack [x-pos ch])))

(defmethod push-char* "\""
  [stack [x-pos ch]]
  (cond
    (in-escape? stack) (pop stack)
    (in-str? stack) (pop stack)
    :else (conj stack [x-pos ch])))

(defmethod push-char* :default
  [stack [x-pos ch]]
  (cond
    (in-escape? stack) (pop stack)
    :else stack))

(defn whitespace? [ch]
  (re-find #"[\s,]" ch))

(defn close-delims
  ([result] (close-delims result 0))
  ([result x-pos]
   ;; TODO: close all delimiters that are >= x-pos, and insert at insertion point
   (assoc result :track-indent? false)))

(defn push-char
  [{:keys [stack track-indent? line-no] :as result} [x-pos ch]]

  (let [;; Insert closing delimiters if required.
        should-close? (and track-indent?
                           (in-code? stack)
                           (not (whitespace? ch))
                           (not= ";" ch)
                           (or (not (isa? char-hierarchy ch :close))
                               (throw (str "cannot start line with closing delimiter: " ch))))
        result (cond-> result should-close? (close-delims x-pos))

        ;; Update the delimiter stack with this character.
        result (update-in result [:stack] push-char* [x-pos ch])
        stack (:stack result)

        ;; Add potential insert point for closing delimiters if required.
        insert (when (and (seq stack)
                          (in-code? stack)
                          (not (whitespace? ch)))
                 {:line-no line-no
                  :x-pos x-pos})
        result (cond-> result insert (assoc :insert insert))]
    result))

;; main parsers

(def empty-result
  {:lines []
   :line-no -1
   :track-indent? false
   :insert {:line nil :col nil}
   :stack []})

(defn parse-line
  ([line] (parse-line empty-result line))
  ([{:keys [stack lines line-no] :as result} line]
   (let [result (assoc result
                  :track-indent? (and (seq stack) (not (in-str? stack)))
                  :lines (conj lines line)
                  :line-no (inc line-no))
         result (reduce push-char result (map-indexed vector (str line "\n")))]
     result)))

(defn parse-text
  ([text] (parse-text empty-result text))
  ([result text]
   (let [result (reduce parse-line result (split-lines text))
         result (cond-> result (seq (:stack result)) close-delims)]
     result)))

;; EVENTS
;; when editing current line, prevent invalid closers
