Home

Awesome

outline-indent.el - Make Emacs Fold Text Using Indentation Levels

MELPA Build Status License

The outline-indent Emacs package provides a minor mode that enables code folding based on indentation levels.

The outline-indent package is a fast and reliable alternative to the origami.el and yafolding.el packages. (origami.el and yafolding.el are no longer maintained, slow, and known to have bugs that impact their reliability and performance.)

In addition to code folding, outline-indent allows:

The outline-indent package uses the built-in outline-minor-mode, which is maintained by the Emacs developers and is less likely to be abandoned like origami.el or yafolding.el. Since outline-indent is based on outline-minor-mode, it's also much much faster than origami.el and yafolding.el.

(The Emacs theme in the screenshot above is the tomorrow-night-deepblue-theme)

The outline-indent Emacs package offers a similar functionality to Vim's set foldmethod=indent setting. Just as in Vim, it allows to fold and unfold code sections based on their indentation levels.

Table of Contents

<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->

Table of Contents

<!-- markdown-toc end -->

Installation

To install outline-indent from MELPA:

  1. If you haven't already done so, add MELPA repository to your Emacs configuration.

  2. Add the following code to your Emacs init file to install outline-indent from MELPA:

(use-package outline-indent
  :ensure t
  :custom
  (outline-indent-ellipsis " ▼ "))

Activation

Manual activation

Once installed, the minor mode can be activated using:

(outline-indent-minor-mode)

Automatic activation using hooks

The minor mode can also be automatically activated for a certain mode. For example for Python and YAML:

;; Python
(add-hook 'python-mode-hook #'outline-indent-minor-mode)
(add-hook 'python-ts-mode-hook #'outline-indent-minor-mode)

;; YAML
(add-hook 'yaml-mode-hook #'outline-indent-minor-mode)
(add-hook 'yaml-ts-mode-hook #'outline-indent-minor-mode)

Adjusting the shift width and default offset

You can adjust the outline-indent-shift-width and outline-indent-default-offset according to your preferences. While the default value of 1 is adequate for most modes, setting the appropriate value ensures that the promote and demote functions correctly adjust the indentation of blocks. For example:

;; Python
(dolist (hook '(python-mode python-ts-mode-hook))
  (add-hook hook #'(lambda()
                     (setq-local outline-indent-default-offset 4)
                     (setq-local outline-indent-shift-width 4))))

;; YAML
(dolist (hook '(yaml-mode yaml-ts-mode-hook))
  (add-hook hook #'(lambda()
                     (setq-local outline-indent-default-offset 2)
                     (setq-local outline-indent-shift-width 2)))

Explanation:

  1. Outline Indentation Parameters:

    • outline-indent-default-offset: This variable determines the base indentation level for each outline level. It specifies the amount by which each successive outline level should be indented, effectively controlling the visual structure of the outline.
    • outline-indent-shift-width: This variable determines the number of spaces by which to adjust the indentation when promoting or demoting an indented block with (outline-indent-shift-right) and (outline-indent-shift-left).
  2. Why Customize These Values?:

    • Language-specific Indentation: Different programming languages and file formats have different indentation standards. Python typically uses 4 spaces per indentation level, while YAML often uses 2 spaces. Customizing these values for different modes ensures that your outline structure is consistent with the language's indentation practices.
    • Shift right and shift left: When you use outline-indent-shift-left and outline-indent-shift-right functions, these settings control how much the outline level is adjusted. For instance, in Python mode, promoting a block of code (moving it to a higher outline level) will decrease its indentation by 4 spaces, and demoting it will increase its indentation by 4 spaces.
  3. Default Behavior:

    • By default, outline-indent-default-offset is set to 1, which works with any indentation level, as even a single space is enough to fold any indented block using outline-indent.
    • By default, outline-indent-default-shift-width is nil, which means it inherits the value of outline-indent-default-offset. If you do not explicitly set outline-indent-shift-width, the promote and demote operations will use the same value as the offset. This default behavior works well in many cases, but fine-tuning these values can be necessary for languages or formats with specific indentation needs.

Usage

How to check if it is working?

Run the following function to fold all indented blocks:

(outline-indent-close-folds)

Functions specific to outline-indent-minor-mode

Managing folds

These functions help you manage the visibility of code blocks or headings in outline-indent-minor-mode. Use them to control which sections of your document are visible or hidden:

Fold at point:

All folds:

Other:

Toggle:

Selecting indented text

The current indented text can be selected using:

(outline-indent-select)

outline-indent-backward-same-level and outline-indent-forward-same-level

(By default, outline-indent-advise-outline-functions is set to t, which means that you can also use the built-in outline functions (outline-backward-same-level) and (outline-forward-same-level) as an alternative to (outline-indent-backward-same-level) and (outline-indent-forward-same-level))

To move to the next block with the same indentation level:

(outline-indent-forward-same-level)

To move to the previous block with the same indentation level:

(outline-indent-backward-same-level)

outline-indent-shift-left and outline-indent-shift-right

(By default, outline-indent-advise-outline-functions is set to t, which means that you can also use the built-in outline functions (outline-promote) and (outline-demote) as an alternative to (outline-indent-shift-left) and (outline-indent-shift-right))

These functions can be used to decrease and increase the indentation level of indented blocks.

To increase indentation:

(outline-indent-shift-right)

To decrease indentation:

(outline-indent-shift-left)

The global variable outline-indent-shift-width is used to determine the number of spaces to indent or unindent the subtree.

outline-indent-move-subtree-up and outline-indent-move-subtree-down

(By default, outline-indent-advise-outline-functions is set to t, which means that you can also use the built-in outline functions (outline-move-subtree-up) and (outline-move-subtree-down), as an alternative to (outline-indent-move-subtree-up) and (outline-indent-move-subtree-down))

These functions can be used to move the current subtree down past ARGS headlines of the same level.

To move the subtree down, use:

(outline-indent-move-subtree-down)

To move the subtree up, use:

(outline-indent-move-subtree-up)

outline-indent-insert-heading

(By default, outline-indent-advise-outline-functions is set to t, which means that you can also use the built-in outline function outline-insert-heading as an alternative to outline-indent-insert-heading)

The (outline-indent-insert-heading) function inserts a new line with the same indentation level/depth as the current line just before the next heading that shares the same or less indentation level. It finds the nearest non-empty line with the same or less indentation as the current line and inserts a new line before it.

In outline-indent-minor-mode, where most lines are treated as headings, this function is suitable for maintaining consistent indentation within the outline structure. It can be used as an alternative to outline-insert-heading to insert content at the same indentation level after the current fold.

Example usage:

(outline-indent-insert-heading)

Vanilla Emacs

Use the standard outline-mode/outline-minor-mode commands to fold and unfold sections of your indented file:

You can also indent/unindent and move subtree up and down using:

Move to the next and previous visible fold:

Move forward or backward to the same indentation level:

Evil mode

In Evil mode, outline-indent works out of the box if you install evil-collection, and you can use the Evil and evil-collection keyboard mappings:

You may want to set a few additional key mappings:

(with-eval-after-load "evil"
  (defun my-evil-define-key-outline-indent-minor-mode ()
    ;; Set `M-h` and `M-l` to decrease and increase the indentation level of
    ;; indented blocks
    (evil-define-key 'normal 'local (kbd "M-h") #'outline-indent-shift-left)
    (evil-define-key 'normal 'local (kbd "M-l") #'outline-indent-shift-right)

    ;; Set `M-k` and `M-j` to move indented blocks up and down
    (evil-define-key 'normal 'local (kbd "M-k") #'outline-indent-move-subtree-up)
    (evil-define-key 'normal 'local (kbd "M-j") #'outline-indent-move-subtree-down)

    (evil-define-key 'normal 'local (kbd "]]") #'outline-indent-forward-same-level)
    (evil-define-key 'normal 'local (kbd "[[") #'outline-indent-backward-same-level)
    (evil-define-key 'normal 'local (kbd "gj") #'outline-indent-forward-same-level)
    (evil-define-key 'normal 'local (kbd "gk") #'outline-indent-backward-same-level)

    ;; Set C-<return> to insert a new line with the same indentation
    ;; level/depth as the current line just before the next heading
    (evil-define-key '(normal insert) 'local (kbd "C-<return>")
      (defun my-evil-outline-indent-insert-heading ()
        (interactive)
        (outline-indent-insert-heading)
        (evil-insert-state))))

  (add-hook 'outline-indent-minor-mode-hook
            #'my-evil-define-key-outline-indent-minor-mode))

Frequently asked questions

Maintaining blank lines between folded sections

The outline-blank-line variable can be set to t (true) to maintain blank lines between folded sections, making it easier to distinguish between folds:

(setq outline-blank-line t)

How to Prevent Emacs from Searching Folded Sections

To prevent Emacs from searching within folded sections, set search-invisible to nil by adding the following line to your Emacs init file:

(setq-default search-invisible nil)

This setting ensures that Emacs skips invisible or folded text during searches, so hidden sections are not included in the search results.

Why not use origami.el or yafolding?

The origami.el and yafolding.el package are not reliable method for folding indented code because they are:

On the other hand, outline-indent leverages the built-in outline-minor-mode, which is:

Why not use folding.el?

The folding.el package is no longer maintained (abandoned) and uses markers in the buffer to annotate folds. It does not support using indentation levels to determine foldable sections.

In contrast, outline-indent uses indentation levels to determine foldable sections.

How to make Emacs indent new lines based on previous non-blank line?

The following code snippet configures Emacs to indent based on the indentation of the previous non-blank line:

;; This ensures that pressing Enter will insert a new line and indent it.
(global-set-key (kbd "RET") #'newline-and-indent)

;; Indentation based on the indentation of the previous non-blank line.
(setq-default indent-line-function #'indent-relative-first-indent-point)

;; In modes such as `text-mode', pressing Enter multiple times removes
;; the indentation. The following fixes the issue and ensures that text
;; is properly indented using `indent-relative' or
;; `indent-relative-first-indent-point'.
(setq-default indent-line-ignored-functions '())

What other packages can be used to maintain proper indentation in indentation-sensitive programming languages?

Displaying vertical indentation guide bars

Choose one of these packages that are available on MELPA:

(There is also indent-bars, but it is not yet available on MELPA.)

Detecting indentation

The dtrt-indent package automatically detects the indentation offset used in source code files and adjusts Emacs settings to match, simplifying the editing of files with varying indentation styles. To install it, add the following to your Emacs init file:

(use-package dtrt-indent
  :ensure t
  :commands (dtrt-indent-global-mode
             dtrt-indent-mode
             dtrt-indent-adapt
             dtrt-indent-undo
             dtrt-indent-diagnosis
             dtrt-indent-highlight)
  :config
  (dtrt-indent-global-mode))

How to view different outline-indent folds in separate windows?

You can use indirect buffers, a feature that allow multiple views of the same underlying data in separate windows.

Indirect buffers are useful when working with outline-indent folds where you might want to focus on different sections of a document simultaneously, without altering the view in other windows.

For example, one window might display a fully expanded view (original buffer), while another window (the indirect buffer) shows only specific folds or indentation levels, allowing you to compare or edit sections side by side.

To create an indirect buffer of the current buffer, you can use the following function:

(clone-indirect-buffer nil t)

License

The outline-indent Emacs package has been written by James Cherti and is distributed under terms of the GNU General Public License version 3, or, at your choice, any later version.

Copyright (C) 2024 James Cherti

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program.

Links

Other Emacs packages by the same author: