953 lines
22 KiB
Markdown
953 lines
22 KiB
Markdown
---
|
|
name: widget
|
|
description: A guide to using the Emacs Widget Library for creating interactive UI elements in buffers.
|
|
---
|
|
|
|
# Emacs Widget Library
|
|
|
|
Build interactive forms and UI elements in Emacs buffers using the built-in widget library (wid-edit.el).
|
|
|
|
## Overview
|
|
|
|
The Emacs Widget Library provides a framework for creating interactive UI elements within text buffers. Widgets enable building forms, configuration interfaces, and custom UI components without requiring external dependencies.
|
|
|
|
**Core libraries:**
|
|
- `widget.el` - Top-level widget interface
|
|
- `wid-edit.el` - Widget implementation and editing
|
|
|
|
**Primary use cases:**
|
|
- Custom configuration interfaces
|
|
- Interactive forms and dialogs
|
|
- Settings and preference editors
|
|
- Data entry and validation
|
|
|
|
## Setup
|
|
|
|
```elisp
|
|
(require 'widget)
|
|
(eval-when-compile
|
|
(require 'wid-edit))
|
|
```
|
|
|
|
## Core Concepts
|
|
|
|
### Widget Lifecycle
|
|
|
|
1. **Create** - `widget-create` returns a widget object
|
|
2. **Setup** - `widget-setup` enables interaction after creation
|
|
3. **Interact** - User edits/activates widgets
|
|
4. **Query** - Retrieve values with `widget-value`
|
|
5. **Delete** - Clean up with `widget-delete`
|
|
|
|
### Widget Buffer Setup
|
|
|
|
```elisp
|
|
(defun my-widget-example ()
|
|
(interactive)
|
|
(switch-to-buffer "*Widget Example*")
|
|
(kill-all-local-variables)
|
|
(erase-buffer)
|
|
(remove-overlays)
|
|
|
|
;; Create widgets
|
|
(widget-insert "Example Form\n\n")
|
|
(widget-create 'editable-field
|
|
:format "Name: %v"
|
|
:size 30
|
|
"Enter name")
|
|
(widget-insert "\n\n")
|
|
(widget-create 'push-button
|
|
:notify (lambda (&rest ignore)
|
|
(message "Submitted!"))
|
|
"Submit")
|
|
(widget-insert "\n")
|
|
|
|
;; Enable widgets
|
|
(use-local-map widget-keymap)
|
|
(widget-setup))
|
|
```
|
|
|
|
### Widget Properties
|
|
|
|
Widgets are configured via keyword arguments:
|
|
- `:value` - Initial/current value
|
|
- `:format` - Display format string
|
|
- `:tag` - Label text
|
|
- `:notify` - Callback function on change
|
|
- `:help-echo` - Tooltip text
|
|
|
|
## Widget Types
|
|
|
|
### Text Input
|
|
|
|
#### editable-field
|
|
|
|
Basic text input field.
|
|
|
|
```elisp
|
|
;; Simple field
|
|
(widget-create 'editable-field
|
|
:format "Label: %v\n"
|
|
"default text")
|
|
|
|
;; Sized field
|
|
(widget-create 'editable-field
|
|
:size 20
|
|
:format "Username: %v\n")
|
|
|
|
;; Password field
|
|
(widget-create 'editable-field
|
|
:secret ?*
|
|
:format "Password: %v\n")
|
|
|
|
;; With change notification
|
|
(widget-create 'editable-field
|
|
:notify (lambda (widget &rest ignore)
|
|
(message "Value: %s" (widget-value widget)))
|
|
:format "Email: %v\n")
|
|
```
|
|
|
|
#### text
|
|
|
|
Multi-line text area.
|
|
|
|
```elisp
|
|
(widget-create 'text
|
|
:format "Comments:\n%v"
|
|
:value "Line 1\nLine 2\nLine 3")
|
|
```
|
|
|
|
### Buttons
|
|
|
|
#### push-button
|
|
|
|
Clickable button that triggers action.
|
|
|
|
```elisp
|
|
;; Basic button
|
|
(widget-create 'push-button
|
|
:notify (lambda (&rest ignore)
|
|
(message "Clicked!"))
|
|
"Click Me")
|
|
|
|
;; Styled button
|
|
(widget-create 'push-button
|
|
:button-face 'custom-button
|
|
:format "%[%t%]\n"
|
|
:tag "Submit Form"
|
|
:notify (lambda (&rest ignore)
|
|
(my-submit-form)))
|
|
```
|
|
|
|
#### link
|
|
|
|
Hyperlink that executes action.
|
|
|
|
```elisp
|
|
;; Function link
|
|
(widget-create 'link
|
|
:button-face 'info-xref
|
|
:help-echo "View documentation"
|
|
:notify (lambda (&rest ignore)
|
|
(describe-function 'widget-create))
|
|
"Documentation")
|
|
|
|
;; URL link
|
|
(widget-create 'url-link
|
|
:format "%[%t%]"
|
|
:tag "Emacs Manual"
|
|
"https://www.gnu.org/software/emacs/manual/")
|
|
```
|
|
|
|
### Selection
|
|
|
|
#### checkbox
|
|
|
|
Boolean toggle (checked/unchecked).
|
|
|
|
```elisp
|
|
(widget-create 'checkbox
|
|
:format "%[%v%] Enable feature\n"
|
|
:notify (lambda (widget &rest ignore)
|
|
(message "Feature %s"
|
|
(if (widget-value widget)
|
|
"enabled"
|
|
"disabled")))
|
|
t) ; Initial state
|
|
```
|
|
|
|
#### toggle
|
|
|
|
Text-based on/off switch.
|
|
|
|
```elisp
|
|
(widget-create 'toggle
|
|
:on "Enabled"
|
|
:off "Disabled"
|
|
:notify (lambda (widget &rest ignore)
|
|
(my-update-setting (widget-value widget)))
|
|
nil) ; Initially off
|
|
```
|
|
|
|
#### radio-button-choice
|
|
|
|
Single selection from multiple options.
|
|
|
|
```elisp
|
|
(widget-create 'radio-button-choice
|
|
:value "medium"
|
|
:notify (lambda (widget &rest ignore)
|
|
(message "Selected: %s" (widget-value widget)))
|
|
'(item "small")
|
|
'(item "medium")
|
|
'(item "large"))
|
|
```
|
|
|
|
#### menu-choice
|
|
|
|
Dropdown menu selection.
|
|
|
|
```elisp
|
|
(widget-create 'menu-choice
|
|
:tag "Output Format"
|
|
:value 'json
|
|
'(const :tag "JSON" json)
|
|
'(const :tag "XML" xml)
|
|
'(const :tag "Plain Text" text)
|
|
'(editable-field :menu-tag "Custom" "custom-format"))
|
|
```
|
|
|
|
#### checklist
|
|
|
|
Multiple selections (subset of options).
|
|
|
|
```elisp
|
|
(widget-create 'checklist
|
|
:notify (lambda (widget &rest ignore)
|
|
(message "Selected: %S" (widget-value widget)))
|
|
'(const :tag "Option A" option-a)
|
|
'(const :tag "Option B" option-b)
|
|
'(const :tag "Option C" option-c))
|
|
```
|
|
|
|
### Lists
|
|
|
|
#### editable-list
|
|
|
|
Dynamic list with add/remove buttons.
|
|
|
|
```elisp
|
|
(widget-create 'editable-list
|
|
:entry-format "%i %d %v"
|
|
:value '("item1" "item2")
|
|
'(editable-field :value ""))
|
|
```
|
|
|
|
**Format specifiers:**
|
|
- `%i` - Insert button (INS)
|
|
- `%d` - Delete button (DEL)
|
|
- `%v` - The value widget
|
|
|
|
## Widget API
|
|
|
|
### Creation and Setup
|
|
|
|
#### widget-create
|
|
|
|
Create widget and return widget object.
|
|
|
|
```elisp
|
|
(widget-create TYPE [KEYWORD ARGUMENT]...)
|
|
|
|
;; Example
|
|
(setq my-widget
|
|
(widget-create 'editable-field
|
|
:size 25
|
|
:value "initial"))
|
|
```
|
|
|
|
#### widget-setup
|
|
|
|
Enable widgets after creation. Must be called before user interaction.
|
|
|
|
```elisp
|
|
(widget-setup)
|
|
```
|
|
|
|
Required after:
|
|
- Initial widget creation
|
|
- Calling `widget-value-set`
|
|
- Modifying widget structure
|
|
|
|
#### widget-insert
|
|
|
|
Insert text at point (not a widget).
|
|
|
|
```elisp
|
|
(widget-insert "Header Text\n\n")
|
|
```
|
|
|
|
### Value Access
|
|
|
|
#### widget-value
|
|
|
|
Get current widget value.
|
|
|
|
```elisp
|
|
(widget-value WIDGET)
|
|
|
|
;; Example
|
|
(let ((name (widget-value name-widget))
|
|
(enabled (widget-value checkbox-widget)))
|
|
(message "Name: %s, Enabled: %s" name enabled))
|
|
```
|
|
|
|
#### widget-value-set
|
|
|
|
Set widget value programmatically.
|
|
|
|
```elisp
|
|
(widget-value-set WIDGET VALUE)
|
|
(widget-setup) ; Required after value change
|
|
|
|
;; Example
|
|
(widget-value-set email-widget "user@example.com")
|
|
(widget-setup)
|
|
```
|
|
|
|
### Property Access
|
|
|
|
#### widget-get
|
|
|
|
Retrieve widget property.
|
|
|
|
```elisp
|
|
(widget-get WIDGET PROPERTY)
|
|
|
|
;; Example
|
|
(widget-get my-widget :tag)
|
|
(widget-get my-widget :size)
|
|
```
|
|
|
|
#### widget-put
|
|
|
|
Set widget property.
|
|
|
|
```elisp
|
|
(widget-put WIDGET PROPERTY VALUE)
|
|
|
|
;; Example
|
|
(widget-put my-widget :help-echo "Enter your email address")
|
|
```
|
|
|
|
#### widget-apply
|
|
|
|
Call widget method with arguments.
|
|
|
|
```elisp
|
|
(widget-apply WIDGET PROPERTY &rest ARGS)
|
|
|
|
;; Example
|
|
(widget-apply my-widget :notify)
|
|
```
|
|
|
|
### Navigation
|
|
|
|
#### widget-forward
|
|
|
|
Move to next widget (bound to TAB).
|
|
|
|
```elisp
|
|
(widget-forward &optional COUNT)
|
|
```
|
|
|
|
#### widget-backward
|
|
|
|
Move to previous widget (bound to S-TAB/M-TAB).
|
|
|
|
```elisp
|
|
(widget-backward &optional COUNT)
|
|
```
|
|
|
|
### Cleanup
|
|
|
|
#### widget-delete
|
|
|
|
Delete widget and clean up.
|
|
|
|
```elisp
|
|
(widget-delete WIDGET)
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Form with Validation
|
|
|
|
```elisp
|
|
(defun my-registration-form ()
|
|
(interactive)
|
|
(let (username-widget email-widget)
|
|
(switch-to-buffer "*Registration*")
|
|
(kill-all-local-variables)
|
|
(erase-buffer)
|
|
(remove-overlays)
|
|
|
|
(widget-insert "User Registration\n\n")
|
|
|
|
(setq username-widget
|
|
(widget-create 'editable-field
|
|
:format "Username: %v\n"
|
|
:size 25))
|
|
|
|
(widget-insert "\n")
|
|
|
|
(setq email-widget
|
|
(widget-create 'editable-field
|
|
:format "Email: %v\n"
|
|
:size 40))
|
|
|
|
(widget-insert "\n\n")
|
|
|
|
(widget-create 'push-button
|
|
:notify (lambda (&rest ignore)
|
|
(let ((user (widget-value username-widget))
|
|
(email (widget-value email-widget)))
|
|
(if (and (> (length user) 0)
|
|
(string-match-p "@" email))
|
|
(message "Registered: %s <%s>" user email)
|
|
(message "Invalid input"))))
|
|
"Register")
|
|
|
|
(use-local-map widget-keymap)
|
|
(widget-setup)))
|
|
```
|
|
|
|
### Settings Interface
|
|
|
|
```elisp
|
|
(defun my-settings ()
|
|
(interactive)
|
|
(let (theme-widget auto-save-widget)
|
|
(switch-to-buffer "*Settings*")
|
|
(kill-all-local-variables)
|
|
(erase-buffer)
|
|
(remove-overlays)
|
|
|
|
(widget-insert "Application Settings\n\n")
|
|
|
|
;; Theme selection
|
|
(widget-insert "Theme:\n")
|
|
(setq theme-widget
|
|
(widget-create 'radio-button-choice
|
|
:value my-current-theme
|
|
'(const :tag "Light" light)
|
|
'(const :tag "Dark" dark)
|
|
'(const :tag "Auto" auto)))
|
|
|
|
(widget-insert "\n")
|
|
|
|
;; Auto-save toggle
|
|
(setq auto-save-widget
|
|
(widget-create 'checkbox
|
|
:format "%[%v%] Enable auto-save\n"
|
|
my-auto-save-enabled))
|
|
|
|
(widget-insert "\n")
|
|
|
|
;; Save button
|
|
(widget-create 'push-button
|
|
:notify (lambda (&rest ignore)
|
|
(setq my-current-theme
|
|
(widget-value theme-widget))
|
|
(setq my-auto-save-enabled
|
|
(widget-value auto-save-widget))
|
|
(message "Settings saved"))
|
|
"Save Settings")
|
|
|
|
(use-local-map widget-keymap)
|
|
(widget-setup)))
|
|
```
|
|
|
|
### Dynamic Form
|
|
|
|
```elisp
|
|
(defun my-dynamic-list ()
|
|
(interactive)
|
|
(let (items-widget)
|
|
(switch-to-buffer "*Dynamic List*")
|
|
(kill-all-local-variables)
|
|
(erase-buffer)
|
|
(remove-overlays)
|
|
|
|
(widget-insert "Todo List\n\n")
|
|
|
|
(setq items-widget
|
|
(widget-create 'editable-list
|
|
:format "%v%i\n"
|
|
:entry-format "%i %d %v\n"
|
|
:value '("Buy groceries" "Write code")
|
|
'(editable-field :size 40)))
|
|
|
|
(widget-insert "\n")
|
|
|
|
(widget-create 'push-button
|
|
:notify (lambda (&rest ignore)
|
|
(let ((items (widget-value items-widget)))
|
|
(message "Todo items: %S" items)))
|
|
"Show Items")
|
|
|
|
(use-local-map widget-keymap)
|
|
(widget-setup)))
|
|
```
|
|
|
|
### Collecting Multiple Values
|
|
|
|
```elisp
|
|
(defun my-collect-values (widgets)
|
|
"Collect values from multiple widgets into alist."
|
|
(mapcar (lambda (pair)
|
|
(cons (car pair)
|
|
(widget-value (cdr pair))))
|
|
widgets))
|
|
|
|
(defun my-form-with-collection ()
|
|
(interactive)
|
|
(let (name-w email-w age-w widgets)
|
|
(switch-to-buffer "*Form*")
|
|
(kill-all-local-variables)
|
|
(erase-buffer)
|
|
(remove-overlays)
|
|
|
|
(widget-insert "User Information\n\n")
|
|
|
|
(setq name-w (widget-create 'editable-field
|
|
:format "Name: %v\n"))
|
|
(setq email-w (widget-create 'editable-field
|
|
:format "Email: %v\n"))
|
|
(setq age-w (widget-create 'editable-field
|
|
:format "Age: %v\n"))
|
|
|
|
(setq widgets `((name . ,name-w)
|
|
(email . ,email-w)
|
|
(age . ,age-w)))
|
|
|
|
(widget-insert "\n")
|
|
|
|
(widget-create 'push-button
|
|
:notify (lambda (&rest ignore)
|
|
(let ((data (my-collect-values widgets)))
|
|
(message "Data: %S" data)))
|
|
"Submit")
|
|
|
|
(use-local-map widget-keymap)
|
|
(widget-setup)))
|
|
```
|
|
|
|
## Format Strings
|
|
|
|
Widget `:format` property controls display using escape sequences:
|
|
|
|
- `%v` - Widget value
|
|
- `%t` - Tag
|
|
- `%d` - Documentation string
|
|
- `%h` - Help-echo
|
|
- `%[` - Button prefix
|
|
- `%]` - Button suffix
|
|
- `%%` - Literal %
|
|
|
|
```elisp
|
|
;; Custom formats
|
|
(widget-create 'push-button
|
|
:format "Click %[here%] to continue\n"
|
|
:notify #'my-callback
|
|
"Continue")
|
|
|
|
(widget-create 'editable-field
|
|
:format "%t: %v (%h)\n"
|
|
:tag "Email"
|
|
:help-echo "user@domain.com")
|
|
```
|
|
|
|
## Notifications and Callbacks
|
|
|
|
The `:notify` property specifies callback on widget change/activation.
|
|
|
|
**Callback signature:**
|
|
```elisp
|
|
(lambda (widget &optional changed-widget &rest event)
|
|
;; widget - the widget with :notify property
|
|
;; changed-widget - widget that actually changed (for composite widgets)
|
|
;; event - interaction event
|
|
...)
|
|
```
|
|
|
|
**Examples:**
|
|
|
|
```elisp
|
|
;; Simple notification
|
|
(widget-create 'checkbox
|
|
:notify (lambda (w &rest _)
|
|
(message "Checked: %s" (widget-value w)))
|
|
nil)
|
|
|
|
;; Access parent widget
|
|
(widget-create 'radio-button-choice
|
|
:notify (lambda (parent child &rest _)
|
|
(message "Parent value: %s" (widget-value parent))
|
|
(message "Child value: %s" (widget-value child)))
|
|
'(item "A")
|
|
'(item "B"))
|
|
|
|
;; Update other widgets
|
|
(let (field1 field2)
|
|
(setq field1
|
|
(widget-create 'editable-field
|
|
:notify (lambda (w &rest _)
|
|
(widget-value-set field2
|
|
(upcase (widget-value w)))
|
|
(widget-setup))))
|
|
(setq field2 (widget-create 'editable-field)))
|
|
```
|
|
|
|
## Keybindings
|
|
|
|
`widget-keymap` provides default bindings:
|
|
|
|
- `TAB` - `widget-forward` - Next widget
|
|
- `S-TAB` / `M-TAB` - `widget-backward` - Previous widget
|
|
- `RET` - `widget-button-press` - Activate button
|
|
- `C-k` - `widget-kill-line` - Kill to end of field
|
|
- `M-TAB` - `widget-complete` - Complete field (if supported)
|
|
|
|
**Custom keymap:**
|
|
|
|
```elisp
|
|
(defvar my-widget-mode-map
|
|
(let ((map (make-sparse-keymap)))
|
|
(set-keymap-parent map widget-keymap)
|
|
(define-key map (kbd "C-c C-c") 'my-submit)
|
|
(define-key map (kbd "C-c C-k") 'my-cancel)
|
|
map))
|
|
|
|
(use-local-map my-widget-mode-map)
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
### Validation
|
|
|
|
```elisp
|
|
(defun my-validate-email (widget)
|
|
(let ((value (widget-value widget)))
|
|
(unless (string-match-p "^[^@]+@[^@]+\\.[^@]+$" value)
|
|
(error "Invalid email format"))))
|
|
|
|
(widget-create 'editable-field
|
|
:format "Email: %v\n"
|
|
:notify (lambda (w &rest _)
|
|
(condition-case err
|
|
(my-validate-email w)
|
|
(error (message "Error: %s" (error-message-string err))))))
|
|
```
|
|
|
|
### Safe Value Retrieval
|
|
|
|
```elisp
|
|
(defun my-safe-widget-value (widget &optional default)
|
|
"Get widget value with fallback."
|
|
(condition-case nil
|
|
(widget-value widget)
|
|
(error default)))
|
|
```
|
|
|
|
## Buffer Cleanup
|
|
|
|
```elisp
|
|
(defun my-widget-cleanup ()
|
|
"Clean up widget buffer."
|
|
(interactive)
|
|
(when (eq major-mode 'my-widget-mode)
|
|
(remove-overlays)
|
|
(kill-buffer)))
|
|
|
|
;; With kill-buffer-hook
|
|
(defvar my-widget-mode-hook nil)
|
|
|
|
(add-hook 'my-widget-mode-hook
|
|
(lambda ()
|
|
(add-hook 'kill-buffer-hook
|
|
(lambda ()
|
|
(remove-overlays))
|
|
nil t)))
|
|
```
|
|
|
|
## Defining Custom Widgets
|
|
|
|
```elisp
|
|
(define-widget 'my-email-field 'editable-field
|
|
"Email input field with validation."
|
|
:format "Email: %v\n"
|
|
:size 40
|
|
:valid-regexp "^[^@]+@[^@]+\\.[^@]+$"
|
|
:notify (lambda (widget &rest ignore)
|
|
(let ((value (widget-value widget))
|
|
(regexp (widget-get widget :valid-regexp)))
|
|
(if (string-match-p regexp value)
|
|
(message "Valid email")
|
|
(message "Invalid email format")))))
|
|
|
|
;; Usage
|
|
(widget-create 'my-email-field)
|
|
```
|
|
|
|
**Inheritance:**
|
|
|
|
```elisp
|
|
(define-widget 'my-readonly-field 'editable-field
|
|
"Read-only text field."
|
|
:keymap (let ((map (copy-keymap widget-field-keymap)))
|
|
(suppress-keymap map)
|
|
map))
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
**Minimize redraws:**
|
|
```elisp
|
|
;; Bad: Multiple setups
|
|
(widget-value-set widget1 val1)
|
|
(widget-setup)
|
|
(widget-value-set widget2 val2)
|
|
(widget-setup)
|
|
|
|
;; Good: Batch updates
|
|
(widget-value-set widget1 val1)
|
|
(widget-value-set widget2 val2)
|
|
(widget-setup)
|
|
```
|
|
|
|
**Use buffer-local variables:**
|
|
```elisp
|
|
(defvar-local my-widget-data nil
|
|
"Buffer-local widget storage.")
|
|
```
|
|
|
|
**Avoid redundant notifications:**
|
|
```elisp
|
|
(defvar my-updating nil)
|
|
|
|
(widget-create 'editable-field
|
|
:notify (lambda (w &rest _)
|
|
(unless my-updating
|
|
(setq my-updating t)
|
|
(my-expensive-update)
|
|
(setq my-updating nil))))
|
|
```
|
|
|
|
## Integration with Major Modes
|
|
|
|
```elisp
|
|
(define-derived-mode my-widget-mode special-mode "MyWidget"
|
|
"Major mode for widget-based interface."
|
|
:group 'my-widget
|
|
(setq truncate-lines t)
|
|
(setq buffer-read-only nil))
|
|
|
|
(defun my-widget-interface ()
|
|
(interactive)
|
|
(switch-to-buffer "*My Interface*")
|
|
(my-widget-mode)
|
|
(erase-buffer)
|
|
|
|
;; Build interface
|
|
(widget-insert "My Application\n\n")
|
|
;; ... create widgets ...
|
|
|
|
(use-local-map widget-keymap)
|
|
(widget-setup)
|
|
(goto-char (point-min))
|
|
(widget-forward 1))
|
|
```
|
|
|
|
## Debugging
|
|
|
|
```elisp
|
|
;; Inspect widget properties
|
|
(pp-eval-expression '(widget-get my-widget :value))
|
|
|
|
;; Check widget type
|
|
(widget-type my-widget)
|
|
|
|
;; View all properties
|
|
(let ((widget my-widget))
|
|
(while widget
|
|
(prin1 (car widget))
|
|
(terpri)
|
|
(setq widget (cdr widget))))
|
|
|
|
;; Trace notifications
|
|
(widget-create 'checkbox
|
|
:notify (lambda (&rest args)
|
|
(message "Notify called with: %S" args)
|
|
(backtrace))
|
|
t)
|
|
```
|
|
|
|
## Use Cases
|
|
|
|
### Configuration Editor
|
|
|
|
Customize package using widgets instead of Custom interface:
|
|
|
|
```elisp
|
|
(defun my-config-editor ()
|
|
(interactive)
|
|
(let (theme-w size-w)
|
|
(switch-to-buffer "*Config*")
|
|
(kill-all-local-variables)
|
|
(erase-buffer)
|
|
|
|
(widget-insert "Configuration\n\n")
|
|
|
|
(setq theme-w
|
|
(widget-create 'menu-choice
|
|
:tag "Theme"
|
|
:value my-theme
|
|
'(const light)
|
|
'(const dark)))
|
|
|
|
(setq size-w
|
|
(widget-create 'editable-field
|
|
:format "\nFont size: %v\n"
|
|
:value (number-to-string my-font-size)))
|
|
|
|
(widget-insert "\n")
|
|
(widget-create 'push-button
|
|
:notify (lambda (&rest _)
|
|
(setq my-theme (widget-value theme-w))
|
|
(setq my-font-size
|
|
(string-to-number (widget-value size-w)))
|
|
(my-apply-config))
|
|
"Apply")
|
|
|
|
(use-local-map widget-keymap)
|
|
(widget-setup)))
|
|
```
|
|
|
|
### Search/Filter Interface
|
|
|
|
```elisp
|
|
(defun my-search-interface ()
|
|
(interactive)
|
|
(let (query-w type-w)
|
|
(switch-to-buffer "*Search*")
|
|
(kill-all-local-variables)
|
|
(erase-buffer)
|
|
|
|
(setq query-w
|
|
(widget-create 'editable-field
|
|
:format "Query: %v\n"
|
|
:size 50))
|
|
|
|
(setq type-w
|
|
(widget-create 'checklist
|
|
:format "\nTypes:\n%v\n"
|
|
'(const :tag "Functions" function)
|
|
'(const :tag "Variables" variable)
|
|
'(const :tag "Faces" face)))
|
|
|
|
(widget-create 'push-button
|
|
:notify (lambda (&rest _)
|
|
(my-perform-search
|
|
(widget-value query-w)
|
|
(widget-value type-w)))
|
|
"Search")
|
|
|
|
(use-local-map widget-keymap)
|
|
(widget-setup)))
|
|
```
|
|
|
|
### Wizard/Multi-step Form
|
|
|
|
```elisp
|
|
(defvar my-wizard-step 1)
|
|
(defvar my-wizard-data nil)
|
|
|
|
(defun my-wizard-next ()
|
|
(setq my-wizard-data
|
|
(plist-put my-wizard-data
|
|
(intern (format "step%d" my-wizard-step))
|
|
(my-collect-current-step)))
|
|
(setq my-wizard-step (1+ my-wizard-step))
|
|
(my-wizard-show))
|
|
|
|
(defun my-wizard-show ()
|
|
(erase-buffer)
|
|
(cond
|
|
((= my-wizard-step 1)
|
|
(my-wizard-step-1))
|
|
((= my-wizard-step 2)
|
|
(my-wizard-step-2))
|
|
(t
|
|
(my-wizard-finish)))
|
|
(widget-setup))
|
|
```
|
|
|
|
## Common Pitfalls
|
|
|
|
**Forgetting widget-setup:**
|
|
```elisp
|
|
;; Wrong
|
|
(widget-create 'editable-field)
|
|
;; User can't interact yet!
|
|
|
|
;; Correct
|
|
(widget-create 'editable-field)
|
|
(widget-setup)
|
|
```
|
|
|
|
**Not calling widget-setup after value-set:**
|
|
```elisp
|
|
;; Wrong
|
|
(widget-value-set widget "new value")
|
|
;; Widget not updated!
|
|
|
|
;; Correct
|
|
(widget-value-set widget "new value")
|
|
(widget-setup)
|
|
```
|
|
|
|
**Incorrect buffer setup:**
|
|
```elisp
|
|
;; Missing keymap
|
|
(erase-buffer)
|
|
(widget-create 'editable-field)
|
|
(widget-setup)
|
|
;; TAB won't navigate!
|
|
|
|
;; Correct
|
|
(erase-buffer)
|
|
(widget-create 'editable-field)
|
|
(use-local-map widget-keymap)
|
|
(widget-setup)
|
|
```
|
|
|
|
**Not cleaning overlays:**
|
|
```elisp
|
|
;; Memory leak
|
|
(erase-buffer)
|
|
(widget-create ...)
|
|
|
|
;; Proper cleanup
|
|
(erase-buffer)
|
|
(remove-overlays)
|
|
(widget-create ...)
|
|
```
|
|
|
|
## Resources
|
|
|
|
- Info manual: `C-h i m Widget RET`
|
|
- Source: `wid-edit.el`, `widget.el` in Emacs distribution
|
|
- Demo: `M-x widget-example` (if available)
|
|
- Tutorial: Official Emacs Widget Library manual
|