1 " Vim plug-in
  2 " Author: Peter Odding <peter@peterodding.com>
  3 " Last Change: July 28, 2010
  4 " URL: http://peterodding.com/code/vim/pyref/
  5 " License: MIT
  6 " Version: 0.5
  7 
  8 " Support for automatic update using the GLVS plug-in.
  9 " GetLatestVimScripts: 3104 1 :AutoInstall: pyref.zip
 10 
 11 " Don't source the plug-in when its already been loaded or &compatible is set.
 12 if &cp || exists('g:loaded_pyref')
 13   finish
 14 endif
 15 
 16 " Configuration defaults. {{{1
 17 
 18 " Use a script-local function to define the configuration defaults so that we
 19 " don't pollute Vim's global scope with temporary variables.
 20 
 21 function! s:CheckOptions()
 22   if !exists('g:pyref_mapping')
 23     let g:pyref_mapping = '<F1>'
 24   endif
 25   if !exists('g:pyref_mirror')
 26     let local_mirror = '/usr/share/doc/python2.6/html'
 27     if isdirectory(local_mirror)
 28       let g:pyref_mirror = 'file://' . local_mirror
 29     else
 30       let g:pyref_mirror = 'http://docs.python.org'
 31     endif
 32   endif
 33   if !exists('g:pyref_index')
 34     if has('win32') || has('win64')
 35       let g:pyref_index = '~/vimfiles/misc/pyref_index'
 36     else
 37       let g:pyref_index = '~/.vim/misc/pyref_index'
 38     endif
 39   endif
 40   if !filereadable(fnamemodify(g:pyref_index, ':p'))
 41     let msg = "pyref.vim: The index file doesn't exist or isn't readable! (%s)"
 42     echoerr printf(msg, g:pyref_index)
 43     return 0 " Initialization failed.
 44   endif
 45   if !exists('g:pyref_browser')
 46     if has('win32') || has('win64')
 47       " On Windows the default web browser is accessible using the START command.
 48       let g:pyref_browser = 'CMD /C START ""'
 49     else
 50       " On UNIX we decide whether to use a CLI or GUI web browser based on
 51       " whether the $DISPLAY environment variable is set.
 52       if $DISPLAY == ''
 53         let known_browsers = ['lynx', 'links', 'w3m']
 54       else
 55         " Note: Don't use `xdg-open' here, it ignores fragment identifiers :-S
 56         let known_browsers = ['gnome-open', 'firefox', 'google-chrome', 'konqueror']
 57       endif
 58       " Otherwise we search for a sensible default browser.
 59       let search_path = substitute(substitute($PATH, ',', '\\,', 'g'), ':', ',', 'g')
 60       for browser in known_browsers
 61         " Use globpath()'s third argument where possible (since Vim 7.3?).
 62         try
 63           let matches = split(globpath(search_path, browser, 1), '\n')
 64         catch
 65           let matches = split(globpath(search_path, browser), '\n')
 66         endtry
 67         if len(matches) > 0
 68           let g:pyref_browser = matches[0]
 69           break
 70         endif
 71       endfor
 72       if !exists('g:pyref_browser')
 73         let msg = "pyref.vim: Failed to find a default web browser!"
 74         echoerr msg . "\nPlease set the global variable `pyref_browser' manually."
 75         return 0 " Initialization failed.
 76       endif
 77     endif
 78   endif
 79   return 1 " Initialization successful.
 80 endfunction
 81 
 82 if s:CheckOptions()
 83   " Don't reload the plug-in once its been successfully initialized.
 84   let g:loaded_pyref = 1
 85 else
 86   " Don't finish sourcing the script when there's no point.
 87   finish
 88 endif
 89 
 90 " Automatic command to define key-mapping. {{{1
 91 
 92 augroup PluginPyRef
 93   autocmd! FileType python call s:DefineMappings()
 94 augroup END
 95 
 96 function! s:DefineMappings() " {{{1
 97   let command = '%s <silent> <buffer> %s %s:call <Sid>PyRef()<CR>'
 98   " Always define the normal mode mapping.
 99   execute printf(command, 'nmap', g:pyref_mapping, '')
100   " Don't create the insert mode mapping when "g:pyref_mapping" has been
101   " changed to something like K because it'll conflict with regular input.
102   if g:pyref_mapping =~ '^<[^>]\+>'
103     execute printf(command, 'imap', g:pyref_mapping, '<C-O>')
104   endif
105 endfunction
106 
107 function! s:PyRef() " {{{1
108 
109   " Get the identifier under the cursor including any dots to match
110   " identifiers like `os.path.join' instead of single words like `join'.
111   try
112     let isk_save = &isk
113     let &isk = '@,48-57,_,192-255,.'
114     let ident = expand('<cword>')
115   finally
116     let &isk = isk_save
117   endtry
118 
119   " Do something useful when there's nothing at the current position.
120   if ident == ''
121     return s:OpenBrowser(g:pyref_mirror . '/contents.html')
122   endif
123 
124   " Escape any dots in the expression so it can be used as a pattern.
125   let pattern = substitute(ident, '\.', '\\.', 'g')
126 
127   " Search for an exact match of a module name or identifier in the index.
128   let indexfile = fnamemodify(g:pyref_index, ':p')
129   try
130     let lines = readfile(indexfile)
131   catch
132     let lines = []
133     echoerr "pyref.vim: Failed to read index file! (" . indexfile . ")"
134   endtry
135   if s:JumpToEntry(lines, '^\C\(module-\|exceptions\.\)\?' . pattern . '\t')
136     return
137   endif
138 
139   " Search for a substring match on word boundaries.
140   if s:JumpToEntry(lines, '\C\<' . pattern . '\>.*\t')
141     return
142   endif
143 
144   " Try to match a method name of one of the standard Python types: strings,
145   " lists, dictionaries and files (not exactly ideal but better than nothing).
146   for [url, method_pattern] in s:object_methods
147     let method = matchstr(ident, method_pattern)
148     if method != ''
149       if url =~ '%s'
150         let url = printf(url, method)
151       endif
152       return s:OpenBrowser(g:pyref_mirror . '/' . url)
153     endif
154   endfor
155 
156   " Search for a substring match in the index.
157   if s:JumpToEntry(lines, '\C' . pattern . '.*\t')
158     return
159   endif
160 
161   " Split the expression on all dots and search for a progressively smaller
162   " suffix to resolve object attributes like "self.parser.add_option" to
163   " global identifiers like "optparse.OptionParser.add_option". This relies
164   " on the uniqueness of the method names in the standard library.
165   let parts = split(ident, '\.')
166   while len(parts) > 1
167     call remove(parts, 0)
168     let pattern = '\C\<' . join(parts, '\.') . '$'
169     if s:JumpToEntry(lines, pattern)
170       return
171     endif
172   endwhile
173 
174   " As a last resort, search all of http://docs.python.org/ using Google.
175   call s:OpenBrowser('http://google.com/search?btnI&q=inurl:docs.python.org/+' . ident)
176 
177 endfunction
178 
179 " This list of lists contains [url_format, method_pattern] pairs that are used
180 " to recognize calls to methods of objects that are one of Python's standard
181 " types: strings, lists, dictionaries and file handles.
182 let s:object_methods = [
183       \ ['library/stdtypes.html#str.%s', '\C\.\@<=\(capitalize\|center\|count\|decode\|encode\|endswith\|expandtabs\|find\|format\|index\|isalnum\|isalpha\|isdigit\|islower\|isspace\|istitle\|isupper\|join\|ljust\|lower\|lstrip\|partition\|replace\|rfind\|rindex\|rjust\|rpartition\|rsplit\|rstrip\|split\|splitlines\|startswith\|strip\|swapcase\|title\|translate\|upper\|zfill\)$'],
184       \ ['tutorial/datastructures.html#more-on-lists', '\C\.\@<=\(append\|count\|extend\|index\|insert\|pop\|remove\|reverse\|sort\)$'],
185       \ ['library/stdtypes.html#dict.%s', '\C\.\@<=\(clear\|copy\|fromkeys\|get\|has_key\|items\|iteritems\|iterkeys\|itervalues\|keys\|pop\|popitem\|setdefault\|update\|values\)$'],
186       \ ['library/stdtypes.html#file.%s', '\C\.\@<=\(close\|closed\|encoding\|errors\|fileno\|flush\|isatty\|mode\|name\|newlines\|next\|read\|readinto\|readline\|readlines\|seek\|softspace\|tell\|truncate\|write\|writelines\|xreadlines\)$']]
187 
188 function! s:JumpToEntry(lines, pattern) " {{{1
189   if &verbose
190     echomsg "pyref.vim: Trying to match" string(a:pattern)
191   endif
192   let index = match(a:lines, a:pattern)
193   if index >= 0
194     let url = split(a:lines[index], '\t')[1]
195     call s:OpenBrowser(g:pyref_mirror . '/' . url)
196     return 1
197   endif
198   return 0
199 endfunction
200 
201 function! s:OpenBrowser(url) " {{{1
202   let browser = g:pyref_browser
203   if browser =~ '\<\(lynx\|links\|w3m\)\>'
204     execute '!' . browser fnameescape(a:url)
205   else
206     if browser !~ '^CMD /C START'
207       let browser = shellescape(browser)
208     endif
209     call system(browser . ' ' . shellescape(a:url))
210   endif
211   if v:shell_error && browser !~ '^CMD /C START'
212     " When I tested this on Windows Vista the START command worked just fine
213     " but it always exited with a status code of 1. Therefor the status code
214     " of the START command is now ignored.
215     let message = "pyref.vim: Failed to execute %s! (status code %i)"
216     echoerr printf(message, browser, v:shell_error)
217     return 0
218   endif
219   return 1
220 endfunction
221 
222 " vim: ts=2 sw=2 et nowrap