6e679d6d4f
--HG-- extra : rebase_source : 17ea9665bc826a9365264122781e1a7f99948b34
309 lines
9.6 KiB
VimL
309 lines
9.6 KiB
VimL
"=============================================================================
|
||
" $Id: path.vim 237 2010-06-01 00:44:35Z luc.hermitte $
|
||
" File: autoload/lh/path.vim {{{1
|
||
" Author: Luc Hermitte <EMAIL:hermitte {at} free {dot} fr>
|
||
" <URL:http://code.google.com/p/lh-vim/>
|
||
" Version: 2.2.1
|
||
" Created: 23rd Jan 2007
|
||
" Last Update: 11th Feb 2008
|
||
"------------------------------------------------------------------------
|
||
" Description:
|
||
" Functions related to the handling of pathnames
|
||
"
|
||
"------------------------------------------------------------------------
|
||
" Installation:
|
||
" Drop this file into {rtp}/autoload/lh
|
||
" Requires Vim7+
|
||
" History:
|
||
" v 1.0.0 First Version
|
||
" (*) Functions moved from searchInRuntimeTime
|
||
" v 2.0.1
|
||
" (*) lh#path#Simplify() becomes like |simplify()| except for trailing
|
||
" v 2.0.2
|
||
" (*) lh#path#SelectOne()
|
||
" (*) lh#path#ToRelative()
|
||
" v 2.0.3
|
||
" (*) lh#path#GlobAsList()
|
||
" v 2.0.4
|
||
" (*) lh#path#StripStart()
|
||
" v 2.0.5
|
||
" (*) lh#path#StripStart() interprets '.' as getcwd()
|
||
" v 2.2.0
|
||
" (*) new functions: lh#path#common(), lh#path#to_dirname(),
|
||
" lh#path#depth(), lh#path#relative_to(), lh#path#to_regex(),
|
||
" lh#path#find()
|
||
" (*) lh#path#simplify() fixed
|
||
" (*) lh#path#to_relative() use simplify()
|
||
" TODO:
|
||
" (*) Decide what #depth('../../bar') shall return
|
||
" (*) Fix #simplify('../../bar')
|
||
" }}}1
|
||
"=============================================================================
|
||
|
||
|
||
"=============================================================================
|
||
" Avoid global reinclusion {{{1
|
||
let s:cpo_save=&cpo
|
||
set cpo&vim
|
||
|
||
"=============================================================================
|
||
" ## Functions {{{1
|
||
" # Debug {{{2
|
||
let s:verbose = 0
|
||
function! lh#path#verbose(...)
|
||
if a:0 > 0 | let s:verbose = a:1 | endif
|
||
return s:verbose
|
||
endfunction
|
||
|
||
function! s:Verbose(expr)
|
||
if s:verbose
|
||
echomsg a:expr
|
||
endif
|
||
endfunction
|
||
|
||
function! lh#path#debug(expr)
|
||
return eval(a:expr)
|
||
endfunction
|
||
|
||
"=============================================================================
|
||
" # Public {{{2
|
||
" Function: lh#path#simplify({pathname}) {{{3
|
||
" Like |simplify()|, but also strip the leading './'
|
||
" It seems unable to simplify '..\' when compiled without +shellslash
|
||
function! lh#path#simplify(pathname)
|
||
let pathname = simplify(a:pathname)
|
||
let pathname = substitute(pathname, '^\%(\.[/\\]\)\+', '', '')
|
||
let pathname = substitute(pathname, '\([/\\]\)\%(\.[/\\]\)\+', '\1', 'g')
|
||
let pwd = getcwd().'/'
|
||
let pathname = substitute(pathname, '^'.lh#path#to_regex(pwd), '', 'g')
|
||
return pathname
|
||
endfunction
|
||
function! lh#path#Simplify(pathname)
|
||
return lh#path#simplify(a:pathname)
|
||
endfunction
|
||
|
||
" Function: lh#path#common({pathnames}) {{{3
|
||
" Find the common leading path between all pathnames
|
||
function! lh#path#common(pathnames)
|
||
" assert(len(pathnames)) > 1
|
||
let common = a:pathnames[0]
|
||
let i = 1
|
||
while i < len(a:pathnames)
|
||
let fcrt = a:pathnames[i]
|
||
" pathnames should not contain @
|
||
let common = matchstr(common.'@@'.fcrt, '^\zs\(.*[/\\]\)\ze.\{-}@@\1.*$')
|
||
if strlen(common) == 0
|
||
" No need to further checks
|
||
break
|
||
endif
|
||
let i += 1
|
||
endwhile
|
||
return common
|
||
endfunction
|
||
|
||
" Function: lh#path#strip_common({pathnames}) {{{3
|
||
" Find the common leading path between all pathnames, and strip it
|
||
function! lh#path#strip_common(pathnames)
|
||
" assert(len(pathnames)) > 1
|
||
let common = lh#path#common(a:pathnames)
|
||
let l = strlen(common)
|
||
if l == 0
|
||
return a:pathnames
|
||
else
|
||
let pathnames = a:pathnames
|
||
call map(pathnames, 'strpart(v:val, '.l.')' )
|
||
return pathnames
|
||
endif
|
||
endfunction
|
||
function! lh#path#StripCommon(pathnames)
|
||
return lh#path#strip_common(a:pathnames)
|
||
endfunction
|
||
|
||
" Function: lh#path#is_absolute_path({path}) {{{3
|
||
function! lh#path#is_absolute_path(path)
|
||
return a:path =~ '^/'
|
||
\ . '\|^[a-zA-Z]:[/\\]'
|
||
\ . '\|^[/\\]\{2}'
|
||
" Unix absolute path
|
||
" or Windows absolute path
|
||
" or UNC path
|
||
endfunction
|
||
function! lh#path#IsAbsolutePath(path)
|
||
return lh#path#is_absolute_path(a:path)
|
||
endfunction
|
||
|
||
" Function: lh#path#is_url({path}) {{{3
|
||
function! lh#path#is_url(path)
|
||
" todo: support UNC paths and other urls
|
||
return a:path =~ '^\%(https\=\|s\=ftp\|dav\|fetch\|file\|rcp\|rsynch\|scp\)://'
|
||
endfunction
|
||
function! lh#path#IsURL(path)
|
||
return lh#path#is_url(a:path)
|
||
endfunction
|
||
|
||
" Function: lh#path#select_one({pathnames},{prompt}) {{{3
|
||
function! lh#path#select_one(pathnames, prompt)
|
||
if len(a:pathnames) > 1
|
||
let simpl_pathnames = deepcopy(a:pathnames)
|
||
let simpl_pathnames = lh#path#strip_common(simpl_pathnames)
|
||
let simpl_pathnames = [ '&Cancel' ] + simpl_pathnames
|
||
" Consider guioptions+=c is case of difficulties with the gui
|
||
let selection = confirm(a:prompt, join(simpl_pathnames,"\n"), 1, 'Question')
|
||
let file = (selection == 1) ? '' : a:pathnames[selection-2]
|
||
return file
|
||
elseif len(a:pathnames) == 0
|
||
return ''
|
||
else
|
||
return a:pathnames[0]
|
||
endif
|
||
endfunction
|
||
function! lh#path#SelectOne(pathnames, prompt)
|
||
return lh#path#select_one(a:pathnames, a:prompt)
|
||
endfunction
|
||
|
||
" Function: lh#path#to_relative({pathname}) {{{3
|
||
function! lh#path#to_relative(pathname)
|
||
let newpath = fnamemodify(a:pathname, ':p:.')
|
||
let newpath = simplify(newpath)
|
||
return newpath
|
||
endfunction
|
||
function! lh#path#ToRelative(pathname)
|
||
return lh#path#to_relative(a:pathname)
|
||
endfunction
|
||
|
||
" Function: lh#path#to_dirname({dirname}) {{{3
|
||
" todo: use &shellslash
|
||
function! lh#path#to_dirname(dirname)
|
||
let dirname = a:dirname . (a:dirname[-1:] =~ '[/\\]' ? '' : '/')
|
||
return dirname
|
||
endfunction
|
||
|
||
" Function: lh#path#depth({dirname}) {{{3
|
||
" todo: make a choice about "negative" paths like "../../foo"
|
||
function! lh#path#depth(dirname)
|
||
if empty(a:dirname) | return 0 | endif
|
||
let dirname = lh#path#to_dirname(a:dirname)
|
||
let dirname = lh#path#simplify(dirname)
|
||
if lh#path#is_absolute_path(dirname)
|
||
let dirname = matchstr(dirname, '.\{-}[/\\]\zs.*')
|
||
endif
|
||
let depth = len(substitute(dirname, '[^/\\]\+[/\\]', '<27>', 'g'))
|
||
return depth
|
||
endfunction
|
||
|
||
" Function: lh#path#relative_to({from}, {to}) {{{3
|
||
" @param two directories
|
||
" @return a directories delta that ends with a '/' (may depends on
|
||
" &shellslash)
|
||
function! lh#path#relative_to(from, to)
|
||
" let from = fnamemodify(a:from, ':p')
|
||
" let to = fnamemodify(a:to , ':p')
|
||
let from = lh#path#to_dirname(a:from)
|
||
let to = lh#path#to_dirname(a:to )
|
||
let [from, to] = lh#path#strip_common([from, to])
|
||
let nb_up = lh#path#depth(from)
|
||
return repeat('../', nb_up).to
|
||
|
||
" cannot rely on :cd (as it alters things, and doesn't work with
|
||
" non-existant paths)
|
||
let pwd = getcwd()
|
||
exe 'cd '.a:to
|
||
let res = lh#path#to_relative(a:from)
|
||
exe 'cd '.pwd
|
||
return res
|
||
endfunction
|
||
|
||
" Function: lh#path#glob_as_list({pathslist}, {expr}) {{{3
|
||
function! s:GlobAsList(pathslist, expr)
|
||
let sResult = globpath(a:pathslist, a:expr)
|
||
let lResult = split(sResult, '\n')
|
||
" workaround a non feature of wildignore: it does not ignore directories
|
||
for ignored_pattern in split(&wildignore,',')
|
||
if stridx(ignored_pattern,'/') != -1
|
||
call filter(lResult, 'v:val !~ '.string(ignored_pattern))
|
||
endif
|
||
endfor
|
||
return lResult
|
||
endfunction
|
||
|
||
function! lh#path#glob_as_list(pathslist, expr)
|
||
if type(a:expr) == type('string')
|
||
return s:GlobAsList(a:pathslist, a:expr)
|
||
elseif type(a:expr) == type([])
|
||
let res = []
|
||
for expr in a:expr
|
||
call extend(res, s:GlobAsList(a:pathslist, expr))
|
||
endfor
|
||
return res
|
||
else
|
||
throw "Unexpected type for a:expression"
|
||
endif
|
||
endfunction
|
||
function! lh#path#GlobAsList(pathslist, expr)
|
||
return lh#path#glob_as_list(a:pathslist, a:expr)
|
||
endfunction
|
||
|
||
" Function: lh#path#strip_start({pathname}, {pathslist}) {{{3
|
||
" Strip occurrence of paths from {pathslist} in {pathname}
|
||
" @param[in] {pathname} name to simplify
|
||
" @param[in] {pathslist} list of pathname (can be a |string| of pathnames
|
||
" separated by ",", of a |List|).
|
||
function! lh#path#strip_start(pathname, pathslist)
|
||
if type(a:pathslist) == type('string')
|
||
" let strip_re = escape(a:pathslist, '\\.')
|
||
" let strip_re = '^' . substitute(strip_re, ',', '\\|^', 'g')
|
||
let pathslist = split(a:pathslist, ',')
|
||
elseif type(a:pathslist) == type([])
|
||
let pathslist = deepcopy(a:pathslist)
|
||
else
|
||
throw "Unexpected type for a:pathname"
|
||
endif
|
||
|
||
" apply a realpath like operation
|
||
let nb_paths = len(pathslist) " set before the loop
|
||
let i = 0
|
||
while i != nb_paths
|
||
if pathslist[i] =~ '^\.\%(/\|$\)'
|
||
let path2 = getcwd().pathslist[i][1:]
|
||
call add(pathslist, path2)
|
||
endif
|
||
let i += 1
|
||
endwhile
|
||
" replace path separators by a regex that can match them
|
||
call map(pathslist, 'substitute(v:val, "[\\\\/]", "[\\\\/]", "g")')
|
||
" echomsg string(pathslist)
|
||
" escape .
|
||
call map(pathslist, '"^".escape(v:val, ".")')
|
||
" build the strip regex
|
||
let strip_re = join(pathslist, '\|')
|
||
" echomsg strip_re
|
||
let res = substitute(a:pathname, '\%('.strip_re.'\)[/\\]\=', '', '')
|
||
return res
|
||
endfunction
|
||
function! lh#path#StripStart(pathname, pathslist)
|
||
return lh#path#strip_start(a:pathname, a:pathslist)
|
||
endfunction
|
||
|
||
" Function: lh#path#to_regex({pathname}) {{{3
|
||
function! lh#path#to_regex(path)
|
||
let regex = substitute(a:path, '[/\\]', '[/\\\\]', 'g')
|
||
return regex
|
||
endfunction
|
||
|
||
" Function: lh#path#find({pathname}, {regex}) {{{3
|
||
function! lh#path#find(paths, regex)
|
||
let paths = (type(a:paths) == type([]))
|
||
\ ? (a:paths)
|
||
\ : split(a:paths,',')
|
||
for path in paths
|
||
if match(path ,a:regex) != -1
|
||
return path
|
||
endif
|
||
endfor
|
||
return ''
|
||
endfunction
|
||
"=============================================================================
|
||
let &cpo=s:cpo_save
|
||
"=============================================================================
|
||
" vim600: set fdm=marker:
|