Replace the legacy managed layer renderer with one independent retained/reactive text runtime. TP now owns exact dependencies, stable objects, marker-backed mounts, property contribution composition, atomic publication, rollback, and direct text-property facades without ECSS or Ebox dependencies.\n\nBREAKING CHANGE: remove tp-render, tp-stack, scan-driven managed layers, inline runtime metadata, TP-owned CSS cascade APIs, and dollar-variable declarations.\n\nVerified: 290/290 ERT, shuffled 290/290 (seed 20260806), 8/8 doctests, WERROR compile-all, checkdoc, package-lint, diff-check, and isolated TP-only load.
179 lines
6.5 KiB
EmacsLisp
179 lines
6.5 KiB
EmacsLisp
;;; reactive-status.el --- Public TP reactive status watch example -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2026 Geekinney
|
|
;; SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
;;; Commentary:
|
|
|
|
;; Reactive status example built only from public APIs:
|
|
;; - `tp-signal-create`
|
|
;; - `tp-signal-set`
|
|
;; - `tp-watch`
|
|
;;
|
|
;; The watch surface only owns a fixed range and updates properties when the
|
|
;; status signal changes.
|
|
|
|
;;; Code:
|
|
|
|
(require 'tp)
|
|
|
|
(defconst tp-example-reactive-status-tag "STATUS"
|
|
"Fixed status label shown by this example.")
|
|
|
|
(defun tp-example-reactive-status-mount (buffer)
|
|
"Mount a status watch on BUFFER and return its control state plist.
|
|
|
|
The returned state has keys:
|
|
|
|
- `:buffer` target buffer
|
|
- `:surface` retained properties surface returned by `tp-watch`
|
|
- `:status` status signal controlling foreground color
|
|
- `:noise` unrelated signal used to demonstrate sparse updates
|
|
- `:range` watched region"
|
|
(let* ((target (get-buffer-create buffer))
|
|
(status (tp-signal-create 'ready))
|
|
(noise (tp-signal-create 0))
|
|
(surface nil))
|
|
(with-current-buffer target
|
|
(erase-buffer)
|
|
(insert "STATUS")
|
|
(setq surface
|
|
(tp-watch target 1 7
|
|
(lambda ()
|
|
(list
|
|
'face
|
|
(if (eq (tp-signal-read status) 'ready)
|
|
'(:foreground "ForestGreen")
|
|
'(:foreground "IndianRed"))
|
|
'help-echo
|
|
(tp-computed
|
|
(lambda ()
|
|
(format "status=%s"
|
|
(tp-signal-read status))))))))
|
|
(list :buffer target
|
|
:surface surface
|
|
:status status
|
|
:noise noise
|
|
:range '(1 . 7)))))
|
|
|
|
(defun tp-example-reactive-status-dispose (state)
|
|
"Unmount reactive status STATE and dispose internal signals."
|
|
(when-let ((surface (plist-get state :surface)))
|
|
(when (tp-surface-live-p surface)
|
|
(tp-surface-unmount surface))
|
|
(setf (plist-get state :surface) nil))
|
|
(when-let ((status (plist-get state :status)))
|
|
(when (tp-signal-live-p status)
|
|
(tp-signal-dispose status))
|
|
(setf (plist-get state :status) nil))
|
|
(when-let ((noise (plist-get state :noise)))
|
|
(when (tp-signal-live-p noise)
|
|
(tp-signal-dispose noise))
|
|
(setf (plist-get state :noise) nil)))
|
|
|
|
(defun tp-example-reactive-status-set (state value)
|
|
"Set status STATE to VALUE.
|
|
|
|
STATE must come from `tp-example-reactive-status-mount`."
|
|
(tp-signal-set (plist-get state :status) value))
|
|
|
|
(defun tp-example-reactive-status-poke (state value)
|
|
"Set an unrelated signal in STATE to VALUE.
|
|
|
|
This must not affect watched STATUS rendering."
|
|
(tp-signal-set (plist-get state :noise) value))
|
|
|
|
(defun tp-example-reactive-status-clear-reactive-counters ()
|
|
"Reset TP reactive scheduler counters.
|
|
|
|
Useful before measuring sparse update behavior."
|
|
(tp-reactive-reset-counters))
|
|
|
|
(defun tp-example-reactive-status-watch-report (state)
|
|
"Return `tp-surface-report` for STATE."
|
|
(tp-surface-report (plist-get state :surface)))
|
|
|
|
(defun tp-example-reactive-status-color (state)
|
|
"Return the effective face color on STATE's watched range.
|
|
|
|
If called outside STATE's buffer, returns nil."
|
|
(with-current-buffer (plist-get state :buffer)
|
|
(plist-get (tp-at 1 'face) :foreground)))
|
|
|
|
(defun tp-example-reactive-content--producer (state)
|
|
"Return a retained content producer bound to STATE."
|
|
(lambda (context)
|
|
(let* ((object (tp-object-ensure context nil 'status 'text))
|
|
(branch
|
|
(tp-bind
|
|
object '(example . branch)
|
|
(lambda ()
|
|
(if (tp-signal-read (plist-get state :enabled))
|
|
(cons 'primary
|
|
(tp-signal-read (plist-get state :primary)))
|
|
(cons 'fallback
|
|
(tp-signal-read (plist-get state :fallback)))))))
|
|
(label
|
|
(tp-bind
|
|
object '(example . label)
|
|
(lambda ()
|
|
(pcase-let ((`(,source . ,value) (tp-binding-read branch)))
|
|
(format "%s:%s" source value))))))
|
|
(tp-surface-plan-create
|
|
:key 'status :kind 'text :text (tp-binding-read label)
|
|
:props '(face bold) :capability 'content))))
|
|
|
|
(defun tp-example-reactive-content-mount (buffer)
|
|
"Mount a conditional retained status in BUFFER and return its state."
|
|
(let* ((target (get-buffer-create buffer))
|
|
(state (list :buffer target
|
|
:enabled (tp-signal-create t)
|
|
:primary (tp-signal-create "ready")
|
|
:fallback (tp-signal-create "offline")))
|
|
(producer (tp-example-reactive-content--producer state))
|
|
(surface (tp-surface-mount
|
|
target producer '(:capability content))))
|
|
(setf (plist-get state :producer) producer
|
|
(plist-get state :surface) surface)
|
|
state))
|
|
|
|
(defun tp-example-reactive-content-set-enabled (state enabled)
|
|
"Set STATE's conditional branch to ENABLED and return its report."
|
|
(tp-signal-set (plist-get state :enabled) enabled)
|
|
(tp-surface-report (plist-get state :surface)))
|
|
|
|
(defun tp-example-reactive-content-set-primary (state value)
|
|
"Set STATE's primary status to VALUE and return its report."
|
|
(tp-signal-set (plist-get state :primary) value)
|
|
(tp-surface-report (plist-get state :surface)))
|
|
|
|
(defun tp-example-reactive-content-set-fallback (state value)
|
|
"Set STATE's fallback status to VALUE and return its report."
|
|
(tp-signal-set (plist-get state :fallback) value)
|
|
(tp-surface-report (plist-get state :surface)))
|
|
|
|
(defun tp-example-reactive-content-batch-primary (state values)
|
|
"Set STATE's primary status through VALUES in one transaction."
|
|
(tp-with-transaction
|
|
(dolist (value values)
|
|
(tp-signal-set (plist-get state :primary) value)))
|
|
(tp-surface-report (plist-get state :surface)))
|
|
|
|
(defun tp-example-reactive-content-text (state)
|
|
"Return plain retained status text from STATE."
|
|
(with-current-buffer (plist-get state :buffer)
|
|
(buffer-substring-no-properties (point-min) (point-max))))
|
|
|
|
(defun tp-example-reactive-content-dispose (state)
|
|
"Unmount STATE and dispose all signals it owns."
|
|
(when (tp-surface-live-p (plist-get state :surface))
|
|
(tp-surface-unmount (plist-get state :surface)))
|
|
(dolist (key '(:enabled :primary :fallback))
|
|
(let ((signal (plist-get state key)))
|
|
(when (tp-signal-live-p signal)
|
|
(tp-signal-dispose signal)))))
|
|
|
|
(provide 'reactive-status)
|
|
|
|
;;; reactive-status.el ends here
|