By default, Org-mode headers form trees. That is, every header can have an arbitrary number of sub-tasks, and this relationship is recursive to an arbitrary depth. However, a task in practice can be a dependency of two parents. Adding this property creates a directed acyclic graph (a DAG).

This relationship is not supported out of the box by Org mode, though it is available in Leo, as well as a number of enterprise issue managers. Leo, in particular, implements a DAG through cloned nodes. These are nodes that can appear in more than one place in an outline. Hence, they can have multiple parents, correctly reflecting the property that they may be required for multiple higher-level concerns.

Further justification for this feature is provided in the Leo documentation on personal information management:

Clones can greatly accelerate your work flow. To start a project, clone nodes related to the project and drag them at or near the top level, where you can get at them easily. When the project is complete, just delete the clones. This work flow is surprisingly effective:

  • The original nodes never move, but they change whenever their clones do.
  • There is nothing to “put back in place” when you are done. Just delete the clones.

Used this way, clones create views: when you gather cloned nodes together for a project, you are, in effect, creating a project-oriented view of the outline. This view focuses your attention on only those nodes that are relevant to the task at hand.

How can cloned nodes be implemented in Org mode?

  • I'm afraid this kind of question is better asked on the org mailing list and risks being closed by moderators for being too broad
    – wasamasa
    Jun 17 '19 at 7:51

Emacs 26.1 has built-in support for cloned text (function text-clone-create). At least part of the aspects of cloned nodes can be achieved with text clones.

Test usage:

  1. Run the code below
  2. Open an Org mode file
  3. Mark an Org heading in that file with M-x text-clone-mark-region
  4. Place point where you want to clone the text within the same file and call M-x text-clone-copy
  5. Check how the clones work by editing the text within the clone or the cloned region
  6. Save and close the file
  7. Re-open the file and verify that the clones are preserved by repeating 5.

You can mark only a part of the stars of the original heading to promote the clone and you can insert stars before the clone to demote the clone.

(defface text-clone-overlay-face '((t :background "yellow"))
  "Face for marking regions to be cloned.")

(defvar-local text-clone-overlay nil
  "Store for the region to be cloned.")

(defun text-clone-mark-region (b e)
  "Mark region from B to E for cloning."
  (interactive "r")
  (if text-clone-overlay
      (move-overlay text-clone-overlay b e)
    (setq text-clone-overlay (make-overlay b e))
    (overlay-put text-clone-overlay 'face 'text-clone-overlay-face)))

(defun text-clone-copy (point)
  "Copy the clone overlay region and create text-clone at POINT."
  (interactive "d")
  (let ((b (overlay-start text-clone-overlay))
    (e (overlay-end text-clone-overlay)))
    (when (and (>= point b)
           (< point e))
      (user-error "Point within cloned region"))
    (let ((str (buffer-substring b e)))
      (insert str)
    (goto-char point)
    (text-clone-create b e)))))

(defvar-local text-clone-list nil
  "List of text clones in current buffer.")

(defun text-clone-list (&optional begin end)
  "Get clones in region from begin to end."
  (let* ((cnt -1)
     (ols (cl-loop for ol being the overlays
              if (overlay-get ol 'text-clones)
              do (overlay-put ol 'text-clone-index (cl-incf cnt))
              and collect ol))
    (dolist (ol ols)
      (setq ret (cons
           (overlay-start ol)
           (overlay-end ol))
           (lambda (clone)
             (overlay-get clone 'text-clone-index))
          (overlay-get ol 'text-clones)))
    (nreverse ret)))

(defun text-clone-save ()
  "Add `text-clone-list' as local variable."
    (add-file-local-variable 'text-clone-list (text-clone-list))))

(defun text-clone-read ()
  "Create text clones according to `text-clone-list'."
  (let (clones)
    (dolist (clone text-clone-list)
      (let ((ol (make-overlay (car clone) (cadr clone))))
    (overlay-put ol 'text-clones (nthcdr 2 clone))
    (setq clones (cons ol clones))))
    (setq clones (nreverse clones))
    (dolist (clone clones)
      (overlay-put clone
       (mapcar (lambda (idx)
         (nth idx clones))
           (overlay-get clone 'text-clones)))
      (overlay-put clone 'modification-hooks '(text-clone--maintain))
      (overlay-put clone 'evaporate t))))

(define-minor-mode text-clone-mode
  "Make text clones permanent in their file."
  (if text-clone-mode
    (add-hook 'after-change-major-mode-hook #'text-clone-read t t)
    (add-hook 'before-save-hook #'text-clone-save t t))
    (save-excursion (add-file-local-variable 'text-clone-list nil))
    (remove-hook 'before-save-hook #'text-clone-save t)
    (remove-hook 'after-change-major-mode-hook #'text-clone-read t)))

(add-hook 'org-mode-hook #'text-clone-mode)
  • This is compelling, thank you! However, when I get to step 5, editing the text in one of the regions does not change the other region, in either direction. Is there more I need to set up such that changes are transferred? Jun 17 '19 at 19:58
  • @MatthewPiziak This is just a demo. The clones currently do not expand. Please edit text in the middle of the clones. Does that work? Just tested again with
    – Tobias
    Jun 17 '19 at 21:51
  • @MatthewPiziak Sorry, I had to replace incf with cl-incf to ensure that the cl-lib package is loaded. It works now for me with emacs -Q Emacs 26.2, org-version 9.1.9.
    – Tobias
    Jun 17 '19 at 22:03

A packaged called org-clones is now available. It supports cloned nodes in Org mode.

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.