v0.10.1: architectural cleanup — full-frame redraw, explicit bg everywhere, :bg-input fallback
- redraw: always draws all three views (status/chat/input) when any dirty flag is set. Dirty flags only gate frame rendering, not which parts render. Fixes disappearing input/history. - Added :bg-input to all 13 presets with #2e2e2e (dark) / #d4d4d4 (light-amber). theme-load fills missing keys from current preset defaults for backward compatibility. - Removed unused *sidebar-panels* defvar and obsolete contract docs. - Renamed dim-bg → dim-fg (foreground color, not background). - All draw-text calls in sidebar and dialog minibuffer now pass explicit bg-panel, preventing background leaks. - render-styled (markdown renderer) passes explicit (theme-color :bg). - Fix h shadowing in view-chat scroll loop (h → mh).
This commit is contained in:
@@ -1016,17 +1016,8 @@ Returns T on success, nil on failure. Does NOT wait or retry."
|
||||
;; Guard w and h before render (resize or other code may have set them to nil)
|
||||
(setq w (or (and (numberp w) (> w 0) w) 80)
|
||||
h (or (and (numberp h) (> h 0) h) 24))
|
||||
(when (and (or (first (st :dirty)) (second (st :dirty)) (third (st :dirty)))
|
||||
(null (st :dialog-stack)))
|
||||
(cl-tty.backend:begin-sync be)
|
||||
(cl-tty.backend:draw-rect be 0 0 w h :bg (theme-color :bg))
|
||||
(view-status be w h)
|
||||
(view-chat be w h)
|
||||
(view-input be w h)
|
||||
(when (sidebar-visible-p w)
|
||||
(view-sidebar be w h))
|
||||
(cl-tty.backend:end-sync be)
|
||||
(setf (st :dirty) (list nil nil nil)))
|
||||
(unless (st :dialog-stack)
|
||||
(redraw be w h))
|
||||
(let ((ds (st :dialog-stack)))
|
||||
(when ds
|
||||
(cl-tty.backend:begin-sync be)
|
||||
@@ -1038,42 +1029,42 @@ Returns T on success, nil on failure. Does NOT wait or retry."
|
||||
(cnt (length filtered))
|
||||
(filter (cl-tty.select:select-filter sel))
|
||||
(mh (min 15 (+ 1 cnt)))
|
||||
(top (max 0 (- h 4 mh)))
|
||||
(top (max 0 (- h 7 mh)))
|
||||
(bg-p (theme-color :bg-panel))
|
||||
(sep-c (theme-color :separator)))
|
||||
;; Fill minibuffer area with panel bg
|
||||
(dotimes (r (min (- h 3 top) h))
|
||||
(cl-tty.backend:draw-rect be 0 (+ top r) chat-w 1 :bg bg-p))
|
||||
;; Top separator
|
||||
(cl-tty.backend:draw-text be 0 top
|
||||
(make-string chat-w :initial-element #\─)
|
||||
sep-c nil)
|
||||
(cl-tty.backend:draw-text be 1 top
|
||||
(cl-tty.dialog:dialog-title dlg)
|
||||
(theme-color :accent) nil)
|
||||
;; Options
|
||||
(let ((y-off 1))
|
||||
(dolist (item filtered)
|
||||
(let* ((display-idx (first item))
|
||||
(option (third item))
|
||||
(title (getf option :title))
|
||||
(cat (getf option :category))
|
||||
(sel-p (eql display-idx sel-idx))
|
||||
(text (if cat (format nil " ~a" title)
|
||||
(format nil " ~a~a" (if sel-p "▸ " " ") title)))
|
||||
(row (+ top y-off)))
|
||||
(when (>= row (1- h)) (return))
|
||||
(cl-tty.backend:draw-text be 1 row text
|
||||
(cond (cat (theme-color :text-muted))
|
||||
(sel-p (theme-color :accent))
|
||||
(t (theme-color :agent-fg)))
|
||||
nil :bold sel-p)
|
||||
(incf y-off))))
|
||||
;; Filter prompt
|
||||
(cl-tty.backend:draw-rect be 0 (- h 3) chat-w 1 :bg bg-p)
|
||||
(cl-tty.backend:draw-text be 0 (- h 3)
|
||||
(format nil "> ~a" (or filter ""))
|
||||
(theme-color :input-prompt) nil))
|
||||
;; Top separator
|
||||
(cl-tty.backend:draw-text be 0 top
|
||||
(make-string chat-w :initial-element #\─)
|
||||
sep-c bg-p)
|
||||
(cl-tty.backend:draw-text be 1 top
|
||||
(cl-tty.dialog:dialog-title dlg)
|
||||
(theme-color :accent) bg-p)
|
||||
;; Options
|
||||
(let ((y-off 1))
|
||||
(dolist (item filtered)
|
||||
(let* ((display-idx (first item))
|
||||
(option (third item))
|
||||
(title (getf option :title))
|
||||
(cat (getf option :category))
|
||||
(sel-p (eql display-idx sel-idx))
|
||||
(text (if cat (format nil " ~a" title)
|
||||
(format nil " ~a~a" (if sel-p "▸ " " ") title)))
|
||||
(row (+ top y-off)))
|
||||
(when (>= row (1- h)) (return))
|
||||
(cl-tty.backend:draw-text be 1 row text
|
||||
(cond (cat (theme-color :text-muted))
|
||||
(sel-p (theme-color :accent))
|
||||
(t (theme-color :agent-fg)))
|
||||
bg-p :bold sel-p)
|
||||
(incf y-off))))
|
||||
;; Filter prompt
|
||||
(cl-tty.backend:draw-rect be 0 (- h 3) chat-w 1 :bg bg-p)
|
||||
(cl-tty.backend:draw-text be 0 (- h 3)
|
||||
(format nil "> ~a" (or filter ""))
|
||||
(theme-color :input-prompt) bg-p))
|
||||
(cl-tty.backend:end-sync be))
|
||||
(sleep 0.1)))
|
||||
(progn (disconnect-daemon)))))
|
||||
|
||||
@@ -42,13 +42,14 @@ All state mutation flows through event handlers in the controller.
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#7fd88f" :dot-disconnected "#e06c75"
|
||||
:bg-input "#2e2e2e"
|
||||
:error "#e06c75"
|
||||
:tool-running "#fab283" :tool-done "#7fd88f" :tool-error "#e06c75"
|
||||
:separator "#3c3c3c" :accent "#fab283" :dim "#606060")
|
||||
"Dark-neutral color theme with warm amber accent. Backgrounds are dark grays,
|
||||
semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:text-muted, :user-fg/bg/border, :agent-header/fg, :system, :input-prompt/fg,
|
||||
:hint, :status-bg/fg, :dot-connected/disconnected, :error, :tool-*,
|
||||
:hint, :status-bg/fg, :bg-input, :dot-connected/disconnected, :error, :tool-*,
|
||||
:separator, :accent, :dim.")
|
||||
|
||||
(defvar *tui-theme-presets*
|
||||
@@ -60,6 +61,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:hint "#606060"
|
||||
:status-bg "#141414" :status-fg "#e8e8e8"
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#7fd88f" :dot-disconnected "#e06c75"
|
||||
:error "#e06c75"
|
||||
@@ -73,6 +75,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:hint "#606060"
|
||||
:status-bg "#141414" :status-fg "#ffd700"
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#7fd88f" :dot-disconnected "#e06c75"
|
||||
:error "#e06c75"
|
||||
@@ -86,6 +89,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:hint "#606060"
|
||||
:status-bg "#141414" :status-fg "#d4956a"
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#6cb85c" :dot-disconnected "#d94a3a"
|
||||
:error "#d94a3a"
|
||||
@@ -99,6 +103,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:hint "#606060"
|
||||
:status-bg "#141414" :status-fg "#b89870"
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#7aac5c" :dot-disconnected "#c84a3a"
|
||||
:error "#c84a3a"
|
||||
@@ -112,6 +117,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:hint "#606060"
|
||||
:status-bg "#141414" :status-fg "#c8a080"
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#7cb860" :dot-disconnected "#d06050"
|
||||
:error "#d06050"
|
||||
@@ -125,6 +131,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:hint "#606060"
|
||||
:status-bg "#141414" :status-fg "#cc9966"
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#7ab85c" :dot-disconnected "#d94a3a"
|
||||
:error "#d94a3a"
|
||||
@@ -138,6 +145,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:hint "#606060"
|
||||
:status-bg "#141414" :status-fg "#c8a070"
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#7ab85c" :dot-disconnected "#d94a3a"
|
||||
:error "#d94a3a"
|
||||
@@ -150,7 +158,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:input-prompt "#cc6600" :input-fg "#3a2a1a"
|
||||
:hint "#a0a0a0"
|
||||
:status-bg "#ebebeb" :status-fg "#3a2a1a"
|
||||
:bg "#ffffff" :bg-panel "#f5f5f5" :bg-element "#ebebeb"
|
||||
:bg "#ffffff" :bg-panel "#f5f5f5" :bg-element "#ebebeb"
|
||||
:bg-input "#d4d4d4"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#2e8b57" :dot-disconnected "#cc3300"
|
||||
:error "#cc3300"
|
||||
@@ -163,7 +172,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:input-prompt "#fab387" :input-fg "#cdd6f4"
|
||||
:hint "#6c7086"
|
||||
:status-bg "#181825" :status-fg "#a6adc8"
|
||||
:bg "#11111b" :bg-panel "#181825" :bg-element "#1e1e2e"
|
||||
:bg "#11111b" :bg-panel "#181825" :bg-element "#1e1e2e"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#6c7086"
|
||||
:dot-connected "#a6e3a1" :dot-disconnected "#f38ba8"
|
||||
:error "#f38ba8"
|
||||
@@ -176,7 +186,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:input-prompt "#ff9e64" :input-fg "#a9b1d6"
|
||||
:hint "#565f89"
|
||||
:status-bg "#16161e" :status-fg "#9aa5ce"
|
||||
:bg "#0f0f18" :bg-panel "#16161e" :bg-element "#1a1b26"
|
||||
:bg "#0f0f18" :bg-panel "#16161e" :bg-element "#1a1b26"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#565f89"
|
||||
:dot-connected "#9ece6a" :dot-disconnected "#db4b4b"
|
||||
:error "#db4b4b"
|
||||
@@ -189,7 +200,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:input-prompt "#ff9580" :input-fg "#f8f8f2"
|
||||
:hint "#6272a4"
|
||||
:status-bg "#191a24" :status-fg "#e0e0e0"
|
||||
:bg "#0f101a" :bg-panel "#191a24" :bg-element "#1e1f2b"
|
||||
:bg "#0f101a" :bg-panel "#191a24" :bg-element "#1e1f2b"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#6272a4"
|
||||
:dot-connected "#50fa7b" :dot-disconnected "#ff5555"
|
||||
:error "#ff5555"
|
||||
@@ -202,7 +214,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:input-prompt "#87afff" :input-fg "#ffffff"
|
||||
:hint "#606060"
|
||||
:status-bg "#141414" :status-fg "#afafaf"
|
||||
:bg "#000000" :bg-panel "#141414" :bg-element "#1a1a1a"
|
||||
:bg "#000000" :bg-panel "#141414" :bg-element "#1a1a1a"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#d7ffd7" :dot-disconnected "#ff87af"
|
||||
:error "#ff87af"
|
||||
@@ -215,7 +228,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:input-prompt "#ffffff" :input-fg "#d0d0d0"
|
||||
:hint "#606060"
|
||||
:status-bg "#141414" :status-fg "#b0b0b0"
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1a1a1a"
|
||||
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1a1a1a"
|
||||
:bg-input "#2e2e2e"
|
||||
:text-muted "#808080"
|
||||
:dot-connected "#a0a0a0" :dot-disconnected "#808080"
|
||||
:error "#808080"
|
||||
@@ -223,7 +237,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:separator "#303030" :accent "#ffffff" :dim "#505050"))
|
||||
"13 theme presets (amber, gold, terracotta, sepia, nord-warm,
|
||||
monokai-warm, gruvbox-warm, light-amber, catppuccin, tokyonight, dracula,
|
||||
gemini, mono). Now with dark-neutral backgrounds and new :bg/:bg-panel/:bg-element/:text-muted slots.")
|
||||
gemini, mono). Keys: :bg/:bg-panel/:bg-element/:bg-input/:text-muted.")
|
||||
|
||||
(defvar *tui-theme-current-name* :amber
|
||||
"Name of the currently active theme preset.")
|
||||
@@ -240,11 +254,19 @@ gemini, mono). Now with dark-neutral backgrounds and new :bg/:bg-panel/:bg-eleme
|
||||
t))
|
||||
|
||||
(defun theme-load ()
|
||||
"Load persisted theme from disk. Called at startup."
|
||||
"Load persisted theme from disk. Called at startup.
|
||||
Adds any missing keys with defaults to handle saved themes from older versions."
|
||||
(let ((path (merge-pathnames ".cache/passepartout/theme.lisp"
|
||||
(user-homedir-pathname))))
|
||||
(when (uiop:file-exists-p path)
|
||||
(ignore-errors (load path)))))
|
||||
(ignore-errors (load path)))
|
||||
;; Fill in any missing keys from the default preset
|
||||
(let ((defaults (getf *tui-theme-presets* *tui-theme-current-name*)))
|
||||
(when defaults
|
||||
(dolist (key '(:bg-input :bg-element :text-muted))
|
||||
(unless (getf *tui-theme* key)
|
||||
(let ((val (getf defaults key)))
|
||||
(when val (setf (getf *tui-theme* key) val)))))))))
|
||||
|
||||
(defun theme-switch (name)
|
||||
"Switch to a named theme preset. Returns the preset name or nil if not found."
|
||||
@@ -298,18 +320,6 @@ gemini, mono). Now with dark-neutral backgrounds and new :bg/:bg-panel/:bg-eleme
|
||||
:dirty (list nil nil nil))))
|
||||
#+END_SRC
|
||||
|
||||
** Sidebar panel definitions
|
||||
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
|
||||
(defvar *sidebar-panels*
|
||||
'((:id :gate-trace :title "Gate Trace" :width 28)
|
||||
(:id :focus :title "Focus" :width 28)
|
||||
(:id :rules :title "Rules" :width 28)
|
||||
(:id :context :title "Context" :width 28)
|
||||
(:id :cost :title "Cost" :width 28)
|
||||
(:id :files :title "Files" :width 28))
|
||||
"Sidebar panel definitions for cl-tty slot registrations.")
|
||||
#+END_SRC
|
||||
|
||||
** Helpers
|
||||
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
|
||||
(defun now ()
|
||||
|
||||
@@ -8,21 +8,19 @@
|
||||
|
||||
** Contract
|
||||
|
||||
1. (view-status win): renders the status bar with connection info,
|
||||
msg count, scroll offset, rule counter, focus map (v0.4.0), and
|
||||
timestamp. Two lines: line 1 (status + rules), line 2 (focus + time).
|
||||
2. (view-chat win h): renders the scrolled chat message list. Takes
|
||||
window and available height. Messages are color-coded: green (user),
|
||||
white (agent), yellow (system).
|
||||
3. (view-input win): renders the input line with cursor and typing
|
||||
indicator.
|
||||
4. (redraw sw cw ch iw): dispatches redraws based on ~(st :dirty)~
|
||||
flags (status, chat, input). Minimizes terminal writes.
|
||||
5. (char-width ch): returns the terminal column width of character CH.
|
||||
1. (view-status fb w h): no-op. Status bar is a clean black line.
|
||||
2. (view-chat fb w h): renders scrolled chat messages. User messages
|
||||
get amber left border (│), agent messages no border, streaming
|
||||
agent gets grey left border. Gate traces/tool calls use ╎ prefix.
|
||||
3. (view-input fb w h): renders light grey input box (h-7 to h-4),
|
||||
prompt at h-6, right-aligned lowercase hint at h-2.
|
||||
4. (redraw fb w h): wraps view-status/chat/input in begin-sync/end-sync,
|
||||
dispatches per dirty flags, fills global :bg first.
|
||||
5. (char-width ch): returns terminal column width of character CH.
|
||||
ASCII < 128 = 1. CJK, fullwidth, emoji = 2. Combining marks = 0.
|
||||
Tab = 8. Used by word-wrap for accurate line counting (v0.7.0).
|
||||
6. (view-status win): v0.7.0 — timestamp right-aligned at (- chat-w 12)
|
||||
on line 2, focus info at :x 1. No overlap.
|
||||
6. (sidebar-visible-p w): returns T if sidebar should show given width W
|
||||
and current :sidebar-mode (:auto >120, :visible always, :hidden never).
|
||||
|
||||
** Status Bar
|
||||
|
||||
@@ -92,11 +90,11 @@ Returns a list of strings, one per line."
|
||||
(defun view-chat (fb w h)
|
||||
(let* ((w (or (and (numberp w) (> w 0) w) 80))
|
||||
(h (or (and (numberp h) (> h 0) h) 24))
|
||||
(hpad 2)
|
||||
(sidebar-w (if (sidebar-visible-p w) (or (st :sidebar-width) 42) 0))
|
||||
(chat-w (- w sidebar-w))
|
||||
(msgs (st :messages)) (total (length msgs))
|
||||
(max-lines (- h 4)) (is-search (st :search-mode))
|
||||
(hpad 2)
|
||||
(sidebar-w (if (sidebar-visible-p w) (or (st :sidebar-width) 42) 0))
|
||||
(chat-w (- w sidebar-w))
|
||||
(msgs (st :messages)) (total (length msgs))
|
||||
(max-lines (- h 7)) (is-search (st :search-mode))
|
||||
(bordered-w (- chat-w (* 2 hpad) 2))
|
||||
(unbordered-w (- chat-w (* 2 hpad)))
|
||||
(y 0))
|
||||
@@ -113,7 +111,7 @@ Returns a list of strings, one per line."
|
||||
(content (getf msg :content))
|
||||
(cs (if is-search (search-highlight content (st :search-query)) content))
|
||||
(pairs nil)
|
||||
(dim-bg (theme-color :dim))
|
||||
(dim-fg (theme-color :dim))
|
||||
(user-bdr (theme-color :user-border))
|
||||
(user-fg (theme-color :user-fg))
|
||||
(agent-fg (theme-color :agent-fg))
|
||||
@@ -124,7 +122,7 @@ Returns a list of strings, one per line."
|
||||
(push (list "│" user-bdr l user-fg) pairs)))
|
||||
(:agent
|
||||
(let* ((streaming (getf msg :streaming))
|
||||
(bdr-color (if streaming dim-bg nil))
|
||||
(bdr-color (if streaming dim-fg nil))
|
||||
(bdr-str (if streaming "│" ""))
|
||||
(wrap-w (if streaming bordered-w unbordered-w))
|
||||
(nodes (cl-tty.markdown:parse-blocks cs))
|
||||
@@ -138,18 +136,18 @@ Returns a list of strings, one per line."
|
||||
(let ((gt (getf msg :gate-trace)))
|
||||
(when (and gt (eq role :agent))
|
||||
(if (member i (st :collapsed-gates))
|
||||
(push (list "╎" dim-bg (format nil "Gate trace: ~a gates" (length gt)) dim-bg) pairs)
|
||||
(push (list "╎" dim-fg (format nil "Gate trace: ~a gates" (length gt)) dim-fg) pairs)
|
||||
(dolist (entry (passepartout::gate-trace-lines gt))
|
||||
(let ((ec (theme-color (getf (cdr entry) :fgcolor))))
|
||||
(dolist (l (cl-tty.box:word-wrap (car entry) bordered-w))
|
||||
(push (list "╎" dim-bg l ec) pairs)))))))
|
||||
(push (list "╎" dim-fg l ec) pairs)))))))
|
||||
;; Tool calls
|
||||
(let ((tc (getf msg :tool-calls)))
|
||||
(when tc
|
||||
(if (member i (st :collapsed-tools))
|
||||
(let* ((n (or (getf (first tc) :name) "tool"))
|
||||
(d (or (getf (first tc) :duration) 0.0)))
|
||||
(push (list "╎" dim-bg (format nil "~a … ~,1fs" n d) (theme-color :tool-done)) pairs))
|
||||
(push (list "╎" dim-fg (format nil "~a … ~,1fs" n d) (theme-color :tool-done)) pairs))
|
||||
(dolist (call tc)
|
||||
(let* ((name (or (getf call :name) "tool"))
|
||||
(dur (or (getf call :duration) 0.0))
|
||||
@@ -169,16 +167,16 @@ Returns a list of strings, one per line."
|
||||
(let ((msg-count 0) (lines-remaining max-lines))
|
||||
(loop for i from (1- total) downto 0
|
||||
while (> lines-remaining 0)
|
||||
do (let ((h (aref msg-heights i)))
|
||||
(if (<= h lines-remaining)
|
||||
(progn (decf lines-remaining h) (incf msg-count))
|
||||
do (let ((mh (aref msg-heights i)))
|
||||
(if (<= mh lines-remaining)
|
||||
(progn (decf lines-remaining mh) (incf msg-count))
|
||||
(setf lines-remaining 0))))
|
||||
(let* ((scroll-skip (st :scroll-offset))
|
||||
(start (max 0 (- total msg-count scroll-skip))))
|
||||
(loop for i from start below total while (< y (- h 4))
|
||||
do (let ((pairs (aref msg-lines i)))
|
||||
(dolist (pair pairs)
|
||||
(when (>= y (- h 4)) (return))
|
||||
(loop for i from start below total while (< y (- h 7))
|
||||
do (let ((pairs (aref msg-lines i)))
|
||||
(dolist (pair pairs)
|
||||
(when (>= y (- h 7)) (return))
|
||||
(destructuring-bind (bstr bcolor tstr tcolor) pair
|
||||
(let ((has-border (and bstr (> (length bstr) 0))))
|
||||
(when has-border
|
||||
@@ -201,19 +199,16 @@ Returns a list of strings, one per line."
|
||||
(pos (or (st :cursor-pos) 0))
|
||||
(display-start (max 0 (- pos (1- prompt-w))))
|
||||
(visible (subseq text display-start (min (length text) (+ display-start prompt-w))))
|
||||
(bg-p (theme-color :bg-panel))
|
||||
(sep-c (theme-color :separator))
|
||||
(input-fg (theme-color :input-fg))
|
||||
(hint-fg (theme-color :hint)))
|
||||
;; Fill the 2-line input area (separator + prompt) with panel bg, indented by hpad
|
||||
(cl-tty.backend:draw-rect fb hpad (- h 4) inner-w 2 :bg bg-p)
|
||||
;; Separator line within the panel
|
||||
(cl-tty.backend:draw-text fb hpad (- h 4) (make-string inner-w :initial-element #\─) sep-c nil)
|
||||
;; Input line
|
||||
(cl-tty.backend:draw-text fb hpad (- h 3) (format nil"> ~a" visible) input-fg nil)
|
||||
;; Hint line — right-aligned on black background at the very bottom
|
||||
(let ((hint "Ctrl+P | /help"))
|
||||
(cl-tty.backend:draw-text fb (- chat-w (length hint) 2) (- h 1) hint hint-fg (theme-color :bg)))))
|
||||
(bg-i (theme-color :bg-input))
|
||||
(input-fg (theme-color :input-fg))
|
||||
(hint-fg (theme-color :hint)))
|
||||
;; Light grey input panel: h-7 to h-4 (4 rows), indented by hpad
|
||||
(cl-tty.backend:draw-rect fb hpad (- h 7) inner-w 4 :bg bg-i)
|
||||
;; Prompt at h-6, second row at h-5 (placeholder for expansion)
|
||||
(cl-tty.backend:draw-text fb hpad (- h 6) (format nil"> ~a" visible) input-fg nil)
|
||||
;; Hint — lowercase, right-aligned at h-2
|
||||
(let ((hint "ctrl+p | /help"))
|
||||
(cl-tty.backend:draw-text fb (- chat-w (length hint) 2) (- h 2) hint hint-fg (theme-color :bg)))))
|
||||
#+end_src
|
||||
|
||||
** Sidebar
|
||||
@@ -275,14 +270,15 @@ Returns a list of strings, one per line."
|
||||
(defun redraw (fb w h)
|
||||
(setq w (or (and (numberp w) (> w 0) w) 80)
|
||||
h (or (and (numberp h) (> h 0) h) 24))
|
||||
(destructuring-bind (sd cd id) (st :dirty)
|
||||
;; Fill global background
|
||||
(when (or (first (st :dirty)) (second (st :dirty)) (third (st :dirty)))
|
||||
(cl-tty.backend:begin-sync fb)
|
||||
(cl-tty.backend:draw-rect fb 0 0 w h :bg (theme-color :bg))
|
||||
(when sd (view-status fb w h))
|
||||
(when cd (view-chat fb w h))
|
||||
(when id (view-input fb w h))
|
||||
(view-status fb w h)
|
||||
(view-chat fb w h)
|
||||
(view-input fb w h)
|
||||
(when (sidebar-visible-p w)
|
||||
(view-sidebar fb w h))
|
||||
(cl-tty.backend:end-sync fb)
|
||||
(setf (st :dirty) (list nil nil nil))))
|
||||
#+END_SRC
|
||||
|
||||
@@ -380,7 +376,7 @@ dead code.
|
||||
(cl-tty.backend:draw-text fb x y text
|
||||
(cond (url (passepartout.channel-tui:theme-color :accent))
|
||||
(t (passepartout.channel-tui:theme-color (or (getf attrs :role) :agent-fg))))
|
||||
nil
|
||||
(passepartout.channel-tui:theme-color :bg)
|
||||
:bold bold)
|
||||
(incf x (length text))))
|
||||
y)
|
||||
@@ -628,9 +624,10 @@ and current sidebar mode."
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :status-bg)))
|
||||
|
||||
(test test-new-theme-keys
|
||||
"v0.10.0: theme has new :bg, :bg-panel, :bg-element, :text-muted keys."
|
||||
"v0.10.0: theme has new :bg, :bg-panel, :bg-element, :bg-input, :text-muted keys."
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :bg))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :bg-panel))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :bg-element))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :bg-input))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :text-muted)))
|
||||
#+END_SRC
|
||||
|
||||
Reference in New Issue
Block a user