Claude Code Plugins

Community-maintained marketplace

Feedback

A guide to using magit-section for building collapsible, hierarchical buffer UIs in Emacs.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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:

(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:

(magit-insert-section (file "README.md")
  (magit-insert-heading "README.md")
  (insert "File contents here\n"))

Advanced Usage:

(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:

(magit-insert-heading &rest ARGS)

Example:

(magit-insert-heading
  (format "Changes (%d)" (length file-list)))

magit-insert-section-body

Defer section body evaluation until first expansion:

(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:

(when (null items)
  (magit-cancel-section))

Removes partial section from buffer and section tree.

Navigation Commands

Movement

(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:

(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

(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

(magit-section-show-children SECTION &optional DEPTH)
(magit-section-hide-children SECTION)
(magit-section-show-headings SECTION)

Example:

;; Expand section and first level of children
(magit-section-show-children (magit-current-section) 1)

Level-Based Visibility

(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

(magit-section-cycle &optional SECTION)        ; Current section
(magit-section-cycle-global)                   ; All sections

Cycles through: collapsed → expanded → children-collapsed → collapsed

Querying Sections

Current Section

(magit-current-section)           ; Section at point
(magit-section-at &optional POS)  ; Section at position

Example:

(let ((section (magit-current-section)))
  (message "Type: %s, Value: %s"
           (oref section type)
           (oref section value)))

Section Properties

(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:

(when (magit-section-content-p section)
  (magit-section-show section))

Section Matching

(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:

;; 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

(magit-region-sections &optional CONDITION MULTIPLE)

Get sibling sections in active region.

Example:

(let ((selected-files (magit-region-sections 'file t)))
  (dolist (section selected-files)
    (message "Selected: %s" (oref section value))))

Section Inspection

(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:

(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:

(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

;; Highlight current section
(setq magit-section-highlight-current t)

;; Highlight region selection
(setq magit-section-highlight-selection t)

Child Count Display

;; Show number of children in headings
(setq magit-section-show-child-count t)

Visibility Indicators

;; Customize expansion/collapse indicators
(setq magit-section-visibility-indicators
      '((expanded . "▼")
        (collapsed . "▶")))

Visibility Caching

;; 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

;; Disable line numbers in section buffers (default)
(setq magit-section-disable-line-numbers t)

Common Patterns

Basic Section Buffer

(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

(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

(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

(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

(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

(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

(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

(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

(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

(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

(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

;; 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

(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:

(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:

(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:

;; 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:

(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:

(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

;; Describe current section
(call-interactively #'magit-describe-section)

;; Brief description
(magit-describe-section-briefly)

Debug Helpers

(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:

;; 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:

;; org-mode cycling
(org-cycle)

;; magit-section cycling
(magit-section-cycle)

Resources

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