Initial commit
This commit is contained in:
228
skills/magit-section/README.md
Normal file
228
skills/magit-section/README.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# magit-section
|
||||
|
||||
Library for creating collapsible, hierarchical buffer UIs with interactive sections - the foundation of Magit's interface.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Section Buffer
|
||||
|
||||
```elisp
|
||||
(require 'magit-section)
|
||||
|
||||
(defun my-simple-browser ()
|
||||
"Create a buffer with collapsible file sections."
|
||||
(interactive)
|
||||
(let ((buf (get-buffer-create "*File Browser*")))
|
||||
(with-current-buffer buf
|
||||
(magit-section-mode)
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(magit-insert-section (root)
|
||||
(magit-insert-heading "Files")
|
||||
(dolist (file (directory-files default-directory))
|
||||
(magit-insert-section (file file)
|
||||
(magit-insert-heading file)
|
||||
(insert (format " Size: %d bytes\n"
|
||||
(file-attribute-size
|
||||
(file-attributes file)))))))))
|
||||
(pop-to-buffer buf)))
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
**Keybindings:**
|
||||
- `n` / `p` - Next/previous section
|
||||
- `^` - Parent section
|
||||
- `TAB` - Toggle current section
|
||||
- `M-n` / `M-p` - Next/previous sibling
|
||||
- `1-4` - Show levels around current section
|
||||
|
||||
### Section Creation
|
||||
|
||||
```elisp
|
||||
;; Basic section
|
||||
(magit-insert-section (type-name value)
|
||||
(magit-insert-heading "Section Title")
|
||||
(insert "Content here\n"))
|
||||
|
||||
;; Nested sections
|
||||
(magit-insert-section (parent "parent-value")
|
||||
(magit-insert-heading "Parent")
|
||||
(magit-insert-section (child "child-value")
|
||||
(magit-insert-heading "Child")
|
||||
(insert "Nested content\n")))
|
||||
|
||||
;; Initially hidden section
|
||||
(magit-insert-section (data value t) ; t = hidden
|
||||
(magit-insert-heading "Hidden by Default")
|
||||
(insert "This content is initially collapsed\n"))
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Hierarchical structure:** Nest sections to any depth
|
||||
- **Interactive navigation:** Built-in keybindings for movement
|
||||
- **Visibility control:** Collapse/expand sections individually or globally
|
||||
- **Section-specific keymaps:** Custom actions per section type
|
||||
- **Data association:** Store arbitrary values in sections
|
||||
- **Visibility caching:** Preserve collapse state across refreshes
|
||||
- **Performance:** Deferred content loading for large datasets
|
||||
- **Mouse support:** Click margins to toggle sections
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Refreshable Buffer
|
||||
|
||||
```elisp
|
||||
(defvar-local my-refresh-fn nil)
|
||||
|
||||
(defun my-refresh ()
|
||||
"Refresh current buffer."
|
||||
(interactive)
|
||||
(when my-refresh-fn
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(funcall my-refresh-fn))))
|
||||
|
||||
(defun my-create-buffer ()
|
||||
(let ((buf (get-buffer-create "*My Data*")))
|
||||
(with-current-buffer buf
|
||||
(magit-section-mode)
|
||||
(setq my-refresh-fn #'my-insert-data)
|
||||
(local-set-key (kbd "g") #'my-refresh)
|
||||
(my-refresh))
|
||||
buf))
|
||||
```
|
||||
|
||||
### Section-Specific Actions
|
||||
|
||||
```elisp
|
||||
(defclass file-section (magit-section)
|
||||
((keymap :initform 'file-section-map)))
|
||||
|
||||
(defvar file-section-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "RET") #'my-open-file)
|
||||
(define-key map (kbd "d") #'my-delete-file)
|
||||
map))
|
||||
|
||||
(defun my-open-file ()
|
||||
"Open file in current section."
|
||||
(interactive)
|
||||
(when-let ((section (magit-current-section)))
|
||||
(when (eq (oref section type) 'file-section)
|
||||
(find-file (oref section value)))))
|
||||
|
||||
;; Use the custom section type
|
||||
(magit-insert-section (file-section "/path/to/file")
|
||||
(magit-insert-heading "README.md")
|
||||
(insert "Content\n"))
|
||||
```
|
||||
|
||||
### Querying Sections
|
||||
|
||||
```elisp
|
||||
;; Get current section
|
||||
(let ((section (magit-current-section)))
|
||||
(message "Type: %s, Value: %s"
|
||||
(oref section type)
|
||||
(oref section value)))
|
||||
|
||||
;; Check section type
|
||||
(when (magit-section-match 'file)
|
||||
(message "On a file section"))
|
||||
|
||||
;; Match hierarchy (hunk within file)
|
||||
(when (magit-section-match [file hunk])
|
||||
(message "On a hunk in a file"))
|
||||
|
||||
;; Get selected sections in region
|
||||
(let ((files (magit-region-sections 'file t)))
|
||||
(message "Selected %d files" (length files)))
|
||||
```
|
||||
|
||||
### Deferred Content Loading
|
||||
|
||||
```elisp
|
||||
;; Only compute when section is first expanded
|
||||
(magit-insert-section (expensive-data)
|
||||
(magit-insert-heading "Large Dataset")
|
||||
(magit-insert-section-body
|
||||
(insert (compute-expensive-data))))
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```elisp
|
||||
;; Highlight current section
|
||||
(setq magit-section-highlight-current t)
|
||||
|
||||
;; Show child count in headings
|
||||
(setq magit-section-show-child-count t)
|
||||
|
||||
;; Cache visibility across refreshes
|
||||
(setq magit-section-cache-visibility t)
|
||||
|
||||
;; Custom visibility indicators
|
||||
(setq magit-section-visibility-indicators
|
||||
'((expanded . "▼")
|
||||
(collapsed . "▶")))
|
||||
|
||||
;; Initial visibility by type
|
||||
(setq magit-section-initial-visibility-alist
|
||||
'((details . hide)
|
||||
(metadata . hide)))
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **File browsers** with hierarchical directory trees
|
||||
- **Log viewers** with collapsible entries
|
||||
- **Process monitors** with expandable details
|
||||
- **Configuration inspectors** organizing settings
|
||||
- **Documentation browsers** with section navigation
|
||||
- **Data explorers** for structured content
|
||||
- **Custom Git UIs** (like Magit itself)
|
||||
- **API response viewers** with nested data
|
||||
|
||||
## Using the Skill
|
||||
|
||||
This skill provides:
|
||||
- Complete API reference for all magit-section functions
|
||||
- Comprehensive usage patterns and best practices
|
||||
- Detailed examples for common scenarios
|
||||
- Performance optimization techniques
|
||||
- Integration patterns with Emacs modes
|
||||
|
||||
Consult `SKILL.md` for in-depth documentation.
|
||||
|
||||
## Learning Path
|
||||
|
||||
1. **Basics:** Create simple sections, basic navigation
|
||||
2. **Structure:** Nested hierarchies, headings, content
|
||||
3. **Interaction:** Section keymaps, actions, queries
|
||||
4. **Visibility:** Toggle, cycle, caching strategies
|
||||
5. **Advanced:** Custom section types, washing buffers, performance
|
||||
6. **Integration:** Major modes, refreshing, context menus
|
||||
|
||||
## External Resources
|
||||
|
||||
- [Magit Repository](https://github.com/magit/magit)
|
||||
- [Magit-Section Tutorial](https://github.com/magit/magit/wiki/Magit-Section-Tutorial)
|
||||
- [Magit Documentation](https://magit.vc/manual/magit.html)
|
||||
- Package: Available on NonGNU ELPA
|
||||
|
||||
## Requirements
|
||||
|
||||
- Emacs 28.1+
|
||||
- Dependencies: compat 30.1+, cond-let 0.1+, llama 1.0+, seq 2.24+
|
||||
|
||||
## Installation
|
||||
|
||||
```elisp
|
||||
;; Via package.el
|
||||
(package-install 'magit-section)
|
||||
|
||||
;; In your init file
|
||||
(require 'magit-section)
|
||||
```
|
||||
792
skills/magit-section/SKILL.md
Normal file
792
skills/magit-section/SKILL.md
Normal file
@@ -0,0 +1,792 @@
|
||||
---
|
||||
name: magit-section
|
||||
description: A guide to using magit-section for building collapsible, hierarchical buffer UIs in Emacs.
|
||||
---
|
||||
|
||||
# magit-section: Collapsible Section-Based UIs
|
||||
|
||||
magit-section is Emacs's premier library for creating interactive, hierarchical buffer interfaces with collapsible sections. Originally extracted from Magit, it provides the foundation for building information-dense UIs that users can navigate and explore efficiently.
|
||||
|
||||
## Overview
|
||||
|
||||
magit-section enables creation of buffers with tree-like, collapsible content organized into sections. Each section can contain nested child sections, custom keybindings, associated data, and responsive highlighting.
|
||||
|
||||
**Key Characteristics:**
|
||||
- Hierarchical collapsible sections with visibility control
|
||||
- Section-specific keymaps and actions
|
||||
- Built-in navigation commands
|
||||
- Visibility caching across buffer refreshes
|
||||
- Mouse and keyboard interaction
|
||||
- Integrated with Emacs region selection
|
||||
- Requires Emacs 28.1+
|
||||
|
||||
**Version:** 4.2.0+ (January 2025)
|
||||
**Repository:** https://github.com/magit/magit
|
||||
**License:** GPL-3.0+
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Section Object
|
||||
|
||||
Sections are EIEIO objects with these slots:
|
||||
|
||||
- `type` - Symbol identifying section kind (e.g., `file`, `commit`, `hunk`)
|
||||
- `value` - Associated data (filename, commit SHA, etc.)
|
||||
- `start` - Buffer position where section begins (includes heading)
|
||||
- `content` - Buffer position where body content starts
|
||||
- `end` - Buffer position where section ends
|
||||
- `hidden` - Visibility state (nil=visible, non-nil=hidden)
|
||||
- `children` - List of child sections
|
||||
- `parent` - Parent section reference
|
||||
- `keymap` - Section-specific key bindings
|
||||
- `washer` - Function for deferred content generation
|
||||
|
||||
### Buffer Structure
|
||||
|
||||
Every magit-section buffer requires a single root section that spans the entire buffer. Sections form a tree hierarchy with proper nesting.
|
||||
|
||||
### Visibility States
|
||||
|
||||
Sections can be:
|
||||
- **Fully visible** - Heading and all content shown
|
||||
- **Hidden** - Only heading visible, content collapsed
|
||||
- **Heading-only** - Nested sections show only headings
|
||||
|
||||
## API Reference
|
||||
|
||||
### Creating Sections
|
||||
|
||||
#### magit-insert-section
|
||||
|
||||
Primary macro for section creation:
|
||||
|
||||
```elisp
|
||||
(magit-insert-section (type value &optional hide)
|
||||
[HEADING-FORM]
|
||||
BODY...)
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
- `type` - Section type (symbol or `(eval FORM)`)
|
||||
- `value` - Data to store in section's value slot
|
||||
- `hide` - Initial visibility (nil=visible, t=hidden)
|
||||
|
||||
**Example:**
|
||||
|
||||
```elisp
|
||||
(magit-insert-section (file "README.md")
|
||||
(magit-insert-heading "README.md")
|
||||
(insert "File contents here\n"))
|
||||
```
|
||||
|
||||
**Advanced Usage:**
|
||||
|
||||
```elisp
|
||||
(magit-insert-section (commit commit-sha nil)
|
||||
(magit-insert-heading
|
||||
(format "%s %s"
|
||||
(substring commit-sha 0 7)
|
||||
(magit-commit-message commit-sha)))
|
||||
;; Insert commit details
|
||||
(magit-insert-section (diffstat commit-sha)
|
||||
(insert (magit-format-diffstat commit-sha))))
|
||||
```
|
||||
|
||||
#### magit-insert-heading
|
||||
|
||||
Insert section heading with optional child count:
|
||||
|
||||
```elisp
|
||||
(magit-insert-heading &rest ARGS)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```elisp
|
||||
(magit-insert-heading
|
||||
(format "Changes (%d)" (length file-list)))
|
||||
```
|
||||
|
||||
#### magit-insert-section-body
|
||||
|
||||
Defer section body evaluation until first expansion:
|
||||
|
||||
```elisp
|
||||
(magit-insert-section-body
|
||||
;; Expensive operations here
|
||||
(insert (expensive-computation)))
|
||||
```
|
||||
|
||||
Use for performance when sections are initially hidden.
|
||||
|
||||
#### magit-cancel-section
|
||||
|
||||
Abort partial section creation:
|
||||
|
||||
```elisp
|
||||
(when (null items)
|
||||
(magit-cancel-section))
|
||||
```
|
||||
|
||||
Removes partial section from buffer and section tree.
|
||||
|
||||
### Navigation Commands
|
||||
|
||||
#### Movement
|
||||
|
||||
```elisp
|
||||
(magit-section-forward) ; Next sibling or parent's next
|
||||
(magit-section-backward) ; Previous sibling or parent
|
||||
(magit-section-up) ; Parent section
|
||||
(magit-section-forward-sibling) ; Next sibling
|
||||
(magit-section-backward-sibling) ; Previous sibling
|
||||
```
|
||||
|
||||
**Keybindings:**
|
||||
- `n` - Forward
|
||||
- `p` - Backward
|
||||
- `^` - Up to parent
|
||||
- `M-n` - Forward sibling
|
||||
- `M-p` - Backward sibling
|
||||
|
||||
**Example Navigation Hook:**
|
||||
|
||||
```elisp
|
||||
(add-hook 'magit-section-movement-hook
|
||||
(lambda ()
|
||||
(when (eq (oref (magit-current-section) type) 'commit)
|
||||
(message "On commit: %s"
|
||||
(oref (magit-current-section) value)))))
|
||||
```
|
||||
|
||||
### Visibility Control
|
||||
|
||||
#### Basic Toggle
|
||||
|
||||
```elisp
|
||||
(magit-section-toggle &optional SECTION) ; Toggle current/specified
|
||||
(magit-section-show SECTION) ; Expand
|
||||
(magit-section-hide SECTION) ; Collapse
|
||||
```
|
||||
|
||||
**Keybindings:**
|
||||
- `TAB` - Toggle current section
|
||||
- `C-c TAB` / `C-<tab>` - Cycle visibility states
|
||||
- `<backtab>` - Cycle all top-level sections
|
||||
|
||||
#### Recursive Operations
|
||||
|
||||
```elisp
|
||||
(magit-section-show-children SECTION &optional DEPTH)
|
||||
(magit-section-hide-children SECTION)
|
||||
(magit-section-show-headings SECTION)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```elisp
|
||||
;; Expand section and first level of children
|
||||
(magit-section-show-children (magit-current-section) 1)
|
||||
```
|
||||
|
||||
#### Level-Based Visibility
|
||||
|
||||
```elisp
|
||||
(magit-section-show-level-1) ; Show only top level
|
||||
(magit-section-show-level-2) ; Show two levels
|
||||
(magit-section-show-level-3) ; Show three levels
|
||||
(magit-section-show-level-4) ; Show four levels
|
||||
```
|
||||
|
||||
**Keybindings:**
|
||||
- `1` / `2` / `3` / `4` - Show levels around current section
|
||||
- `M-1` / `M-2` / `M-3` / `M-4` - Show levels for entire buffer
|
||||
|
||||
#### Cycling
|
||||
|
||||
```elisp
|
||||
(magit-section-cycle &optional SECTION) ; Current section
|
||||
(magit-section-cycle-global) ; All sections
|
||||
```
|
||||
|
||||
Cycles through: collapsed → expanded → children-collapsed → collapsed
|
||||
|
||||
### Querying Sections
|
||||
|
||||
#### Current Section
|
||||
|
||||
```elisp
|
||||
(magit-current-section) ; Section at point
|
||||
(magit-section-at &optional POS) ; Section at position
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```elisp
|
||||
(let ((section (magit-current-section)))
|
||||
(message "Type: %s, Value: %s"
|
||||
(oref section type)
|
||||
(oref section value)))
|
||||
```
|
||||
|
||||
#### Section Properties
|
||||
|
||||
```elisp
|
||||
(magit-section-ident SECTION) ; Unique identifier
|
||||
(magit-section-lineage SECTION) ; List of ancestor sections
|
||||
(magit-section-hidden SECTION) ; Hidden or ancestor hidden?
|
||||
(magit-section-content-p SECTION) ; Has body content?
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```elisp
|
||||
(when (magit-section-content-p section)
|
||||
(magit-section-show section))
|
||||
```
|
||||
|
||||
#### Section Matching
|
||||
|
||||
```elisp
|
||||
(magit-section-match CONDITIONS &optional SECTION)
|
||||
```
|
||||
|
||||
**Conditions:**
|
||||
- Symbol matches type
|
||||
- List `(TYPE VALUE)` matches type and value
|
||||
- List of symbols matches any type in list
|
||||
- `[TYPE...]` matches type hierarchy
|
||||
|
||||
**Example:**
|
||||
|
||||
```elisp
|
||||
;; Check if section is a file
|
||||
(when (magit-section-match 'file)
|
||||
(message "Current section is a file"))
|
||||
|
||||
;; Match specific file
|
||||
(when (magit-section-match '(file "README.md"))
|
||||
(message "On README.md"))
|
||||
|
||||
;; Match hierarchy: hunk within file
|
||||
(when (magit-section-match [file hunk])
|
||||
(message "In a hunk within a file section"))
|
||||
```
|
||||
|
||||
#### Region Selection
|
||||
|
||||
```elisp
|
||||
(magit-region-sections &optional CONDITION MULTIPLE)
|
||||
```
|
||||
|
||||
Get sibling sections in active region.
|
||||
|
||||
**Example:**
|
||||
|
||||
```elisp
|
||||
(let ((selected-files (magit-region-sections 'file t)))
|
||||
(dolist (section selected-files)
|
||||
(message "Selected: %s" (oref section value))))
|
||||
```
|
||||
|
||||
### Section Inspection
|
||||
|
||||
```elisp
|
||||
(magit-describe-section &optional SECTION INTERACTIVE)
|
||||
(magit-describe-section-briefly &optional SECTION INTERACTIVE)
|
||||
```
|
||||
|
||||
Display detailed section information for debugging.
|
||||
|
||||
**Keybinding:** `C-h .` (in magit-section buffers)
|
||||
|
||||
### Section-Specific Keymaps
|
||||
|
||||
Define per-type keybindings using the `:keymap` class slot:
|
||||
|
||||
```elisp
|
||||
(defclass my-file-section (magit-section)
|
||||
((keymap :initform 'my-file-section-map)))
|
||||
|
||||
(defvar my-file-section-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "RET") 'my-open-file)
|
||||
(define-key map (kbd "d") 'my-delete-file)
|
||||
map))
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```elisp
|
||||
(magit-insert-section (my-file-section filename)
|
||||
(magit-insert-heading filename)
|
||||
(insert "Content..."))
|
||||
;; RET and d keys work only on this section type
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Highlighting
|
||||
|
||||
```elisp
|
||||
;; Highlight current section
|
||||
(setq magit-section-highlight-current t)
|
||||
|
||||
;; Highlight region selection
|
||||
(setq magit-section-highlight-selection t)
|
||||
```
|
||||
|
||||
### Child Count Display
|
||||
|
||||
```elisp
|
||||
;; Show number of children in headings
|
||||
(setq magit-section-show-child-count t)
|
||||
```
|
||||
|
||||
### Visibility Indicators
|
||||
|
||||
```elisp
|
||||
;; Customize expansion/collapse indicators
|
||||
(setq magit-section-visibility-indicators
|
||||
'((expanded . "▼")
|
||||
(collapsed . "▶")))
|
||||
```
|
||||
|
||||
### Visibility Caching
|
||||
|
||||
```elisp
|
||||
;; Cache visibility across refreshes
|
||||
(setq magit-section-cache-visibility t)
|
||||
|
||||
;; Cache only specific section types
|
||||
(setq magit-section-cache-visibility '(file commit))
|
||||
|
||||
;; Set initial visibility by type
|
||||
(setq magit-section-initial-visibility-alist
|
||||
'((stash . hide)
|
||||
(untracked . hide)))
|
||||
```
|
||||
|
||||
### Line Numbers
|
||||
|
||||
```elisp
|
||||
;; Disable line numbers in section buffers (default)
|
||||
(setq magit-section-disable-line-numbers t)
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Basic Section Buffer
|
||||
|
||||
```elisp
|
||||
(defun my-section-buffer ()
|
||||
"Create a buffer with collapsible sections."
|
||||
(interactive)
|
||||
(let ((buf (get-buffer-create "*My Sections*")))
|
||||
(with-current-buffer buf
|
||||
(magit-section-mode)
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(magit-insert-section (root)
|
||||
(magit-insert-heading "My Data")
|
||||
(magit-insert-section (files)
|
||||
(magit-insert-heading "Files")
|
||||
(dolist (file (directory-files default-directory))
|
||||
(magit-insert-section (file file)
|
||||
(insert file "\n"))))
|
||||
(magit-insert-section (buffers)
|
||||
(magit-insert-heading "Buffers")
|
||||
(dolist (buf (buffer-list))
|
||||
(magit-insert-section (buffer buf)
|
||||
(insert (buffer-name buf) "\n")))))))
|
||||
(pop-to-buffer buf)))
|
||||
```
|
||||
|
||||
### Refreshable Buffer
|
||||
|
||||
```elisp
|
||||
(defvar-local my-refresh-function nil)
|
||||
|
||||
(defun my-section-refresh ()
|
||||
"Refresh current section buffer."
|
||||
(interactive)
|
||||
(when my-refresh-function
|
||||
(let ((inhibit-read-only t)
|
||||
(line (line-number-at-pos))
|
||||
(col (current-column)))
|
||||
(erase-buffer)
|
||||
(funcall my-refresh-function)
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- line))
|
||||
(move-to-column col))))
|
||||
|
||||
(defun my-create-buffer ()
|
||||
(let ((buf (get-buffer-create "*My Data*")))
|
||||
(with-current-buffer buf
|
||||
(magit-section-mode)
|
||||
(setq my-refresh-function #'my-insert-content)
|
||||
(local-set-key (kbd "g") #'my-section-refresh)
|
||||
(my-section-refresh))
|
||||
buf))
|
||||
```
|
||||
|
||||
### Washing External Command Output
|
||||
|
||||
```elisp
|
||||
(defun my-insert-git-status ()
|
||||
"Insert git status output as sections."
|
||||
(magit-insert-section (status)
|
||||
(magit-insert-heading "Git Status")
|
||||
(let ((start (point)))
|
||||
(call-process "git" nil t nil "status" "--short")
|
||||
(save-restriction
|
||||
(narrow-to-region start (point))
|
||||
(goto-char start)
|
||||
(while (not (eobp))
|
||||
(let ((line-start (point))
|
||||
(file (buffer-substring (+ (point) 3)
|
||||
(line-end-position))))
|
||||
(magit-insert-section (file file)
|
||||
(forward-line 1))))))))
|
||||
```
|
||||
|
||||
### Section Actions
|
||||
|
||||
```elisp
|
||||
(defclass file-section (magit-section)
|
||||
((keymap :initform 'file-section-map)))
|
||||
|
||||
(defvar file-section-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "RET") 'my-visit-file)
|
||||
(define-key map (kbd "d") 'my-delete-file)
|
||||
(define-key map (kbd "r") 'my-rename-file)
|
||||
map))
|
||||
|
||||
(defun my-visit-file ()
|
||||
"Visit file in current section."
|
||||
(interactive)
|
||||
(when-let ((section (magit-current-section)))
|
||||
(when (eq (oref section type) 'file-section)
|
||||
(find-file (oref section value)))))
|
||||
|
||||
(defun my-delete-file ()
|
||||
"Delete file in current section."
|
||||
(interactive)
|
||||
(when-let ((section (magit-current-section)))
|
||||
(when (and (eq (oref section type) 'file-section)
|
||||
(yes-or-no-p (format "Delete %s? "
|
||||
(oref section value))))
|
||||
(delete-file (oref section value))
|
||||
(my-section-refresh))))
|
||||
```
|
||||
|
||||
### Deferred Content Loading
|
||||
|
||||
```elisp
|
||||
(magit-insert-section (expensive-data)
|
||||
(magit-insert-heading "Large Dataset")
|
||||
(magit-insert-section-body
|
||||
;; Only computed when section first expanded
|
||||
(insert (format-large-dataset (compute-expensive-data)))))
|
||||
```
|
||||
|
||||
### Visibility Hooks
|
||||
|
||||
```elisp
|
||||
(add-hook 'magit-section-set-visibility-hook
|
||||
(lambda (section)
|
||||
;; Keep commit details hidden by default
|
||||
(when (eq (oref section type) 'commit-details)
|
||||
'hide)))
|
||||
```
|
||||
|
||||
### Context Menu Integration
|
||||
|
||||
```elisp
|
||||
(defun my-section-context-menu (menu section)
|
||||
"Add items to context menu based on section."
|
||||
(when (eq (oref section type) 'file)
|
||||
(define-key menu [my-open]
|
||||
'("Open File" . my-visit-file))
|
||||
(define-key menu [my-delete]
|
||||
'("Delete File" . my-delete-file)))
|
||||
menu)
|
||||
|
||||
(add-hook 'magit-menu-alternative-section-hook
|
||||
#'my-section-context-menu)
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### File Browser
|
||||
|
||||
```elisp
|
||||
(defun my-file-browser (dir)
|
||||
"Browse directory with collapsible sections."
|
||||
(interactive "DDirectory: ")
|
||||
(let ((buf (get-buffer-create (format "*Files: %s*" dir))))
|
||||
(with-current-buffer buf
|
||||
(magit-section-mode)
|
||||
(let ((inhibit-read-only t)
|
||||
(default-directory dir))
|
||||
(erase-buffer)
|
||||
(magit-insert-section (root)
|
||||
(magit-insert-heading (format "Directory: %s" dir))
|
||||
(my-insert-directory-tree "."))))
|
||||
(pop-to-buffer buf)))
|
||||
|
||||
(defun my-insert-directory-tree (path)
|
||||
"Recursively insert directory structure."
|
||||
(dolist (file (directory-files path))
|
||||
(unless (member file '("." ".."))
|
||||
(let ((full-path (expand-file-name file path)))
|
||||
(if (file-directory-p full-path)
|
||||
(magit-insert-section (directory full-path)
|
||||
(magit-insert-heading (concat file "/"))
|
||||
(my-insert-directory-tree full-path))
|
||||
(magit-insert-section (file full-path)
|
||||
(insert file "\n")))))))
|
||||
```
|
||||
|
||||
### Log Viewer
|
||||
|
||||
```elisp
|
||||
(defun my-log-viewer (log-file)
|
||||
"View log file with collapsible sections per entry."
|
||||
(interactive "fLog file: ")
|
||||
(let ((buf (get-buffer-create "*Log Viewer*")))
|
||||
(with-current-buffer buf
|
||||
(magit-section-mode)
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(magit-insert-section (root)
|
||||
(magit-insert-heading (format "Log: %s" log-file))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents log-file)
|
||||
(goto-char (point-min))
|
||||
(my-parse-log-entries)))))
|
||||
(pop-to-buffer buf)))
|
||||
```
|
||||
|
||||
### Process Monitor
|
||||
|
||||
```elisp
|
||||
(defun my-process-monitor ()
|
||||
"Display running processes in sections."
|
||||
(interactive)
|
||||
(let ((buf (get-buffer-create "*Processes*")))
|
||||
(with-current-buffer buf
|
||||
(magit-section-mode)
|
||||
(setq my-refresh-function #'my-insert-processes)
|
||||
(local-set-key (kbd "g") #'my-section-refresh)
|
||||
(local-set-key (kbd "k") #'my-kill-process)
|
||||
(my-section-refresh))
|
||||
(pop-to-buffer buf)))
|
||||
|
||||
(defun my-insert-processes ()
|
||||
"Insert process list as sections."
|
||||
(magit-insert-section (root)
|
||||
(magit-insert-heading "Running Processes")
|
||||
(dolist (proc (process-list))
|
||||
(magit-insert-section (process proc)
|
||||
(magit-insert-heading
|
||||
(format "%s [%s]"
|
||||
(process-name proc)
|
||||
(process-status proc)))
|
||||
(insert (format " Command: %s\n"
|
||||
(mapconcat #'identity
|
||||
(process-command proc)
|
||||
" ")))))))
|
||||
```
|
||||
|
||||
### Configuration Inspector
|
||||
|
||||
```elisp
|
||||
(defun my-config-inspector ()
|
||||
"Inspect Emacs configuration in sections."
|
||||
(interactive)
|
||||
(let ((buf (get-buffer-create "*Config*")))
|
||||
(with-current-buffer buf
|
||||
(magit-section-mode)
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(magit-insert-section (root)
|
||||
(magit-insert-heading "Emacs Configuration")
|
||||
|
||||
(magit-insert-section (variables)
|
||||
(magit-insert-heading "Custom Variables")
|
||||
(dolist (var (sort (my-get-custom-vars) #'string<))
|
||||
(magit-insert-section (variable var)
|
||||
(magit-insert-heading (symbol-name var))
|
||||
(insert (format " %S\n" (symbol-value var))))))
|
||||
|
||||
(magit-insert-section (features)
|
||||
(magit-insert-heading "Loaded Features")
|
||||
(dolist (feature features)
|
||||
(magit-insert-section (feature feature)
|
||||
(insert (format "%s\n" feature))))))))
|
||||
(pop-to-buffer buf)))
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Validation
|
||||
|
||||
```elisp
|
||||
;; Ensure root section exists
|
||||
(defun my-ensure-root-section ()
|
||||
(unless magit-root-section
|
||||
(error "Buffer does not have a root section. Enable magit-section-mode.")))
|
||||
|
||||
;; Validate section type
|
||||
(defun my-require-file-section ()
|
||||
(let ((section (magit-current-section)))
|
||||
(unless (eq (oref section type) 'file)
|
||||
(user-error "Not on a file section"))))
|
||||
```
|
||||
|
||||
### Graceful Degradation
|
||||
|
||||
```elisp
|
||||
(defun my-safe-section-value ()
|
||||
"Get section value safely."
|
||||
(when-let ((section (magit-current-section)))
|
||||
(ignore-errors
|
||||
(oref section value))))
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Lazy Loading
|
||||
|
||||
Use `magit-insert-section-body` for expensive operations:
|
||||
|
||||
```elisp
|
||||
(magit-insert-section (large-data)
|
||||
(magit-insert-heading "Large Dataset")
|
||||
(magit-insert-section-body
|
||||
;; Only executed when expanded
|
||||
(my-compute-and-insert-large-data)))
|
||||
```
|
||||
|
||||
### Visibility Caching
|
||||
|
||||
Cache section visibility to preserve state across refreshes:
|
||||
|
||||
```elisp
|
||||
(setq magit-section-cache-visibility t)
|
||||
|
||||
;; Preserve visibility during refresh
|
||||
(let ((magit-section-preserve-visibility t))
|
||||
(my-refresh-buffer))
|
||||
```
|
||||
|
||||
### Efficient Updates
|
||||
|
||||
Minimize full buffer refreshes:
|
||||
|
||||
```elisp
|
||||
;; Update single section instead of full refresh
|
||||
(defun my-update-section (section)
|
||||
(save-excursion
|
||||
(goto-char (oref section start))
|
||||
(let ((inhibit-read-only t)
|
||||
(hidden (oref section hidden)))
|
||||
(delete-region (oref section start) (oref section end))
|
||||
(my-insert-section-content section)
|
||||
(when hidden
|
||||
(magit-section-hide section)))))
|
||||
```
|
||||
|
||||
## Integration with Emacs
|
||||
|
||||
### magit-section-mode
|
||||
|
||||
Enable in buffers using sections:
|
||||
|
||||
```elisp
|
||||
(define-derived-mode my-viewer-mode magit-section-mode "MyViewer"
|
||||
"Major mode for viewing data with sections."
|
||||
(setq-local my-refresh-function #'my-insert-data))
|
||||
```
|
||||
|
||||
### Faces
|
||||
|
||||
Customize appearance:
|
||||
|
||||
```elisp
|
||||
(custom-set-faces
|
||||
'(magit-section-heading ((t (:weight bold :foreground "blue"))))
|
||||
'(magit-section-highlight ((t (:background "gray95")))))
|
||||
```
|
||||
|
||||
### Mouse Support
|
||||
|
||||
Mouse clicks on section margins toggle visibility automatically.
|
||||
|
||||
## Debugging
|
||||
|
||||
### Inspection Commands
|
||||
|
||||
```elisp
|
||||
;; Describe current section
|
||||
(call-interactively #'magit-describe-section)
|
||||
|
||||
;; Brief description
|
||||
(magit-describe-section-briefly)
|
||||
```
|
||||
|
||||
### Debug Helpers
|
||||
|
||||
```elisp
|
||||
(defun my-debug-section ()
|
||||
"Debug current section structure."
|
||||
(interactive)
|
||||
(let ((section (magit-current-section)))
|
||||
(message "Type: %S, Value: %S, Hidden: %S, Children: %d"
|
||||
(oref section type)
|
||||
(oref section value)
|
||||
(oref section hidden)
|
||||
(length (oref section children)))))
|
||||
```
|
||||
|
||||
## Migration from Other UIs
|
||||
|
||||
### From outline-mode
|
||||
|
||||
magit-section offers superior interactivity and data association:
|
||||
|
||||
```elisp
|
||||
;; outline-mode
|
||||
(outline-hide-subtree)
|
||||
|
||||
;; magit-section equivalent
|
||||
(magit-section-hide (magit-current-section))
|
||||
```
|
||||
|
||||
### From org-mode
|
||||
|
||||
Use magit-section for custom data not suited to org's document structure:
|
||||
|
||||
```elisp
|
||||
;; org-mode cycling
|
||||
(org-cycle)
|
||||
|
||||
;; magit-section cycling
|
||||
(magit-section-cycle)
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **Repository:** https://github.com/magit/magit
|
||||
- **Tutorial:** https://github.com/magit/magit/wiki/Magit-Section-Tutorial
|
||||
- **Magit Documentation:** https://magit.vc/manual/magit.html
|
||||
- **ELPA:** Available on NonGNU ELPA
|
||||
|
||||
## See Also
|
||||
|
||||
- **Magit:** Git interface built on magit-section
|
||||
- **taxy-magit-section:** Integrate taxy hierarchies with magit-section
|
||||
- **outline-mode:** Built-in outline collapsing
|
||||
- **org-mode:** Document structure with folding
|
||||
148
skills/magit-section/metadata.edn
Normal file
148
skills/magit-section/metadata.edn
Normal file
@@ -0,0 +1,148 @@
|
||||
{:skill
|
||||
{:name "magit-section"
|
||||
:version "1.0.0"
|
||||
:description "Build collapsible, hierarchical buffer UIs with sections - the foundation of Magit's interface"
|
||||
:tags [:ui :navigation :buffers :interactive :hierarchy :magit :emacs-lisp]
|
||||
|
||||
:library
|
||||
{:name "magit-section"
|
||||
:version "4.2.0+"
|
||||
:url "https://github.com/magit/magit"
|
||||
:license "GPL-3.0-or-later"
|
||||
:built-in false
|
||||
:package-manager :nongnu-elpa}
|
||||
|
||||
:features
|
||||
["Hierarchical collapsible sections"
|
||||
"Section-specific keymaps and actions"
|
||||
"Built-in navigation commands"
|
||||
"Visibility state caching across refreshes"
|
||||
"Mouse and keyboard interaction"
|
||||
"Integrated region selection"
|
||||
"Deferred content loading for performance"
|
||||
"Section matching and querying"
|
||||
"Customizable visibility indicators"
|
||||
"Context menu integration"
|
||||
"Highlighting and visual feedback"
|
||||
"EIEIO-based section objects"]
|
||||
|
||||
:use-cases
|
||||
["Building custom file browsers"
|
||||
"Creating log viewers with collapsible entries"
|
||||
"Process monitoring interfaces"
|
||||
"Configuration inspectors"
|
||||
"Package management UIs"
|
||||
"Documentation browsers"
|
||||
"Data explorers and viewers"
|
||||
"Interactive command output"
|
||||
"Project management interfaces"
|
||||
"Custom Git interfaces (like Magit)"
|
||||
"Database query result viewers"
|
||||
"API response inspectors"]
|
||||
|
||||
:file-structure
|
||||
{:main-docs "SKILL.md"
|
||||
:quick-start "README.md"
|
||||
:metadata "metadata.edn"}
|
||||
|
||||
:learning-path
|
||||
{:beginner
|
||||
["Understanding section objects and hierarchy"
|
||||
"Creating basic sections with magit-insert-section"
|
||||
"Using magit-insert-heading"
|
||||
"Basic navigation with n/p/^"
|
||||
"Toggling visibility with TAB"
|
||||
"Querying sections with magit-current-section"]
|
||||
|
||||
:intermediate
|
||||
["Section type matching with magit-section-match"
|
||||
"Creating refreshable buffers"
|
||||
"Using section-specific keymaps"
|
||||
"Working with section values and data"
|
||||
"Visibility control and cycling"
|
||||
"Region selection of sections"
|
||||
"Configuring visibility caching"]
|
||||
|
||||
:advanced
|
||||
["Washing external command output"
|
||||
"Deferred content loading with magit-insert-section-body"
|
||||
"Custom section classes with EIEIO"
|
||||
"Visibility hooks and customization"
|
||||
"Context menu integration"
|
||||
"Performance optimization techniques"
|
||||
"Complex hierarchical data structures"
|
||||
"Dynamic section updates"]}
|
||||
|
||||
:platform
|
||||
{:os [:unix :windows :macos]
|
||||
:emacs-version "28.1+"}
|
||||
|
||||
:api-coverage
|
||||
{:section-creation
|
||||
["magit-insert-section"
|
||||
"magit-insert-heading"
|
||||
"magit-insert-section-body"
|
||||
"magit-cancel-section"
|
||||
"magit-insert-child-count"]
|
||||
|
||||
:navigation
|
||||
["magit-section-forward"
|
||||
"magit-section-backward"
|
||||
"magit-section-up"
|
||||
"magit-section-forward-sibling"
|
||||
"magit-section-backward-sibling"
|
||||
"magit-section-goto"]
|
||||
|
||||
:visibility
|
||||
["magit-section-toggle"
|
||||
"magit-section-show"
|
||||
"magit-section-hide"
|
||||
"magit-section-cycle"
|
||||
"magit-section-cycle-global"
|
||||
"magit-section-show-children"
|
||||
"magit-section-hide-children"
|
||||
"magit-section-show-headings"
|
||||
"magit-section-show-level-1 through 4"]
|
||||
|
||||
:querying
|
||||
["magit-current-section"
|
||||
"magit-section-at"
|
||||
"magit-section-ident"
|
||||
"magit-section-match"
|
||||
"magit-section-value-if"
|
||||
"magit-section-lineage"
|
||||
"magit-section-hidden"
|
||||
"magit-section-content-p"
|
||||
"magit-region-sections"]
|
||||
|
||||
:inspection
|
||||
["magit-describe-section"
|
||||
"magit-describe-section-briefly"]
|
||||
|
||||
:section-object-slots
|
||||
["type"
|
||||
"value"
|
||||
"start"
|
||||
"content"
|
||||
"end"
|
||||
"hidden"
|
||||
"children"
|
||||
"parent"
|
||||
"keymap"
|
||||
"washer"]
|
||||
|
||||
:configuration
|
||||
["magit-section-highlight-current"
|
||||
"magit-section-highlight-selection"
|
||||
"magit-section-show-child-count"
|
||||
"magit-section-cache-visibility"
|
||||
"magit-section-initial-visibility-alist"
|
||||
"magit-section-visibility-indicators"
|
||||
"magit-section-disable-line-numbers"]
|
||||
|
||||
:hooks
|
||||
["magit-section-movement-hook"
|
||||
"magit-section-set-visibility-hook"
|
||||
"magit-insert-section-hook"
|
||||
"magit-menu-alternative-section-hook"
|
||||
"magit-region-highlight-hook"]}}}
|
||||
Reference in New Issue
Block a user