init
This commit is contained in:
+11
-9
@@ -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
@@ -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
@@ -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
@@ -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 ===
|
||||
|
||||
Reference in New Issue
Block a user