emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: hugo@heagren.com
To: "Thomas S. Dye" <tsd@tsdye.online>
Cc: emacs-orgmode@gnu.org
Subject: [PATCH 1] lisp/org-fold.el: Fold header lines in blocks (was: Proposal: folding stacked `#+header:' lines in src blocks)
Date: Mon, 12 Dec 2022 12:10:31 +0000	[thread overview]
Message-ID: <32864528822dfa0bc8efe3bcf112cbea@heagren.com> (raw)
In-Reply-To: <87v8mndluw.fsf@tsdye.online>

[-- Attachment #1: Type: text/plain, Size: 2068 bytes --]

On 2022-12-07 19:58, Thomas S. Dye wrote:
> I would use this feature.  My stack typically includes headers, a
> name, and a caption.  It would be nice to fold them all out of sight.

Great! I've worked up a patch (well, two patches), attached.

Some details of the implementation:
- folding blocks now cycles:
   - everything folded (only top header line visible if present,
     otherwise only `#+begin' line visible).
   - all content visible, (header stack folded, top header visible,
     `#+begin' and `#+end' lines visible).
   - everything visible
   - (the second and third are treated as equivalent unless there is
     more than one header line.)
- Folding will happen on pressing <tab> (or whatever you have bound)
   with point:
   - anywhere on the `#+begin' or `#+end' line
   - on any header keyword (`#+name:', `#+header', etc.)
   - anywhere else in the header stack where completion does not
     otherwise kick in (not quite sure of the exact mechanics on this
     one, might be dependant on one's configuration)? This includes
     part-way through header lines.
   - This seemed to me the most comfortable configuration for use.
- Retain the old behaviour of moving point to the beginning of the
   remaining visible lines when point is hidden by folding.
- renames `org-fold--hide-wrapper-toggle' to
   `org-fold--hide-wrapper-cycle', which seemed more appropriate under
   the circumstances.
- All the tests in testing/lisp/test-org-fold.el pass for me.

Some less-than-perfections:
- I'm not sure what to do about default block folding (as in, how
   blocks are folded when a file is first visited). First, I don't have
   a complete list of things (variables, startup options, etc.) which
   affect it. Secondly, I'm not sure what the behaviour should be now
   that blocks can be in up to three folding states. Advice would be
   appreciated.
- I'm not sure what other documentation I should add. Once this patch
   is stable and people approve of it, I can add a news entry. Should I
   update the manual at all?

Hope this is useful!

Hugo

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0002-lisp-org-fold.el-Rename-org-fold-hide-wrapper-toggle.patch --]
[-- Type: text/x-diff; name=0002-lisp-org-fold.el-Rename-org-fold-hide-wrapper-toggle.patch, Size: 1692 bytes --]

From 04186df871b94d4a961a5193a7cb050f2a4bc2ff Mon Sep 17 00:00:00 2001
From: Hugo Heagren <hugo@heagren.com>
Date: Mon, 12 Dec 2022 11:30:10 +0000
Subject: [PATCH 2/2] lisp/org-fold.el: Rename `org-fold--hide-wrapper-toggle'
 to `org-fold--hide-wrapper-cycle'

* lisp/org-fold.el: rename
(org-fold--hide-wrapper-toggle): Rename to
`org-fold--hide-wrapper-cycle'.
(org-fold-hide-block-toggle, org-fold-hide-drawer-toggle): Replace old
function name with new.
---
 lisp/org-fold.el | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lisp/org-fold.el b/lisp/org-fold.el
index 21cdc6fad..37e6d6450 100644
--- a/lisp/org-fold.el
+++ b/lisp/org-fold.el
@@ -477,7 +477,7 @@ heading to appear."
 
 ;;;;; Blocks and drawers visibility
 
-(defun org-fold--hide-wrapper-toggle (element category force no-error)
+(defun org-fold--hide-wrapper-cycle (element category force no-error)
   "Cycle visibility for ELEMENT.
 
 ELEMENT is a block or drawer type parsed element.  CATEGORY is
@@ -579,7 +579,7 @@ ELEMENT is provided, consider it instead of the current block.
 
 Return a non-nil value when toggling is successful."
   (interactive)
-  (org-fold--hide-wrapper-toggle
+  (org-fold--hide-wrapper-cycle
    (or element (org-element-at-point)) 'block force no-error))
 
 (defun org-fold-hide-drawer-toggle (&optional force no-error element)
@@ -592,7 +592,7 @@ ELEMENT is provided, consider it instead of the current drawer.
 
 Return a non-nil value when toggling is successful."
   (interactive)
-  (org-fold--hide-wrapper-toggle
+  (org-fold--hide-wrapper-cycle
    (or element (org-element-at-point)) 'drawer force no-error))
 
 (defun org-fold-hide-block-all ()
-- 
2.20.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0001-lisp-org-fold.el-Fold-header-lines-in-blocks.patch --]
[-- Type: text/x-diff; name=0001-lisp-org-fold.el-Fold-header-lines-in-blocks.patch, Size: 5403 bytes --]

From 62ef11504f361d058c5425ad5e1184be6e19bd6f Mon Sep 17 00:00:00 2001
From: Hugo Heagren <hugo@heagren.com>
Date: Mon, 12 Dec 2022 11:25:14 +0000
Subject: [PATCH 1/2] lisp/org-fold.el: Fold header lines in blocks

* lisp/org-fold.el (org-fold--hide-wrapper-toggle): Cycle blocks
between three folding states, potentially including headers in
folding.  Update docstring accordingly.
---
 lisp/org-fold.el | 65 +++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 53 insertions(+), 12 deletions(-)

diff --git a/lisp/org-fold.el b/lisp/org-fold.el
index 05ac71ea4..21cdc6fad 100644
--- a/lisp/org-fold.el
+++ b/lisp/org-fold.el
@@ -478,14 +478,27 @@ heading to appear."
 ;;;;; Blocks and drawers visibility
 
 (defun org-fold--hide-wrapper-toggle (element category force no-error)
-  "Toggle visibility for ELEMENT.
+  "Cycle visibility for ELEMENT.
 
 ELEMENT is a block or drawer type parsed element.  CATEGORY is
 either `block' or `drawer'.  When FORCE is `off', show the block
 or drawer.  If it is non-nil, hide it unconditionally.  Throw an
 error when not at a block or drawer, unless NO-ERROR is non-nil.
 
-Return a non-nil value when toggling is successful."
+A property drawer will cycle between open and closed states.
+
+A block with not extra header arguments (lines beginning
+`#+header:', `#+name:' etc. directly above it) will cycle between
+open and closed states.  A block with such headers will cycle:
+- everything closed (with only the top header visible)
+- header stack folded (top header visible, other headers hidden, body
+  and content of block visible)
+- everything open
+
+When there is only one header argument, no distinction is made
+between the the second and third states.
+
+Return a non-nil value when cycling is successful."
   (let ((type (org-element-type element)))
     (cond
      ((memq type
@@ -495,28 +508,56 @@ Return a non-nil value when toggling is successful."
                         comment-block dynamic-block example-block export-block
                         quote-block special-block src-block verse-block))
               (_ (error "Unknown category: %S" category))))
-      (let* ((post (org-element-property :post-affiliated element))
+      (let* ((post (org-element-property :post-affiliated element)) ; bol of begin_x
+             (begin (org-element-property :begin element))          ; bol of first header
+             (content-begin                                         ; bol of block content
+              (1+ (save-excursion (goto-char post) (line-end-position))))
+             (state
+              (cond
+               ;; Everything is folded
+               ((org-fold-folded-p content-begin 'block) 'all)
+               ;; Everything is open
+               (t 'nothing)))
              (start (save-excursion
-                      (goto-char post)
+                      (goto-char
+                       (if (equal category 'block)
+                           (pcase state
+                             ;; If there are no headers, start unfolding
+                             ;; from the char before the char at bol
+                             ;; of `begin_x', so that `begin_x' is
+                             ;; displayed after unfolding. Otherwise
+                             ;; begin unfolding from bol of that line.
+                             ('all (if (= post begin) post (1- post)))
+                             ('nothing begin))
+                         post))
                       (line-end-position)))
              (end (save-excursion
-                    (goto-char (org-element-property :end element))
+                    (goto-char
+                     (if (equal category 'block)
+                         (pcase state
+                           ('all (org-element-property :end element))
+                           ('nothing (org-element-property :end element)))
+                       (org-element-property :end element)))
                     (skip-chars-backward " \t\n")
                     (line-end-position))))
         ;; Do nothing when not before or at the block opening line or
         ;; at the block closing line.
         (unless (let ((eol (line-end-position)))
-                  (and (> eol start) (/= eol end)))
+                  (and (> eol content-begin) (/= eol end)))
           (org-fold-region start end
-                   (cond ((eq force 'off) nil)
-                         (force t)
-                         ((org-fold-folded-p start category) nil)
-                         (t t))
-                   category)
+                           (cond ((eq force 'off) nil)
+                                 (force t)
+                                 ((org-fold-folded-p start category) nil)
+                                 (t t))
+                           category)
           ;; When the block is hidden away, make sure point is left in
           ;; a visible part of the buffer.
           (when (invisible-p (max (1- (point)) (point-min)))
-            (goto-char post))
+            ;; The only state transition which hides point is from
+            ;; everything open to everything folded. In this case, the
+            ;; best place to leave point is at bol of first visible
+            ;; line, which is `begin'.
+            (goto-char begin))
           ;; Signal success.
           t)))
      (no-error nil)
-- 
2.20.1


  reply	other threads:[~2022-12-12 12:12 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-12-07 11:26 Proposal: folding stacked `#+header:' lines in src blocks hugo
2022-12-07 19:58 ` Thomas S. Dye
2022-12-12 12:10   ` hugo [this message]
2022-12-13  9:19     ` [PATCH 1] lisp/org-fold.el: Fold header lines in blocks (was: Proposal: folding stacked `#+header:' lines in src blocks) Ihor Radchenko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=32864528822dfa0bc8efe3bcf112cbea@heagren.com \
    --to=hugo@heagren.com \
    --cc=emacs-orgmode@gnu.org \
    --cc=tsd@tsdye.online \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).