# HG changeset patch # User Steve Losh # Date 1319676144 14400 # Node ID e6267718ee3c720c96fc77733d799492def1c973 # Parent 02d1c7c8455da57785374ce36e07c042445b36d7 New chapter. diff -r 02d1c7c8455d -r e6267718ee3c chapters/38.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chapters/38.markdown Wed Oct 26 20:42:24 2011 -0400 @@ -0,0 +1,200 @@ +Toggling +======== + +In one of the first chapters we talked about how to set options in Vim. For +boolean options we can use `set someoption!` to "toggle" the option. This is +expecially nice when we create a mapping for that command. + +Run the following command: + + :nnoremap N :setlocal number! + +Try it out by pressing `N` in normal mode. Vim will toggle the line +numbers for the current window off and on. Creating a "toggle" mapping like +this is really handy, because we don't need to have two separate keys to turn +something off and on. + +Unfortunately this only works for boolean options. If we want to toggle +a non-boolean option we'll need to do a bit more work. + +Toggling Options +---------------- + +Let's start by creating a function that will toggle an option for us, and +a mapping that will call it. Put the following into your `~/.vimrc` file (or +a separate file in `~/.vim/plugin/` if you prefer): + + nnoremap :call FoldColumnToggle() + + function! FoldColumnToggle() + echom &foldcolumn + endfunction + +Write and source the file, then try it out by pressing ``. Vim will +display the current value of the `foldcolumn` option. Go ahead and read `:help +foldcolumn` if you're unfamiliar with this option. + +Let's add in the actual toggling functionality. Edit the code to look like +this: + + nnoremap :call FoldColumnToggle() + + function! FoldColumnToggle() + if &foldcolumn + setlocal foldcolumn=0 + else + setlocal foldcolumn=4 + endif + endfunction + +Write and source the file and try it out. Each time you press it Vim will +either show or hide the fold column. + +The `if` statement simply checks if `&foldcolumn` is truthy (remember that Vim +treats the integer 0 as falsy and any other number as truthy). If so, it sets +it to zero (which hides it). Otherwise it sets it to four. Pretty simple. + +You can use a simple function like this to toggle any option where `0` means +"off" and any other number is "on". + +Toggling Other Things +--------------------- + +Options aren't the only thing we might want to toggle. One particularly nice +thing to have a mapping for is the quickfix window. Let's start with the same +skeleton as before. Add the following code to your file: + + nnoremap :call QuickfixToggle() + + function! QuickfixToggle() + return + endfunction + +This mapping doesn't do anything yet. Let's transform it into something +slightly more useful (but not completely finished yet). Change the code to +look like this: + + nnoremap :call QuickfixToggle() + + function! QuickfixToggle() + copen + endfunction + +Write and source the file. If you try out the mapping now you'll see that it +simply opens the quickfix window. + +To get the "toggling" behavior we're looking for we'll use a quick, dirty +solution: a global variable. Change the code to look like this: + + nnoremap :call QuickfixToggle() + + function! QuickfixToggle() + if g:quickfix_is_open + cclose + let g:quickfix_is_open = 0 + else + copen + let g:quickfix_is_open = 1 + endif + endfunction + +What we've done is pretty simple -- we're simply storing a global variable +describing the open/closed state of the quickfix window whenever we call the +function. + +Write and source the file, and try to run the mapping. Vim will complain that +the variable is not defined yet! Let's fix that by initializing it once: + + nnoremap :call QuickfixToggle() + + let g:quickfix_is_open = 0 + + function! QuickfixToggle() + if g:quickfix_is_open + cclose + let g:quickfix_is_open = 0 + else + copen + let g:quickfix_is_open = 1 + endif + endfunction + +Write and source the file, and try the mapping. It works! + +Improvements +------------ + +Our toggle function works, but has a few problems. + +The first is that if the user manually opens or closes the window with `:copen` +or `:cclose` our global variable doesn't get updated. This isn't really a huge +problem in practice because most of the time the user will probably be opening +the window with the mapping, and if not they can always just press it again. + +This illustrates an important point about writing Vimscript code: if you try to +handle every single edge case you'll get bogged down in it and never get any +work done. + +Getting something that works most of the time (and doesn't explode when it +doesn't work) and getting back to coding is usually better than spending hours +getting it 100% perfect. The exception is when you're writing a plugin you +expect many people to use. In that case it's best to spend the time and make it +bulletproof to keep your users happy and reduce bug reports. + +Restoring Windows/Buffers +------------------------- + +The other problem with our function is that if the user runs the mapping when +they're already in the quickfix window, Vim closes it and dumps them into the +last split instead of sending them back where they were. This is annoying if +you just want to check the quickfix window really quick and get back to working. + +To solve this we'll introduce an idiom that comes in handy a lot when writing +Vim plugins. Edit your code to look like this: + + nnoremap :call QuickfixToggle() + + let g:quickfix_is_open = 0 + + function! QuickfixToggle() + if g:quickfix_is_open + cclose + let g:quickfix_is_open = 0 + execute g:quickfix_return_to_window . "wincmd w" + else + let g:quickfix_return_to_window = winnr() + copen + let g:quickfix_is_open = 1 + endif + endfunction + +We've added two new lines in this mapping. One of them (in the `else` clause) +sets another global variable which saves the current window number before we run +`:copen`. + +The second line (in the `if` clause) executes `wincmd w` with that number +prepended as a count, which tells Vim to go to that window. + +Once again our solution isn't bulletproof, because the user might open or close +new split between runs of the mapping. Even so, it handles the majority of +cases so it's good enough for now. + +This strategy of manually saving global state would be frowned upon in most +serious programs, but for tiny little Vimscript functions it's a quick and dirty +way of getting something mostly working and moving on with your life. + +Exercises +--------- + +Read `:help foldcolumn`. + +Read `:help winnr()` + +Read `:help ctrl-w_w`. + +Read `:help wincmd`. + +Read `:help ctrl-q` and `:help ctrl-f` to see what you overwrote with these +mappings. + +Namespace the functions by adding `s:` and `` where necessary.