Accept the family-consistent rotate order; document stack string mutation
API-ARG-01: tp-rotate-layer's region form now also accepts the canonical (START END DIRECTION &optional COUNT OBJECT) order, with OBJECT last like every sibling. Dispatch is unambiguous: the symbols up/down can never be a valid OBJECT, so a third argument of up/down selects the canonical order and anything else (nil, buffer, string) keeps the legacy (START END OBJECT DIRECTION COUNT) order bit-identically. (tp-rotate-layer 1 5 'up) now works without a nil placeholder. The docstring lists the canonical order first and the legacy order as a permanent compatibility form. API-MUT-01: every string-form stack mutator (all 16: put/push/pop/ delete/move/raise/lower/rotate/pin/switch/hide/show/merge/flatten/ add-to-layers/add-to-all-layers) now carries an explicit warning block: the string form modifies STRING destructively in place, unlike tp-set's copy semantics - never pass literals or shared strings. Semantics unification is deferred to 0.4; no behavior changes. API-NAME-03: tp-pin-layer's summary now says what it does - a one-shot move to the top of the stack; nothing stays pinned, and later pushes cover it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b4dc8abc8b
commit
f6032a891e
@ -796,6 +796,72 @@ nothing matched."
|
||||
(should (= (tp-rotate-layer 4 6) 0))
|
||||
(should (eq (get-text-property 1 'tp-name) 'la))))
|
||||
|
||||
;;; API-ARG-01: canonical (START END DIRECTION COUNT OBJECT) rotate order
|
||||
|
||||
(ert-deftest tp-stack-test-rotate-layer-canonical-order ()
|
||||
"The canonical order needs no nil OBJECT placeholder."
|
||||
(tp-stack-tests--with-env
|
||||
(insert "abcdef")
|
||||
(define-tp la () '(face bold))
|
||||
(define-tp lb () '(face italic))
|
||||
(define-tp lc () '(face underline))
|
||||
(tp-push-layer 1 6 'la)
|
||||
(tp-push-layer 1 6 'lb)
|
||||
(tp-push-layer 1 6 'lc) ; top->bottom: lc lb la
|
||||
(should (= (tp-rotate-layer 1 6 'up) 1))
|
||||
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lc lb)))
|
||||
(should (= (tp-rotate-layer 1 6 'down) 1))
|
||||
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lc lb la)))
|
||||
;; COUNT rides fourth in the canonical order.
|
||||
(should (= (tp-rotate-layer 1 6 'down 2) 1))
|
||||
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lc lb)))
|
||||
(should (= (tp-rotate-layer 1 6 'up 2) 1))
|
||||
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lc lb la)))))
|
||||
|
||||
(ert-deftest tp-stack-test-rotate-layer-canonical-order-object-last ()
|
||||
"OBJECT rides last in the canonical order (buffer and string)."
|
||||
(tp-stack-tests--with-env
|
||||
(insert "abcdef")
|
||||
(define-tp la () '(face bold))
|
||||
(define-tp lb () '(face italic))
|
||||
(tp-push-layer 1 6 'la)
|
||||
(tp-push-layer 1 6 'lb) ; top->bottom: lb la
|
||||
(let ((buf (current-buffer)))
|
||||
(with-temp-buffer ; a different current buffer
|
||||
(should (= (tp-rotate-layer 1 6 'up 1 buf) 1))))
|
||||
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lb)))
|
||||
;; nil COUNT in the canonical order still defaults to 1.
|
||||
(let ((buf (current-buffer)))
|
||||
(with-temp-buffer
|
||||
(should (= (tp-rotate-layer 1 6 'down nil buf) 1))))
|
||||
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lb la)))
|
||||
;; A string OBJECT in the canonical order's last slot.
|
||||
(let ((str (copy-sequence "xyz")))
|
||||
(tp-push-layer str 'la)
|
||||
(tp-push-layer str 'lb)
|
||||
(should (= (tp-rotate-layer 0 3 'up 1 str) 1))
|
||||
(should (equal (mapcar #'car (tp-layer-stack-at 0 str)) '(la lb))))))
|
||||
|
||||
(ert-deftest tp-stack-test-rotate-layer-legacy-order-still-works ()
|
||||
"The legacy (START END OBJECT DIRECTION COUNT) order keeps working.
|
||||
A non-up/down third argument - nil, a buffer or a string - still
|
||||
selects the legacy order."
|
||||
(tp-stack-tests--with-env
|
||||
(let ((str (copy-sequence "abcdef")))
|
||||
(define-tp la () '(face bold))
|
||||
(define-tp lb () '(face italic))
|
||||
(tp-push-layer str 'la)
|
||||
(tp-push-layer str 'lb) ; top->bottom: lb la
|
||||
(should (= (tp-rotate-layer 0 6 str 'up 1) 1))
|
||||
(should (equal (mapcar #'car (tp-layer-stack-at 0 str)) '(la lb))))
|
||||
(insert "abcdef")
|
||||
(tp-push-layer 1 6 'la)
|
||||
(tp-push-layer 1 6 'lb)
|
||||
(should (= (tp-rotate-layer 1 6 nil 'up 1) 1))
|
||||
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lb)))
|
||||
;; Canonical-order direction errors still signal.
|
||||
(should-error (tp-rotate-layer 1 6 'sideways))))
|
||||
|
||||
;;; 0.3.0 S3: tp-layer-stack-at
|
||||
|
||||
(ert-deftest tp-stack-test-layer-stack-at-shape ()
|
||||
|
||||
131
tp-stack.el
131
tp-stack.el
@ -306,6 +306,11 @@ A LAYER naming an undefined layer or group normally signals an
|
||||
error. If NOERROR is non-nil, return nil instead of signaling when
|
||||
LAYER cannot be resolved; nothing is modified in that case.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own. The returned string is
|
||||
that same mutated object.
|
||||
|
||||
Returns OBJECT when one was given (in particular the string in
|
||||
string forms), otherwise the cons (START . END)."
|
||||
(pcase-let ((`(,start ,end ,obj ,layer-spec ,idx)
|
||||
@ -352,6 +357,11 @@ A LAYER naming an undefined layer or group normally signals an
|
||||
error. If NOERROR is non-nil, return nil instead of signaling when
|
||||
LAYER cannot be resolved; nothing is modified in that case.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own. The returned string is
|
||||
that same mutated object.
|
||||
|
||||
Returns what `tp-put-layer' returns: OBJECT when one was given (in
|
||||
particular the string in string forms), otherwise (START . END)."
|
||||
(pcase-let ((`(,start ,end ,obj ,layer)
|
||||
@ -377,6 +387,10 @@ LAYER-NAME/IDX can be:
|
||||
|
||||
Only text inside [START, END) is modified.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified. A LAYER-NAME/IDX
|
||||
matching no layer never signals: unmatched runs are silently left
|
||||
alone and a return value of 0 means nothing matched at all."
|
||||
@ -409,6 +423,10 @@ Calling conventions:
|
||||
2. Entire string:
|
||||
(tp-pop-layer STRING)
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified; 0 means no run in the
|
||||
region had a layer to pop."
|
||||
(pcase-let ((`(,start ,end ,obj)
|
||||
@ -497,6 +515,10 @@ Both indices refer to positions before the move.
|
||||
The layer at FROM-ID is removed and inserted at TO-IDX position.
|
||||
OBJECT defaults to current buffer for region form.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified. A FROM-ID matching no
|
||||
layer never signals: unmatched runs are silently left alone and a
|
||||
return value of 0 means nothing matched at all."
|
||||
@ -533,6 +555,10 @@ N defaults to 1. The resulting position is clamped to the stack.
|
||||
Uses `tp--raise-layer-in-stack' internally, which is built on
|
||||
`tp--move-layer-in-stack'.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified. An IDX/LAYER-NAME
|
||||
matching no layer never signals: unmatched runs are silently left
|
||||
alone and a return value of 0 means nothing matched at all."
|
||||
@ -576,6 +602,10 @@ N defaults to 1. The resulting position is clamped to the stack.
|
||||
|
||||
OBJECT defaults to current buffer for region form.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified. An IDX/LAYER-NAME
|
||||
matching no layer never signals: unmatched runs are silently left
|
||||
alone and a return value of 0 means nothing matched at all."
|
||||
@ -586,16 +616,29 @@ alone and a return value of 0 means nothing matched at all."
|
||||
(setq n (or n 1))
|
||||
(tp-raise-layer start end layer-id (- n) obj)))
|
||||
|
||||
(defun tp-rotate-layer (start-or-string &optional end-or-direction object-or-count direction count)
|
||||
(defun tp-rotate-layer (start-or-string &optional end-or-direction
|
||||
direction-object-or-count
|
||||
count-or-direction object-or-count)
|
||||
"Rotate layers, by default moving the top layer to the bottom.
|
||||
|
||||
Calling conventions:
|
||||
1. Buffer/string region:
|
||||
(tp-rotate-layer START END OBJECT DIRECTION COUNT)
|
||||
1. Buffer/string region (canonical order, OBJECT last like the rest
|
||||
of the stack family):
|
||||
(tp-rotate-layer START END DIRECTION &optional COUNT OBJECT)
|
||||
|
||||
2. Entire string:
|
||||
(tp-rotate-layer STRING DIRECTION COUNT)
|
||||
|
||||
3. Buffer/string region (legacy 0.3.0 order, kept working forever):
|
||||
(tp-rotate-layer START END OBJECT DIRECTION COUNT)
|
||||
|
||||
The two region orders are told apart by the third argument: the
|
||||
symbols `up' and `down' are never valid OBJECTs, so a third argument
|
||||
of `up'/`down' unambiguously selects the canonical order, e.g.
|
||||
\(tp-rotate-layer 1 5 \\='up) - no nil OBJECT placeholder needed.
|
||||
Any other third argument (a buffer, a string, or nil for the current
|
||||
buffer) selects the legacy order.
|
||||
|
||||
DIRECTION is `down' or nil to move the top layer to the bottom (the
|
||||
historical behavior), or `up' to move the bottom layer to the top;
|
||||
any other value signals an error. COUNT is the number of rotation
|
||||
@ -603,18 +646,41 @@ steps and defaults to 1; a COUNT below 1 rotates nothing. Layers
|
||||
keep their relative order; hidden layers rotate with the rest of the
|
||||
stack.
|
||||
|
||||
OBJECT defaults to current buffer for region form.
|
||||
OBJECT defaults to current buffer for region forms.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified; 0 means no run in the
|
||||
region had layers to rotate (or COUNT was below 1)."
|
||||
(pcase-let ((`(,start ,end ,obj)
|
||||
(tp--parse-layer-args
|
||||
start-or-string
|
||||
(list end-or-direction object-or-count) 0)))
|
||||
(let* ((string-form (stringp start-or-string))
|
||||
(dir (or (if string-form end-or-direction direction) 'down))
|
||||
(cnt (or (if string-form object-or-count count) 1))
|
||||
(applied 0))
|
||||
(let (start end obj dir cnt)
|
||||
(cond
|
||||
;; Entire string form: (STRING DIRECTION COUNT).
|
||||
((stringp start-or-string)
|
||||
(setq start 0
|
||||
end (length start-or-string)
|
||||
obj start-or-string
|
||||
dir end-or-direction
|
||||
cnt direction-object-or-count))
|
||||
((numberp start-or-string)
|
||||
(setq start start-or-string
|
||||
end end-or-direction)
|
||||
(if (memq direction-object-or-count '(up down))
|
||||
;; Canonical region order: (START END DIRECTION COUNT OBJECT).
|
||||
(setq dir direction-object-or-count
|
||||
cnt count-or-direction
|
||||
obj object-or-count)
|
||||
;; Legacy region order: (START END OBJECT DIRECTION COUNT).
|
||||
(setq obj direction-object-or-count
|
||||
dir count-or-direction
|
||||
cnt object-or-count)))
|
||||
(t (error "Invalid layer arguments: %S"
|
||||
(cons start-or-string
|
||||
(list end-or-direction direction-object-or-count)))))
|
||||
(let ((applied 0))
|
||||
(setq dir (or dir 'down)
|
||||
cnt (or cnt 1))
|
||||
(unless (memq dir '(up down))
|
||||
(error "Invalid rotate direction: %S" dir))
|
||||
(when (>= cnt 1)
|
||||
@ -634,7 +700,12 @@ region had layers to rotate (or COUNT was below 1)."
|
||||
applied)))
|
||||
|
||||
(defun tp-pin-layer (start-or-string &optional end-or-idx idx-or-object object)
|
||||
"Pin a layer to the top (make it visible).
|
||||
"Move layer IDX/LAYER-NAME to the top of the stack (one-shot).
|
||||
|
||||
Despite the name, nothing stays pinned: this is a single move to
|
||||
index 0, exactly (tp-move-layer ... IDX/LAYER-NAME 0 ...), and
|
||||
nothing prevents a later `tp-push-layer' or `tp-put-layer' from
|
||||
covering the moved layer again.
|
||||
|
||||
Calling conventions:
|
||||
1. Buffer/string region:
|
||||
@ -643,7 +714,9 @@ Calling conventions:
|
||||
2. Entire string:
|
||||
(tp-pin-layer STRING IDX/LAYER-NAME)
|
||||
|
||||
Uses `tp-move-layer' internally to move the specified layer to index 0 (top).
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified. An IDX/LAYER-NAME
|
||||
matching no layer never signals: unmatched runs are silently left
|
||||
@ -666,6 +739,10 @@ Calling conventions:
|
||||
|
||||
Uses `tp--switch-layers-in-stack' internally.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified. When either layer is
|
||||
missing from a run's stack nothing signals: such runs are silently
|
||||
left alone and a return value of 0 means nothing matched at all."
|
||||
@ -714,6 +791,10 @@ property name inside layers, like `tp-name'.
|
||||
|
||||
OBJECT defaults to current buffer for region form.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified. A NAME matching no
|
||||
layer never signals; runs whose match is already hidden are left
|
||||
alone as well, so a return value of 0 means nothing changed."
|
||||
@ -757,6 +838,10 @@ again, restoring its properties onto the text.
|
||||
|
||||
OBJECT defaults to current buffer for region form.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified. A NAME matching no
|
||||
layer never signals; runs whose match is not hidden are left alone
|
||||
as well, so a return value of 0 means nothing changed."
|
||||
@ -820,6 +905,10 @@ hidden, the merged layer keeps their merged properties but carries
|
||||
the `tp-hidden' flag itself: the data is preserved without un-hiding
|
||||
anything, and `tp-show-layer' on the merged layer renders it.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified, counting like
|
||||
`tp-delete-layer': a run counts when at least one listed layer
|
||||
matched and the merge rewrote it, and 0 means nothing matched at
|
||||
@ -888,6 +977,10 @@ was hidden. When EVERY layer of a run is hidden, the run's
|
||||
properties are cleared entirely (bare text), consistent with the
|
||||
all-hidden rendering of `tp-hide-layer'.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own.
|
||||
|
||||
Returns the number of property runs modified, counting like
|
||||
`tp-delete-layer': every run that had layers to flatten counts, and
|
||||
0 means no run in the region had any layers."
|
||||
@ -929,6 +1022,11 @@ Properties are deeply merged (nested plists are merged, not replaced).
|
||||
|
||||
OBJECT defaults to current buffer for region form.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own. The returned string is
|
||||
that same mutated object.
|
||||
|
||||
Returns the modified object (string) or nil for buffer operations."
|
||||
(let (start end plist obj layer-ids)
|
||||
(setq layer-ids idx-or-layer-name-list)
|
||||
@ -1002,6 +1100,11 @@ OBJECT defaults to current buffer for region form.
|
||||
This function uses `tp-add-to-layers' internally, collecting all
|
||||
layer indices and passing them to add the plist to every layer.
|
||||
|
||||
Unlike `tp-set', the string form modifies STRING destructively (in
|
||||
place) rather than returning a propertized copy: never pass a string
|
||||
literal or a shared string you do not own. The returned string is
|
||||
that same mutated object.
|
||||
|
||||
Returns the modified object (string) or nil for buffer operations."
|
||||
(let (start end plist obj)
|
||||
(cond
|
||||
|
||||
Loading…
Reference in New Issue
Block a user