Initial commit
This commit is contained in:
138
skills/widget/README.md
Normal file
138
skills/widget/README.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Emacs Widget Library
|
||||
|
||||
Build interactive forms and UI elements in Emacs buffers.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```elisp
|
||||
(require 'widget)
|
||||
(eval-when-compile
|
||||
(require 'wid-edit))
|
||||
|
||||
(defun my-simple-form ()
|
||||
(interactive)
|
||||
(switch-to-buffer "*My Form*")
|
||||
(kill-all-local-variables)
|
||||
(erase-buffer)
|
||||
(remove-overlays)
|
||||
|
||||
;; Create widgets
|
||||
(widget-insert "Contact Form\n\n")
|
||||
|
||||
(widget-create 'editable-field
|
||||
:format "Name: %v\n"
|
||||
:size 30)
|
||||
|
||||
(widget-insert "\n")
|
||||
|
||||
(widget-create 'editable-field
|
||||
:format "Email: %v\n"
|
||||
:size 40)
|
||||
|
||||
(widget-insert "\n\n")
|
||||
|
||||
(widget-create 'push-button
|
||||
:notify (lambda (&rest ignore)
|
||||
(message "Form submitted!"))
|
||||
"Submit")
|
||||
|
||||
;; Enable interaction
|
||||
(use-local-map widget-keymap)
|
||||
(widget-setup))
|
||||
```
|
||||
|
||||
## Key Widget Types
|
||||
|
||||
**Text Input:**
|
||||
- `editable-field` - Single-line text
|
||||
- `text` - Multi-line text area
|
||||
|
||||
**Buttons:**
|
||||
- `push-button` - Clickable action button
|
||||
- `link` - Hyperlink with action
|
||||
|
||||
**Selection:**
|
||||
- `checkbox` - Boolean toggle
|
||||
- `radio-button-choice` - Single choice from options
|
||||
- `menu-choice` - Dropdown selection
|
||||
- `checklist` - Multiple selections
|
||||
|
||||
**Lists:**
|
||||
- `editable-list` - Dynamic list with add/remove
|
||||
|
||||
## Essential Functions
|
||||
|
||||
```elisp
|
||||
;; Create widget
|
||||
(widget-create TYPE &rest ARGS)
|
||||
|
||||
;; Enable widgets (required after creation)
|
||||
(widget-setup)
|
||||
|
||||
;; Get/set values
|
||||
(widget-value WIDGET)
|
||||
(widget-value-set WIDGET VALUE)
|
||||
|
||||
;; Navigate
|
||||
(widget-forward) ; TAB
|
||||
(widget-backward) ; S-TAB
|
||||
|
||||
;; Properties
|
||||
(widget-get WIDGET PROPERTY)
|
||||
(widget-put WIDGET PROPERTY VALUE)
|
||||
```
|
||||
|
||||
## Common Pattern
|
||||
|
||||
```elisp
|
||||
(defun my-widget-interface ()
|
||||
(interactive)
|
||||
(let (field1 field2)
|
||||
;; Setup buffer
|
||||
(switch-to-buffer "*Interface*")
|
||||
(kill-all-local-variables)
|
||||
(erase-buffer)
|
||||
(remove-overlays)
|
||||
|
||||
;; Build UI
|
||||
(widget-insert "Title\n\n")
|
||||
(setq field1 (widget-create 'editable-field "default"))
|
||||
(widget-insert "\n")
|
||||
(setq field2 (widget-create 'checkbox t))
|
||||
|
||||
;; Submit button
|
||||
(widget-create 'push-button
|
||||
:notify (lambda (&rest _)
|
||||
(message "Values: %S %S"
|
||||
(widget-value field1)
|
||||
(widget-value field2)))
|
||||
"Submit")
|
||||
|
||||
;; Activate
|
||||
(use-local-map widget-keymap)
|
||||
(widget-setup)))
|
||||
```
|
||||
|
||||
## Using the Skill
|
||||
|
||||
Invoke this skill when building:
|
||||
- Custom configuration interfaces
|
||||
- Interactive forms
|
||||
- Settings editors
|
||||
- Data entry dialogs
|
||||
|
||||
The skill provides comprehensive coverage of widget types, API functions, patterns, and real-world examples.
|
||||
|
||||
## Learning Path
|
||||
|
||||
1. **Basics** - Simple forms with editable-field and push-button
|
||||
2. **Selection** - checkbox, radio-button-choice, menu-choice
|
||||
3. **Callbacks** - :notify handlers and value retrieval
|
||||
4. **Validation** - Input validation and error handling
|
||||
5. **Advanced** - Custom widgets and complex interfaces
|
||||
|
||||
## Resources
|
||||
|
||||
- Emacs Info: `C-h i m Widget RET`
|
||||
- Source: `wid-edit.el` in Emacs distribution
|
||||
- Built-in demo: `widget-example` (if available)
|
||||
952
skills/widget/SKILL.md
Normal file
952
skills/widget/SKILL.md
Normal file
@@ -0,0 +1,952 @@
|
||||
---
|
||||
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
|
||||
73
skills/widget/metadata.edn
Normal file
73
skills/widget/metadata.edn
Normal file
@@ -0,0 +1,73 @@
|
||||
{:name "widget"
|
||||
:version "1.0.0"
|
||||
:description "A guide to using the Emacs Widget Library for creating interactive UI elements in buffers."
|
||||
|
||||
:library {:name "wid-edit.el / widget.el"
|
||||
:version "Built-in"
|
||||
:url "https://www.gnu.org/software/emacs/manual/html_mono/widget.html"
|
||||
:license "GPL-3.0-or-later"
|
||||
:repository "https://github.com/emacs-mirror/emacs/blob/master/lisp/wid-edit.el"}
|
||||
|
||||
:tags [:emacs :ui :forms :widgets :interactive :buffer :interface]
|
||||
|
||||
:use-cases ["Custom configuration interfaces"
|
||||
"Interactive forms and dialogs"
|
||||
"Settings and preference editors"
|
||||
"Data entry and validation"
|
||||
"Multi-step wizards"
|
||||
"Search and filter interfaces"]
|
||||
|
||||
:features ["Text input fields (single and multi-line)"
|
||||
"Buttons and links"
|
||||
"Checkboxes and radio buttons"
|
||||
"Dropdown menus and checklists"
|
||||
"Dynamic editable lists"
|
||||
"Custom widget definitions"
|
||||
"Value validation"
|
||||
"Callback notifications"
|
||||
"Keyboard navigation"]
|
||||
|
||||
:files {:skill "SKILL.md"
|
||||
:readme "README.md"
|
||||
:examples nil}
|
||||
|
||||
:learning-path [{:level "beginner"
|
||||
:topics ["Basic widget creation"
|
||||
"Buffer setup"
|
||||
"editable-field and push-button"
|
||||
"widget-setup requirement"]}
|
||||
{:level "intermediate"
|
||||
:topics ["Selection widgets"
|
||||
"Callbacks and notifications"
|
||||
"Value retrieval and setting"
|
||||
"Form validation"]}
|
||||
{:level "advanced"
|
||||
:topics ["Custom widget definitions"
|
||||
"Complex forms"
|
||||
"Major mode integration"
|
||||
"Performance optimization"]}]
|
||||
|
||||
:platform {:emacs "Built-in"
|
||||
:dependencies ["widget.el" "wid-edit.el"]}
|
||||
|
||||
:api-coverage {:core ["widget-create"
|
||||
"widget-setup"
|
||||
"widget-value"
|
||||
"widget-value-set"
|
||||
"widget-insert"]
|
||||
:navigation ["widget-forward"
|
||||
"widget-backward"]
|
||||
:properties ["widget-get"
|
||||
"widget-put"
|
||||
"widget-apply"]
|
||||
:lifecycle ["widget-delete"]
|
||||
:types ["editable-field"
|
||||
"text"
|
||||
"push-button"
|
||||
"link"
|
||||
"checkbox"
|
||||
"toggle"
|
||||
"radio-button-choice"
|
||||
"menu-choice"
|
||||
"checklist"
|
||||
"editable-list"]}}
|
||||
Reference in New Issue
Block a user