<commit_before>(ns nightcode.editors
  (:require [clojure.data :refer [diff]]
            [clojure.java.io :as io]
            [clojure.string :as str]
            [flatland.ordered.map :as flatland]
            [nightcode.completions :as completions]
            [nightcode.dialogs :as dialogs]
            [nightcode.file-browser :as file-browser]
            [nightcode.shortcuts :as shortcuts]
            [nightcode.ui :as ui]
            [nightcode.utils :as utils]
            [seesaw.color :as color]
            [seesaw.core :as s]
            [mistakes-were-made.core :as mwm]
            [tag-soup.core :as ts])
  (:import [java.awt.event KeyEvent KeyListener MouseListener]
           [javax.swing.event DocumentListener HyperlinkEvent$EventType]
           [nightcode.ui JConsole]
           [org.fife.ui.rsyntaxtextarea FileLocation TextEditorPane Theme]
           [org.fife.ui.rtextarea RTextScrollPane SearchContext SearchEngine
            SearchResult]
           [com.oakmac.parinfer Parinfer ParinferResult]))

(def ^:const min-font-size 8)
(def editors (atom (flatland/ordered-map)))
(def font-size (atom (max min-font-size (utils/read-pref :font-size 14))))
(def tabs (atom nil))

; basic getters

(defn get-text-area
  [view]
  (when view
    (->> [:<org.fife.ui.rsyntaxtextarea.TextEditorPane>]
         (s/select view)
         first)))

(defn get-text-area-from-path
  [path]
  (get-text-area (get-in @editors [path :view])))

(defn get-selected-text-area
  []
  (get-text-area-from-path @ui/tree-selection))

(defn get-selected-editor
  []
  (get-in @editors [@ui/tree-selection :view]))

(defn unsaved?
  [path]
  (when-let [text-area (get-text-area-from-path path)]
    (.isDirty text-area)))

(defn unsaved-paths
  ([]
   (filter unsaved? (keys @editors)))
  ([path]
   (filter #(.startsWith % path) (unsaved-paths))))

(defn get-editor-text
  []
  (when-let [text-area (get-selected-text-area)]
    (.getText text-area)))

(defn get-editor-selected-text
  []
  (when-let [text-area (get-selected-text-area)]
    (.getSelectedText text-area)))

; tabs

(def ^:dynamic *reorder-tabs?* true)

(defn move-tab-selection!
  [diff]
  (let [paths (reverse (keys @editors))
        index (.indexOf paths @ui/tree-selection)
        max-index (dec (count paths))
        new-index (+ index diff)
        new-index (cond
                    (neg? new-index) max-index
                    (> new-index max-index) 0
                    :else new-index)]
    (when (pos? (count paths))
      (binding [*reorder-tabs?* false]
        (ui/update-project-tree! (nth paths new-index)))))
  true)

(defn update-tabs!
  [path]
  (doto @ui/root .invalidate .validate)
  (let [editor-pane (ui/get-editor-pane)]
    (when @tabs (.closeBalloon @tabs))
    (->> (for [[e-path {:keys [italicize-fn]}] (reverse @editors)]
           (format "<a href='%s' style='text-decoration: %s;
                                        font-style: %s;'>%s</a>"
                   e-path
                   (if (utils/parent-path? path e-path) "underline" "none")
                   (if (italicize-fn) "italic" "normal")
                   (-> e-path io/file .getName)))
         (cons "<center>PgUp PgDn</center>")
         (str/join "<br/>")
         shortcuts/wrap-hint-text
         (s/editor-pane :editable? false :content-type "text/html" :text)
         (shortcuts/create-hint! true editor-pane)
         (reset! tabs))
    (s/listen (.getContents @tabs)
              :hyperlink
              (fn [e]
                (when (= (.getEventType e) HyperlinkEvent$EventType/ACTIVATED)
                  (binding [*reorder-tabs?* false]
                    (ui/update-project-tree! (.getDescription e))))))
    (shortcuts/toggle-hint! @tabs @shortcuts/down?)))

; button bar actions

(defn update-buttons!
  [editor ^TextEditorPane text-area]
  (when (ui/config! editor :#save :enabled? (.isDirty text-area))
    (update-tabs! @ui/tree-selection))
  (ui/config! editor :#undo :enabled? (.canUndo text-area))
  (ui/config! editor :#redo :enabled? (.canRedo text-area)))

(defn save-file!
  [& _]
  (when-let [text-area (get-selected-text-area)]
    (io!
      (with-open [w (io/writer (io/file @ui/tree-selection))]
        (.write text-area w)))
    (.setDirty text-area false)
    (s/request-focus! text-area)
    (update-buttons! (get-selected-editor) text-area))
  true)

(defn undo-file!
  [& _]
  (when-let [text-area (get-selected-text-area)]
    (.undoLastAction text-area)
    (s/request-focus! text-area)
    (update-buttons! (get-selected-editor) text-area)))

(defn redo-file!
  [& _]
  (when-let [text-area (get-selected-text-area)]
    (.redoLastAction text-area)
    (s/request-focus! text-area)
    (update-buttons! (get-selected-editor) text-area)))

(defn set-font-size!
  [text-area size]
  (.setFont text-area (-> text-area .getFont (.deriveFont (float size))))
  (s/request-focus! text-area))

(defn save-font-size!
  [size]
  (utils/write-pref! :font-size size))

(defn decrease-font-size!
  [& _]
  (swap! font-size (comp #(max min-font-size %) dec)))

(defn increase-font-size!
  [& _]
  (swap! font-size inc))

(defn save-doc!
  [enabled?]
  (utils/write-pref! :enable-doc enabled?))

(defn toggle-doc!
  [& _]
  (reset! completions/doc-enabled? (not @completions/doc-enabled?))
  (some-> (get-selected-text-area) s/request-focus!))

(defn focus-on-field!
  [id]
  (when-let [editor (get-selected-editor)]
    (when-let [widget (s/select editor [id])]
      (doto widget
        s/request-focus!
        .selectAll))))

(defn focus-on-find!
  [& _]
  (focus-on-field! :#find))

(defn focus-on-replace!
  [& _]
  (focus-on-field! :#replace))

(defn find-text!
  [e]
  (when-let [text-area (get-selected-text-area)]
    (let [key-code (.getKeyCode e)
          enter-key? (= key-code 10)
          find-text (s/text e)
          printable-char? (-> text-area .getFont (.canDisplay key-code))
          meta-keys #{KeyEvent/VK_SHIFT KeyEvent/VK_CONTROL KeyEvent/VK_META}
          valid-search? (and (pos? (count find-text))
                             printable-char?
                             (not @shortcuts/down?)
                             (not (contains? meta-keys (.getKeyCode e))))
          context (doto (SearchContext. find-text)
                    (.setMatchCase true))]
      (when valid-search?
        (when-not enter-key?
          (.setCaretPosition text-area 0))
        (when (.isShiftDown e)
          (.setSearchForward context false)))
      (if (or (not valid-search?)
              (let [result (SearchEngine/find text-area context)]
                (if (isa? (type result) SearchResult)
                  (-> result .getCount (> 0))
                  result)))
        (s/config! e :background nil)
        (s/config! e :background (color/color :red)))
      (when (= (count find-text) 0)
        (SearchEngine/find text-area context)))))

(defn replace-text!
  [e]
  (when-let [text-area (get-selected-text-area)]
    (let [key-code (.getKeyCode e)
          enter-key? (= key-code 10)
          editor (get-selected-editor)
          find-text (s/text (s/select editor [:#find]))
          replace-text (s/text e)
          context (doto (SearchContext. find-text)
                    (.setMatchCase true))]
      (.setReplaceWith context replace-text)
      (if (and enter-key?
               (or (zero? (count find-text))
                   (not (try (SearchEngine/replaceAll text-area context)
                          (catch Exception _ false)))))
        (s/config! e :background (color/color :red))
        (s/config! e :background nil))
      (when enter-key?
        (update-buttons! editor text-area)))))

; create and display editors

(defn add-watchers!
  [path extension text-area completer]
  (let [clojure? (contains? utils/clojure-exts extension)
        clojure-file? (and clojure? (.isFile (io/file path)))]
    (add-watch font-size
               (utils/hashed-keyword path)
               (fn [_ _ _ x]
                 (set-font-size! text-area x)))
    (when completer
      (add-watch completions/doc-enabled?
                 (utils/hashed-keyword path)
                 (fn [_ _ _ enable?]
                   (.setAutoActivationEnabled completer enable?))))))

(defn add-button-watchers!
  [path pane]
  (add-watch completions/doc-enabled?
             (utils/hashed-keyword (str "button:" path))
             (fn [_ _ _ enable?]
               (some-> (s/select pane [:#doc])
                       (s/config! :selected? enable?)))))

(defn remove-watchers!
  [path]
  (remove-watch font-size (utils/hashed-keyword path))
  (remove-watch completions/doc-enabled? (utils/hashed-keyword path))
  (remove-watch completions/doc-enabled?
                (utils/hashed-keyword (str "button:" path))))

(defn apply-settings!
  [text-area]
  (-> @ui/theme-resource
      io/input-stream
      Theme/load
      (.apply text-area))
  (set-font-size! text-area @font-size))

(defn paren-mode [text x line]
  (let [res (Parinfer/parenMode text (int x) (int line) nil)]
    {:x (.-cursorX res) :text (.-text res)}))

(defn indent-mode [text x line]
  (let [^ParinferResult res (Parinfer/indentMode text (int x) (int line) nil)]
    {:x (.-cursorX res) :text (.-text res)}))

(defn get-parinfer-state
  [^TextEditorPane text-area paren-mode?]
  (let [old-pos (.getCaretPosition text-area)
        old-x (.getCaretOffsetFromLineStart text-area)
        old-line (.getCaretLineNumber text-area)
        old-text (.getText text-area)
        result (if paren-mode?
                 (paren-mode old-text old-x old-line)
                 (indent-mode old-text old-x old-line))]
    (mwm/get-state (:text result) old-line (:x result))))

(defn get-normal-state
  [^TextEditorPane text-area]
  (let [position (.getCaretPosition text-area)
        text (.getText text-area)]
    (assoc (mwm/get-state text position) :should-indent? true)))

(defn add-indent-if-necessary
  [lines text tags state]
  (if (:should-indent? state)
    (let [[cursor-line _] (mwm/position->row-col text (:cursor-position state))
          indent-level (ts/indent-for-line tags cursor-line)]
      [(update lines
               cursor-line
               (fn [line]
                 (str (str/join (repeat indent-level " ")) (str/triml line))))
       (+ (:cursor-position state) indent-level)])
    [lines (:cursor-position state)]))

(defn refresh-content!
  [^TextEditorPane text-area state]
  (let [old-text (.getText text-area)
        lines (:lines state)
        new-text (str/join \newline lines)
        tags (ts/str->tags new-text)
        [lines cursor-position] (add-indent-if-necessary lines new-text tags state)
        new-text (str/join \newline lines)]
    (.replaceRange text-area new-text 0 (count old-text))
    (.setCaretPosition text-area cursor-position)
    state))

(defn init-parinfer!
  [^TextEditorPane text-area extension edit-history preprocess?]
  (if (contains? utils/clojure-exts extension)
    (let [old-text (.getText text-area)]
      ; use paren mode to preprocess the code
      (when preprocess?
        (->> (assoc (get-parinfer-state text-area true) :cursor-position 0)
             (refresh-content! text-area)
             (mwm/update-edit-history! edit-history)))
      (.discardAllEdits text-area)
      (.setDirty text-area (not= old-text (.getText text-area)))
      ; disable auto indent because we're providing our own
      (.setAutoIndentEnabled text-area false)
      ; add a listener to run indent mode when a key is pressed
      (.addKeyListener text-area
        (reify KeyListener
          (keyReleased [this e]
            (cond
              (contains? #{KeyEvent/VK_DOWN KeyEvent/VK_UP
                           KeyEvent/VK_RIGHT KeyEvent/VK_LEFT}
                         (.getKeyCode e))
              (mwm/update-cursor-position! edit-history (.getCaretPosition text-area))
              
              (not (or (contains? #{KeyEvent/VK_SHIFT KeyEvent/VK_CONTROL
                                    KeyEvent/VK_ALT KeyEvent/VK_META}
                                  (.getKeyCode e))
                       (.isControlDown e)
                       (.isMetaDown e)))
              (->> (if (= (.getKeyCode e) KeyEvent/VK_ENTER)
                     (get-normal-state text-area)
                     (get-parinfer-state text-area false))
                   (refresh-content! text-area)
                   (mwm/update-edit-history! edit-history))))
          (keyTyped [this e] nil)
          (keyPressed [this e] nil)))
      ; add a listener to update the cursor position when the mouse is released
      (.addMouseListener text-area
        (reify MouseListener
          (mouseClicked [this e] nil)
          (mouseEntered [this e] nil)
          (mouseExited [this e] nil)
          (mousePressed [this e] nil)
          (mouseReleased [this e]
            (mwm/update-cursor-position! edit-history (.getCaretPosition text-area))))))
    (reset! edit-history nil))
  text-area)

(defn create-text-area
  ([]
   (create-text-area (atom nil)))
  ([edit-history]
   (doto (proxy [TextEditorPane] []
           (setMarginLineEnabled [enabled?]
             (proxy-super setMarginLineEnabled enabled?))
           (setMarginLinePosition [size]
             (proxy-super setMarginLinePosition size))
           (processKeyBinding [ks e condition pressed]
             (proxy-super processKeyBinding ks e condition pressed))<commit_msg>Preserve selection when hitting tab<commit_after>(ns nightcode.editors
  (:require [clojure.data :refer [diff]]
            [clojure.java.io :as io]
            [clojure.string :as str]
            [flatland.ordered.map :as flatland]
            [nightcode.completions :as completions]
            [nightcode.dialogs :as dialogs]
            [nightcode.file-browser :as file-browser]
            [nightcode.shortcuts :as shortcuts]
            [nightcode.ui :as ui]
            [nightcode.utils :as utils]
            [seesaw.color :as color]
            [seesaw.core :as s]
            [mistakes-were-made.core :as mwm]
            [tag-soup.core :as ts])
  (:import [java.awt.event KeyEvent KeyListener MouseListener]
           [javax.swing.event DocumentListener HyperlinkEvent$EventType]
           [nightcode.ui JConsole]
           [org.fife.ui.rsyntaxtextarea FileLocation TextEditorPane Theme]
           [org.fife.ui.rtextarea RTextScrollPane SearchContext SearchEngine
            SearchResult]
           [com.oakmac.parinfer Parinfer ParinferResult]))

(def ^:const min-font-size 8)
(def editors (atom (flatland/ordered-map)))
(def font-size (atom (max min-font-size (utils/read-pref :font-size 14))))
(def tabs (atom nil))

; basic getters

(defn get-text-area
  [view]
  (when view
    (->> [:<org.fife.ui.rsyntaxtextarea.TextEditorPane>]
         (s/select view)
         first)))

(defn get-text-area-from-path
  [path]
  (get-text-area (get-in @editors [path :view])))

(defn get-selected-text-area
  []
  (get-text-area-from-path @ui/tree-selection))

(defn get-selected-editor
  []
  (get-in @editors [@ui/tree-selection :view]))

(defn unsaved?
  [path]
  (when-let [text-area (get-text-area-from-path path)]
    (.isDirty text-area)))

(defn unsaved-paths
  ([]
   (filter unsaved? (keys @editors)))
  ([path]
   (filter #(.startsWith % path) (unsaved-paths))))

(defn get-editor-text
  []
  (when-let [text-area (get-selected-text-area)]
    (.getText text-area)))

(defn get-editor-selected-text
  []
  (when-let [text-area (get-selected-text-area)]
    (.getSelectedText text-area)))

; tabs

(def ^:dynamic *reorder-tabs?* true)

(defn move-tab-selection!
  [diff]
  (let [paths (reverse (keys @editors))
        index (.indexOf paths @ui/tree-selection)
        max-index (dec (count paths))
        new-index (+ index diff)
        new-index (cond
                    (neg? new-index) max-index
                    (> new-index max-index) 0
                    :else new-index)]
    (when (pos? (count paths))
      (binding [*reorder-tabs?* false]
        (ui/update-project-tree! (nth paths new-index)))))
  true)

(defn update-tabs!
  [path]
  (doto @ui/root .invalidate .validate)
  (let [editor-pane (ui/get-editor-pane)]
    (when @tabs (.closeBalloon @tabs))
    (->> (for [[e-path {:keys [italicize-fn]}] (reverse @editors)]
           (format "<a href='%s' style='text-decoration: %s;
                                        font-style: %s;'>%s</a>"
                   e-path
                   (if (utils/parent-path? path e-path) "underline" "none")
                   (if (italicize-fn) "italic" "normal")
                   (-> e-path io/file .getName)))
         (cons "<center>PgUp PgDn</center>")
         (str/join "<br/>")
         shortcuts/wrap-hint-text
         (s/editor-pane :editable? false :content-type "text/html" :text)
         (shortcuts/create-hint! true editor-pane)
         (reset! tabs))
    (s/listen (.getContents @tabs)
              :hyperlink
              (fn [e]
                (when (= (.getEventType e) HyperlinkEvent$EventType/ACTIVATED)
                  (binding [*reorder-tabs?* false]
                    (ui/update-project-tree! (.getDescription e))))))
    (shortcuts/toggle-hint! @tabs @shortcuts/down?)))

; button bar actions

(defn update-buttons!
  [editor ^TextEditorPane text-area]
  (when (ui/config! editor :#save :enabled? (.isDirty text-area))
    (update-tabs! @ui/tree-selection))
  (ui/config! editor :#undo :enabled? (.canUndo text-area))
  (ui/config! editor :#redo :enabled? (.canRedo text-area)))

(defn save-file!
  [& _]
  (when-let [text-area (get-selected-text-area)]
    (io!
      (with-open [w (io/writer (io/file @ui/tree-selection))]
        (.write text-area w)))
    (.setDirty text-area false)
    (s/request-focus! text-area)
    (update-buttons! (get-selected-editor) text-area))
  true)

(defn undo-file!
  [& _]
  (when-let [text-area (get-selected-text-area)]
    (.undoLastAction text-area)
    (s/request-focus! text-area)
    (update-buttons! (get-selected-editor) text-area)))

(defn redo-file!
  [& _]
  (when-let [text-area (get-selected-text-area)]
    (.redoLastAction text-area)
    (s/request-focus! text-area)
    (update-buttons! (get-selected-editor) text-area)))

(defn set-font-size!
  [text-area size]
  (.setFont text-area (-> text-area .getFont (.deriveFont (float size))))
  (s/request-focus! text-area))

(defn save-font-size!
  [size]
  (utils/write-pref! :font-size size))

(defn decrease-font-size!
  [& _]
  (swap! font-size (comp #(max min-font-size %) dec)))

(defn increase-font-size!
  [& _]
  (swap! font-size inc))

(defn save-doc!
  [enabled?]
  (utils/write-pref! :enable-doc enabled?))

(defn toggle-doc!
  [& _]
  (reset! completions/doc-enabled? (not @completions/doc-enabled?))
  (some-> (get-selected-text-area) s/request-focus!))

(defn focus-on-field!
  [id]
  (when-let [editor (get-selected-editor)]
    (when-let [widget (s/select editor [id])]
      (doto widget
        s/request-focus!
        .selectAll))))

(defn focus-on-find!
  [& _]
  (focus-on-field! :#find))

(defn focus-on-replace!
  [& _]
  (focus-on-field! :#replace))

(defn find-text!
  [e]
  (when-let [text-area (get-selected-text-area)]
    (let [key-code (.getKeyCode e)
          enter-key? (= key-code 10)
          find-text (s/text e)
          printable-char? (-> text-area .getFont (.canDisplay key-code))
          meta-keys #{KeyEvent/VK_SHIFT KeyEvent/VK_CONTROL KeyEvent/VK_META}
          valid-search? (and (pos? (count find-text))
                             printable-char?
                             (not @shortcuts/down?)
                             (not (contains? meta-keys (.getKeyCode e))))
          context (doto (SearchContext. find-text)
                    (.setMatchCase true))]
      (when valid-search?
        (when-not enter-key?
          (.setCaretPosition text-area 0))
        (when (.isShiftDown e)
          (.setSearchForward context false)))
      (if (or (not valid-search?)
              (let [result (SearchEngine/find text-area context)]
                (if (isa? (type result) SearchResult)
                  (-> result .getCount (> 0))
                  result)))
        (s/config! e :background nil)
        (s/config! e :background (color/color :red)))
      (when (= (count find-text) 0)
        (SearchEngine/find text-area context)))))

(defn replace-text!
  [e]
  (when-let [text-area (get-selected-text-area)]
    (let [key-code (.getKeyCode e)
          enter-key? (= key-code 10)
          editor (get-selected-editor)
          find-text (s/text (s/select editor [:#find]))
          replace-text (s/text e)
          context (doto (SearchContext. find-text)
                    (.setMatchCase true))]
      (.setReplaceWith context replace-text)
      (if (and enter-key?
               (or (zero? (count find-text))
                   (not (try (SearchEngine/replaceAll text-area context)
                          (catch Exception _ false)))))
        (s/config! e :background (color/color :red))
        (s/config! e :background nil))
      (when enter-key?
        (update-buttons! editor text-area)))))

; create and display editors

(defn add-watchers!
  [path extension text-area completer]
  (let [clojure? (contains? utils/clojure-exts extension)
        clojure-file? (and clojure? (.isFile (io/file path)))]
    (add-watch font-size
               (utils/hashed-keyword path)
               (fn [_ _ _ x]
                 (set-font-size! text-area x)))
    (when completer
      (add-watch completions/doc-enabled?
                 (utils/hashed-keyword path)
                 (fn [_ _ _ enable?]
                   (.setAutoActivationEnabled completer enable?))))))

(defn add-button-watchers!
  [path pane]
  (add-watch completions/doc-enabled?
             (utils/hashed-keyword (str "button:" path))
             (fn [_ _ _ enable?]
               (some-> (s/select pane [:#doc])
                       (s/config! :selected? enable?)))))

(defn remove-watchers!
  [path]
  (remove-watch font-size (utils/hashed-keyword path))
  (remove-watch completions/doc-enabled? (utils/hashed-keyword path))
  (remove-watch completions/doc-enabled?
                (utils/hashed-keyword (str "button:" path))))

(defn apply-settings!
  [text-area]
  (-> @ui/theme-resource
      io/input-stream
      Theme/load
      (.apply text-area))
  (set-font-size! text-area @font-size))

(defn paren-mode [text x line]
  (let [res (Parinfer/parenMode text (int x) (int line) nil)]
    {:x (.-cursorX res) :text (.-text res)}))

(defn indent-mode [text x line]
  (let [^ParinferResult res (Parinfer/indentMode text (int x) (int line) nil)]
    {:x (.-cursorX res) :text (.-text res)}))

(defn get-cursor-position
  [^TextEditorPane text-area]
  [(.getSelectionStart text-area) (.getSelectionEnd text-area)])

(defn set-cursor-position!
  [^TextEditorPane text-area & [start-pos end-pos]]
  (if (and start-pos end-pos (not= start-pos end-pos))
    (do
      (.setSelectionStart text-area start-pos)
      (.setSelectionEnd text-area end-pos))
    (.setCaretPosition text-area start-pos)))

(defn get-parinfer-state
  [^TextEditorPane text-area paren-mode?]
  (let [cursor-position (get-cursor-position text-area)
        [start-pos end-pos] cursor-position
        selected? (not= start-pos end-pos)
        [col row] (if selected?
                    [0 0]
                    [(.getCaretOffsetFromLineStart text-area)
                     (.getCaretLineNumber text-area)])
        old-text (.getText text-area)
        result (if paren-mode?
                 (paren-mode old-text col row)
                 (indent-mode old-text col row))]
    (if selected?
      (mwm/get-state (:text result) cursor-position)
      (mwm/get-state (:text result) row (:x result)))))

(defn get-normal-state
  [^TextEditorPane text-area]
  (let [position (get-cursor-position text-area)
        text (.getText text-area)]
    (assoc (mwm/get-state text position) :should-indent? true)))

(defn add-indent!
  [^TextEditorPane text-area lines text tags state]
  (let [start-pos (first (:cursor-position state))
        [cursor-line _] (mwm/position->row-col text start-pos)
        indent-level (ts/indent-for-line tags cursor-line)
        lines (update lines
                cursor-line
                (fn [line]
                  (str (str/join (repeat indent-level " ")) (str/triml line))))
        text (str/join \newline lines)]
    (.setText text-area text)
    (.setCaretPosition text-area (+ start-pos indent-level))))

(defn refresh-content!
  [^TextEditorPane text-area state]
  (let [lines (:lines state)
        [start-pos end-pos] (:cursor-position state)
        new-text (str/join \newline lines)]
    (if (:should-indent? state)
      (add-indent! text-area lines new-text (ts/str->tags new-text) state)
      (do
        (.setText text-area new-text)
        (set-cursor-position! text-area start-pos end-pos)))
    state))

(defn init-parinfer!
  [^TextEditorPane text-area extension edit-history preprocess?]
  (if (contains? utils/clojure-exts extension)
    (let [old-text (.getText text-area)]
      ; use paren mode to preprocess the code
      (when preprocess?
        (->> (get-parinfer-state text-area true)
             (refresh-content! text-area)
             (mwm/update-edit-history! edit-history)))
      (.discardAllEdits text-area)
      (.setDirty text-area (not= old-text (.getText text-area)))
      ; disable auto indent because we're providing our own
      (.setAutoIndentEnabled text-area false)
      ; add a listener to run indent mode when a key is pressed
      (.addKeyListener text-area
        (reify KeyListener
          (keyReleased [this e]
            (cond
              (contains? #{KeyEvent/VK_DOWN KeyEvent/VK_UP
                           KeyEvent/VK_RIGHT KeyEvent/VK_LEFT}
                         (.getKeyCode e))
              (mwm/update-cursor-position! edit-history (get-cursor-position text-area))
              
              (not (or (contains? #{KeyEvent/VK_SHIFT KeyEvent/VK_CONTROL
                                    KeyEvent/VK_ALT KeyEvent/VK_META}
                                  (.getKeyCode e))
                       (.isControlDown e)
                       (.isMetaDown e)))
              (->> (if (= (.getKeyCode e) KeyEvent/VK_ENTER)
                     (get-normal-state text-area)
                     (get-parinfer-state text-area false))
                   (refresh-content! text-area)
                   (mwm/update-edit-history! edit-history))))
          (keyTyped [this e] nil)
          (keyPressed [this e] nil)))
      ; add a listener to update the cursor position when the mouse is released
      (.addMouseListener text-area
        (reify MouseListener
          (mouseClicked [this e] nil)
          (mouseEntered [this e] nil)
          (mouseExited [this e] nil)
          (mousePressed [this e] nil)
          (mouseReleased [this e]
            (mwm/update-cursor-position! edit-history (get-cursor-position text-area))))))
    (reset! edit-history nil))
  text-area)

(defn create-text-area
  ([]
   (create-text-area (atom nil)))
  ([edit-history]
   (doto (proxy [TextEditorPane] []
           (setMarginLineEnabled [enabled?]
             (proxy-super setMarginLineEnabled enabled?))
           (setMarginLinePosition [size]
             (proxy-super setMarginLinePosition size))
           (processKeyBinding [ks e condition pressed]
             (proxy-super processKeyBinding ks e condition pressed))
{'fraction_non_alphanumeric': 0.14517360479912744, 'fraction_numerical': 0.0009452826758771133, 'mean_word_length': 2.198000232531101, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '226801', 'n_tokens_mistral': 9160, 'n_tokens_neox': 8865, 'n_words': 2092}