12

I have been using eclipse for a little while now and I did find some shortcuts highly useful, in particular the ability to move a rectangular selection of lines up and down using Alt+Up/Down. I have been looking around for this functionality in emacs. So far I have found the following script:

(defun move-text-internal (arg)
   (cond
    ((and mark-active transient-mark-mode)
     (if (> (point) (mark))
            (exchange-point-and-mark))
     (let ((column (current-column))
              (text (delete-and-extract-region (point) (mark))))
       (forward-line arg)
       (move-to-column column t)
       (set-mark (point))
       (insert text)
       (exchange-point-and-mark)
       (setq deactivate-mark nil)))
    (t
     (beginning-of-line)
     (when (or (> arg 0) (not (bobp)))
       (forward-line)
       (when (or (< arg 0) (not (eobp)))
            (transpose-lines arg))
       (forward-line -1)))))

(defun move-text-down (arg)
   "Move region (transient-mark-mode active) or current line
  arg lines down."
   (interactive "*p")
   (move-text-internal arg))

(defun move-text-up (arg)
   "Move region (transient-mark-mode active) or current line
  arg lines up."
   (interactive "*p")
   (move-text-internal (- arg)))

(global-set-key [\M-up] 'move-text-up)
(global-set-key [\M-down] 'move-text-down)

This does work except for the fact that this moves the selection. I would rather move all lines (including trailing newline) included in the selection instead. Is there a way to realize this as well?

3

3 Answers 3

18

drag-stuff

Check out the drag-stuff package (also available on Melpa).

You can then select a region and use drag-stuff-up/drag-stuff-down to move that region up/down.


Alternative behavior when dragging lines

By default, the drag-stuff commands will also drag the line the point is on (even if the point is on the very first column). If you want to select, let's say 2 whole lines by doing C-a C-SPC C-n C-n, the selection will look something like this

 line 1
▯line 2
 line 3
▮line 4 
 line 5

Note that here I intend to move just lines 2 and 3, not line 4. But drag-stuff will move that 3rd line as well by default.

That was my pet-peeve (and probably doesn't apply to anyone else) and so I requested the package dev for a solution. Here's a hack you can put in your emacs config after requiring drag-stuff if you do not want this default behavior. The hack will not move the current line IF the point is on column 0 (first column).

;; https://github.com/kaushalmodi/.emacs.d/blob/master/setup-files/setup-drag-stuff.el
;; https://github.com/rejeep/drag-stuff.el/issues/4
(defvar modi/drag-stuff--point-adjusted nil)
(defvar modi/drag-stuff--point-mark-exchanged nil)

(defun modi/drag-stuff--adj-pt-pre-drag ()
  "If a region is selected AND the `point' is in the first column, move
back the point by one char so that it ends up on the previous line. If the
point is above the mark, exchange the point and mark temporarily."
  (when (region-active-p)
    (when (< (point) (mark)) ; selection is done starting from bottom to up
      (exchange-point-and-mark)
      (setq modi/drag-stuff--point-mark-exchanged t))
    (if (zerop (current-column))
        (progn
          (backward-char 1)
          (setq modi/drag-stuff--point-adjusted t))
      ;; If point did not end up being on the first column after the
      ;; point/mark exchange, revert that exchange.
      (when modi/drag-stuff--point-mark-exchanged
        (exchange-point-and-mark) ; restore the original point and mark loc
        (setq modi/drag-stuff--point-mark-exchanged nil)))))

(defun modi/drag-stuff--rst-pt-post-drag ()
  "Restore the `point' to where it was by forwarding it by one char after
the vertical drag is done."
  (when modi/drag-stuff--point-adjusted
    (forward-char 1)
    (setq modi/drag-stuff--point-adjusted nil))
  (when modi/drag-stuff--point-mark-exchanged
    (exchange-point-and-mark) ; restore the original point and mark loc
    (setq modi/drag-stuff--point-mark-exchanged nil)))

(add-hook 'drag-stuff-before-drag-hook #'modi/drag-stuff--adj-pt-pre-drag)
(add-hook 'drag-stuff-after-drag-hook  #'modi/drag-stuff--rst-pt-post-drag)

Demonstration of how dragging lines works before and after the above hack

Before hack

 line 1                                      line 1
▯line 2                                      line 5
 line 3    --(M-x drag-stuff-down)-->       ▯line 2   MOVED LINE
▮line 4                                      line 3   MOVED LINE
 line 5                                     ▮line 4   MOVED LINE

After hack

 line 1                                      line 1
▯line 2                                      line 4
 line 3    --(M-x drag-stuff-down)-->       ▯line 2   MOVED LINE
▮line 4                                      line 3   MOVED LINE
 line 5                                     ▮line 5

Keybindings

To achieve the eclipse-like behavior just add appropriate key bindings:

(global-set-key (kbd "M-<up>")   #'drag-stuff-up)
(global-set-key (kbd "M-<down>") #'drag-stuff-down)
4
  • Hm... as annoying as moving line 4 is, it's definitely more consistent. For example, if you were to move lines, but your point was at EOL, the new "hacked" behavior would seem very strange indeed.
    – PythonNut
    Jul 13, 2015 at 17:47
  • @PythonNut Yeah, if point were not on col 0, that whole line would be moved. But that's why this is a hack; it fits my workflow. I believe in 99% of my region selections when moving lines, my point is on col 0 because I tend to select whole lines (example C-a C-SPC C-4 C-n) or whole paragraphs (example C-a C-SPC M-}). I know there is M-h, just haven't got used to that yet. Imagine how annoying it could get if each time I ended up dragging my selected paragraph plus the next line! :) Jul 13, 2015 at 17:53
  • Won't your hack do the wrong thing if point is at the beginning of the region instead of at the end? Jul 24, 2015 at 10:47
  • @HaraldHanche-Olsen Correct. Surprisingly I never did selection that way (starting from bottom and going up) and so I never caught this issue :). It's fixed now (as I could tell from a quick verification). Let me know if this works for you. Thanks for noticing this. Jul 24, 2015 at 19:18
3

I coded this a loong time ago, use it every day.

(defun move-line-up ()
  "Move up the current line."
  (interactive)
  (transpose-lines 1)
  (forward-line -2)
  (indent-according-to-mode))

(defun move-line-down ()
  "Move down the current line."
  (interactive)
  (forward-line 1)
  (transpose-lines 1)
  (forward-line -1)
  (indent-according-to-mode))

(global-set-key [(meta shift up)]  'move-line-up)
(global-set-key [(meta shift down)]  'move-line-down)
2
  • 1
    this is fantastic, thanks for posting this, I've no idea why you don't have more upvotes!
    – iFunction
    Dec 14, 2019 at 6:33
  • Hmm, good, but doesn't work on selected ranges (I would be happy with it ignoring the characters in the ranges and work on lines inclusive
    – Yarek T
    Aug 25, 2020 at 16:48
1

Here's a way to do this using keyboard macros:

  • C-x ( start recording the macro.
  • C-aSPCC-pSPCC-wC-pC-y cut a line and paste it one line earlier in the buffer.
  • C-x ) finish recording the macro.
  • C-x e will move the line up as many times as you press it.
2
  • Well, there is a function transpose-lines which works in this way, but this only works for single lines as does your suggestion...
    – hfhc2
    Jul 13, 2015 at 20:23
  • @hfhc2 the keyboard macro is an ad hoc solution anyway. At the same rate you could make it move more than one line (just add a numeric argument to C-p).
    – wvxvw
    Jul 13, 2015 at 23:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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