Files
2020-09-18 19:26:52 +02:00

186 lines
7.8 KiB
VimL

" ingo/escape/file.vim: Additional escapings of filespecs.
"
" DEPENDENCIES:
" - ingo/os.vim autoload script
"
" Copyright: (C) 2013-2020 Ingo Karkat
" The VIM LICENSE applies to this script; see ':help copyright'.
"
" Maintainer: Ingo Karkat <ingo@karkat.de>
"
" REVISION DATE REMARKS
" 1.025.005 01-Mar-2016 BUG: Unescaped backslash resulted in unclosed
" [...] regexp collection causing
" ingo#escape#file#fnameunescape() to fail to
" escape on Unix.
" 1.023.004 17-Dec-2014 ENH: Add a:isFile flag to
" ingo#escape#file#bufnameescape() in order to do
" full matching on scratch buffer names. There,
" the expansion to a full absolute path must be
" skipped in order to match.
" 1.019.003 23-May-2014 FIX: Correct ingo#escape#file#wildcardescape()
" of * and ? on Windows.
" 1.018.002 21-Mar-2014 Add ingo#escape#file#wildcardescape().
" 1.012.001 08-Aug-2013 file creation
function! ingo#escape#file#bufnameescape( filespec, ... )
"*******************************************************************************
"* PURPOSE:
" Escape a normal filespec syntax so that it can be used for the bufname(),
" bufnr(), bufwinnr() commands.
" Note: bufexists(), buflisted() and bufloaded() do not need
" ingo#escape#file#bufnameescape() escaping; they only match relative or full
" paths, anyway.
" Ensure that there are no double (back-/forward) slashes inside the path; the
" anchored pattern doesn't match in those cases!
"
"* ASSUMPTIONS / PRECONDITIONS:
" ? List of any external variable, control, or other element whose state affects this procedure.
"* EFFECTS / POSTCONDITIONS:
" ? List of the procedure's effect on each external variable, control, or other element.
"* INPUTS:
" a:filespec Normal filespec
" a:isFullMatch Optional flag whether only the full filespec should be
" matched (default=1). If 0, the escaped filespec will not be
" anchored.
" a:isFile Optional flag whether a:filespec represents a file
" (default=1). Set to 0 to search for (scratch) buffers with
" 'buftype' set to "nofile" with a:isFullMatch = 1.
"* RETURN VALUES:
" Filespec escaped for the bufname() etc. commands listed above.
"*******************************************************************************
let l:isFullMatch = (a:0 ? a:1 : 1)
let l:isFile = (a:0 >= 2 ? a:2 : 1)
" For a full match, the passed a:filespec must be converted to a full
" absolute path (with symlinks resolved, just like Vim does on opening a
" file) in order to match.
let l:escapedFilespec = (l:isFile ? resolve(fnamemodify(a:filespec, ':p')) : a:filespec)
" Backslashes are converted to forward slashes, as the comparison is done with
" these on all platforms, anyway (cp. :help file-pattern).
let l:escapedFilespec = tr(l:escapedFilespec, '\', '/')
" Special file-pattern characters must be escaped: [ escapes to [[], not \[.
let l:escapedFilespec = substitute(l:escapedFilespec, '[\[\]]', '[\0]', 'g')
" The special filenames '#' and '%' need not be escaped when they are anchored
" or occur within a longer filespec.
let l:escapedFilespec = escape(l:escapedFilespec, '?*')
" I didn't find any working escaping for {, so it is replaced with the ?
" wildcard.
let l:escapedFilespec = substitute(l:escapedFilespec, '[{}]', '?', 'g')
if l:isFullMatch
" The filespec must be anchored to ^ and $ to avoid matching filespec
" fragments.
return '^' . l:escapedFilespec . '$'
else
return l:escapedFilespec
endif
endfunction
function! ingo#escape#file#fnameunescape( exfilespec, ... )
"*******************************************************************************
"* PURPOSE:
" Converts the passed a:exfilespec to the normal filespec syntax (i.e. no
" escaping of Ex special chars like [%#]). The normal syntax is required by
" Vim functions such as filereadable(), because they do not understand the
" escaping for Ex commands.
" Note: On Windows, fnamemodify() doesn't convert path separators to
" backslashes. We don't force that neither, as forward slashes work just as
" well and there is even less potential for problems.
"* ASSUMPTIONS / PRECONDITIONS:
" ? List of any external variable, control, or other element whose state affects this procedure.
"* EFFECTS / POSTCONDITIONS:
" ? List of the procedure's effect on each external variable, control, or other element.
"* INPUTS:
" a:exfilespec Escaped filespec to be passed as a {file} argument to an Ex
" command.
" a:isMakeFullPath Flag whether the filespec should also be expanded to a
" full path, or kept in whatever form it currently is.
"* RETURN VALUES:
" Unescaped, normal filespec.
"*******************************************************************************
let l:isMakeFullPath = (a:0 ? a:1 : 0)
return fnamemodify(a:exfilespec, ':gs+\\\([ \t\n*?`%#''"|!<' . (ingo#os#IsWinOrDos() ? '' : '[{$\\') . ']\)+\1+' . (l:isMakeFullPath ? ':p' : ''))
endfunction
function! ingo#escape#file#autocmdescape( filespec )
"******************************************************************************
"* PURPOSE:
" Escape a normal filespec syntax so that it can be used in an :autocmd.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:filespec Normal filespec or file pattern.
"* RETURN VALUES:
" Escaped filespec to be passed as a {pat} argument to :autocmd.
"******************************************************************************
let l:filespec = a:filespec
if ingo#os#IsWinOrDos()
" Windows: Replace backslashes in filespec with forward slashes.
" Otherwise, the autocmd won't match the filespec.
let l:filespec = tr(l:filespec, '\', '/')
endif
" Escape spaces in filespec.
" Otherwise, the autocmd will be parsed wrongly, taking only the first part
" of the filespec as the file and interpreting the remainder of the filespec
" as part of the command.
return escape(l:filespec, ' ')
endfunction
function! ingo#escape#file#wildcardescape( filespec )
"******************************************************************************
"* PURPOSE:
" Escape a normal filespec for (literal) use in glob(). Escapes [, ?, * and
" **.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:filespec Normal filespec
"* RETURN VALUES:
" Escaped filespec to be passed as an argument to glob().
"******************************************************************************
" On Unix, * and ? can be escaped via backslash; this doesn't work on
" Windows, though, so we use the alternative [*]. We only need to ensure
" that the wildcard is deactivated, as Windows file systems cannot contain
" literal * and ? characters, anyway.
if ingo#os#IsWinOrDos()
return substitute(a:filespec, '[[?*]', '[&]', 'g')
else
return substitute(escape(a:filespec, '?*'), '[[]', '[[]', 'g')
endif
endfunction
function! ingo#escape#file#CmdlineSpecialEscape( filespec ) abort
"******************************************************************************
"* PURPOSE:
" Just escape |cmdline-special| symbols (%, #, <) to work around the bug that
" when defining a custom command with -nargs=+ -complete=file, these lose
" their escaping.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:filespec Normal filespec, from a <q-args> of a :command -nargs=+
" -complete=file
"* RETURN VALUES:
" Escaped filespec to the appended to an Ex command (without further
" fnameescape()).
"******************************************************************************
" Note: Everything starting with < is escaped, though strictly only the
" actual <cword> etc. variants would require escaping).
return substitute(a:filespec, '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<![%#<]', '\\&', 'g')
endfunction
" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :