From fbd1e728367f3e4b0e249a3391518ff7f8aa9b3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 01:14:33 +0000 Subject: [PATCH] Update widget slot system to boolean and support multiple slots, add optimization doc Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com> --- docs/widget-optimization-proposals.md | 399 ++++++++++++++++++++++++++ tp-tests.el | 117 ++++++-- tp.el | 90 ++++-- 3 files changed, 556 insertions(+), 50 deletions(-) create mode 100644 docs/widget-optimization-proposals.md diff --git a/docs/widget-optimization-proposals.md b/docs/widget-optimization-proposals.md new file mode 100644 index 0000000..d364f35 --- /dev/null +++ b/docs/widget-optimization-proposals.md @@ -0,0 +1,399 @@ +# 组件优化提案文档(Widget Optimization Proposals) + +本文档基于当前 `tp-define-widget` 的实现,参考 Vue3 组合式 API 的设计理念,提出一系列优化和扩展方案。 + +## 当前实现 + +### 现有特性 +- `:props` - 支持属性定义,包含默认值 `(prop . default)` +- `:slot` - 布尔值,`t` 表示支持 slot,`nil` 表示不支持 +- `:render` - 渲染函数 `(lambda (props slot) ...)` +- 支持多个 slot 值(字符串和嵌套组件) + +--- + +## 优化提案 + +### 1. 生命周期钩子(Lifecycle Hooks) + +**参考**: Vue3 的 `onMounted`, `onBeforeUpdate`, `onUpdated` 等 + +**功能描述**: +```elisp +(tp-define-widget my-widget + :props '(value) + :slot t + :on-render (lambda (props slot) ...) ; 渲染前 + :on-rendered (lambda (result) ...) ; 渲染后 + :render (lambda (props slot) ...)) +``` + +**作用**: +- 在渲染前后执行特定逻辑(如日志记录、性能监控) +- 支持渲染结果的后处理 + +**是否必要**: ⭐⭐ 低优先级 +- 目前可通过在 render 函数中处理 +- 如果组件变得复杂且需要统一的渲染管道处理,则有价值 + +--- + +### 2. 命名插槽(Named Slots) + +**参考**: Vue3 的 ``, `v-slot:header` + +**功能描述**: +```elisp +(tp-define-widget card + :props '(title) + :slots '(header content footer) ; 定义多个命名插槽 + :render (lambda (props slots) + (concat (plist-get slots :header) + "\n" + (plist-get slots :content) + "\n" + (plist-get slots :footer)))) + +;; 使用 +(tp-widget-parse + '(card :title "My Card" + :header-slot "Header Content" + :content-slot "Main Content" + :footer-slot "Footer")) +``` + +**作用**: +- 支持更灵活的内容分发 +- 组件可以有多个内容区域 + +**是否必要**: ⭐⭐⭐ 中等优先级 +- 当组件需要在不同位置插入内容时非常有用 +- 类似 Vue/React 的 slot 概念 + +--- + +### 3. 作用域插槽(Scoped Slots) + +**参考**: Vue3 的作用域插槽,允许父组件访问子组件数据 + +**功能描述**: +```elisp +(tp-define-widget list-item + :props '(items) + :slot t ; slot 可以是函数 + :render (lambda (props slot-fn) + (mapconcat + (lambda (item) + ;; slot-fn 可以访问当前 item + (funcall slot-fn item)) + (plist-get props :items) + "\n"))) + +;; 使用 +(tp-widget-parse + '(list-item :items ("apple" "banana" "orange") + (lambda (item) + (tp-set item 'face 'bold)))) +``` + +**作用**: +- 父组件可以访问子组件的内部数据 +- 更灵活的渲染控制 + +**是否必要**: ⭐⭐ 低优先级 +- 增加复杂度 +- Emacs Lisp 的闭包可以部分实现此功能 + +--- + +### 4. 组件继承/组合(Component Inheritance/Composition) + +**参考**: Vue3 的 `mixins`, `extends` + +**功能描述**: +```elisp +(tp-define-widget base-button + :props '((type . "default")) + :slot t + :render (lambda (props slot) + (tp-set slot 'face 'button))) + +(tp-define-widget primary-button + :extends 'base-button + :props '((type . "primary")) ; 覆盖默认值 + :render (lambda (props slot parent-render) + (let ((result (funcall parent-render props slot))) + (tp-add result 'face '(:foreground "blue"))))) +``` + +**作用**: +- 代码复用 +- 创建组件变体 + +**是否必要**: ⭐⭐⭐ 中等优先级 +- 对于创建组件库非常有用 +- 避免重复代码 + +--- + +### 5. 响应式状态(Reactive State) + +**参考**: Vue3 的 `ref`, `reactive` + +**功能描述**: +```elisp +(tp-define-widget counter + :state '((count . 0)) ; 组件内部状态 + :slot t + :render (lambda (props state slot) + (let ((count (plist-get state :count))) + (format "Count: %d %s" count slot)))) + +;; 状态更新时自动重新渲染 +(tp-widget-update 'counter :count 5) +``` + +**作用**: +- 组件拥有自己的内部状态 +- 与现有的响应式系统(`$variable`)集成 + +**是否必要**: ⭐⭐⭐⭐ 高优先级 +- 对于交互式组件非常重要 +- 可以利用现有的 tp reactive 系统 + +--- + +### 6. 事件系统(Event System) + +**参考**: Vue3 的 `$emit`, `v-on` + +**功能描述**: +```elisp +(tp-define-widget button + :props '(label) + :emits '(click hover) ; 声明可触发的事件 + :slot t + :render (lambda (props slot emit) + (tp-add slot + 'mouse-1 (lambda () (funcall emit :click)) + 'pointer 'hand))) + +;; 使用 +(tp-widget-parse + '(button :label "Click Me" + :on-click (lambda () (message "Clicked!")) + "Submit")) +``` + +**作用**: +- 组件间通信 +- 事件驱动的交互 + +**是否必要**: ⭐⭐⭐⭐ 高优先级 +- 对于交互式 UI 必要 +- 支持按钮、链接等组件的回调 + +--- + +### 7. 依赖注入(Provide/Inject) + +**参考**: Vue3 的 `provide`, `inject` + +**功能描述**: +```elisp +(tp-define-widget theme-provider + :provide '(theme) ; 向下提供 + :props '((theme . "dark")) + :slot t + :render (lambda (props slot) slot)) + +(tp-define-widget themed-text + :inject '(theme) ; 从上层获取 + :slot t + :render (lambda (props slot injected) + (let ((theme (plist-get injected :theme))) + (tp-set slot 'face + (if (equal theme "dark") + '(:foreground "white" :background "black") + '(:foreground "black" :background "white")))))) +``` + +**作用**: +- 跨层级的数据传递 +- 主题、配置等全局状态的共享 + +**是否必要**: ⭐⭐ 低优先级 +- Emacs 可以使用动态绑定实现 +- 如果组件树很深,可能有价值 + +--- + +### 8. 条件渲染辅助(Conditional Rendering Helpers) + +**参考**: Vue3 的 `v-if`, `v-show`, `v-for` + +**功能描述**: +```elisp +;; 辅助函数 +(defun tp-if (condition then &optional else) + "条件渲染" + (if condition then (or else ""))) + +(defun tp-for (items template) + "列表渲染" + (mapconcat template items "")) + +;; 使用 +(tp-define-widget user-list + :props '(users show-email) + :render (lambda (props _slot) + (tp-for (plist-get props :users) + (lambda (user) + (concat (plist-get user :name) + (tp-if (plist-get props :show-email) + (format " <%s>" (plist-get user :email)))))))) +``` + +**作用**: +- 简化常见的渲染模式 +- 提高代码可读性 + +**是否必要**: ⭐⭐⭐ 中等优先级 +- 作为辅助函数很有用 +- 可以独立于核心组件系统实现 + +--- + +### 9. 插槽类型验证(Slot Type Validation) + +**功能描述**: +```elisp +(tp-define-widget container + :slot 'string ; 只接受字符串 + ;; 或 + :slot '(string widget) ; 接受字符串和组件 + ;; 或 + :slot '(widget button text) ; 只接受特定组件 + :render ...) +``` + +**作用**: +- 类型安全 +- 更好的错误提示 + +**是否必要**: ⭐⭐ 低优先级 +- 开发时有用 +- 可能影响性能 + +--- + +### 10. 异步组件(Async Components) + +**参考**: Vue3 的 `defineAsyncComponent` + +**功能描述**: +```elisp +(tp-define-async-widget remote-content + :props '(url) + :loading "Loading..." + :error "Failed to load" + :render (lambda (props slot) + (url-retrieve-synchronously (plist-get props :url)) + ...)) +``` + +**作用**: +- 支持异步数据加载 +- 加载和错误状态处理 + +**是否必要**: ⭐ 最低优先级 +- Emacs 的异步模型与 Web 不同 +- 可能需要使用 `url-retrieve` 和回调 + +--- + +## 优先级总结 + +| 优化项 | 优先级 | 复杂度 | 价值 | +|-------|-------|-------|-----| +| 响应式状态 | ⭐⭐⭐⭐ | 中 | 高 | +| 事件系统 | ⭐⭐⭐⭐ | 中 | 高 | +| 命名插槽 | ⭐⭐⭐ | 低 | 中 | +| 组件继承 | ⭐⭐⭐ | 中 | 中 | +| 条件渲染辅助 | ⭐⭐⭐ | 低 | 中 | +| 生命周期钩子 | ⭐⭐ | 低 | 低 | +| 作用域插槽 | ⭐⭐ | 高 | 中 | +| 依赖注入 | ⭐⭐ | 中 | 低 | +| 类型验证 | ⭐⭐ | 低 | 低 | +| 异步组件 | ⭐ | 高 | 低 | + +--- + +## 建议实施顺序 + +1. **第一阶段**: 事件系统 + 响应式状态集成 + - 这两个特性对交互式组件最重要 + - 可以利用现有的 tp reactive 系统 + +2. **第二阶段**: 命名插槽 + 条件渲染辅助 + - 提升组件的灵活性和开发体验 + +3. **第三阶段**: 组件继承 + 生命周期钩子 + - 对于构建组件库有价值 + +4. **第四阶段**: 其他高级特性 + - 根据实际需求决定 + +--- + +## 示例:完整的组件定义(理想状态) + +```elisp +(tp-define-widget button + ;; 属性定义 + :props '(action + (type . "default") + (size . "medium") + (disabled . nil)) + + ;; 状态(响应式) + :state '((loading . nil) + (focused . nil)) + + ;; 支持插槽 + :slot t + + ;; 可触发的事件 + :emits '(click focus blur) + + ;; 生命周期 + :on-render (lambda (props) + (unless (plist-get props :disabled) + (message "Button rendering..."))) + + ;; 渲染函数 + :render (lambda (props state slot emit) + (let* ((type (plist-get props :type)) + (size (plist-get props :size)) + (disabled (plist-get props :disabled)) + (loading (plist-get state :loading)) + (content (if loading "Loading..." slot)) + (face (cond + (disabled '(:foreground "gray")) + ((equal type "primary") '(:foreground "white" :background "blue")) + ((equal type "danger") '(:foreground "white" :background "red")) + (t '(:foreground "black" :background "#eee"))))) + (tp-add content + 'face face + 'mouse-1 (unless disabled + (lambda () + (funcall emit :click) + (funcall (plist-get props :action)))) + 'pointer (unless disabled 'hand))))) +``` + +--- + +## 结论 + +当前的组件系统已经具备基本功能。上述优化提案可以根据实际使用场景和需求逐步实施。建议从**事件系统**和**响应式状态集成**开始,因为这两个特性对于构建交互式 UI 组件最为重要。 diff --git a/tp-tests.el b/tp-tests.el index a47b20c..234c3b1 100644 --- a/tp-tests.el +++ b/tp-tests.el @@ -3436,24 +3436,24 @@ When using tp-set (direct property setting), tp-name is NOT added." (ert-deftest tp-test-define-twidget-basic () "Test basic twidget definition." (tp-test-with-temp-buffer - (tp-twidget-reset) + (tp-widget-reset) (tp-define-twidget test-widget :props '(name) - :slot 'content + :slot t :render (lambda (props slot) (concat "Hello " (plist-get props :name) ": " slot))) - (should (assoc 'test-widget tp-twidget-alist)) - (let ((def (cdr (assoc 'test-widget tp-twidget-alist)))) + (should (assoc 'test-widget tp-widget-alist)) + (let ((def (cdr (assoc 'test-widget tp-widget-alist)))) (should (equal (plist-get def :props) '(name))) - (should (equal (plist-get def :slot) 'content))))) + (should (equal (plist-get def :slot) t))))) (ert-deftest tp-test-widget-parse-basic () "Test basic widget parsing." (tp-test-with-temp-buffer - (tp-twidget-reset) + (tp-widget-reset) (tp-define-twidget greeting :props '(name) - :slot 'message + :slot t :render (lambda (props slot) (concat "Hello " (plist-get props :name) "! " slot))) (let ((result (tp-widget-parse '(greeting :name "World" "Nice to meet you")))) @@ -3462,10 +3462,10 @@ When using tp-set (direct property setting), tp-name is NOT added." (ert-deftest tp-test-widget-parse-with-default () "Test widget parsing with default prop values." (tp-test-with-temp-buffer - (tp-twidget-reset) + (tp-widget-reset) (tp-define-twidget styled-text :props '((color . "blue") message) - :slot 'text + :slot t :render (lambda (props slot) (let ((color (plist-get props :color)) (msg (plist-get props :message))) @@ -3483,11 +3483,11 @@ When using tp-set (direct property setting), tp-name is NOT added." (ert-deftest tp-test-widget-parse-with-text-properties () "Test widget parsing with text properties." (tp-test-with-temp-buffer - (tp-twidget-reset) + (tp-widget-reset) ;; Define a simple widget that applies text properties (tp-define-twidget bold-text :props '((color . "black")) - :slot 'content + :slot t :render (lambda (props slot) (let ((color (plist-get props :color))) (tp-set slot 'face `(:foreground ,color :weight bold))))) @@ -3501,7 +3501,7 @@ When using tp-set (direct property setting), tp-name is NOT added." (ert-deftest tp-test-widget-parse-button-example () "Test the button widget example from the problem statement." (tp-test-with-temp-buffer - (tp-twidget-reset) + (tp-widget-reset) ;; First define the tp-space layer (from problem statement) (define-tp tp-space (pixel) `(display (space :width (,pixel)))) @@ -3509,7 +3509,7 @@ When using tp-set (direct property setting), tp-name is NOT added." ;; Note: Using tp-button as the property name to match problem statement (tp-define-twidget button :props '(action (bgcolor . "green")) - :slot 'label + :slot t :render (lambda (props slot) (let ((action (plist-get props :action)) (bgcolor (plist-get props :bgcolor))) @@ -3532,35 +3532,35 @@ When using tp-set (direct property setting), tp-name is NOT added." (let ((action-prop (get-text-property 2 'tp-button result))) (should (listp (plist-get action-prop :action))))))) -(ert-deftest tp-test-twidget-reset () - "Test twidget reset clears all definitions." +(ert-deftest tp-test-widget-reset () + "Test widget reset clears all definitions." (tp-test-with-temp-buffer - (tp-twidget-reset) + (tp-widget-reset) (tp-define-twidget test-widget1 :props '(a) - :slot 'b + :slot t :render (lambda (p s) "")) (tp-define-twidget test-widget2 :props '(c) - :slot 'd + :slot t :render (lambda (p s) "")) - (should (= (length tp-twidget-alist) 2)) - (tp-twidget-reset) - (should (= (length tp-twidget-alist) 0)))) + (should (= (length tp-widget-alist) 2)) + (tp-widget-reset) + (should (= (length tp-widget-alist) 0)))) (ert-deftest tp-test-widget-parse-error-undefined () "Test widget parse with undefined widget raises error." (tp-test-with-temp-buffer - (tp-twidget-reset) + (tp-widget-reset) (should-error (tp-widget-parse '(undefined-widget "content"))))) (ert-deftest tp-test-widget-multiple-props () "Test widget with multiple props including defaults." (tp-test-with-temp-buffer - (tp-twidget-reset) + (tp-widget-reset) (tp-define-twidget multi-prop :props '(required (opt1 . "default1") (opt2 . "default2")) - :slot 'content + :slot t :render (lambda (props slot) (format "%s|%s|%s|%s" (plist-get props :required) @@ -3577,5 +3577,74 @@ When using tp-set (direct property setting), tp-name is NOT added." (let ((result (tp-widget-parse '(multi-prop :required "req" :opt1 "c1" :opt2 "c2" "slot")))) (should (equal result "req|c1|c2|slot"))))) +(ert-deftest tp-test-widget-multiple-slots () + "Test widget with multiple slot values." + (tp-test-with-temp-buffer + (tp-widget-reset) + (tp-define-twidget container + :slot t + :render (lambda (_props slot) (format "[%s]" slot))) + (let ((result (tp-widget-parse '(container "hello " "world")))) + (should (equal result "[hello world]"))))) + +(ert-deftest tp-test-widget-nested-widgets () + "Test widget with nested widget forms in slot." + (tp-test-with-temp-buffer + (tp-widget-reset) + ;; Define parent container + (tp-define-twidget p + :slot t + :render (lambda (_props slot) slot)) + ;; Define text wrapper + (tp-define-twidget text + :slot t + :render (lambda (_props slot) + (tp-set slot 'face 'bold))) + ;; Define button widget + (tp-define-twidget button + :props '(action (bgcolor . "orange")) + :slot t + :render (lambda (props slot) + (let ((bgcolor (plist-get props :bgcolor))) + (tp-add slot 'tp-button `(:bgcolor ,bgcolor))))) + ;; Test nested widgets (example from problem statement) + (let ((result (tp-widget-parse + '(p "happy hacking " + (text "emacs") + (button :action (lambda () (message "clicked!")) + "click"))))) + (should (stringp result)) + (should (equal (substring-no-properties result) "happy hacking emacsclick")) + ;; Check that "emacs" part has bold face + (should (eq (get-text-property 14 'face result) 'bold)) + ;; Check that "click" part has tp-button property + (should (get-text-property 19 'tp-button result))))) + +(ert-deftest tp-test-widget-no-slot () + "Test widget without slot support." + (tp-test-with-temp-buffer + (tp-widget-reset) + ;; Widget without :slot defined (defaults to nil) + (tp-define-twidget static-widget + :props '((text . "default text")) + :render (lambda (props _slot) + (plist-get props :text))) + ;; Slot values should be ignored + (let ((result (tp-widget-parse '(static-widget :text "hello" "ignored")))) + (should (equal result "hello"))))) + +(ert-deftest tp-test-widget-slot-boolean-false () + "Test widget with :slot nil explicitly." + (tp-test-with-temp-buffer + (tp-widget-reset) + (tp-define-twidget no-slot-widget + :props '(value) + :slot nil + :render (lambda (props slot) + (format "%s (slot: %s)" (plist-get props :value) slot))) + ;; Slot should be nil even if arguments are provided + (let ((result (tp-widget-parse '(no-slot-widget :value "test" "ignored slot")))) + (should (equal result "test (slot: nil)"))))) + (provide 'tp-ert-tests) ;;; tp-ert-tests.el ends here diff --git a/tp.el b/tp.el index 3105fe6..09827ab 100644 --- a/tp.el +++ b/tp.el @@ -3541,6 +3541,9 @@ Returns the modified object (string) or nil for buffer operations." "Alist of widget definitions: (WIDGET-NAME . DEFINITION). Each DEFINITION is a plist with :props, :slot, and :render keys.") +;; Alias for backward compatibility +(defvaralias 'tp-twidget-alist 'tp-widget-alist) + (defmacro tp-define-widget (name &rest args) "Define a text widget (widget) named NAME. @@ -3548,25 +3551,42 @@ ARGS should include: :props - A quoted list of property definitions. Each can be: - A symbol: required property accessed via keyword - A cons cell (SYMBOL . DEFAULT): property with default value - :slot - A quoted symbol naming the slot (last positional argument) + :slot - Boolean value. nil (default) means widget does not support slot. + t means widget supports slot content. :render - A lambda (props slot) that returns the rendered string The render function receives: - PROPS: a plist of resolved property values (with :keyword keys) - - SLOT: the slot value (last positional argument) + - SLOT: the slot content. When :slot is t, this is a string containing + all slot values concatenated together. Slot values can be plain + strings or nested widget-forms (which are recursively parsed). + When :slot is nil, SLOT will be nil. + +Slot values are all elements that remain after extracting the plist +(keyword-value pairs) from the widget invocation. Multiple slot values +are supported and can include both strings and nested widget-forms. Example: (tp-define-widget button - :props \\='(action (bgcolor . \"green\")) - :slot \\='label + :props \\='(action (bgcolor . \"orange\")) + :slot t :render (lambda (props slot) (let ((action (plist-get props :action)) (bgcolor (plist-get props :bgcolor))) (tp-add (format \"%s%s%s\" - (tp-set \" \" \\='tp-space 2) - slot (tp-set \" \" \\='tp-space 2)) - \\='face \\=`(:background ,bgcolor) - \\='tp-button \\=`(:action ,action)))))" + (tp-set \" \" \\='tp-space 6) + slot (tp-set \" \" \\='tp-space 6)) + \\='tp-button \\=`(:bgcolor ,bgcolor :action ,action))))) + + (tp-define-widget p + :slot t + :render (lambda (_props slot) slot)) + + ;; Usage with multiple slot values: + (tp-widget-parse \\='(p \"happy hacking \" + (text \"emacs\") + (button :action (lambda () (message \"clicked!\")) + \"click\")))" (declare (indent defun)) (let ((props nil) (slot nil) @@ -3582,11 +3602,13 @@ Example: `(tp--define-widget-internal ',name ,props ,slot ,render))) (defalias 'define-twidget 'tp-define-widget) +(defalias 'tp-define-twidget 'tp-define-widget) +(defalias 'tp-twidget-reset 'tp-widget-reset) (defun tp--define-widget-internal (name props slot render) "Internal function to define a widget NAME with PROPS, SLOT, and RENDER. PROPS is a list of property definitions. -SLOT is the slot name symbol. +SLOT is a boolean indicating whether the widget supports slot content. RENDER is the render function." (let ((definition (list :props props :slot slot :render render)) (existing (assoc name tp-widget-alist))) @@ -3617,19 +3639,22 @@ Returns nil if no default is specified." "Parse and render a widget invocation. WIDGET-FORM is a list starting with the widget name, followed by -keyword-value pairs for props, and ending with a single slot value. +keyword-value pairs for props, and then slot values (if the widget +supports slots). -The format is: (WIDGET-NAME :prop1 val1 :prop2 val2 ... SLOT-VALUE) +The format is: (WIDGET-NAME :prop1 val1 :prop2 val2 ... SLOT-VALUES...) -Keyword arguments must come before the slot value. The slot value -is the last non-keyword argument and must be exactly one value. +Keyword arguments must come before slot values. Slot values are all +remaining elements after the keyword-value pairs. Each slot value can be: + - A string: used directly + - A list starting with a widget name: recursively parsed as a widget Example: (tp-widget-parse - \\='(button :action (lambda () - (interactive) - (message \"button clicked!\")) - \"CLICK\")) + \\='(p \"happy hacking \" + (text \"emacs\") + (button :action (lambda () (message \"clicked!\")) + \"click\"))) Returns the rendered string with text properties applied." (unless (and (listp widget-form) (symbolp (car widget-form))) @@ -3640,26 +3665,39 @@ Returns the rendered string with text properties applied." (unless definition (error "Undefined widget: %S" widget-name)) (let* ((prop-defs (plist-get definition :props)) - (slot-name (plist-get definition :slot)) + (slot-supported (plist-get definition :slot)) (render-fn (plist-get definition :render)) (parsed-props nil) (slot-value nil)) ;; Parse the widget invocation arguments - ;; Extract keyword arguments and the slot (last positional argument) + ;; Extract keyword arguments and collect slot values (let ((args rest) - (collected-props nil)) + (collected-props nil) + (slot-parts nil)) ;; Parse keyword arguments (while (and args (keywordp (car args))) (let ((key (car args)) (val (cadr args))) (push (cons key val) collected-props) (setq args (cddr args)))) - ;; The remaining argument(s) should be the slot value (exactly one) - (when args - (setq slot-value (car args)) - (when (cdr args) - (warn "tp-widget-parse: Extra arguments after slot\ - value ignored: %S" (cdr args)))) + ;; The remaining arguments are slot values (if slot is supported) + (when (and slot-supported args) + ;; Process each slot value + (dolist (slot-item args) + (cond + ;; String: use directly + ((stringp slot-item) + (push slot-item slot-parts)) + ;; List starting with a defined widget name: recursively parse + ((and (listp slot-item) + (symbolp (car slot-item)) + (assoc (car slot-item) tp-widget-alist)) + (push (tp-widget-parse slot-item) slot-parts)) + ;; Other values: convert to string + (t + (push (format "%s" slot-item) slot-parts)))) + ;; Combine all slot parts into one string + (setq slot-value (apply #'concat (nreverse slot-parts)))) ;; Build the props plist with defaults (dolist (prop-def prop-defs) (let* ((prop-name (tp--widget-prop-name prop-def))