Feeds:
Posts
Comments

Archive for May, 2009

Vim as a List Picker

I think I already established that Vim makes an excellent pager. Let me take it one step further: Vim is a customizable, programmable pager. (!)

There are plenty of cases where you want to pick one (1) thing out of a list. Vim can easily be made into a list picker.

A few examples

  • pick a deep directory under the current directory (pick from find . -type d)
  • pick a GNU screen session out of many (pick from screen -ls)
  • pick a process to kill (pick from ps)

There are basically 2 components to these examples:

  • what command will generate the list
  • what command to run on the selection

The Code

Here’s the PickerMode plugin (put in ~/.vim/plugin/picker.vim)


function PickerMode()
  set cursorline
  nmap <buffer> <CR>    V:w! ~/.picked<CR>:qa!<CR>
endfunction
command -nargs=0 PickerMode :call PickerMode()

Comments:

  • cursorline highlight the line the cursor is on
  • return saves the current line to ~/.picked

Here’s the bash code to invoke vim and execute a command on the selection:


# start vim in PAGER mode, with PickerMode plugin
function vim_picker() {
  vim -c "PickerMode" -R -
}

# 1st parameter is command to generate a list
# 2nd parameter is command to run on selection
# 3rd (optional) parameter is DIRECT selection, bypassing VIM
function pick_with_vim() {
  if [ -e ~/.picked ]; then
    rm ~/.picked
  fi

  if [ -n "$3" ]; then
    eval "$1" | sed -n $3p > ~/.picked
  else
    eval "$1" | vim_picker
  fi

  if [ -e ~/.picked ]; then
    $2 "`cat ~/.picked`"
  fi
}

Comments:

  • the selection is written to a file called ~/.picked
  • the existence of the file ~/.picked proves that you selected something
  • functional programming in bash (!)

Using pick_with_vim

pick a deep directory under the current directory:


# pick from a list of directories (recursive) and cd into it
function c() {
  pick_with_vim "find . -type d" "cd"
}

how to pick from screen:


function screen_r_x() {
  screen -r $1 || screen -x $1
}

function sc() {
  pick_with_vim "screen -ls | awk ‘/^\t/ {print \$1}’" "screen_r_x"
}

Here’s a much simpler rewrite of “go” (my directory bookmark miniapp)


# pick from directories in $HOME/.gorc and cd into it
function go() {
  if [ ! -f $HOME/.gorc ]; then
    echo "$HOME/.gorc does not exist…"
    return 1
  fi

  pick_with_vim "cat $HOME/.gorc" "cd" $1
}

What now?

This code is available as part of my dotfiles on github. (though it is mixed with the rest)

Read Full Post »

Using vim as a pager

I’ve talked casually about using Vim as a pager before. However, I’m still surprised to see how many people use Vim regularly and don’t know about this feature.

Here’s a quote straight from vim --help

vim [arguments] -               read text from stdin

Admittedly, it’s easy to overlook the hyphen in the explanation.

vim hyphen

Why Vim as a Pager?

If you’re using Vim already, there’s nothing else to install.

If you’re using Vim already, it’s already configured the way you like it.

More importanly, Vim detects the kind of file it is being piped and turns on the appropriate syntax highlighting. Why page in black and white? In this case, “less” is definitely less!

Improving the experience

As a pager, you want to use Vim in read-only mode.

some command | vim -R -

What the difference? Vim doesn’t ask you to save the file if you try to quit. Of course, you can still modify and write the file … the -R flag is just a more reasonable pager default.

PAGER variable and ANSI Escape Sequences

You probably don’t want to set the PAGER variable. Vim doesn’t understand ANSI escape sequences. As such, a command like “man vim | vim -R -” won’t show colors; it will show escape sequences.

vim and ansi

I haven’t found any quick and simple solution to make Vim show ANSI escape sequences, but it’s pretty easy to strip them out before passing the file to Vim:

man vim | col -b | vim -R -

I use less as PAGER. I use vim in explicit cases.

View

The view command gets installed at the same time as vim. It’s just a symlink to vim. Using view is exactly like typing vim -R.

There’s a certain aesthetic in:

some command | view -

But I find that typing vim -R - is easier on my finger’s muscle memory.

Read Full Post »

Git Diff with Vimdiff

What happens when you type git diff? As with all interesting questions, the answer is “it depends…”

Here’s one thing you want git to do:

Vimdiff!

Step 1: add this to your .gitconfig


[diff]
  external = git_diff_wrapper
[pager]
  diff =

Step 2: create a file named git_diff_wrapper, put it somewhere in your $PATH


#!/bin/sh

vimdiff "$2" "$5"

I still have access to the default git diff behavior with the --no-ext-diff flag. Here’s a function I put in my bash configuration files:


function git_diff() {
  git diff --no-ext-diff -w "$@" | vim -R -
}
  • --no-ext-diff : to prevent using vimdiff
  • -w : to ignore whitespace
  • -R : to start vim in read-only mode
  • - : to make vim act as a pager

When it comes to vimdiff, you can get started with this tutorial.

Read Full Post »

Code Tetris

“How did we get into this mess?” you exclaim looking at the code equivalent of:

tetris mess

There are a lot of reasons why projects end up looking like that. Beyond incompetence, there is the realization that you know more about a problem after you solve it.

Here’s a familiar scenario:

You extract an ugly part of a function. Out of the remaining code, you notice that it’s just a special case of a problem you have solved in a more general way elsewhere. A few tweaks later, you remove the special case, a whole bunch of code is gone.

The “Tetris” effect!

A good refactor feels the same way it does when you clear a couple of rows in Tetris. There are insights which allow you to provide the same (if not more) functionality in less code.

I remember reading Refactoring and being annoyed at the series of seemingly trivial changes that were being done to the code. However, one change at a time, the code was getting more and more manageable.

The same is true with Tetris: clearing the first row of block, you can start attacking the second and so forth. Hopefully you reach to bottom.

Which brings another interesting parallel with Tetris: if you don’t start simplifying things and cleaning up your mess, you end up with a big pile of unmanageable code. One block goes on top of another until you touch the top. Eventually, you have to start another game.

Compare that to incoming requirements which result in more features piling on top of each other. At some point, somebody starts talking about doing a rewrite. In effect, that would be like restarting the game. It’s always easier to throw blocks down an empty screen rather than fit them on top of an existing mess.

I’m not blaming anyone … there is a time and place for rewrites. Use judgment.

Can you also think of a time where a new requirement messed up the beauty of the model you had built. Something like this?
tetrishappens

The more I think about it, the more I like it.

Read Full Post »

Next page bookmarklet

Most useful websites use some form of pagination. However, link relations are not in widespread use.

I liked the explanation:

Regular links (<a href>) simply point to another page. Link relations are a way to explain why you’re pointing to another page. They finish the sentence “I’m pointing to this other page because…”

I know a few sites which, to my “View Source” surprise, make use of <a rel="next">.

I wrote a jQuery version under 5 minutes, but not all pages have jQuery. Adding jQuery to the current page is relatively simple (although not trivial: follow the evolution of the jQuerify bookmarklet here, here and finally, here) but the complexity of “importing” jQuery overtakes the even simpler task of finding an <a> tag with a rel attribute.

I also found a Prototype version which … wasn’t working … but …

I don’t need a JavaScript framework: I decided to use the browser DOM 3 XPath which would work on any browser I care about:

Anytime you are looking for a specific node or set of nodes buried inside of a document, consider using XPath to speed up the process in Firefox, Safari, Chrome, and Opera (Internet Explorer doesn’t support DOM 3 XPath).

Here’s the code:

location = document.evaluate(‘//*[translate(@rel,"NEXT","next")="next"]‘,
                             document,
                             null,
                             XPathResult.FIRST_ORDERED_NODE_TYPE,
                             null).singleNodeValue.href

A few notes:

  • the rel attribute may be attached to either a <link> or an <a> tag
  • I used translate to make the value of rel case-insensitive
  • there should only be one rel="next" on the page, the script grabs the first one (a reasonable compromise)

Since I can’t seem to be able to embed a bookmarklet in WordPress, here’s a page where you can grab it.

Read Full Post »

Follow

Get every new post delivered to your Inbox.