This is a read-only archive!

Vim vs. Emacs: Indenting text before copying

I use Markdown on my blog for posts and comments, and I post at other sites that use Markdown (e.g. Stack Overflow). In Markdown, text indented four spaces is displayed as code, in pre tags.

I find myself often writing code in Vim or Emacs and needing to copy/paste it into a browser in a Markdown-suitable back. This is easy to do in Vim and Emacs, only a few keystrokes. But "a few" is still greater than "one", so the heck with that. Let's script it.

Vim version

This keymapping in Vim will do it all for me:

vmap <Leader>y :s/^/    /<CR>gv"+ygv:s/^    //<CR>

One clumsy thing about Vim is needing to restore the previous visual selection after each regex-replacement. I could use the marks '< and '> as ranges to :s instead, but that's more typing than simply doing gv in the mapping. Copying to the system clipboard is easy because Vim has a register "+ for that purpose.

This took me maybe 45 seconds to write, probably due to being pretty familiar with Vim already. But in Vim, mappings are easy. You just type the characters that you'd type if you were doing it manually.

Emacs version

Trying to do the same in Emacs was painful. My Emacs-fu is sorely inadequate, compared to my Vim-jitsu. This seems to work, but ugh:

;; adapted from
(defun expand-region-linewise ()
  (let ((start (region-beginning))
        (end (region-end)))
   (goto-char start)
   (set-mark (point))
   (goto-char end)
   (unless (bolp) (end-of-line))))

(defun markdown-copy ()
       (narrow-to-region (region-beginning) (region-end))
       (goto-char (point-min))
       (replace-regexp "^" "    ")
       (clipboard-kill-ring-save (point-min) (point-max))
       (goto-char (point-min))
       (replace-regexp "^    " "")))))

Writing this involved a long journey through the Emacs documentation.

One difficulty was getting Emacs to play friendly with my fat-fingered region-marking. I don't always highlight from the beginning of the first line to the end of the last. That's why Vim's visual-line mode is awesome; the cursor can be anywhere on the line, it still selects the whole line. The handy function above (found on the Emacs wiki) takes care of that. I don't know how long it would've taken me to come up with that on my own.

Then it was a matter of rooting through millions of Emacs functions until I found the ones that move the point around and copy text to the clipboard. Along the way I discovered the wonders of "narrowing", which limits Emacs to work on some region of text, and all those macros to undo the messes I make while moving around.

Maybe I could've done this with an Emacs keyboard macro, and then called apply-macro-to-region-lines. And maybe I could use append-next-kill to build up the indented text one line at a time. But my efforts to do this or anything like it failed horribly.

In any case I thought it was an interesting comparison. Improvements to either version are welcome.

EDIT: This works too (thanks Holger Durer):

(defun markdown-copy ()
    (indent-rigidly (region-beginning) (region-end) 4)
    (clipboard-kill-ring-save (region-beginning) (region-end))
    (indent-rigidly (region-beginning) (region-end) -4)))
May 13, 2010 @ 9:21 AM PDT
Cateogory: Programming


Holger Durer
Quoth Holger Durer on May 13, 2010 @ 4:12 PM PDT

Wouldn't using indent-rigidly be much easier? It's bound to C-x TAB and I use it interactively all the time.

Dion Moult
Quoth Dion Moult on May 13, 2010 @ 4:59 PM PDT

The mapping is nice, but you could easily achieve the same with a simple insert. Do ctrl-v for column visual select, select the first column of the chunk of code you want to copy, press I (capital i), put your 4 spaces in front, ESC, and move anywhere - the chunk has been indented. Now just copy it as usual with visual select, then when you're done just undo a couple times to get it back to what it was like before.

Arne Skjærholt
Quoth Arne Skjærholt on May 13, 2010 @ 9:10 PM PDT

How about >ap in normal mode? That will indent a paragraph for you, no matter where in it you are. If you have more than one paragraph 2>ap and so on.

Assuming your shiftwidth is 4, of course.

Quoth Leonel on May 13, 2010 @ 10:22 PM PDT

Hey ! Comparing vim's visual-mode with the emacs documentation is not fair.

What's more fair would be to compare it with emacs' rectangle mode.

C-a     ;; beginning of line
C-spc   ;; mark
C-n     ;; select some lines down
C-x r t ;; start rectangle
spc spc ;; two spaces
<return>;; return

Your selected lines will be indented with two spaces. There ! I just saved you from a trip of 45 more minutes to the emacs docs.

I agree with you that Vim wins this one, hands down. But at least I could give you one more hint on Emacs

Quoth Steve on May 13, 2010 @ 10:29 PM PDT

A nice way to do this is with CUA mode's rectangular selections: with cua-selection-mode enabled, just C-RET at the beginning of a line, then UP/DOWN to select lines, and finally type four spaces. Press C-RET or C-g to finish.

Quoth Brian on May 14, 2010 @ 1:17 AM PDT

CUA's rectangle mode is how I used to do it in Emacs (and visual block mode is how I used to do it in Vim). But it's tedious to do 6 or 7 times in a row. That's why I want to script it. Is there an easier way to script Emacs to do rectangle mode manipulations via a function call?

@Holger Durer: Thanks, I didn't know about that function. That works well.

Quoth Bryan on May 14, 2010 @ 9:14 AM PDT

Building on Leonel's example, for good measure, wrap those commands in a macro on the macro kill ring:

...                  ;;select region
F3                   ;;begin macro
C-a C-x C-x C-a      ;;point and mark to beginning of their lines
C-x r t C-u SPC RET  ;;indent as desired
M-w                  ;;copy to clipboard (or M-x clipboard-kill-ring-save)
C-/                  ;;undo indenting
F4                   ;;finish and save macro

Now hitting F4 will repeat this macro as often as you like :). These macros are super easy to hack together when you're too lazy to write a function. (C-x C-k C-h for more macro commands.) You can assign it to a key sequence with Elisp too; for example, a hotkey to "Alt-tab" buffers would be:

(global-set-key (kbd "C-x j") (kbd "C-x b RET"))