784 lines
23 KiB
VimL
784 lines
23 KiB
VimL
" don't spam the user when Vim is started in Vi compatibility mode
|
|
let s:cpo_save = &cpo
|
|
set cpo&vim
|
|
|
|
" PathSep returns the appropriate OS specific path separator.
|
|
function! go#util#PathSep() abort
|
|
if go#util#IsWin()
|
|
return '\'
|
|
endif
|
|
return '/'
|
|
endfunction
|
|
|
|
" PathListSep returns the appropriate OS specific path list separator.
|
|
function! go#util#PathListSep() abort
|
|
if go#util#IsWin()
|
|
return ";"
|
|
endif
|
|
return ":"
|
|
endfunction
|
|
|
|
" LineEnding returns the correct line ending, based on the current fileformat
|
|
function! go#util#LineEnding() abort
|
|
if &fileformat == 'dos'
|
|
return "\r\n"
|
|
elseif &fileformat == 'mac'
|
|
return "\r"
|
|
endif
|
|
|
|
return "\n"
|
|
endfunction
|
|
|
|
" Join joins any number of path elements into a single path, adding a
|
|
" Separator if necessary and returns the result
|
|
function! go#util#Join(...) abort
|
|
return join(a:000, go#util#PathSep())
|
|
endfunction
|
|
|
|
" IsWin returns 1 if current OS is Windows or 0 otherwise
|
|
" Note that has('win32') is always 1 when has('win64') is 1, so has('win32') is enough.
|
|
function! go#util#IsWin() abort
|
|
return has('win32')
|
|
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') ||
|
|
\ go#util#Exec(['uname'])[0] =~? '^darwin'
|
|
endfunction
|
|
|
|
" 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
|
|
|
|
" Check if Vim jobs API is supported.
|
|
"
|
|
" The (optional) first parameter can be added to indicate the 'cwd' or 'env'
|
|
" parameters will be used, which wasn't added until a later version.
|
|
function! go#util#has_job(...) abort
|
|
return has('job') || has('nvim')
|
|
endfunction
|
|
|
|
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
|
|
" version.
|
|
function! go#util#env(key) abort
|
|
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
|
|
|
|
" gobin returns 'go env GOBIN'. This is an internal function and shouldn't be
|
|
" used. Use go#util#env('gobin') instead.
|
|
function! go#util#gobin() abort
|
|
return substitute(s:exec(['go', 'env', 'GOBIN'])[0], '\n', '', 'g')
|
|
endfunction
|
|
|
|
" goarch returns 'go env GOARCH'. This is an internal function and shouldn't
|
|
" be used. Use go#util#env('goarch') instead.
|
|
function! go#util#goarch() abort
|
|
return substitute(s:exec(['go', 'env', 'GOARCH'])[0], '\n', '', 'g')
|
|
endfunction
|
|
|
|
" goos returns 'go env GOOS'. This is an internal function and shouldn't be
|
|
" used. Use go#util#env('goos') instead.
|
|
function! go#util#goos() abort
|
|
return substitute(s:exec(['go', 'env', 'GOOS'])[0], '\n', '', 'g')
|
|
endfunction
|
|
|
|
" goroot returns 'go env GOROOT'. This is an internal function and shouldn't
|
|
" be used. Use go#util#env('goroot') instead.
|
|
function! go#util#goroot() abort
|
|
return substitute(s:exec(['go', 'env', 'GOROOT'])[0], '\n', '', 'g')
|
|
endfunction
|
|
|
|
" gopath returns 'go env GOPATH'. This is an internal function and shouldn't
|
|
" be used. Use go#util#env('gopath') instead.
|
|
function! go#util#gopath() abort
|
|
return substitute(s:exec(['go', 'env', 'GOPATH'])[0], '\n', '', 'g')
|
|
endfunction
|
|
|
|
" 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
|
|
|
|
" gomodcache returns 'go env GOMODCACHE'. This is an internal function and
|
|
" shouldn't be used. Use go#util#env('gomodcache') instead.
|
|
function! go#util#gomodcache() abort
|
|
return substitute(s:exec(['go', 'env', 'GOMODCACHE'])[0], '\n', '', 'g')
|
|
endfunction
|
|
|
|
" hostosarch returns the OS and ARCH values that the go binary is intended for.
|
|
function! go#util#hostosarch() abort
|
|
let [l:hostos, l:err] = s:exec(['go', 'env', 'GOHOSTOS'])
|
|
let [l:hostarch, l:err] = s:exec(['go', 'env', 'GOHOSTARCH'])
|
|
return [substitute(l:hostos, '\n', '', 'g'), substitute(l:hostarch, '\n', '', 'g')]
|
|
endfunction
|
|
|
|
" go#util#ModuleRoot returns the root directory of the module of the current
|
|
" buffer. An optional argument can be provided to check an arbitrary
|
|
" directory.
|
|
function! go#util#ModuleRoot(...) abort
|
|
let l:wd = ''
|
|
if a:0 > 0
|
|
let l:wd = go#util#Chdir(a:1)
|
|
endif
|
|
try
|
|
let [l:out, l:err] = go#util#ExecInDir(['go', 'env', 'GOMOD'])
|
|
if l:err != 0
|
|
return -1
|
|
endif
|
|
finally
|
|
if l:wd != ''
|
|
call go#util#Chdir(l:wd)
|
|
endif
|
|
endtry
|
|
|
|
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 resolve(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
|
|
" Preserve original shell, shellredir and shellcmdflag values
|
|
let l:shell = &shell
|
|
let l:shellredir = &shellredir
|
|
let l:shellcmdflag = &shellcmdflag
|
|
let l:shellquote = &shellquote
|
|
let l:shellxquote = &shellxquote
|
|
|
|
if !go#util#IsWin() && executable('/bin/sh')
|
|
set shell=/bin/sh shellredir=>%s\ 2>&1 shellcmdflag=-c
|
|
endif
|
|
|
|
if go#util#IsWin()
|
|
if executable($COMSPEC)
|
|
let &shell = $COMSPEC
|
|
set shellcmdflag=/C
|
|
set shellquote&
|
|
set shellxquote&
|
|
endif
|
|
endif
|
|
|
|
try
|
|
return call('system', [a:cmd] + a:000)
|
|
finally
|
|
" Restore original values
|
|
let &shell = l:shell
|
|
let &shellredir = l:shellredir
|
|
let &shellcmdflag = l:shellcmdflag
|
|
let &shellquote = l:shellquote
|
|
let &shellxquote = l:shellxquote
|
|
endtry
|
|
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")
|
|
return ['', 1]
|
|
endif
|
|
|
|
let l:bin = a:cmd[0]
|
|
|
|
" Lookup the full path, respecting settings such as 'go_bin_path'. On errors,
|
|
" CheckBinPath will show a warning for us.
|
|
let l:bin = go#path#CheckBinPath(l:bin)
|
|
if empty(l:bin)
|
|
return ['', 1]
|
|
endif
|
|
|
|
" 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)
|
|
endfunction
|
|
|
|
" ExecInDir will execute cmd with the working directory set to the current
|
|
" buffer's directory.
|
|
function! go#util#ExecInDir(cmd, ...) abort
|
|
let l:wd = expand('%:p:h')
|
|
return call('go#util#ExecInWorkDir', [a:cmd, l:wd] + a:000)
|
|
endfunction
|
|
|
|
" ExecInWorkDir will execute cmd with the working diretory set to wd. Additional arguments will be passed
|
|
" to cmd.
|
|
function! go#util#ExecInWorkDir(cmd, wd, ...) abort
|
|
if !isdirectory(a:wd)
|
|
return ['', 1]
|
|
endif
|
|
|
|
let l:dir = go#util#Chdir(a:wd)
|
|
try
|
|
let [l:out, l:err] = call('go#util#Exec', [a:cmd] + a:000)
|
|
finally
|
|
call go#util#Chdir(l:dir)
|
|
endtry
|
|
return [l:out, l:err]
|
|
endfunction
|
|
|
|
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
|
|
|
|
let l:out = call('s:system', [l:cmd] + a:000)
|
|
return [l:out, go#util#ShellError()]
|
|
endfunction
|
|
|
|
function! go#util#ShellError() abort
|
|
return v:shell_error
|
|
endfunction
|
|
|
|
" StripPath strips the path's last character if it's a path separator.
|
|
" example: '/foo/bar/' -> '/foo/bar'
|
|
function! go#util#StripPathSep(path) abort
|
|
let last_char = strlen(a:path) - 1
|
|
if a:path[last_char] == go#util#PathSep()
|
|
return strpart(a:path, 0, last_char)
|
|
endif
|
|
|
|
return a:path
|
|
endfunction
|
|
|
|
" StripTrailingSlash strips the trailing slash from the given path list.
|
|
" example: ['/foo/bar/'] -> ['/foo/bar']
|
|
function! go#util#StripTrailingSlash(paths) abort
|
|
return map(copy(a:paths), 'go#util#StripPathSep(v:val)')
|
|
endfunction
|
|
|
|
" Shelljoin returns a shell-safe string representation of arglist. The
|
|
" {special} argument of shellescape() may optionally be passed.
|
|
function! go#util#Shelljoin(arglist, ...) abort
|
|
" Preserve original shell. This needs to be kept in sync with how s:system
|
|
" sets shell.
|
|
let l:shell = &shell
|
|
|
|
try
|
|
if !go#util#IsWin() && executable('/bin/sh')
|
|
set shell=/bin/sh
|
|
endif
|
|
|
|
if go#util#IsWin()
|
|
if executable($COMSPEC)
|
|
let &shell = $COMSPEC
|
|
endif
|
|
endif
|
|
|
|
let ssl_save = &shellslash
|
|
set noshellslash
|
|
if a:0
|
|
return join(map(copy(a:arglist), 'shellescape(v:val, ' . a:1 . ')'), ' ')
|
|
endif
|
|
|
|
return join(map(copy(a:arglist), 'shellescape(v:val)'), ' ')
|
|
finally
|
|
" Restore original values
|
|
let &shellslash = ssl_save
|
|
let &shell = l:shell
|
|
endtry
|
|
endfunction
|
|
|
|
" Shelllist returns a shell-safe representation of the items in the given
|
|
" arglist. The {special} argument of shellescape() may optionally be passed.
|
|
function! go#util#Shelllist(arglist, ...) abort
|
|
try
|
|
let ssl_save = &shellslash
|
|
set noshellslash
|
|
if a:0
|
|
return map(copy(a:arglist), 'go#util#Shelljoin(v:val, ' . a:1 . ')')
|
|
endif
|
|
return map(copy(a:arglist), 'go#util#Shelljoin(v:val)')
|
|
finally
|
|
let &shellslash = ssl_save
|
|
endtry
|
|
endfunction
|
|
|
|
" Returns the byte offset for line and column
|
|
function! go#util#Offset(line, col) abort
|
|
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)
|
|
endfunction
|
|
"
|
|
" Returns the byte offset for the cursor
|
|
function! go#util#OffsetCursor() abort
|
|
return go#util#Offset(line('.'), col('.'))
|
|
endfunction
|
|
|
|
" Windo is like the built-in :windo, only it returns to the window the command
|
|
" was issued from
|
|
function! go#util#Windo(command) abort
|
|
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.
|
|
function! go#util#snippetcase(word) abort
|
|
let l:snippet_case = go#config#AddtagsTransform()
|
|
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
|
|
endfunction
|
|
|
|
" snakecase converts a string to snake case. i.e: FooBar -> foo_bar
|
|
" Copied from tpope/vim-abolish
|
|
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')
|
|
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.
|
|
function! go#util#camelcase(word) abort
|
|
let word = substitute(a:word, '-', '_', 'g')
|
|
if word !~# '_' && word =~# '\l'
|
|
return substitute(word, '^.', '\l&', '')
|
|
else
|
|
return substitute(word, '\C\(_\)\=\(.\)', '\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))','g')
|
|
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.
|
|
"
|
|
" 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
|
|
endfunction
|
|
|
|
function! go#util#EchoSuccess(msg)
|
|
call s:echo(a:msg, 'Function')
|
|
endfunction
|
|
function! go#util#EchoError(msg)
|
|
call s:echo(a:msg, 'ErrorMsg')
|
|
endfunction
|
|
function! go#util#EchoWarning(msg)
|
|
call s:echo(a:msg, 'WarningMsg')
|
|
endfunction
|
|
function! go#util#EchoProgress(msg)
|
|
redraw
|
|
call s:echo(a:msg, 'Identifier')
|
|
endfunction
|
|
function! go#util#EchoInfo(msg)
|
|
call s:echo(a:msg, 'Debug')
|
|
endfunction
|
|
|
|
" Get all lines in the buffer as a a list.
|
|
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 == ''
|
|
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.
|
|
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
|
|
|
|
" Report if the user enabled a debug flag in g:go_debug.
|
|
function! go#util#HasDebug(flag)
|
|
return index(go#config#Debug(), a:flag) >= 0
|
|
endfunction
|
|
|
|
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
|
|
|
|
" 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
|
|
|
|
" 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 . "'")
|
|
|
|
if l:remove
|
|
return function('s:unset', [a:name], l:state)
|
|
endif
|
|
|
|
return function('go#util#SetEnv', [a:name, l:oldvalue], l:state)
|
|
endfunction
|
|
|
|
function! go#util#ClearHighlights(group) abort
|
|
if has('textprop')
|
|
" the property type may not exist when syntax highlighting is not enabled.
|
|
if empty(prop_type_get(a:group))
|
|
return
|
|
endif
|
|
if !has('patch-8.1.1035')
|
|
return prop_remove({'type': a:group, 'all': 1}, 1, line('$'))
|
|
endif
|
|
return prop_remove({'type': a:group, 'all': 1})
|
|
endif
|
|
|
|
if exists("*matchaddpos")
|
|
return s:clear_group_from_matches(a:group)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:clear_group_from_matches(group) abort
|
|
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
|
|
|
|
function! s:noop(...) abort dict
|
|
endfunction
|
|
|
|
" go#util#HighlightPositions highlights using text properties if possible and
|
|
" falls back to matchaddpos() if necessary. It works around matchaddpos()'s
|
|
" limit of only 8 positions per call by calling matchaddpos() with no more
|
|
" than 8 positions per call.
|
|
"
|
|
" pos should be a list of 3 element lists. The lists should be [line, col,
|
|
" length] as used by matchaddpos().
|
|
function! go#util#HighlightPositions(group, pos) abort
|
|
if has('textprop')
|
|
for l:pos in a:pos
|
|
" use a single line prop by default
|
|
let l:prop = {'type': a:group, 'length': l:pos[2]}
|
|
|
|
let l:line = getline(l:pos[0])
|
|
|
|
" l:max is the 1-based index within the buffer of the first character after l:pos.
|
|
let l:max = line2byte(l:pos[0]) + l:pos[1] + l:pos[2] - 1
|
|
if has('patch-8.2.115')
|
|
" Use byte2line as long as 8.2.115 (which resolved
|
|
" https://github.com/vim/vim/issues/5334) is available.
|
|
let l:end_lnum = byte2line(l:max)
|
|
|
|
" specify end line and column if needed.
|
|
if l:pos[0] != l:end_lnum
|
|
let l:end_col = l:max - line2byte(l:end_lnum)
|
|
let l:prop = {'type': a:group, 'end_lnum': l:end_lnum, 'end_col': l:end_col}
|
|
endif
|
|
elseif l:pos[1] + l:pos[2] - 1 > len(l:line)
|
|
let l:end_lnum = l:pos[0]
|
|
while line2byte(l:end_lnum+1) < l:max
|
|
let l:end_lnum += 1
|
|
endwhile
|
|
|
|
" l:end_col is the full length - the byte position of l:end_lnum +
|
|
" the number of newlines (number of newlines is l:end_lnum -
|
|
" l:pos[0].
|
|
let l:end_col = l:max - line2byte(l:end_lnum) + l:end_lnum - l:pos[0]
|
|
let l:prop = {'type': a:group, 'end_lnum': l:end_lnum, 'end_col': l:end_col}
|
|
endif
|
|
try
|
|
call prop_add(l:pos[0], l:pos[1], l:prop)
|
|
catch
|
|
" Swallow any exceptions encountered while trying to add the property
|
|
" Due to the asynchronous nature, it's possible that the buffer has
|
|
" changed since the buffer was analyzed and that the specified
|
|
" position is no longer valid.
|
|
endtry
|
|
endfor
|
|
return
|
|
endif
|
|
|
|
if exists('*matchaddpos')
|
|
return s:matchaddpos(a:group, a:pos)
|
|
endif
|
|
endfunction
|
|
|
|
" s:matchaddpos works around matchaddpos()'s limit of only 8 positions per
|
|
" call by calling matchaddpos() with no more than 8 positions per call.
|
|
function! s:matchaddpos(group, pos) abort
|
|
let l:partitions = []
|
|
let l:partitionsIdx = 0
|
|
let l:posIdx = 0
|
|
for l:pos in a:pos
|
|
if l:posIdx % 8 == 0
|
|
let l:partitions = add(l:partitions, [])
|
|
let l:partitionsIdx = len(l:partitions) - 1
|
|
endif
|
|
let l:partitions[l:partitionsIdx] = add(l:partitions[l:partitionsIdx], l:pos)
|
|
let l:posIdx = l:posIdx + 1
|
|
endfor
|
|
|
|
for l:positions in l:partitions
|
|
call matchaddpos(a:group, l:positions)
|
|
endfor
|
|
endfunction
|
|
|
|
function! go#util#Chdir(dir) abort
|
|
if !exists('*chdir')
|
|
let l:olddir = getcwd()
|
|
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
|
|
execute printf('%s %s', cd, fnameescape(a:dir))
|
|
return l:olddir
|
|
endif
|
|
return chdir(a:dir)
|
|
endfunction
|
|
|
|
" go#util#TestName returns the name of the test function that preceeds the
|
|
" cursor.
|
|
function go#util#TestName() abort
|
|
" search flags legend (used only)
|
|
" 'b' search backward instead of forward
|
|
" 'c' accept a match at the cursor position
|
|
" 'n' do Not move the cursor
|
|
" 'W' don't wrap around the end of the file
|
|
"
|
|
" for the full list
|
|
" :help search
|
|
let l:line = search('func \(Test\|Example\)', "bcnW")
|
|
|
|
if l:line == 0
|
|
return ''
|
|
endif
|
|
|
|
let l:decl = getline(l:line)
|
|
return split(split(l:decl, " ")[1], "(")[0]
|
|
endfunction
|
|
|
|
function go#util#ExpandPattern(...) abort
|
|
let l:packages = []
|
|
for l:pattern in a:000
|
|
let l:pkgs = go#tool#List(l:pattern)
|
|
if l:pkgs is -1
|
|
call go#util#EchoError('could not expand package pattern')
|
|
continue
|
|
endif
|
|
|
|
let l:packages = extend(l:packages, l:pkgs)
|
|
call go#util#EchoInfo(printf("l:packages = %s, l:pkgs = %s", l:packages, l:pkgs))
|
|
endfor
|
|
|
|
return uniq(sort(l:packages))
|
|
endfunction
|
|
|
|
" restore Vi compatibility settings
|
|
let &cpo = s:cpo_save
|
|
unlet s:cpo_save
|
|
|
|
" vim: sw=2 ts=2 et
|