This commit is contained in:
2026-01-21 10:30:07 -05:00
parent a990076b03
commit b14ba33c3a
20 changed files with 3718 additions and 43 deletions
+11 -9
View File
@@ -4,7 +4,7 @@
[tui.input :as input]
[tui.render :as render]
[tui.ansi :as ansi]
[clojure.core.async :as async :refer [go go-loop chan <! >! >!! <!! put! close! timeout alt!]]))
[clojure.core.async :as async :refer [go go-loop chan <! >! >!! <!! put! close! timeout alt! alt!!]]))
;; === Command Types ===
;; nil - no-op
@@ -81,13 +81,15 @@
;; === Input Loop ===
(defn- start-input-loop!
"Start goroutine that reads input and puts messages on channel."
"Start thread that reads input and puts messages on channel.
Uses thread instead of go-loop because input reading is blocking I/O."
[msg-chan running?]
(go-loop []
(when @running?
(when-let [key-msg (input/read-key)]
(>! msg-chan key-msg))
(recur))))
(async/thread
(loop []
(when @running?
(when-let [key-msg (input/read-key)]
(>!! msg-chan key-msg))
(recur)))))
;; === Main Run Loop ===
(defn run
@@ -109,8 +111,8 @@
frame-time (/ 1000 fps)]
;; Setup terminal
(term/init-input!)
(term/raw-mode!)
(term/init-input!)
(when alt-screen (term/alt-screen!))
(term/clear!)
@@ -131,7 +133,7 @@
last-render (System/currentTimeMillis)]
(let [;; Wait for message with timeout for frame limiting
remaining (max 1 (- frame-time (- (System/currentTimeMillis) last-render)))
msg (alt!
msg (alt!!
msg-chan ([v] v)
(timeout remaining) nil)]
+16 -5
View File
@@ -4,6 +4,15 @@
[clojure.string :as str]))
;; === Hiccup Parsing ===
(defn- flatten-children
"Flatten sequences in children (but not vectors, which are hiccup elements)."
[children]
(vec (mapcat (fn [child]
(if (and (sequential? child) (not (vector? child)))
(flatten-children child)
[child]))
children)))
(defn- parse-element
"Parse hiccup element into [tag attrs children]."
[elem]
@@ -14,8 +23,8 @@
(vector? elem)
(let [[tag & rest] elem
[attrs children] (if (map? (first rest))
[(first rest) (vec (next rest))]
[{} (vec rest)])]
[(first rest) (flatten-children (next rest))]
[{} (flatten-children rest)])]
[tag attrs children])
:else [:text {} [(str elem)]]))
@@ -82,7 +91,9 @@
;; Calculate content width
max-content-width (apply max 0 (map ansi/visible-length lines))
inner-width (+ max-content-width pad-left pad-right)
box-width (or width (+ inner-width 2))
;; Title needs: "─ title " = title-length + 3
title-width (if title (+ (count title) 3) 0)
box-width (or width (+ (max inner-width title-width) 2))
content-width (- box-width 2)
;; Pad lines
@@ -101,8 +112,8 @@
;; Build box
top-line (str (:tl chars)
(if title
(str " " title " "
(apply str (repeat (- content-width (count title) 3) (:h chars))))
(str (:h chars) " " title " "
(apply str (repeat (- content-width (count title) 4) (:h chars))))
(apply str (repeat content-width (:h chars))))
(:tr chars))
bottom-line (str (:bl chars)
+1 -1
View File
@@ -35,8 +35,8 @@
:or {alt-screen false}}]
;; Setup terminal
(term/init-input!)
(term/raw-mode!)
(term/init-input!)
(when alt-screen (term/alt-screen!))
(term/clear!)
+24 -8
View File
@@ -1,23 +1,37 @@
(ns tui.terminal
"Terminal management: raw mode, size, input/output."
(:require [tui.ansi :as ansi]
[clojure.java.io :as io]
[clojure.java.shell :refer [sh]])
[clojure.java.io :as io])
(:import [java.io BufferedReader InputStreamReader]))
;; === Terminal State ===
(def ^:private original-stty (atom nil))
(defn- stty [& args]
(let [result (apply sh "stty" (concat args [:in (io/file "/dev/tty")]))]
(when (zero? (:exit result))
(clojure.string/trim (:out result)))))
(let [cmd (concat ["sh" "-c" (str "stty " (clojure.string/join " " args) " </dev/tty")]
(when (empty? args) ["sh" "-c" "stty </dev/tty"]))
pb (ProcessBuilder. ^java.util.List (vec cmd))
_ (.inheritIO pb)
proc (.start pb)
exit (.waitFor proc)]
(when (zero? exit)
"")))
(defn- stty-get [& args]
(let [cmd ["sh" "-c" (str "stty " (clojure.string/join " " args) " </dev/tty")]
pb (ProcessBuilder. ^java.util.List (vec cmd))
_ (.redirectInput pb java.lang.ProcessBuilder$Redirect/INHERIT)
proc (.start pb)
output (slurp (.getInputStream proc))
exit (.waitFor proc)]
(when (zero? exit)
(clojure.string/trim output))))
(defn get-terminal-size
"Get terminal dimensions as [width height]."
[]
(try
(let [result (stty "size")]
(let [result (stty-get "size")]
(when result
(let [[rows cols] (map parse-long (clojure.string/split result #"\s+"))]
{:width cols :height rows})))
@@ -27,7 +41,7 @@
(defn raw-mode!
"Enter raw terminal mode (no echo, no line buffering)."
[]
(reset! original-stty (stty "-g"))
(reset! original-stty (stty-get "-g"))
(stty "raw" "-echo" "-icanon" "min" "1")
(print ansi/hide-cursor)
(flush))
@@ -66,7 +80,9 @@
[s]
(print ansi/cursor-home)
(print ansi/clear-to-end)
(print s)
;; In raw mode, \n only moves down without returning to column 0
;; Replace \n with \r\n to get proper line breaks
(print (clojure.string/replace s "\n" "\r\n"))
(flush))
;; === Input Handling ===