Skip to content

Instantly share code, notes, and snippets.

@yibie
Created August 10, 2026 22:57
Show Gist options
  • Select an option

  • Save yibie/67d2bfeedda162751a3016eef220a4aa to your computer and use it in GitHub Desktop.

Select an option

Save yibie/67d2bfeedda162751a3016eef220a4aa to your computer and use it in GitHub Desktop.
VUI btop-like state and incremental-rendering prototype from the TextUI experiments (GPL-3.0-or-later)
;;; vui-btop-state-prototype.el --- VUI btop state experiment -*- lexical-binding: t; -*-
;; PROTOTYPE QUESTION: Does VUI materially reduce the state, async, and
;; lifecycle plumbing needed by a live btop-like Emacs UI?
;;
;; PARITY TARGET: examples/textui-btop-prototype.el. This version keeps the
;; same live data, four responsive boxes, process interactions, keyboard and
;; mouse controls, pause behavior, and cleanup. VUI horizontal layouts assume
;; single-line children, so side-by-side multi-line panels are combined one row
;; at a time; that compatibility cost intentionally remains visible here.
;;
;; Run:
;; emacs -Q -L /path/to/vui.el -l examples/vui-btop-state-prototype.el
;;; Code:
(require 'cl-lib)
(require 'subr-x)
(require 'vui)
(defconst vui-btop-state-prototype--buffer-name
"*VUI btop STATE PROTOTYPE*")
(defconst vui-btop-state-prototype--sample-command
'("/bin/sh" "-c"
"/bin/ps -axo pid=,pcpu=,rss=,user=,state=,etime=,comm=; printf '\n__VUI_VM__\n'; /usr/bin/vm_stat; printf '\n__VUI_NET__\n'; /usr/sbin/netstat -ibn; printf '\n__VUI_SYSTEM__\n'; /usr/sbin/sysctl -n hw.memsize; /usr/sbin/sysctl -n machdep.cpu.brand_string"))
(defconst vui-btop-state-prototype--interval 1.0)
(defconst vui-btop-state-prototype--border-face
'(:foreground "#4d8f87"))
(defconst vui-btop-state-prototype--accent-face
'(:foreground "#9ece6a" :weight bold))
(defconst vui-btop-state-prototype--memory-face
'(:foreground "#ff5d8f" :weight bold))
(defconst vui-btop-state-prototype--disk-face
'(:foreground "#ffc777" :weight bold))
(defconst vui-btop-state-prototype--net-face
'(:foreground "#65d1ff" :weight bold))
(defconst vui-btop-state-prototype--upload-face
'(:foreground "#c099ff" :weight bold))
(defconst vui-btop-state-prototype--process-face
'(:foreground "#ffc777" :weight bold))
(defconst vui-btop-state-prototype--muted-face
'(:foreground "#7f8ea3"))
(defconst vui-btop-state-prototype--selected-face
'(:background "#365f9d" :foreground "#ffffff" :weight bold))
(vui-defcomponent vui-btop-state-prototype-line (node keymap)
"Render one memoized frame line for experimental incremental updates."
:memo t
:render (vui-vstack :keymap keymap node))
(defun vui-btop-state-prototype--empty-snapshot ()
"Return initial live-data state."
(list :tick 0
:cpu-history nil :download-history nil :upload-history nil
:processes nil :cpu 0.0 :cpu-model "sampling…"
:load-average '(0.0 0.0 0.0)
:memory-total 0 :memory-used 0 :memory-free 0 :memory-cached 0
:disk-total 0 :disk-used 0 :disk-free 0
:download-rate 0.0 :upload-rate 0.0
:download-total 0 :upload-total 0
:sample-ms 0.0 :sampled-at "waiting for first sample"))
(defun vui-btop-state-prototype--fit (string width)
"Return STRING truncated and padded to WIDTH display cells."
(let* ((width (max 0 width))
(value (truncate-string-to-width string width nil nil "…")))
(concat value (make-string (max 0 (- width (string-width value))) ?\s))))
(defun vui-btop-state-prototype--vm-pages (text label)
"Return page count for LABEL in vm_stat TEXT."
(if (string-match
(format "^%s:[[:space:]]+\\([0-9]+\\)\\."
(regexp-quote label))
text)
(string-to-number (match-string 1 text))
0))
(defun vui-btop-state-prototype--parse-processes (text)
"Return (CPU . PROCESSES) parsed from ps TEXT."
(let ((cpu-total 0.0)
rows)
(dolist (line (split-string text "\n" t))
(when (string-match
"^[[:space:]]*\\([0-9]+\\)[[:space:]]+\\([0-9.]+\\)[[:space:]]+\\([0-9]+\\)[[:space:]]+\\([^[:space:]]+\\)[[:space:]]+\\([^[:space:]]+\\)[[:space:]]+\\([^[:space:]]+\\)[[:space:]]+\\(.+\\)$"
line)
(let* ((cpu (string-to-number (match-string 2 line)))
(command (string-trim (match-string 7 line)))
(name (file-name-nondirectory command)))
(setq cpu-total (+ cpu-total cpu))
(push (list :pid (string-to-number (match-string 1 line))
:cpu cpu
:mem (round (/ (string-to-number (match-string 3 line))
1024.0))
:user (match-string 4 line)
:state (match-string 5 line)
:etime (match-string 6 line)
:command command
:name (if (string-empty-p name) command name))
rows))))
(cons (min 100.0 (/ cpu-total (max 1 (num-processors))))
(nreverse rows))))
(defun vui-btop-state-prototype--parse-network (text previous)
"Return network values parsed from TEXT relative to PREVIOUS totals."
(let ((line
(cl-find-if
(lambda (candidate)
(and (string-prefix-p "en0 " candidate)
(string-match-p "<Link#" candidate)))
(split-string text "\n" t))))
(if (not line)
(list :rate-down 0.0 :rate-up 0.0 :total-down 0 :total-up 0
:sample previous)
(let* ((fields (split-string line "[[:space:]]+" t))
(download (string-to-number (or (nth 6 fields) "0")))
(upload (string-to-number (or (nth 9 fields) "0")))
(now (float-time))
(elapsed (and previous (- now (plist-get previous :time))))
(rate-down
(if (and elapsed (> elapsed 0))
(max 0.0 (/ (- download (plist-get previous :download))
elapsed))
0.0))
(rate-up
(if (and elapsed (> elapsed 0))
(max 0.0 (/ (- upload (plist-get previous :upload))
elapsed))
0.0)))
(list :rate-down rate-down :rate-up rate-up
:total-down download :total-up upload
:sample (list :time now :download download :upload upload))))))
(defun vui-btop-state-prototype--parse-output
(text previous previous-network sample-ms)
"Return (SNAPSHOT . NETWORK-SAMPLE) parsed from sampler TEXT.
PREVIOUS supplies graph history, PREVIOUS-NETWORK supplies rate totals, and
SAMPLE-MS is the sampling duration."
(let* ((vm-marker "\n__VUI_VM__\n")
(net-marker "\n__VUI_NET__\n")
(system-marker "\n__VUI_SYSTEM__\n")
(vm-start (string-match (regexp-quote vm-marker) text))
(net-start (and vm-start
(string-match (regexp-quote net-marker) text
(+ vm-start (length vm-marker)))))
(system-start (and net-start
(string-match (regexp-quote system-marker) text
(+ net-start (length net-marker))))))
(unless (and vm-start net-start system-start)
(error "VUI btop sampler returned incomplete output"))
(let* ((process-data
(vui-btop-state-prototype--parse-processes
(substring text 0 vm-start)))
(vm (substring text (+ vm-start (length vm-marker)) net-start))
(net (substring text (+ net-start (length net-marker)) system-start))
(system (split-string
(substring text (+ system-start (length system-marker)))
"\n" t))
(memory-total (string-to-number (or (nth 0 system) "0")))
(page-size
(if (string-match "page size of \\([0-9]+\\) bytes" vm)
(string-to-number (match-string 1 vm))
0))
(free-pages
(+ (vui-btop-state-prototype--vm-pages vm "Pages free")
(vui-btop-state-prototype--vm-pages vm "Pages inactive")
(vui-btop-state-prototype--vm-pages vm "Pages speculative")))
(memory-free (min memory-total (* page-size free-pages)))
(memory-cached
(* page-size
(vui-btop-state-prototype--vm-pages vm "File-backed pages")))
(network
(vui-btop-state-prototype--parse-network net previous-network))
(disk (file-system-info "/"))
(disk-total (or (nth 0 disk) 0))
(disk-free (or (nth 1 disk) 0))
(cpu (car process-data))
(down (plist-get network :rate-down))
(up (plist-get network :rate-up)))
(cons
(list :tick (1+ (plist-get previous :tick))
:cpu-history
(vui-btop-state-prototype--append-sample
(plist-get previous :cpu-history) cpu)
:download-history
(vui-btop-state-prototype--append-sample
(plist-get previous :download-history) down)
:upload-history
(vui-btop-state-prototype--append-sample
(plist-get previous :upload-history) up)
:processes (cdr process-data)
:cpu cpu :cpu-model (or (nth 1 system) "unknown CPU")
:load-average (load-average t)
:memory-total memory-total
:memory-used (max 0 (- memory-total memory-free))
:memory-free memory-free :memory-cached memory-cached
:disk-total disk-total
:disk-used (max 0 (- disk-total disk-free))
:disk-free disk-free
:download-rate down :upload-rate up
:download-total (plist-get network :total-down)
:upload-total (plist-get network :total-up)
:sample-ms sample-ms
:sampled-at (format-time-string "%H:%M:%S"))
(plist-get network :sample)))))
(defun vui-btop-state-prototype--append-sample (history value)
"Append VALUE to HISTORY, retaining its newest 120 values."
(let ((next (append history (list value))))
(if (> (length next) 120) (cdr next) next)))
(defun vui-btop-state-prototype--graph-lines
(values width height face &optional maximum)
"Render VALUES as WIDTH by HEIGHT graph lines using FACE.
FACE may be a top-to-bottom vector. MAXIMUM defaults to 100."
(let* ((width (max 1 width))
(height (max 1 height))
(maximum (max 1.0 (or maximum 100.0)))
(visible (last values (min width (length values))))
(visible (append (make-list (- width (length visible)) 0) visible)))
(cl-loop
for row below height
collect
(let ((row-face
(if (vectorp face)
(aref face (min (1- (length face))
(/ (* row (length face)) height)))
face))
chars)
(dolist (value visible)
(let* ((filled (round (* (max 0.0 (min maximum value))
height 8.0 (/ 1.0 maximum))))
(level (max 0 (min 8 (- filled (* (- height row 1) 8))))))
(push (aref " ▁▂▃▄▅▆▇█" level) chars)))
(propertize (concat (nreverse chars)) 'face row-face)))))
(defun vui-btop-state-prototype--bar (value width face)
"Return a VALUE-percent bar of WIDTH using FACE."
(let* ((width (max 1 width))
(filled (min width (round (* width (/ value 100.0))))))
(concat (propertize (make-string filled ?█) 'face face)
(propertize (make-string (- width filled) ?░)
'face vui-btop-state-prototype--muted-face))))
(defun vui-btop-state-prototype--percent (part total)
"Return PART as a percentage of TOTAL."
(if (> total 0) (min 100.0 (* 100.0 (/ (float part) total))) 0.0))
(defun vui-btop-state-prototype--window-size ()
"Return current target window size as (WIDTH . HEIGHT)."
(let ((window (or (get-buffer-window (current-buffer) t)
(selected-window))))
(cons (max 1 (1- (window-body-width window)))
(window-body-height window))))
(defun vui-btop-state-prototype--title-line (width key title right face)
"Return btop-style top border using KEY, TITLE, RIGHT, and FACE."
(let* ((inside (max 0 (- width 2)))
(left (format "─%s%s" (if key (format "%s" key) "") title))
(right (if (string-empty-p right) "" (format "%s─" right)))
(room (max 0 (- inside (string-width right))))
(left (truncate-string-to-width left room nil nil "…")))
(propertize
(concat "╭" left (make-string (max 0 (- room (string-width left))) ?─)
right "╮")
'face face)))
(defun vui-btop-state-prototype--inside-line (width text)
"Return TEXT inside a WIDTH frame."
(concat (propertize "│" 'face vui-btop-state-prototype--border-face)
(vui-btop-state-prototype--fit text (max 0 (- width 2)))
(propertize "│" 'face vui-btop-state-prototype--border-face)))
(defun vui-btop-state-prototype--separator-line (width label face)
"Return a framed separator containing LABEL."
(let* ((inside (max 0 (- width 2)))
(label (truncate-string-to-width (format "─%s" label) inside)))
(propertize
(concat "├" label (make-string (- inside (string-width label)) ?─) "┤")
'face face)))
(defun vui-btop-state-prototype--bottom-line (width suffix face)
"Return WIDTH bottom border ending in SUFFIX with FACE."
(let* ((inside (max 0 (- width 2)))
(suffix (if (string-empty-p suffix) "" (format "%s─" suffix)))
(suffix (truncate-string-to-width suffix inside)))
(propertize
(concat "╰" (make-string (- inside (string-width suffix)) ?─) suffix "╯")
'face face)))
(defun vui-btop-state-prototype--text (value &optional face key)
"Return a VUI text node showing VALUE with optional FACE and KEY."
(vui-text value :face face :key key))
(defun vui-btop-state-prototype--combine-lines (left right)
"Combine equally tall LEFT and RIGHT line nodes horizontally."
(cl-mapcar (lambda (a b) (vui-hstack :spacing 0 a b)) left right))
(defun vui-btop-state-prototype--cpu-panel (snapshot width height)
"Return live CPU panel lines for SNAPSHOT at WIDTH by HEIGHT."
(let* ((inside (max 1 (- width 2)))
(slots (max 1 (- height 2)))
(stats-width (if (>= width 90) (min 31 (/ width 4)) 20))
(graph-width (max 8 (- inside stats-width 1)))
(cpu (or (car (last (plist-get snapshot :cpu-history))) 0))
(graphs
(vui-btop-state-prototype--graph-lines
(plist-get snapshot :cpu-history) graph-width slots
(vector vui-btop-state-prototype--accent-face
'(:foreground "#e0af68" :weight bold)
'(:foreground "#9ece6a" :weight bold))))
(top-processes
(sort (copy-sequence (plist-get snapshot :processes))
(lambda (left right)
(> (plist-get left :cpu) (plist-get right :cpu)))))
lines)
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--title-line
width "1" "cpu" "1000ms +" vui-btop-state-prototype--accent-face))
lines)
(dotimes (row slots)
(let* ((process (and (>= row 2) (nth (- row 2) top-processes)))
(load (plist-get snapshot :load-average))
(stats
(cond
((= row 0)
(format " %s %d cores"
(plist-get snapshot :cpu-model) (num-processors)))
((= row 1) (format " CPU %5.1f%%" cpu))
((= row (1- slots))
(format " Load AVG: %.2f %.2f %.2f"
(or (nth 0 load) 0.0) (or (nth 1 load) 0.0)
(or (nth 2 load) 0.0)))
(process
(format " %-16s %5.1f%%"
(plist-get process :name) (plist-get process :cpu)))
(t ""))))
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--inside-line
width
(concat (nth row graphs) " "
(vui-btop-state-prototype--fit stats stats-width))))
lines)))
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--bottom-line
width
(format "sample %d: %.0fms" (plist-get snapshot :tick)
(plist-get snapshot :sample-ms))
vui-btop-state-prototype--border-face))
lines)
(nreverse lines)))
(defun vui-btop-state-prototype--simple-panel
(width height key title right content face)
"Return a framed panel of CONTENT lines at WIDTH by HEIGHT."
(let ((slots (max 0 (- height 2)))
lines)
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--title-line
width key title right face))
lines)
(dotimes (row slots)
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--inside-line
width (or (nth row content) "")))
lines))
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--bottom-line width "" face))
lines)
(nreverse lines)))
(defun vui-btop-state-prototype--memory-lines (snapshot width)
"Return memory data from SNAPSHOT fitted for WIDTH."
(let* ((bar-width (max 5 (- width 13)))
(used (vui-btop-state-prototype--percent
(plist-get snapshot :memory-used)
(plist-get snapshot :memory-total)))
(free (vui-btop-state-prototype--percent
(plist-get snapshot :memory-free)
(plist-get snapshot :memory-total))))
(list (format " Total: %s"
(file-size-human-readable (plist-get snapshot :memory-total)))
(format " Used: %s"
(file-size-human-readable (plist-get snapshot :memory-used)))
(concat " " (vui-btop-state-prototype--bar
used bar-width vui-btop-state-prototype--memory-face)
(format " %.0f%%" used))
(format " Available: %s"
(file-size-human-readable (plist-get snapshot :memory-free)))
(concat " " (vui-btop-state-prototype--bar
free bar-width vui-btop-state-prototype--disk-face)
(format " %.0f%%" free))
(format " Cached: %s"
(file-size-human-readable (plist-get snapshot :memory-cached)))
(format " Free: %s"
(file-size-human-readable (plist-get snapshot :memory-free))))))
(defun vui-btop-state-prototype--disk-lines (snapshot width)
"Return disk data from SNAPSHOT fitted for WIDTH."
(let* ((bar-width (max 5 (- width 15)))
(used (vui-btop-state-prototype--percent
(plist-get snapshot :disk-used)
(plist-get snapshot :disk-total))))
(list (format " / %s"
(file-size-human-readable (plist-get snapshot :disk-total)))
(concat " " (vui-btop-state-prototype--bar
used bar-width vui-btop-state-prototype--memory-face)
(format " %.0f%%" used))
(format " Used: %s"
(file-size-human-readable (plist-get snapshot :disk-used)))
(format " Free: %s"
(file-size-human-readable (plist-get snapshot :disk-free)))
" Filesystem: apfs"
" Source: file-system-info"
"")))
(defun vui-btop-state-prototype--memory-group (snapshot width height)
"Return memory and disk panel lines inside WIDTH by HEIGHT."
(if (>= width 62)
(let* ((memory-width (/ width 2))
(disk-width (- width memory-width)))
(vui-btop-state-prototype--combine-lines
(vui-btop-state-prototype--simple-panel
memory-width height "2" "mem" ""
(vui-btop-state-prototype--memory-lines snapshot memory-width)
vui-btop-state-prototype--memory-face)
(vui-btop-state-prototype--simple-panel
disk-width height nil "disks" "io"
(vui-btop-state-prototype--disk-lines snapshot disk-width)
vui-btop-state-prototype--disk-face)))
(vui-btop-state-prototype--simple-panel
width height "2" "mem + disks" ""
(append (cl-subseq (vui-btop-state-prototype--memory-lines snapshot width)
0 4)
(cl-subseq (vui-btop-state-prototype--disk-lines snapshot width)
0 3))
vui-btop-state-prototype--memory-face)))
(defun vui-btop-state-prototype--network-lines (snapshot width height)
"Return HEIGHT live network graph lines from SNAPSHOT at WIDTH."
(let* ((inside (max 1 (- width 2)))
(stats-width (if (>= width 48) 22 14))
(graph-width (max 5 (- inside stats-width 1)))
(half (max 1 (/ height 2)))
(down-history (plist-get snapshot :download-history))
(up-history (plist-get snapshot :upload-history))
(down-graph
(vui-btop-state-prototype--graph-lines
down-history graph-width half vui-btop-state-prototype--net-face
(if down-history (apply #'max down-history) 1.0)))
(up-graph
(vui-btop-state-prototype--graph-lines
up-history graph-width (- height half)
vui-btop-state-prototype--upload-face
(if up-history (apply #'max up-history) 1.0)))
(graphs (append down-graph up-graph)))
(cl-loop
for row below height
collect
(let ((stats
(cond
((= row 0) "▼ download")
((= row 1)
(format "▼ %s/s"
(file-size-human-readable
(plist-get snapshot :download-rate))))
((= row (1- half))
(format "▼ Total: %s"
(file-size-human-readable
(plist-get snapshot :download-total))))
((= row half) "▲ upload")
((= row (1+ half))
(format "▲ %s/s"
(file-size-human-readable
(plist-get snapshot :upload-rate))))
((= row (1- height))
(format "▲ Total: %s"
(file-size-human-readable
(plist-get snapshot :upload-total))))
(t ""))))
(concat (nth row graphs) " "
(vui-btop-state-prototype--fit stats stats-width))))))
(defun vui-btop-state-prototype--network-panel (snapshot width height)
"Return live network panel lines from SNAPSHOT at WIDTH by HEIGHT."
(vui-btop-state-prototype--simple-panel
width height "3" "net" "en0"
(vui-btop-state-prototype--network-lines snapshot width (max 0 (- height 2)))
vui-btop-state-prototype--net-face))
(defun vui-btop-state-prototype--ordered-processes
(snapshot sort-index reversed filter)
"Return filtered and sorted SNAPSHOT processes for UI state."
(let* ((filtered
(if (string-empty-p filter)
(copy-sequence (plist-get snapshot :processes))
(cl-remove-if-not
(lambda (process)
(string-match-p (regexp-quote filter)
(downcase (plist-get process :name))))
(plist-get snapshot :processes))))
(key (aref [cpu memory pid] sort-index))
(sorted
(sort filtered
(lambda (left right)
(> (pcase key
('cpu (plist-get left :cpu))
('memory (plist-get left :mem))
(_ (plist-get left :pid)))
(pcase key
('cpu (plist-get right :cpu))
('memory (plist-get right :mem))
(_ (plist-get right :pid))))))))
(if reversed (nreverse sorted) sorted)))
(defun vui-btop-state-prototype--process-header (width)
"Return process table header fitted to WIDTH."
(if (>= width 72)
(vui-btop-state-prototype--fit
" PID Program Command State User MemB Cpu%"
width)
(vui-btop-state-prototype--fit
" PID Program MemB Cpu%" width)))
(defun vui-btop-state-prototype--process-label (process width)
"Return one responsive PROCESS row for WIDTH."
(if (>= width 72)
(format "%6d %-18s %-28s %-5s %-9s %5d %5.1f"
(plist-get process :pid) (plist-get process :name)
(plist-get process :command) (plist-get process :state)
(plist-get process :user) (plist-get process :mem)
(float (plist-get process :cpu)))
(format "%6d %-23s %5d %5.1f"
(plist-get process :pid) (plist-get process :name)
(plist-get process :mem) (float (plist-get process :cpu)))))
(defun vui-btop-state-prototype--process-row
(process index width selected select-function row-keymap)
"Return clickable PROCESS row at INDEX in WIDTH."
(vui-button
(vui-btop-state-prototype--inside-line
width
(vui-btop-state-prototype--process-label process (max 1 (- width 2))))
:key (plist-get process :pid)
:no-decoration t
:help-echo nil
:keymap row-keymap
:face (if selected vui-btop-state-prototype--selected-face 'default)
:on-click (lambda () (funcall select-function index))))
(defun vui-btop-state-prototype--process-panel
(snapshot width height process-index sort-index reversed filter details paused
select-function row-keymap)
"Return responsive process panel lines for SNAPSHOT and UI state."
(let* ((processes
(vui-btop-state-prototype--ordered-processes
snapshot sort-index reversed filter))
(maximum (max 0 (1- (length processes))))
(selected (min process-index maximum))
(process (nth selected processes))
(detail-height (if (and details process) 6 0))
(fixed (+ 3 detail-height))
(slots (max 1 (- height fixed)))
(start (max 0 (min (- (length processes)
(min slots (length processes)))
(- selected (/ slots 2)))))
(visible (cl-subseq processes start
(min (length processes) (+ start slots))))
(sort (aref [cpu memory pid] sort-index))
(attributes (and process (process-attributes (plist-get process :pid))))
(threads (or (alist-get 'thcount attributes) 0))
lines)
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--title-line
width "4" "proc"
(format "%s%s%s" sort (if reversed " reverse" "")
(if paused " paused" ""))
vui-btop-state-prototype--process-face))
lines)
(when (and details process)
(dolist (line
(list
(format " Status: %-8s Elapsed: %s"
(plist-get process :state) (plist-get process :etime))
(format " PID: %-8d User: %-10s Threads: %d"
(plist-get process :pid) (plist-get process :user) threads)
(format " CPU: %.1f%% Memory: %d MiB"
(float (plist-get process :cpu)) (plist-get process :mem))
"" (format " %s" (plist-get process :command))))
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--inside-line width line))
lines))
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--separator-line
width "proc filter per-core reverse tree"
vui-btop-state-prototype--process-face))
lines))
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--inside-line
width (vui-btop-state-prototype--process-header (max 1 (- width 2))))
vui-btop-state-prototype--process-face)
lines)
(let ((index start))
(dolist (row visible)
(push (vui-btop-state-prototype--process-row
row index width (= index selected) select-function row-keymap)
lines)
(setq index (1+ index))))
(dotimes (_ (- slots (length visible)))
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--inside-line width ""))
lines))
(push (vui-btop-state-prototype--text
(vui-btop-state-prototype--bottom-line
width (format "%d/%d" (if process (1+ selected) 0)
(length processes))
vui-btop-state-prototype--process-face))
lines)
(nreverse lines)))
(defun vui-btop-state-prototype--left-column (snapshot width height boxes)
"Return visible memory/network panel lines at WIDTH by HEIGHT."
(let ((mem (memq 'mem boxes))
(net (memq 'net boxes)))
(cond
((and mem net)
(let ((top (/ height 2)))
(append (vui-btop-state-prototype--memory-group snapshot width top)
(vui-btop-state-prototype--network-panel
snapshot width (- height top)))))
(mem (vui-btop-state-prototype--memory-group snapshot width height))
(net (vui-btop-state-prototype--network-panel snapshot width height))
(t nil))))
(defun vui-btop-state-prototype--lower-layout
(snapshot width height boxes process-index sort-index reversed filter details
paused select-function row-keymap)
"Return responsive lower btop lines for SNAPSHOT and UI state."
(let ((left-visible (or (memq 'mem boxes) (memq 'net boxes)))
(proc-visible (memq 'proc boxes)))
(cond
((and (>= width 108) left-visible proc-visible)
(let* ((left (max 46 (/ (* width 45) 100)))
(right (- width left)))
(vui-btop-state-prototype--combine-lines
(vui-btop-state-prototype--left-column snapshot left height boxes)
(vui-btop-state-prototype--process-panel
snapshot right height process-index sort-index reversed filter details
paused select-function row-keymap))))
((and left-visible proc-visible)
(let* ((mem (memq 'mem boxes))
(net (memq 'net boxes))
(left-count (+ (if mem 1 0) (if net 1 0)))
(small (max 6 (/ height (+ left-count 2))))
(used (* small left-count))
lines)
(when mem
(setq lines
(append lines
(vui-btop-state-prototype--memory-group
snapshot width small))))
(when net
(setq lines
(append lines
(vui-btop-state-prototype--network-panel
snapshot width small))))
(append lines
(vui-btop-state-prototype--process-panel
snapshot width (max 6 (- height used)) process-index sort-index
reversed filter details paused select-function row-keymap))))
(left-visible
(vui-btop-state-prototype--left-column snapshot width height boxes))
(proc-visible
(vui-btop-state-prototype--process-panel
snapshot width height process-index sort-index reversed filter details
paused select-function row-keymap))
(t
(vui-btop-state-prototype--simple-panel
width height nil "btop" ""
'(" No boxes visible. Press 1, 2, 3, or 4.")
vui-btop-state-prototype--muted-face)))))
(defun vui-btop-state-prototype--frame-lines
(snapshot width visible-height boxes process-index sort-index reversed filter
details paused select-function row-keymap)
"Return the complete functionally equivalent btop VUI frame."
(let* ((body-height (max 12 (1- visible-height)))
(cpu-height (if (memq 'cpu boxes)
(max 8 (min 14 (/ body-height 3)))
0))
(lower-height (max 8 (- body-height cpu-height)))
lines)
(when (memq 'cpu boxes)
(setq lines
(vui-btop-state-prototype--cpu-panel snapshot width cpu-height)))
(setq lines
(append
lines
(vui-btop-state-prototype--lower-layout
snapshot width lower-height boxes process-index sort-index reversed
filter details paused select-function row-keymap)
(list
(vui-btop-state-prototype--text
(vui-btop-state-prototype--fit
(format
"↑↓ select Enter details ←→ sort / filter r reverse p %s 1-4 boxes q quit"
(if paused "resume" "pause"))
width)
vui-btop-state-prototype--muted-face))))
lines))
(defun vui-btop-state-prototype--toggle (item items)
"Return ITEMS with ITEM toggled, preserving the canonical panel order."
(if (memq item items)
(delq item (copy-sequence items))
(cl-remove-if-not (lambda (candidate) (memq candidate (cons item items)))
'(cpu mem net proc))))
(defun vui-btop-state-prototype--interactive-command (callback)
"Return an interactive command invoking zero-argument CALLBACK."
(lambda () (interactive) (funcall callback)))
(vui-defcomponent vui-btop-state-prototype-app ()
:state ((snapshot (vui-btop-state-prototype--empty-snapshot))
(process-index 0)
(sort-index 0)
(reversed nil)
(filter "")
(paused nil)
(details t)
(boxes '(cpu mem net proc))
(viewport nil))
:render
(let* ((process-ref (vui-use-ref nil))
(started-ref (vui-use-ref 0.0))
(network-ref (vui-use-ref nil))
(keymap-ref (vui-use-ref nil))
(row-keymap-ref (vui-use-ref nil))
(finished
(vui-async-callback (process _event)
(when (memq (process-status process) '(exit signal))
(let* ((output (process-buffer process))
(status (process-exit-status process))
(text (and (buffer-live-p output)
(with-current-buffer output (buffer-string)))))
(when (buffer-live-p output) (kill-buffer output))
(setcar process-ref nil)
(when (and (= status 0) text)
(vui-set-state
:snapshot
(lambda (previous)
(let ((parsed
(vui-btop-state-prototype--parse-output
text previous (car network-ref)
(* 1000.0
(- (float-time) (car started-ref))))))
(setcar network-ref (cdr parsed))
(car parsed)))))))))
(sample
(vui-with-async-context
(unless (process-live-p (car process-ref))
(let ((output (generate-new-buffer " *vui-btop-sample*")))
(setcar started-ref (float-time))
(setcar process-ref
(make-process
:name "vui-btop-sample"
:buffer output
:command vui-btop-state-prototype--sample-command
:coding 'utf-8-unix
:noquery t
:connection-type 'pipe
:sentinel finished))))))
(size (or viewport (vui-btop-state-prototype--window-size)))
(width (car size))
(processes
(vui-btop-state-prototype--ordered-processes
snapshot sort-index reversed filter))
(maximum (max 0 (1- (length processes))))
(select-process
(lambda (index) (vui-set-state :process-index index)))
(move
(lambda (amount)
(vui-set-state
:process-index
(max 0 (min maximum
(+ (min maximum process-index) amount))))))
(down (vui-with-async-context (funcall move 1)))
(up (vui-with-async-context (funcall move -1)))
(toggle-details
(vui-with-async-context
(vui-set-state :details (not details))))
(toggle-pause
(vui-with-async-context
(vui-set-state :paused (not paused))))
(sort-left
(vui-with-async-context
(vui-batch
(vui-set-state :sort-index (mod (1- sort-index) 3))
(vui-set-state :process-index 0))))
(sort-right
(vui-with-async-context
(vui-batch
(vui-set-state :sort-index (mod (1+ sort-index) 3))
(vui-set-state :process-index 0))))
(toggle-reverse
(vui-with-async-context
(vui-batch
(vui-set-state :reversed (not reversed))
(vui-set-state :process-index 0))))
(toggle-filter
(vui-with-async-context
(vui-batch
(vui-set-state :filter (if (string-empty-p filter) "emacs" ""))
(vui-set-state :process-index 0))))
(toggle-box
(lambda (box)
(vui-set-state :boxes
(lambda (current-boxes)
(vui-btop-state-prototype--toggle
box current-boxes)))))
(box-1 (vui-with-async-context (funcall toggle-box 'cpu)))
(box-2 (vui-with-async-context (funcall toggle-box 'mem)))
(box-3 (vui-with-async-context (funcall toggle-box 'net)))
(box-4 (vui-with-async-context (funcall toggle-box 'proc)))
(quit (lambda () (kill-buffer (current-buffer))))
(resize
(vui-with-async-context
(let ((next (vui-btop-state-prototype--window-size)))
(unless (equal next viewport)
(vui-set-state :viewport next)))))
(keymap (or (car keymap-ref)
(setcar keymap-ref (make-sparse-keymap))))
(row-keymap (or (car row-keymap-ref)
(setcar row-keymap-ref (make-sparse-keymap)))))
(dolist (key '("j" "\C-n" [down] [wheel-down] [mouse-5]))
(define-key keymap key
(vui-btop-state-prototype--interactive-command down)))
(dolist (key '("k" "\C-p" [up] [wheel-up] [mouse-4]))
(define-key keymap key
(vui-btop-state-prototype--interactive-command up)))
(define-key keymap (kbd "RET")
(vui-btop-state-prototype--interactive-command toggle-details))
(define-key row-keymap (kbd "RET")
(vui-btop-state-prototype--interactive-command toggle-details))
(define-key keymap "p"
(vui-btop-state-prototype--interactive-command toggle-pause))
(define-key keymap [left]
(vui-btop-state-prototype--interactive-command sort-left))
(define-key keymap [right]
(vui-btop-state-prototype--interactive-command sort-right))
(define-key keymap "r"
(vui-btop-state-prototype--interactive-command toggle-reverse))
(define-key keymap "/"
(vui-btop-state-prototype--interactive-command toggle-filter))
(define-key keymap "1"
(vui-btop-state-prototype--interactive-command box-1))
(define-key keymap "2"
(vui-btop-state-prototype--interactive-command box-2))
(define-key keymap "3"
(vui-btop-state-prototype--interactive-command box-3))
(define-key keymap "4"
(vui-btop-state-prototype--interactive-command box-4))
(define-key keymap "q"
(vui-btop-state-prototype--interactive-command quit))
(vui-use-effect (paused)
(unless paused
(let ((timer (run-with-timer
0 vui-btop-state-prototype--interval sample)))
(lambda ()
(cancel-timer timer)
(when-let* ((process (car process-ref)))
(let ((output (process-buffer process)))
(when (process-live-p process) (delete-process process))
(when (buffer-live-p output) (kill-buffer output))
(setcar process-ref nil)))))))
(vui-use-effect (viewport)
(add-hook 'window-configuration-change-hook resize nil t)
(lambda ()
(remove-hook 'window-configuration-change-hook resize t)))
(apply
#'vui-vstack :spacing 0
(cl-loop
for node in
(vui-btop-state-prototype--frame-lines
snapshot width (cdr size) boxes process-index sort-index reversed filter
details paused select-process row-keymap)
for index from 0
collect (vui-component 'vui-btop-state-prototype-line
:key index :node node :keymap keymap)))))
(defun vui-btop-state-prototype-open ()
"Open the live VUI btop state experiment."
(interactive)
(unless (eq system-type 'darwin)
(user-error "This throwaway sampler currently targets macOS"))
(vui-mount (vui-component 'vui-btop-state-prototype-app)
vui-btop-state-prototype--buffer-name)
(with-current-buffer vui-btop-state-prototype--buffer-name
(setq-local truncate-lines t
cursor-type nil
mode-line-format nil
vui-incremental-render t)
(face-remap-add-relative
'default '(:background "#070a0f" :foreground "#c8d3f5"))))
(unless noninteractive
(set-frame-size (selected-frame) 112 43)
(set-frame-name "VUI — btop state experiment")
(vui-btop-state-prototype-open))
(provide 'vui-btop-state-prototype)
;;; vui-btop-state-prototype.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment