util.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. " PathSep returns the appropriate OS specific path separator.
  2. function! go#util#PathSep() abort
  3. if go#util#IsWin()
  4. return '\'
  5. endif
  6. return '/'
  7. endfunction
  8. " PathListSep returns the appropriate OS specific path list separator.
  9. function! go#util#PathListSep() abort
  10. if go#util#IsWin()
  11. return ";"
  12. endif
  13. return ":"
  14. endfunction
  15. " LineEnding returns the correct line ending, based on the current fileformat
  16. function! go#util#LineEnding() abort
  17. if &fileformat == 'dos'
  18. return "\r\n"
  19. elseif &fileformat == 'mac'
  20. return "\r"
  21. endif
  22. return "\n"
  23. endfunction
  24. " Join joins any number of path elements into a single path, adding a
  25. " Separator if necessary and returns the result
  26. function! go#util#Join(...) abort
  27. return join(a:000, go#util#PathSep())
  28. endfunction
  29. " IsWin returns 1 if current OS is Windows or 0 otherwise
  30. function! go#util#IsWin() abort
  31. let win = ['win16', 'win32', 'win64', 'win95']
  32. for w in win
  33. if (has(w))
  34. return 1
  35. endif
  36. endfor
  37. return 0
  38. endfunction
  39. " IsMac returns 1 if current OS is macOS or 0 otherwise.
  40. function! go#util#IsMac() abort
  41. return has('mac') ||
  42. \ has('macunix') ||
  43. \ has('gui_macvim') ||
  44. \ go#util#System('uname') =~? '^darwin'
  45. endfunction
  46. " Checks if using:
  47. " 1) Windows system,
  48. " 2) And has cygpath executable,
  49. " 3) And uses *sh* as 'shell'
  50. function! go#util#IsUsingCygwinShell()
  51. return go#util#IsWin() && executable('cygpath') && &shell =~ '.*sh.*'
  52. endfunction
  53. function! go#util#has_job() abort
  54. " job was introduced in 7.4.xxx however there are multiple bug fixes and one
  55. " of the latest is 8.0.0087 which is required for a stable async API.
  56. return has('job') && has("patch-8.0.0087")
  57. endfunction
  58. let s:env_cache = {}
  59. " env returns the go environment variable for the given key. Where key can be
  60. " GOARCH, GOOS, GOROOT, etc... It caches the result and returns the cached
  61. " version.
  62. function! go#util#env(key) abort
  63. let l:key = tolower(a:key)
  64. if has_key(s:env_cache, l:key)
  65. return s:env_cache[l:key]
  66. endif
  67. if executable('go')
  68. let l:var = call('go#util#'.l:key, [])
  69. if go#util#ShellError() != 0
  70. call go#util#EchoError(printf("'go env %s' failed", toupper(l:key)))
  71. return ''
  72. endif
  73. else
  74. let l:var = eval("$".toupper(a:key))
  75. endif
  76. let s:env_cache[l:key] = l:var
  77. return l:var
  78. endfunction
  79. " goarch returns 'go env GOARCH'. This is an internal function and shouldn't
  80. " be used. Instead use 'go#util#env("goarch")'
  81. function! go#util#goarch() abort
  82. return substitute(go#util#System('go env GOARCH'), '\n', '', 'g')
  83. endfunction
  84. " goos returns 'go env GOOS'. This is an internal function and shouldn't
  85. " be used. Instead use 'go#util#env("goos")'
  86. function! go#util#goos() abort
  87. return substitute(go#util#System('go env GOOS'), '\n', '', 'g')
  88. endfunction
  89. " goroot returns 'go env GOROOT'. This is an internal function and shouldn't
  90. " be used. Instead use 'go#util#env("goroot")'
  91. function! go#util#goroot() abort
  92. return substitute(go#util#System('go env GOROOT'), '\n', '', 'g')
  93. endfunction
  94. " gopath returns 'go env GOPATH'. This is an internal function and shouldn't
  95. " be used. Instead use 'go#util#env("gopath")'
  96. function! go#util#gopath() abort
  97. return substitute(go#util#System('go env GOPATH'), '\n', '', 'g')
  98. endfunction
  99. function! go#util#osarch() abort
  100. return go#util#env("goos") . '_' . go#util#env("goarch")
  101. endfunction
  102. " Run a shell command.
  103. "
  104. " It will temporary set the shell to /bin/sh for Unix-like systems if possible,
  105. " so that we always use a standard POSIX-compatible Bourne shell (and not e.g.
  106. " csh, fish, etc.) See #988 and #1276.
  107. function! s:system(cmd, ...) abort
  108. " Preserve original shell and shellredir values
  109. let l:shell = &shell
  110. let l:shellredir = &shellredir
  111. if !go#util#IsWin() && executable('/bin/sh')
  112. set shell=/bin/sh shellredir=>%s\ 2>&1
  113. endif
  114. try
  115. return call('system', [a:cmd] + a:000)
  116. finally
  117. " Restore original values
  118. let &shell = l:shell
  119. let &shellredir = l:shellredir
  120. endtry
  121. endfunction
  122. " System runs a shell command "str". Every arguments after "str" is passed to
  123. " stdin.
  124. function! go#util#System(str, ...) abort
  125. return call('s:system', [a:str] + a:000)
  126. endfunction
  127. " Exec runs a shell command "cmd", which must be a list, one argument per item.
  128. " Every list entry will be automatically shell-escaped
  129. " Every other argument is passed to stdin.
  130. function! go#util#Exec(cmd, ...) abort
  131. if len(a:cmd) == 0
  132. call go#util#EchoError("go#util#Exec() called with empty a:cmd")
  133. return
  134. endif
  135. " CheckBinPath will show a warning for us.
  136. let l:bin = go#path#CheckBinPath(a:cmd[0])
  137. if empty(l:bin)
  138. return ["", 1]
  139. endif
  140. let l:out = call('s:system', [go#util#Shelljoin([l:bin] + a:cmd[1:])] + a:000)
  141. return [l:out, go#util#ShellError()]
  142. endfunction
  143. function! go#util#ShellError() abort
  144. return v:shell_error
  145. endfunction
  146. " StripPath strips the path's last character if it's a path separator.
  147. " example: '/foo/bar/' -> '/foo/bar'
  148. function! go#util#StripPathSep(path) abort
  149. let last_char = strlen(a:path) - 1
  150. if a:path[last_char] == go#util#PathSep()
  151. return strpart(a:path, 0, last_char)
  152. endif
  153. return a:path
  154. endfunction
  155. " StripTrailingSlash strips the trailing slash from the given path list.
  156. " example: ['/foo/bar/'] -> ['/foo/bar']
  157. function! go#util#StripTrailingSlash(paths) abort
  158. return map(copy(a:paths), 'go#util#StripPathSep(v:val)')
  159. endfunction
  160. " Shelljoin returns a shell-safe string representation of arglist. The
  161. " {special} argument of shellescape() may optionally be passed.
  162. function! go#util#Shelljoin(arglist, ...) abort
  163. try
  164. let ssl_save = &shellslash
  165. set noshellslash
  166. if a:0
  167. return join(map(copy(a:arglist), 'shellescape(v:val, ' . a:1 . ')'), ' ')
  168. endif
  169. return join(map(copy(a:arglist), 'shellescape(v:val)'), ' ')
  170. finally
  171. let &shellslash = ssl_save
  172. endtry
  173. endfunction
  174. fu! go#util#Shellescape(arg)
  175. try
  176. let ssl_save = &shellslash
  177. set noshellslash
  178. return shellescape(a:arg)
  179. finally
  180. let &shellslash = ssl_save
  181. endtry
  182. endf
  183. " Shelllist returns a shell-safe representation of the items in the given
  184. " arglist. The {special} argument of shellescape() may optionally be passed.
  185. function! go#util#Shelllist(arglist, ...) abort
  186. try
  187. let ssl_save = &shellslash
  188. set noshellslash
  189. if a:0
  190. return map(copy(a:arglist), 'shellescape(v:val, ' . a:1 . ')')
  191. endif
  192. return map(copy(a:arglist), 'shellescape(v:val)')
  193. finally
  194. let &shellslash = ssl_save
  195. endtry
  196. endfunction
  197. " Returns the byte offset for line and column
  198. function! go#util#Offset(line, col) abort
  199. if &encoding != 'utf-8'
  200. let sep = go#util#LineEnding()
  201. let buf = a:line == 1 ? '' : (join(getline(1, a:line-1), sep) . sep)
  202. let buf .= a:col == 1 ? '' : getline('.')[:a:col-2]
  203. return len(iconv(buf, &encoding, 'utf-8'))
  204. endif
  205. return line2byte(a:line) + (a:col-2)
  206. endfunction
  207. "
  208. " Returns the byte offset for the cursor
  209. function! go#util#OffsetCursor() abort
  210. return go#util#Offset(line('.'), col('.'))
  211. endfunction
  212. " Windo is like the built-in :windo, only it returns to the window the command
  213. " was issued from
  214. function! go#util#Windo(command) abort
  215. let s:currentWindow = winnr()
  216. try
  217. execute "windo " . a:command
  218. finally
  219. execute s:currentWindow. "wincmd w"
  220. unlet s:currentWindow
  221. endtry
  222. endfunction
  223. " snippetcase converts the given word to given preferred snippet setting type
  224. " case.
  225. function! go#util#snippetcase(word) abort
  226. let l:snippet_case = get(g:, 'go_addtags_transform', "snakecase")
  227. if l:snippet_case == "snakecase"
  228. return go#util#snakecase(a:word)
  229. elseif l:snippet_case == "camelcase"
  230. return go#util#camelcase(a:word)
  231. else
  232. return a:word " do nothing
  233. endif
  234. endfunction
  235. " snakecase converts a string to snake case. i.e: FooBar -> foo_bar
  236. " Copied from tpope/vim-abolish
  237. function! go#util#snakecase(word) abort
  238. let word = substitute(a:word, '::', '/', 'g')
  239. let word = substitute(word, '\(\u\+\)\(\u\l\)', '\1_\2', 'g')
  240. let word = substitute(word, '\(\l\|\d\)\(\u\)', '\1_\2', 'g')
  241. let word = substitute(word, '[.-]', '_', 'g')
  242. let word = tolower(word)
  243. return word
  244. endfunction
  245. " camelcase converts a string to camel case. e.g. FooBar or foo_bar will become
  246. " fooBar.
  247. " Copied from tpope/vim-abolish.
  248. function! go#util#camelcase(word) abort
  249. let word = substitute(a:word, '-', '_', 'g')
  250. if word !~# '_' && word =~# '\l'
  251. return substitute(word, '^.', '\l&', '')
  252. else
  253. return substitute(word, '\C\(_\)\=\(.\)', '\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))','g')
  254. endif
  255. endfunction
  256. " pascalcase converts a string to 'PascalCase'. e.g. fooBar or foo_bar will
  257. " become FooBar.
  258. function! go#util#pascalcase(word) abort
  259. let word = go#util#camelcase(a:word)
  260. return toupper(word[0]) . word[1:]
  261. endfunction
  262. " Echo a message to the screen and highlight it with the group in a:hi.
  263. "
  264. " The message can be a list or string; every line with be :echomsg'd separately.
  265. function! s:echo(msg, hi)
  266. let l:msg = []
  267. if type(a:msg) != type([])
  268. let l:msg = split(a:msg, "\n")
  269. else
  270. let l:msg = a:msg
  271. endif
  272. " Tabs display as ^I or <09>, so manually expand them.
  273. let l:msg = map(l:msg, 'substitute(v:val, "\t", " ", "")')
  274. exe 'echohl ' . a:hi
  275. for line in l:msg
  276. echom "vim-go: " . line
  277. endfor
  278. echohl None
  279. endfunction
  280. function! go#util#EchoSuccess(msg)
  281. call s:echo(a:msg, 'Function')
  282. endfunction
  283. function! go#util#EchoError(msg)
  284. call s:echo(a:msg, 'ErrorMsg')
  285. endfunction
  286. function! go#util#EchoWarning(msg)
  287. call s:echo(a:msg, 'WarningMsg')
  288. endfunction
  289. function! go#util#EchoProgress(msg)
  290. call s:echo(a:msg, 'Identifier')
  291. endfunction
  292. function! go#util#EchoInfo(msg)
  293. call s:echo(a:msg, 'Debug')
  294. endfunction
  295. " Get all lines in the buffer as a a list.
  296. function! go#util#GetLines()
  297. let buf = getline(1, '$')
  298. if &encoding != 'utf-8'
  299. let buf = map(buf, 'iconv(v:val, &encoding, "utf-8")')
  300. endif
  301. if &l:fileformat == 'dos'
  302. " XXX: line2byte() depend on 'fileformat' option.
  303. " so if fileformat is 'dos', 'buf' must include '\r'.
  304. let buf = map(buf, 'v:val."\r"')
  305. endif
  306. return buf
  307. endfunction
  308. " Convert the current buffer to the "archive" format of
  309. " golang.org/x/tools/go/buildutil:
  310. " https://godoc.org/golang.org/x/tools/go/buildutil#ParseOverlayArchive
  311. "
  312. " > The archive consists of a series of files. Each file consists of a name, a
  313. " > decimal file size and the file contents, separated by newlinews. No newline
  314. " > follows after the file contents.
  315. function! go#util#archive()
  316. let l:buffer = join(go#util#GetLines(), "\n")
  317. return expand("%:p:gs!\\!/!") . "\n" . strlen(l:buffer) . "\n" . l:buffer
  318. endfunction
  319. " Make a named temporary directory which starts with "prefix".
  320. "
  321. " Unfortunately Vim's tempname() is not portable enough across various systems;
  322. " see: https://github.com/mattn/vim-go/pull/3#discussion_r138084911
  323. function! go#util#tempdir(prefix) abort
  324. " See :help tempfile
  325. if go#util#IsWin()
  326. let l:dirs = [$TMP, $TEMP, 'c:\tmp', 'c:\temp']
  327. else
  328. let l:dirs = [$TMPDIR, '/tmp', './', $HOME]
  329. endif
  330. let l:dir = ''
  331. for l:d in dirs
  332. if !empty(l:d) && filewritable(l:d) == 2
  333. let l:dir = l:d
  334. break
  335. endif
  336. endfor
  337. if l:dir == ''
  338. call go#util#EchoError('Unable to find directory to store temporary directory in')
  339. return
  340. endif
  341. " Not great randomness, but "good enough" for our purpose here.
  342. let l:rnd = sha256(printf('%s%s', localtime(), fnamemodify(bufname(''), ":p")))
  343. let l:tmp = printf("%s/%s%s", l:dir, a:prefix, l:rnd)
  344. call mkdir(l:tmp, 'p', 0700)
  345. return l:tmp
  346. endfunction
  347. " Report if the user enabled a debug flag in g:go_debug.
  348. function! go#util#HasDebug(flag)
  349. return index(get(g:, 'go_debug', []), a:flag) >= 0
  350. endfunction
  351. " vim: sw=2 ts=2 et