ce37028052fe

Autoloading.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sat, 03 Nov 2012 15:41:09 -0400
parents efe4cc9cbd7e
children 51a18213a0aa 450e610a934f
branches/tags (none)
files build.sh chapters/53.markdown chapters/56.markdown publish.sh

Changes

--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/build.sh	Sat Nov 03 15:41:09 2012 -0400
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+set -e
+python ../bookmarkdown/bookmarkdown/bookmarkdown build
--- a/chapters/53.markdown	Wed Oct 31 18:28:40 2012 -0400
+++ b/chapters/53.markdown	Sat Nov 03 15:41:09 2012 -0400
@@ -1,11 +1,260 @@
 Autoloading
 ===========
 
+We've written a fair amount of functionality for our Potion plugin, and that's
+all we're going to do in this book.  Before we finish we'll talk about a few
+more important ways to polish it up and really make it shine.
+
+First on the list is making out plugin more efficient with autoloading.
+
 How Autoload Works
 ------------------
 
+Currently when a user loads our plugin (by opening a Potion file) *all* of its
+functionality is loaded.  Our plugin is still small so this probably isn't a big
+deal, but for larger plugins loading all of their code can take a noticeable
+amount of time.
+
+Vim's solution to this is something called "autoload".  Autoload lets you delay
+loading code until it's actually needed.  You'll take a slight performance hit
+overall, but if your users don't always use every single bit of code in your
+plugin autoloading can be a huge speedup.
+
+Here's how it works.  Look at the following command:
+
+    :::vim
+    :call somefile#Hello()
+
+When you run this command, Vim will behave a bit differently than a normal
+function call.
+
+If this function has already been loaded, Vim will simply call it normally.
+
+Otherwise Vim will look for a file called `autoload/somefile.vim` in your
+`~/.vim` directory (and any Pathogen bundles).
+
+If this file exists, Vim will load/source the file.  It will then try to call
+the function normally.
+
+Inside this file, the function should be defined like this:
+
+    :::vim
+    function somefile#Hello()
+        " ...
+    endfunction
+
+You can use multiple `#` characters in the function name to represent
+subdirectories.  For example:
+
+    :::vim
+    :call myplugin#somefile#Hello()
+
+This will look for the autoloaded file at `autoload/myplugin/somefile.vim`.  The
+function inside it needs to be defined with the full autoload path:
+
+    :::vim
+    function myplugin#somefile#Hello()
+        " ...
+    endfunction
+
+Experimenting
+-------------
+
+To get a feel for how this works, let's give it a try.  Create
+a `~/.vim/autoload/example.vim` file and add the following to it:
+
+    :::vim
+    echom "Loading..."
+
+    function! example#Hello()
+        echom "Hello, world!"
+    endfunction
+
+    echom "Done loading."
+
+Save the file and run `:call example#Hello()`.  Vim will output the following:
+
+    :::text
+    Loading...
+    Done loading.
+    Hello, world!
+
+This little demonstration proves a few things:
+
+1. Vim really does load the `example.vim` file on the fly.  It didn't even exist
+   when we opened Vim, so it couldn't have been loaded on startup!
+2. When Vim finds the file it needs to autoload, it loads the entire file before
+   actually calling the function.
+
+**Without closing Vim**, change the definition of the function to look like
+this:
+
+    :::vim
+    echom "Loading..."
+
+    function! example#Hello()
+        echom "Hello AGAIN, world!"
+    endfunction
+
+    echom "Done loading."
+
+Save the file and **without closing Vim** run `:call example#Hello()`.  Vim will
+simply output:
+
+    :::text
+    Hello, world!
+
+Vim already has a definition for `example#Hello`, so it doesn't need to reload
+the file, which means:
+
+1. The code outside the function wasn't run again.
+2. It didn't pick up the changes to the function.
+
+Now run `:call example#BadFunction()`.  You'll see the loading messages again,
+as well as an error about a nonexistent function.  But now try running `:call
+example#Hello()` again.  This time you'll see the updated message!
+
+By now you should have a pretty clear grip on what happens when Vim encounters
+a call to a function with an autoload-style name:
+
+1. It checks to see if it has a function by that name defined already.  If so,
+   just call it.
+2. Otherwise, find the appropriate file (based on the name) and source it.
+3. Then attempt to call the function.  If it works, great. If it fails, just
+   print an error.
+
+If that's not completely solid in your mind yet, go back and work through this
+demonstration again and try to see where each rule takes effect.
+
 What to Autoload
 ----------------
 
+Autoloading isn't free.  There's some (small) overhead involved with setting it
+up, not to mention the ugly function names you need to sprinkle through your
+code.
+
+With that said, if you're creating a plugin that won't be used *every* time
+a user opens a Vim session it's probably a good idea to move as much
+functionality into autoloaded files as possible.  This will reduce the impact
+your plugin has on your users' startup times, which is important as people
+install more and more Vim plugins.
+
+So what kind of things can be safely autoloaded?  The answer is basically
+anything that's not directly called by your users.  Mappings and custom commands
+can't be autoloaded (because they wouldn't be available for the users to call),
+but many other things can be.
+
+Let's look at our Potion plugin and see what we can autoload.
+
+Adding Autoloading to the Potion Plugin
+---------------------------------------
+
+We'll start with the compile and run functionality.  Remember that our
+`ftplugin/potion/running.vim` file looked like this at the end of the previous
+chapter:
+
+    :::vim
+    if !exists("g:potion_command")
+        let g:potion_command = "/Users/sjl/src/potion/potion"
+    endif
+
+    function! PotionCompileAndRunFile()
+        silent !clear
+        execute "!" . g:potion_command . " " . bufname("%")
+    endfunction
+
+    function! PotionShowBytecode()
+        " Get the bytecode.
+        let bytecode = system(g:potion_command . " -c -V " . bufname("%"))
+
+        " Open a new split and set it up.
+        vsplit __Potion_Bytecode__
+        normal! ggdG
+        setlocal filetype=potionbytecode
+        setlocal buftype=nofile
+
+        " Insert the bytecode.
+        call append(0, split(bytecode, '\v\n'))
+    endfunction
+
+    nnoremap <buffer> <localleader>r :call PotionCompileAndRunFile()<cr>
+    nnoremap <buffer> <localleader>b :call PotionShowBytecode()<cr>
+
+This file is already only called when a Potion file is loaded, so it doesn't add
+to the overhead of Vim's startup in general.  But there may be some users who
+simply don't need this functionality, so if we can autoload some of it we'll
+save them a few milliseconds every time they open a Potion file.
+
+Yes, in this case the savings won't be huge.  But I'm sure you can imagine
+a plugin with many thousands of lines of functions where the time required to
+load them would be more significant.
+
+Let's get started.  Create an `autoload/potion/running.vim` file in your plugin
+repo.  Then move the two functions into it and adjust their names, so they look
+like this:
+
+    :::vim
+    echom "Autoloading..."
+
+    function! potion#running#PotionCompileAndRunFile()
+        silent !clear
+        execute "!" . g:potion_command . " " . bufname("%")
+    endfunction
+
+    function! potion#running#PotionShowBytecode()
+        " Get the bytecode.
+        let bytecode = system(g:potion_command . " -c -V " . bufname("%"))
+
+        " Open a new split and set it up.
+        vsplit __Potion_Bytecode__
+        normal! ggdG
+        setlocal filetype=potionbytecode
+        setlocal buftype=nofile
+
+        " Insert the bytecode.
+        call append(0, split(bytecode, '\v\n'))
+    endfunction
+
+Notice how the `potion#running` portion of the function names matches the
+directory and file name where they live.  Now change the
+`ftplugin/potion/running.vim` file to look like this:
+
+    :::vim
+    if !exists("g:potion_command")
+        let g:potion_command = "/Users/sjl/src/potion/potion"
+    endif
+
+    nnoremap <buffer> <localleader>rr
+                \ :call potion#running#PotionCompileAndRunFile()<cr>
+
+    nnoremap <buffer> <localleader>b
+                \ :call potion#running#PotionShowBytecode()<cr>
+
+Save the files, close Vim, and open up your `factorial.pn` file.  Try using the
+mappings to make sure they still work properly.
+
+Make sure that you see the diagnostic "Autoloading..." message only the first
+time you run one of the mappings (you may need to use `:messages` to see it).
+Once you confirm that autoloading is working properly you can remove that
+message.
+
+As you can see, we've left the `nnoremap` calls that map the keys.  We can't
+autoload these because the user would have no way to initiate the autoloading if
+we did!
+
+This is a common pattern you'll see in Vim plugins: most of their functionality
+will be held in autoloaded functions, with just `nnoremap` and `command`
+commands in the files that Vim loads every time.  Keep it in mind whenever
+you're writing a non-trivial Vim plugin.
+
 Exercises
 ---------
+
+Read `:help autoload`.
+
+Experiment a bit and find out how autoloading variables behaves.
+
+Suppose you wanted to programatically force a reload of an autoload file Vim has
+already loaded, without bothering the user.  How might you do this?  You may
+want to read `:help silent!`.  Please don't ever do this in real life.
+
--- a/chapters/56.markdown	Wed Oct 31 18:28:40 2012 -0400
+++ b/chapters/56.markdown	Sat Nov 03 15:41:09 2012 -0400
@@ -1,6 +1,12 @@
 What Now?
 =========
 
+runtimepath
+-----------
+
+The Command Command
+-------------------
+
 Omnicomplete
 ------------
 
--- a/publish.sh	Wed Oct 31 18:28:40 2012 -0400
+++ b/publish.sh	Sat Nov 03 15:41:09 2012 -0400
@@ -1,5 +1,5 @@
 #!/usr/bin/env bash
 
 set -e
-python ../bookmarkdown/bookmarkdown/bookmarkdown build
+./build.sh
 rsync --delete -az build/html/ sl:/var/www/vimscript/