tp/tp-builtins.el
Kinneyzhang 49cb8d8062 Fix confirmed bugs in core ops and builtins/palette modules
ops/core (B1-B8): tp-remove no longer drops 3rd+ properties; string
removal is per-interval via tp--map-intervals instead of smearing
position-0 props; tp-clear defaults bounds from OBJECT; (tp-get STR N N)
works like the buffer region form; no bogus (:key nil) from trailing
bare keywords; region form signals immediately on flat PROP/VAL args;
face-family prepend semantics extended to font-lock-face/mouse-face.

builtins/palette (B45-B51): Emacs 28.1 compat for plistp/subr-x;
display-buffer macros use a minor-mode keymap instead of mutating the
major-mode map; tp-link resolves palette colors lazily (theme-correct);
tp-palette-alist is the single source of truth (stale defvars dropped);
tp-headline handles integer heights; tp-space matches its documented
pixel spec; tp-parse-color accepts one-sided cons colors.

Test infra: fixture gains unwind-protect teardown via tp-layer-reset
(incl. transforms); file header/provide renamed to tp-tests; suite is
order-independent (verified with shuffled runs). Adds Makefile with
test/compile/clean targets.

334 tests green (280 legacy + 54 new regression tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 18:31:59 +08:00

194 lines
7.1 KiB
EmacsLisp

;;; tp-builtins.el --- Built-in layers and display helpers for tp -*- lexical-binding: t -*-
;; Copyright (C) 2024-2026 Geekinney
;; Author: Geekinney (kinneyzhang666@gmail.com)
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 3 of
;; the License, or (at your option) any later version.
;;; Commentary:
;; Batteries: the built-in layers (tp-fg, tp-bg, tp-button, tp-link,
;; tp-space, tp-headline, tp-action, ...), the palette gallery command
;; `tp-palette-show', and the read-only display buffer macros.
;;; Code:
(require 'cl-lib)
(require 'tp-core)
(require 'tp-layer)
(require 'tp-ops)
(require 'tp-palette)
(defvar tp-display-buffer-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "q" #'quit-window)
map)
"Keymap for `tp-display-buffer-mode'.")
(define-minor-mode tp-display-buffer-mode
"Minor mode enabled in tp read-only display buffers.
It binds \\`q' to `quit-window' in its own buffer-local minor-mode
keymap, leaving the major-mode keymap (which is shared by every
buffer of that major mode) untouched."
:lighter nil
:keymap tp-display-buffer-mode-map)
(eval-and-compile
(defun tp--display-buffer-form (buffer-or-name body display-fn)
"Build the shared expansion of the display-buffer macros.
BUFFER-OR-NAME and BODY are the macro arguments; DISPLAY-FN is
the symbol of the function used to display the populated buffer."
(let ((buffer (gensym "tp-buffer-")))
`(let ((,buffer (get-buffer-create ,buffer-or-name)))
(tp-with-current-buffer ,buffer
(erase-buffer)
,@body
(tp-display-buffer-mode 1)
(read-only-mode 1))
(,display-fn ,buffer)))))
(defmacro tp-pop-to-buffer (buffer-or-name &rest body)
"Show BUFFER-OR-NAME with `pop-to-buffer' after filling it by BODY.
The buffer is created if needed and erased, then BODY runs inside
it with `inhibit-read-only' non-nil. The buffer is finally made
read-only with `tp-display-buffer-mode' enabled, so \\`q' quits
its window."
(declare (indent defun))
(tp--display-buffer-form buffer-or-name body 'pop-to-buffer))
(defmacro tp-switch-to-buffer (buffer-or-name &rest body)
"Show BUFFER-OR-NAME with `switch-to-buffer' after filling it by BODY.
The buffer is created if needed and erased, then BODY runs inside
it with `inhibit-read-only' non-nil. The buffer is finally made
read-only with `tp-display-buffer-mode' enabled, so \\`q' quits
its window."
(declare (indent defun))
(tp--display-buffer-form buffer-or-name body 'switch-to-buffer))
(define-tp tp-palette (palette)
(let* ((pure-palette (tp-palette-pure palette))
(fg-color (tp-palette-fg-color pure-palette))
(bg-color (tp-palette-bg-color pure-palette))
(border-color (tp-palette-border-color pure-palette)))
(pcase palette
((pred tp-palette-p)
`(face (,@(when fg-color (list :foreground fg-color))
,@(when bg-color (list :background bg-color))
,@(when border-color (list :box (list :color border-color))))))
((pred tp-palette-fg-p)
`(face (,@(when fg-color (list :foreground fg-color)))))
((pred tp-palette-bg-p)
`(face (,@(when bg-color (list :background bg-color)))))
((pred tp-palette-fbg-p)
`(face (,@(when fg-color (list :foreground fg-color))
,@(when bg-color (list :background bg-color)))))
((pred tp-palette-border-p)
`(face (,@(when border-color (list :box (list :color border-color))))))
(_ (error "Invalid palette: %S" palette)))))
(defun tp-suffix-symbol (symbol string)
(intern (concat (symbol-name symbol) string)))
;;;###autoload
(defun tp-palette-show ()
(interactive)
(let ((alist (seq-reverse tp-palette-alist)))
(tp-switch-to-buffer "*tp-palette-gallery*"
(insert
"Please set " (tp-set "'tp-palette" 'tp-palette 'code)
" text property with following symbols:\n\n"
(mapconcat
(lambda (item)
(let* ((symbol (car item))
(name (symbol-name symbol)))
(concat (tp-set name 'tp-palette symbol)
" "
(tp-set (concat name "-fg")
'tp-palette
(tp-suffix-symbol symbol "-fg"))
" "
(tp-set (concat name "-bg")
'tp-palette
(tp-suffix-symbol symbol "-bg"))
" "
(tp-set (concat name "-fbg")
'tp-palette
(tp-suffix-symbol symbol "-fbg"))
" "
(tp-set (concat name "-border")
'tp-palette
(tp-suffix-symbol symbol "-border")))))
alist "\n")))))
(define-tp tp-fg (color)
`(face (:foreground ,color)))
(define-tp tp-bg (color)
`(face (:background ,color)))
(define-tp tp-button (type)
(let ((palette (intern
(format "%s%s%s" "button-" (symbol-name type) "-fbg"))))
`( tp-palette ,palette pointer hand
face (:box ( :line-width -1
:style released-button)))))
(define-tp tp-underline (color)
`(face (:underline (:color ,color))))
(define-tp tp-delete (color)
`(face (:strike-through ,color)))
(define-tp tp-link ()
;; No color is resolved here: the body of a zero-arg layer is
;; evaluated once, when this file is loaded, so any color computed
;; here would be frozen forever (wrong after a theme switch, or in a
;; daemon session started before any frame exists). Instead the
;; nested parameterized layer `tp-palette' resolves the info
;; foreground lazily at application time, and `:underline t'
;; underlines with that same foreground color.
'( face (:underline t)
tp-palette info-fg
mouse-face highlight
pointer hand))
(define-tp tp-space (pixel)
`(display (space :width (,pixel))))
(define-tp tp-headline (props)
;; PROPS is either a number - a float scaling factor or an integer
;; absolute height in units of 1/10 pt, both valid face :height
;; values - implying bold, or a (:height H :bold B) plist.
(let (height boldp)
(cond ((numberp props)
(setq height props boldp t))
((tp-palette--plistp props)
(setq height (plist-get props :height)
boldp (plist-get props :bold)))
(t (error "Invalid tp-headline spec: %S" props)))
`(face (,@(when height (list :height height))
,@(when boldp '(:weight bold))))))
(define-tp tp-action (sexp)
;; SEXP is a function or plist
(let (action keys)
(if (functionp sexp)
(progn
(setq action sexp)
(setq keys `(,(kbd "RET") [mouse-1])))
(setq action (plist-get sexp :action))
(setq keys (or (plist-get sexp :keys)
`(,(kbd "RET") [mouse-1]))))
`( keymap ,(let ((keymap (make-sparse-keymap)))
(dolist (key keys)
(define-key keymap key action))
keymap)
rear-nonsticky (keymap))))
(provide 'tp-builtins)
;;; tp-builtins.el ends here