4

The task: I have a large-ish file, from which I need to pick certain chunks of text and move them into a certain place so they go one after another, in the order in which I pick them. More specifically, it's an Org-mode file, and the chunks are headings (possibly with subtrees). The target file may be the same, or it might not.

Question: is there a package/mode or something, that would let me specify the target place and then add chunks of text there while I roam around and point out the pieces to cut?

My attempts at finding such a thing turned up nothing so far, however the search terms are rather generic so the results are messy. I've thrown together code that inserts multiple items from the kill ring—I kill the chunks then insert them; but this doesn't seem quite clean, especially if I ever manage to save the file with the cuts made and have Emacs or the machine crash before inserting the text. Plus, the workflow doesn't feel right.

Double kudos if the solution handles Org-mode headings: e.g. adjusts the inserted ones to the level of the ones in the target place. Triple kudos if I can bind it in Evil right away, i.e. it works with text objects.

aaa
  • 426
  • 3
  • 9

3 Answers3

4

A basic option is to use append-next-kill.

Rather than killing each region with just C-w (or similar), you instead use C-M-wC-w (i.e. type C-M-w immediately prior to whichever kill command you're using) to append the new kill to the most recent kill ring item.

Do that for every region in sequence, and then a single C-y will insert the whole thing.

C-M-w runs the command append-next-kill (found in global-map)

Cause following command, if it kills, to add to previous kill.
If the next command kills forward from point, the kill is
appended to the previous killed text.  If the command kills
backward, the kill is prepended.  Kill commands that act on the
region, such as ‘kill-region’, are regarded as killing forward if
point is after mark, and killing backward if point is before
mark.

If the next command is not a kill command, ‘append-next-kill’ has
no effect.
phils
  • 48,657
  • 3
  • 76
  • 115
  • Alas that seems very similar to what I've already slapped together—only I need to do the hotkeys on every piece of text instead of selecting the pieces from the kill-ring once at the target. – aaa Nov 12 '20 at 15:16
3

I believe you're looking for org-refile (bound to C-c C-w). By default, org-refile will only consider the current file. To refile into another file add the target file with your desired headings to org-refile-targets:

;; Add the current buffer as possible refile target ; only consider up to level 2 as target.
(add-to-list 'org-refile-targets `(,buffer-file-name :maxlevel . 2))

You can also use a regular expression to refile to a specific heading:

;; Use path/to/file as refile target, and ONLY headings that fit "List of great quotes"
(setq org-refile-targets '(("path/to/file" :regexp . "List of great quotes")))

org-refile-targets's customization interface (M-x customize-option RET org-refile-targets RET) might be a little bit easier than lisp-based modification, though.

Even better, store the location yourself and then use the stored location repeatedly:

(defvar aaa/refile-location nil 
  "Location used for `aaa/refile-to-saved-location'.
Use `aaa/save-refile-location' to set it in an org-mode buffer.")

(defun aaa/save-refile-location ()
  "Save current position for later refiling via `aaa/refile-to-saved-location'."
  (interactive)
  (when (not (equal major-mode 'org-mode))
    (user-error "Refile location can only get saved in org-mode buffers!"))
  (let ((heading (org-entry-get nil "ITEM"))
        (file    (buffer-file-name))
        (pos     (point)))
    (setq aaa/refile-location (list heading file nil pos))
    (message "Saved \"%s\" in \"%s\"" heading file)))

(defun aaa/refile-to-saved-location ()
  "Move the current item to the saved location."
  (interactive)
  (if aaa/refile-location
      (org-refile nil nil aaa/refile-location)
    (user-error "Refile location is unset!")))

(global-set-key (kbd "<f6>") 'aaa/refile-to-saved-location)
(global-set-key (kbd "C-<f6>") 'aaa/save-refile-location)

This will enable you to use C-F6 to store your target location and F6 afterwards to move items (with their subtree).

Zeta
  • 1,045
  • 9
  • 18
  • The idea of using org-refile for this is interesting, hasn't occurred to me since the requirements are a bit different. It does seem like a clean solution with the tool made just for that task. However, dealing with the target selection dialog every time is not very enticing. I'll see if org-refile can take the target place as an argument, otherwise I guess I'll need to make a command that replaces the targets list with just one heading. (And maybe add the ability to use the heading's ID in `org-refile-targets`.) – aaa Nov 12 '20 at 15:26
  • *"However, dealing with the target selection dialog every time is not very enticing."* You can use `C-u C-u M-x org-refile` to reuse the last location. But I'll try to come up with something easier/better. – Zeta Nov 12 '20 at 16:06
  • @aaa There. That seems a lot more user-friendly. However, I'm not sure about best-practice. – Zeta Nov 12 '20 at 17:05
  • Excellent, this is pretty much exactly what I had in mind. `org-refile` really was the missing link—it even works with multiple selected headings, out of the box. By now, only a solution that somehow works equally well for simple text would beat this, but I doubt that it's feasible, both practically and semantically. Now that I looked closer at the workings of `org-refile`, I might whip me up some additions, like the choice of recent locations, filing above/below the selected heading, etc. Neat stuff. – aaa Nov 12 '20 at 20:59
0

By the way, it turns out that the Doom configuration has functionality very similar to what I wanted. After using org-refile, mapped to SPC m r r, I can invoke +org/refile-to-last-location, or SPC m r l.

(It's not the same as first pointing out the target destination, from the comfort of browsing the outline itself as usual—instead, org-refile pops up an Ivy/Helm completion listing all existing nodes in the file. But it's quite tolerable, at least as long as I know what the target node is called. And these commands have the benefit of already having decent mappings, instead of me desperately trying to come up with new ones.)

Doom also has +org/refile-to-other-window (SPC m r o) and +org/refile-to-other-buffer (SPC m r O), which sound like they should do the same org-refile thing, but offer nodes from all open Org windows/buffers—in fact, the latter function's source seems to do exactly that. However for some reason they fail at this, and again do regular old org-refile to the current buffer. If I figure out the problem and a fix, these functions should alleviate almost all of my org-refile needs. (I'm guessing the hiccup is with some var not being dynamically overridden when it should be.)

Conceivably, I could also write a command that will store a location for +org/refile-to-last-location to use, just as I would do with my separate custom commands (e.g. aaa/save-refile-location)—and invoke it from the outline, as I originally wanted.

aaa
  • 426
  • 3
  • 9
  • Also, Doom's `org-refile` offers previously used target locations, one at the top of the list and several at the bottom—at least with Helm enabled instead of Ivy. I'm yet to see exactly how it works, but it's possible that this solves the problem of having several alternate locations for frequent refiling. – aaa Aug 06 '21 at 21:30