e6267718ee3c

New chapter.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Wed, 26 Oct 2011 20:42:24 -0400
parents 02d1c7c8455d
children d33b3af9c536
branches/tags (none)
files chapters/38.markdown

Changes

--- /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 <leader>N :setlocal number!<cr>
+
+Try it out by pressing `<leader>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 <c-f> :call FoldColumnToggle()<cr>
+
+    function! FoldColumnToggle()
+        echom &foldcolumn
+    endfunction
+
+Write and source the file, then try it out by pressing `<c-f>`.  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 <c-f> :call FoldColumnToggle()<cr>
+
+    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 <c-q> :call QuickfixToggle()<cr>
+
+    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 <c-q> :call QuickfixToggle()<cr>
+
+    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 <c-q> :call QuickfixToggle()<cr>
+
+    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 <c-q> :call QuickfixToggle()<cr>
+
+    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 <c-q> :call QuickfixToggle()<cr>
+
+    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 `<SID>` where necessary.