Shift a region or line in emacs
Asked Answered
Z

4

19

I'm looking for a way in emacs to shift text to the right or to the left by n spaces. A similar functionality that it in vim << or >>. It should work on a region or if no region is selected on a current line and not move the cursor from its current location.

The solution from EmacsWiki does not work very well as the M-x indent-rigidly since it somewhat remembers the last region used and shifts that one instead. The closest seems to be the one here but I did not managed to make it work. I'm not a lisp developer so it's difficult to modify the code. I will appreciate any help.

Thanks!

Zita answered 1/7, 2010 at 9:17 Comment(0)
S
9

Maybe this works the way you want.

(defun shift-text (distance)
  (if (use-region-p)
      (let ((mark (mark)))
        (save-excursion
          (indent-rigidly (region-beginning)
                          (region-end)
                          distance)
          (push-mark mark t t)
          (setq deactivate-mark nil)))
    (indent-rigidly (line-beginning-position)
                    (line-end-position)
                    distance)))

(defun shift-right (count)
  (interactive "p")
  (shift-text count))

(defun shift-left (count)
  (interactive "p")
  (shift-text (- count)))
Sward answered 1/7, 2010 at 9:48 Comment(2)
Excellent! That is exactly what I was looking for! Thanks a lot. Don't you wanna put it also to EmacsWiki?Zita
What should I do to make it shift 4 call the function 4 times?Feingold
B
40

You could select the region then C-u C-x <tab> will shift 4 spaces. You can type a number after C-u to change 4 to anything else.

Buckish answered 30/10, 2012 at 14:9 Comment(1)
One more tip: the number after C-u may be negative, in this case region will be shifted to the left instead of to the right.Blanca
S
9

Maybe this works the way you want.

(defun shift-text (distance)
  (if (use-region-p)
      (let ((mark (mark)))
        (save-excursion
          (indent-rigidly (region-beginning)
                          (region-end)
                          distance)
          (push-mark mark t t)
          (setq deactivate-mark nil)))
    (indent-rigidly (line-beginning-position)
                    (line-end-position)
                    distance)))

(defun shift-right (count)
  (interactive "p")
  (shift-text count))

(defun shift-left (count)
  (interactive "p")
  (shift-text (- count)))
Sward answered 1/7, 2010 at 9:48 Comment(2)
Excellent! That is exactly what I was looking for! Thanks a lot. Don't you wanna put it also to EmacsWiki?Zita
What should I do to make it shift 4 call the function 4 times?Feingold
U
6

To achieve this I usually do a trick:

  • activate CUA mode
  • go to the beginning of line
  • C-RET, now if you move the cursor you should see a rectangular red region
  • Move the cursor down the lines and type space until you've obtained the correct shifting.

This can be done also programmatically in some way (in the same way).

EDIT: I've just read the article in emacs wiki, it's the same solution except for the CUA mode that is infinitely more powerful than the "common" rectanguar selection (since it's visual).

Ulises answered 1/7, 2010 at 9:22 Comment(4)
You can enable CUA's excellent rectangle editing facilities separately from its other features with (cua-selection-mode t). Provided you don't have C-RET bound to something else, you can put this in your init file and it won't conflict with anything.Mcmorris
Quick notes on basic usage for shifting lines: C-RET + cursor movements to specify rectangle, RET to cycle corners, typing inserts before/after rectangle, C-RET to exit. See the notes under "CUA rectangle support" in cua-base.el for the full details on what this minor mode provides, or read trey-jackson.blogspot.com/2008/10/…Mcmorris
I have a confession. I use CUA mode.Auburta
LOVE THIS TIP (emacs user of 20+ years)Antimalarial
E
1

As I use Evil (with Doom Emacs), the Vim-like region shifting is already implemented in evil's visual mode with S-v and </> properly.

I'm mostly using insert-mode though, and when it's active I also want to be able to shift the region, preferrably by the current language's shift-width.

To achieve this, here's an implementation that re-uses evil's shifting, but does it "properly" in insert-mode.

(defun jj/shift-text (beg end shift-block-fun shift-line-fun)
  "shift text in region or line using evil like S-v with < and > do in Vim.
  It takes special care of preserving or even extending the region to the moved text lines."
  (if (use-region-p)
      (progn
        (let ((point-at-end (< (mark) (point))))

          ;; fix up current region end to grab the whole line
          (if point-at-end
              (end-of-line)
            (beginning-of-line))

          ;; then fix up the other region end
          (exchange-point-and-mark)
          (if point-at-end
              (beginning-of-line)
            (end-of-line))

          ;; restore mark-point order
          (exchange-point-and-mark)

          (let ((linebeg (if point-at-end (mark) (point)))
                (lineend (if point-at-end (point) (mark))))
            ;; shift the text
            (save-mark-and-excursion
              (funcall shift-block-fun linebeg lineend)
              ;; "In Transient Mark mode, every buffer-modifying primitive sets deactivate-mark"
              ;; but we wanna keep it active :)
              (setq deactivate-mark nil)))))

    (funcall shift-line-fun 1)))

(defun jj/shift-left (beg end)
  (interactive "r")
  (jj/shift-text beg end #'evil-shift-left #'evil-shift-left-line))

(defun jj/shift-right (beg end)
  (interactive "r")
  (jj/shift-text beg end #'evil-shift-right #'evil-shift-right-line))

and where your keybindings are defined:

  ;; text shifting. evil-normal-state-map has these anyway.
  (define-key evil-hybrid-state-map (kbd "M-<") #'jj/shift-left)
  (define-key evil-hybrid-state-map (kbd "M->") #'jj/shift-right)
Espionage answered 8/7, 2022 at 11:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.