bump passepartout: v0.8.0 TUI upgrade — all 6 items
Minibuffer (dialog stack), conversation view (ScrollBox+Markdown), command palette (Ctrl+P), sidebar (6 panels, Ctrl+B), status bar (degraded-mode signaling), keybinding layer (defkeymap).
This commit is contained in:
@@ -570,9 +570,45 @@
|
||||
(keyword (let ((s (string ch)))
|
||||
(and (= (length s) 1) (char-downcase (char s 0)))))
|
||||
(t nil))))
|
||||
(when (and chr (graphic-char-p chr))
|
||||
(input-insert-char chr)
|
||||
(setf (st :dirty) (list nil nil t)))))))
|
||||
(when (and chr (graphic-char-p chr))
|
||||
(input-insert-char chr)
|
||||
(setf (st :dirty) (list nil nil t))
|
||||
(when (and (char= chr #\/) (null (st :dialog-stack))
|
||||
(= (length (st :input-buffer)) 1))
|
||||
(minibuffer-show-commands)))))))
|
||||
|
||||
;; v0.8.0 — minibuffer dialog for slash commands
|
||||
(defun minibuffer-show-commands ()
|
||||
(let* ((on-select (lambda (opt)
|
||||
(let ((cmd (getf opt :value)))
|
||||
(pop (st :dialog-stack))
|
||||
(setf (st :minibuffer-active) nil)
|
||||
(setf (st :input-buffer) (reverse (coerce cmd 'list)))
|
||||
(setf (st :cursor-pos) 0)
|
||||
(setf (st :dirty) (list nil nil t)))))
|
||||
(sel (cl-tty.select:make-select :options *slash-commands* :on-select on-select))
|
||||
(dlg (make-instance 'cl-tty.dialog:dialog
|
||||
:title "Commands"
|
||||
:content sel)))
|
||||
(push dlg (st :dialog-stack))
|
||||
(setf (st :minibuffer-active) t)))
|
||||
|
||||
;; v0.8.0 — command palette for daemon commands (Ctrl+P)
|
||||
(defun command-palette-show-commands ()
|
||||
(let* ((on-select (lambda (opt)
|
||||
(let ((cmd (getf opt :value)))
|
||||
(pop (st :dialog-stack))
|
||||
(setf (st :command-palette-active) nil)
|
||||
(add-msg :system (format nil "Dispatching: ~s" cmd))
|
||||
(send-daemon (list :type :event :payload cmd))
|
||||
(setf (st :busy) t)
|
||||
(setf (st :dirty) (list t t nil)))))
|
||||
(sel (cl-tty.select:make-select :options *daemon-commands* :on-select on-select))
|
||||
(dlg (make-instance 'cl-tty.dialog:dialog
|
||||
:title "Command Palette"
|
||||
:content sel)))
|
||||
(push dlg (st :dialog-stack))
|
||||
(setf (st :command-palette-active) t)))
|
||||
|
||||
;; v0.7.2 — resolve-hitl-panel: marks panel as resolved after approve/deny
|
||||
(defun resolve-hitl-panel (decision)
|
||||
@@ -601,12 +637,10 @@
|
||||
(cond
|
||||
;; New headline
|
||||
((and (>= (length trimmed) 2) (eql (char trimmed 0) #\*))
|
||||
;; Flush previous section if in one
|
||||
(when (and in-section section-content)
|
||||
(push (cons in-section (string-trim '(#\Space #\Newline)
|
||||
(format nil "~{~a~^ ~}" (reverse section-content))))
|
||||
results))
|
||||
;; Check if this headline matches topic
|
||||
(let ((title (string-trim '(#\Space #\*) trimmed)))
|
||||
(if (search topic title :test #'char-equal)
|
||||
(setf in-section title
|
||||
@@ -618,7 +652,6 @@
|
||||
(when (and (> (length trimmed) 0)
|
||||
(not (eql (char trimmed 0) #\#)))
|
||||
(push trimmed section-content))))))
|
||||
;; Flush last section
|
||||
(when (and in-section section-content)
|
||||
(push (cons in-section (string-trim '(#\Space #\Newline)
|
||||
(format nil "~{~a~^ ~}" (reverse section-content))))
|
||||
@@ -770,6 +803,32 @@
|
||||
(setf (st :stream) nil (st :connected) nil)
|
||||
(add-msg :system "* Disconnected *")))
|
||||
|
||||
;; v0.8.0 — Global keymap
|
||||
(eval-when (:load-toplevel :execute)
|
||||
(cl-tty.input:defkeymap :global
|
||||
(:ctrl+q (lambda (e) (declare (ignore e))
|
||||
(setf (st :running) nil)))
|
||||
(:ctrl+p (lambda (e) (declare (ignore e))
|
||||
(command-palette-show-commands)))
|
||||
(:ctrl+b (lambda (e) (declare (ignore e))
|
||||
(setf (st :sidebar-visible) (not (st :sidebar-visible)))
|
||||
(setf (st :dirty) (list t t nil))))
|
||||
(:ppage (lambda (e) (declare (ignore e))
|
||||
(let ((max-offset (max 0 (- (length (st :messages)) 1))))
|
||||
(setf (st :scroll-offset) (min max-offset (+ (st :scroll-offset) 10))))
|
||||
(setf (st :dirty) (list nil t nil))))
|
||||
(:npage (lambda (e) (declare (ignore e))
|
||||
(setf (st :scroll-offset) (max 0 (- (st :scroll-offset) 10)))
|
||||
(setf (st :dirty) (list nil t nil))))))
|
||||
|
||||
;; v0.8.0 — Prompt/local keymap (for when input is active)
|
||||
(eval-when (:load-toplevel :execute)
|
||||
(cl-tty.input:defkeymap :local
|
||||
(:enter (lambda (e) (declare (ignore e)) (on-key :enter)))
|
||||
(:up (lambda (e) (declare (ignore e)) (on-key :up)))
|
||||
(:down (lambda (e) (declare (ignore e)) (on-key :down)))
|
||||
(:escape (lambda (e) (declare (ignore e)) (on-key :escape)))))
|
||||
|
||||
(defun tui-main ()
|
||||
(init-state)
|
||||
(load-history)
|
||||
@@ -819,35 +878,74 @@
|
||||
(cl-tty.input:key-event
|
||||
(cl-tty.input:key-event-key data))
|
||||
(t data))))
|
||||
(cond
|
||||
((eql ch :escape)
|
||||
(when (st :streaming-text)
|
||||
(send-daemon (list :type :event :payload '(:action :cancel-stream)))
|
||||
(when (> (length (st :messages)) 0)
|
||||
(let ((idx (1- (length (st :messages)))))
|
||||
(setf (getf (aref (st :messages) idx) :content)
|
||||
(concatenate 'string
|
||||
(getf (aref (st :messages) idx) :content)
|
||||
" [interrupted]"))
|
||||
(setf (getf (aref (st :messages) idx) :streaming) nil)
|
||||
(setf (getf (aref (st :messages) idx) :time) (now))))
|
||||
(setf (st :streaming-text) nil)
|
||||
(setf (st :busy) nil)
|
||||
(setf (st :dirty) (list t t nil)))
|
||||
(when (st :search-mode)
|
||||
(setf (st :search-mode) nil
|
||||
(st :search-matches) nil
|
||||
(st :search-query) "")
|
||||
(setf (st :dirty) (list nil t nil))
|
||||
(add-msg :system "Search exited")))
|
||||
(t (on-key ch)))))))
|
||||
(when (or (first (st :dirty)) (second (st :dirty)) (third (st :dirty)))
|
||||
(cl-tty.backend:backend-clear be)
|
||||
(redraw curr-fb w h)
|
||||
(cl-tty.rendering:flush-framebuffer prev-fb curr-fb be)
|
||||
(rotatef prev-fb curr-fb))
|
||||
(sleep 0.1))))
|
||||
(disconnect-daemon))))
|
||||
(cond
|
||||
((st :dialog-stack)
|
||||
(let* ((dlg (car (st :dialog-stack)))
|
||||
(sel (cl-tty.dialog:dialog-content dlg)))
|
||||
(cond
|
||||
((eql ch :escape)
|
||||
(pop (st :dialog-stack))
|
||||
(setf (st :minibuffer-active) nil)
|
||||
(setf (st :command-palette-active) nil)
|
||||
(setf (st :dirty) (list t t nil)))
|
||||
((member ch '(:up :down))
|
||||
(if (eql ch :up) (cl-tty.select:select-prev sel)
|
||||
(cl-tty.select:select-next sel)))
|
||||
((member ch '(:enter 13 10 #\Newline #\Return))
|
||||
(let* ((filtered (cl-tty.select:select-filtered-options sel))
|
||||
(idx (cl-tty.select:select-selected-index sel))
|
||||
(item (when (< idx (length filtered))
|
||||
(third (nth idx filtered)))))
|
||||
(when item
|
||||
(let ((cb (cl-tty.select:select-on-select sel)))
|
||||
(when cb (funcall cb item))))))
|
||||
((and (characterp ch) (graphic-char-p ch))
|
||||
(setf (cl-tty.select:select-filter sel)
|
||||
(concatenate 'string (or (cl-tty.select:select-filter sel) "") (string ch))))
|
||||
((member ch '(:backspace 127 8))
|
||||
(let ((f (cl-tty.select:select-filter sel)))
|
||||
(when (> (length f) 0)
|
||||
(setf (cl-tty.select:select-filter sel) (subseq f 0 (1- f)))))))))
|
||||
((cl-tty.input:dispatch-key-event data)
|
||||
nil)
|
||||
(t (on-key ch)))))))
|
||||
(when (or (first (st :dirty)) (second (st :dirty)) (third (st :dirty)))
|
||||
(cl-tty.backend:backend-clear be)
|
||||
(redraw curr-fb w h)
|
||||
(cl-tty.rendering:flush-framebuffer prev-fb curr-fb be)
|
||||
(rotatef prev-fb curr-fb))
|
||||
(let ((ds (st :dialog-stack)))
|
||||
(when ds
|
||||
(let* ((dlg (car ds))
|
||||
(sel (cl-tty.dialog:dialog-content dlg))
|
||||
(filtered (cl-tty.select:select-filtered-options sel))
|
||||
(sel-idx (cl-tty.select:select-selected-index sel))
|
||||
(cnt (length filtered))
|
||||
(dw 60) (dh (min 20 (+ 4 cnt)))
|
||||
(mx (floor (- w dw) 2))
|
||||
(my (floor (- h dh) 2)))
|
||||
(dotimes (row h)
|
||||
(cl-tty.backend:draw-rect be 0 row w 1 :bg :bright-black))
|
||||
(cl-tty.backend:draw-border be mx my dw dh :style :single
|
||||
:title (cl-tty.dialog:dialog-title dlg))
|
||||
(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" sel-p title))))
|
||||
(when (>= y-off (1- dh)) (return))
|
||||
(cl-tty.backend:draw-text be (1+ mx) (+ my y-off) text
|
||||
(cond (cat (theme-color :dim))
|
||||
(sel-p (theme-color :highlight))
|
||||
(t (theme-color :agent)))
|
||||
nil :bold sel-p)
|
||||
(incf y-off)))))))
|
||||
(sleep 0.1))))
|
||||
(disconnect-daemon))))
|
||||
|
||||
(eval-when (:compile-toplevel :load-toplevel :execute)
|
||||
(ql:quickload :fiveam :silent t))
|
||||
@@ -1323,3 +1421,25 @@
|
||||
(setf (st :scroll-offset) 3)
|
||||
(on-key :npage)
|
||||
(fiveam:is (= 0 (st :scroll-offset))))
|
||||
|
||||
;; ── v0.8.0 Minibuffer ──
|
||||
|
||||
(fiveam:test test-slash-commands-defined
|
||||
"Contract v0.8.0: *slash-commands* is non-nil list of option plists."
|
||||
(fiveam:is (listp passepartout.channel-tui::*slash-commands*))
|
||||
(fiveam:is (> (length passepartout.channel-tui::*slash-commands*) 0))
|
||||
(fiveam:is (every (lambda (opt)
|
||||
(and (getf opt :title) (getf opt :value) (getf opt :category)))
|
||||
passepartout.channel-tui::*slash-commands*)))
|
||||
|
||||
(fiveam:test test-minibuffer-state
|
||||
"Contract v0.8.0: init-state has :dialog-stack and :minibuffer-active fields."
|
||||
(init-state)
|
||||
(fiveam:is (null (st :dialog-stack)))
|
||||
(fiveam:is (null (st :minibuffer-active))))
|
||||
|
||||
(fiveam:test test-command-palette-state
|
||||
"Contract v0.8.0: init-state has :command-palette-active and :command-palette-dialog as nil."
|
||||
(init-state)
|
||||
(fiveam:is (null (st :command-palette-active)))
|
||||
(fiveam:is (null (st :command-palette-dialog))))
|
||||
|
||||
Reference in New Issue
Block a user