1
0
Fork 0
mirror of synced 2024-12-23 07:20:42 -05:00
ultimate-vim/sources_non_forked/vim-go/autoload/go/util.vim

597 lines
17 KiB
VimL
Raw Normal View History

2018-12-17 06:28:27 -05:00
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
2015-07-13 06:22:46 -04:00
" PathSep returns the appropriate OS specific path separator.
2016-12-27 09:46:49 -05:00
function! go#util#PathSep() abort
2016-06-26 07:12:36 -04:00
if go#util#IsWin()
return '\'
endif
return '/'
2015-07-13 06:22:46 -04:00
endfunction
" PathListSep returns the appropriate OS specific path list separator.
2016-12-27 09:46:49 -05:00
function! go#util#PathListSep() abort
2016-06-26 07:12:36 -04:00
if go#util#IsWin()
return ";"
endif
return ":"
2015-07-13 06:22:46 -04:00
endfunction
" LineEnding returns the correct line ending, based on the current fileformat
2016-12-27 09:46:49 -05:00
function! go#util#LineEnding() abort
2016-06-26 07:12:36 -04:00
if &fileformat == 'dos'
return "\r\n"
elseif &fileformat == 'mac'
return "\r"
endif
2015-07-13 06:22:46 -04:00
2016-06-26 07:12:36 -04:00
return "\n"
2015-07-13 06:22:46 -04:00
endfunction
2016-07-03 07:53:59 -04:00
" Join joins any number of path elements into a single path, adding a
" Separator if necessary and returns the result
2016-12-27 09:46:49 -05:00
function! go#util#Join(...) abort
2016-07-03 07:53:59 -04:00
return join(a:000, go#util#PathSep())
endfunction
2015-07-13 06:22:46 -04:00
" IsWin returns 1 if current OS is Windows or 0 otherwise
2019-08-22 11:36:17 -04:00
" Note that has('win32') is always 1 when has('win64') is 1, so has('win32') is enough.
2016-12-27 09:46:49 -05:00
function! go#util#IsWin() abort
2019-08-22 11:36:17 -04:00
return has('win32')
2015-07-13 06:22:46 -04:00
endfunction
" IsMac returns 1 if current OS is macOS or 0 otherwise.
function! go#util#IsMac() abort
return has('mac') ||
\ has('macunix') ||
\ has('gui_macvim') ||
2018-06-14 06:31:12 -04:00
\ go#util#Exec(['uname'])[0] =~? '^darwin'
endfunction
2017-09-02 06:43:18 -04:00
" Checks if using:
" 1) Windows system,
" 2) And has cygpath executable,
" 3) And uses *sh* as 'shell'
function! go#util#IsUsingCygwinShell()
return go#util#IsWin() && executable('cygpath') && &shell =~ '.*sh.*'
endfunction
2018-06-14 06:31:12 -04:00
" Check if Vim jobs API is supported.
"
2018-08-25 12:13:42 -04:00
" The (optional) first parameter can be added to indicate the 'cwd' or 'env'
2018-06-14 06:31:12 -04:00
" parameters will be used, which wasn't added until a later version.
function! go#util#has_job(...) abort
2019-11-16 10:28:42 -05:00
return has('job') || has('nvim')
2016-12-27 09:46:49 -05:00
endfunction
2016-11-09 12:22:55 -05:00
let s:env_cache = {}
" env returns the go environment variable for the given key. Where key can be
" GOARCH, GOOS, GOROOT, etc... It caches the result and returns the cached
2017-03-07 12:04:28 -05:00
" version.
2016-12-27 09:46:49 -05:00
function! go#util#env(key) abort
2016-11-09 12:22:55 -05:00
let l:key = tolower(a:key)
if has_key(s:env_cache, l:key)
return s:env_cache[l:key]
endif
if executable('go')
let l:var = call('go#util#'.l:key, [])
if go#util#ShellError() != 0
call go#util#EchoError(printf("'go env %s' failed", toupper(l:key)))
return ''
endif
else
let l:var = eval("$".toupper(a:key))
endif
let s:env_cache[l:key] = l:var
return l:var
endfunction
2017-07-06 08:57:35 -04:00
" goarch returns 'go env GOARCH'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("goarch")'
2016-12-27 09:46:49 -05:00
function! go#util#goarch() abort
2018-06-14 06:31:12 -04:00
return substitute(s:exec(['go', 'env', 'GOARCH'])[0], '\n', '', 'g')
2016-05-14 07:57:54 -04:00
endfunction
2017-07-06 08:57:35 -04:00
" goos returns 'go env GOOS'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("goos")'
2016-12-27 09:46:49 -05:00
function! go#util#goos() abort
2018-06-14 06:31:12 -04:00
return substitute(s:exec(['go', 'env', 'GOOS'])[0], '\n', '', 'g')
2016-05-14 07:57:54 -04:00
endfunction
2017-07-06 08:57:35 -04:00
" goroot returns 'go env GOROOT'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("goroot")'
2016-12-27 09:46:49 -05:00
function! go#util#goroot() abort
2018-06-14 06:31:12 -04:00
return substitute(s:exec(['go', 'env', 'GOROOT'])[0], '\n', '', 'g')
2016-05-14 07:57:54 -04:00
endfunction
2017-07-06 08:57:35 -04:00
" gopath returns 'go env GOPATH'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("gopath")'
2016-12-27 09:46:49 -05:00
function! go#util#gopath() abort
2018-06-14 06:31:12 -04:00
return substitute(s:exec(['go', 'env', 'GOPATH'])[0], '\n', '', 'g')
2016-05-14 07:57:54 -04:00
endfunction
2018-11-01 06:03:42 -04:00
" gomod returns 'go env GOMOD'. gomod changes depending on the folder. Don't
" use go#util#env as it caches the value.
function! go#util#gomod() abort
return substitute(s:exec(['go', 'env', 'GOMOD'])[0], '\n', '', 'g')
endfunction
2016-12-27 09:46:49 -05:00
function! go#util#osarch() abort
2017-07-06 08:57:35 -04:00
return go#util#env("goos") . '_' . go#util#env("goarch")
2016-05-14 07:57:54 -04:00
endfunction
2019-05-17 10:09:13 -04:00
" go#util#ModuleRoot returns the root directory of the module of the current
" buffer.
function! go#util#ModuleRoot() abort
let [l:out, l:err] = go#util#ExecInDir(['go', 'env', 'GOMOD'])
if l:err != 0
return -1
endif
let l:module = split(l:out, '\n', 1)[0]
" When run with `GO111MODULE=on and not in a module directory, the module will be reported as /dev/null.
let l:fakeModule = '/dev/null'
if go#util#IsWin()
let l:fakeModule = 'NUL'
endif
if l:fakeModule == l:module
return expand('%:p:h')
endif
return fnamemodify(l:module, ':p:h')
endfunction
" Run a shell command.
"
" It will temporary set the shell to /bin/sh for Unix-like systems if possible,
" so that we always use a standard POSIX-compatible Bourne shell (and not e.g.
" csh, fish, etc.) See #988 and #1276.
function! s:system(cmd, ...) abort
2018-11-01 06:03:42 -04:00
" Preserve original shell, shellredir and shellcmdflag values
2016-08-02 08:48:32 -04:00
let l:shell = &shell
2017-05-26 05:30:32 -04:00
let l:shellredir = &shellredir
2018-11-01 06:03:42 -04:00
let l:shellcmdflag = &shellcmdflag
2017-05-26 05:30:32 -04:00
2016-08-02 08:48:32 -04:00
if !go#util#IsWin() && executable('/bin/sh')
2018-11-01 06:03:42 -04:00
set shell=/bin/sh shellredir=>%s\ 2>&1 shellcmdflag=-c
2016-08-02 08:48:32 -04:00
endif
try
return call('system', [a:cmd] + a:000)
2016-08-02 08:48:32 -04:00
finally
2017-05-26 05:30:32 -04:00
" Restore original values
2016-08-02 08:48:32 -04:00
let &shell = l:shell
2017-05-26 05:30:32 -04:00
let &shellredir = l:shellredir
2018-11-01 06:03:42 -04:00
let &shellcmdflag = l:shellcmdflag
2016-08-02 08:48:32 -04:00
endtry
2016-05-14 07:57:54 -04:00
endfunction
" System runs a shell command "str". Every arguments after "str" is passed to
" stdin.
function! go#util#System(str, ...) abort
return call('s:system', [a:str] + a:000)
endfunction
" Exec runs a shell command "cmd", which must be a list, one argument per item.
" Every list entry will be automatically shell-escaped
" Every other argument is passed to stdin.
function! go#util#Exec(cmd, ...) abort
if len(a:cmd) == 0
call go#util#EchoError("go#util#Exec() called with empty a:cmd")
2018-06-14 06:31:12 -04:00
return ['', 1]
endif
2018-06-14 06:31:12 -04:00
let l:bin = a:cmd[0]
2018-07-19 08:52:53 -04:00
" Lookup the full path, respecting settings such as 'go_bin_path'. On errors,
" CheckBinPath will show a warning for us.
2018-06-14 06:31:12 -04:00
let l:bin = go#path#CheckBinPath(l:bin)
if empty(l:bin)
2018-06-14 06:31:12 -04:00
return ['', 1]
endif
2018-07-19 08:52:53 -04:00
" Finally execute the command using the full, resolved path. Do not pass the
" unmodified command as the correct program might not exist in $PATH.
return call('s:exec', [[l:bin] + a:cmd[1:]] + a:000)
2018-06-14 06:31:12 -04:00
endfunction
2019-03-08 06:04:56 -05:00
function! go#util#ExecInDir(cmd, ...) abort
if !isdirectory(expand("%:p:h"))
return ['', 1]
endif
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
try
execute cd . fnameescape(expand("%:p:h"))
let [l:out, l:err] = call('go#util#Exec', [a:cmd] + a:000)
finally
execute cd . fnameescape(l:dir)
endtry
return [l:out, l:err]
endfunction
2018-06-14 06:31:12 -04:00
function! s:exec(cmd, ...) abort
let l:bin = a:cmd[0]
let l:cmd = go#util#Shelljoin([l:bin] + a:cmd[1:])
if go#util#HasDebug('shell-commands')
call go#util#EchoInfo('shell command: ' . l:cmd)
endif
2018-06-14 06:31:12 -04:00
let l:out = call('s:system', [l:cmd] + a:000)
return [l:out, go#util#ShellError()]
endfunction
2016-12-27 09:46:49 -05:00
function! go#util#ShellError() abort
return v:shell_error
2016-05-14 07:57:54 -04:00
endfunction
2015-07-13 06:22:46 -04:00
" StripPath strips the path's last character if it's a path separator.
" example: '/foo/bar/' -> '/foo/bar'
2016-12-27 09:46:49 -05:00
function! go#util#StripPathSep(path) abort
2016-06-26 07:12:36 -04:00
let last_char = strlen(a:path) - 1
if a:path[last_char] == go#util#PathSep()
return strpart(a:path, 0, last_char)
endif
2015-07-13 06:22:46 -04:00
2016-06-26 07:12:36 -04:00
return a:path
2015-07-13 06:22:46 -04:00
endfunction
2016-03-14 06:04:57 -04:00
" StripTrailingSlash strips the trailing slash from the given path list.
" example: ['/foo/bar/'] -> ['/foo/bar']
2016-12-27 09:46:49 -05:00
function! go#util#StripTrailingSlash(paths) abort
2016-03-14 06:04:57 -04:00
return map(copy(a:paths), 'go#util#StripPathSep(v:val)')
endfunction
2015-12-08 08:20:04 -05:00
" Shelljoin returns a shell-safe string representation of arglist. The
" {special} argument of shellescape() may optionally be passed.
2016-12-27 09:46:49 -05:00
function! go#util#Shelljoin(arglist, ...) abort
2016-06-26 07:12:36 -04:00
try
let ssl_save = &shellslash
set noshellslash
if a:0
return join(map(copy(a:arglist), 'shellescape(v:val, ' . a:1 . ')'), ' ')
endif
2016-02-20 08:13:10 -05:00
2016-07-16 14:30:35 -04:00
return join(map(copy(a:arglist), 'shellescape(v:val)'), ' ')
finally
let &shellslash = ssl_save
endtry
2015-12-16 08:53:53 -05:00
endfunction
2016-05-14 07:57:54 -04:00
fu! go#util#Shellescape(arg)
2016-07-16 14:30:35 -04:00
try
let ssl_save = &shellslash
set noshellslash
return shellescape(a:arg)
finally
let &shellslash = ssl_save
endtry
2016-05-14 07:57:54 -04:00
endf
2016-02-20 08:13:10 -05:00
" Shelllist returns a shell-safe representation of the items in the given
2015-12-16 08:53:53 -05:00
" arglist. The {special} argument of shellescape() may optionally be passed.
2016-12-27 09:46:49 -05:00
function! go#util#Shelllist(arglist, ...) abort
2016-07-16 14:30:35 -04:00
try
let ssl_save = &shellslash
set noshellslash
if a:0
return map(copy(a:arglist), 'shellescape(v:val, ' . a:1 . ')')
endif
return map(copy(a:arglist), 'shellescape(v:val)')
finally
let &shellslash = ssl_save
endtry
2015-12-16 08:53:53 -05:00
endfunction
2016-03-20 14:01:44 -04:00
" Returns the byte offset for line and column
2016-12-27 09:46:49 -05:00
function! go#util#Offset(line, col) abort
2016-07-16 14:30:35 -04:00
if &encoding != 'utf-8'
let sep = go#util#LineEnding()
let buf = a:line == 1 ? '' : (join(getline(1, a:line-1), sep) . sep)
let buf .= a:col == 1 ? '' : getline('.')[:a:col-2]
return len(iconv(buf, &encoding, 'utf-8'))
endif
return line2byte(a:line) + (a:col-2)
2016-03-20 14:01:44 -04:00
endfunction
"
" Returns the byte offset for the cursor
2016-12-27 09:46:49 -05:00
function! go#util#OffsetCursor() abort
2016-07-16 14:30:35 -04:00
return go#util#Offset(line('.'), col('.'))
2016-03-20 14:01:44 -04:00
endfunction
2016-04-12 04:31:09 -04:00
" Windo is like the built-in :windo, only it returns to the window the command
" was issued from
2016-12-27 09:46:49 -05:00
function! go#util#Windo(command) abort
2016-07-16 14:30:35 -04:00
let s:currentWindow = winnr()
try
execute "windo " . a:command
finally
execute s:currentWindow. "wincmd w"
unlet s:currentWindow
endtry
endfunction
" snippetcase converts the given word to given preferred snippet setting type
" case.
2016-12-27 09:46:49 -05:00
function! go#util#snippetcase(word) abort
2018-06-14 06:31:12 -04:00
let l:snippet_case = go#config#AddtagsTransform()
2016-07-16 14:30:35 -04:00
if l:snippet_case == "snakecase"
return go#util#snakecase(a:word)
elseif l:snippet_case == "camelcase"
return go#util#camelcase(a:word)
else
return a:word " do nothing
endif
2016-04-12 04:31:09 -04:00
endfunction
2016-07-16 14:30:35 -04:00
" snakecase converts a string to snake case. i.e: FooBar -> foo_bar
" Copied from tpope/vim-abolish
2016-12-27 09:46:49 -05:00
function! go#util#snakecase(word) abort
let word = substitute(a:word, '::', '/', 'g')
let word = substitute(word, '\(\u\+\)\(\u\l\)', '\1_\2', 'g')
let word = substitute(word, '\(\l\|\d\)\(\u\)', '\1_\2', 'g')
let word = substitute(word, '[.-]', '_', 'g')
2016-07-16 14:30:35 -04:00
let word = tolower(word)
return word
endfunction
" camelcase converts a string to camel case. e.g. FooBar or foo_bar will become
" fooBar.
" Copied from tpope/vim-abolish.
2016-12-27 09:46:49 -05:00
function! go#util#camelcase(word) abort
2016-07-16 14:30:35 -04:00
let word = substitute(a:word, '-', '_', 'g')
if word !~# '_' && word =~# '\l'
return substitute(word, '^.', '\l&', '')
2016-07-16 14:30:35 -04:00
else
return substitute(word, '\C\(_\)\=\(.\)', '\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))','g')
2016-07-16 14:30:35 -04:00
endif
endfunction
" pascalcase converts a string to 'PascalCase'. e.g. fooBar or foo_bar will
" become FooBar.
function! go#util#pascalcase(word) abort
let word = go#util#camelcase(a:word)
return toupper(word[0]) . word[1:]
endfunction
" Echo a message to the screen and highlight it with the group in a:hi.
2016-12-27 09:46:49 -05:00
"
" The message can be a list or string; every line with be :echomsg'd separately.
function! s:echo(msg, hi)
let l:msg = []
if type(a:msg) != type([])
let l:msg = split(a:msg, "\n")
else
let l:msg = a:msg
endif
" Tabs display as ^I or <09>, so manually expand them.
let l:msg = map(l:msg, 'substitute(v:val, "\t", " ", "")')
exe 'echohl ' . a:hi
for line in l:msg
echom "vim-go: " . line
endfor
echohl None
2015-12-08 08:20:04 -05:00
endfunction
2015-12-16 08:53:53 -05:00
function! go#util#EchoSuccess(msg)
call s:echo(a:msg, 'Function')
endfunction
2015-12-16 08:53:53 -05:00
function! go#util#EchoError(msg)
call s:echo(a:msg, 'ErrorMsg')
2015-12-16 08:53:53 -05:00
endfunction
function! go#util#EchoWarning(msg)
call s:echo(a:msg, 'WarningMsg')
2015-12-16 08:53:53 -05:00
endfunction
function! go#util#EchoProgress(msg)
2018-03-31 10:56:26 -04:00
redraw
call s:echo(a:msg, 'Identifier')
2016-12-27 09:46:49 -05:00
endfunction
function! go#util#EchoInfo(msg)
call s:echo(a:msg, 'Debug')
2015-12-16 08:53:53 -05:00
endfunction
" Get all lines in the buffer as a a list.
2017-02-11 08:01:38 -05:00
function! go#util#GetLines()
let buf = getline(1, '$')
if &encoding != 'utf-8'
let buf = map(buf, 'iconv(v:val, &encoding, "utf-8")')
endif
if &l:fileformat == 'dos'
" XXX: line2byte() depend on 'fileformat' option.
" so if fileformat is 'dos', 'buf' must include '\r'.
let buf = map(buf, 'v:val."\r"')
endif
return buf
endfunction
" Convert the current buffer to the "archive" format of
" golang.org/x/tools/go/buildutil:
" https://godoc.org/golang.org/x/tools/go/buildutil#ParseOverlayArchive
"
" > The archive consists of a series of files. Each file consists of a name, a
" > decimal file size and the file contents, separated by newlinews. No newline
" > follows after the file contents.
function! go#util#archive()
let l:buffer = join(go#util#GetLines(), "\n")
return expand("%:p:gs!\\!/!") . "\n" . strlen(l:buffer) . "\n" . l:buffer
endfunction
" Make a named temporary directory which starts with "prefix".
"
" Unfortunately Vim's tempname() is not portable enough across various systems;
" see: https://github.com/mattn/vim-go/pull/3#discussion_r138084911
function! go#util#tempdir(prefix) abort
" See :help tempfile
if go#util#IsWin()
let l:dirs = [$TMP, $TEMP, 'c:\tmp', 'c:\temp']
else
let l:dirs = [$TMPDIR, '/tmp', './', $HOME]
endif
let l:dir = ''
for l:d in dirs
if !empty(l:d) && filewritable(l:d) == 2
let l:dir = l:d
break
endif
endfor
if l:dir == ''
2018-03-31 10:56:26 -04:00
call go#util#EchoError('Unable to find directory to store temporary directory in')
return
endif
" Not great randomness, but "good enough" for our purpose here.
2019-08-22 11:36:17 -04:00
let l:rnd = sha256(printf('%s%s', reltimestr(reltime()), fnamemodify(bufname(''), ":p")))
let l:tmp = printf("%s/%s%s", l:dir, a:prefix, l:rnd)
call mkdir(l:tmp, 'p', 0700)
return l:tmp
endfunction
2018-03-31 10:56:26 -04:00
" Report if the user enabled a debug flag in g:go_debug.
function! go#util#HasDebug(flag)
2018-06-14 06:31:12 -04:00
return index(go#config#Debug(), a:flag) >= 0
2018-03-31 10:56:26 -04:00
endfunction
2019-03-08 06:04:56 -05:00
function! go#util#OpenBrowser(url) abort
let l:cmd = go#config#PlayBrowserCommand()
if len(l:cmd) == 0
redraw
echohl WarningMsg
echo "It seems that you don't have general web browser. Open URL below."
echohl None
echo a:url
return
endif
" if setting starts with a !.
if l:cmd =~ '^!'
let l:cmd = substitute(l:cmd, '%URL%', '\=escape(shellescape(a:url), "#")', 'g')
silent! exec l:cmd
elseif cmd =~ '^:[A-Z]'
let l:cmd = substitute(l:cmd, '%URL%', '\=escape(a:url,"#")', 'g')
exec l:cmd
else
let l:cmd = substitute(l:cmd, '%URL%', '\=shellescape(a:url)', 'g')
call go#util#System(l:cmd)
endif
endfunction
function! go#util#ParseErrors(lines) abort
let errors = []
for line in a:lines
let fatalerrors = matchlist(line, '^\(fatal error:.*\)$')
let tokens = matchlist(line, '^\s*\(.\{-}\):\(\d\+\):\s*\(.*\)')
if !empty(fatalerrors)
call add(errors, {"text": fatalerrors[1]})
elseif !empty(tokens)
" strip endlines of form ^M
let out = substitute(tokens[3], '\r$', '', '')
call add(errors, {
\ "filename" : fnamemodify(tokens[1], ':p'),
\ "lnum" : tokens[2],
\ "text" : out,
\ })
elseif !empty(errors)
" Preserve indented lines.
" This comes up especially with multi-line test output.
if match(line, '^\s') >= 0
call add(errors, {"text": substitute(line, '\r$', '', '')})
endif
endif
endfor
return errors
endfunction
function! go#util#ShowInfo(info)
if empty(a:info)
return
endif
echo "vim-go: " | echohl Function | echon a:info | echohl None
endfunction
2019-05-17 10:09:13 -04:00
" go#util#SetEnv takes the name of an environment variable and what its value
" should be and returns a function that will restore it to its original value.
function! go#util#SetEnv(name, value) abort
let l:state = {}
if len(a:name) == 0
return function('s:noop', [], l:state)
endif
let l:remove = 0
if exists('$' . a:name)
let l:oldvalue = eval('$' . a:name)
else
let l:remove = 1
endif
2019-08-22 11:36:17 -04:00
" wrap the value in single quotes so that it will work on windows when there
" are backslashes present in the value (e.g. $PATH).
call execute('let $' . a:name . " = '" . a:value . "'")
2019-05-17 10:09:13 -04:00
if l:remove
2019-11-16 10:28:42 -05:00
return function('s:unset', [a:name], l:state)
2019-05-17 10:09:13 -04:00
endif
return function('go#util#SetEnv', [a:name, l:oldvalue], l:state)
endfunction
2019-11-16 10:28:42 -05:00
function! go#util#ClearGroupFromMatches(group) abort
if !exists("*matchaddpos")
return 0
endif
let l:cleared = 0
let m = getmatches()
for item in m
if item['group'] == a:group
call matchdelete(item['id'])
let l:cleared = 1
endif
endfor
return l:cleared
endfunction
function! s:unset(name) abort
try
" unlet $VAR was introducted in Vim 8.0.1832, which is newer than the
" minimal version that vim-go supports. Set the environment variable to
" the empty string in that case. It's not perfect, but it will work fine
" for most things, and is really the best alternative that's available.
if !has('patch-8.0.1832')
call go#util#SetEnv(a:name, '')
return
endif
call execute('unlet $' . a:name)
catch
call go#util#EchoError(printf('could not unset $%s: %s', a:name, v:exception))
endtry
endfunction
2019-05-17 10:09:13 -04:00
function! s:noop(...) abort dict
endfunction
2018-12-17 06:28:27 -05:00
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
2016-06-26 07:12:36 -04:00
" vim: sw=2 ts=2 et