3

I have two org subtrees that look like this:

* Subtree A [2016-03-31 Thu 07:11] 
This is subtree A.
* Subtree B [2016-03-31 Thu 07:12] 
This is subtree B.

If I run M-x org-cut-subtree with my cursor at the beginning of Subtree A and then immediately run it again (at the beginning of Subtree B), what I end up with is a single kill-ring entry that looks like this:

* Subtree A [2016-03-31 Thu 07:11] 
This is subtree A.
* Subtree B [2016-03-31 Thu 07:12] 
This is subtree B.

I want only the subtree I'm cutting to end up in each kill ring; I don't want subsequent cuts to be appended.

I've spent a good amount of time trying to figure this out (I'm relatively new to Emacs) and I believe the reason for this appending behavior is the following:

two or more kill commands in a row combine their text into a single entry, so that a single C-y yanks all the text as a unit, just as it was before it was killed. https://www.gnu.org/software/emacs/manual/html_node/emacs/Appending-Kills.html

What I want is to only push the currently cut subtree onto the ring, not append.

I'm using org-cut-subtree in a function that I run repeatedly while reviewing my refile.org. I run the function to refile each subtree once I've reviewed it, however this appending to existing kill ring behavior is causing duplicate entries to be refiled whenever I run the command more than once (the second call ends up refiling a copy of both the previous and the current subtree).

How can I prevent this from happening? I noticed if I run M-SPC to set a new mark before calling org-cut-subtree again, the issue seems to get resolved, but I haven't been able to figure out how to do that from within my function.


For a little context, here's the function where I'm using org-cut-subtree (to refile journal entries from refile.org into my main journal file):

(defun my/org-refile-to-journal ()
  "Refile an entry to journal file's date-tree"
  (interactive)
  (require 'org-datetree)
  (let ((journal (expand-file-name "journal-2016.org" org-directory))
    post-date)
    (setq post-date (or (org-entry-get (point) "TIMESTAMP_IA")
            (org-entry-get (point) "TIMESTAMP")))
    (setq post-date (nthcdr 3 (parse-time-string post-date)))
    (setq post-date (list (cadr post-date)
              (car post-date)
              (caddr post-date)))
    (org-cut-subtree)
    (with-current-buffer (or (find-buffer-visiting journal)
                 (find-file-noselect journal))
      (save-excursion
    (org-datetree-file-entry-under (current-kill 0) post-date)
    (bookmark-set "org-refile-last-stored")))
    (message "Refiled to %s" journal)))

Answer

As @lawlist explained in a comment to his answer below, adding (setq this-command 'my/org-refile-to-journal) to the end of my function does exactly what I wanted.

A review of the source for kill-region (which org-cut-subtree uses to cut) reveals that kill-region will only append to an existing ring if this-command is set to a kill-related function (i.e., if the previous command was a kill-related function).

Since 'my/org-refile-to-journal is not a kill-related function, setting this-command to that function effectively temporarily disables the append-to-kill-ring behavior when running my function multiple times in a row.

For reference, here's my updated function that is working as expected:

(defun my/org-refile-to-journal ()
  "Refile an entry to journal file's date-tree"
  (interactive)
  (require 'org-datetree)
  (let ((journal (expand-file-name "journal-2016.org" org-directory))
    post-date)
    (setq post-date (or (org-entry-get (point) "TIMESTAMP_IA")
            (org-entry-get (point) "TIMESTAMP")))
    (setq post-date (nthcdr 3 (parse-time-string post-date)))
    (setq post-date (list (cadr post-date)
              (car post-date)
              (caddr post-date)))
    (org-cut-subtree)
    (with-current-buffer (or (find-buffer-visiting journal)
                 (find-file-noselect journal))
      (save-excursion
    (org-datetree-file-entry-under (current-kill 0) post-date)
    (bookmark-set "org-refile-last-stored")))
    (message "Refiled to %s" journal))
  (setq this-command 'my/org-refile-to-journal))
2

Under certain kill-related circumstances, the kill-related function(s) expressly set this-command to kill-region. The function kill-region looks at the value of the previous command -- which becomes last-command -- and appends subsequent kills to prior kills (if (eq last-command 'kill-region) . . . One way of preventing the append behavior is to modify the value of this-command at the tail end of the current function (which was modified by kill-region). For example, the original poster could place the following snippet at the tail end of his current function: (setq this-command 'my/org-refile-to-journal)


The following snippet achieves a different result -- i.e., it merely preserves the prior values of kill-ring and kill-ring-yank-pointer. For example, a user may wish to run a series of cut/paste operations that rely upon the kill-ring, but the user may nevertheless desire that his/her kill-ring remain the same as it was before running those cut/paste operations. To the extent that the user wishes to preserve his/her system clipboard in its original state, the following variables can also be set temporarily to nil by let-binding them: interprogram-cut-function and interprogram-paste-function.

(require 'cl) ;; for `copy-list'

(defun hello-world ()
"doc-string"
(interactive)
(let* (
    (original-kill-ring (copy-list kill-ring))
    (original-kill-ring-yank-pointer (copy-list kill-ring-yank-pointer))
    [INSERT ANYTHING ELSE THAT NEEDS LET-BINDING] )
  [INSERT CUSTOM STUFF HERE]
  (setq kill-ring original-kill-ring)
  (setq kill-ring-yank-pointer original-kill-ring-yank-pointer)))

I see that the original poster is programmatically reorganizing org-mode files. Although not needed to answer the question, the following link contains another method to accomplish something similar using org-archive-subtree which can cut/paste things to different locations (including other files): https://stackoverflow.com/a/22232709/2112489 -- the variables org-archive-location and org-archive-save-context-info facilitate that process.

  • When I try using your above example, I get "user-error: The kill is not a (set of) tree(s) - please use s-v to yank anyway" on the second call to my function (the first call works fine, but all subsequent calls result in that error). – Raam Dev Mar 31 '16 at 23:34
  • The original answer was meant to be a snippet, not an entire function. I updated the example to include a sample function -- you'll need to insert your additional let-bound variables at the section that reads [INSERT ANYTHING ELSE THAT NEEDS LET-BINDING], and you'll need to insert everything that should be in the middle at [INSERT CUSTOM STUFF HERE]. Because I am not going to be duplicating your exact working environment, I don't feel comfortable inserting your own code because I have no way to test it. – lawlist Mar 31 '16 at 23:59
  • I'm still having trouble getting it working. I've only recently started learning Lisp, so it's probably something obvious that I'm doing wrong. I just read up about let-binding, as I wasn't even sure what that was. I think my trouble with using your snippet is that I'm not sure what "custom stuff" is versus stuff that needs to be let-bound, and then how to adapt the code in my existing function (which I pulled from somewhere) to use your snippet (which I do understand--it sounds like exactly what I need). It's getting late here; I'll have to revisit this with a clear head. Appreciate the help! – Raam Dev Apr 1 '16 at 0:32
  • Perhaps you can achieve what you really want by just making the last line of your custom function named my/org-refile-to-journal include: (setq this-command 'my/org-refile-to-journal) I suggest that because the kill-related command(s) look to see what the last function was in order to determine whether to append to a previous kill. And the kill-related function(s) expressly set this-command depending upon the circumstances -- so you can change the value of this-command at the very end of your function. The answer above is different -- i.e., it just preserves the prior kill-ring. – lawlist Apr 1 '16 at 5:41
  • That worked! Adding (setq this-command 'my/org-refile-to-journal) to the end of my function did exactly what I wanted. Also, it seems like my kill ring is preserved anyway--after running my function three times consecutively, I see three entries for the subtrees I cut plus everything else that was already on my kill ring. Reviewing the code for the kill-related functions now to figure out how that's working. – Raam Dev Apr 1 '16 at 10:26
1

Sorry, I misunderstood your question.

org-cut-subtree is calling org-copy-subtree, which then calls kill-region. As you've discovered, this will result in a series of kills getting appended together. @lawlist has provide a workaround, and it should be ok if you don't mind not having the kills available in your kill-ring beyond the duration of the function itself.

As an alternative, you could use a modified version of org-copy-subtree to force each kill to be treated as a distinct unit, and not appended.

For example:

(defun my-cut-trees1 ()
  "Cut subtrees using org defaults"
  (interactive)
  (org-cut-subtree))

(defun my-cut-trees2 ()
  "Cut subtrees without appending kills"
  (interactive)
  (my-org-copy-subtree nil 'cut))

(defun my-org-copy-subtree (&optional n cut force-store-markers nosubtrees)
  "org-copy-subtree, but without appending adjacent kills"
  (interactive "p")
  (let (beg end folded (beg0 (point)))
    (if (org-called-interactively-p 'any)
        (org-back-to-heading nil) ; take what looks like a subtree
      (org-back-to-heading t)) ; take what is really there
    (setq beg (point))
    (skip-chars-forward " \t\r\n")
    (save-match-data
      (if nosubtrees
          (outline-next-heading)
        (save-excursion (outline-end-of-heading)
                        (setq folded (outline-invisible-p)))
        (ignore-errors (org-forward-heading-same-level (1- n) t))
        (org-end-of-subtree t t)))
    ;; Include the end of an inlinetask
    (when (and (featurep 'org-inlinetask)
               (looking-at-p (concat (org-inlinetask-outline-regexp)
                                     "END[ \t]*$")))
      (end-of-line))
    (setq end (point))
    (goto-char beg0)
    (when (> end beg)
      (setq org-subtree-clip-folded folded)
      (when (or cut force-store-markers)
        (org-save-markers-in-region beg end))

      ;; MODIFICATION HERE
      ;; replace kill-region with kill-new/delete-region 
      (if cut (progn (kill-new (buffer-substring beg end))
                     (delete-region beg end))
      ;; end modification

        (copy-region-as-kill beg end))
      (setq org-subtree-clip (current-kill 0))
      (message "%s: Subtree(s) with %d characters"
               (if cut "Cut" "Copied")
               (length org-subtree-clip)))))

Try this on your org file: my-cut-trees1, which calls org-cut-subtree will append kills, but my-cut-trees2, which uses my modified org-copy-subtree instead won't. Maybe that will solve your problem.

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.