File: hideshow.el.html

* Commands provided

This file provides the Hideshow minor mode, it includes the following commands (and their keybindings) to hiding and showing code and comment blocks:

  hs-hide-block C-c @ C-h/C-d
  hs-show-block C-c @ C-s
  hs-hide-all C-c @ C-M-h/C-t
  hs-show-all C-c @ C-M-s/C-a
  hs-hide-level C-c @ C-l
  hs-toggle-hiding C-c @ C-c/C-e or S-<mouse-2>
  hs-hide-initial-comment-block
  hs-cycle C-c @ TAB
  hs-toggle-all C-c @ <backtab>

All these commands are defined in hs-prefix-map(var)/hs-prefix-map(fun), hs-minor-mode-map and hs-indicators-map.

Blocks are defined per mode. For example, in c-mode and similar, they are simply text between curly braces, while in Lisp-ish modes parens are used. Multi-line comment blocks can also be hidden. Read-only buffers are not a problem, since hideshow doesn't modify the text.

The command M-x hs-minor-mode toggles the minor mode or sets it buffer-local.

* Suggested usage

Add the following to your init file:

    (add-hook 'X-mode-hook #'hs-minor-mode) ; other modes similarly

where X = {emacs-lisp,c,c++,perl,...}. You can also manually toggle hideshow minor mode by typing M-x hs-minor-mode. After hideshow is activated or deactivated, hs-minor-mode-hook is run with run-hooks.

Additionally, Joseph Eydelnant writes:
  I enjoy your package hideshow.el Version 5.24 2001/02/13
  a lot and I've been looking for the following functionality:
  toggle hide/show all with a single key.
  Here are a few lines of code that lets me do just that.

    (defvar my-hs-hide nil "Current state of hideshow for toggling all.")
    ;;;###autoload
    (defun my-toggle-hideshow-all () "Toggle hideshow all."
      (interactive)
      (setq my-hs-hide (not my-hs-hide))
      (if my-hs-hide
          (hs-hide-all)
        (hs-show-all)))

* Customization

Hideshow provides the following user options:

- hs-hide-comments-when-hiding-all
  If non-nil, hs-hide-all, hs-cycle and hs-hide-level will hide
  comments too.
- hs-hide-all-non-comment-function
  If non-nil, after calling hs-hide-all, this function is called
  with no arguments.
- hs-isearch-open
  What kind of hidden blocks to open when doing isearch.
- hs-set-up-overlay
  Function called with one arg (an overlay), intended to customize
  the block hiding appearance.
- hs-display-lines-hidden
  Displays the number of hidden lines next to the ellipsis.
- hs-show-indicators
  Display indicators to show and toggle the block hiding.
- hs-indicator-type
  Which indicator type should be used for the block indicators.
- hs-indicator-maximum-buffer-size
  Max buffer size in bytes where the indicators should be enabled.
- hs-allow-nesting
  If non-nil, hiding remembers internal blocks.
- hs-cycle-filter
  Control where typing a TAB cycles the visibility.

The variable hs-hide-all-non-comment-function may be useful if you only want to hide some N levels blocks for some languages/files or implement your idea of what is more useful. For example, the following code shows the next nested level in addition to the top-level for java:

    (defun ttn-hs-hide-level-2 ()
      (when (funcall hs-looking-at-block-start-predicate)
        (hs-hide-level 2)))
    (add-hook 'java-mode-hook
              (lambda ()
                (setq-local hs-hide-all-non-comment-function
                            #'ttn-hs-hide-level-2)))

Hideshow works with incremental search (isearch) by setting the variable hs-headline, which is the line of text at the beginning of a hidden block that contains a match for the search. You can have this show up in the mode line by modifying the variable mode-line-format. For example, the following code prepends this info to the mode line:

    (unless (memq 'hs-headline mode-line-format)
      (setq mode-line-format
            (append '("-" hs-headline) mode-line-format)))


The following hooks are run after some commands:

  hs-hide-hook => hs-hide-block hs-hide-all hs-hide-level hs-cycle
  hs-show-hook => hs-show-block hs-show-all hs-cycle

The variable hs-set-up-overlay allow customize the appearance of the hidden block and other effects associated with overlays. For example:

    (setopt hs-set-up-overlay
            (defun my-display-code-line-counts (ov)
              (when (eq 'code (overlay-get ov 'hs))
                (overlay-put ov 'display
                             (propertize
                              (format " [... <%d>] "
                                      (count-lines (overlay-start ov)
                                                   (overlay-end ov)))
                              'face 'font-lock-type-face)))))

* Extending hideshow

** Adding support for a major mode

Normally, hideshow tries to determine appropriate values for block and comment definitions by examining the major mode settings. If the major mode is not derived from prog-mode, hideshow will not activate. If you want to override this, you can set any of the following variables: hs-block-start-regexp, hs-block-start-mdata-select, hs-block-end-regexp, hs-c-start-regexp, hs-forward-sexp-function, hs-adjust-block-beginning-function, hs-adjust-block-end-function, hs-find-block-beginning-function, hs-find-next-block-function, hs-looking-at-block-start-predicate, hs-inside-comment-predicate, hs-treesit-things.

These variables help hideshow know what is considered a block, which function to use to get the block positions, etc.

A block is defined as text surrounded by hs-block-start-regexp and hs-block-end-regexp.

For some major modes, forward-sexp does not work properly. In those cases, hs-forward-sexp-function specifies another function to use instead.

*** Tree-sitter support

All the treesit based modes already have support for hiding/showing using the treesit thing list (see treesit-major-mode-setup).

However, for some modes the list thing is not enough for detecting the proper code block and the range to hide, you can set the variable hs-treesit-things to override this, but ensure you have the proper values in hs-adjust-block-end-function and hs-adjust-block-beginning-function to properly hide the code block.

** Migrating from hs-special-modes-alist

Starting with Emacs 31, hs-special-modes-alist has been deprecated. Instead, modes should use the buffer-local variables that replace each of the options in hs-special-modes-alist. The following table shows the old elements of hs-special-modes-alist and their replacement buffer-local variables:

  Instead of this Use this
  -----------------------------------------------------------------------
  START hs-block-start-regexp
  (START . MDATA) hs-block-start-regexp and hs-block-start-mdata-select
  END hs-block-end-regexp
  COMMENT-START hs-c-start-regexp
  FORWARD-SEXP-FUNC hs-forward-sexp-function
  ADJUST-BEG-FUNC hs-adjust-block-beginning-function
  FIND-BLOCK-BEGINNING-FUNC hs-find-block-beginning-function
  FIND-NEXT-BLOCK-FUNC hs-find-next-block-function
  LOOKING-AT-BLOCK-START-P-FUNC hs-looking-at-block-start-predicate)

* Bugs

1) Sometimes hs-headline can become out of sync. To reset, type
   M-x hs-minor-mode twice (that is, deactivate then re-activate
   hideshow).

2) Some buffers can't be byte-compile-filed properly. This is because
   byte-compile-file inserts the file to be compiled in a temporary
   buffer and switches normal-mode on. In the case where you have
   hs-hide-initial-comment-block in hs-minor-mode-hook, the hiding of
   the initial comment sometimes hides parts of the first statement (seems
   to be only in normal-mode), so there are unbalanced parenthesis.

   The workaround is to clear hs-minor-mode-hook when byte-compiling:

   (define-advice byte-compile-file (:around
                                     (fn &rest rest)
                                     byte-compile-file-hideshow-off)
     (let (hs-minor-mode-hook)
       (apply #'fn rest)))

3) Hideshow interacts badly with Ediff and vc-diff. At the moment, the
   suggested workaround is to turn off hideshow entirely, for example:

   (add-hook 'ediff-prepare-buffer-hook #'turn-off-hideshow)
   (add-hook 'vc-before-checkin-hook #'turn-off-hideshow)

   In the case of vc-diff, here is a less invasive workaround:

   (add-hook 'vc-before-checkin-hook
             (lambda ()
               (goto-char (point-min))
               (hs-show-block)))

   Unfortunately, these workarounds do not restore hideshow state.

* Thanks

Thanks go to the following people for valuable ideas, code and bug reports.

 Dean Andrews, Alf-Ivar Holm, Holger Bauer, Christoph Conrad, Dave Love,
 Dirk Herrmann, Gael Marziou, Jan Djarv, Guillaume Leray, Moody Ahmad,
 Preston F. Crow, Lars Lindberg, Reto Zimmermann, Keith Sheffield,
 Chew Meng Kuan, Tony Lam, Pete Ware, François Pinard, Stefan Monnier,
 Joseph Eydelnant, Michael Ernst, Peter Heslin

Special thanks go to Dan Nicolaescu, who reimplemented hideshow using overlays (rather than selective display), added isearch magic, folded in custom.el compatibility, generalized comment handling, incorporated mouse support, and maintained the code in general. Version 4.0 is largely due to his efforts.

* History (author commentary)

Hideshow was inspired when I learned about selective display. It was reimplemented to use overlays for 4.0 (see above). WRT older history, entries in the masterfile corresponding to versions 1.x and 2.x have been lost. XEmacs support is reliable as of 4.29. State save and restore was added in 3.5 (not widely distributed), and reliable as of
4.30. Otherwise, the code seems stable. Passes checkdoc as of 4.32.
Version 5.x uses new algorithms for block selection and traversal, unbundles state save and restore, and includes more isearch support.

Defined variables (37)

hs-adjust-block-beginningFunction used to tweak the block beginning.
hs-adjust-block-beginning-functionFunction used to tweak the block beginning.
hs-adjust-block-end-functionFunction used to tweak the block end.
hs-allow-nestingIf non-nil, hiding remembers internal blocks.
hs-block-end-regexpRegexp for end of block.
hs-block-start-mdata-selectElement in ‘hs-block-start-regexp’ match data to consider as block start.
hs-block-start-regexpRegexp for beginning of block.
hs-c-start-regexpRegexp for beginning of comments.
hs-cycle-filterControl where typing a TAB cycles the visibility.
hs-display-lines-hiddenIf non-nil, display the number of hidden lines next to the ellipsis.
hs-find-block-beginning-funcFunction used to do ‘hs-find-block-beginning’.
hs-find-block-beginning-functionFunction used to do ‘hs-find-block-beginning’.
hs-find-next-block-funcFunction used to do ‘hs-find-next-block’.
hs-find-next-block-functionFunction used to do ‘hs-find-next-block’.
hs-forward-sexp-funcFunction used to do a ‘forward-sexp’.
hs-forward-sexp-functionFunction used to do a ‘forward-sexp’.
hs-headlineText of the line where a hidden block begins, set during isearch.
hs-hide-all-non-comment-functionFunction called if non-nil when doing ‘hs-hide-all’ for non-comments.
hs-hide-block-behaviorHow hideshow should hide a block.
hs-hide-comments-when-hiding-allWhether the comments should be hidden.
hs-hide-hookHook called (with ‘run-hooks’) at the end of commands to hide text.
hs-indicator-maximum-buffer-sizeMax buffer size in bytes where the indicators should be enabled.
hs-indicator-typeIndicate which indicator type to use for the block indicators.
hs-indicators-mapKeymap for hideshow indicators.
hs-inside-comment-predicateFunction used to check if point is inside a comment.
hs-isearch-openWhat kind of hidden blocks to open when doing ‘isearch’.
hs-looking-at-block-start-p-funcFunction used to do ‘hs-looking-at-block-start-p’.
hs-looking-at-block-start-predicateFunction used to do ‘hs-looking-at-block-start-p’.
hs-minor-modeNon-nil if hs minor mode is enabled.
hs-minor-mode-hookHook called when hideshow minor mode is activated or deactivated.
hs-minor-mode-mapKeymap for hideshow minor mode.
hs-minor-mode-menuMenu used when hideshow minor mode is active.
hs-prefix-mapKeymap for hideshow commands.
hs-set-up-overlayFunction called with one arg, OV, a newly initialized overlay.
hs-show-hookHook called (with ‘run-hooks’) at the end of commands to show text.
hs-show-indicatorsWhether hideshow should display block hide/show indicators.
hs-treesit-thingsTreesit things to check if point is at a valid block.

Defined functions (42)

hs--add-indicators(&optional BEG END)
hs--discard-overlay-before-changes(O &rest R)
hs--get-ellipsis(B E)
hs--make-indicators-overlays(BEG)
hs--refresh-indicators(FROM TO)
hs--set-variable(VAR NTH &optional DEFAULT)
hs-already-hidden-p()
hs-block-positions(&optional ADJUST-BEG ADJUST-END)
hs-c-like-adjust-block-beginning(INITIAL)
hs-cycle(&optional LEVEL)
hs-discard-overlays(BEG END)
hs-find-block-beg-fn--default()
hs-find-block-beginning()
hs-find-next-block(REGEXP BOUND COMMENTS)
hs-find-next-block-fn--default(REGEXP BOUND COMMENTS)
hs-forward-sexp(MATCH-DATA ARG)
hs-get-first-block-on-line(&optional INCLUDE-COMMENTS)
hs-get-near-block(&optional INCLUDE-COMMENT)
hs-grok-mode-type()
hs-hide-all()
hs-hide-block()
hs-hide-block-at-point(&optional COMMENT-REG)
hs-hide-comment-region(BEG END &optional REPOS-END)
hs-hide-initial-comment-block()
hs-hide-level(ARG)
hs-hide-level-recursive(ARG BEG END &optional INCLUDE-COMMENTS FUNC PROGRESS)
hs-hideable-region-p(BEG END)
hs-indicator-mouse-toggle-hiding(EVENT)
hs-isearch-show(OV)
hs-isearch-show-temporary(OV HIDE-P)
hs-life-goes-on(&rest BODY)
hs-looking-at-block-start-p--default()
hs-make-overlay(B E KIND)
hs-minor-mode(&optional ARG)
hs-minor-mode-menu(ARG1)
hs-mouse-toggle-hiding(&optional E)
hs-overlay-at(POSITION)
hs-show-all()
hs-show-block()
hs-toggle-all()
hs-toggle-hiding(&optional E)
turn-off-hideshow()

Defined faces (2)

hs-ellipsisFace used for hideshow ellipsis. Note: If ‘selective-display’ ellipsis already has a face, hideshow will use that face for the ellipsis instead.
hs-indicator-showFace used in hideshow indicator to indicate a shown block.