Skip to content

Commit d530837

Browse files
committed
Use HTTP HEAD to disambiguate URLs with trailing punctuation (issue #4)
1 parent 37dbfc3 commit d530837

File tree

4 files changed

+93
-4
lines changed

4 files changed

+93
-4
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ Since no mappings will be defined now you can add something like the following t
8383
:inoremap <Leader>op <C-o>:Open<CR>
8484
:nnoremap <Leader>op :Open<CR>
8585

86+
### The `g:shell_verify_urls` option
87+
88+
When you use the `:Open` command or the `<F6>` mapping to open the URL under the text cursor, the shell plug-in uses a regular expression to guess where the URL starts and ends. This works 99% percent of the time but it can break, because in this process the shell plug-in will strip trailing punctuation characters like dots (because they were likely not intended to be included in the URL).
89+
90+
If you actually deal with URLs that include significant trailing punctuation and your Vim is compiled with Python support you can enable the option `g:shell_verify_urls` (by setting it to 1 in your [vimrc script] [vimrc]). When you do this the plug-in will perform an HTTP HEAD request on the URL without stripping trailing punctuation. If the request returns an HTTP status code that indicates some form of success (the status code is at least 200 and less than 400) the URL including trailing punctuation is opened. If the HEAD request fails the plug-in will try again without trailing punctuation.
91+
8692
## Background
8793

8894
Vim has a limited ability to call external libraries using the Vim script function [libcall()][libcall]. A few years ago when I was still using Windows a lot I created a [Windows DLL][dll] that could be used with [libcall()][libcall] to toggle [Vim][vim]'s GUI window between regular and full-screen mode. I also added a few other useful functions, e.g. `openurl()` to launch the default web browser and `execute()` which works like Vim's [system()][system] function but doesn't wait for the process to finish and doesn't show a command prompt.

autoload/xolox/shell.vim

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
" Vim auto-load script
22
" Author: Peter Odding <peter@peterodding.com>
3-
" Last Change: October 18, 2011
3+
" Last Change: October 28, 2011
44
" URL: http://peterodding.com/code/vim/shell/
55

6-
let g:xolox#shell#version = '0.9.17'
6+
let g:xolox#shell#version = '0.9.18'
77

88
if !exists('s:fullscreen_enabled')
99
let s:enoimpl = "%s() hasn't been implemented on your platform! %s"
@@ -14,17 +14,22 @@ endif
1414

1515
function! xolox#shell#open_cmd(arg) " -- implementation of the :Open command {{{1
1616
try
17+
" No argument?
1718
if a:arg !~ '\S'
19+
" Filename, URL or e-mail address at text cursor location?
1820
if !s:open_at_cursor()
21+
" Open the directory of the current buffer.
1922
let bufdir = expand('%:p:h')
2023
call xolox#misc#msg#debug("shell.vim %s: Opening directory of current buffer '%s'.", g:xolox#shell#version, bufdir)
2124
call xolox#misc#open#file(bufdir)
2225
endif
2326
elseif (a:arg =~ xolox#shell#url_pattern()) || (a:arg =~ xolox#shell#mail_pattern())
27+
" Open the URL or e-mail address given as an argument.
2428
call xolox#misc#msg#debug("shell.vim %s: Opening URL or e-mail address '%s'.", g:xolox#shell#version, a:arg)
2529
call xolox#misc#open#url(a:arg)
2630
else
2731
let arg = fnamemodify(a:arg, ':p')
32+
" Does the argument point to an existing file or directory?
2833
if isdirectory(arg) || filereadable(arg)
2934
call xolox#misc#msg#debug("shell.vim %s: Opening valid filename '%s'.", g:xolox#shell#version, arg)
3035
call xolox#misc#open#file(arg)
@@ -42,7 +47,11 @@ function! s:open_at_cursor()
4247
let cWORD = expand('<cWORD>')
4348
" Start by trying to match a URL in <cWORD> because URLs can be more-or-less
4449
" unambiguously distinguished from e-mail addresses and filenames.
45-
let match = matchstr(cWORD, xolox#shell#url_pattern())
50+
if g:shell_verify_urls && cWORD =~ '^\(http\|https\)://.\{-}[[:punct:]]$' && xolox#shell#url_exists(cWORD)
51+
let match = cWORD
52+
else
53+
let match = matchstr(cWORD, xolox#shell#url_pattern())
54+
endif
4655
if match != ''
4756
call xolox#misc#msg#debug("shell.vim %s: Matched URL '%s' in cWORD '%s'.", g:xolox#shell#version, match, cWORD)
4857
else
@@ -248,6 +257,54 @@ function! xolox#shell#is_fullscreen() " -- check whether Vim is currently in ful
248257
return s:fullscreen_enabled
249258
endfunction
250259

260+
function! xolox#shell#url_exists(url) " -- check whether a URL points to an existing resource (using Python) {{{1
261+
try
262+
" Embedding Python code in Vim scripts is always a bit awkward :-(
263+
" (because of the forced indentation thing Python insists on).
264+
let starttime = xolox#misc#timer#start()
265+
python <<EOF
266+
267+
# Standard library modules.
268+
import httplib
269+
import urlparse
270+
271+
# Only loaded inside the Python interface to Vim.
272+
import vim
273+
274+
# We need to define a function to enable redirection implemented through recursion.
275+
276+
def shell_url_exists(absolute_url, rec=0):
277+
assert rec <= 10
278+
components = urlparse.urlparse(absolute_url)
279+
netloc = components.netloc.split(':', 1)
280+
if components.scheme == 'http':
281+
connection = httplib.HTTPConnection(*netloc)
282+
elif components.scheme == 'https':
283+
connection = httplib.HTTPSConnection(*netloc)
284+
else:
285+
assert False, "Unsupported URL scheme"
286+
relative_url = urlparse.urlunparse(('', '') + components[2:])
287+
connection.request('HEAD', relative_url)
288+
response = connection.getresponse()
289+
if 300 <= response.status < 400:
290+
for name, value in response.getheaders():
291+
if name.lower() == 'location':
292+
shell_url_exists(value.strip(), rec+1)
293+
break
294+
else:
295+
assert 200 <= response.status < 400
296+
297+
shell_url_exists(vim.eval('a:url'))
298+
299+
EOF
300+
call xolox#misc#timer#stop("shell.vim %s: Took %s to verify whether %s exists (it does).", g:xolox#shell#version, starttime, a:url)
301+
return 1
302+
catch
303+
call xolox#misc#timer#stop("shell.vim %s: Took %s to verify whether %s exists (it doesn't).", g:xolox#shell#version, starttime, a:url)
304+
return 0
305+
endtry
306+
endfunction
307+
251308
function! xolox#shell#url_pattern() " -- get the preferred/default pattern to match URLs {{{1
252309
return xolox#misc#option#get('shell_patt_url', '\<\w\{3,}://\(\S*\w\)\+[/?#]\?')
253310
endfunction

doc/shell.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@ to your |vimrc| script:
155155
:inoremap <Leader>op <C-o>:Open<CR>
156156
:nnoremap <Leader>op :Open<CR>
157157
158+
-------------------------------------------------------------------------------
159+
The *g:shell_verify_urls* option
160+
161+
When you use the |:Open| command or the '<F6>' mapping to open the URL under
162+
the text cursor, the shell plug-in uses a regular expression to guess where
163+
the URL starts and ends. This works 99% percent of the time but it can break,
164+
because in this process the shell plug-in will strip trailing punctuation
165+
characters like dots (because they were likely not intended to be included in
166+
the URL).
167+
168+
If you actually deal with URLs that include significant trailing punctuation
169+
and your Vim is compiled with Python support you can enable the option
170+
|g:shell_verify_urls| (by setting it to 1 in your |vimrc| script). When you do
171+
this the plug-in will perform an HTTP HEAD request on the URL without
172+
stripping trailing punctuation. If the request returns an HTTP status code
173+
that indicates some form of success (the status code is at least 200 and less
174+
than 400) the URL including trailing punctuation is opened. If the HEAD
175+
request fails the plug-in will try again without trailing punctuation.
176+
158177
===============================================================================
159178
*shell-background*
160179
Background ~

plugin/shell.vim

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
" Vim plug-in
22
" Author: Peter Odding <peter@peterodding.com>
3-
" Last Change: September 18, 2011
3+
" Last Change: October 28, 2011
44
" URL: http://peterodding.com/code/vim/shell/
55

66
" Support for automatic update using the GLVS plug-in.
@@ -18,6 +18,13 @@ if !exists('g:shell_mappings_enabled')
1818
let g:shell_mappings_enabled = 1
1919
endif
2020

21+
if !exists('g:shell_verify_urls')
22+
" Set this to true if your URLs include significant trailing punctuation and
23+
" your Vim is compiled with Python support. XXX In this case the shell
24+
" plug-in will perform HTTP HEAD requests on your behalf.
25+
let g:shell_verify_urls = 0
26+
endif
27+
2128
" Automatic commands. {{{1
2229

2330
augroup PluginShell

0 commit comments

Comments
 (0)