Occult - from Latin occultus - “hidden, secret”
It lets you collapse any buffer region into a single-line summary using overlays. The hidden text remains fully present in the buffer - accessible to buffer-string, org-export, copy/kill, and LLM context extraction.
The first line of the folded region stays visible and navigable as regular text. Everything after it is hidden behind an ellipsis.
Sometimes you want to visually declutter a buffer without losing content. LLM chat sessions get verbose, org documents accumulate boilerplate, eshell output floods the screen. Existing folding mechanisms (outline-mode, org-cycle, hideshow) are structure-aware - they fold headings, blocks, or defuns. They can’t fold an arbitrary selected region.
Occult works on any region in any buffer - including read-only and special-mode buffers. The hidden text is never removed from the buffer, so tools that read buffer content (LLM packages like gptel, eca, agent-shell; org-export; copy/kill) see the full unfolded text.
Available on MELPA:
(use-package occult
:ensure t)Four commands:
occult-toggle- select a region and call it to collapse. Call it again with point on a fold to expand. TAB also toggles when point is on a fold.occult-reveal-all- expand all folds in the current buffer.occult-fold-noise- fold every stretch the buffer calls noise in one go; see Folding noise.occult-edit-region- open the fold at point in a narrowed indirect buffer for editing. Bound toeon the fold keymap.
Bind them however you like:
(global-set-key (kbd "C-c z") #'occult-toggle)
(global-set-key (kbd "C-c Z") #'occult-reveal-all)Press e with point on a fold (or call M-x occult-edit-region) to open the fold content in a separate indirect buffer. The base buffer’s fold stays collapsed while you edit; changes propagate live because the text is shared. A header line shows the current commit and abort key bindings. When the base buffer is read-only the session opens in view mode with a single close key instead.
Edit Occult Fold │ C-c C-c commit │ C-c C-k abort
C-c C-c(customizable viaoccult-edit-commit-key) - commit and close the indirect buffer, keeping user changes.C-c C-k(customizable viaoccult-edit-abort-key) - restore the original fold contents and close the indirect buffer.
| Variable | Default | Description |
|---|---|---|
occult-indicator | "📎 " | Prefix glyph for the fold summary¹ |
occult-ellipsis | "..." | Suffix shown where text is hidden |
occult-summary-max-length | 80 | Max visible chars from the first line |
occult-summary-replace-alist | nil | Patterns the summary line displays differently |
occult-summary-line-prefix | nil | Prefix drawn at the start of the summary line |
occult-auto-reveal | nil | Auto-reveal mode: nil, echo, or expand |
occult-edit-commit-key | "C-c C-c" | Key that commits an edit session |
occult-edit-abort-key | "C-c C-k" | Key that aborts / closes an edit session |
¹ The appearance and feel of folds can drastically change based on this seemingly unimportant setting; I highly recommend picking a symbol that is visually appealing to you, and there are plenty of options to choose from - C-x 8 RET to find one. My personal preference is what’s called double-ended multimap - ⧟
The first line of a folded region often carries something that is useful in place and noise in a summary: a status glyph, an elapsed time, a shell prompt, a block marker. Three options change what that line shows. All of them are buffer-local when set, because what counts as noise differs from buffer to buffer, and all default to nil, so folds look the same until you set one.
occult-summary-replace-alist displays every match of a regexp as something else:
(add-hook 'eca-chat-mode-hook
(lambda ()
(setq occult-summary-replace-alist '((" ✅ [0-9]+s" . "")))))A fold whose first line reads Editing occult.el +8 -5 ✅ 0s then shows Editing occult.el +8 -5. Nothing is cut: the rest of the line stays, and the buffer keeps the whole thing, so search, kill/yank and occult-edit-region still see the status. A replacement can use \1 for a group of the match, or be a function called with the matched text.
occult-summary-line-prefix replaces the line-prefix the buffer draws at the start of that line. Modes draw indentation guides and collapsible-block markers there, and those are display properties rather than text, so no regexp can reach them. Setting the option to "" drops the prefix from the summary line, and only from it.
occult-indicator is buffer-local when set, so folds in a chat buffer can carry a different glyph from folds in a log.
Some buffers grow stretches of text you read once and then want out of the way: source blocks in a long org document, tool calls and thoughts in an LLM chat, DEBUG lines in a log, command output in a shell. Tell occult what the noise looks like, and M-x occult-fold-noise folds every stretch of it in one go, each into its own fold with its first line as the summary. Both options are buffer-local, so an org buffer and a log can each say what noise means there.
occult-noise-regexps names the noise by pattern. A match is one stretch, widened to whole lines; matches that only blank lines separate merge into one fold, and so do matches that overlap. A regexp may span lines:
(add-hook 'org-mode-hook
(lambda ()
(setq occult-noise-regexps
'("^[ \t]*#\\+begin_src\\(?:.*\n\\)*?[ \t]*#\\+end_src.*"))))occult-noise-regions-function names the noise by computation, for a mode where the noise is not a matter of text - a chat that marks its blocks with overlays, say. The function returns a list of (BEG . END) conses in buffer order and replaces the regexps.
occult-fold-noise folds every stretch in the buffer, or, with an active region or with BEG and END from Lisp, only the stretches reaching into those bounds - a chat can fold the turn that just finished and leave what you opened earlier alone. Running it again is cheap and changes nothing: a stretch a fold already hides is skipped. occult-reveal-all opens everything, and a single fold opens with TAB as usual.
Emacs isearch (C-s / C-r) finds text inside folds. Matches are temporarily revealed during search and permanently expanded if isearch exits inside a fold.
Evil users: / and ? (evil-ex-search-forward / evil-ex-search-backward) are also supported. When a search lands inside a fold, it is temporarily revealed and re-hidden when point moves away.
Evil binds its fold commands in evil-normal-state-map only. In visual state z is a prefix holding scroll commands, so zc there is undefined, evil drops the sequence, and the next c runs evil-change and deletes the selection. Folding a selection therefore needs its own visual-state binding rather than advice on evil-close-fold:
(with-eval-after-load 'evil
(evil-define-key 'visual 'global (kbd "zc") #'occult-toggle)
(evil-define-key 'visual 'global (kbd "za") #'occult-toggle))The normal-state commands can be advised to delegate to occult when point is on a fold, and fall through to evil otherwise.
zo/zOon an occult fold - reveal itza- reveal the fold at pointzR- also reveals all occult folds alongside native folds
(with-eval-after-load 'evil
(dolist (fn '(evil-open-fold evil-open-fold-rec))
(advice-add fn :around
(lambda (fn &rest args)
(if (cl-find-if (lambda (o) (overlay-get o 'occult))
(overlays-at (point)))
(occult-toggle)
(apply fn args)))))
;; `evil-open-folds' signals a user-error in a buffer with no folding
;; minor mode, which must not stop occult's own folds from opening.
(advice-add 'evil-open-folds :around
(lambda (fn &rest args)
(if (cl-find-if (lambda (o) (overlay-get o 'occult))
(overlays-in (point-min) (point-max)))
(progn (ignore-error user-error (apply fn args))
(occult-reveal-all))
(apply fn args))))
(advice-add 'evil-toggle-fold :around
(lambda (fn &rest args)
(if (cl-find-if (lambda (o) (overlay-get o 'occult))
(overlays-at (point)))
(occult-toggle)
(apply fn args)))))Other Emacs folding packages. Most fold by language structure or indentation; occult complements them by folding arbitrary regions into a one-line summary, keeping the underlying text accessible to buffer-string, org-export, kill/yank, and isearch.
- hideshow - built-in
hs-minor-modefor syntax-aware folding of comments and code blocks - folding /
folding-mode- marker-based folding (triple-brace comments), by Jari Aalto - origami - parser-based structural folding with indentation fallback
- lsp-origami - origami driven by LSP fold ranges
- ts-fold - tree-sitter folding via external
emacs-tree-sitter - treesit-fold - tree-sitter folding via built-in
treesit(Emacs 29+) - yafolding - indentation-based folding
Copyright © 2026 Ag Ibragimov
GPL-3.0-or-later. See LICENSE. e Copyright © 2026 Ag Ibragimov
GPL-3.0-or-later. See LICENSE.
