Separate tp-define-layer and tp-define-layer-group macros

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-12-17 16:08:10 +00:00
parent fd732679a3
commit b6f3f87925
4 changed files with 403 additions and 111 deletions

114
README.md
View File

@ -206,7 +206,8 @@ A complete overview of all tp.el functions organized by category:
#### Property Layer Definition Functions #### Property Layer Definition Functions
| Function | Description | | Function | Description |
|----------|-------------| |----------|-------------|
| [`tp-define-layer`](#tp-define-layer---define-layers) | Define a layer or layer group | | [`tp-define-layer`](#tp-define-layer---define-single-layer) | Define a single layer |
| [`tp-define-layer-group`](#tp-define-layer-group---define-layer-group) | Define a group of layers |
| [`tp-layer-props`](#tp-layer-props--tp-group-props) | Get properties for a layer | | [`tp-layer-props`](#tp-layer-props--tp-group-props) | Get properties for a layer |
| [`tp-group-props`](#tp-layer-props--tp-group-props) | Get properties for all layers in a group | | [`tp-group-props`](#tp-layer-props--tp-group-props) | Get properties for all layers in a group |
| [`tp-undefine-layer`](#tp-undefine-layer--tp-undefine-group) | Remove layer definition | | [`tp-undefine-layer`](#tp-undefine-layer--tp-undefine-group) | Remove layer definition |
@ -1078,32 +1079,30 @@ The **property layer system** is tp.el's innovative feature that allows stacking
### Property Layer Definition ### Property Layer Definition
#### `tp-define-layer` - Define Layer(s) #### `tp-define-layer` - Define Single Layer
Define a single layer or a group of multiple layers. Define a single text property layer. Supports two formats:
**Single Layer:** **Format 1 - Direct plist:**
```elisp ```elisp
(tp-define-layer layer-name (tp-define-layer layer-name
(face (:background "cyan") line-prefix ">>")) (face (:background "cyan") line-prefix ">>"))
``` ```
**Multiple Layers (Layer Group):** **Format 2 - With :props keyword (for future extensibility):**
```elisp ```elisp
(tp-define-layer my-group (tp-define-layer layer-name
layer-1 ; Reference existing layer :props (face (:background "cyan") line-prefix ">>"))
(face (:background "red") line-prefix ">>") ; Anonymous layer
(face (:background "green" :weight bold))) ; Another anonymous layer
``` ```
The first layer in the definition is the top layer (visible by default). If a layer with the same name already exists, it will be overwritten with the new definition.
**Examples:** **Examples:**
```elisp ```elisp
;; Define individual layers ;; Define individual layers using Format 1 (direct plist)
(progn (progn
(setq tp-layer-alist nil) ; Reset for clean example (setq tp-layer-alist nil) ; Reset for clean example
(tp-define-layer highlight (tp-define-layer highlight
@ -1111,27 +1110,102 @@ The first layer in the definition is the top layer (visible by default).
(tp-layer-props 'highlight)) (tp-layer-props 'highlight))
;; => (face (:background "yellow" :foreground "black") tp-name highlight) ;; => (face (:background "yellow" :foreground "black") tp-name highlight)
;; Define a layer using Format 2 (:props keyword)
(progn (progn
(tp-define-layer error (tp-define-layer error
(face (:background "red" :foreground "white") :props (face (:background "red" :foreground "white")
help-echo "Error!")) help-echo "Error!"))
(tp-layer-props 'error)) (tp-layer-props 'error))
;; => (face (:background "red" :foreground "white") help-echo "Error!" tp-name error) ;; => (face (:background "red" :foreground "white") help-echo "Error!" tp-name error)
;; Redefine an existing layer (overwrites the old definition)
(progn (progn
(tp-define-layer test-layer (face bold))
(tp-define-layer test-layer (face italic)) ; Overwrites
(tp-layer-props 'test-layer))
;; => (face italic tp-name test-layer)
```
---
#### `tp-define-layer-group` - Define Layer Group
Define a group of multiple layers. Supports three formats for each element:
**Format 1 - Anonymous layers (named as GROUP-NAME-0, GROUP-NAME-1, etc.):**
```elisp
(tp-define-layer-group tp-test-moons
(display "🌑" face (:height 1.0))
(display "🌘" face (:height 1.5))
(display "🌗" face (:height 2.0)))
;; Creates layers: tp-test-moons-0, tp-test-moons-1, tp-test-moons-2
```
**Format 2 - Named layers with cons-cell (named as GROUP-NAME-suffix):**
```elisp
(tp-define-layer-group tp-test-moons
("新月" . (display "🌑" face (:height 1.0)))
("残月" . (display "🌘" face (:height 1.5)))
("下弦月" . (display "🌗" face (:height 2.0))))
;; Creates layers: tp-test-moons-新月, tp-test-moons-残月, tp-test-moons-下弦月
```
**Format 3 - Named layers with :props keyword (named as GROUP-NAME-suffix):**
```elisp
(tp-define-layer-group tp-test-moons
("新月" :props (display "🌑" face (:height 1.0)))
("残月" :props (display "🌘" face (:height 1.5)))
("下弦月" :props (display "🌗" face (:height 2.0))))
;; Creates layers: tp-test-moons-新月, tp-test-moons-残月, tp-test-moons-下弦月
```
You can also reference already-defined layers in a group:
```elisp
(tp-define-layer existing-layer (face bold))
(tp-define-layer-group my-group
existing-layer ; Reference existing layer
(face (:background "red") line-prefix ">>") ; Anonymous layer
("named" . (face italic))) ; Named layer
```
If a layer group with the same name already exists, it will be overwritten.
The first layer in the definition is the top layer (visible by default).
**Examples:**
```elisp
;; Define status layers, then group them
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-define-layer highlight
(face (:background "yellow" :foreground "black")))
(tp-define-layer error
(face (:background "red" :foreground "white")))
(tp-define-layer info (tp-define-layer info
(face (:background "blue" :foreground "white"))) (face (:background "blue" :foreground "white")))
(tp-layer-props 'info)) (tp-define-layer-group status-colors
;; => (face (:background "blue" :foreground "white") tp-name info)
;; Define a layer group
(progn
(tp-define-layer status-colors
highlight highlight
error error
info) info)
(length (tp-group-props 'status-colors))) (length (tp-group-props 'status-colors)))
;; => 3 ;; => 3
;; Define a layer group with named layers
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-define-layer-group moon-phases
("new" . (display "🌑"))
("waxing-crescent" . (display "🌒"))
("first-quarter" . (display "🌓"))
("full" . (display "🌕")))
(tp-layer-props 'moon-phases-full))
;; => (display "🌕" tp-name moon-phases-full)
``` ```
--- ---
@ -1161,7 +1235,7 @@ Get properties for a layer or all layers in a group.
(setq tp-layer-groups nil) (setq tp-layer-groups nil)
(tp-define-layer layer1 (face bold)) (tp-define-layer layer1 (face bold))
(tp-define-layer layer2 (face italic)) (tp-define-layer layer2 (face italic))
(tp-define-layer my-group layer1 layer2) (tp-define-layer-group my-group layer1 layer2)
(length (tp-group-props 'my-group))) (length (tp-group-props 'my-group)))
;; => 2 ;; => 2
``` ```
@ -1988,7 +2062,7 @@ Return t if OBJECT has no text properties.
(tp-define-layer status-todo (face (:foreground "gray"))) (tp-define-layer status-todo (face (:foreground "gray")))
(tp-define-layer status-progress (face (:foreground "yellow"))) (tp-define-layer status-progress (face (:foreground "yellow")))
(tp-define-layer status-done (face (:foreground "green"))) (tp-define-layer status-done (face (:foreground "green")))
(tp-define-layer task-status status-todo status-progress status-done) (tp-define-layer-group task-status status-todo status-progress status-done)
;; Check group is defined ;; Check group is defined
(length (tp-group-props 'task-status))) (length (tp-group-props 'task-status)))
;; => 3 ;; => 3

View File

@ -205,7 +205,8 @@ tp.el 所有函数按类别组织的完整概览:
#### 属性层定义函数 #### 属性层定义函数
| 函数 | 描述 | | 函数 | 描述 |
|------|------| |------|------|
| [`tp-define-layer`](#tp-define-layer---定义属性层) | 定义属性层或属性层组 | | [`tp-define-layer`](#tp-define-layer---定义单个属性层) | 定义单个属性层 |
| [`tp-define-layer-group`](#tp-define-layer-group---定义属性层组) | 定义属性层组 |
| [`tp-layer-props`](#tp-layer-props--tp-group-props) | 获取属性层的属性 | | [`tp-layer-props`](#tp-layer-props--tp-group-props) | 获取属性层的属性 |
| [`tp-group-props`](#tp-layer-props--tp-group-props) | 获取属性层组中所有属性层的属性 | | [`tp-group-props`](#tp-layer-props--tp-group-props) | 获取属性层组中所有属性层的属性 |
| [`tp-undefine-layer`](#tp-undefine-layer--tp-undefine-group) | 移除属性层定义 | | [`tp-undefine-layer`](#tp-undefine-layer--tp-undefine-group) | 移除属性层定义 |
@ -1075,32 +1076,30 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
### 属性层定义 ### 属性层定义
#### `tp-define-layer` - 定义属性层 #### `tp-define-layer` - 定义单个属性层
定义单个属性层或多个属性层组。 定义单个文本属性层。支持两种格式:
**单个属性层** **格式一 - 直接定义文本属性**
```elisp ```elisp
(tp-define-layer layer-name (tp-define-layer layer-name
(face (:background "cyan") line-prefix ">>")) (face (:background "cyan") line-prefix ">>"))
``` ```
**多个属性层(属性层组** **格式二 - 使用 :props 关键字(为后续扩展预留**
```elisp ```elisp
(tp-define-layer my-group (tp-define-layer layer-name
layer-1 ; 引用已存在的属性层 :props (face (:background "cyan") line-prefix ">>"))
(face (:background "red") line-prefix ">>") ; 匿名属性层
(face (:background "green" :weight bold))) ; 另一个匿名属性层
``` ```
定义中的第一个属性层是顶层(默认可见) 如果同名的层已存在,新定义将覆盖旧定义
**示例:** **示例:**
```elisp ```elisp
;; 定义单个属性层 ;; 使用格式一(直接 plist定义单个属性层
(progn (progn
(setq tp-layer-alist nil) ; 重置以确保干净的示例 (setq tp-layer-alist nil) ; 重置以确保干净的示例
(tp-define-layer highlight (tp-define-layer highlight
@ -1108,27 +1107,102 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(tp-layer-props 'highlight)) (tp-layer-props 'highlight))
;; => (face (:background "yellow" :foreground "black") tp-name highlight) ;; => (face (:background "yellow" :foreground "black") tp-name highlight)
;; 使用格式二(:props 关键字)定义属性层
(progn (progn
(tp-define-layer error (tp-define-layer error
(face (:background "red" :foreground "white") :props (face (:background "red" :foreground "white")
help-echo "错误!")) help-echo "错误!"))
(tp-layer-props 'error)) (tp-layer-props 'error))
;; => (face (:background "red" :foreground "white") help-echo "错误!" tp-name error) ;; => (face (:background "red" :foreground "white") help-echo "错误!" tp-name error)
;; 重新定义已存在的属性层(覆盖旧定义)
(progn (progn
(tp-define-layer test-layer (face bold))
(tp-define-layer test-layer (face italic)) ; 覆盖
(tp-layer-props 'test-layer))
;; => (face italic tp-name test-layer)
```
---
#### `tp-define-layer-group` - 定义属性层组
定义包含多个属性层的层组。每个元素支持三种格式:
**格式一 - 匿名层(命名为 GROUP-NAME-0, GROUP-NAME-1 等):**
```elisp
(tp-define-layer-group tp-test-moons
(display "🌑" face (:height 1.0))
(display "🌘" face (:height 1.5))
(display "🌗" face (:height 2.0)))
;; 创建层: tp-test-moons-0, tp-test-moons-1, tp-test-moons-2
```
**格式二 - 使用 cons-cell 命名层(命名为 GROUP-NAME-suffix**
```elisp
(tp-define-layer-group tp-test-moons
("新月" . (display "🌑" face (:height 1.0)))
("残月" . (display "🌘" face (:height 1.5)))
("下弦月" . (display "🌗" face (:height 2.0))))
;; 创建层: tp-test-moons-新月, tp-test-moons-残月, tp-test-moons-下弦月
```
**格式三 - 使用 :props 关键字命名层(命名为 GROUP-NAME-suffix**
```elisp
(tp-define-layer-group tp-test-moons
("新月" :props (display "🌑" face (:height 1.0)))
("残月" :props (display "🌘" face (:height 1.5)))
("下弦月" :props (display "🌗" face (:height 2.0))))
;; 创建层: tp-test-moons-新月, tp-test-moons-残月, tp-test-moons-下弦月
```
你也可以在层组中引用已定义的层:
```elisp
(tp-define-layer existing-layer (face bold))
(tp-define-layer-group my-group
existing-layer ; 引用已存在的属性层
(face (:background "red") line-prefix ">>") ; 匿名属性层
("named" . (face italic))) ; 命名属性层
```
如果同名的层组已存在,新定义将覆盖旧定义。
定义中的第一个属性层是顶层(默认可见)。
**示例:**
```elisp
;; 先定义状态层,然后将它们组合成层组
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-define-layer highlight
(face (:background "yellow" :foreground "black")))
(tp-define-layer error
(face (:background "red" :foreground "white")))
(tp-define-layer info (tp-define-layer info
(face (:background "blue" :foreground "white"))) (face (:background "blue" :foreground "white")))
(tp-layer-props 'info)) (tp-define-layer-group status-colors
;; => (face (:background "blue" :foreground "white") tp-name info)
;; 定义属性层组
(progn
(tp-define-layer status-colors
highlight highlight
error error
info) info)
(length (tp-group-props 'status-colors))) (length (tp-group-props 'status-colors)))
;; => 3 ;; => 3
;; 使用命名层定义层组
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-define-layer-group moon-phases
("new" . (display "🌑"))
("waxing-crescent" . (display "🌒"))
("first-quarter" . (display "🌓"))
("full" . (display "🌕")))
(tp-layer-props 'moon-phases-full))
;; => (display "🌕" tp-name moon-phases-full)
``` ```
--- ---
@ -1158,7 +1232,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(setq tp-layer-groups nil) (setq tp-layer-groups nil)
(tp-define-layer layer1 (face bold)) (tp-define-layer layer1 (face bold))
(tp-define-layer layer2 (face italic)) (tp-define-layer layer2 (face italic))
(tp-define-layer my-group layer1 layer2) (tp-define-layer-group my-group layer1 layer2)
(length (tp-group-props 'my-group))) (length (tp-group-props 'my-group)))
;; => 2 ;; => 2
``` ```
@ -1985,7 +2059,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(tp-define-layer status-todo (face (:foreground "gray"))) (tp-define-layer status-todo (face (:foreground "gray")))
(tp-define-layer status-progress (face (:foreground "yellow"))) (tp-define-layer status-progress (face (:foreground "yellow")))
(tp-define-layer status-done (face (:foreground "green"))) (tp-define-layer status-done (face (:foreground "green")))
(tp-define-layer task-status status-todo status-progress status-done) (tp-define-layer-group task-status status-todo status-progress status-done)
;; 检查组是否已定义 ;; 检查组是否已定义
(length (tp-group-props 'task-status))) (length (tp-group-props 'task-status)))
;; => 3 ;; => 3

View File

@ -220,13 +220,21 @@
;;; ============================================================ ;;; ============================================================
(ert-deftest tp-test-define-layer () (ert-deftest tp-test-define-layer ()
"Test tp-define-layer creates a layer." "Test tp-define-layer creates a layer (Format 1 - direct plist)."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(tp-define-layer test-layer (face bold help-echo "test")) (tp-define-layer test-layer (face bold help-echo "test"))
(should (assoc 'test-layer tp-layer-alist)) (should (assoc 'test-layer tp-layer-alist))
(should (equal (cdr (assoc 'test-layer tp-layer-alist)) (should (equal (cdr (assoc 'test-layer tp-layer-alist))
'(face bold help-echo "test"))))) '(face bold help-echo "test")))))
(ert-deftest tp-test-define-layer-with-props ()
"Test tp-define-layer with :props keyword (Format 2)."
(tp-test-with-temp-buffer
(tp-define-layer test-layer :props (face italic help-echo "props"))
(should (assoc 'test-layer tp-layer-alist))
(should (equal (cdr (assoc 'test-layer tp-layer-alist))
'(face italic help-echo "props")))))
(ert-deftest tp-test-define-layer-updates-existing () (ert-deftest tp-test-define-layer-updates-existing ()
"Test tp-define-layer updates existing layer." "Test tp-define-layer updates existing layer."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
@ -235,6 +243,14 @@
(should (equal (cdr (assoc 'test-layer tp-layer-alist)) (should (equal (cdr (assoc 'test-layer tp-layer-alist))
'(face italic))))) '(face italic)))))
(ert-deftest tp-test-define-layer-updates-existing-with-props ()
"Test tp-define-layer with :props updates existing layer."
(tp-test-with-temp-buffer
(tp-define-layer test-layer (face bold))
(tp-define-layer test-layer :props (face underline))
(should (equal (cdr (assoc 'test-layer tp-layer-alist))
'(face underline)))))
(ert-deftest tp-test-layer-props () (ert-deftest tp-test-layer-props ()
"Test tp-layer-props returns properties with tp-name." "Test tp-layer-props returns properties with tp-name."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
@ -257,14 +273,14 @@
(should-not (assoc 'test-layer tp-layer-alist)))) (should-not (assoc 'test-layer tp-layer-alist))))
;;; ============================================================ ;;; ============================================================
;;; Layer Group Tests (using tp-define-layer with multiple layers) ;;; Layer Group Tests (using tp-define-layer-group)
;;; ============================================================ ;;; ============================================================
(ert-deftest tp-test-define-layer-multiple () (ert-deftest tp-test-define-layer-group-anonymous ()
"Test tp-define-layer creates a layer group with multiple layers." "Test tp-define-layer-group creates a layer group with anonymous layers."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(tp-define-layer layer1 (face bold)) (tp-define-layer layer1 (face bold))
(tp-define-layer my-group (tp-define-layer-group my-group
layer1 layer1
(face italic) (face italic)
(face underline)) (face underline))
@ -272,14 +288,64 @@
;; Check all layers are present in the group ;; Check all layers are present in the group
(let ((layers (cdr (assoc 'my-group tp-layer-groups)))) (let ((layers (cdr (assoc 'my-group tp-layer-groups))))
(should (= (length layers) 3)) (should (= (length layers) 3))
(should (memq 'layer1 layers))))) (should (memq 'layer1 layers))
;; Anonymous layers should be named my-group-0 and my-group-1
(should (memq 'my-group-0 layers))
(should (memq 'my-group-1 layers)))))
(ert-deftest tp-test-define-layer-group-named-cons ()
"Test tp-define-layer-group with named cons-cell format."
(tp-test-with-temp-buffer
(tp-define-layer-group my-group
("first" . (face bold))
("second" . (face italic)))
(should (assoc 'my-group tp-layer-groups))
(let ((layers (cdr (assoc 'my-group tp-layer-groups))))
(should (= (length layers) 2))
(should (memq 'my-group-first layers))
(should (memq 'my-group-second layers)))
;; Check that layers are properly defined
(should (equal (cdr (assoc 'my-group-first tp-layer-alist)) '(face bold)))
(should (equal (cdr (assoc 'my-group-second tp-layer-alist)) '(face italic)))))
(ert-deftest tp-test-define-layer-group-named-props ()
"Test tp-define-layer-group with :props format."
(tp-test-with-temp-buffer
(tp-define-layer-group my-group
("first" :props (face bold))
("second" :props (face italic)))
(should (assoc 'my-group tp-layer-groups))
(let ((layers (cdr (assoc 'my-group tp-layer-groups))))
(should (= (length layers) 2))
(should (memq 'my-group-first layers))
(should (memq 'my-group-second layers)))
;; Check that layers are properly defined
(should (equal (cdr (assoc 'my-group-first tp-layer-alist)) '(face bold)))
(should (equal (cdr (assoc 'my-group-second tp-layer-alist)) '(face italic)))))
(ert-deftest tp-test-define-layer-group-mixed ()
"Test tp-define-layer-group with mixed formats."
(tp-test-with-temp-buffer
(tp-define-layer existing-layer (face underline))
(tp-define-layer-group my-group
existing-layer
(face bold)
("named" . (face italic))
("with-props" :props (face strike-through)))
(should (assoc 'my-group tp-layer-groups))
(let ((layers (cdr (assoc 'my-group tp-layer-groups))))
(should (= (length layers) 4))
(should (memq 'existing-layer layers))
(should (memq 'my-group-0 layers))
(should (memq 'my-group-named layers))
(should (memq 'my-group-with-props layers)))))
(ert-deftest tp-test-group-props () (ert-deftest tp-test-group-props ()
"Test tp-group-props returns all layer properties." "Test tp-group-props returns all layer properties."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(tp-define-layer layer1 (face bold)) (tp-define-layer layer1 (face bold))
(tp-define-layer layer2 (face italic)) (tp-define-layer layer2 (face italic))
(tp-define-layer my-group layer1 layer2) (tp-define-layer-group my-group layer1 layer2)
(let ((props-list (tp-group-props 'my-group))) (let ((props-list (tp-group-props 'my-group)))
(should (= (length props-list) 2)) (should (= (length props-list) 2))
;; Check that both layers are present ;; Check that both layers are present
@ -291,17 +357,27 @@
"Test tp-undefine-group removes group definition." "Test tp-undefine-group removes group definition."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(tp-define-layer layer1 (face bold)) (tp-define-layer layer1 (face bold))
(tp-define-layer my-group layer1) (tp-define-layer-group my-group layer1)
(should (assoc 'my-group tp-layer-groups)) (should (assoc 'my-group tp-layer-groups))
(tp-undefine-group 'my-group) (tp-undefine-group 'my-group)
(should-not (assoc 'my-group tp-layer-groups)))) (should-not (assoc 'my-group tp-layer-groups))))
(ert-deftest tp-test-layer-group-updates-existing ()
"Test tp-define-layer-group updates existing group."
(tp-test-with-temp-buffer
(tp-define-layer layer1 (face bold))
(tp-define-layer layer2 (face italic))
(tp-define-layer-group my-group layer1)
(should (= (length (cdr (assoc 'my-group tp-layer-groups))) 1))
(tp-define-layer-group my-group layer1 layer2)
(should (= (length (cdr (assoc 'my-group tp-layer-groups))) 2))))
(ert-deftest tp-test-layer-reset () (ert-deftest tp-test-layer-reset ()
"Test tp-layer-reset clears all definitions." "Test tp-layer-reset clears all definitions."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(tp-define-layer layer1 (face bold)) (tp-define-layer layer1 (face bold))
(tp-define-layer layer2 (face italic)) (tp-define-layer layer2 (face italic))
(tp-define-layer group1 layer1 layer2) (tp-define-layer-group group1 layer1 layer2)
(should tp-layer-alist) (should tp-layer-alist)
(should tp-layer-groups) (should tp-layer-groups)
(tp-layer-reset) (tp-layer-reset)

162
tp.el
View File

@ -1511,63 +1511,131 @@ Returns a plist of all properties in the region or string."
;;; Layer Definition Functions ;;; Layer Definition Functions
(defmacro tp-define-layer (name &rest layers) (defmacro tp-define-layer (name &rest args)
"Define a text property layer or layer group named NAME. "Define a single text property layer named NAME.
Single layer: This macro supports two formats:
(tp-define-layer layer-1 \\='(face (:background \"cyan\") line-prefix \">>\"))
Multiple layers (first defined layer is the top layer): Format 1 - Direct plist:
(tp-define-layer layers-2 (tp-define-layer layer-name
\\='layer-1 (display \"🌑\" face (:height 1.0)))
\\='(face (:background \"red\") line-prefix \">>\")
\\='(face (:background \"green\" :weight bold) line-prefix \"::\"))
LAYERS can be: Format 2 - With :props keyword (for future extensibility):
- A single plist for a single layer definition (tp-define-layer layer-name
- Multiple items where each can be: :props (display \"🌑\" face (:height 1.0)))
- A symbol referencing another defined layer
- A plist defining an anonymous sub-layer
For multiple layers, they are stored as a group in `tp-layer-groups'. If a layer with the same NAME already exists, it will be overwritten
The first layer in the definition is the top layer." with the new definition.
The layer is stored in `tp-layer-alist'."
(declare (indent defun)) (declare (indent defun))
;; Determine if this is a single layer (one plist argument) or multiple layers (let ((properties
(let ((first-layer (car layers))) (cond
(let ((is-single-layer ;; Format 2: :props (plist)
(and (= (length layers) 1) ((and (eq (car args) :props)
first-layer (cadr args))
(listp first-layer) (cadr args))
;; A plist has an even number of elements (key-value pairs) ;; Format 1: (plist)
(cl-evenp (length first-layer)) ((and (= (length args) 1)
;; The first element is a property name (symbol, not nil) (listp (car args)))
(symbolp (car first-layer))))) (car args))
(if is-single-layer (t (error "Invalid tp-define-layer format for %s" name)))))
;; Single layer: (tp-define-layer name '(plist...))
(let ((properties first-layer))
`(progn `(progn
(if (assoc ',name tp-layer-alist) (if (assoc ',name tp-layer-alist)
(setf (cdr (assoc ',name tp-layer-alist)) ',properties) (setf (cdr (assoc ',name tp-layer-alist)) ',properties)
(push (cons ',name ',properties) tp-layer-alist)) (push (cons ',name ',properties) tp-layer-alist))
(assoc ',name tp-layer-alist))) (assoc ',name tp-layer-alist))))
;; Multiple layers: (tp-define-layer name 'layer1 '(plist1) '(plist2) ...)
(let ((layer-names nil) (defun tp--parse-layer-group-element (group-name element idx)
(idx 0) "Parse a layer group element and return (layer-name . properties).
(layer-defs nil)) GROUP-NAME is the name of the layer group.
(dolist (layer layers) ELEMENT is the element to parse (can be anonymous plist, cons-cell, or :props form).
IDX is the index for anonymous elements.
Returns a cons cell (LAYER-NAME . PROPERTIES) or a symbol if ELEMENT
references an already-defined layer."
(cond (cond
;; Reference to existing layer ;; Case: already defined layer (symbol)
((symbolp layer) ((symbolp element)
(push layer layer-names)) element)
;; Plist layer - create with auto-generated name ;; Case: Format 3 - ("name" :props (plist...))
((listp layer) ((and (listp element)
(let ((sub-name (intern (format "%s-layer-%d" name idx)))) (stringp (car element))
(push `(if (assoc ',sub-name tp-layer-alist) (eq (cadr element) :props)
(setf (cdr (assoc ',sub-name tp-layer-alist)) ',layer) (caddr element))
(push (cons ',sub-name ',layer) tp-layer-alist)) (let* ((layer-suffix (car element))
(layer-name (intern (format "%s-%s" group-name layer-suffix)))
(props (caddr element)))
(cons layer-name props)))
;; Case: Format 2 - ("name" . (plist...)) - cons cell
((and (consp element)
(stringp (car element))
(listp (cdr element)))
(let* ((layer-suffix (car element))
(layer-name (intern (format "%s-%s" group-name layer-suffix)))
(props (cdr element)))
(cons layer-name props)))
;; Case: Format 1 - (plist...) - anonymous, use index
((and (listp element)
(not (stringp (car element))))
(let ((layer-name (intern (format "%s-%d" group-name idx))))
(cons layer-name element)))
(t (error "Invalid layer group element: %S" element))))
(defmacro tp-define-layer-group (name &rest elements)
"Define a layer group named NAME containing multiple layers.
This macro supports three formats for each element:
Format 1 - Anonymous layers (named as NAME-0, NAME-1, etc.):
(tp-define-layer-group group-name
(display \"🌑\" face (:height 1.0))
(display \"🌘\" face (:height 1.5))
(display \"🌗\" face (:height 2.0)))
Format 2 - Named layers with cons-cell (named as NAME-suffix):
(tp-define-layer-group group-name
(\"新月\" . (display \"🌑\" face (:height 1.0)))
(\"残月\" . (display \"🌘\" face (:height 1.5)))
(\"下弦月\" . (display \"🌗\" face (:height 2.0))))
Format 3 - Named layers with :props keyword (named as NAME-suffix):
(tp-define-layer-group group-name
(\"新月\" :props (display \"🌑\" face (:height 1.0)))
(\"残月\" :props (display \"🌘\" face (:height 1.5)))
(\"下弦月\" :props (display \"🌗\" face (:height 2.0))))
You can also reference already-defined layers by their symbol name:
(tp-define-layer-group group-name
existing-layer-1
existing-layer-2
(display \"🌗\" face (:height 2.0)))
If a layer group with the same NAME already exists, it will be overwritten.
Individual layers created by the group are stored in `tp-layer-alist',
and the group itself is stored in `tp-layer-groups'."
(declare (indent defun))
(let ((layer-names nil)
(layer-defs nil)
(idx 0))
(dolist (element elements)
(let ((parsed (tp--parse-layer-group-element name element idx)))
(cond
;; Reference to existing layer (symbol)
((symbolp parsed)
(push parsed layer-names))
;; New layer definition (cons cell of name . props)
((consp parsed)
(let ((layer-name (car parsed))
(props (cdr parsed)))
(push `(if (assoc ',layer-name tp-layer-alist)
(setf (cdr (assoc ',layer-name tp-layer-alist)) ',props)
(push (cons ',layer-name ',props) tp-layer-alist))
layer-defs) layer-defs)
(push sub-name layer-names) (push layer-name layer-names)
(cl-incf idx))))) ;; Only increment idx for anonymous (Format 1) elements
(unless (and (listp element) (stringp (car element)))
(cl-incf idx)))))))
(setq layer-names (nreverse layer-names)) (setq layer-names (nreverse layer-names))
(setq layer-defs (nreverse layer-defs)) (setq layer-defs (nreverse layer-defs))
`(progn `(progn
@ -1575,7 +1643,7 @@ The first layer in the definition is the top layer."
(if (assoc ',name tp-layer-groups) (if (assoc ',name tp-layer-groups)
(setf (cdr (assoc ',name tp-layer-groups)) ',layer-names) (setf (cdr (assoc ',name tp-layer-groups)) ',layer-names)
(push (cons ',name ',layer-names) tp-layer-groups)) (push (cons ',name ',layer-names) tp-layer-groups))
(assoc ',name tp-layer-groups))))))) (assoc ',name tp-layer-groups))))
(defun tp-layer-props (layer-name) (defun tp-layer-props (layer-name)
"Return properties for layer LAYER-NAME from `tp-layer-alist'. "Return properties for layer LAYER-NAME from `tp-layer-alist'.