1 " Vim auto-load script
  2 " Author: Peter Odding <peter@peterodding.com>
  3 " Last Change: August 30, 2010
  4 " URL: http://peterodding.com/code/vim/shell/
  5 
  6 if !exists('s:script')
  7   let s:script = expand('<sfile>:p:~')
  8   let s:enoimpl = "%s() hasn't been implemented on your platform! %s"
  9   let s:contact = "If you have suggestions, please contact the vim_dev mailing-list or peter@peterodding.com."
 10   let s:fullscreen_enabled = 0
 11 endif
 12 
 13 function! xolox#shell#open_cmd(arg) " -- implementation of the :Open command {{{1
 14   if a:arg !~ '\S'
 15     if !s:open_at_cursor()
 16       call xolox#shell#open_with(expand('%:p:h'))
 17     endif
 18   elseif a:arg =~ g:shell_patt_url || a:arg =~ g:shell_patt_mail
 19     call xolox#shell#open_url(a:arg)
 20   else
 21     let arg = fnamemodify(a:arg, ':p')
 22     if isdirectory(arg) || filereadable(arg)
 23       call xolox#shell#open_with(arg)
 24     else
 25       let msg = "%s: I don't know how to open %s!"
 26       echoerr printf(msg, s:script, string(a:arg))
 27     endif
 28   endif
 29 endfunction
 30 
 31 function! s:open_at_cursor()
 32   let cWORD = expand('<cWORD>')
 33   " Start by trying to match a URL in <cWORD> because URLs can be more-or-less
 34   " unambiguously distinguished from e-mail addresses and filenames.
 35   let match = matchstr(cWORD, g:shell_patt_url)
 36   if match == ''
 37     " Now try to match an e-mail address in <cWORD> because most filenames
 38     " won't contain an @-sign while e-mail addresses require it.
 39     let match = matchstr(cWORD, g:shell_patt_mail)
 40     if match == ''
 41       " As a last resort try to match a filename at the text cursor position.
 42       let line = getline('.')
 43       let idx = col('.') - 1
 44       let match = matchstr(line[0 : idx], '\f*$')
 45       let match .= matchstr(line[idx+1 : -1], '^\f*')
 46       " Expand leading tilde and/or environment variables in filename?
 47       if match =~ '^\~' || match =~ '\$'
 48         " TODO This can return multiple files?!
 49         let match = expand(match)
 50       endif
 51       if !isdirectory(match) && !filereadable(match)
 52         let match = ''
 53       endif
 54     endif
 55   endif
 56   if match != ''
 57     call xolox#shell#open_url(match)
 58     return 1
 59   endif
 60 endfunction
 61 
 62 function! xolox#shell#open_url(url) " -- open the given URL in the user's preferred web browser {{{1
 63   try
 64     let url = a:url
 65     if url =~ g:shell_patt_mail && url !~ '^mailto:'
 66       let url = 'mailto:' . url
 67     endif
 68     if s:is_windows()
 69       if s:has_dll()
 70         call s:library_call('openurl', url)
 71       else
 72         call s:execute('CMD /C START "" %s', [url])
 73       endif
 74       return 1
 75     elseif has('macunix')
 76       " I don't have OS X available to test this but since `open`
 77       " seems such a simple command this should be fine?
 78       call s:execute('open %s', [url])
 79       return 1
 80     elseif has('unix')
 81       if !has('gui_running') && $DISPLAY == ''
 82         for browser in ['lynx', 'links', 'w3m']
 83           if executable(browser)
 84             execute '!' . browser fnameescape(url)
 85             return 1
 86           endif
 87         endfor
 88         let msg = "Failed to find command-line web browser. %s"
 89         throw printf(msg, s:contact)
 90       elseif xolox#shell#open_with(url, 'firefox', 'google-chrome')
 91         return 1
 92       else
 93         let msg = "Failed to find graphical web browser. %s"
 94         throw printf(msg, s:contact)
 95       endif
 96     endif
 97     throw printf(s:enoimpl, 'openurl', s:contact)
 98   catch
 99     call xolox#warning("%s: %s at %s", s:script, v:exception, v:throwpoint)
100   endtry
101 endfunction
102 
103 function! xolox#shell#open_with(location, ...) " -- generic handler to open files in the user's preferred applications {{{1
104   if s:is_windows()
105     if s:has_dll()
106       " A bit of a misnomer: openurl() in shell.dll is implemented using
107       " ShellExecute() which also knows how to open files and directories.
108       call s:library_call('openurl', a:location)
109     else
110       call s:execute('CMD /C START "" %s', [a:location])
111     endif
112     return 1
113   else
114     for handler in g:shell_open_cmds + a:000
115       if executable(handler)
116         let location = a:location
117         if a:location !~ g:shell_patt_url && a:location !~ g:shell_patt_mail
118           let location = fnamemodify(location, ':p:~')
119         endif
120         call xolox#message("Opening %s with %s", location, handler)
121         call s:execute('%s %s', [handler, a:location])
122         return 1
123       endif
124     endfor
125   endif
126 endfunction
127 
128 function! xolox#shell#highlight_urls() " -- highlight URLs and e-mail addresses embedded in source code comments {{{1
129   if exists('g:syntax_on') && &ft !~ g:shell_hl_exclude
130     if &ft == 'help'
131       let command = 'syntax match %s /%s/'
132       let urlgroup = 'HelpURL'
133       let mailgroup = 'HelpEmail'
134     else
135       let command = 'syntax match %s /%s/ contained containedin=.*Comment.*'
136       let urlgroup = 'CommentURL'
137       let mailgroup = 'CommentEmail'
138     endif
139     execute printf(command, urlgroup, escape(g:shell_patt_url, '/'))
140     execute printf(command, mailgroup, escape(g:shell_patt_mail, '/'))
141     execute 'highlight def link' urlgroup 'Underlined'
142     execute 'highlight def link' mailgroup 'Underlined'
143   endif
144 endfunction
145 
146 function! xolox#shell#execute(command, synchronous, ...) " -- execute external commands asynchronously {{{1
147   try
148     let cmd = a:command
149     let has_input = a:0 > 0
150     if has_input
151       let tempin = tempname()
152       call writefile(type(a:1) == type([]) ? a:1 : split(a:1, "\n"), tempin)
153       let cmd .= ' < ' . shellescape(tempin)
154     endif
155     if a:synchronous
156       let tempout = tempname()
157       let cmd .= ' > ' . shellescape(tempout) . ' 2>&1'
158     endif
159     if s:is_windows() && s:has_dll()
160       let fn = 'execute_' . (a:synchronous ? '' : 'a') . 'synchronous'
161       let cmd = ($COMSPEC != '' ? $COMSPEC : 'CMD.EXE') . ' /C ' . cmd
162       let error = s:library_call(fn, cmd)
163       if error != ''
164         let msg = '%s: %s(%s) failed! (error: %s)'
165         throw printf(msg, s:script, fn, strtrans(cmd), strtrans(error))
166       endif
167     else
168       if has('unix') && !a:synchronous
169         let cmd = '(' . cmd . ') &'
170       endif
171       let output = split(system(cmd), "\n")
172       call s:handle_error(cmd, output)
173     endif
174     if a:synchronous
175       if !filereadable(tempout)
176         let msg = '%s: Failed to execute %s!'
177         throw printf(msg, s:script, strtrans(cmd))
178       endif
179       return readfile(tempout)
180     else
181       return 1
182     endif
183   catch
184     call xolox#warning("%s: %s at %s", s:script, v:exception, v:throwpoint)
185   finally
186     if exists('tempin') | call delete(tempin) | endif
187     if exists('tempout') | call delete(tempout) | endif
188   endtry
189 endfunction
190 
191 function! xolox#shell#fullscreen() " -- toggle Vim between normal and full-screen mode {{{1
192 
193   " On entering full-screen hide GUI components like the main menu, tool bar
194   " and tab line. Remember which components were actually hidden and should be
195   " restored when leaving full-screen later.
196   if !s:fullscreen_enabled
197     let s:go_toggled = ''
198     for item in split(g:shell_fullscreen_items, '.\zs')
199       if &go =~# item
200         let s:go_toggled .= item
201         execute 'set go-=' . item
202       endif
203     endfor
204     if g:shell_fullscreen_items =~# 'e' && &stal != 0
205       let s:stal_save = &stal
206       set showtabline=0
207     endif
208   endif
209 
210   " Now try to toggle the real full-screen status of Vim's GUI window using a
211   " custom dynamic link library on Windows or the "wmctrl" program on UNIX.
212   try
213     if s:is_windows()
214       if !s:has_dll()
215         let msg = "The DLL library %s is missing!"
216         throw printf(msg, string(s:library))
217       endif
218       let error = s:library_call('fullscreen', !s:fullscreen_enabled)
219       if error != ''
220         throw "shell.dll failed with: " . error
221       endif
222     elseif has('unix')
223       if !executable('wmctrl')
224         let msg = "Full-screen on UNIX requires the `wmctrl' program!"
225         throw msg . " On Debian/Ubuntu you can install it by executing `sudo apt-get install wmctrl'."
226       endif
227       call s:execute('wmctrl -r %s -b toggle,fullscreen 2>&1', [':ACTIVE:'])
228     else
229       throw printf(s:enoimpl, 'fullscreen', s:contact)
230     endif
231   catch
232     call xolox#warning("%s: %s at %s", s:script, v:exception, v:throwpoint)
233   endtry
234 
235   " On leaving full-screen restore display of previously hidden GUI components?
236   if s:fullscreen_enabled
237     let &go .= s:go_toggled
238     if exists('s:stal_save')
239       let &stal = s:stal_save
240       unlet s:stal_save
241     endif
242   endif
243 
244   " Toggle the boolean status returned by xolox#shell#is_fullscreen().
245   let s:fullscreen_enabled = !s:fullscreen_enabled
246 
247   " Let the user know how to leave full-screen mode?
248   if s:fullscreen_enabled
249     sleep 50 m
250     call xolox#message("To return from full-screen type <F11> or execute :Fullscreen.")
251   endif
252 
253 endfunction
254 
255 function! xolox#shell#is_fullscreen() " -- check whether Vim is currently in full-screen mode {{{1
256   return s:fullscreen_enabled
257 endfunction
258 
259 function! xolox#shell#build_cmd(cmd, args) " -- create a command-line from a program and its escaped arguments {{{1
260   if a:args == []
261     return a:cmd
262   else
263     let args = map(copy(a:args), 'shellescape(v:val)')
264     call insert(args, a:cmd, 0)
265     return call('printf', args)
266   endif
267 endfunction
268 
269 " Miscellaneous script-local functions. {{{1
270 
271 function! s:is_windows() " {{{2
272   return has('win32') || has('win64')
273 endfunction
274 
275 if s:is_windows()
276 
277   let s:library = expand('<sfile>:p:h') . '\shell.dll'
278 
279   function! s:library_call(fn, arg) " {{{2
280     return libcall(s:library, a:fn, a:arg)
281   endfunction
282 
283   function! s:find_dll_version() " {{{2
284     try
285       return s:library_call('libversion', '')
286     catch
287       let msg = "%s: Failed to load %s DLL!"
288       let lib = fnamemodify(s:library, ':~')
289       echohl warningmsg
290       echomsg printf(msg, s:script, lib)
291       echohl none
292     endtry
293     return '?'
294   endfunction
295 
296   function! s:has_dll() " {{{2
297     " Check that the DLL is available using libversion() before calling any of
298     " the other functions. This is only done the first time this plug-in needs
299     " to call the DLL and also makes sure the right version is loaded.
300     if !exists('s:library_version')
301       let s:library_version = s:find_dll_version()
302     endif
303     return s:library_version == '0.2'
304   endfunction
305 
306 endif
307 
308 function! s:execute(cmd, args) " {{{2
309   let cmd = xolox#shell#build_cmd(a:cmd, a:args)
310   let output = system(cmd)
311   call s:handle_error(cmd, output)
312   return output
313 endfunction
314 
315 function! s:handle_error(cmd, output) " {{{2
316   if v:shell_error
317     if type(a:output) == type([])
318       let output = join(a:output, "\n")
319     else
320       let output = a:output
321     endif
322     let msg = "Command %s failed!"
323     if output =~ '^\_s*$'
324       throw printf(msg, string(a:cmd))
325     else
326       let msg .= ' (output: %s)'
327       throw printf(msg, string(a:cmd), strtrans(output))
328     endif
329   endif
330 endfunction
331 
332 " vim: ts=2 sw=2 et fdm=marker