51 Posts Tagged 'Vim'
Vim :ruby and :rubydo scope
Note to self. In old Vim (tested in 7.2.320), I could do this:
:ruby x='foo'
:rubydo $_=x
Now every line in the file says foo. But in Vim 7.3 I get an error:
NameError: undefined local variable or method `x' for main:Object
The scoping rules for Ruby in Vim must have changed somewhere along the line. I was abusing this feature to do some handy things, so this is sad.
A workaround is to use global variables in Ruby instead. So this still works:
:ruby $x='foo'
:rubydo $_=$x
Phew.
Emacs undo trees
I've said it before: undo in Emacs is horrible. On the other hand, undo in Vim is awesome.
But this is true no longer. Now there are undo trees for Emacs! Yes, this news is so important I had to italicize and bold it. It's like Emacs has been punching me in the face for years, and today I got it to stop. I never thought I'd see the day.
And it works great too. You can even view the tree visually and navigate it with the cursor keys, which is a step up on what Vim offers out of the box.

In other news, Vim 7.3 is out and it now has persistent undo across reloads. It's like an arms race, and gleeful hackers reap the benefits.
Vim and plaintext data files
I use Vim to work with plaintext datasets. Here are some habits and code snippets I've picked up which make data files a bit easier to deal with.
Vim regex - remove kind-of-matching lines
I have a file where every line starts with a number (followed by whitespace and a bunch of other stuff). Every number appears on either one or two lines, and if two, the second line always has a b after the number.
I need to delete every line for which there's a corresponding b line. But if there's no corresponding b line I want to leave the original line there.
Before:
123 foo bar
456 blarg
789 quux
123b foo baz
789b quux blurble
After:
123b foo baz
456 blarg
789b quux blurble
Except in my real file, I have a thousand lines and it'd take a year to do by hand. Vim to the rescue:
:sort
:%s/^\v((\d+).*\n)(\2b.*)/\3/
And that is why Vim is awesome. Can you think of a shorter way to do this, in Vim or Emacs?
Emacs: Yank lines as lines
One thing nice about Vim is manipulating whole lines at a time. dd deletes a line (including trailing newline), regardless of where the cursor is on the line. Then, p puts that line (with its newline) as a new line after the current line, and P puts it above the current line, again regardless of where your cursor is at the moment. (It also jumps the cursor to the beginning of the text you just inserted, which is nice.)
Emacs has kill-whole-line (C-S-Backspace) which is like Vim's dd. But I didn't find an equivalent of p and P. So here's my version:
(defun yank-with-newline ()
"Yank, appending a newline if the yanked text doesn't end with one."
(yank)
(when (not (string-match "\n$" (current-kill 0)))
(newline-and-indent)))
(defun yank-as-line-above ()
"Yank text as a new line above the current line.
Also moves point to the beginning of the text you just yanked."
(interactive)
(let ((lnum (line-number-at-pos (point))))
(beginning-of-line)
(yank-with-newline)
(goto-line lnum)))
(defun yank-as-line-below ()
"Yank text as a new line below the current line.
Also moves point to the beginning of the text you just yanked."
(interactive)
(let* ((lnum (line-number-at-pos (point)))
(lnum (if (eobp) lnum (1+ lnum))))
(if (and (eobp) (not (bolp)))
(newline-and-indent)
(forward-line 1))
(yank-with-newline)
(goto-line lnum)))
(global-set-key "\M-P" 'yank-as-line-above)
(global-set-key "\M-p" 'yank-as-line-below)
Just one more step along the path to Vimmify my Emacs setup. Emacs has some weird edge cases because you can move the cursor one "line" past the last real line in the file. But I think I worked out something comfortable for myself.
PS: I've written about this before, but if you use C-S-Backspace a lot in Emacs on Linux, I highly recommend putting this into your X11 config:
Option "DontZap" "True"
It's really easy to mix up C-S-Backspace and C-M-Backspace (the latter of which kills your X server). It's not fun to mix those up. Not fun at all.
PPS: This thread on Stack Overflow has some Emacs equivalents of Vim's o and O which are pretty nice too.
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 way.it 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 http://www.emacswiki.org/emacs/.emacs-ChristianRovner.el
(defun expand-region-linewise ()
(interactive)
(let ((start (region-beginning))
(end (region-end)))
(goto-char start)
(beginning-of-line)
(set-mark (point))
(goto-char end)
(unless (bolp) (end-of-line))))
(defun markdown-copy ()
(interactive)
(save-window-excursion
(save-excursion
(save-restriction
(expand-region-linewise)
(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 ()
(interactive)
(save-excursion
(expand-region-linewise)
(indent-rigidly (region-beginning) (region-end) 4)
(clipboard-kill-ring-save (region-beginning) (region-end))
(indent-rigidly (region-beginning) (region-end) -4)))
Review: Hacking Vim 7.2
Vim is an open-source text editor with a power and flexibility matched only by the steepness of its learning curve. As the author of this book states, "Vim Can Do Everything"; but configuring it to do so is sometimes daunting. Hacking Vim 7.2 aims to help the average Vimmer get the most out of customizing Vim, for fun and productivity.
searchpairpos
How did I live this long without knowing about searchpairpos() in Vim? I hate when I write a clumsy, slow reimplementation of something that already exists as a standard function.
The only bad thing about Vim and Emacs both is that the feature list is about a mile and a half long (and that's a bad thing only in the sense of being an overwhelming amount of good things).
I have read almost the entire Vim manual at this point but there are corners that remain unexplored, and sometimes they contain treasure. One thing I love doing is answering Vim questions on Stack Overflow because most of the time I don't know the answer right off the bat, and so looking it up or figuring it out teaches me something new.
Emacs is another story... Emacs remains a mystery to me in many ways, in spite of having used it for about a year now. I very much plan to read the whole Emacs manual. I've already read parts of it but I seem to have barely made a dent. There are things I know should be simple to do or that there are already built-in options for, but I don't know how to do them.
- How do I kill a word and also kill the whitespace immediately after it so it yanks properly later?
- When I
kill-whole-lineand paste that line elesewhere, I lose a newline and screw up indenting. Sometimes it works how I expect and sometimes it doesn't. - There are so many things I can do in Vim but can't in Emacs... marks, multiple registers, abbreviations, sensibly configured per-filetype indentation.
etc. etc. I know there are ways to do these things once I have time to just sit down and read the darned manual. And learn elisp's syntax and semantics (which can't be harder than learning Vim script). My ~/.vimrc is currently twice as long as my ~/.emacs, which says a lot.
On a related note, I'm in the process of putting my Vim and Emacs configs on github.
Vim and Emacs modelines
Both Vim and Emacs have "windows", resizeable panes inside the main app "frame". Windows are a very useful feature (aside from the ill-chosen name; should've been "panes" or something). I find windows superior to tabs in my opinion; you can easily view more than one file at a time, view two files side-by-side for comparison, and such. Both Vim and Emacs use the border under the window to show helpful information like the filename and editing mode such; this is called the "status line" or "modeline". Again, very handy.
How do you set a nice custom modeline in Vim? Here's my config from my .vimrc:
set statusline=%f\ %2*%m\ %1*%h%r%=[%{&encoding}\ %{&fileformat}\ %{strlen(&ft)?&ft:'none'}\ %{getfperm(@%)}]\ 0x%B\ %12.(%c:%l/%L%)
Yikes! What a mess. It's just a big string with a bunch of special codes and literal characters all mixed up. If you do :h 'statusline' you can read about all the options. That string is ugly though.
Or so I thought, until I witnessed the trainwreck that is Emacs. The documentation for Emacs' modeline comprises a small encyclopedia. The configuration itself is spread across a couple dozen configuration variables. This is the default value for just one of them:
(#("%[" 0 2
(help-echo "Recursive edit, type C-M-c to get out"))
#("(" 0 1
(help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current window occupy the whole frame\nmouse-3: Remove current window from display"))
(:propertize
("" mode-name)
help-echo "Major mode\nmouse-1: Display major mode menu\nmouse-2: Show help for major mode\nmouse-3: Toggle minor modes" mouse-face mode-line-highlight local-map
(keymap
(mode-line keymap
(down-mouse-3 keymap
(abbrev-mode menu-item "Abbrev (Abbrev)" abbrev-mode :help "Automatically expand abbreviations" :button
(:toggle . abbrev-mode))
(auto-fill-mode menu-item "Auto fill (Fill)" auto-fill-mode :help "Automatically insert new lines" :button
(:toggle . auto-fill-function))
(auto-revert-mode menu-item "Auto revert (ARev)" auto-revert-mode :help "Revert the buffer when the file on disk changes" :button
(:toggle bound-and-true-p auto-revert-mode))
(auto-revert-tail-mode menu-item "Auto revert tail (Tail)" auto-revert-tail-mode :help "Revert the tail of the buffer when buffer grows" :enable
(buffer-file-name)
:button
(:toggle bound-and-true-p auto-revert-tail-mode))
(flyspell-mode menu-item "Flyspell (Fly)" flyspell-mode :help "Spell checking on the fly" :button
(:toggle bound-and-true-p flyspell-mode))
(font-lock-mode menu-item "Font Lock" font-lock-mode :help "Syntax coloring" :button
(:toggle . font-lock-mode))
(glasses-mode menu-item "Glasses (o^o)" glasses-mode :help "Insert virtual separators to make long identifiers easy to read" :button
(:toggle bound-and-true-p glasses-mode))
(hide-ifdef-mode menu-item "Hide ifdef (Ifdef)" hide-ifdef-mode :help "Show/Hide code within #ifdef constructs" :button
(:toggle bound-and-true-p hide-ifdef-mode))
(highlight-changes-mode menu-item "Highlight changes (Chg)" highlight-changes-mode :help "Show changes in the buffer in a distinctive color" :button
(:toggle bound-and-true-p highlight-changes-mode))
(outline-minor-mode menu-item "Outline (Outl)" outline-minor-mode :help "" :button
(:toggle bound-and-true-p outline-minor-mode))
(overwrite-mode menu-item "Overwrite (Ovwrt)" overwrite-mode :help "Overwrite mode: typed characters replace existing text" :button
(:toggle . overwrite-mode))
"Minor Modes")
(mouse-2 . describe-mode)
(down-mouse-1 menu-item "Menu Bar" ignore :filter
(lambda
(_)
(mouse-menu-major-mode-map))))))
("" mode-line-process)
(:propertize
("" minor-mode-alist)
mouse-face mode-line-highlight help-echo "Minor mode\nmouse-1: Display minor mode menu\nmouse-2: Show help for minor mode\nmouse-3: Toggle minor modes" local-map
(keymap
(header-line keymap
(down-mouse-3 keymap
(abbrev-mode menu-item "Abbrev (Abbrev)" abbrev-mode :help "Automatically expand abbreviations" :button
(:toggle . abbrev-mode))
(auto-fill-mode menu-item "Auto fill (Fill)" auto-fill-mode :help "Automatically insert new lines" :button
(:toggle . auto-fill-function))
(auto-revert-mode menu-item "Auto revert (ARev)" auto-revert-mode :help "Revert the buffer when the file on disk changes" :button
(:toggle bound-and-true-p auto-revert-mode))
(auto-revert-tail-mode menu-item "Auto revert tail (Tail)" auto-revert-tail-mode :help "Revert the tail of the buffer when buffer grows" :enable
(buffer-file-name)
:button
(:toggle bound-and-true-p auto-revert-tail-mode))
(flyspell-mode menu-item "Flyspell (Fly)" flyspell-mode :help "Spell checking on the fly" :button
(:toggle bound-and-true-p flyspell-mode))
(font-lock-mode menu-item "Font Lock" font-lock-mode :help "Syntax coloring" :button
(:toggle . font-lock-mode))
(glasses-mode menu-item "Glasses (o^o)" glasses-mode :help "Insert virtual separators to make long identifiers easy to read" :button
(:toggle bound-and-true-p glasses-mode))
(hide-ifdef-mode menu-item "Hide ifdef (Ifdef)" hide-ifdef-mode :help "Show/Hide code within #ifdef constructs" :button
(:toggle bound-and-true-p hide-ifdef-mode))
(highlight-changes-mode menu-item "Highlight changes (Chg)" highlight-changes-mode :help "Show changes in the buffer in a distinctive color" :button
(:toggle bound-and-true-p highlight-changes-mode))
(outline-minor-mode menu-item "Outline (Outl)" outline-minor-mode :help "" :button
(:toggle bound-and-true-p outline-minor-mode))
(overwrite-mode menu-item "Overwrite (Ovwrt)" overwrite-mode :help "Overwrite mode: typed characters replace existing text" :button
(:toggle . overwrite-mode))
"Minor Modes"))
(mode-line keymap
(down-mouse-3 keymap
(abbrev-mode menu-item "Abbrev (Abbrev)" abbrev-mode :help "Automatically expand abbreviations" :button
(:toggle . abbrev-mode))
(auto-fill-mode menu-item "Auto fill (Fill)" auto-fill-mode :help "Automatically insert new lines" :button
(:toggle . auto-fill-function))
(auto-revert-mode menu-item "Auto revert (ARev)" auto-revert-mode :help "Revert the buffer when the file on disk changes" :button
(:toggle bound-and-true-p auto-revert-mode))
(auto-revert-tail-mode menu-item "Auto revert tail (Tail)" auto-revert-tail-mode :help "Revert the tail of the buffer when buffer grows" :enable
(buffer-file-name)
:button
(:toggle bound-and-true-p auto-revert-tail-mode))
(flyspell-mode menu-item "Flyspell (Fly)" flyspell-mode :help "Spell checking on the fly" :button
(:toggle bound-and-true-p flyspell-mode))
(font-lock-mode menu-item "Font Lock" font-lock-mode :help "Syntax coloring" :button
(:toggle . font-lock-mode))
(glasses-mode menu-item "Glasses (o^o)" glasses-mode :help "Insert virtual separators to make long identifiers easy to read" :button
(:toggle bound-and-true-p glasses-mode))
(hide-ifdef-mode menu-item "Hide ifdef (Ifdef)" hide-ifdef-mode :help "Show/Hide code within #ifdef constructs" :button
(:toggle bound-and-true-p hide-ifdef-mode))
(highlight-changes-mode menu-item "Highlight changes (Chg)" highlight-changes-mode :help "Show changes in the buffer in a distinctive color" :button
(:toggle bound-and-true-p highlight-changes-mode))
(outline-minor-mode menu-item "Outline (Outl)" outline-minor-mode :help "" :button
(:toggle bound-and-true-p outline-minor-mode))
(overwrite-mode menu-item "Overwrite (Ovwrt)" overwrite-mode :help "Overwrite mode: typed characters replace existing text" :button
(:toggle . overwrite-mode))
"Minor Modes")
(mouse-2 . mode-line-minor-mode-help)
(down-mouse-1 . mouse-minor-mode-menu))))
#("%n" 0 2
(local-map
(keymap
(mode-line keymap
(mouse-2 . mode-line-widen)))
mouse-face mode-line-highlight help-echo "mouse-2: Remove narrowing from the current buffer"))
#(")" 0 1
(help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current window occupy the whole frame\nmouse-3: Remove current window from display"))
#("%]" 0 2
(help-echo "Recursive edit, type C-M-c to get out"))
#("--" 0 2
(help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current window occupy the whole frame\nmouse-3: Remove current window from display")))
What the hell?
Instead of a string, it's an s-expression, which is better, right? Well no, it's still just a big construct with arbitrary meanings assigned to its contents. Lists mean one thing, strings mean another thing (and those strings, like Vim's, can contain special escape sequences). Symbols mean something else, symbols that are keywords mean something else, numbers mean something else, and so on.
To decipher this I had to learn this mini-language. And also learn about "text properties" and a bunch of elisp stuff. It also required knowledge about a bunch of minor modes and how they tie into the modeline, all of which is essentially a bowl of spaghetti code. And keymaps, and maps to control mouse click events and such. Eventually I figured out that most of that crap is controlling tooltip text.
If you do M-x customize-apropos in Emacs and search for "mode-line", you'll get a helpful list of all of the configuration values and their values. (The default values contain literal tab characters, which you can't even type into the customize text fields without C-qing, because tab jumps you between fields. Ughhhhhhh.)
I gave up even trying to get Emacs to have all the helpful information my Vim modeline has. Even deleting the default crap to pare this down to something readable took some effort. This what I ended up with:
(mode-line-format (quote ("%e--[" mode-line-buffer-identification "]" (vc-mode vc-mode) " " mode-line-modes global-mode-string " %-")))
(mode-line-in-non-selected-windows t)
(mode-line-modes (quote ("%[" "(" (:propertize ("" mode-name)) ("" mode-line-process) (:propertize ("" minor-mode-alist)) "%n" ")" "%]")))
(mode-line ((((class color) (min-colors 88)) (:background "#333333" :foreground "#bcbcbc" :box (:line-width -1 :color "#333333")))))
(mode-line-highlight ((((class color) (min-colors 88)) nil)))
(mode-line-inactive ((default (:inherit mode-line)) (((class color) (min-colors 88) (background dark)) (:foreground "#8b8b8b" :weight light))))
What does that mean? Don't ask me, I can no longer read it. If customize hadn't produced a lot of that for me, I probably wouldn't have managed. My favorite part is the four-deep nested list of lists of lists of lists for the colors.
Verdict?
Vim's modeline is less powerful than Emacs. But who cares if you can't even read it to edit it? Most of Emacs' modeline features are annoying. (My motivation for editing this to begin with was to turn off all the mouse buttons and mini-menus and crap.)
Vim's status line is exactly configurable enough. I don't want to build a small GUI app in my modeline. I want it to show certain bits of information about the buffer, that's it. Vim does this.
- Vim script is less elegant than elisp. Or is it? Vim's modeline is a custom DSL for formatting modelines. It's hard to think of anything more concise. Concision is a very good thing. Emacs' version is more general, at the expense of horrid verbosity and unreadability.
Vim's modeline is a string, which means you either write it literally, or you construct it by concatenating lots of other strings. This is a faux pas, right? It's like using
evalin Ruby or Perl. It's fragile and error-prone. Emacs uses a Lisp, with its macros and quoted lists and code is data and so on.But who cares? In this case, a simple string is powerful enough. I don't need a whole Turing-complete programming language to configure a modeline. It's massive overkill and you pay a price for it. The minute a human being is supposed to be keeping track that the second element in the 5-deep nested list means "x", something has gone horribly wrong.
Vim wins this round.
Vim regexes are awesome
Two years ago I wrote about how Vim's regexes were no fun compared to :perldo and :rubydo. Turns out I was wrong, it was just a matter of not being used to them.
Vim's regexes are very good. They have all of the good features of Perl/Ruby regexes, plus some extra features that don't make sense outside of a text editor, but are nonetheless very helpful in Vim.
Here are a few of the neat things you can do.
Very magic
Vim regexes are inconsistent when it comes to what needs to be backslash-escaped and what doesn't, which is the one bad thing. But Vim lets you put \v to make everything suddenly consistent: everything except letters, numbers and underscores becomes "special" unless backslash-escaped.
Without \v:
:%s/^\%(foo\)\{1,3}\(.\+\)bar$/\1/
With \v:
:%s/\v^%(foo){1,3}(.+)bar$/\1/
Far easier to read. Along with \c to turn on and off case sensitivity, these are good options to make a habit of prepending to regexes when needed. It eventually becomes second-nature. See also :h /\v
Spanning newlines
One thing that :perldo and :rubydo can't do is span newlines; you can't combine two lines and you can't break one line into two.
But Vim's regexes can span newlines if you use \_. instead of .. I find this to be a lot more aesthetically pleasing than Perl's horrible s and m modifiers tacked onto the end of a regex. e.g. this strips <body> tags from a text document.
:%s@<body>\v(\_.+)\V</body>@\1@
(Note: in real life, never use a regex to parse HTML or XML. Down that path lies madness. The above is OK because I'd expect only one <body> tag to appear in any document.)
(Note^2: being able to turn on and off magic in the middle of a regex is awfully helpful.)
(Note^4: You can use arbitrary delimiters like @ for the regex, which is useful if your pattern includes literal /'s.)
See also :h \_.
\zs
Vim lets you demand that some text match, but ignore that text when it comes to the substitution part. This is handy for certain specific kinds of regexes. Normally if you want to match some text and then leave it alone in the substitution, you have to capture it and then put it back manually; \zs lets you avoid this.
Say you want to chop some text off the end of a line, but leave the rest of the line alone. Normally you'd have to do this:
:%s/\v^(foobar)(baz)/\1/
to put the foobar back. Of course you can also use a zero-width lookbehind assertion:
:%s/\v(^foobar)@<=baz//
But that's even more line-noise. This is the easiest way:
:%s/^foobar\zsbaz//
See :h /\zs. (And :h /\@<= if you're so inclined.)
Expressions
Using \=, you can put arbitrary expressions on the right side of a regex substitution. For example say you have this text:
~/foo ~/bar
If you do this:
:%s/\v(\S+)/\=expand(submatch(1))/g
You end up with:
/home/user/foo /home/user/bar
Because you can also call your own user-defined functions in the expression part, this can end up being pretty powerful. For example it can be used to insert incrementing numbers into arbitrary places in your text. See :h sub-replace-\=.
And so on
Read :h regexp if you haven't already. Tons of other features in there that can make your life easy if you manage to internalize them. It is difficult to get used to Vim's funky syntax if you're very familiar with Perl/Ruby-style regexes, but I think it's worth it. Only took me two years! (OK, more like a couple days of concerted effort after a year-and-a-half delay.)
