I often find I want to close the nearest parent subtree, but don't know of a better way of doing this than navigating to the header first and then pressing tab. I use evil-mode, so properly this should be the effect of
zc, but it is not.
I tried implementing the 'keep closing parent' behaviour (please edit the question to say more explicitly what you wanted). I'm quite sure this can be drastically simplified and is likely buggy, but this seems to work for me:
(defun my/org-get-folded-state () (cond ((not (or (org-at-item-p) (org-at-heading-p))) 'not-at-node) ((org-before-first-heading-p) 'not-at-node) (t (let (eoh eol eos has-children children-skipped struct) ;; First, determine end of headline (EOH), end of subtree or item ;; (EOS), and if item or heading has children (HAS-CHILDREN). (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 (org-end-of-subtree t t) (when (bolp) (backward-char)) (point))) (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))))) ;; Determine end invisible part of buffer (EOL) (beginning-of-line 2) (while (and (not (eobp)) ;; this is like `next-line' (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) 'empty-node) ((or (>= eol eos) (not (string-match "\\S-" (buffer-substring eol eos)))) 'folded) (t 'not-folded)))))) (defun my/org-at-list-root () ;; (org-at-item-p) must be true when you call this (save-excursion (org-beginning-of-item-list) (let ((old (point))) (org-back-to-heading) (forward-line) (eq (point) old)))) (defun my/org-jump-to-parent-item () (org-beginning-of-item-list) (org-up-element)) (defun my/org-tree-can-fold-p () (not (member (my/org-get-folded-state) (list 'folded 'empty-node)))) (defun my/org-cycle-until-folded () (while (my/org-tree-can-fold-p) (org-cycle))) (defun my/org-close-next-parent () (interactive) (cond ((org-before-first-heading-p) nil) ((org-at-item-p) (if (my/org-tree-can-fold-p) (my/org-cycle-until-folded) (if (my/org-at-list-root) (progn (org-back-to-heading) (my/org-close-next-parent)) (my/org-jump-to-parent-item) (my/org-close-next-parent)))) ((org-at-heading-p) (if (my/org-tree-can-fold-p) (my/org-cycle-until-folded) (if (not (eq (org-outline-level) 1)) (progn (call-interactively #'outline-up-heading) (my/org-close-next-parent))))) ((org-in-item-p) (org-beginning-of-item) (my/org-cycle-until-folded)) (t ;; in heading (org-back-to-heading) (my/org-cycle-until-folded))))
How about something like this?
(defun my/org-cycle-parent-heading () (interactive) (org-back-to-heading) (org-cycle)))
evil-org-mode, so my key binding configuration for that looks like this:
(use-package org :general (:states '(normal) :keymaps 'evil-org-mode-map "zc" 'my/org-cycle-parent-heading))
This seems work.
(defun evil*-close-fold () (interactive) (if (and (or (org-at-heading-p) (invisible-p (point))) (invisible-p (point-at-eol)) (>= (org-current-level) 2)) (outline-up-heading 1)) (evil-close-fold)) (define-key evil-normal-state-map (kbd "zc") 'evil*-close-fold)
The disjunction is necessary because, after
(org-at-heading-p) will not return
t, unless point was at
the heading from before.