Search code examples
emacslispelisporg-modefootnotes

in org-mode, how to fold/hide footnotes?


In Emacs org-mode, is there a way to get inline footnote definitions to appear as collapsed?

So that for instance, a line like this:

This effect is due to the strength of weak ties[fn:: Newman, Mark, Albert-László Barabási, and Duncan J. Watts. 2006. The Structure and Dynamics of Networks. Princeton, NJ: Princeton University Press].

might simply appear like this:

This effect is due to the strength of weak ties[✭].

I would also need a command to show the footnotes when necessary. So maybe what is needed are two commands: org-hide-footnotes and org-show-footnotes.


Solution

  • INITIAL (February 6, 2014):   First working draft.

    EDIT February 18, 2014:  Revised the function lawlist-toggle-block-visibility so that it contains a proper if/then/else statement -- i.e., if the line contains the requisite beginning region regexp, then the block visibility will be toggled, else a message saying sorry . . . . Added a citation to a related thread for code folding. Revised the error message to refer to a point rather than a line.

    The source code for the answer below is also stored on Github: https://github.com/lawlist/lawlist-org-block-toggle/blob/master/lawlist-org-block-toggle.el

    On a related issue (i.e., to completely hide the properties drawer including the word :PROPERTIES:), please refer to the following thread: Completely hide the :PROPERTIES: drawer in org-mode

    On a semi-related issue (i.e., to create a custom block to be code-folded), see also: https://tex.stackexchange.com/a/161196/26911

    This solution was tested with a fairly recent version of Emacs Trunk (built on January 19, 2014), which contains org-mode version 8.2.5c. Inasmuch as the :PROPERTIES: drawer through its :END: gets folded separately from the footnotes and the html blocks, this solution contemplates that the footnote and/or html code blocks will not be anywhere inside that properties drawer. The footnote can appear anywhere in the text paragraph, but cannot have another pair of square brackets inside the footnote -- since this code looks for the first ending square bracket in order to mark the end of the folded region. This code contemplates that #+BEGIN_HTML and #+END_HTML will both be flush-left with the left-hand margin. The startup views still work the same way -- e.g., the variables org-startup-folded and org-hide-block-startup.

    The modification of org-cycle-internal-local enables tab cycling for all forms of folding in org-mode. All we did was fix the pagination of the function to make it more readable, and added the following condition: ((eq org-cycle-subtree-status 'subtree) (org-show-subtree) (message "ALL") (setq org-cycle-subtree-status 'all)). The interactive function org-cycle is still used to tab cycle between all of the various folded / unfolded views. The function lawlist-block-org-cycle-internal-local is a non-interactive supporting function that is used by org-cycle. The two defalias portions of the code in this solution are needed for everything to work properly. Rather than using tab cycling, the user can also call the interactive function directly at the beginning of the headings or subheadings with: M-x org-cycle RET

    To toggle visibility of the footnote or html block directly, we use the interactive function lawlist-toggle-block-visibility. Place the cursor anywhere on the line containing the beginning of the footnote or, the beginning of the html block, and type: M-x lawlist-toggle-block-visibility RET

    (require 'org)
    
    (defalias 'org-cycle-hide-drawers 'lawlist-block-org-cycle-hide-drawers)
    
    (defun lawlist-block-org-cycle-hide-drawers (state)
      "Re-hide all drawers, footnotes or html blocks after a visibility state change."
      (when
        (and
          (derived-mode-p 'org-mode)
          (not (memq state '(overview folded contents))))
        (save-excursion
          (let* (
              (globalp (memq state '(contents all)))
              (beg (if globalp (point-min) (point)))
              (end
                (cond
                  (globalp
                    (point-max))
                  ((eq state 'children)
                    (save-excursion (outline-next-heading) (point)))
                  (t (org-end-of-subtree t)) )))
            (goto-char beg)
            (while
              (re-search-forward
                ".*\\[fn\\|^\\#\\+BEGIN_HTML.*$\\|^[ \t]*:PROPERTIES:[ \t]*$" end t)
              (lawlist-org-flag t))))))
    
    (defalias 'org-cycle-internal-local 'lawlist-block-org-cycle-internal-local)
    
    (defun lawlist-block-org-cycle-internal-local ()
      "Do the local cycling action."
      (let ((goal-column 0) eoh eol eos has-children children-skipped struct)
        (save-excursion
          (if (org-at-item-p)
            (progn
              (beginning-of-line)
              (setq struct (org-list-struct))
              (setq eoh (point-at-eol))
              (setq eos (org-list-get-item-end-before-blank (point) struct))
              (setq has-children (org-list-has-child-p (point) struct)))
            (org-back-to-heading)
            (setq eoh (save-excursion (outline-end-of-heading) (point)))
            (setq eos (save-excursion (1- (org-end-of-subtree t t))))
            (setq has-children
              (or
                (save-excursion
                  (let ((level (funcall outline-level)))
                    (outline-next-heading)
                    (and
                      (org-at-heading-p t)
                      (> (funcall outline-level) level))))
                (save-excursion
                  (org-list-search-forward (org-item-beginning-re) eos t)))))
          (beginning-of-line 2)
          (if (featurep 'xemacs)
            (while
                (and
                  (not (eobp))
                  (get-char-property (1- (point)) 'invisible))
              (beginning-of-line 2))
            (while
                (and
                  (not (eobp))
                  (get-char-property (1- (point)) 'invisible))
              (goto-char (next-single-char-property-change (point) 'invisible))
              (and
                (eolp)
                (beginning-of-line 2))))
          (setq eol (point)))
        (cond
          ((= eos eoh)
            (unless (org-before-first-heading-p)
              (run-hook-with-args 'org-pre-cycle-hook 'empty))
            (org-unlogged-message "EMPTY ENTRY")
            (setq org-cycle-subtree-status nil)
            (save-excursion
              (goto-char eos)
              (outline-next-heading)
              (if (outline-invisible-p)
                (org-flag-heading nil))))
          ((and
              (or
                (>= eol eos)
                (not (string-match "\\S-" (buffer-substring eol eos))))
              (or
                has-children
                (not (setq children-skipped
                  org-cycle-skip-children-state-if-no-children))))
            (unless (org-before-first-heading-p)
              (run-hook-with-args 'org-pre-cycle-hook 'children))
            (if (org-at-item-p)
              ;; then
              (org-list-set-item-visibility (point-at-bol) struct 'children)
              ;; else
              (org-show-entry)
              (org-with-limited-levels (show-children))
              (when (eq org-cycle-include-plain-lists 'integrate)
                (save-excursion
                  (org-back-to-heading)
                  (while (org-list-search-forward (org-item-beginning-re) eos t)
                    (beginning-of-line 1)
                    (let* (
                        (struct (org-list-struct))
                        (prevs (org-list-prevs-alist struct))
                        (end (org-list-get-bottom-point struct)))
                      (mapc (lambda (e) (org-list-set-item-visibility e struct 'folded))
                        (org-list-get-all-items (point) struct prevs))
                      (goto-char (if (< end eos) end eos)))))))
            (org-unlogged-message "CHILDREN")
            (save-excursion
              (goto-char eos)
              (outline-next-heading)
              (if (outline-invisible-p)
                (org-flag-heading nil)))
            (setq org-cycle-subtree-status 'children)
            (unless (org-before-first-heading-p)
              (run-hook-with-args 'org-cycle-hook 'children)))
          ((or
              children-skipped
              (and
                (eq last-command this-command)
                (eq org-cycle-subtree-status 'children)))
            (unless (org-before-first-heading-p)
              (run-hook-with-args 'org-pre-cycle-hook 'subtree))
            (outline-flag-region eoh eos nil)
            (org-unlogged-message
            (if children-skipped
              "SUBTREE (NO CHILDREN)"
              "SUBTREE"))
            (setq org-cycle-subtree-status 'subtree)
            (unless (org-before-first-heading-p)
              (run-hook-with-args 'org-cycle-hook 'subtree)))
          ((eq org-cycle-subtree-status 'subtree)
            (org-show-subtree)
            (message "ALL")
            (setq org-cycle-subtree-status 'all))
          (t
            (run-hook-with-args 'org-pre-cycle-hook 'folded)
            (outline-flag-region eoh eos t)
            (org-unlogged-message "FOLDED")
            (setq org-cycle-subtree-status 'folded)
            (unless (org-before-first-heading-p)
            (run-hook-with-args 'org-cycle-hook 'folded))))))
    
    (defun lawlist-org-flag (flag)
      "When FLAG is non-nil, hide any of the following:  html code block;
    footnote; or, the properties drawer.  Otherwise make it visible."
      (save-excursion
        (beginning-of-line 1)
        (cond
          ((looking-at ".*\\[fn")
            (let* (
              (begin (match-end 0))
              end-footnote)
              (if (re-search-forward "\\]"
                    (save-excursion (outline-next-heading) (point)) t)
                (progn
                  (setq end-footnote (point))
                  (outline-flag-region begin end-footnote flag))
                (user-error "Error beginning at point %s." begin))))
          ((looking-at "^\\#\\+BEGIN_HTML.*$\\|^[ \t]*:PROPERTIES:[ \t]*$")
            (let* ((begin (match-end 0)))
              (if (re-search-forward "^\\#\\+END_HTML.*$\\|^[ \t]*:END:"
                    (save-excursion (outline-next-heading) (point)) t)
                (outline-flag-region begin (point-at-eol) flag)
                (user-error "Error beginning at point %s." begin)))))))
    
    (defun lawlist-toggle-block-visibility ()
    "For this function to work, the cursor must be on the same line as the regexp."
    (interactive)
      (if
          (save-excursion
            (beginning-of-line 1)
              (looking-at
                ".*\\[fn\\|^\\#\\+BEGIN_HTML.*$\\|^[ \t]*:PROPERTIES:[ \t]*$"))
        (lawlist-org-flag (not (get-char-property (match-end 0) 'invisible)))
        (message "Sorry, you are not on a line containing the beginning regexp.")))