Saturday, September 6, 2008

Vim pr0n: Creating named marks

A few days ago in #vim, someone asked if it is possible to create "named marks" with vim, i.e. marking a location with a name instead of a letter or number. The answer is no, but it was an interesting idea, so I turned down my pornography and wrote a script to do it.

When writing the code, I used prototype based OO — mainly to provide a more complex example for my previous raving.

Marks in vim


In vim you can mark a location in a file so you can quickly jump back there. A mark consists of a position (line/column) and an identifier (a single letter). Every buffer has its own set of lowercase marks (letters a–z) that exist only as long as the buffer exists. There is also a set of global marks using capital letters. These marks are persistent and there is only one set.

More info can be found at :help mark-motions.

Enter "named marks"


In the IRC chat, the guy wanted the same functionality as the uppercase marks, but with more descriptive names. Which is fair enough IMO since "A" doesn't really tell you anything about the position it jumps to.

So named marks are global, persistent marks with descriptive names.

The code


OMG check this out:

  1 "start the named mark prototype
  2 let s:NamedMark = {}
  3
  4 "the file the marks are stored in
  5 let s:NamedMark.Filename = expand('~/.namedMarks')
  6
  7 "constructor
  8 function! s:NamedMark.New(name, column, line, path)
  9   if a:name =~ ' '
 10     throw "IllegalNamedmarkNameError illegal name:" . a:name
 11   endif
 12
 13   let newNamedMark = copy(self)
 14   let newNamedMark.name = a:name
 15   let newNamedMark.column = a:column
 16   let newNamedMark.line = a:line
 17   let newNamedMark.path = a:path
 18   return newNamedMark
 19 endfunction
 20
 21 "lazy load and cache all named marks
 22 function! s:NamedMark.All()
 23   if !exists("s:NamedMark.AllMarks")
 24     let s:NamedMark.AllMarks = s:NamedMark.Read()
 25   endif
 26   return s:NamedMark.AllMarks
 27 endfunction
 28
 29 "create and add a new mark to the list
 30 function! s:NamedMark.Add(name, column, line, path)
 31
 32   try
 33     "if the mark already exists, just update it
 34     let mark = s:NamedMark.FindFor(a:name)
 35     let mark.column = a:column
 36     let mark.line = a:line
 37     let mark.path = a:path
 38
 39   catch /NamedMarkNotFoundError/
 40     let newMark = s:NamedMark.New(a:name, a:column, a:line, a:path)
 41     call add(s:NamedMark.All(), newMark)
 42
 43   finally
 44     call s:NamedMark.Write()
 45   endtry
 46 endfunction
 47
 48 "find the mark with the given name
 49 function! s:NamedMark.FindFor(name)
 50   for i in s:NamedMark.All()
 51     if i.name == a:name
 52       return i
 53     endif
 54   endfor
 55   throw "NamedMarkNotFoundError no mark found for name: \"".a:name.'"'
 56 endfunction
 57
 58 "get a list of all mark names
 59 function! s:NamedMark.Names()
 60   let names = []
 61   for i in s:NamedMark.All()
 62     call add(names, i.name)
 63   endfor
 64   return names
 65 endfunction
 66
 67 "delete this mark
 68 function! s:NamedMark.delete()
 69   call remove(s:NamedMark.All(), index(s:NamedMark.All(), self))
 70   call s:NamedMark.Write()
 71 endfunction
 72
 73 "go to this mark
 74 function! s:NamedMark.recall()
 75   exec "edit " . self.path
 76   call cursor(self.line, self.column)
 77 endfunction
 78
 79 "read the marks from the filesystem and return the list
 80 function! s:NamedMark.Read()
 81   let marks = []
 82   if filereadable(s:NamedMark.Filename)
 83     let lines = readfile(s:NamedMark.Filename)
 84     for i in lines
 85       let name   = substitute(i, '^\(.\{-}\) \d\{-} \d\{-} .*$', '\1', '')
 86       let column = substitute(i, '^.\{-} \(\d\{-}\) \d\{-} .*$', '\1', '')
 87       let line   = substitute(i, '^.\{-} \d\{-} \(\d\{-}\) .*$', '\1', '')
 88       let path   = substitute(i, '^.\{-} \d\{-} \d\{-} \(.*\)$', '\1', '')
 89
 90       let namedMark = s:NamedMark.New(name, column, line, path)
 91       call add(marks, namedMark)
 92     endfor
 93   endif
 94   return marks
 95 endfunction
 96
 97 "write all named marks to the filesystem
 98 function! s:NamedMark.Write()
 99   let lines = []
100   for i in s:NamedMark.All()
101     call add(lines, i.name .' '. i.column .' '. i.line .' '. i.path)
102   endfor
103   call writefile(lines, s:NamedMark.Filename)
104 endfunction
105
106 "NM command, adds a new named mark
107 command! -nargs=1
108   \ NM call s:NamedMark.Add('<args>', col("."), line("."), expand("%:p"))
109
110 "RM command, recalls a named mark
111 command! -nargs=1 -complete=customlist,s:CompleteNamedMarks
112   \ RM call s:NamedMark.FindFor('<args>').recall()
113
114 "DeleteNamedMark command
115 command! -nargs=1 -complete=customlist,s:CompleteNamedMarks
116   \ DeleteNamedMark call s:NamedMark.FindFor('<args>').delete()
117
118 "used by the above commands for cmd line completion
119 function! s:CompleteNamedMarks(A,L,P)
120   return filter(s:NamedMark.Names(), 'v:val =~ "^' . a:A . '"')
121 endfunction


lol, so... wtf does that do?


From a users perspective, if you were to shove this code in your vimrc, or in a plugin, it would provide three commands:

  • NM: create a new named mark, e.g. :NM main-function would create the main-function mark and bind it to the current file and cursor position. If that mark already existed, it would be overwritten

  • RM: recall a previous mark, e.g. :RM main-function would jump the cursor back to the same file and position where main-function was created

  • DeleteNamedMark: delete a named mark, e.g. :DeleteNamedMark main-function would remove main-function from the mark list.


From a programming perspective, the code defines one prototype object (lines 1–104), the three commands (lines 106–116), and a completion function for two of the commands (lines 118–121).

If I had to jack off to one part of this code, it would be the prototype object. It contains:

  • Seven class methods and two class variables which are used to maintain the list of named marks, including reading/writing to the filesystem.

  • Two instance methods for deleting and recalling marks.

  • Four instance variables specifying the position and name of the mark.


If I was actually going to turn this into a plugin, I would want to flesh it out a bit and add, for example, better error handling and a command similar to the existing :marks command to list named marks.

Wednesday, September 3, 2008

Vim pr0n: Implementing prototype based objects

When vim 7 came along with support for lists (arrays) and dictionaries (hashes), nerds were delirious with joy. On the day it was released, geeks everywhere ripped off their pants and cartwheeled up and down their office aisles, their junk flapping freely in the air con. It was a great day to be alive.

It was also a great day to be blind.

One of the cool things about having dictionaries is that we can now implement prototype based OO. I've been playing around with this in vim script for a while now. If you are interested in doing any sort of OO programming with vim script then read this raving.

"Prototype Based OO" wtf yo?!


There are two types of object oriented programming: class based OO and prototype based OO.

In class based OO (e.g. java, c++, ruby etc) you write the blueprints for your objects then use those blueprints to create working object instances.

In prototype based OO (e.g. javascript, lua), you create a fully functional working object (i.e. a "prototype") and then clone that object. So the prototype object effectively serves as the class, while the clones of that prototype serve as the instances.

Getting started — methods, properties and constructors


In vim script the prototype object is defined as a dictionary, where the dictionary keys map to values (properties) and function references (methods).

Check this example out:


 1 "start the prototype
 2 let AK47 = {}
 3
 4 "the constructor
 5 function! AK47.New(ammo)
 6     let newAK47 = copy(self)
 7     let newAK47.ammo = a:ammo
 8     return newAK47
 9 endfunction
10
11 "an instance method
12 function! AK47.fire()
13     if self.ammo > 0
14         echo "BANG!"
15         let self.ammo -= 1
16     else
17         echo "click"
18     endif
19 endfunction
20
21 "at runtime we can do this:
22 let a = AK47.New(2)
23 echo a.ammo   " => 2
24 call a.fire() " => BANG!
25 call a.fire() " => BANG!
26 call a.fire() " => click


In this example, our prototype starts as an empty dictionary.

The first thing we add to the prototype is a constructor. There are a few ways you could do this, but I like to do it with a method called New(). It clones the prototype object (i.e. self), assigns the ammo instance property and returns the new object.

Next we add an instance method called fire() which "shoots" a bullet if there's any ammo left.

Notice that self is a reference to the current object. It is mandatory to use this reference when accessing members of the current object. In the above example, if self.ammo > 0 succeeds, whereas if ammo > 0 would result in an Undefined variable error.

Private methods and properties


There is no way to make methods or properties private. However, if you make up some conventions for yourself, then you can make the intent of your code clear (even though vim wont actually enforce it). I have the convention that any method or property starting with an underscore should be treated as private.


So, for example, I could make the ammo instance property "private" like this:


 1 "start the prototype
 2 let AK47 = {}
 3
 4 "the constructor
 5 function! AK47.New(ammo)
 6     let newAK47 = copy(self)
 7     let newAK47._ammo = a:ammo
 8     return newAK47
 9 endfunction
10
11 "an instance method
12 function! AK47.fire()
13     if self._ammo > 0
14         echo "BANG!"
15         let self._ammo -= 1
16     else
17         echo "click"
18     endif
19 endfunction


Class methods and class properties


Officially in vim script, there are no such things as class methods or class variables, but you can still implement them.

If a method doesn't access any instance variables or instance methods then, practically speaking, it's a class method. The New() methods above are examples.

If a variable is defined and accessed on the prototype object, then its a class variable.

I have some conventions I've been using:

  • I like to start all class members with a capital letter and all instance members with a lower case letter.

  • If I'm calling a class method from inside an instance method then I like to use the prototype name as the target object, i.e. TheClass.TheClassMethod() rather than self.TheClassMethod(). Similarly for class variables.


Inheritance


Prototyping with Vim script is an example of "Pure prototyping", or "Concatenative prototyping". Each object stands alone and has no links to its parent prototype. If you want to create a subclass, you must clone the parent prototype then add the new features to the clone. You can see why it's called called "Concatenative", since all the prototypes are joined together as you go down the inheritance tree.

This is in contrast to, say, javascript where, instead of cloning the parent, every object has a magic prototype property which points to the parent. The interpreter then uses this link for method dispatching, i.e. it searches back up the inheritance tree for the method definition.

Anyway, let's look at an example:


 1 "clone the AK47 prototype
 2 let AK47GL = copy(AK47)
 3
 4 "override the old constructor
 5 function! AK47GL.New(ammo, grenades)
 6     let newAK47GL = copy(self)
 7     let newAK47GL.ammo = a:ammo
 8     let newAK47GL.grenades = a:grenades
 9     return newAK47GL
10 endfunction
11
12 "define a new instance method
13 function! AK47GL.fireGL()
14     if self.grenades > 0
15         echo "OMG BOOOOOM!"
16         let self.grenades -= 1
17     else
18         echo "click"
19     endif
20 endfunction
21
22 "at runtime we can do this:
23 let a = AK47GL.New(2,1)
24 echo a.ammo     " => 2
25 echo a.grenades " => 1
26 call a.fire()   " => BANG!
27 call a.fire()   " => BANG!
28 call a.fire()   " => click
29 call a.fireGL() " => OMG BOOOOOM!
30 call a.fireGL() " => click


Here we define a new type of AK47 called AK47GL (an AK with an under-slung grenade launcher).

First we clone the AK47 prototype.

Then we replace the New method with one that accepts a grenade ammo counter. This is how we override methods. If we were really hardcore, we could rename the old New() method to something like _AK47_New() so that it will still be available to us, but I haven't bothered here.

Lastly, we define a new method called fireGL().

One thing to note about inheritance is that the subclass must be defined after the superclass in the code. Otherwise the initial copy() will fail.

Final ranting


We've seen that, using dictionaries we can create prototype objects with methods and properties. We can implement class methods and class variables. We can't implement private or protected members, but we can at least indicate our intentions with naming conventions. We've also seen how to implement inheritance.

I realise that there's a lot of stuff that I've left out, and if you need to know something I haven't covered here then the best source of information is other prototyping languages. Take a look at how people do it with javascript (the prototype library could be useful) or lua. Also, there's a whole list of prototype based OO languages here that you can steal ideas from.

Saturday, August 30, 2008

Vim pr0n: Making statuslines that own

Many of vim's default options smoke boner hard enough to turn a man inside out. One such option is 'statusline'. By default this option is blank, causing the statusline to display only the tail of the filename for the current buffer. Most people like much more detailed information to be displayed. For example, check out my current statusline:


set statusline=%t[%{strlen(&fenc)?&fenc:'none'},%{&ff}]%h%m%r%y%=%c,%l/%L\ %P


If you don't have a statusline that owns, then read this raving right now.

Commented form


Before we get into actually putting information on your statusline, another way to express your statusline is like this:

 1 set statusline=%t       "tail of the filename
 2 set statusline+=[%{strlen(&fenc)?&fenc:'none'}, "file encoding
 3 set statusline+=%{&ff}] "file format
 4 set statusline+=%h      "help file flag
 5 set statusline+=%m      "modified flag
 6 set statusline+=%r      "read only flag
 7 set statusline+=%y      "filetype
 8 set statusline+=%=      "left/right separator
 9 set statusline+=%c,     "cursor column
10 set statusline+=%l/%L   "cursor line/total lines
11 set statusline+=\ %P    "percent through file


This format is way more useful if you are experimenting with all the statusline flags etc to find a good setting. You may want to use it all the time anyway as it is more readable and maintainable.

Statusline flags


The easiest way to put information on your statusline is with the built in flags. For example, %m displays a [+] if the current buffer is modified, while %F displays the full path to the current file. A full list of these flags can be found at :help 'statusline, there's tons of them so be sure to have a good read through.

Using expressions and functions calls


You are not restricted to using just these flags though. You can put anything you want on your statusline! Any expression enclosed by %{...} will be evaluated and displayed. For example: %{strlen(&ft)?&ft:'ZOMG'} will display the current filetype or "ZOMG" if none is set.

If you want to display something that requires any complex logic, you can extract it out into a function. For example:

 1 set statusline=......%{FileSize()}.....
 2 function! FileSize()
 3     let bytes = getfsize(expand("%:p"))
 4     if bytes <= 0
 5         return ""
 6     endif
 7     if bytes < 1024
 8         return bytes
 9     else
10         return (bytes / 1024) . "K"
11     endif
12 endfunction


In this example a function is used to display the size of the current file (in kilobytes if appropriate).

Controlling formatting and alignment


You can control the width and alignment of each item on the statusline as you specify it. The full syntax for specifying an item is:
%-0{minwid}.{maxwid}{item}

All of the parameters are optional except the {item} itself. A full description of the syntax is at :help 'statusline but basically:

  • The - forces the item to be left aligned, omitting it makes it right aligned.

  • The 0 forces numeric values to be left padded with zeros.

  • Then we have some width arguments and the item itself.


Examples:
%-.100F — displays the full filename of the current buffer, left aligned, 100 characters max.

%03.b — displays the ascii value of the current byte, at least 3 characters wide and left padded with zeros.

%20.20{'tabstop='.&tabstop} — displays "tabstop=<current tabstop>" 20 characters wide, right aligned.

You can also group several items together and apply formatting to the group as a whole. To do this, use the %(...%) syntax. For example: %20(%l/%L%) would display <current-line>/<total-lines> at a minimum width of 20 characters.

One more thing I will mention about formatting is the magic %= flag. If this flag appears in your statusline then everything after it will be shoved to the right as far as possible.

Using colors


There are two general approaches you can take with color. It depends on your needs and how hardcore you are. One way allows you to control exactly what colors are used, while the other way lets you use existing highlight groups which are controlled by your colorscheme.

Using existing highlight groups is easy, just use %#foo# to switch colors to the foo highlight group. Use %* to switch back to using the statusline highlight group. For example:


1 set statusline=
2 set statusline+=%#todo#  "switch to todo highlight
3 set statusline+=%F       "full filename
4 set statusline+=%#error# "switch to error highlight
5 set statusline+=%y       "filetype
6 set statusline+=%*       "switch back to normal statusline highlight
7 set statusline+=%l       "line number


The two disadvantages to doing color this way is that you don't have direct control over which colors are used, and if you change colorschemes then all the colors on your statusline will change.

The second method requires you to define up to nine highlight groups called User1,User2..User9. Then, vim provides shortcuts for you to switch between these colors on the statusline. For example:

 1 "define 3 custom highlight groups
 2 hi User1 ctermbg=green ctermfg=red   guibg=green guifg=red
 3 hi User2 ctermbg=red   ctermfg=blue  guibg=red   guifg=blue
 4 hi User3 ctermbg=blue  ctermfg=green guibg=blue  guifg=green
 5
 6 set statusline=
 7 set statusline+=%1*  "switch to User1 highlight
 8 set statusline+=%F   "full filename
 9 set statusline+=%2*  "switch to User2 highlight
10 set statusline+=%y   "filetype
11 set statusline+=%3*  "switch to User3 highlight
12 set statusline+=%l   "line number
13 set statusline+=%*   "switch back to statusline highlight
14 set statusline+=%P   "percentage thru file


Note that you must define your custom highlight groups after any :colorscheme in your vimrc otherwise your highlight groups will be cleared when :colorscheme is called.

OMG the big gotcha


Remember, any spaces is your statusline must be escaped, this includes any strings resulting from a function call as well. If you get any strange error messages when setting your statusline, check for spaces.

Some examples


While writing this raving I journeyed into the nerdfest that is #vim on freenode and asked (harassed) people there to give me their statusline settings.

I got some responses from some pretty high powered geeks. Take a look though these examples and steal bits and pieces. If you want to try one out, be sure to strip out any non-standard function calls as well as color settings.

 1 " spiiph's
 2 set statusline=
 3 set statusline+=%<\                       " cut at start
 4 set statusline+=%2*[%n%H%M%R%W]%*\        " flags and buf no
 5 set statusline+=%-40f\                    " path
 6 set statusline+=%=%1*%y%*%*\              " file type
 7 set statusline+=%10((%l,%c)%)\            " line and column
 8 set statusline+=%P                        " percentage of file
 9
10
11 " jamessan's
12 set statusline=   " clear the statusline for when vimrc is reloaded
13 set statusline+=%-3.3n\                      " buffer number
14 set statusline+=%f\                          " file name
15 set statusline+=%h%m%r%w                     " flags
16 set statusline+=[%{strlen(&ft)?&ft:'none'},  " filetype
17 set statusline+=%{strlen(&fenc)?&fenc:&enc}, " encoding
18 set statusline+=%{&fileformat}]              " file format
19 set statusline+=%=                           " right align
20 set statusline+=%{synIDattr(synID(line('.'),col('.'),1),'name')}\  " highlight
21 set statusline+=%b,0x%-8B\                   " current char
22 set statusline+=%-14.(%l,%c%V%)\ %<%P        " offset
23
24
25 " tpope's
26 set statusline=[%n]\ %<%.99f\ %h%w%m%r%{exists('*CapsLockStatusline')?CapsLockStatusline():''}%y%=%-16(\ %l,%c-%v\ %)%P
27
28
29 " frogonwheels'
30 set statusline=%f%w%m%h%1*%r%2*%{VarExists('b:devpath','<Rel>')}%3*%{VarExists('b:relpath','<Dev>')}%{XLockStat()}%=%-15(%l,%c%V%)%P
31
32
33 " godlygeek's
34 let &statusline='%<%f%{&mod?"[+]":""}%r%{&fenc !~ "^$\\|utf-8" || &bomb ? "[".&fenc.(&bomb?"-bom":"")."]" : ""}%=%15.(%l,%c%V %P%)'
35
36
37 " Another way to write godlygeeks:
38 set statusline=%<%f%m%r%{Fenc()}%=%15.(%l,%c%V\ %P%)
39 function! Fenc()
40     if &fenc !~ "^$\|utf-8" || &bomb
41         return "[" . &fenc . (&bomb ? "-bom" : "" ) . "]"
42     else
43         return ""
44     endif
45 endfunction

Wednesday, August 13, 2008

Vim pr0n: A simple template engine

I frequently hang out in #vim on freenode. It's a funny place to be in sometimes. People come and go, asking all kinds of random questions, but occasionally someone will ask an interesting question that requires vimscript hacks. Its times like these where, if you listen closely, you can hear the collective *click* of all available nerds simultaneously pausing their pornography. This is closely followed by the furious stampeding of fingers over keyboards as said nerds race each other to hack and pastebin the solution.

A while ago, someone asked how to write a simple template engine, i.e. something like "when I create a new java file, how can I choose a code skeleton to prefill it with". I could practically hear the starter pistol fire. I cant remember if I won or not, but I wrote some dynamite hacks and kept them in my vimrc so that the next time someone asked I could pastebin them straight away and pretend I wrote them on the spot.

Anyway, a few days ago I revisited the code and cleaned it up a bit. Here it is:


 1 "define the Template command
 2 command! -complete=customlist,AvailableTemplates -n=1
 3     \ Template :call InsertTemplate('<args>')
 4
 5 function! InsertTemplate(name)
 6
 7     "read in the template
 8     execute 'read ~/.vim/templates/' . &filetype . '/' . a:name
 9
10     "if the cursor was previously on a blank line, delete it
11     if getline(line(".")-1) =~ '^\s*$'
12         exec line(".")-1 . 'd'
13     endif
14 endfunction
15
16 function! AvailableTemplates(lead, cmdline, cursorpos)
17     let templateDir = expand('~/.vim/templates/' . &filetype . '/')
18     let files = split(globpath(templateDir, a:lead . '*'), '\n')
19
20     "chop off the templateDir from each file
21     return map(files, 'strpart(v:val,strlen(templateDir))')
22 endfunction


The idea is that you store all your template files in:

~/.vim/templates/<filetype>/

So, for example, you might have three html templates and a java template:

~/.vim/templates/html/first.html
~/.vim/templates/html/second.html
~/.vim/templates/html/third.html
~/.vim/templates/java/foo.java

Then you edit an html file and apply a template with:

:Template first.html

You can also tab complete template names.

Limitations: No error handling. No clever variable expansion etc. Assumes you are working in *nix style os, but could easily be hacked for MF Windows.

Sunday, July 27, 2008

The truth about New Zealanders and sheep

Every now and then on MSN, IRC, or in a game, it comes up that I'm from New Zealand. In almost all cases people will make the same jokes and ask the same boneheaded questions. I feel I must set matters straight once and for all — for the good of my country.

Yes.

All New Zealanders do in fact fuck sheep.

On a daily basis.

In New Zealand we shag sheep as though the apocalypse will probably arrive before dinner. On weekends we go out and get boozed, blazed, and randy as fuck before driving out to the country to tour the all-you-can-fuck sheep buffets run by the local farmers.

New Zealanders embrace polygamy. You may be disturbed by this, but we are not constrained by your moral, traditional or religious views. Our lust for the fluffy livestock overrules all such things, and is simply too great for one sheep to satisfy.

Also, we don't live in mammoth apartment buildings, stacked and racked on top and beside each other like Lego blocks. We all require backyards for our spouses to run free in while we're at work. Otherwise they would piss and shit all over the furniture. You get what I'm saying?

And our national sport is the Fluffofuckathon — a racing event where each contestant must propel a sheep down the 100 meter race track using only pelvic thrusts. Every year hundreds of spectators are trampled to death in the mad frenzy on the final race of the season. Everyone has their favourite team, but I personally root for Roger Ramjammer and his partner The Woolcano.



I am aware that such claims require photographic evidence. Lucky for you I just got married and have plenty of photos from my honeymoon.

Me and the missus outside the beehive in Sydney





Then we caught the train to downtown L.A. to see the pyramids


Then we drove up to Moscow to see the Eiffel Tower


There we rented pogo sticks and hopped over to the moon


From there it was just a short journey to China where we walked on the sun (fucking freezing this time of year)



I hope this has been educational.

Tuesday, July 22, 2008

The windows copying commands suck giant spicy boner

I really love using windows. Its almost as pleasant as having your balls severed, liquefied in acid then pumped intravenously back into your body. Trying to use the terminal is fucking great! Its only marginally less pleasant than having feral cats surgically implanted into your scrotum. The copying commands in said terminal are brilliant! Using them is not quite as pleasant as having flaming gerbils stuffed into your anus by a cult of psychotic homosexual rapists.

The other day I was hacking on the fucking NERD tree trying to implement copying tree nodes (i.e. files/directories) in windows. Vimscript doesn't provide a function to copy files/directories so you have to shell out to do it. This is fine on *nix and osx where you can be sure legions of sweaty computer nerds are, even as we speak, busting out the TLC to improve the terminals/terminal apps. The same cannot be said for the windows terminal. This goes a long way toward explaining why its copying commands suck the spicy rod of beelzebub.

When I was writing the copying code, I was wanting to shell out to a single command for each OS that required no user input.

This is the copying command the NERD tree uses for *nix/osx

cp -r source destination

Clearly this needs to be much more complex and difficult. Good thing we have windows. Lets take a look at the windows copying commands!

The first one I looked at was copy.

copy source destination

This is ok for files, but when source is a directory it (most logically) copies the entire contents of that directory into a single file. That's understandable. The programmer who wrote the command was handicapped. I get it.

The second command I looked at was xcopy.

xcopy /E source destination

This is almost good. Apart from the fact that the program is so abysmally fucking stupid that it has to ask you if destination should be a directory or a file. Could the programmer who wrote xcopy possibly be any more retarded? Let me put it like this: If a scientist was cloning a sheep for me and asked me whether said clone should be a sheep or a cow, I would take out my gun and strengthen the human gene pool by blowing his dumb ass out of it. Fuck you xcopy, you fail.

Next on the list of candidate commands was something called robocopy. Thankfully that wasn't even present on my install. Come on! "robocopy"!?! wtf, how many attempts does it take to make a program to copy stuff!?

I am aware that the windows terminal and all its shitty copying commands probably suck because they are complying to some standard or pseudo standard for backwards compatibility. I don't care, I'm still gonna hate on them, because they still suck sweaty ballsack.

Wednesday, July 16, 2008

Vim pr0n: Visual search mappings

I've always maintained that, if you're looking to jack off to the vim help pages, a good place to start is at :help * or :help #. That shit is guaranteed to please. Strangely, vim has no baked-in visual mode equivalents to these mappings i.e. there is no mapping to search for other occurrences of the currently selected text. This is kind of gay, but it's also a blessing when you're in mob formation with a pack of bored-as-fuck nerds on irc. Let me tell you a tale ...

A few days ago in #vim on freenode someone asked how you can create the aforementioned visual mode * and # mappings. Id recently stolen some basic versions from godlygeek's vimrc so I paste binned these and posted the link:
vnoremap * y/<c-r>"<cr>
vnoremap # y?<c-r>"<cr>

Nerds immediately attacked, pointing out that they didn't handle regex escaping. For example if you searched for the string "\<foo.*", the mappings would treat the \< and .* as regex magic rather than the literal strings. So we revised the mappings to these:
vnoremap * y/\V<c-r>=escape(@", '\')<cr><cr>
vnoremap # y/\V<c-r>=escape(@", '\')<cr><cr>

Nerds everywhere began furiously jacking off with one hand while updating their vimrcs with the other. Then strull dumped his version into the channel:
vmap * :<C-U>let old_reg=@"<cr>gvy/<C-R><C-R>=substitute(escape(@",'\/.*$^~[]'),"\n$","","")<CR><CR>:let @"=old_reg<cr>

This one doesn't clobber the unnamed register, and also chops off any trailing \n — handy if you hit * from visual-line mode. His regex escaping was inferior though.

Building on this, godlygeek came along with:
exe 'vnoremap * <C-\><C-n>'
\.':let [@", @/] = reverse([@", @/])<CR>'
\.'gvy'
\.':let [@", @/] = reverse([@", @/])<CR>'
\.":let @/='\\V'.escape(@/, '\\')<CR>"
\.":let @/=substitute(@/, '\\n', '\\\\'.'n', 'g')<CR>"
\."/<CR>"
vmap # *?<CR>?<CR>

He was quite proud of his version because the # mapping was expressed in terms of the * mapping, and because the search string was rightfully jammed in the / register. Probably also because the mapping was fucking nigh incomprehensible.

After a couple more revisions it ended up being expressed, somewhat more readable as this:
function! s:VSetSearch()
let temp = @@
norm! gvy
let @/ = '\V' . substitute(escape(@@, '\'), '\n', '\\n', 'g')
let @@ = temp
endfunction

vnoremap * :<C-u>call <SID>VSetSearch()<CR>//<CR>
vnoremap # :<C-u>call <SID>VSetSearch()<CR>??<CR>

This is the version that is sitting in my vimrc at the moment. Try it out. It owns. Visit the official wiki page here.