mogenslund / liquid

Clojure Text Editor, for editing clojure code and markdown. Written entirely in Clojure with inspiration from Emacs and Vim.
Eclipse Public License 1.0
963 stars 42 forks source link

Use WINCH signal if available to trigger refresh on terminal resize #69

Closed tom-adsfund closed 4 months ago

tom-adsfund commented 1 year ago

In my window manager, i3, the shape of the terminal changes over time, and so if I return to Liquid, it's all jumbled up. Can we just have a running autorefresh, or something similar?

mogenslund commented 1 year ago

I will give it at shot more, soon. As far as I remember it can be difficult to get information about the dimensions of the terminal on runtime. But I will give it another try :-)

tom-adsfund commented 1 year ago

Wouldn't it be easiest to do a periodic refresh whatever happens? Maybe add a toggle to stop it if someone finds there's an issue?

mogenslund commented 1 year ago

Pressing ESC twice usually redraws everything, but it does not take into account, if the terminal has be resized. I will have to recalculate the size of each window. I think periodic refreshes will be a bit annoying since the user will experience a slight flicker when nothing is happening. But if I manage to create a good resize function, it should be possible to make a toggle on the periodic refresh.

tom-adsfund commented 5 months ago

stty size gives the terminal size. Which you're actually using already in tty_output.cljc.

SIGWINCH process signal tells when the terminal has been resized. But Java only has a non-standard mechanism to get it:

; Nice GPT4o solution:
; (:import [sun.misc Signal SignalHandler])

(defn handle-sigwinch [ch]
  (reify SignalHandler
    (handle [_ signal]
      (let [[rows cols] (get-terminal-size)]
        (put! ch {:rows rows :cols cols})))))

(defn redraw-editor [rows cols]
  (println (str "Terminal resized to " rows " rows and " cols " columns")))

(defn main-loop []
  (let [sigwinch-chan (chan)]
    (Signal/handle (Signal. "WINCH") (handle-sigwinch sigwinch-chan))
    (go-loop []
      (let [{:keys [rows cols]} (<! sigwinch-chan)]
        (redraw-editor rows cols)
        (recur)))))

But you can at least hook in stty size to the ESC ESC, and maybe put on a toggleable timer.

tom-adsfund commented 5 months ago

Also:

; (:require [clojure.java.shell :refer [sh]]
(defn get-terminal-size []
  (let [size (sh "stty" "size")]
    (mapv #(Integer/parseInt %) (re-seq #"\d+" (:out size)))))
tom-adsfund commented 4 months ago
#?(:clj
(try ; just ignore if class not found
  (let [
    signal-class (Class/forName "sun.misc.Signal")
    winch-signal (new sun.misc.Signal "WINCH")]
    (.handle sun.misc.Signal winch-signal (reify sun.misc.SignalHandler (handle [_ _] (redraw)))))
  (catch Exception e)))

I've tried it and it works triggering when the terminal size changes.

tom-adsfund commented 4 months ago

This actually doesn't work at all with new versions of Java, which make these internal classes inaccessible.

To solve this in my copy of Liquid, I use the following (while requiring editor):

(defn- future-check-size-change [rs cs]
  (future (Thread/sleep 100)
          (let [rs2 (rows) cs2 (cols)]
            (if (or (not= rs rs2) (not= cs cs2))
              (do (editor/paint-all-buffer-groups) (editor/paint-buffer)))
            (future-check-size-change rs2 cs2))))

(def ^:private checking-size (atom nil))

(defn get-dimensions
  []
  (let [rs (rows) cs (cols)]
    (if (not @checking-size)
      (do
        (reset! checking-size true)
        (future-check-size-change rs cs)))
    {:rows rs :cols cs}))