diff --git a/pack/colors/start/robertmeta-nofrils/LICENSE b/pack/colors/start/robertmeta-nofrils/LICENSE index e227212d..37f27521 100644 --- a/pack/colors/start/robertmeta-nofrils/LICENSE +++ b/pack/colors/start/robertmeta-nofrils/LICENSE @@ -1,6 +1,6 @@ -Copyright (c) 2016 Robert Melton - +MIT License +Copyright (c) 2018 Robert Melton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,17 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - - +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pack/colors/start/robertmeta-nofrils/README.md b/pack/colors/start/robertmeta-nofrils/README.md index e89dc4a8..02719fa5 100644 --- a/pack/colors/start/robertmeta-nofrils/README.md +++ b/pack/colors/start/robertmeta-nofrils/README.md @@ -1,50 +1,50 @@ This is an extremely minimalist vim colorscheme (almost no syntax highlighting). To use: -- :colo nofrils-dark -- :colo nofrils-light -- :colo nofrils-sepia -- :colo nofrils-acme **NEW** +- `:colo nofrils-dark` +- `:colo nofrils-light` +- `:colo nofrils-sepia` +- `:colo nofrils-acme` To customize: -- :let g:nofrils\_strbackgrounds=1 to turn highlighted string backgrounds, for languages where strings are a major part of them -- :let g:nofrils\_heavycomments=1 to turn on high contrast comments rather than the default faded style, for projects where the comments are not deceit and lies -- :let g:nofrils\_heavylinenumbers=1 to turn on brighter line numbers, for people who use relative line numbers to hop around +- `:let g:nofrils_strbackgrounds=1` to turn highlighted string backgrounds, for languages where strings are a major part of them +- `:let g:nofrils_heavycomments=1` to turn on high contrast comments rather than the default faded style, for projects where the comments are not deceit and lies +- `:let g:nofrils_heavylinenumbers=1` to turn on brighter line numbers, for people who use relative line numbers to hop around Commands (once theme is loaded): -- :NofrilsDark use dark theme -- :NofrilsLight use light theme -- :NofrilsSepia use sepia theme -- :NofrilsAcme use acme theme -- :NofrilsFocusNormal reset back to normal settings -- :NofrilsFocusCode focus only code, fade everything else -- :NofrilsFocusComments focus only comments, fade everything else +- `:NofrilsDark` use dark theme +- `:NofrilsLight` use light theme +- `:NofrilsSepia` use sepia theme +- `:NofrilsAcme` use acme theme +- `:NofrilsFocusNormal` reset back to normal settings +- `:NofrilsFocusCode` focus only code, fade everything else +- `:NofrilsFocusComments` focus only comments, fade everything else The only highlighted elements are spelling, errors, comments, vim features (diff, etc) and *optionally* string backgrounds **Click for non-fuzzified versions** -*nofrils-dark*: **let g:nofrils_heavylinenumbers=1**, **:let g:nofrils_strbackgrounds=1** and **:let g:nofrils_heavycomments=1** +*nofrils-dark*: **`:let g:nofrils_heavylinenumbers=1`**, **`:let g:nofrils_strbackgrounds=1`** and **`:let g:nofrils_heavycomments=1`** ![Dark Version](http://i.imgur.com/1lUx2hY.png) ---- -*nofrils-acme*: **let g:nofrils_heavylinenumbers=0**, **:let g:nofrils_strbackgrounds=0** and **:let g:nofrils_heavycomments=0** +*nofrils-acme*: **`:let g:nofrils_heavylinenumbers=0`**, **`:let g:nofrils_strbackgrounds=0`** and **`:let g:nofrils_heavycomments=0`** ![Acme Version](http://i.imgur.com/yn7OJrY.png) ---- -*nofrils-sepia*: **let g:nofrils_heavylinenumbers=1**, **:let g:nofrils_strbackgrounds=0** and **:let g:nofrils_heavycomments=1** +*nofrils-sepia*: **`:let g:nofrils_heavylinenumbers=1`**, **`:let g:nofrils_strbackgrounds=0`** and **`:let g:nofrils_heavycomments=1`** ![Sepia Version](http://i.imgur.com/zwW5kir.png) ---- -*nofrils-light*: **let g:nofrils_heavylinenumbers=0**, **:let g:nofrils_strbackgrounds=0** and **:let g:nofrils_heavycomments=1** +*nofrils-light*: **`:let g:nofrils_heavylinenumbers=0`**, **`:let g:nofrils_strbackgrounds=0`** and **`:let g:nofrils_heavycomments=1`** ![Light Version](http://i.imgur.com/XXoxztJ.png) ---- -*nofrils-acme*: **let g:nofrils_heavylinenumbers=0**, **:let g:nofrils_strbackgrounds=0** and **:let g:nofrils_heavycomments=0** +*nofrils-acme*: **`:let g:nofrils_heavylinenumbers=0`**, **`:let g:nofrils_strbackgrounds=0`** and **`:let g:nofrils_heavycomments=0`** ![Diffs Arcme Version](http://i.imgur.com/cLcbq7M.png) @@ -58,4 +58,5 @@ Why did you even create this thing? https://www.robertmelton.com/2016/03/13/syn Forks +- [Kakoune](http://kakoune.org/): https://github.com/robertmeta/nofrils-kakoune - Atom: https://atom.io/themes/nothrill-dark-syntax diff --git a/pack/colors/start/romainl-apprentice/README.md b/pack/colors/start/romainl-apprentice/README.md index 4ad48875..a5340c57 100644 --- a/pack/colors/start/romainl-apprentice/README.md +++ b/pack/colors/start/romainl-apprentice/README.md @@ -20,7 +20,7 @@ Some code in iTerm, with `TERM=xterm`, using the Tango color palette: ![image](http://romainl.github.io/Apprentice/images/08termtango.png) -## Preparing your environment. +## Preparing your environment Apprentice is designed first and foremost to look “good” in terminal emulators supporting 256 colors and in GUI Vim (GVim/MacVim). It supports lesser terminal emulators in the sense that it doesn’t break but it will definitely look “better” in more capable environments. @@ -36,7 +36,7 @@ In practice, this new development doesn't change much for Apprentice which uses :set termguicolors -See [this gist](https://gist.github.com/XVilka/8346728) for more information and support status and, of course, `:help termguicolors`. +See [this gist](https://gist.github.com/XVilka/8346728) for more information and support status and, of course, `:help 'termguicolors'`. ### 256color-ready terminal emulators @@ -122,32 +122,48 @@ I recommend to adjust your terminal's background color to the one used in Appren | hexadecimal | `#262626` | | rgb | `rgb(38,38,38)` | -## Installing Apprentice. +## Installing Apprentice Colorschemes must be placed in a directory named `colors` that is somewhere in Vim’s `runtimepath`: The canonical location is: - ~/.vim/colors/apprentice.vim + colors/apprentice.vim but it could be: - ~/.vim/bundle/apprentice/colors/apprentice.vim + bundle/apprentice/colors/apprentice.vim or whatever works for you. Arch users may be happy to hear that Apprentice [has landed in AUR](https://aur.archlinux.org/packages/vim-apprentice/). To install it, use an AUR helper — `yaourt -S vim-apprentice` — or download the `PKGBUILD` and do `$ makepkg -i`. -## Enabling Apprentice. +## Enabling Apprentice To test Apprentice, just type this command from *normal* mode and hit `Enter`: :colorscheme apprentice -If you like what you see and want to make Apprentice your default colorscheme, add this line to your `~/.vimrc`, preferably near the end, after any `syntax enable`, `syntax on`, `filetype ... on`, `call plug#end()`, or `call vundle#end()` line: +If you like what you see and want to make Apprentice your default colorscheme, add this line to your `vimrc`, preferably near the end, after any `syntax enable`, `syntax on`, `filetype ... on`, `call plug#end()`, or `call vundle#end()` line: colorscheme apprentice +## Tweaking Apprentice + +If you don't want to maintain your own fork of Apprentice you can add something like this to your `vimrc`, before `colorscheme apprentice`: + + function! MyHighlights() abort + highlight Comment ctermfg=245 + highlight NonText ctermbg=17 + endfunction + + augroup MyColors + autocmd! + autocmd ColorScheme * call MyHighlights() + augroup END + +See [this Gist](https://gist.github.com/romainl/379904f91fa40533175dfaec4c833f2f) for reference. + ## Lightline and Airline themes I removed them from `master` but you can still find them in the "fancylines-and-neovim" branch. diff --git a/pack/plugins/start/airblade-vim-gitgutter/.github/issue_template.md b/pack/plugins/start/airblade-vim-gitgutter/.github/issue_template.md new file mode 100644 index 00000000..732d22ef --- /dev/null +++ b/pack/plugins/start/airblade-vim-gitgutter/.github/issue_template.md @@ -0,0 +1,8 @@ +> What is the latest commit SHA in your installed vim-gitgutter? + +> What vim/nvim version are you on? + +> If no signs are showing at all, what does `:echo b:gitgutter.path` give? + +> If no signs are showing at all, and the `path` value is a path and not `-2`, does it work with `let g:gitgutter_grep=''`? + diff --git a/pack/plugins/start/airblade-vim-gitgutter/README.mkd b/pack/plugins/start/airblade-vim-gitgutter/README.mkd index 978a2774..d87367a5 100644 --- a/pack/plugins/start/airblade-vim-gitgutter/README.mkd +++ b/pack/plugins/start/airblade-vim-gitgutter/README.mkd @@ -1,6 +1,6 @@ ## vim-gitgutter -A Vim plugin which shows a git diff in the 'gutter' (sign column). It shows which lines have been added, modified, or removed. You can also preview, stage, and undo individual hunks. +A Vim plugin which shows a git diff in the 'gutter' (sign column). It shows which lines have been added, modified, or removed. You can also preview, stage, and undo individual hunks. The plugin also provides a hunk text object. The signs are always up to date and the plugin never saves your buffer. @@ -23,9 +23,8 @@ Features: Constraints: -* Supports git only. - -If you work with other version control systems, I recommend [vim-signify](https://github.com/mhinz/vim-signify). +* Supports git only. If you work with other version control systems, I recommend [vim-signify](https://github.com/mhinz/vim-signify). +* Relies on the `FocusGained` event. If your terminal doesn't report focus events, either use something like [Terminus][] or set `let g:gitgutter_terminal_reports_focus=0`. ### Screenshot @@ -108,7 +107,7 @@ When you make a change to a file tracked by git, the diff markers should appear You can jump between hunks with `[c` and `]c`. You can preview, stage, and undo hunks with `hp`, `hs`, and `hu` respectively. -You cannot currently unstage a staged hunk. +You cannot unstage a staged hunk. #### Activation @@ -213,6 +212,7 @@ You can customise: * The signs' colours and symbols * Line highlights * The base of the diff +* Extra arguments for `git` when running `git diff` * Extra arguments for `git diff` * Key mappings * Whether or not vim-gitgutter is on initially (defaults to on) @@ -298,6 +298,14 @@ let g:gitgutter_diff_base = '' ``` +#### Extra arguments for `git` when running `git diff` + +If you want to pass extra arguments to `git` when running `git diff`, do so like this: + +```viml +let g:gitgutter_git_args = '--git-dir-""' +``` + #### Extra arguments for `git diff` If you want to pass extra arguments to `git diff`, for example to ignore whitespace, do so like this: @@ -462,23 +470,26 @@ nmap [c :call PrevHunkAllBuffers() ### FAQ -> Why can't I unstage staged changes? +> How can I turn off realtime updates? -Unstaging staged hunks is feasible but not quite as easy as it sounds. There are three relevant versions of a file at any one time: +Add this to your vim configuration (in an `/after/plugin` directory): -1. The version at HEAD in the repo. -2. The version staged in the index. -3. The version in the working tree, in your vim buffer. +```viml +" .vim/after/plugin/gitgutter.vim +autocmd! gitgutter CursorHold,CursorHoldI +``` -`git-diff` without arguments shows you how 2 and 3 differ; this is what vim-gitgutter shows too. +> I turned off realtime updates, how can I have signs updated when I save a file? -`git-diff --staged` shows you how 1 and 2 differ. +If you really want to update the signs when you save a file, add this to your vimrc: -Let's say you are looking at a file in vim which has some unstaged changes. Now you stage a hunk, either via vim-gitgutter or another means. The hunk is no longer marked in vim-gitgutter because it is the same in 2 and 3. +```viml +autocmd BufWritePost * GitGutter +``` -Now you want to unstage that hunk. To see it, you need the difference between 1 and 2. For vim-gitgutter to show those differences, it would need to show you 2 instead of 3 in your vim buffer. But 2 is virtual so vim-gitgutter would need to handle it without touching 3. +> Why can't I unstage staged changes? -I intend to implement this but I can't commit to any deadline. +This plugin is for showing changes between the working tree and the index (and staging/undoing those changes). Unstaging a staged hunk would require showing changes between the index and HEAD, which is out of scope. > Why are the colours in the sign column weird? @@ -505,6 +516,14 @@ Here are some things you can check: * If you use zsh, and you set `CDPATH`, make sure `CDPATH` doesn't include the current directory. +#### When signs take a few seconds to appear + +* Try reducing `updatetime`, e.g. `set updatetime=100`. + +#### When signs don't update after focusing Vim + +* Your terminal probably isn't reporting focus events. Either try installing [Terminus][] or set `let g:gitgutter_terminal_reports_focus=0`. + ### Shameless Plug @@ -525,3 +544,4 @@ Copyright Andrew Stewart, AirBlade Software Ltd. Released under the MIT licence [pathogen]: https://github.com/tpope/vim-pathogen [siv]: http://pluralsight.com/training/Courses/TableOfContents/smash-into-vim [airblade]: http://airbladesoftware.com/peepcode-vim + [terminus]: https://github.com/wincent/terminus diff --git a/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter.vim b/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter.vim index 4a4ed9d1..1a4a2cfb 100644 --- a/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter.vim +++ b/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter.vim @@ -1,11 +1,21 @@ +let s:t_string = type('') + " Primary functions {{{ function! gitgutter#all(force) abort - for bufnr in tabpagebuflist() - let file = expand('#'.bufnr.':p') - if !empty(file) - call gitgutter#init_buffer(bufnr) - call gitgutter#process_buffer(bufnr, a:force) + let visible = tabpagebuflist() + + for bufnr in range(1, bufnr('$') + 1) + if buflisted(bufnr) + let file = expand('#'.bufnr.':p') + if !empty(file) + if index(visible, bufnr) != -1 + call gitgutter#init_buffer(bufnr) + call gitgutter#process_buffer(bufnr, a:force) + elseif a:force + call s:reset_tick(bufnr) + endif + endif endif endfor endfunction @@ -15,8 +25,9 @@ endfunction function! gitgutter#init_buffer(bufnr) if gitgutter#utility#is_active(a:bufnr) let p = gitgutter#utility#repo_path(a:bufnr, 0) - if type(p) != v:t_string || empty(p) + if type(p) != s:t_string || empty(p) call gitgutter#utility#set_repo_path(a:bufnr) + call s:setup_maps() endif endif endfunction @@ -49,17 +60,12 @@ endfunction function! gitgutter#disable() abort " get list of all buffers (across all tabs) - let buflist = [] - for i in range(tabpagenr('$')) - call extend(buflist, tabpagebuflist(i + 1)) - endfor - - for bufnr in buflist - let file = expand('#'.bufnr.':p') - if !empty(file) - call gitgutter#sign#clear_signs(bufnr) - call gitgutter#sign#remove_dummy_sign(bufnr, 1) - call gitgutter#hunk#reset(bufnr) + for bufnr in range(1, bufnr('$') + 1) + if buflisted(bufnr) + let file = expand('#'.bufnr.':p') + if !empty(file) + call s:clear(bufnr) + endif endif endfor @@ -81,7 +87,53 @@ endfunction " }}} +function! s:setup_maps() + if !g:gitgutter_map_keys + return + endif + + if !hasmapto('GitGutterPrevHunk') && maparg('[c', 'n') ==# '' + nmap [c GitGutterPrevHunk + endif + if !hasmapto('GitGutterNextHunk') && maparg(']c', 'n') ==# '' + nmap ]c GitGutterNextHunk + endif + + if !hasmapto('GitGutterStageHunk') && maparg('hs', 'n') ==# '' + nmap hs GitGutterStageHunk + endif + if !hasmapto('GitGutterUndoHunk') && maparg('hu', 'n') ==# '' + nmap hu GitGutterUndoHunk + endif + if !hasmapto('GitGutterPreviewHunk') && maparg('hp', 'n') ==# '' + nmap hp GitGutterPreviewHunk + endif + + if !hasmapto('GitGutterTextObjectInnerPending') && maparg('ic', 'o') ==# '' + omap ic GitGutterTextObjectInnerPending + endif + if !hasmapto('GitGutterTextObjectOuterPending') && maparg('ac', 'o') ==# '' + omap ac GitGutterTextObjectOuterPending + endif + if !hasmapto('GitGutterTextObjectInnerVisual') && maparg('ic', 'x') ==# '' + xmap ic GitGutterTextObjectInnerVisual + endif + if !hasmapto('GitGutterTextObjectOuterVisual') && maparg('ac', 'x') ==# '' + xmap ac GitGutterTextObjectOuterVisual + endif +endfunction + function! s:has_fresh_changes(bufnr) abort return getbufvar(a:bufnr, 'changedtick') != gitgutter#utility#getbufvar(a:bufnr, 'tick') endfunction +function! s:reset_tick(bufnr) abort + call gitgutter#utility#setbufvar(a:bufnr, 'tick', 0) +endfunction + +function! s:clear(bufnr) + call gitgutter#sign#clear_signs(a:bufnr) + call gitgutter#sign#remove_dummy_sign(a:bufnr, 1) + call gitgutter#hunk#reset(a:bufnr) + call s:reset_tick(a:bufnr) +endfunction diff --git a/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/diff.vim b/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/diff.vim index 710b611f..356a3b00 100644 --- a/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/diff.vim +++ b/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/diff.vim @@ -25,19 +25,38 @@ let s:temp_buffer = tempname() " " git show :myfile > myfileA " -" and myfileB is the buffer contents. Ideally we would pass this to -" git-diff on stdin via the second argument to vim's system() function. -" Unfortunately git-diff does not do CRLF conversion for input received on -" stdin, and git-show never performs CRLF conversion, so repos with CRLF -" conversion report that every line is modified due to mismatching EOLs. -" -" Instead, we write the buffer contents to a temporary file - myfileB in this -" example. Note the file extension must be preserved for the CRLF -" conversion to work. +" and myfileB is the buffer contents. " " After running the diff we pass it through grep where available to reduce " subsequent processing by the plugin. If grep is not available the plugin " does the filtering instead. +" +" +" Regarding line endings: +" +" git-show does not convert line endings. +" git-diff FILE FILE does convert line endings for the given files. +" +" If a file has CRLF line endings and git's core.autocrlf is true, +" the file in git's object store will have LF line endings. Writing +" it out via git-show will produce a file with LF line endings. +" +" If this last file is one of the files passed to git-diff, git-diff will +" convert its line endings to CRLF before diffing -- which is what we want -- +" but also by default output a warning on stderr. +" +" warning: LF will be replace by CRLF in . +" The file will have its original line endings in your working directory. +" +" When running the diff asynchronously, the warning message triggers the stderr +" callbacks which assume the overall command has failed and reset all the +" signs. As this is not what we want, and we can safely ignore the warning, +" we turn it off by passing the '-c "core.safecrlf=false"' argument to +" git-diff. +" +" When writing the temporary files we preserve the original file's extension +" so that repos using .gitattributes to control EOL conversion continue to +" convert correctly. function! gitgutter#diff#run_diff(bufnr, preserve_full_diff) abort while gitgutter#utility#repo_path(a:bufnr, 0) == -1 sleep 5m @@ -52,29 +71,42 @@ function! gitgutter#diff#run_diff(bufnr, preserve_full_diff) abort " bash doesn't mind the parentheses. let cmd = '(' - let blob_file = s:temp_index - let buff_file = s:temp_buffer + " Append buffer number to avoid race conditions between writing and reading + " the files when asynchronously processing multiple buffers. + " + " Without the buffer number, index_file would have a race in the shell + " between the second process writing it (with git-show) and the first + " reading it (with git-diff). + let index_file = s:temp_index.'.'.a:bufnr + + " Without the buffer number, buff_file would have a race between the + " second gitgutter#process_buffer() writing the file (synchronously, below) + " and the first gitgutter#process_buffer()'s async job reading it (with + " git-diff). + let buff_file = s:temp_buffer.'.'.a:bufnr let extension = gitgutter#utility#extension(a:bufnr) if !empty(extension) - let blob_file .= '.'.extension + let index_file .= '.'.extension let buff_file .= '.'.extension endif " Write file from index to temporary file. - let blob_name = g:gitgutter_diff_base.':'.gitgutter#utility#repo_path(a:bufnr, 1) - let cmd .= g:gitgutter_git_executable.' show '.blob_name.' > '.blob_file.' && ' + let index_name = g:gitgutter_diff_base.':'.gitgutter#utility#repo_path(a:bufnr, 1) + let cmd .= g:gitgutter_git_executable.' --no-pager show '.index_name.' > '.index_file.' && ' " Write buffer to temporary file. + " Note: this is synchronous. call s:write_buffer(a:bufnr, buff_file) " Call git-diff with the temporary files. - let cmd .= g:gitgutter_git_executable + let cmd .= g:gitgutter_git_executable.' --no-pager '.g:gitgutter_git_args if s:c_flag let cmd .= ' -c "diff.autorefreshindex=0"' let cmd .= ' -c "diff.noprefix=false"' + let cmd .= ' -c "core.safecrlf=false"' endif - let cmd .= ' diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args.' -- '.blob_file.' '.buff_file + let cmd .= ' diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args.' -- '.index_file.' '.buff_file " Pipe git-diff output into grep. if !a:preserve_full_diff && !empty(g:gitgutter_grep) @@ -114,11 +146,18 @@ endfunction function! gitgutter#diff#handler(bufnr, diff) abort call gitgutter#debug#log(a:diff) + if !bufexists(a:bufnr) + return + endif + call gitgutter#hunk#set_hunks(a:bufnr, gitgutter#diff#parse_diff(a:diff)) - let modified_lines = s:process_hunks(a:bufnr, gitgutter#hunk#hunks(a:bufnr)) + let modified_lines = gitgutter#diff#process_hunks(a:bufnr, gitgutter#hunk#hunks(a:bufnr)) - if len(modified_lines) > g:gitgutter_max_signs - call gitgutter#utility#warn_once('exceeded maximum number of signs (configured by g:gitgutter_max_signs).', 'max_signs') + let signs_count = len(modified_lines) + if signs_count > g:gitgutter_max_signs + call gitgutter#utility#warn_once(a:bufnr, printf( + \ 'exceeded maximum number of signs (%d > %d, configured by g:gitgutter_max_signs).', + \ signs_count, g:gitgutter_max_signs), 'max_signs') call gitgutter#sign#clear_signs(a:bufnr) else @@ -128,7 +167,11 @@ function! gitgutter#diff#handler(bufnr, diff) abort endif call s:save_last_seen_change(a:bufnr) - execute "silent doautocmd" s:nomodeline "User GitGutter" + if exists('#User#GitGutter') + let g:gitgutter_hook_context = {'bufnr': a:bufnr} + execute 'doautocmd' s:nomodeline 'User GitGutter' + unlet g:gitgutter_hook_context + endif endfunction @@ -156,7 +199,9 @@ function! gitgutter#diff#parse_hunk(line) abort end endfunction -function! s:process_hunks(bufnr, hunks) abort +" This function is public so it may be used by other plugins +" e.g. vim-signature. +function! gitgutter#diff#process_hunks(bufnr, hunks) abort let modified_lines = [] for hunk in a:hunks call extend(modified_lines, s:process_hunk(a:bufnr, hunk)) @@ -289,31 +334,22 @@ endfunction function! s:write_buffer(bufnr, file) - let _write = &write - set write - - " Write specified buffer (which may not be the current buffer) to buff_file. - " There doesn't seem to be a clean way to write a buffer that isn't the current - " to a file; we have to switch to it, write it, then switch back. - let current_buffer = bufnr('') - execute 'noautocmd buffer' a:bufnr + let bufcontents = getbufline(a:bufnr, 1, '$') - " Writing the whole buffer resets the '[ and '] marks and also the - " 'modified' flag (if &cpoptions includes '+'). These are unwanted - " side-effects so we save and restore the values ourselves. - let modified = getbufvar(a:bufnr, "&mod") - let op_mark_start = getpos("'[") - let op_mark_end = getpos("']") - - execute 'keepalt noautocmd silent write!' a:file + if getbufvar(a:bufnr, '&fileformat') ==# 'dos' + call map(bufcontents, 'v:val."\r"') + endif - call setbufvar(a:bufnr, "&mod", modified) - call setpos("'[", op_mark_start) - call setpos("']", op_mark_end) + let fenc = getbufvar(a:bufnr, '&fileencoding') + if fenc !=# &encoding + call map(bufcontents, 'iconv(v:val, &encoding, "'.fenc.'")') + endif - execute 'noautocmd buffer' current_buffer + if getbufvar(a:bufnr, '&bomb') + let bufcontents[0]=''.bufcontents[0] + endif - let &write = _write + call writefile(bufcontents, a:file) endfunction diff --git a/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/hunk.vim b/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/hunk.vim index ac8e3279..d3e55bbd 100644 --- a/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/hunk.vim +++ b/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/hunk.vim @@ -108,8 +108,7 @@ function! gitgutter#hunk#cursor_in_hunk(hunk) abort endfunction function! gitgutter#hunk#text_object(inner) abort - let bufnr = bufnr('') - let hunk = s:current_hunk(bufnr) + let hunk = s:current_hunk() if empty(hunk) return @@ -222,20 +221,35 @@ endfunction function! s:adjust_header(bufnr, hunk_diff) - return s:adjust_hunk_summary(s:fix_file_references(a:bufnr, a:hunk_diff)) + let filepath = gitgutter#utility#repo_path(a:bufnr, 0) + return s:adjust_hunk_summary(s:fix_file_references(filepath, a:hunk_diff)) endfunction " Replaces references to temp files with the actual file. -function! s:fix_file_references(bufnr, hunk_diff) - let filepath = gitgutter#utility#repo_path(a:bufnr, 0) - let diff = a:hunk_diff - for tmp in matchlist(diff, '\vdiff --git a/(\S+) b/(\S+)\n')[1:2] - let diff = substitute(diff, tmp, filepath, 'g') - endfor - return diff +function! s:fix_file_references(filepath, hunk_diff) + let lines = split(a:hunk_diff, '\n') + + let left_prefix = matchstr(lines[2], '[abciow12]').'/' + let right_prefix = matchstr(lines[3], '[abciow12]').'/' + let quote = lines[0][11] == '"' ? '"' : '' + + let left_file = quote.left_prefix.a:filepath.quote + let right_file = quote.right_prefix.a:filepath.quote + + let lines[0] = 'diff --git '.left_file.' '.right_file + let lines[2] = '--- '.left_file + let lines[3] = '+++ '.right_file + + return join(lines, "\n")."\n" endfunction +if $VIM_GITGUTTER_TEST + function! gitgutter#hunk#fix_file_references(filepath, hunk_diff) + return s:fix_file_references(a:filepath, a:hunk_diff) + endfunction +endif + function! s:adjust_hunk_summary(hunk_diff) abort let line_adjustment = s:line_adjustment_for_current_hunk() diff --git a/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/utility.vim b/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/utility.vim index abb6c378..d497938f 100644 --- a/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/utility.vim +++ b/pack/plugins/start/airblade-vim-gitgutter/autoload/gitgutter/utility.vim @@ -1,7 +1,18 @@ +function! gitgutter#utility#supports_overscore_sign() + if gitgutter#utility#windows() + return &encoding ==? 'utf-8' + else + return &termencoding ==? &encoding || &termencoding == '' + endif +endfunction + function! gitgutter#utility#setbufvar(buffer, varname, val) let dict = get(getbufvar(a:buffer, ''), 'gitgutter', {}) + let needs_setting = empty(dict) let dict[a:varname] = a:val - call setbufvar(a:buffer, 'gitgutter', dict) + if needs_setting + call setbufvar(+a:buffer, 'gitgutter', dict) + endif endfunction function! gitgutter#utility#getbufvar(buffer, varname, ...) @@ -22,11 +33,11 @@ function! gitgutter#utility#warn(message) abort let v:warningmsg = a:message endfunction -function! gitgutter#utility#warn_once(message, key) abort - if empty(gitgutter#utility#getbufvar(s:bufnr, a:key)) - call gitgutter#utility#setbufvar(s:bufnr, a:key, '1') +function! gitgutter#utility#warn_once(bufnr, message, key) abort + if empty(gitgutter#utility#getbufvar(a:bufnr, a:key)) + call gitgutter#utility#setbufvar(a:bufnr, a:key, '1') echohl WarningMsg - redraw | echo 'vim-gitgutter: ' . a:message + redraw | echom 'vim-gitgutter: ' . a:message echohl None let v:warningmsg = a:message endif @@ -103,13 +114,27 @@ function! gitgutter#utility#set_repo_path(bufnr) abort " * -2 - not tracked by git call gitgutter#utility#setbufvar(a:bufnr, 'path', -1) - let cmd = gitgutter#utility#cd_cmd(a:bufnr, g:gitgutter_git_executable.' ls-files --error-unmatch --full-name '.gitgutter#utility#shellescape(s:filename(a:bufnr))) + let cmd = gitgutter#utility#cd_cmd(a:bufnr, g:gitgutter_git_executable.' ls-files --error-unmatch --full-name -- '.gitgutter#utility#shellescape(s:filename(a:bufnr))) if g:gitgutter_async && gitgutter#async#available() - call gitgutter#async#execute(cmd, a:bufnr, { - \ 'out': {bufnr, path -> gitgutter#utility#setbufvar(bufnr, 'path', s:strip_trailing_new_line(path))}, - \ 'err': {bufnr -> gitgutter#utility#setbufvar(bufnr, 'path', -2)}, - \ }) + if has('lambda') + call gitgutter#async#execute(cmd, a:bufnr, { + \ 'out': {bufnr, path -> gitgutter#utility#setbufvar(bufnr, 'path', s:strip_trailing_new_line(path))}, + \ 'err': {bufnr -> gitgutter#utility#setbufvar(bufnr, 'path', -2)}, + \ }) + else + if has('nvim') && !has('nvim-0.2.0') + call gitgutter#async#execute(cmd, a:bufnr, { + \ 'out': function('s:set_path'), + \ 'err': function('s:not_tracked_by_git') + \ }) + else + call gitgutter#async#execute(cmd, a:bufnr, { + \ 'out': function('s:set_path'), + \ 'err': function('s:set_path', [-2]) + \ }) + endif + endif else let path = gitgutter#utility#system(cmd) if v:shell_error @@ -120,8 +145,28 @@ function! gitgutter#utility#set_repo_path(bufnr) abort endif endfunction +if has('nvim') && !has('nvim-0.2.0') + function! s:not_tracked_by_git(bufnr) + call s:set_path(a:bufnr, -2) + endfunction +endif + +function! s:set_path(bufnr, path) + if a:bufnr == -2 + let [bufnr, path] = [a:path, a:bufnr] + call gitgutter#utility#setbufvar(bufnr, 'path', path) + else + call gitgutter#utility#setbufvar(a:bufnr, 'path', s:strip_trailing_new_line(a:path)) + endif +endfunction + function! gitgutter#utility#cd_cmd(bufnr, cmd) abort - return 'cd '.s:dir(a:bufnr).' && '.a:cmd + let cd = s:unc_path(a:bufnr) ? 'pushd' : (gitgutter#utility#windows() ? 'cd /d' : 'cd') + return cd.' '.s:dir(a:bufnr).' && '.a:cmd +endfunction + +function! s:unc_path(bufnr) + return s:abs_path(a:bufnr, 0) =~ '^\\\\' endfunction function! s:use_known_shell() abort @@ -159,3 +204,7 @@ endfunction function! s:strip_trailing_new_line(line) abort return substitute(a:line, '\n$', '', '') endfunction + +function! gitgutter#utility#windows() + return has('win64') || has('win32') || has('win16') +endfunction diff --git a/pack/plugins/start/airblade-vim-gitgutter/doc/gitgutter.txt b/pack/plugins/start/airblade-vim-gitgutter/doc/gitgutter.txt index 22e1933f..d0732c3c 100644 --- a/pack/plugins/start/airblade-vim-gitgutter/doc/gitgutter.txt +++ b/pack/plugins/start/airblade-vim-gitgutter/doc/gitgutter.txt @@ -4,45 +4,50 @@ Vim Git Gutter -Author: Andy Stewart +Author: Andy Stewart Plugin Homepage: + =============================================================================== -CONTENTS *GitGutterContents* +CONTENTS *gitgutter* + + Introduction ................. |gitgutter-introduction| + Installation ................. |gitgutter-installation| + Commands ..................... |gitgutter-commands| + Mappings ..................... |gitgutter-mappings| + Autocommand .................. |gitgutter-autocommand| + Options ...................... |gitgutter-options| + Highlights ................... |gitgutter-highlights| + FAQ .......................... |gitgutter-faq| + TROUBLESHOOTING .............. |gitgutter-troubleshooting| - 1. Introduction ................. |GitGutterIntroduction| - 2. Installation ................. |GitGutterInstallation| - 3. Usage ........................ |GitGutterUsage| - 4. Commands ..................... |GitGutterCommands| - 5. Autocommand .................. |GitGutterAutocmd| - 6. CUSTOMISATION................. |GitGutterCustomisation| - 7. FAQ .......................... |GitGutterFAQ| =============================================================================== -1. INTRODUCTION *GitGutterIntroduction* - *GitGutter* +INTRODUCTION *gitgutter-introduction* -GitGutter is a Vim plugin which shows a git diff in the 'gutter' (sign -column). It shows whether each line has been added, modified, and where lines -have been removed. +GitGutter is a Vim plugin which shows a git diff in the 'gutter' (sign column). +It shows which lines have been added, modified, or removed. You can also +preview, stage, and undo individual hunks. The plugin also provides a hunk +text object. + +The signs are always up to date and the plugin never saves your buffer. -This is a port of the GitGutter plugin for Sublime Text 2. =============================================================================== -2. INSTALLATION *GitGutterInstallation* +INSTALLATION *gitgutter-installation* -* Pathogen: +Pathogen:~ > cd ~/.vim/bundle git clone git://github.com/airblade/vim-gitgutter.git < -* Voom: +Voom:~ Edit your plugin manifest (`voom edit`) and add: > airblade/vim-gitgutter < -* VimPlug: +VimPlug:~ Place this in your .vimrc: > @@ -53,7 +58,7 @@ Then run the following in Vim: :source % :PlugInstall < -* NeoBundle: +NeoBundle:~ Place this in your .vimrc: > @@ -64,7 +69,7 @@ Then run the following in Vim: :source % :NeoBundleInstall < -* No plugin manager: +No plugin manager:~ Copy vim-gitgutter's subdirectories into your vim configuration directory: > @@ -73,255 +78,462 @@ Copy vim-gitgutter's subdirectories into your vim configuration directory: < See |add-global-plugin|. + =============================================================================== -3. USAGE *GitGutterUsage* +COMMANDS *gitgutter-commands* -You don't have to do anything: it just works. +Commands for turning vim-gitgutter on and off:~ -=============================================================================== -4. COMMANDS *GitGutterCommands* + *gitgutter-:GitGutterDisable* +:GitGutterDisable Turn vim-gitgutter off for all buffers. + + *gitgutter-:GitGutterEnable* +:GitGutterEnable Turn vim-gitgutter on for all buffers. + + *gitgutter-:GitGutterToggle* +:GitGutterToggle Toggle vim-gitgutter on or off for all buffers. + + *gitgutter-:GitGutter* +:GitGutter Update signs for the current buffer. You shouldn't + need to run this. -Commands for turning Git Gutter on and off: + *gitgutter-:GitGutterAll* +:GitGutterAll Update signs for all buffers. You shouldn't need to + run this. - :GitGutterDisable *:GitGutterDisable* - Explicitly turn Git Gutter off. - :GitGutterEnable *:GitGutterEnable* - Explicitly turn Git Gutter on. +Commands for turning signs on and off (defaults to on):~ - :GitGutterToggle *:GitGutterToggle* - Explicitly turn Git Gutter on if it was off and vice versa. + *gitgutter-:GitGutterSignsEnable* +:GitGutterSignsEnable Show signs for the diff. - :GitGutter *:GitGutter* - Update signs for the current buffer. + *gitgutter-:GitGutterSignsDisable* +:GitGutterSignsDisable Do not show signs for the diff. - :GitGutterAll *:GitGutterAll* - Update signs across all buffers. + *gitgutter-:GitGutterSignsToggle* +:GitGutterSignsToggle Toggle signs on or off. -Commands for turning signs on and off (defaults to on): - :GitGutterSignsEnable *:GitGutterSignsEnable* - Explicitly turn line signs on. +Commands for turning line highlighting on and off (defaults to off):~ - :GitGutterSignsDisable *:GitGutterSignsDisable* - Explicitly turn line signs off. + *gitgutter-:GitGutterLineHighlightsEnable* +:GitGutterLineHighlightsEnable Turn on line highlighting. - :GitGutterSignsToggle *:GitGutterSignsToggle* - Explicitly turn line signs on if it was off and vice versa. + *gitgutter-:GitGutterLineHighlightsDisable* +:GitGutterLineHighlightsDisable Turn off line highlighting. -Commands for turning line highlighting on and off (defaults to off): + *gitgutter-:GitGutterLineHighlightsToggle* +:GitGutterLineHighlightsToggle Turn line highlighting on or off. - :GitGutterLineHighlightsEnable *:GitGutterLineHighlightsEnable* - Explicitly turn line highlighting on. - :GitGutterLineHighlightsDisable *:GitGutterLineHighlightsDisable* - Explicitly turn line highlighting off. +Commands for jumping between hunks:~ - :GitGutterLineHighlightsToggle *:GitGutterLineHighlightsToggle* - Explicitly turn line highlighting on if it was off and vice versa. + *gitgutter-:GitGutterNextHunk* +:GitGutterNextHunk Jump to the next [count] hunk. -Commands for jumping between marked hunks: + *gitgutter-:GitGutterPrevHunk* +:GitGutterPrevHunk Jump to the previous [count] hunk. - :GitGutterNextHunk *:GitGutterNextHunk* - Jump to the next marked hunk. Takes a count. - :GitGutterPrevHunk *:GitGutterPrevHunk* - Jump to the previous marked hunk. Takes a count. +Commands for operating on a hunk:~ -Commands for staging or undoing individual hunks: + *gitgutter-:GitGutterStageHunk* +:GitGutterStageHunk Stage the hunk the cursor is in. - :GitGutterStageHunk *:GitGutterStageHunk* - Stage the hunk the cursor is in. + *gitgutter-:GitGutterUndoHunk* +:GitGutterUndoHunk Undo the hunk the cursor is in. - :GitGutterUndoHunk *:GitGutterUndoHunk* - Undo the hunk the cursor is in. + *gitgutter-:GitGutterPreviewHunk* +:GitGutterPreviewHunk Preview the hunk the cursor is in. + Use |:pclose| or |CTRL-W_CTRL-Z| to close the preview + window. - :GitGutterPreviewHunk *:GitGutterPreviewHunk* - Preview the hunk the cursor is in. - Use |:pclose| or |CTRL-W_CTRL-Z| to close the preview window. =============================================================================== -5. AUTOCOMMAND *GitGutterAutocmd* +AUTOCOMMAND *gitgutter-autocommand* + +User GitGutter~ After updating a buffer's signs vim-gitgutter fires a |User| |autocmd| with the event GitGutter. You can listen for this event, for example: > autocmd User GitGutter call updateMyStatusLine() < +A dictionary `g:gitgutter_hook_context` is made available during its execution, +which contains an entry `bufnr` that contains the buffer number being updated. + =============================================================================== -6. CUSTOMISATION *GitGutterCustomisation* +MAPPINGS *gitgutter-mappings* -You can customise: +You can disable all these mappings with: +> + let g:gitgutter_map_keys = 0 +< -- The sign column's colours -- The signs' colours and symbols -- Line highlights -- The base of the diff -- Extra arguments for git-diff -- Key mappings -- The grep executable used -- Whether or not vim-gitgutter is on initially (defaults to on) -- Whether or not signs are shown (defaults to yes) -- Whether or not line highlighting is on initially (defaults to off) -- Whether or not vim-gitgutter runs in realtime (defaults to yes) -- Whether or not vim-gitgutter runs eagerly (defaults to yes) -- Whether or not vim-gitgutter runs asynchronously (defaults to yes) +Hunk operations:~ -Please note that vim-gitgutter won't override any colours or highlights you've -set in your colorscheme. +These can be repeated with `.` if you have vim-repeat installed. -SIGN COLUMN + *gitgutter-hp* +hp Preview the hunk under the cursor. -By default vim-gitgutter will make the sign column look like the line number -column (i.e. the |hl-LineNr| highlight group). + *gitgutter-hs* +hs Stage the hunk under the cursor. -To customise your sign column's background color, first tell vim-gitgutter to -leave it alone: + *gitgutter-hu* +hu Undo the hunk under the cursor. + +You can change these mappings like this: > - let g:gitgutter_override_sign_column_highlight = 0 + nmap ghp GitGutterPreviewHunk + nmap ghs GitGutterStageHunk + nmap ghu GitGutterUndoHunk < -And then either update your colorscheme's |hlSignColumn| highlight group or set -it in your |vimrc|: - - Desired appearance Command ~ - Same as line number column highlight clear SignColumn - User-defined (terminal Vim) highlight SignColumn ctermbg={whatever} - User-defined (graphical Vim) highlight SignColumn guibg={whatever} +Hunk jumping:~ -SIGNS' COLOURS AND SYMBOLS + *gitgutter-]c* +]c Jump to the next [count] hunk. -To customise the colours, set up the following highlight groups in your -colorscheme or |vimrc|: + *gitgutter-[c* +[c Jump to the previous [count] hunk. +You can change these mappings like this: > - GitGutterAdd " an added line - GitGutterChange " a changed line - GitGutterDelete " at least one removed line - GitGutterChangeDelete " a changed line followed by at least one removed line + nmap [c GitGutterPrevHunk + nmap ]c GitGutterNextHunk < -You can either set these with `highlight GitGutterAdd {key}={arg}...` or link -them to existing highlight groups with, say: +Hunk text object:~ + + *gitgutter-ic* *gitgutter-ac* *gitgutter-text-object* +"ic" operates on the current hunk's lines. "ac" does the same but also includes +trailing empty lines. > - highlight link GitGutterAdd DiffAdd + omap ic GitGutterTextObjectInnerPending + omap ac GitGutterTextObjectOuterPending + xmap ic GitGutterTextObjectInnerVisual + xmap ac GitGutterTextObjectOuterVisual < -To customise the symbols, add the following to your |vimrc|: + +=============================================================================== +OPTIONS *gitgutter-options* + +The most important option is 'updatetime' which determines how long (in +milliseconds) the plugin will wait after you stop typing before it updates the +signs. Vim's default is 4000. I recommend 100. + +Most important option:~ + + 'updatetime' + +Git:~ + + |g:gitgutter_git_executable| + |g:gitgutter_git_args| + |g:gitgutter_diff_args| + |g:gitgutter_diff_base| + +Grep:~ + + |g:gitgutter_grep| + +Signs:~ + + |g:gitgutter_signs| + |g:gitgutter_highlight_lines| + |g:gitgutter_max_signs| + |g:gitgutter_sign_added| + |g:gitgutter_sign_modified| + |g:gitgutter_sign_removed| + |g:gitgutter_sign_removed_first_line| + |g:gitgutter_sign_modified_removed| + |g:gitgutter_sign_column_always| + |g:gitgutter_override_sign_column_highlight| + +Terminal:~ + + |g:gitgutter_terminal_reports_focus| + +General:~ + + |g:gitgutter_enabled| + |g:gitgutter_map_keys| + |g:gitgutter_async| + |g:gitgutter_log| + + + *g:gitgutter_git_executable* +Default: 'git' + +This option determines what git binary to use. Set this if git is not on your +path. + + *g:gitgutter_git_args* +Default: empty + +Use this option to pass any extra arguments to git when running git-diff. +For example: > - let g:gitgutter_sign_added = 'xx' - let g:gitgutter_sign_modified = 'yy' - let g:gitgutter_sign_removed = 'zz' - let g:gitgutter_sign_modified_removed = 'ww' + let g:gitgutter_git_args = '--git-dir=""' < -LINE HIGHLIGHTS + *g:gitgutter_diff_args* +Default: empty -Similarly to the signs' colours, set up the following highlight groups in your -colorscheme or |vimrc|: +Use this option to pass any extra arguments to git-diff. For example: > - GitGutterAddLine " default: links to DiffAdd - GitGutterChangeLine " default: links to DiffChange - GitGutterDeleteLine " default: links to DiffDelete - GitGutterChangeDeleteLine " default: links to GitGutterChangeLineDefault + let g:gitgutter_diff_args = '-w' < -THE BASE OF THE DIFF + *g:gitgutter_diff_base* +Default: empty -By default buffers are diffed against the index. To diff against a commit -instead: +By default buffers are diffed against the index. Use this option to diff against +a revision instead. For example: > - let g:gitgutter_diff_base = '' + let g:gitgutter_diff_base = '' < -EXTRA ARGUMENTS FOR GIT-DIFF + *g:gitgutter_grep* +Default: 'grep' -To pass extra arguments to git-diff, add this to your |vimrc|: +The plugin pipes the output of git-diff into grep to minimise the amount of data +vim has to process. Set this option if grep is not on your path. + +grep must produce plain-text output without any ANSI escape codes or colours. +Use this option to turn off colours if necessary. +> + let g:gitgutter_grep = 'grep --color=never' +< +If you do not want to use grep at all (perhaps to debug why signs are not +showing), set this option to an empty string: > - let g:gitgutter_diff_args = '-w' + let g:gitgutter_grep = '' < -KEY MAPPINGS + *g:gitgutter_signs* +Default: 1 + +Determines whether or not to show signs. + + *g:gitgutter_highlight_lines* +Default: 0 + +Determines whether or not to show line highlights. -To disable all key maps: + *g:gitgutter_max_signs* +Default: 500 + +Sets the maximum number of signs to show in a buffer. Vim is slow at updating +signs, so to avoid slowing down the GUI the number of signs is capped. When +the number of changed lines exceeds this value, the plugin removes all signs +and displays a warning message. + + *g:gitgutter_sign_added* + *g:gitgutter_sign_modified* + *g:gitgutter_sign_removed* + *g:gitgutter_sign_removed_first_line* + *g:gitgutter_sign_modified_removed* +Defaults: > - let g:gitgutter_map_keys = 0 + let g:gitgutter_sign_added = '+' + let g:gitgutter_sign_modified = '~' + let g:gitgutter_sign_removed = '_' + let g:gitgutter_sign_removed_first_line = '‾' + let g:gitgutter_sign_modified_removed = '~_' < +You can use unicode characters but not images. Signs must not take up more than +2 columns. + + *g:gitgutter_sign_column_always* +Default: 0 + +This legacy option controls whether the sign column should always be shown, even +if there are no signs to display. -To change the hunk-jumping maps (defaults shown): +From Vim 7.4.2201, use 'signcolumn' instead: > - nmap [c GitGutterPrevHunk - nmap ]c GitGutterNextHunk + set signcolumn=yes < -To change the hunk-staging/undoing/previewing maps (defaults shown): + *g:gitgutter_override_sign_column_highlight* +Default: 1 + +Controls whether to make the sign column look like the line-number column (i.e. +the |hl-LineNr| highlight group). + +To customise your sign column's background color, first tell vim-gitgutter to +leave it alone: > - nmap hs GitGutterStageHunk - nmap hu GitGutterUndoHunk - nmap hp GitGutterPreviewHunk + let g:gitgutter_override_sign_column_highlight = 0 < -To change the hunk text object maps (defaults shown): +And then either update your colorscheme's |hlSignColumn| highlight group or set +it in your |vimrc|: + + Desired appearance Command ~ + Same as line-number column highlight clear SignColumn + User-defined (terminal Vim) highlight SignColumn ctermbg={whatever} + User-defined (graphical Vim) highlight SignColumn guibg={whatever} + + + *g:gitgutter_terminal_reports_focus* +Default: 1 + +Normally the plugin uses |FocusGained| to force-update all buffers when Vim +receives focus. However some terminals do not report focus events and so the +|FocusGained| autocommand never fires. + +If this applies to you, either install something like Terminus +(https://github.com/wincent/terminus) to make |FocusGained| work or set this +option to 0. + +When this option is 0, the plugin force-updates the buffer on |BufEnter| +(instead of only updating if the buffer's contents has changed since the last +update). + + *g:gitgutter_enabled* +Default: 1 + +Controls whether or not the plugin is on at startup. + + *g:gitgutter_map_keys* +Default: 1 + +Controls whether or not the plugin provides mappings. See |gitgutter-mapppings|. + + *g:gitgutter_async* +Default: 1 + +Controls whether or not diffs are run in the background. This has no effect if +your Vim does not support background jobs. + + *g:gitgutter_log* +Default: 0 + +When switched on, the plugin logs to gitgutter.log in the directory where it is +installed. Additionally it logs channel activity to channel.log. + + +=============================================================================== +HIGHLIGHTS *gitgutter-highlights* + +To change the signs' colours, set up the following highlight groups in your +colorscheme or |vimrc|: > - omap ic GitGutterTextObjectInnerPending - omap ac GitGutterTextObjectOuterPending - xmap ic GitGutterTextObjectInnerVisual - xmap ac GitGutterTextObjectOuterVisual + GitGutterAdd " an added line + GitGutterChange " a changed line + GitGutterDelete " at least one removed line + GitGutterChangeDelete " a changed line followed by at least one removed line < -TO USE A CUSTOM GREP COMMAND +You can either set these with `highlight GitGutterAdd {key}={arg}...` or link +them to existing highlight groups with, say: +> + highlight link GitGutterAdd DiffAdd +< -To use a custom invocation for grep, use this: +To change the line highlights, set up the following highlight groups in your +colorscheme or |vimrc|: > - let g:gitgutter_grep_command = 'grep' + GitGutterAddLine " default: links to DiffAdd + GitGutterChangeLine " default: links to DiffChange + GitGutterDeleteLine " default: links to DiffDelete + GitGutterChangeDeleteLine " default: links to GitGutterChangeLineDefault < -TO TURN OFF VIM-GITGUTTER BY DEFAULT -Add to your |vimrc| +=============================================================================== +FAQ *gitgutter-faq* + +a. How do I turn off realtime updates? + + Add this to your vim configuration in an |after-directory|: > - let g:gitgutter_enabled = 0 + autocmd! gitgutter CursorHold,CursorHoldI < -TO TURN OFF SIGNS BY DEFAULT +b. I turned off realtime updates, how can I have signs updated when I save a + file? -Add to your |vimrc| + If you really want to update the signs when you save a file, add this to your + |vimrc|: > - let g:gitgutter_signs = 0 + autocmd BufWritePost * GitGutter < -Note that the sign column will still be present if you have line highlighting -switched on. +c. Why can't I unstage staged changes? + + This plugin is for showing changes between the working tree and the index + (and staging/undoing those changes). Unstaging a staged hunk would require + showing changes between the index and HEAD, which is out of scope. + +d. Why are the colours in the sign column weird? + + Your colorscheme is configuring the |hl-SignColumn| highlight group weirdly. + Please see |g:gitgutter_override_sign_column_highlight| on customising the + sign column. -TO TURN ON LINE HIGHLIGHTING BY DEFAULT +e. What happens if I also use another plugin which uses signs (e.g. Syntastic)? -Add to your |vimrc| + Vim only allows one sign per line. Vim-gitgutter will not interfere with + signs it did not add. + + +=============================================================================== +TROUBLESHOOTING *gitgutter-troubleshooting* + +When no signs are showing at all:~ + +1. Try bypassing grep with: > - let g:gitgutter_highlight_lines = 1 + let g:gitgutter_grep = '' < + If it works, the problem is grep outputting ANSI escape codes. Use this + option to pass arguments to grep to turn off the escape codes. -TO TURN OFF ASYNCHRONOUS UPDATES +2. Verify git is on your path: +> + :echo system('git --version') +< -By default diffs are run asynchronously. To run diffs synchronously -instead: +3. Verify your git config is compatible with the version of git return by the + command above. -Add to your |vimrc| +4. Verify your Vim supports signs. The following should give 1: > -let g:gitgutter_async = 0 + :echo has('signs') < -=============================================================================== -7. FAQ *GitGutterFAQ* +5. Check whether the plugin thinks git knows about your file: +> + :echo getbufvar('','gitgutter').path +< + If the result is -2, the plugin thinks your file is not tracked by git. -a. Why are the colours in the sign column weird? - Your colorscheme is configuring the |hl-SignColumn| highlight group weirdly. - Please see |GitGutterCustomisation| on customising the sign column. +When the whole file is marked as added:~ + +If you use zsh, and you set "CDPATH", make sure "CDPATH" does not include the +current directory. + + +When signs take a few seconds to appear:~ -b. What happens if I also use another plugin which uses signs (e.g. Syntastic)? +Try reducing 'updatetime': +> + set updatetime=100 +< + + +When signs don't update after focusing Vim:~ + +Your terminal probably isn't reporting focus events. Either try installing +Terminus (https://github.com/wincent/terminus) or set: +> + let g:gitgutter_terminal_reports_focus = 0 +< - Vim only allows one sign per line. Before adding a sign to a line, - vim-gitgutter checks whether a sign has already been added by somebody else. - If so it doesn't do anything. In other words vim-gitgutter won't overwrite - another plugin's signs. It also won't remove another plugin's signs. diff --git a/pack/plugins/start/airblade-vim-gitgutter/doc/tags b/pack/plugins/start/airblade-vim-gitgutter/doc/tags index 2310b075..168a2ae8 100644 --- a/pack/plugins/start/airblade-vim-gitgutter/doc/tags +++ b/pack/plugins/start/airblade-vim-gitgutter/doc/tags @@ -1,26 +1,55 @@ -:GitGutter gitgutter.txt /*:GitGutter* -:GitGutterAll gitgutter.txt /*:GitGutterAll* -:GitGutterDisable gitgutter.txt /*:GitGutterDisable* -:GitGutterEnable gitgutter.txt /*:GitGutterEnable* -:GitGutterLineHighlightsDisable gitgutter.txt /*:GitGutterLineHighlightsDisable* -:GitGutterLineHighlightsEnable gitgutter.txt /*:GitGutterLineHighlightsEnable* -:GitGutterLineHighlightsToggle gitgutter.txt /*:GitGutterLineHighlightsToggle* -:GitGutterNextHunk gitgutter.txt /*:GitGutterNextHunk* -:GitGutterPrevHunk gitgutter.txt /*:GitGutterPrevHunk* -:GitGutterPreviewHunk gitgutter.txt /*:GitGutterPreviewHunk* -:GitGutterSignsDisable gitgutter.txt /*:GitGutterSignsDisable* -:GitGutterSignsEnable gitgutter.txt /*:GitGutterSignsEnable* -:GitGutterSignsToggle gitgutter.txt /*:GitGutterSignsToggle* -:GitGutterStageHunk gitgutter.txt /*:GitGutterStageHunk* -:GitGutterToggle gitgutter.txt /*:GitGutterToggle* -:GitGutterUndoHunk gitgutter.txt /*:GitGutterUndoHunk* -GitGutter gitgutter.txt /*GitGutter* -GitGutterAutocmd gitgutter.txt /*GitGutterAutocmd* -GitGutterCommands gitgutter.txt /*GitGutterCommands* -GitGutterContents gitgutter.txt /*GitGutterContents* -GitGutterCustomisation gitgutter.txt /*GitGutterCustomisation* -GitGutterFAQ gitgutter.txt /*GitGutterFAQ* -GitGutterInstallation gitgutter.txt /*GitGutterInstallation* -GitGutterIntroduction gitgutter.txt /*GitGutterIntroduction* -GitGutterUsage gitgutter.txt /*GitGutterUsage* +g:gitgutter_async gitgutter.txt /*g:gitgutter_async* +g:gitgutter_diff_args gitgutter.txt /*g:gitgutter_diff_args* +g:gitgutter_diff_base gitgutter.txt /*g:gitgutter_diff_base* +g:gitgutter_enabled gitgutter.txt /*g:gitgutter_enabled* +g:gitgutter_git_args gitgutter.txt /*g:gitgutter_git_args* +g:gitgutter_git_executable gitgutter.txt /*g:gitgutter_git_executable* +g:gitgutter_grep gitgutter.txt /*g:gitgutter_grep* +g:gitgutter_highlight_lines gitgutter.txt /*g:gitgutter_highlight_lines* +g:gitgutter_log gitgutter.txt /*g:gitgutter_log* +g:gitgutter_map_keys gitgutter.txt /*g:gitgutter_map_keys* +g:gitgutter_max_signs gitgutter.txt /*g:gitgutter_max_signs* +g:gitgutter_override_sign_column_highlight gitgutter.txt /*g:gitgutter_override_sign_column_highlight* +g:gitgutter_sign_added gitgutter.txt /*g:gitgutter_sign_added* +g:gitgutter_sign_column_always gitgutter.txt /*g:gitgutter_sign_column_always* +g:gitgutter_sign_modified gitgutter.txt /*g:gitgutter_sign_modified* +g:gitgutter_sign_modified_removed gitgutter.txt /*g:gitgutter_sign_modified_removed* +g:gitgutter_sign_removed gitgutter.txt /*g:gitgutter_sign_removed* +g:gitgutter_sign_removed_first_line gitgutter.txt /*g:gitgutter_sign_removed_first_line* +g:gitgutter_signs gitgutter.txt /*g:gitgutter_signs* +g:gitgutter_terminal_reports_focus gitgutter.txt /*g:gitgutter_terminal_reports_focus* +gitgutter gitgutter.txt /*gitgutter* +gitgutter-:GitGutter gitgutter.txt /*gitgutter-:GitGutter* +gitgutter-:GitGutterAll gitgutter.txt /*gitgutter-:GitGutterAll* +gitgutter-:GitGutterDisable gitgutter.txt /*gitgutter-:GitGutterDisable* +gitgutter-:GitGutterEnable gitgutter.txt /*gitgutter-:GitGutterEnable* +gitgutter-:GitGutterLineHighlightsDisable gitgutter.txt /*gitgutter-:GitGutterLineHighlightsDisable* +gitgutter-:GitGutterLineHighlightsEnable gitgutter.txt /*gitgutter-:GitGutterLineHighlightsEnable* +gitgutter-:GitGutterLineHighlightsToggle gitgutter.txt /*gitgutter-:GitGutterLineHighlightsToggle* +gitgutter-:GitGutterNextHunk gitgutter.txt /*gitgutter-:GitGutterNextHunk* +gitgutter-:GitGutterPrevHunk gitgutter.txt /*gitgutter-:GitGutterPrevHunk* +gitgutter-:GitGutterPreviewHunk gitgutter.txt /*gitgutter-:GitGutterPreviewHunk* +gitgutter-:GitGutterSignsDisable gitgutter.txt /*gitgutter-:GitGutterSignsDisable* +gitgutter-:GitGutterSignsEnable gitgutter.txt /*gitgutter-:GitGutterSignsEnable* +gitgutter-:GitGutterSignsToggle gitgutter.txt /*gitgutter-:GitGutterSignsToggle* +gitgutter-:GitGutterStageHunk gitgutter.txt /*gitgutter-:GitGutterStageHunk* +gitgutter-:GitGutterToggle gitgutter.txt /*gitgutter-:GitGutterToggle* +gitgutter-:GitGutterUndoHunk gitgutter.txt /*gitgutter-:GitGutterUndoHunk* +gitgutter-hp gitgutter.txt /*gitgutter-hp* +gitgutter-hs gitgutter.txt /*gitgutter-hs* +gitgutter-hu gitgutter.txt /*gitgutter-hu* +gitgutter-[c gitgutter.txt /*gitgutter-[c* +gitgutter-]c gitgutter.txt /*gitgutter-]c* +gitgutter-ac gitgutter.txt /*gitgutter-ac* +gitgutter-autocommand gitgutter.txt /*gitgutter-autocommand* +gitgutter-commands gitgutter.txt /*gitgutter-commands* +gitgutter-faq gitgutter.txt /*gitgutter-faq* +gitgutter-highlights gitgutter.txt /*gitgutter-highlights* +gitgutter-ic gitgutter.txt /*gitgutter-ic* +gitgutter-installation gitgutter.txt /*gitgutter-installation* +gitgutter-introduction gitgutter.txt /*gitgutter-introduction* +gitgutter-mappings gitgutter.txt /*gitgutter-mappings* +gitgutter-options gitgutter.txt /*gitgutter-options* +gitgutter-text-object gitgutter.txt /*gitgutter-text-object* +gitgutter-troubleshooting gitgutter.txt /*gitgutter-troubleshooting* gitgutter.txt gitgutter.txt /*gitgutter.txt* diff --git a/pack/plugins/start/airblade-vim-gitgutter/plugin/gitgutter.vim b/pack/plugins/start/airblade-vim-gitgutter/plugin/gitgutter.vim index 0844cca4..fa3d6a57 100644 --- a/pack/plugins/start/airblade-vim-gitgutter/plugin/gitgutter.vim +++ b/pack/plugins/start/airblade-vim-gitgutter/plugin/gitgutter.vim @@ -7,14 +7,9 @@ let g:loaded_gitgutter = 1 " Initialisation {{{ -" Realtime sign updates require Vim 7.3.105+. if v:version < 703 || (v:version == 703 && !has("patch105")) - let g:gitgutter_realtime = 0 -endif - -" Eager updates require gettabvar()/settabvar(). -if !exists("*gettabvar") - let g:gitgutter_eager = 0 + call gitgutter#utility#warn('requires Vim 7.3.105') + finish endif function! s:set(var, default) abort @@ -42,16 +37,19 @@ call s:set('g:gitgutter_override_sign_column_highlight', 1) call s:set('g:gitgutter_sign_added', '+') call s:set('g:gitgutter_sign_modified', '~') call s:set('g:gitgutter_sign_removed', '_') -try + +if gitgutter#utility#supports_overscore_sign() call s:set('g:gitgutter_sign_removed_first_line', '‾') -catch /E239/ - let g:gitgutter_sign_removed_first_line = '_^' -endtry +else + call s:set('g:gitgutter_sign_removed_first_line', '_^') +endif call s:set('g:gitgutter_sign_modified_removed', '~_') +call s:set('g:gitgutter_git_args', '') call s:set('g:gitgutter_diff_args', '') call s:set('g:gitgutter_diff_base', '') call s:set('g:gitgutter_map_keys', 1) +call s:set('g:gitgutter_terminal_reports_focus', 1) call s:set('g:gitgutter_async', 1) call s:set('g:gitgutter_log', 0) @@ -60,15 +58,18 @@ if !executable(g:gitgutter_git_executable) call gitgutter#utility#warn('cannot find git. Please set g:gitgutter_git_executable.') endif -call s:set('g:gitgutter_grep', 'grep') +let default_grep = 'grep' +call s:set('g:gitgutter_grep', default_grep) if !empty(g:gitgutter_grep) - if !executable(g:gitgutter_grep) - call gitgutter#utility#warn('cannot find '.g:gitgutter_grep.'. Please set g:gitgutter_grep.') - let g:gitgutter_grep = '' - else + if executable(split(g:gitgutter_grep)[0]) if $GREP_OPTIONS =~# '--color=always' let g:gitgutter_grep .= ' --color=never' endif + else + if g:gitgutter_grep !=# default_grep + call gitgutter#utility#warn('cannot find '.g:gitgutter_grep.'. Please check g:gitgutter_grep.') + endif + let g:gitgutter_grep = '' endif endif @@ -76,44 +77,51 @@ call gitgutter#highlight#define_sign_column_highlight() call gitgutter#highlight#define_highlights() call gitgutter#highlight#define_signs() +" Prevent infinite loop where: +" - executing a job in the foreground launches a new window which takes the focus; +" - when the job finishes, focus returns to gvim; +" - the FocusGained event triggers a new job (see below). +if gitgutter#utility#windows() && !(g:gitgutter_async && gitgutter#async#available()) + set noshelltemp +endif + " }}} " Primary functions {{{ -command -bar GitGutterAll call gitgutter#all(1) -command -bar GitGutter call gitgutter#process_buffer(bufnr(''), 1) +command! -bar GitGutterAll call gitgutter#all(1) +command! -bar GitGutter call gitgutter#process_buffer(bufnr(''), 1) -command -bar GitGutterDisable call gitgutter#disable() -command -bar GitGutterEnable call gitgutter#enable() -command -bar GitGutterToggle call gitgutter#toggle() +command! -bar GitGutterDisable call gitgutter#disable() +command! -bar GitGutterEnable call gitgutter#enable() +command! -bar GitGutterToggle call gitgutter#toggle() " }}} " Line highlights {{{ -command -bar GitGutterLineHighlightsDisable call gitgutter#highlight#line_disable() -command -bar GitGutterLineHighlightsEnable call gitgutter#highlight#line_enable() -command -bar GitGutterLineHighlightsToggle call gitgutter#highlight#line_toggle() +command! -bar GitGutterLineHighlightsDisable call gitgutter#highlight#line_disable() +command! -bar GitGutterLineHighlightsEnable call gitgutter#highlight#line_enable() +command! -bar GitGutterLineHighlightsToggle call gitgutter#highlight#line_toggle() " }}} " Signs {{{ -command -bar GitGutterSignsEnable call gitgutter#sign#enable() -command -bar GitGutterSignsDisable call gitgutter#sign#disable() -command -bar GitGutterSignsToggle call gitgutter#sign#toggle() +command! -bar GitGutterSignsEnable call gitgutter#sign#enable() +command! -bar GitGutterSignsDisable call gitgutter#sign#disable() +command! -bar GitGutterSignsToggle call gitgutter#sign#toggle() " }}} " Hunks {{{ -command -bar -count=1 GitGutterNextHunk call gitgutter#hunk#next_hunk() -command -bar -count=1 GitGutterPrevHunk call gitgutter#hunk#prev_hunk() +command! -bar -count=1 GitGutterNextHunk call gitgutter#hunk#next_hunk() +command! -bar -count=1 GitGutterPrevHunk call gitgutter#hunk#prev_hunk() -command -bar GitGutterStageHunk call gitgutter#hunk#stage() -command -bar GitGutterUndoHunk call gitgutter#hunk#undo() -command -bar GitGutterRevertHunk echomsg 'GitGutterRevertHunk is deprecated. Use GitGutterUndoHunk'call gitgutter#hunk#undo() -command -bar GitGutterPreviewHunk call gitgutter#hunk#preview() +command! -bar GitGutterStageHunk call gitgutter#hunk#stage() +command! -bar GitGutterUndoHunk call gitgutter#hunk#undo() +command! -bar GitGutterPreviewHunk call gitgutter#hunk#preview() " Hunk text object onoremap GitGutterTextObjectInnerPending :call gitgutter#hunk#text_object(1) @@ -153,81 +161,47 @@ endfunction " }}} -command -bar GitGutterDebug call gitgutter#debug#debug() +command! -bar GitGutterDebug call gitgutter#debug#debug() " Maps {{{ nnoremap GitGutterNextHunk &diff ? ']c' : ":\execute v:count1 . 'GitGutterNextHunk'\" nnoremap GitGutterPrevHunk &diff ? '[c' : ":\execute v:count1 . 'GitGutterPrevHunk'\" -if g:gitgutter_map_keys - if !hasmapto('GitGutterPrevHunk') && maparg('[c', 'n') ==# '' - nmap [c GitGutterPrevHunk - endif - if !hasmapto('GitGutterNextHunk') && maparg(']c', 'n') ==# '' - nmap ]c GitGutterNextHunk - endif -endif - - nnoremap GitGutterStageHunk :GitGutterStageHunk nnoremap GitGutterUndoHunk :GitGutterUndoHunk nnoremap GitGutterPreviewHunk :GitGutterPreviewHunk -if g:gitgutter_map_keys - if !hasmapto('GitGutterStageHunk') && maparg('hs', 'n') ==# '' - nmap hs GitGutterStageHunk - endif - if !hasmapto('GitGutterUndoHunk') && maparg('hu', 'n') ==# '' - nmap hu GitGutterUndoHunk - endif - if !hasmapto('GitGutterPreviewHunk') && maparg('hp', 'n') ==# '' - nmap hp GitGutterPreviewHunk - endif +" }}} - if !hasmapto('GitGutterTextObjectInnerPending') && maparg('ic', 'o') ==# '' - omap ic GitGutterTextObjectInnerPending - endif - if !hasmapto('GitGutterTextObjectOuterPending') && maparg('ac', 'o') ==# '' - omap ac GitGutterTextObjectOuterPending - endif - if !hasmapto('GitGutterTextObjectInnerVisual') && maparg('ic', 'x') ==# '' - xmap ic GitGutterTextObjectInnerVisual - endif - if !hasmapto('GitGutterTextObjectOuterVisual') && maparg('ac', 'x') ==# '' - xmap ac GitGutterTextObjectOuterVisual +function! s:on_bufenter() + if exists('t:gitgutter_didtabenter') && t:gitgutter_didtabenter + let t:gitgutter_didtabenter = 0 + call gitgutter#all(!g:gitgutter_terminal_reports_focus) + else + call gitgutter#init_buffer(bufnr('')) + call gitgutter#process_buffer(bufnr(''), !g:gitgutter_terminal_reports_focus) endif -endif - -" }}} +endfunction " Autocommands {{{ augroup gitgutter autocmd! - autocmd TabEnter * call settabvar(tabpagenr(), 'gitgutter_didtabenter', 1) + autocmd TabEnter * let t:gitgutter_didtabenter = 1 - autocmd BufEnter * - \ if gettabvar(tabpagenr(), 'gitgutter_didtabenter') | - \ call settabvar(tabpagenr(), 'gitgutter_didtabenter', 0) | - \ call gitgutter#all(0) | - \ else | - \ call gitgutter#init_buffer(bufnr('')) | - \ call gitgutter#process_buffer(bufnr(''), 0) | - \ endif + autocmd BufEnter * call s:on_bufenter() - autocmd CursorHold,CursorHoldI * call gitgutter#process_buffer(bufnr(''), 0) - autocmd FileChangedShellPost,ShellCmdPost * call gitgutter#process_buffer(bufnr(''), 1) + autocmd CursorHold,CursorHoldI * call gitgutter#process_buffer(bufnr(''), 0) + autocmd FileChangedShellPost * call gitgutter#process_buffer(bufnr(''), 1) " Ensure that all buffers are processed when opening vim with multiple files, e.g.: " " vim -o file1 file2 autocmd VimEnter * if winnr() != winnr('$') | call gitgutter#all(0) | endif - if !has('gui_win32') - autocmd FocusGained * call gitgutter#all(1) - endif + autocmd FocusGained,ShellCmdPost * call gitgutter#all(1) autocmd ColorScheme * call gitgutter#highlight#define_sign_column_highlight() | call gitgutter#highlight#define_highlights() diff --git a/pack/plugins/start/airblade-vim-gitgutter/test/cp932.txt b/pack/plugins/start/airblade-vim-gitgutter/test/cp932.txt new file mode 100644 index 00000000..80cb10bf --- /dev/null +++ b/pack/plugins/start/airblade-vim-gitgutter/test/cp932.txt @@ -0,0 +1,8 @@ +The quick brown fox jumps +over the lazy dog + +͂ɂقւƂʂ +킩悽ꂻ‚˂Ȃ +̂܂ӂ +߂݂Ђ + diff --git a/pack/plugins/start/airblade-vim-gitgutter/test/test b/pack/plugins/start/airblade-vim-gitgutter/test/test index 6247a058..4daf052f 100755 --- a/pack/plugins/start/airblade-vim-gitgutter/test/test +++ b/pack/plugins/start/airblade-vim-gitgutter/test/test @@ -2,6 +2,8 @@ VIM="/Applications/MacVim.app/Contents/MacOS/Vim -v" +export VIM_GITGUTTER_TEST=1 + $VIM -u NONE -U NONE -N \ --cmd 'set rtp+=../' \ --cmd 'let g:gitgutter_async=0' \ diff --git a/pack/plugins/start/airblade-vim-gitgutter/test/test_gitgutter.vim b/pack/plugins/start/airblade-vim-gitgutter/test/test_gitgutter.vim index 2c820cd3..98b42209 100644 --- a/pack/plugins/start/airblade-vim-gitgutter/test/test_gitgutter.vim +++ b/pack/plugins/start/airblade-vim-gitgutter/test/test_gitgutter.vim @@ -31,6 +31,10 @@ function s:git_diff_staged() return split(system('git diff -U0 --staged fixture.txt'), '\n') endfunction +function s:trigger_gitgutter() + doautocmd CursorHold +endfunction + " " SetUp / TearDown @@ -40,7 +44,8 @@ function SetUp() call system("git init ".s:test_repo. \ " && cd ".s:test_repo. \ " && cp ../fixture.txt .". - \ " && git add . && git commit -m 'initial'") + \ " && git add . && git commit -m 'initial'". + \ " && git config diff.mnemonicPrefix false") execute ':cd' s:test_repo edit! fixture.txt call gitgutter#sign#reset() @@ -64,7 +69,7 @@ endfunction function Test_add_lines() normal ggo* - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ["line=2 id=3000 name=GitGutterLineAdded"] call assert_equal(expected, s:signs('fixture.txt')) @@ -76,7 +81,7 @@ function Test_add_lines_fish() set shell=/usr/local/bin/fish normal ggo* - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ["line=2 id=3000 name=GitGutterLineAdded"] call assert_equal(expected, s:signs('fixture.txt')) @@ -87,7 +92,7 @@ endfunction function Test_modify_lines() normal ggi* - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ["line=1 id=3000 name=GitGutterLineModified"] call assert_equal(expected, s:signs('fixture.txt')) @@ -96,7 +101,7 @@ endfunction function Test_remove_lines() execute '5d' - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ["line=4 id=3000 name=GitGutterLineRemoved"] call assert_equal(expected, s:signs('fixture.txt')) @@ -105,7 +110,7 @@ endfunction function Test_remove_first_lines() execute '1d' - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ["line=1 id=3000 name=GitGutterLineRemovedFirstLine"] call assert_equal(expected, s:signs('fixture.txt')) @@ -115,7 +120,7 @@ endfunction function Test_edit_file_with_same_name_as_a_branch() normal 5Gi* call system('git checkout -b fixture.txt') - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ["line=5 id=3000 name=GitGutterLineModified"] call assert_equal(expected, s:signs('fixture.txt')) @@ -127,7 +132,7 @@ function Test_file_added_to_git() call system('touch '.tmpfile.' && git add '.tmpfile) execute 'edit '.tmpfile normal ihello - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ["line=1 id=3000 name=GitGutterLineAdded"] call assert_equal(expected, s:signs('fileAddedToGit.tmp')) @@ -138,7 +143,7 @@ function Test_filename_with_equals() call system('touch =fixture=.txt && git add =fixture=.txt') edit =fixture=.txt normal ggo* - doautocmd CursorHold + call s:trigger_gitgutter() let expected = [ \ 'line=1 id=3000 name=GitGutterLineAdded', @@ -152,7 +157,7 @@ function Test_filename_with_square_brackets() call system('touch fix[tu]re.txt && git add fix[tu]re.txt') edit fix[tu]re.txt normal ggo* - doautocmd CursorHold + call s:trigger_gitgutter() let expected = [ \ 'line=1 id=3000 name=GitGutterLineAdded', @@ -162,13 +167,27 @@ function Test_filename_with_square_brackets() endfunction +function Test_filename_leading_dash() + call system('touch -- -fixture.txt && git add -- -fixture.txt') + edit -fixture.txt + normal ggo* + call s:trigger_gitgutter() + + let expected = [ + \ 'line=1 id=3000 name=GitGutterLineAdded', + \ 'line=2 id=3001 name=GitGutterLineAdded' + \ ] + call assert_equal(expected, s:signs('-fixture.txt')) +endfunction + + " FIXME: this test fails when it is the first (or only) test to be run function Test_follow_symlink() let tmp = 'symlink' call system('ln -nfs fixture.txt '.tmp) execute 'edit '.tmp 6d - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ['line=5 id=3000 name=GitGutterLineRemoved'] call assert_equal(expected, s:signs('symlink')) @@ -183,7 +202,7 @@ function Test_keep_alt() call assert_equal('', bufname('#')) normal ggx - doautocmd CursorHold + call s:trigger_gitgutter() call assert_equal('', bufname('#')) endfunction @@ -193,7 +212,7 @@ function Test_keep_modified() normal 5Go* call assert_equal(1, getbufvar('', '&modified')) - doautocmd CursorHold + call s:trigger_gitgutter() call assert_equal(1, getbufvar('', '&modified')) endfunction @@ -204,7 +223,7 @@ function Test_keep_op_marks() call assert_equal([0,6,1,0], getpos("'[")) call assert_equal([0,6,2,0], getpos("']")) - doautocmd CursorHold + call s:trigger_gitgutter() call assert_equal([0,6,1,0], getpos("'[")) call assert_equal([0,6,2,0], getpos("']")) @@ -218,9 +237,9 @@ endfunction function Test_orphaned_signs() execute "normal 5GoX\Y" - doautocmd CursorHold + call s:trigger_gitgutter() 6d - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ['line=6 id=3001 name=GitGutterLineAdded'] call assert_equal(expected, s:signs('fixture.txt')) @@ -241,7 +260,7 @@ function Test_untracked_file_within_repo() call system('touch '.tmp) execute 'edit '.tmp normal ggo* - doautocmd CursorHold + call s:trigger_gitgutter() call assert_equal([], s:signs(tmp)) @@ -254,7 +273,7 @@ function Test_untracked_file_square_brackets_within_repo() call system('touch '.tmp) execute 'edit '.tmp normal ggo* - doautocmd CursorHold + call s:trigger_gitgutter() call assert_equal([], s:signs(tmp)) @@ -393,9 +412,9 @@ function Test_undo_nearby_hunk() execute "normal! 2Gox\y\z" normal 2jdd normal k - doautocmd CursorHold + call s:trigger_gitgutter() GitGutterUndoHunk - doautocmd CursorHold + call s:trigger_gitgutter() let expected = [ \ 'line=3 id=3000 name=GitGutterLineAdded', @@ -430,10 +449,141 @@ function Test_write_option() set nowrite normal ggo* - doautocmd CursorHold + call s:trigger_gitgutter() let expected = ["line=2 id=3000 name=GitGutterLineAdded"] call assert_equal(expected, s:signs('fixture.txt')) set write endfunction + + +function Test_inner_text_object() + execute "normal! 2Gox\y\z\\" + call s:trigger_gitgutter() + normal dic + call s:trigger_gitgutter() + + call assert_equal([], s:signs('fixture.txt')) + call assert_equal(readfile('fixture.txt'), getline(1,'$')) + + " Excludes trailing lines + normal 9Gi* + normal 10Gi* + call s:trigger_gitgutter() + execute "normal vic\" + call assert_equal([9, 10], [line("'<"), line("'>")]) +endfunction + + +function Test_around_text_object() + execute "normal! 2Gox\y\z\\" + call s:trigger_gitgutter() + normal dac + call s:trigger_gitgutter() + + call assert_equal([], s:signs('fixture.txt')) + call assert_equal(readfile('fixture.txt'), getline(1,'$')) + + " Includes trailing lines + normal 9Gi* + normal 10Gi* + call s:trigger_gitgutter() + execute "normal vac\" + call assert_equal([9, 11], [line("'<"), line("'>")]) +endfunction + + +function Test_user_autocmd() + autocmd User GitGutter let s:autocmd_user = g:gitgutter_hook_context.bufnr + + " Verify not fired when nothing changed. + let s:autocmd_user = 0 + call s:trigger_gitgutter() + call assert_equal(0, s:autocmd_user) + + " Verify fired when there was a change. + normal ggo* + let bufnr = bufnr('') + call s:trigger_gitgutter() + call assert_equal(bufnr, s:autocmd_user) +endfunction + + +function Test_fix_file_references() + " No special characters + let hunk_diff = join([ + \ 'diff --git a/fixture.txt b/fixture.txt', + \ 'index f5c6aff..3fbde56 100644', + \ '--- a/fixture.txt', + \ '+++ b/fixture.txt', + \ '@@ -2,0 +3,1 @@ b', + \ '+x' + \ ], "\n")."\n" + let filepath = 'blah.txt' + + let expected = join([ + \ 'diff --git a/blah.txt b/blah.txt', + \ 'index f5c6aff..3fbde56 100644', + \ '--- a/blah.txt', + \ '+++ b/blah.txt', + \ '@@ -2,0 +3,1 @@ b', + \ '+x' + \ ], "\n")."\n" + + call assert_equal(expected, gitgutter#hunk#fix_file_references(filepath, hunk_diff)) + + " diff.mnemonicPrefix; spaces in filename + let hunk_diff = join([ + \ 'diff --git i/x/cat dog w/x/cat dog', + \ 'index f5c6aff..3fbde56 100644', + \ '--- i/x/cat dog', + \ '+++ w/x/cat dog', + \ '@@ -2,0 +3,1 @@ b', + \ '+x' + \ ], "\n")."\n" + let filepath = 'blah.txt' + + let expected = join([ + \ 'diff --git i/blah.txt w/blah.txt', + \ 'index f5c6aff..3fbde56 100644', + \ '--- i/blah.txt', + \ '+++ w/blah.txt', + \ '@@ -2,0 +3,1 @@ b', + \ '+x' + \ ], "\n")."\n" + + call assert_equal(expected, gitgutter#hunk#fix_file_references(filepath, hunk_diff)) + + " Backslashes in filename; quotation marks + let hunk_diff = join([ + \ 'diff --git "a/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\11.1.vim" "b/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\12.1.vim"', + \ 'index f42aeb0..4930403 100644', + \ '--- "a/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\11.1.vim"', + \ '+++ "b/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\12.1.vim"', + \ '@@ -172,0 +173 @@ stuff', + \ '+x' + \ ], "\n")."\n" + let filepath = 'init.vim' + + let expected = join([ + \ 'diff --git "a/init.vim" "b/init.vim"', + \ 'index f42aeb0..4930403 100644', + \ '--- "a/init.vim"', + \ '+++ "b/init.vim"', + \ '@@ -172,0 +173 @@ stuff', + \ '+x' + \ ], "\n")."\n" + + call assert_equal(expected, gitgutter#hunk#fix_file_references(filepath, hunk_diff)) +endfunction + + +function Test_encoding() + call system('cp ../cp932.txt . && git add cp932.txt') + edit ++enc=cp932 cp932.txt + + call s:trigger_gitgutter() + + call assert_equal([], s:signs('cp932.txt')) +endfunction diff --git a/pack/plugins/start/airblade-vim-gitgutter/unplace.vim b/pack/plugins/start/airblade-vim-gitgutter/unplace.vim new file mode 100644 index 00000000..a97993d9 --- /dev/null +++ b/pack/plugins/start/airblade-vim-gitgutter/unplace.vim @@ -0,0 +1,27 @@ +" Measure how long it takes to unplace signs. +" +" Source this file with `:source %` or `vim -S unplace.vim` + + +let num = 500 +sign define Foo text=* + +new + +call append(0, range(1, num)) + +for i in range(1, num) + execute "sign place ".i." line=".i." name=Foo buffer=".bufnr('') +endfor + +let start = reltime() +for i in range(1, num) + execute "sign unplace ".i +endfor +let elapsed = reltime(start) + +bdelete! + +echom split(reltimestr(elapsed))[0]."s to remove ".num." signs" +echom string(reltimefloat(elapsed) * 1000 / num).' ms/sign' +echom string(float2nr(num / reltimefloat(elapsed))).' sign/s' diff --git a/pack/plugins/start/fatih-vim-go/.codecov.yml b/pack/plugins/start/fatih-vim-go/.codecov.yml new file mode 100644 index 00000000..e9062d50 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/.codecov.yml @@ -0,0 +1,12 @@ +--- +coverage: + status: + project: + default: + target: auto + threshold: 1 + base: auto +comment: false +ignore: + - "!autoload/go/*.vim$" + - "autoload/go/*_test.vim$" diff --git a/pack/plugins/start/fatih-vim-go/.coveragerc b/pack/plugins/start/fatih-vim-go/.coveragerc index 9f46ed7f..8cddf91c 100644 --- a/pack/plugins/start/fatih-vim-go/.coveragerc +++ b/pack/plugins/start/fatih-vim-go/.coveragerc @@ -1,3 +1,3 @@ [run] plugins = covimerage -data_file = .coverage.covimerage +data_file = .coverage_covimerage diff --git a/pack/plugins/start/fatih-vim-go/.github/ISSUE_TEMPLATE.md b/pack/plugins/start/fatih-vim-go/.github/ISSUE_TEMPLATE.md index dd376ee8..f2272398 100644 --- a/pack/plugins/start/fatih-vim-go/.github/ISSUE_TEMPLATE.md +++ b/pack/plugins/start/fatih-vim-go/.github/ISSUE_TEMPLATE.md @@ -9,14 +9,13 @@ ### Configuration (**MUST** fill this out): -* Vim version (first two lines from `:version`): - -* Go version (`go version`): - -* Go environment (`go env`): - * vim-go version: * `vimrc` you used to reproduce (use a *minimal* vimrc with other plugins disabled; do not link to a 2,000 line vimrc): +* Vim version (first three lines from `:version`): + +* Go version (`go version`): + +* Go environment (`go env`): diff --git a/pack/plugins/start/fatih-vim-go/.travis.yml b/pack/plugins/start/fatih-vim-go/.travis.yml index fe50e850..b6f0b4de 100644 --- a/pack/plugins/start/fatih-vim-go/.travis.yml +++ b/pack/plugins/start/fatih-vim-go/.travis.yml @@ -1,4 +1,6 @@ language: go +go: + - 1.10.x notifications: email: false matrix: @@ -6,9 +8,16 @@ matrix: - env: SCRIPT="test -c" VIM_VERSION=vim-7.4 - env: SCRIPT="test -c" VIM_VERSION=vim-8.0 - env: SCRIPT="test -c" VIM_VERSION=nvim - - env: SCRIPT=lint VIM_VERSION=vim-8.0 + - env: ENV=vimlint SCRIPT=lint VIM_VERSION=vim-8.0 + language: python + python: 3.6 install: - ./scripts/install-vim $VIM_VERSION - - pip install --user vim-vint covimerage codecov + - | + if [ "$ENV" = "vimlint" ]; then + pip install vim-vint covimerage codecov pathlib + else + pip install --user vim-vint covimerage codecov pathlib + fi script: - ./scripts/$SCRIPT $VIM_VERSION diff --git a/pack/plugins/start/fatih-vim-go/CHANGELOG.md b/pack/plugins/start/fatih-vim-go/CHANGELOG.md index 4df276c8..fde19880 100644 --- a/pack/plugins/start/fatih-vim-go/CHANGELOG.md +++ b/pack/plugins/start/fatih-vim-go/CHANGELOG.md @@ -1,5 +1,117 @@ ## unplanned +IMPROVEMENTS: +* Unify async job handling for Vim8 and Neovim. + [[GH-1864]](https://github.com/fatih/vim-go/pull/1864) +* Document Vim and Neovim requirements in README.md and help file. + [[GH-1889]](https://github.com/fatih/vim-go/pull/1889) + +BUG FIXES: +* Fix `:GoRun %` on Windows. + [[GH-1900]](github.com/fatih/vim-go/pull/1900) + +## 1.18 - (July 18, 2018) + +FEATURES: + +* Add **:GoIfErr** command together with the `(go-iferr)` plug key to + create a custom mapping. This command generates an `if err != nil { return ... }` + automatically which infer the type of return values and the numbers. + For example: + + ``` + func doSomething() (string, error) { + f, err := os.Open("file") + } + ``` + + Becomes: + + ``` + func doSomething() (string, error) { + f, err := os.Open("file") + if err != nil { + return "", err + } + } + ``` + +* Two new text objects has been added: + * `ic` (inner comment) selects the content of the comment, excluding the start/end markers (i.e: `//`, `/*`) + * `ac` (a comment) selects the content of the whole commment block, including markers + To use this new feature, make sure you use use the latest version of + [motion](https://github.com/fatih/motion). You can update the tool from Vim + via `:GoUpdateBinaries` + [[GH-1779]](https://github.com/fatih/vim-go/pull/1779) +* Add `:GoPointsTo` to show all variables to which the pointer under the cursor + may point to. + [[GH-1751]](https://github.com/fatih/vim-go/pull/1751) +* Add `:GoReportGitHubIssue` to initialize a new GitHub issue with as much data + that our template requests as possible. + [[GH-1738]](https://github.com/fatih/vim-go/pull/1738) + +IMPROVEMENTS: + +* Add build tags (with `g:go_build_tags`) to all commands that support it. + [[GH-1705]](https://github.com/fatih/vim-go/pull/1705) +* Some command which operate on files (rather than Vim buffers) will now show a + warning if there are unsaved buffers, similar to Vim's `:make`. + [[GH-1754]](https://github.com/fatih/vim-go/pull/1754) +* Don't return an error from `:GoGuru` functions when the import path is + unknown and scope is unneeded. + [[GH-1826]](https://github.com/fatih/vim-go/pull/1826) +* Performance improvements for the `go.vim` syntax file. + [[GH-1799]](https://github.com/fatih/vim-go/pull/1799) +* Allow `GoDebugBreakpoint` and `GoDebugCurrent` highlight groups to be + overridden by user configuration. + [[GH-1850]](https://github.com/vim-go/pull/1850) +* Strip trailing carriage returns from quickfix errors that are parsed + manually. [[GH-1861]](https://github.com/fatih/vim-go/pull/1861). +* Cleanup title of terminal window. + [[GH-1861]](https://github.com/fatih/vim-go/pull/1861). +* Add `:GoImpl` is able to complete interfaces by their full import path in + addition to the current package name (i.e: `:GoImpl t *T github.com/BurntSushi/toml.Unmarshaller` + is now possible) + [[GH-1884]](https://github.com/fatih/vim-go/pull/1884) + +BUG FIXES: + +* Update the correct window's location list after a long running async job + completes, even when the user changes their window layout while the job is + running. + [[GH-1734]](https://github.com/fatih/vim-go/pull/1734) +* Apply debugger mappings only for Go buffers, and not all buffers. + [[GH-1696]](https://github.com/fatih/vim-go/pull/1696) +* The `gohtmltmpl` filetype will now highlight `{{ .. }}` syntax HTML attributes + and some other locations. + [[GH-1790]](https://github.com/fatih/vim-go/pull/1790) +* Update using the correct logging flag option that was caused with the recent + delve changes + [[GH-1809]](https://github.com/fatih/vim-go/pull/1809) +* Fix gocode option string values that would cause gocode settings not to set + correctly + [[GH-1818]](https://github.com/fatih/vim-go/pull/1818) +* Fix Neovim handling of guru output. + [[GH-1846]](https://github.com/fatih/vim-go/pull/1846) +* Execute commands correctly when they are in $GOBIN but not $PATH. + [[GH-1866]](https://github.com/fatih/vim-go/pull/1866) +* Open files correctly with ctrlp. + [[GH-1878]](https://github.com/fatih/vim-go/pull/1878) +* Fix checking guru binary path + [[GH-1886]](https://github.com/fatih/vim-go/pull/1886) +* Add build tags to `:GoDef` if only it's present + [[GH-1882]](https://github.com/fatih/vim-go/pull/1882) + +## 1.17 - (March 27, 2018) + +FEATURES: + +* **Debugger support!** Add integrated support for the + [`delve`](https://github.com/derekparker/delve) debugger. Use + `:GoInstallBinaries` to install `dlv`, and see `:help go-debug` to get + started. + [[GH-1390]](https://github.com/fatih/vim-go/pull/1390) + IMPROVEMENTS: * Add descriptions to neosnippet abbrevations. @@ -10,6 +122,14 @@ IMPROVEMENTS: [[GH-1652]](https://github.com/fatih/vim-go/pull/1652) * Redraw the screen before executing blocking calls to gocode. [[GH-1671]](https://github.com/fatih/vim-go/pull/1671) +* Add `fe` -> `fmt.Errorf()` snippet for NeoSnippet and UltiSnippets. + [[GH-1677]](https://github.com/fatih/vim-go/pull/1677) +* Use the async api when calling guru from neovim. + [[GH-1678]](https://github.com/fatih/vim-go/pull/1678) +* Use the async api when calling gocode to get type info. + [[GH-1697]](https://github.com/fatih/vim-go/pull/1697) +* Cache import path lookups to improve responsiveness. + [[GH-1713]](https://github.com/fatih/vim-go/pull/1713) BUG FIXES: @@ -49,7 +169,17 @@ BUG FIXES: * Show the file location of test errors when the message is empty or begins with a newline. [[GH-1664]](https://github.com/fatih/vim-go/pull/1664) - +* Fix minisnip on Windows. + [[GH-1698]](https://github.com/fatih/vim-go/pull/1698) +* Keep alternate filename when loading an autocreate template. + [[GH-1675]](https://github.com/fatih/vim-go/pull/1675) +* Parse the column number in errors correctly in vim8 and neovim. + [[GH-1716]](https://github.com/fatih/vim-go/pull/1716) +* Fix race conditions in the terminal handling for neovim. + [[GH-1721]](https://github.com/fatih/vim-go/pull/1721) +* Put the user back in the original window regardless of the value of + `splitright` after starting a neovim terminal window. + [[GH-1725]](https://github.com/fatih/vim-go/pull/1725) BACKWARDS INCOMPATIBILITIES: @@ -59,7 +189,7 @@ BACKWARDS INCOMPATIBILITIES: * `go_highlight_methods` - in favor of the following settings and changes: + in favor of the following settings and changes: * `go_highlight_functions`: This highlights now all function and method declarations (whereas previously it would also highlight function and @@ -69,6 +199,9 @@ BACKWARDS INCOMPATIBILITIES: [[GH-1557]](https://github.com/fatih/vim-go/pull/1557) * Rename g`g:go_metalinter_excludes` to `g:go_metalinter_disabled`. [[GH-1648]](https://github.com/fatih/vim-go/pull/1648) +* `:GoBuild` doesn't append the `-i` flag anymore due the recent Go 1.10 + changes that introduced a build cache. + [[GH-1701]](https://github.com/fatih/vim-go/pull/1701) ## 1.16 - (December 29, 2017) diff --git a/pack/plugins/start/fatih-vim-go/Dockerfile b/pack/plugins/start/fatih-vim-go/Dockerfile index e7ff6298..b46cf5c0 100644 --- a/pack/plugins/start/fatih-vim-go/Dockerfile +++ b/pack/plugins/start/fatih-vim-go/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.9.2 +FROM golang:1.10.1 RUN apt-get update -y && \ apt-get install -y build-essential curl git libncurses5-dev python3-pip && \ diff --git a/pack/plugins/start/fatih-vim-go/README.md b/pack/plugins/start/fatih-vim-go/README.md index 8566224a..0e74a031 100644 --- a/pack/plugins/start/fatih-vim-go/README.md +++ b/pack/plugins/start/fatih-vim-go/README.md @@ -9,16 +9,16 @@ This plugin adds Go language support for Vim, with the following main features: * Compile your package with `:GoBuild`, install it with `:GoInstall` or test it - with `:GoTest`. Run a single tests with `:GoTestFunc`). + with `:GoTest`. Run a single test with `:GoTestFunc`). * Quickly execute your current file(s) with `:GoRun`. * Improved syntax highlighting and folding. +* Debug programs with integrated `delve` support with `:GoDebugStart`. * Completion support via `gocode`. * `gofmt` or `goimports` on save keeps the cursor position and undo history. * Go to symbol/declaration with `:GoDef`. * Look up documentation with `:GoDoc` or `:GoDocBrowser`. * Easily import packages via `:GoImport`, remove them via `:GoDrop`. -* Automatic `GOPATH` detection which works with `gb` and `godep`. Change or - display `GOPATH` with `:GoPath`. +* Precise type-safe renaming of identifiers with `:GoRename`. * See which code is covered by tests with `:GoCoverage`. * Add or remove tags on struct fields with `:GoAddTags` and `:GoRemoveTags`. * Call `gometalinter` with `:GoMetaLinter` to invoke all possible linters @@ -28,16 +28,18 @@ This plugin adds Go language support for Vim, with the following main features: errors, or make sure errors are checked with `:GoErrCheck`. * Advanced source analysis tools utilizing `guru`, such as `:GoImplements`, `:GoCallees`, and `:GoReferrers`. -* Precise type-safe renaming of identifiers with `:GoRename`. * ... and many more! Please see [doc/vim-go.txt](doc/vim-go.txt) for more information. ## Install +vim-go requires at least Vim 7.4.1689 or Neovim 0.2.2. + The [**latest stable release**](https://github.com/fatih/vim-go/releases/latest) is the recommended version to use. If you choose to use the master branch instead, please do so with caution; it is a _development_ branch. + vim-go follows the standard runtime path structure. Below are some helper lines for popular package managers: @@ -46,7 +48,9 @@ for popular package managers: * [Pathogen](https://github.com/tpope/vim-pathogen) * `git clone https://github.com/fatih/vim-go.git ~/.vim/bundle/vim-go` * [vim-plug](https://github.com/junegunn/vim-plug) - * `Plug 'fatih/vim-go'` + * `Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }` +* [Vundle](https://github.com/VundleVim/Vundle.vim) + * `Plugin 'fatih/vim-go'` You will also need to install all the necessary binaries. vim-go makes it easy to install all of them by providing a command, `:GoInstallBinaries`, which will diff --git a/pack/plugins/start/fatih-vim-go/autoload/ctrlp/decls.vim b/pack/plugins/start/fatih-vim-go/autoload/ctrlp/decls.vim index 00ecb89a..5e68b3f9 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/ctrlp/decls.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/ctrlp/decls.vim @@ -60,36 +60,35 @@ function! ctrlp#decls#enter() abort let s:current_dir = fnameescape(expand('%:p:h')) let s:decls = [] - let bin_path = go#path#CheckBinPath('motion') - if empty(bin_path) - return - endif - let command = printf("%s -format vim -mode decls", bin_path) - let command .= " -include ". get(g:, "go_decls_includes", "func,type") + let l:cmd = ['motion', + \ '-format', 'vim', + \ '-mode', 'decls', + \ '-include', go#config#DeclsIncludes(), + \ ] call go#cmd#autowrite() if s:mode == 0 " current file mode - let fname = expand("%:p") + let l:fname = expand("%:p") if exists('s:target') - let fname = s:target + let l:fname = s:target endif - let command .= printf(" -file %s", fname) + let cmd += ['-file', l:fname] else " all functions mode - let dir = expand("%:p:h") + let l:dir = expand("%:p:h") if exists('s:target') - let dir = s:target + let l:dir = s:target endif - let command .= printf(" -dir %s", dir) + let cmd += ['-dir', l:dir] endif - let out = go#util#System(command) - if go#util#ShellError() != 0 - call go#util#EchoError(out) + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err + call go#util#EchoError(l:out) return endif @@ -118,7 +117,7 @@ function! ctrlp#decls#enter() abort call add(s:decls, printf("%s\t%s |%s:%s:%s|\t%s", \ decl.ident . space, \ decl.keyword, - \ fnamemodify(decl.filename, ":t"), + \ fnamemodify(decl.filename, ":."), \ decl.line, \ decl.col, \ decl.full, diff --git a/pack/plugins/start/fatih-vim-go/autoload/fzf/decls.vim b/pack/plugins/start/fatih-vim-go/autoload/fzf/decls.vim index 255320ec..246103ec 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/fzf/decls.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/fzf/decls.vim @@ -58,35 +58,34 @@ function! s:source(mode,...) abort let s:current_dir = expand('%:p:h') let ret_decls = [] - let bin_path = go#path#CheckBinPath('motion') - if empty(bin_path) - return - endif - let command = printf("%s -format vim -mode decls", bin_path) - let command .= " -include ". get(g:, "go_decls_includes", "func,type") + let l:cmd = ['motion', + \ '-format', 'vim', + \ '-mode', 'decls', + \ '-include', go#config#DeclsIncludes(), + \ ] call go#cmd#autowrite() if a:mode == 0 " current file mode - let fname = expand("%:p") + let l:fname = expand("%:p") if a:0 && !empty(a:1) - let fname = a:1 + let l:fname = a:1 endif - let command .= printf(" -file %s", shellescape(fname)) + let cmd += ['-file', l:fname] else " all functions mode if a:0 && !empty(a:1) let s:current_dir = a:1 endif - let command .= printf(" -dir %s", shellescape(s:current_dir)) + let l:cmd += ['-dir', s:current_dir] endif - let out = go#util#System(command) - if go#util#ShellError() != 0 - call go#util#EchoError(out) + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err + call go#util#EchoError(l:out) return endif diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/alternate.vim b/pack/plugins/start/fatih-vim-go/autoload/go/alternate.vim index f2cb210f..9d8af5a1 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/alternate.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/alternate.vim @@ -1,8 +1,3 @@ -" By default use edit (current buffer view) to switch -if !exists("g:go_alternate_mode") - let g:go_alternate_mode = "edit" -endif - " Test alternates between the implementation of code and the test code. function! go#alternate#Switch(bang, cmd) abort let file = expand('%') @@ -23,7 +18,7 @@ function! go#alternate#Switch(bang, cmd) abort call go#util#EchoError("couldn't find ".alt_file) return elseif empty(a:cmd) - execute ":" . g:go_alternate_mode . " " . alt_file + execute ":" . go#config#AlternateMode() . " " . alt_file else execute ":" . a:cmd . " " . alt_file endif diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/asmfmt.vim b/pack/plugins/start/fatih-vim-go/autoload/go/asmfmt.vim index cc8acd3b..c450e334 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/asmfmt.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/asmfmt.vim @@ -28,41 +28,40 @@ function! go#asmfmt#Format() abort call writefile(go#util#GetLines(), l:tmpname) " Run asmfmt. - let path = go#path#CheckBinPath("asmfmt") - if empty(path) + let [l:out, l:err] = go#util#Exec(['asmfmt', '-w', l:tmpname]) + if l:err + call go#util#EchoError(l:out) return endif - let out = go#util#System(path . ' -w ' . l:tmpname) - " If there's no error, replace the current file with the output. - if go#util#ShellError() == 0 - " Remove undo point caused by BufWritePre. - try | silent undojoin | catch | endtry + " Remove undo point caused by BufWritePre. + try | silent undojoin | catch | endtry - " Replace the current file with the temp file; then reload the buffer. - let old_fileformat = &fileformat - " save old file permissions - let original_fperm = getfperm(expand('%')) - call rename(l:tmpname, expand('%')) - " restore old file permissions - call setfperm(expand('%'), original_fperm) - silent edit! - let &fileformat = old_fileformat - let &syntax = &syntax - endif + " Replace the current file with the temp file; then reload the buffer. + let old_fileformat = &fileformat + + " save old file permissions + let original_fperm = getfperm(expand('%')) + call rename(l:tmpname, expand('%')) + + " restore old file permissions + call setfperm(expand('%'), original_fperm) + silent edit! + let &fileformat = old_fileformat + let &syntax = &syntax " Restore the cursor/window positions. call winrestview(l:curw) endfunction function! go#asmfmt#ToggleAsmFmtAutoSave() abort - if get(g:, "go_asmfmt_autosave", 0) - let g:go_asmfmt_autosave = 1 + if go#config#AsmfmtAutosave() + call go#config#SetAsmfmtAutosave(1) call go#util#EchoProgress("auto asmfmt enabled") return end - let g:go_asmfmt_autosave = 0 + call go#config#SetAsmfmtAutosave(0) call go#util#EchoProgress("auto asmfmt disabled") endfunction diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/cmd.vim b/pack/plugins/start/fatih-vim-go/autoload/go/cmd.vim index 863a8de8..05a39cd8 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/cmd.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/cmd.vim @@ -1,6 +1,17 @@ function! go#cmd#autowrite() abort if &autowrite == 1 || &autowriteall == 1 silent! wall + else + for l:nr in range(0, bufnr('$')) + if buflisted(l:nr) && getbufvar(l:nr, '&modified') + " Sleep one second to make sure people see the message. Otherwise it is + " often immediacy overwritten by the async messages (which also don't + " invoke the "hit ENTER" prompt). + call go#util#EchoWarning('[No write since last change]') + sleep 1 + return + endif + endfor endif endfunction @@ -14,14 +25,14 @@ function! go#cmd#Build(bang, ...) abort " placeholder with the current folder (indicated with '.'). We also pass -i " that tries to install the dependencies, this has the side effect that it " caches the build results, so every other build is faster. - let args = - \ ["build"] + + let l:args = + \ ['build', '-tags', go#config#BuildTags()] + \ map(copy(a:000), "expand(v:val)") + - \ ["-i", ".", "errors"] + \ [".", "errors"] - " Vim async. - if go#util#has_job() - if get(g:, 'go_echo_command_info', 1) + " Vim and Neovim async. + if go#util#has_job() || has('nvim') + if go#config#EchoCommandInfo() call go#util#EchoProgress("building dispatched ...") endif @@ -31,14 +42,6 @@ function! go#cmd#Build(bang, ...) abort \ 'for': 'GoBuild', \}) - " Nvim async. - elseif has('nvim') - if get(g:, 'go_echo_command_info', 1) - call go#util#EchoProgress("building dispatched ...") - endif - - call go#jobcontrol#Spawn(a:bang, "build", "GoBuild", args) - " Vim 7.4 without async else let default_makeprg = &makeprg @@ -77,31 +80,42 @@ endfunction " BuildTags sets or shows the current build tags used for tools function! go#cmd#BuildTags(bang, ...) abort if a:0 - if a:0 == 1 && a:1 == '""' - unlet g:go_build_tags + let v = a:1 + if v == '""' || v == "''" + let v = "" + endif + call go#config#SetBuildTags(v) + let tags = go#config#BuildTags() + if empty(tags) call go#util#EchoSuccess("build tags are cleared") else - let g:go_build_tags = a:1 - call go#util#EchoSuccess("build tags are changed to: ". a:1) + call go#util#EchoSuccess("build tags are changed to: " . tags) endif return endif - if !exists('g:go_build_tags') + let tags = go#config#BuildTags() + if empty(tags) call go#util#EchoSuccess("build tags are not set") else - call go#util#EchoSuccess("current build tags: ". g:go_build_tags) + call go#util#EchoSuccess("current build tags: " . tags) endif endfunction " Run runs the current file (and their dependencies if any) in a new terminal. function! go#cmd#RunTerm(bang, mode, files) abort + let cmd = "go run " + let tags = go#config#BuildTags() + if len(tags) > 0 + let cmd .= "-tags " . go#util#Shellescape(tags) . " " + endif + if empty(a:files) - let cmd = "go run ". go#util#Shelljoin(go#tool#Files()) + let cmd .= go#util#Shelljoin(go#tool#Files()) else - let cmd = "go run ". go#util#Shelljoin(map(copy(a:files), "expand(v:val)"), 1) + let cmd .= go#util#Shelljoin(map(copy(a:files), "expand(v:val)"), 1) endif call go#term#newmode(a:bang, cmd, a:mode) endfunction @@ -122,8 +136,19 @@ function! go#cmd#Run(bang, ...) abort " anything. Once this is implemented we're going to make :GoRun async endif + let cmd = "go run " + let tags = go#config#BuildTags() + if len(tags) > 0 + let cmd .= "-tags " . go#util#Shellescape(tags) . " " + endif + if go#util#IsWin() - exec '!go run ' . go#util#Shelljoin(go#tool#Files()) + if a:0 == 0 + exec '!' . cmd . go#util#Shelljoin(go#tool#Files(), 1) + else + exec '!' . cmd . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1) + endif + if v:shell_error redraws! | echon "vim-go: [run] " | echohl ErrorMsg | echon "FAILED"| echohl None else @@ -136,9 +161,9 @@ function! go#cmd#Run(bang, ...) abort " :make expands '%' and '#' wildcards, so they must also be escaped let default_makeprg = &makeprg if a:0 == 0 - let &makeprg = 'go run ' . go#util#Shelljoin(go#tool#Files(), 1) + let &makeprg = cmd . go#util#Shelljoin(go#tool#Files(), 1) else - let &makeprg = "go run " . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1) + let &makeprg = cmd . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1) endif let l:listtype = go#list#Type("GoRun") @@ -170,12 +195,12 @@ function! go#cmd#Install(bang, ...) abort " expand all wildcards(i.e: '%' to the current file name) let goargs = map(copy(a:000), "expand(v:val)") - if get(g:, 'go_echo_command_info', 1) + if go#config#EchoCommandInfo() call go#util#EchoProgress("installing dispatched ...") endif call s:cmd_job({ - \ 'cmd': ['go', 'install'] + goargs, + \ 'cmd': ['go', 'install', '-tags', go#config#BuildTags()] + goargs, \ 'bang': a:bang, \ 'for': 'GoInstall', \}) @@ -269,44 +294,7 @@ function s:cmd_job(args) abort " autowrite is not enabled for jobs call go#cmd#autowrite() - function! s:complete(job, exit_status, data) closure abort - let status = { - \ 'desc': 'last status', - \ 'type': a:args.cmd[1], - \ 'state': "success", - \ } - - if a:exit_status - let status.state = "failed" - endif - - let elapsed_time = reltimestr(reltime(started_at)) - " strip whitespace - let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') - let status.state .= printf(" (%ss)", elapsed_time) - - call go#statusline#Update(status_dir, status) - endfunction - - let a:args.complete = funcref('s:complete') - let callbacks = go#job#Spawn(a:args) - - let start_options = { - \ 'callback': callbacks.callback, - \ 'exit_cb': callbacks.exit_cb, - \ 'close_cb': callbacks.close_cb, - \ } - - " pre start - let dir = getcwd() - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let jobdir = fnameescape(expand("%:p:h")) - execute cd . jobdir - - call job_start(a:args.cmd, start_options) - - " post start - execute cd . fnameescape(dir) + call go#job#Spawn(a:args.cmd, a:args) endfunction " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/cmd_test.vim b/pack/plugins/start/fatih-vim-go/autoload/go/cmd_test.vim new file mode 100644 index 00000000..ef391100 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/cmd_test.vim @@ -0,0 +1,30 @@ +func! Test_GoBuildErrors() + try + let l:filename = 'cmd/bad.go' + let l:tmp = gotest#load_fixture(l:filename) + exe 'cd ' . l:tmp . '/src/cmd' + + " set the compiler type so that the errorformat option will be set + " correctly. + compiler go + + let expected = [{'lnum': 4, 'bufnr': bufnr('%'), 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'undefined: notafunc'}] + " clear the quickfix lists + call setqflist([], 'r') + + call go#cmd#Build(1) + + let actual = getqflist() + let start = reltime() + while len(actual) == 0 && reltimefloat(reltime(start)) < 10 + sleep 100m + let actual = getqflist() + endwhile + + call gotest#assert_quickfix(actual, l:expected) + finally + call delete(l:tmp, 'rf') + endtry +endfunc + +" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/complete.vim b/pack/plugins/start/fatih-vim-go/autoload/go/complete.vim index 8f5adcdc..127d57fc 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/complete.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/complete.vim @@ -1,60 +1,46 @@ -let s:sock_type = (has('win32') || has('win64')) ? 'tcp' : 'unix' - -function! s:gocodeCurrentBuffer() abort - let file = tempname() - call writefile(go#util#GetLines(), file) - return file -endfunction - -function! s:gocodeCommand(cmd, preargs, args) abort - for i in range(0, len(a:args) - 1) - let a:args[i] = go#util#Shellescape(a:args[i]) - endfor - for i in range(0, len(a:preargs) - 1) - let a:preargs[i] = go#util#Shellescape(a:preargs[i]) - endfor - +function! s:gocodeCommand(cmd, args) abort let bin_path = go#path#CheckBinPath("gocode") if empty(bin_path) - return + return [] endif + let socket_type = go#config#GocodeSocketType() + + let cmd = [bin_path] + let cmd = extend(cmd, ['-sock', socket_type]) + let cmd = extend(cmd, ['-f', 'vim']) + let cmd = extend(cmd, [a:cmd]) + let cmd = extend(cmd, a:args) + + return cmd +endfunction + +function! s:sync_gocode(cmd, args, input) abort " We might hit cache problems, as gocode doesn't handle different GOPATHs " well. See: https://github.com/nsf/gocode/issues/239 let old_goroot = $GOROOT let $GOROOT = go#util#env("goroot") try - let socket_type = get(g:, 'go_gocode_socket_type', s:sock_type) - let cmd = printf('%s -sock %s %s %s %s', - \ go#util#Shellescape(bin_path), - \ socket_type, - \ join(a:preargs), - \ go#util#Shellescape(a:cmd), - \ join(a:args) - \ ) - + let cmd = s:gocodeCommand(a:cmd, a:args) " gocode can sometimes be slow, so redraw now to avoid waiting for gocode " to return before redrawing automatically. redraw - let result = go#util#System(cmd) + let [l:result, l:err] = go#util#Exec(cmd, a:input) finally let $GOROOT = old_goroot endtry - if go#util#ShellError() != 0 - return "[\"0\", []]" - else - if &encoding != 'utf-8' - let result = iconv(result, 'utf-8', &encoding) - endif - return result + if l:err != 0 + return "[0, []]" endif -endfunction -function! s:gocodeCurrentBufferOpt(filename) abort - return '-in=' . a:filename + if &encoding != 'utf-8' + let l:result = iconv(l:result, 'utf-8', &encoding) + endif + + return l:result endfunction let s:optionsEnabled = 0 @@ -63,81 +49,180 @@ function! s:gocodeEnableOptions() abort return endif - let bin_path = go#path#CheckBinPath("gocode") - if empty(bin_path) + let l:bin_path = go#path#CheckBinPath("gocode") + if empty(l:bin_path) return endif let s:optionsEnabled = 1 - call go#util#System(printf('%s set propose-builtins %s', go#util#Shellescape(bin_path), s:toBool(get(g:, 'go_gocode_propose_builtins', 1)))) - call go#util#System(printf('%s set autobuild %s', go#util#Shellescape(bin_path), s:toBool(get(g:, 'go_gocode_autobuild', 1)))) - call go#util#System(printf('%s set unimported-packages %s', go#util#Shellescape(bin_path), s:toBool(get(g:, 'go_gocode_unimported_packages', 0)))) + call go#util#Exec(['gocode', 'set', 'propose-builtins', s:toBool(go#config#GocodeProposeBuiltins())]) + call go#util#Exec(['gocode', 'set', 'autobuild', s:toBool(go#config#GocodeAutobuild())]) + call go#util#Exec(['gocode', 'set', 'unimported-packages', s:toBool(go#config#GocodeUnimportedPackages())]) endfunction function! s:toBool(val) abort - if a:val | return 'true ' | else | return 'false' | endif + if a:val | return 'true' | else | return 'false' | endif endfunction function! s:gocodeAutocomplete() abort call s:gocodeEnableOptions() - let filename = s:gocodeCurrentBuffer() - let result = s:gocodeCommand('autocomplete', - \ [s:gocodeCurrentBufferOpt(filename), '-f=vim'], - \ [expand('%:p'), go#util#OffsetCursor()]) - call delete(filename) - return result + " use the offset as is, because the cursor position is the position for + " which autocomplete candidates are needed. + return s:sync_gocode('autocomplete', + \ [expand('%:p'), go#util#OffsetCursor()], + \ go#util#GetLines()) endfunction +" go#complete#GoInfo returns the description of the identifier under the +" cursor. function! go#complete#GetInfo() abort + return s:sync_info(0) +endfunction + +function! go#complete#Info(auto) abort + if go#util#has_job(1) + return s:async_info(a:auto) + else + return s:sync_info(a:auto) + endif +endfunction + +function! s:async_info(auto) + if exists("s:async_info_job") + call job_stop(s:async_info_job) + unlet s:async_info_job + endif + + let state = { + \ 'exited': 0, + \ 'exit_status': 0, + \ 'closed': 0, + \ 'messages': [], + \ 'auto': a:auto + \ } + + function! s:callback(chan, msg) dict + let l:msg = a:msg + if &encoding != 'utf-8' + let l:msg = iconv(l:msg, 'utf-8', &encoding) + endif + call add(self.messages, l:msg) + endfunction + + function! s:exit_cb(job, exitval) dict + let self.exit_status = a:exitval + let self.exited = 1 + + if self.closed + call self.complete() + endif + endfunction + + function! s:close_cb(ch) dict + let self.closed = 1 + if self.exited + call self.complete() + endif + endfunction + + function state.complete() dict + if self.exit_status != 0 + return + endif + + let result = s:info_filter(self.auto, join(self.messages, "\n")) + call s:info_complete(self.auto, result) + endfunction + + " add 1 to the offset, so that the position at the cursor will be included + " in gocode's search let offset = go#util#OffsetCursor()+1 - let filename = s:gocodeCurrentBuffer() - let result = s:gocodeCommand('autocomplete', - \ [s:gocodeCurrentBufferOpt(filename), '-f=godit'], + + " We might hit cache problems, as gocode doesn't handle different GOPATHs + " well. See: https://github.com/nsf/gocode/issues/239 + let env = { + \ "GOROOT": go#util#env("goroot") + \ } + + let cmd = s:gocodeCommand('autocomplete', \ [expand('%:p'), offset]) - call delete(filename) - " first line is: Charcount,,NumberOfCandidates, i.e: 8,,1 - " following lines are candiates, i.e: func foo(name string),,foo( - let out = split(result, '\n') + " TODO(bc): Don't write the buffer to a file; pass the buffer directrly to + " gocode's stdin. It shouldn't be necessary to use {in_io: 'file', in_name: + " s:gocodeFile()}, but unfortunately {in_io: 'buffer', in_buf: bufnr('%')} + " should work. + let options = { + \ 'env': env, + \ 'in_io': 'file', + \ 'in_name': s:gocodeFile(), + \ 'callback': funcref("s:callback", [], state), + \ 'exit_cb': funcref("s:exit_cb", [], state), + \ 'close_cb': funcref("s:close_cb", [], state) + \ } + + let s:async_info_job = job_start(cmd, options) +endfunction + +function! s:gocodeFile() + let file = tempname() + call writefile(go#util#GetLines(), file) + return file +endfunction - " no candidates are found - if len(out) == 1 +function! s:sync_info(auto) + " auto is true if we were called by g:go_auto_type_info's autocmd + + " add 1 to the offset, so that the position at the cursor will be included + " in gocode's search + let offset = go#util#OffsetCursor()+1 + + let result = s:sync_gocode('autocomplete', + \ [expand('%:p'), offset], + \ go#util#GetLines()) + + let result = s:info_filter(a:auto, result) + call s:info_complete(a:auto, result) +endfunction + +function! s:info_filter(auto, result) abort + if empty(a:result) return "" endif - " only one candidate is found - if len(out) == 2 - return split(out[1], ',,')[0] + let l:result = eval(a:result) + if len(l:result) != 2 + return "" endif - " to many candidates are available, pick one that maches the word under the - " cursor - let infos = [] - for info in out[1:] - call add(infos, split(info, ',,')[0]) - endfor + let l:candidates = l:result[1] + if len(l:candidates) == 1 + " When gocode panics in vim mode, it returns + " [0, [{'word': 'PANIC', 'abbr': 'PANIC PANIC PANIC', 'info': 'PANIC PANIC PANIC'}]] + if a:auto && l:candidates[0].info ==# "PANIC PANIC PANIC" + return "" + endif + return l:candidates[0].info + endif + + let filtered = [] let wordMatch = '\<' . expand("") . '\>' " escape single quotes in wordMatch before passing it to filter let wordMatch = substitute(wordMatch, "'", "''", "g") - let filtered = filter(infos, "v:val =~ '".wordMatch."'") + let filtered = filter(l:candidates, "v:val.info =~ '".wordMatch."'") - if len(filtered) == 1 - return filtered[0] + if len(l:filtered) != 1 + return "" endif - return "" + return l:filtered[0].info endfunction -function! go#complete#Info(auto) abort - " auto is true if we were called by g:go_auto_type_info's autocmd - let result = go#complete#GetInfo() - if !empty(result) - " if auto, and the result is a PANIC by gocode, hide it - if a:auto && result ==# 'PANIC PANIC PANIC' | return | endif - echo "vim-go: " | echohl Function | echon result | echohl None +function! s:info_complete(auto, result) abort + if !empty(a:result) + echo "vim-go: " | echohl Function | echon a:result | echohl None endif endfunction @@ -146,31 +231,32 @@ function! s:trim_bracket(val) abort return a:val endfunction +let s:completions = "" function! go#complete#Complete(findstart, base) abort "findstart = 1 when we need to get the text length if a:findstart == 1 - execute "silent let g:gocomplete_completions = " . s:gocodeAutocomplete() - return col('.') - g:gocomplete_completions[0] - 1 + execute "silent let s:completions = " . s:gocodeAutocomplete() + return col('.') - s:completions[0] - 1 "findstart = 0 when we need to return the list of completions else let s = getline(".")[col('.') - 1] if s =~ '[(){}\{\}]' - return map(copy(g:gocomplete_completions[1]), 's:trim_bracket(v:val)') + return map(copy(s:completions[1]), 's:trim_bracket(v:val)') endif - return g:gocomplete_completions[1] + + return s:completions[1] endif -endf +endfunction function! go#complete#ToggleAutoTypeInfo() abort - if get(g:, "go_auto_type_info", 0) - let g:go_auto_type_info = 0 + if go#config#AutoTypeInfo() + call go#config#SetAutoTypeInfo(0) call go#util#EchoProgress("auto type info disabled") return end - let g:go_auto_type_info = 1 + call go#config#SetAutoTypeInfo(1) call go#util#EchoProgress("auto type info enabled") endfunction - " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/config.vim b/pack/plugins/start/fatih-vim-go/autoload/go/config.vim new file mode 100644 index 00000000..140e41c5 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/config.vim @@ -0,0 +1,432 @@ +function! go#config#AutodetectGopath() abort + return get(g:, 'go_autodetect_gopath', 0) +endfunction + +function! go#config#ListTypeCommands() abort + return get(g:, 'go_list_type_commands', {}) +endfunction + +function! go#config#VersionWarning() abort + return get(g:, 'go_version_warning', 1) +endfunction + +function! go#config#BuildTags() abort + return get(g:, 'go_build_tags', '') +endfunction + +function! go#config#SetBuildTags(value) abort + if a:value is '' + silent! unlet g:go_build_tags + return + endif + + let g:go_build_tags = a:value +endfunction + +function! go#config#TestTimeout() abort + return get(g:, 'go_test_timeout', '10s') +endfunction + +function! go#config#TestShowName() abort + return get(g:, 'go_test_show_name', 0) +endfunction + +function! go#config#TermHeight() abort + return get(g:, 'go_term_height', winheight(0)) +endfunction + +function! go#config#TermWidth() abort + return get(g:, 'go_term_width', winwidth(0)) +endfunction + +function! go#config#TermMode() abort + return get(g:, 'go_term_mode', 'vsplit') +endfunction + +function! go#config#TermEnabled() abort + return get(g:, 'go_term_enabled', 0) +endfunction + +function! go#config#SetTermEnabled(value) abort + let g:go_term_enabled = a:value +endfunction + +function! go#config#TemplateUsePkg() abort + return get(g:, 'go_template_use_pkg', 0) +endfunction + +function! go#config#TemplateTestFile() abort + return get(g:, 'go_template_test_file', "hello_world_test.go") +endfunction + +function! go#config#TemplateFile() abort + return get(g:, 'go_template_file', "hello_world.go") +endfunction + +function! go#config#StatuslineDuration() abort + return get(g:, 'go_statusline_duration', 60000) +endfunction + +function! go#config#SnippetEngine() abort + return get(g:, 'go_snippet_engine', 'automatic') +endfunction + +function! go#config#PlayBrowserCommand() abort + if go#util#IsWin() + let go_play_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%' + elseif go#util#IsMac() + let go_play_browser_command = 'open %URL%' + elseif executable('xdg-open') + let go_play_browser_command = 'xdg-open %URL%' + elseif executable('firefox') + let go_play_browser_command = 'firefox %URL% &' + elseif executable('chromium') + let go_play_browser_command = 'chromium %URL% &' + else + let go_play_browser_command = '' + endif + + return get(g:, 'go_play_browser_command', go_play_browser_command) +endfunction + +function! go#config#MetalinterDeadline() abort + " gometalinter has a default deadline of 5 seconds only when asynchronous + " jobs are not supported. + + let deadline = '5s' + if go#util#has_job() && has('lambda') + let deadline = '' + endif + + return get(g:, 'go_metalinter_deadline', deadline) +endfunction + +function! go#config#ListType() abort + return get(g:, 'go_list_type', '') +endfunction + +function! go#config#ListAutoclose() abort + return get(g:, 'go_list_autoclose', 1) +endfunction + +function! go#config#InfoMode() abort + return get(g:, 'go_info_mode', 'gocode') +endfunction + +function! go#config#GuruScope() abort + let scope = get(g:, 'go_guru_scope', []) + + if !empty(scope) + " strip trailing slashes for each path in scope. bug: + " https://github.com/golang/go/issues/14584 + let scopes = go#util#StripTrailingSlash(scope) + endif + + return scope +endfunction + +function! go#config#SetGuruScope(scope) abort + if empty(a:scope) + if exists('g:go_guru_scope') + unlet g:go_guru_scope + endif + else + let g:go_guru_scope = a:scope + endif +endfunction + +function! go#config#GocodeUnimportedPackages() abort + return get(g:, 'go_gocode_unimported_packages', 0) +endfunction + +let s:sock_type = (has('win32') || has('win64')) ? 'tcp' : 'unix' +function! go#config#GocodeSocketType() abort + return get(g:, 'go_gocode_socket_type', s:sock_type) +endfunction + +function! go#config#GocodeProposeBuiltins() abort + return get(g:, 'go_gocode_propose_builtins', 1) +endfunction + +function! go#config#GocodeAutobuild() abort + return get(g:, 'go_gocode_autobuild', 1) +endfunction + +function! go#config#EchoCommandInfo() abort + return get(g:, 'go_echo_command_info', 1) +endfunction + +function! go#config#DocUrl() abort + let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org') + if godoc_url isnot 'https://godoc.org' + " strip last '/' character if available + let last_char = strlen(godoc_url) - 1 + if godoc_url[last_char] == '/' + let godoc_url = strpart(godoc_url, 0, last_char) + endif + " custom godoc installations expect /pkg before package names + let godoc_url .= "/pkg" + endif + return godoc_url +endfunction + +function! go#config#DefReuseBuffer() abort + return get(g:, 'go_def_reuse_buffer', 0) +endfunction + +function! go#config#DefMode() abort + return get(g:, 'go_def_mode', 'guru') +endfunction + +function! go#config#DeclsIncludes() abort + return get(g:, 'go_decls_includes', 'func,type') +endfunction + +function! go#config#Debug() abort + return get(g:, 'go_debug', []) +endfunction + +function! go#config#DebugWindows() abort + return get(g:, 'go_debug_windows', { + \ 'stack': 'leftabove 20vnew', + \ 'out': 'botright 10new', + \ 'vars': 'leftabove 30vnew', + \ } + \ ) + +endfunction + +function! go#config#DebugAddress() abort + return get(g:, 'go_debug_address', '127.0.0.1:8181') +endfunction + +function! go#config#DebugCommands() abort + " make sure g:go_debug_commands is set so that it can be added to easily. + let g:go_debug_commands = get(g:, 'go_debug_commands', {}) + return g:go_debug_commands +endfunction + +function! go#config#SetDebugDiag(value) abort + let g:go_debug_diag = a:value +endfunction + +function! go#config#AutoSameids() abort + return get(g:, 'go_auto_sameids', 0) +endfunction + +function! go#config#SetAutoSameids(value) abort + let g:go_auto_sameids = a:value +endfunction + +function! go#config#AddtagsTransform() abort + return get(g:, 'go_addtags_transform', "snakecase") +endfunction + +function! go#config#TemplateAutocreate() abort + return get(g:, "go_template_autocreate", 1) +endfunction + +function! go#config#SetTemplateAutocreate(value) abort + let g:go_template_autocreate = a:value +endfunction + +function! go#config#MetalinterCommand() abort + return get(g:, "go_metalinter_command", "") +endfunction + +function! go#config#MetalinterAutosaveEnabled() abort + return get(g:, 'go_metalinter_autosave_enabled', ['vet', 'golint']) +endfunction + +function! go#config#MetalinterEnabled() abort + return get(g:, "go_metalinter_enabled", ['vet', 'golint', 'errcheck']) +endfunction + +function! go#config#MetalinterDisabled() abort + return get(g:, "go_metalinter_disabled", []) +endfunction + +function! go#config#GolintBin() abort + return get(g:, "go_golint_bin", "golint") +endfunction + +function! go#config#ErrcheckBin() abort + return get(g:, "go_errcheck_bin", "errcheck") +endfunction + +function! go#config#MetalinterAutosave() abort + return get(g:, "go_metalinter_autosave", 0) +endfunction + +function! go#config#SetMetalinterAutosave(value) abort + let g:go_metalinter_autosave = a:value +endfunction + +function! go#config#ListHeight() abort + return get(g:, "go_list_height", 0) +endfunction + +function! go#config#FmtAutosave() abort + return get(g:, "go_fmt_autosave", 1) +endfunction + +function! go#config#SetFmtAutosave(value) abort + let g:go_fmt_autosave = a:value +endfunction + +function! go#config#AsmfmtAutosave() abort + return get(g:, "go_asmfmt_autosave", 0) +endfunction + +function! go#config#SetAsmfmtAutosave(value) abort + let g:go_asmfmt_autosave = a:value +endfunction + +function! go#config#DocMaxHeight() abort + return get(g:, "go_doc_max_height", 20) +endfunction + +function! go#config#AutoTypeInfo() abort + return get(g:, "go_auto_type_info", 0) +endfunction + +function! go#config#SetAutoTypeInfo(value) abort + let g:go_auto_type_info = a:value +endfunction + +function! go#config#AlternateMode() abort + return get(g:, "go_alternate_mode", "edit") +endfunction + +function! go#config#DeclsMode() abort + return get(g:, "go_decls_mode", "") +endfunction + +function! go#config#DocCommand() abort + return get(g:, "go_doc_command", ["godoc"]) +endfunction + +function! go#config#FmtCommand() abort + return get(g:, "go_fmt_command", "gofmt") +endfunction + +function! go#config#FmtOptions() abort + return get(g:, "go_fmt_options", {}) +endfunction + +function! go#config#FmtFailSilently() abort + return get(g:, "go_fmt_fail_silently", 0) +endfunction + +function! go#config#FmtExperimental() abort + return get(g:, "go_fmt_experimental", 0 ) +endfunction + +function! go#config#PlayOpenBrowser() abort + return get(g:, "go_play_open_browser", 1) +endfunction + +function! go#config#GorenameBin() abort + return get(g:, "go_gorename_bin", "gorename") +endfunction + +function! go#config#GorenamePrefill() abort + return get(g:, "go_gorename_prefill", 'expand("") =~# "^[A-Z]"' . + \ '? go#util#pascalcase(expand(""))' . + \ ': go#util#camelcase(expand(""))') +endfunction + +function! go#config#TextobjIncludeFunctionDoc() abort + return get(g:, "go_textobj_include_function_doc", 1) +endfunction + +function! go#config#TextobjIncludeVariable() abort + return get(g:, "go_textobj_include_variable", 1) +endfunction + +function! go#config#BinPath() abort + return get(g:, "go_bin_path", "") +endfunction + +function! go#config#HighlightArrayWhitespaceError() abort + return get(g:, 'go_highlight_array_whitespace_error', 0) +endfunction + +function! go#config#HighlightChanWhitespaceError() abort + return get(g:, 'go_highlight_chan_whitespace_error', 0) +endfunction + +function! go#config#HighlightExtraTypes() abort + return get(g:, 'go_highlight_extra_types', 0) +endfunction + +function! go#config#HighlightSpaceTabError() abort + return get(g:, 'go_highlight_space_tab_error', 0) +endfunction + +function! go#config#HighlightTrailingWhitespaceError() abort + return get(g:, 'go_highlight_trailing_whitespace_error', 0) +endfunction + +function! go#config#HighlightOperators() abort + return get(g:, 'go_highlight_operators', 0) +endfunction + +function! go#config#HighlightFunctions() abort + return get(g:, 'go_highlight_functions', 0) +endfunction + +function! go#config#HighlightFunctionArguments() abort + return get(g:, 'go_highlight_function_arguments', 0) +endfunction + +function! go#config#HighlightFunctionCalls() abort + return get(g:, 'go_highlight_function_calls', 0) +endfunction + +function! go#config#HighlightFields() abort + return get(g:, 'go_highlight_fields', 0) +endfunction + +function! go#config#HighlightTypes() abort + return get(g:, 'go_highlight_types', 0) +endfunction + +function! go#config#HighlightBuildConstraints() abort + return get(g:, 'go_highlight_build_constraints', 0) +endfunction + +function! go#config#HighlightStringSpellcheck() abort + return get(g:, 'go_highlight_string_spellcheck', 1) +endfunction + +function! go#config#HighlightFormatStrings() abort + return get(g:, 'go_highlight_format_strings', 1) +endfunction + +function! go#config#HighlightGenerateTags() abort + return get(g:, 'go_highlight_generate_tags', 0) +endfunction + +function! go#config#HighlightVariableAssignments() abort + return get(g:, 'go_highlight_variable_assignments', 0) +endfunction + +function! go#config#HighlightVariableDeclarations() abort + return get(g:, 'go_highlight_variable_declarations', 0) +endfunction + +function go#config#FoldEnable(...) abort + if a:0 > 0 + return index(go#config#FoldEnable(), a:1) > -1 + endif + return get(g:, 'go_fold_enable', ['block', 'import', 'varconst', 'package_comment']) +endfunction + +" Set the default value. A value of "1" is a shortcut for this, for +" compatibility reasons. +if exists("g:go_gorename_prefill") && g:go_gorename_prefill == 1 + unlet g:go_gorename_prefill +endif + +" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/coverage.vim b/pack/plugins/start/fatih-vim-go/autoload/go/coverage.vim index 428039cb..c4702dd3 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/coverage.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/coverage.vim @@ -44,16 +44,17 @@ function! go#coverage#Buffer(bang, ...) abort let s:toggle = 1 let l:tmpname = tempname() - if get(g:, 'go_echo_command_info', 1) - echon "vim-go: " | echohl Identifier | echon "testing ..." | echohl None + if go#config#EchoCommandInfo() + call go#util#EchoProgress("testing...") endif - if go#util#has_job() + if go#util#has_job() || has('nvim') call s:coverage_job({ - \ 'cmd': ['go', 'test', '-coverprofile', l:tmpname] + a:000, + \ 'cmd': ['go', 'test', '-tags', go#config#BuildTags(), '-coverprofile', l:tmpname] + a:000, \ 'complete': function('s:coverage_callback', [l:tmpname]), \ 'bang': a:bang, \ 'for': 'GoTest', + \ 'statustype': 'coverage', \ }) return endif @@ -63,24 +64,8 @@ function! go#coverage#Buffer(bang, ...) abort call extend(args, a:000) endif - let disabled_term = 0 - if get(g:, 'go_term_enabled') - let disabled_term = 1 - let g:go_term_enabled = 0 - endif - let id = call('go#test#Test', args) - if disabled_term - let g:go_term_enabled = 1 - endif - - if has('nvim') - call go#jobcontrol#AddHandler(function('s:coverage_handler')) - let s:coverage_handler_jobs[id] = l:tmpname - return - endif - if go#util#ShellError() == 0 call go#coverage#overlay(l:tmpname) endif @@ -104,9 +89,9 @@ endfunction " a new HTML coverage page from that profile in a new browser function! go#coverage#Browser(bang, ...) abort let l:tmpname = tempname() - if go#util#has_job() + if go#util#has_job() || has('nvim') call s:coverage_job({ - \ 'cmd': ['go', 'test', '-coverprofile', l:tmpname], + \ 'cmd': ['go', 'test', '-tags', go#config#BuildTags(), '-coverprofile', l:tmpname], \ 'complete': function('s:coverage_browser_callback', [l:tmpname]), \ 'bang': a:bang, \ 'for': 'GoTest', @@ -120,16 +105,9 @@ function! go#coverage#Browser(bang, ...) abort endif let id = call('go#test#Test', args) - if has('nvim') - call go#jobcontrol#AddHandler(function('s:coverage_browser_handler')) - let s:coverage_browser_handler_jobs[id] = l:tmpname - return - endif - if go#util#ShellError() == 0 - let openHTML = 'go tool cover -html='.l:tmpname - call go#tool#ExecuteInDir(openHTML) + call go#tool#ExecuteInDir(['go', 'tool', 'cover', '-html=' . l:tmpname]) endif call delete(l:tmpname) @@ -277,48 +255,17 @@ function s:coverage_job(args) " autowrite is not enabled for jobs call go#cmd#autowrite() - let status_dir = expand('%:p:h') - let Complete = a:args.complete - function! s:complete(job, exit_status, data) closure - let status = { - \ 'desc': 'last status', - \ 'type': "coverage", - \ 'state': "finished", - \ } - - if a:exit_status - let status.state = "failed" - endif - - call go#statusline#Update(status_dir, status) - return Complete(a:job, a:exit_status, a:data) - endfunction - - let a:args.complete = funcref('s:complete') - let callbacks = go#job#Spawn(a:args) - - let start_options = { - \ 'callback': callbacks.callback, - \ 'exit_cb': callbacks.exit_cb, - \ 'close_cb': callbacks.close_cb, - \ } - - " pre start - let dir = getcwd() - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let jobdir = fnameescape(expand("%:p:h")) - execute cd . jobdir - - call go#statusline#Update(status_dir, { - \ 'desc': "current status", - \ 'type': "coverage", - \ 'state': "started", - \}) + let disabled_term = 0 + if go#config#TermEnabled() + let disabled_term = 1 + call go#config#SetTermEnabled(0) + endif - call job_start(a:args.cmd, start_options) + call go#job#Spawn(a:args.cmd, a:args) - " post start - execute cd . fnameescape(dir) + if disabled_term + call go#config#SetTermEnabled(1) + endif endfunction " coverage_callback is called when the coverage execution is finished @@ -332,47 +279,10 @@ endfunction function! s:coverage_browser_callback(coverfile, job, exit_status, data) if a:exit_status == 0 - let openHTML = 'go tool cover -html='.a:coverfile - call go#tool#ExecuteInDir(openHTML) + call go#tool#ExecuteInDir(['go', 'tool', 'cover', '-html=' . a:coverfile]) endif call delete(a:coverfile) endfunction -" ----------------------- -" | Neovim job handlers | -" ----------------------- - -let s:coverage_handler_jobs = {} -let s:coverage_browser_handler_jobs = {} - -function! s:coverage_handler(job, exit_status, data) abort - if !has_key(s:coverage_handler_jobs, a:job.id) - return - endif - let l:tmpname = s:coverage_handler_jobs[a:job.id] - if a:exit_status == 0 - call go#coverage#overlay(l:tmpname) - endif - - call delete(l:tmpname) - unlet s:coverage_handler_jobs[a:job.id] -endfunction - -function! s:coverage_browser_handler(job, exit_status, data) abort - if !has_key(s:coverage_browser_handler_jobs, a:job.id) - return - endif - - let l:tmpname = s:coverage_browser_handler_jobs[a:job.id] - if a:exit_status == 0 - let openHTML = 'go tool cover -html='.l:tmpname - call go#tool#ExecuteInDir(openHTML) - endif - - call delete(l:tmpname) - unlet s:coverage_browser_handler_jobs[a:job.id] -endfunction - - " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/debug.vim b/pack/plugins/start/fatih-vim-go/autoload/go/debug.vim new file mode 100644 index 00000000..88642490 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/debug.vim @@ -0,0 +1,896 @@ +scriptencoding utf-8 + +if !exists('s:state') + let s:state = { + \ 'rpcid': 1, + \ 'running': 0, + \ 'breakpoint': {}, + \ 'currentThread': {}, + \ 'localVars': {}, + \ 'functionArgs': {}, + \ 'message': [], + \ 'is_test': 0, + \} + + if go#util#HasDebug('debugger-state') + call go#config#SetDebugDiag(s:state) + endif +endif + +if !exists('s:start_args') + let s:start_args = [] +endif + +function! s:groutineID() abort + return s:state['currentThread'].goroutineID +endfunction + +function! s:exit(job, status) abort + if has_key(s:state, 'job') + call remove(s:state, 'job') + endif + call s:clearState() + if a:status > 0 + call go#util#EchoError(s:state['message']) + endif +endfunction + +function! s:logger(prefix, ch, msg) abort + let l:cur_win = bufwinnr('') + let l:log_win = bufwinnr(bufnr('__GODEBUG_OUTPUT__')) + if l:log_win == -1 + return + endif + exe l:log_win 'wincmd w' + + try + setlocal modifiable + if getline(1) == '' + call setline('$', a:prefix . a:msg) + else + call append('$', a:prefix . a:msg) + endif + normal! G + setlocal nomodifiable + finally + exe l:cur_win 'wincmd w' + endtry +endfunction + +function! s:call_jsonrpc(method, ...) abort + if go#util#HasDebug('debugger-commands') + echom 'sending to dlv ' . a:method + endif + + if len(a:000) > 0 && type(a:000[0]) == v:t_func + let Cb = a:000[0] + let args = a:000[1:] + else + let Cb = v:none + let args = a:000 + endif + let s:state['rpcid'] += 1 + let req_json = json_encode({ + \ 'id': s:state['rpcid'], + \ 'method': a:method, + \ 'params': args, + \}) + + try + " Use callback + if type(Cb) == v:t_func + let s:ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'callback': Cb}) + call ch_sendraw(s:ch, req_json) + + if go#util#HasDebug('debugger-commands') + let g:go_debug_commands = add(g:go_debug_commands, { + \ 'request': req_json, + \ 'response': Cb, + \ }) + endif + return + endif + + let ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'timeout': 20000}) + call ch_sendraw(ch, req_json) + let resp_json = ch_readraw(ch) + + if go#util#HasDebug('debugger-commands') + let g:go_debug_commands = add(g:go_debug_commands, { + \ 'request': req_json, + \ 'response': resp_json, + \ }) + endif + + let obj = json_decode(resp_json) + if type(obj) == v:t_dict && has_key(obj, 'error') && !empty(obj.error) + throw obj.error + endif + return obj + catch + throw substitute(v:exception, '^Vim', '', '') + endtry +endfunction + +" Update the location of the current breakpoint or line we're halted on based on +" response from dlv. +function! s:update_breakpoint(res) abort + if type(a:res) ==# v:t_none + return + endif + + let state = a:res.result.State + if !has_key(state, 'currentThread') + return + endif + + let s:state['currentThread'] = state.currentThread + let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"') + if len(bufs) == 0 + return + endif + + exe bufs[0][0] 'wincmd w' + let filename = state.currentThread.file + let linenr = state.currentThread.line + let oldfile = fnamemodify(expand('%'), ':p:gs!\\!/!') + if oldfile != filename + silent! exe 'edit' filename + endif + silent! exe 'norm!' linenr.'G' + silent! normal! zvzz + silent! sign unplace 9999 + silent! exe 'sign place 9999 line=' . linenr . ' name=godebugcurline file=' . filename +endfunction + +" Populate the stacktrace window. +function! s:show_stacktrace(res) abort + if !has_key(a:res, 'result') + return + endif + + let l:stack_win = bufwinnr(bufnr('__GODEBUG_STACKTRACE__')) + if l:stack_win == -1 + return + endif + + let l:cur_win = bufwinnr('') + exe l:stack_win 'wincmd w' + + try + setlocal modifiable + silent %delete _ + for i in range(len(a:res.result.Locations)) + let loc = a:res.result.Locations[i] + call setline(i+1, printf('%s - %s:%d', loc.function.name, fnamemodify(loc.file, ':p'), loc.line)) + endfor + finally + setlocal nomodifiable + exe l:cur_win 'wincmd w' + endtry +endfunction + +" Populate the variable window. +function! s:show_variables() abort + let l:var_win = bufwinnr(bufnr('__GODEBUG_VARIABLES__')) + if l:var_win == -1 + return + endif + + let l:cur_win = bufwinnr('') + exe l:var_win 'wincmd w' + + try + setlocal modifiable + silent %delete _ + + let v = [] + let v += ['# Local Variables'] + if type(get(s:state, 'localVars', [])) is type([]) + for c in s:state['localVars'] + let v += split(s:eval_tree(c, 0), "\n") + endfor + endif + + let v += [''] + let v += ['# Function Arguments'] + if type(get(s:state, 'functionArgs', [])) is type([]) + for c in s:state['functionArgs'] + let v += split(s:eval_tree(c, 0), "\n") + endfor + endif + + call setline(1, v) + finally + setlocal nomodifiable + exe l:cur_win 'wincmd w' + endtry +endfunction + +function! s:clearState() abort + let s:state['currentThread'] = {} + let s:state['localVars'] = {} + let s:state['functionArgs'] = {} + let s:state['message'] = [] + silent! sign unplace 9999 +endfunction + +function! s:stop() abort + call s:clearState() + if has_key(s:state, 'job') + call job_stop(s:state['job']) + call remove(s:state, 'job') + endif +endfunction + +function! go#debug#Stop() abort + " Remove signs. + for k in keys(s:state['breakpoint']) + let bt = s:state['breakpoint'][k] + if bt.id >= 0 + silent exe 'sign unplace ' . bt.id + endif + endfor + + " Remove all commands and add back the default commands. + for k in map(split(execute('command GoDebug'), "\n")[1:], 'matchstr(v:val, "^\\s*\\zs\\S\\+")') + exe 'delcommand' k + endfor + command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start(0, ) + command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start(1, ) + command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint() + + " Remove all mappings. + for k in map(split(execute('map (go-debug-'), "\n")[1:], 'matchstr(v:val, "^n\\s\\+\\zs\\S\\+")') + exe 'unmap' k + endfor + + call s:stop() + + let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"') + if len(bufs) > 0 + exe bufs[0][0] 'wincmd w' + else + wincmd p + endif + silent! exe bufwinnr(bufnr('__GODEBUG_STACKTRACE__')) 'wincmd c' + silent! exe bufwinnr(bufnr('__GODEBUG_VARIABLES__')) 'wincmd c' + silent! exe bufwinnr(bufnr('__GODEBUG_OUTPUT__')) 'wincmd c' + + set noballooneval + set balloonexpr= + + augroup vim-go-debug + autocmd! + augroup END + augroup! vim-go-debug +endfunction + +function! s:goto_file() abort + let m = matchlist(getline('.'), ' - \(.*\):\([0-9]\+\)$') + if m[1] == '' + return + endif + let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"') + if len(bufs) == 0 + return + endif + exe bufs[0][0] 'wincmd w' + let filename = m[1] + let linenr = m[2] + let oldfile = fnamemodify(expand('%'), ':p:gs!\\!/!') + if oldfile != filename + silent! exe 'edit' filename + endif + silent! exe 'norm!' linenr.'G' + silent! normal! zvzz +endfunction + +function! s:delete_expands() + let nr = line('.') + while 1 + let l = getline(nr+1) + if empty(l) || l =~ '^\S' + return + endif + silent! exe (nr+1) . 'd _' + endwhile + silent! exe 'norm!' nr.'G' +endfunction + +function! s:expand_var() abort + " Get name from struct line. + let name = matchstr(getline('.'), '^[^:]\+\ze: [a-zA-Z0-9\.·]\+{\.\.\.}$') + " Anonymous struct + if name == '' + let name = matchstr(getline('.'), '^[^:]\+\ze: struct {.\{-}}$') + endif + + if name != '' + setlocal modifiable + let not_open = getline(line('.')+1) !~ '^ ' + let l = line('.') + call s:delete_expands() + + if not_open + call append(l, split(s:eval(name), "\n")[1:]) + endif + silent! exe 'norm!' l.'G' + setlocal nomodifiable + return + endif + + " Expand maps + let m = matchlist(getline('.'), '^[^:]\+\ze: map.\{-}\[\(\d\+\)\]$') + if len(m) > 0 && m[1] != '' + setlocal modifiable + let not_open = getline(line('.')+1) !~ '^ ' + let l = line('.') + call s:delete_expands() + if not_open + " TODO: Not sure how to do this yet... Need to get keys of the map. + " let vs = '' + " for i in range(0, min([10, m[1]-1])) + " let vs .= ' ' . s:eval(printf("%s[%s]", m[0], )) + " endfor + " call append(l, split(vs, "\n")) + endif + + silent! exe 'norm!' l.'G' + setlocal nomodifiable + return + endif + + " Expand string. + let m = matchlist(getline('.'), '^\([^:]\+\)\ze: \(string\)\[\([0-9]\+\)\]\(: .\{-}\)\?$') + if len(m) > 0 && m[1] != '' + setlocal modifiable + let not_open = getline(line('.')+1) !~ '^ ' + let l = line('.') + call s:delete_expands() + + if not_open + let vs = '' + for i in range(0, min([10, m[3]-1])) + let vs .= ' ' . s:eval(m[1] . '[' . i . ']') + endfor + call append(l, split(vs, "\n")) + endif + + silent! exe 'norm!' l.'G' + setlocal nomodifiable + return + endif + + " Expand slice. + let m = matchlist(getline('.'), '^\([^:]\+\)\ze: \(\[\]\w\{-}\)\[\([0-9]\+\)\]$') + if len(m) > 0 && m[1] != '' + setlocal modifiable + let not_open = getline(line('.')+1) !~ '^ ' + let l = line('.') + call s:delete_expands() + + if not_open + let vs = '' + for i in range(0, min([10, m[3]-1])) + let vs .= ' ' . s:eval(m[1] . '[' . i . ']') + endfor + call append(l, split(vs, "\n")) + endif + silent! exe 'norm!' l.'G' + setlocal nomodifiable + return + endif +endfunction + +function! s:start_cb(ch, json) abort + let res = json_decode(a:json) + if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error) + throw res.error + endif + if empty(res) || !has_key(res, 'result') + return + endif + for bt in res.result.Breakpoints + if bt.id >= 0 + let s:state['breakpoint'][bt.id] = bt + exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file + endif + endfor + + let oldbuf = bufnr('%') + silent! only! + + let winnum = bufwinnr(bufnr('__GODEBUG_STACKTRACE__')) + if winnum != -1 + return + endif + + let debugwindows = go#config#DebugWindows() + if has_key(debugwindows, "stack") && debugwindows['stack'] != '' + exe 'silent ' . debugwindows['stack'] + silent file `='__GODEBUG_STACKTRACE__'` + setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline + setlocal filetype=godebugstacktrace + nmap :call goto_file() + nmap q (go-debug-stop) + endif + + if has_key(debugwindows, "out") && debugwindows['out'] != '' + exe 'silent ' . debugwindows['out'] + silent file `='__GODEBUG_OUTPUT__'` + setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline + setlocal filetype=godebugoutput + nmap q (go-debug-stop) + endif + + if has_key(debugwindows, "vars") && debugwindows['vars'] != '' + exe 'silent ' . debugwindows['vars'] + silent file `='__GODEBUG_VARIABLES__'` + setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline + setlocal filetype=godebugvariables + call append(0, ["# Local Variables", "", "# Function Arguments"]) + nmap :call expand_var() + nmap q (go-debug-stop) + endif + + silent! delcommand GoDebugStart + silent! delcommand GoDebugTest + command! -nargs=0 GoDebugContinue call go#debug#Stack('continue') + command! -nargs=0 GoDebugNext call go#debug#Stack('next') + command! -nargs=0 GoDebugStep call go#debug#Stack('step') + command! -nargs=0 GoDebugStepOut call go#debug#Stack('stepOut') + command! -nargs=0 GoDebugRestart call go#debug#Restart() + command! -nargs=0 GoDebugStop call go#debug#Stop() + command! -nargs=* GoDebugSet call go#debug#Set() + command! -nargs=1 GoDebugPrint call go#debug#Print() + + nnoremap (go-debug-breakpoint) :call go#debug#Breakpoint() + nnoremap (go-debug-next) :call go#debug#Stack('next') + nnoremap (go-debug-step) :call go#debug#Stack('step') + nnoremap (go-debug-stepout) :call go#debug#Stack('stepout') + nnoremap (go-debug-continue) :call go#debug#Stack('continue') + nnoremap (go-debug-stop) :call go#debug#Stop() + nnoremap (go-debug-print) :call go#debug#Print(expand('')) + + set balloonexpr=go#debug#BalloonExpr() + set ballooneval + + exe bufwinnr(oldbuf) 'wincmd w' + + augroup vim-go-debug + autocmd! + autocmd FileType go nmap (go-debug-continue) + autocmd FileType go nmap (go-debug-print) + autocmd FileType go nmap (go-debug-breakpoint) + autocmd FileType go nmap (go-debug-next) + autocmd FileType go nmap (go-debug-step) + augroup END + doautocmd vim-go-debug FileType go +endfunction + +function! s:err_cb(ch, msg) abort + call go#util#EchoError(a:msg) + let s:state['message'] += [a:msg] +endfunction + +function! s:out_cb(ch, msg) abort + call go#util#EchoProgress(a:msg) + let s:state['message'] += [a:msg] + + " TODO: why do this in this callback? + if stridx(a:msg, go#config#DebugAddress()) != -1 + call ch_setoptions(a:ch, { + \ 'out_cb': function('s:logger', ['OUT: ']), + \ 'err_cb': function('s:logger', ['ERR: ']), + \}) + + " Tell dlv about the breakpoints that the user added before delve started. + let l:breaks = copy(s:state.breakpoint) + let s:state['breakpoint'] = {} + for l:bt in values(l:breaks) + call go#debug#Breakpoint(bt.line) + endfor + + call s:call_jsonrpc('RPCServer.ListBreakpoints', function('s:start_cb')) + endif +endfunction + +" Start the debug mode. The first argument is the package name to compile and +" debug, anything else will be passed to the running program. +function! go#debug#Start(is_test, ...) abort + call go#cmd#autowrite() + + if has('nvim') + call go#util#EchoError('This feature only works in Vim for now; Neovim is not (yet) supported. Sorry :-(') + return + endif + if !go#util#has_job() + call go#util#EchoError('This feature requires Vim 8.0.0087 or newer with +job.') + return + endif + + " It's already running. + if has_key(s:state, 'job') && job_status(s:state['job']) == 'run' + return + endif + + let s:start_args = a:000 + + if go#util#HasDebug('debugger-state') + call go#config#SetDebugDiag(s:state) + endif + + " cd in to test directory; this is also what running "go test" does. + if a:is_test + lcd %:p:h + endif + + let s:state.is_test = a:is_test + + let dlv = go#path#CheckBinPath("dlv") + if empty(dlv) + return + endif + + try + if len(a:000) > 0 + let l:pkgname = a:1 + " Expand .; otherwise this won't work from a tmp dir. + if l:pkgname[0] == '.' + let l:pkgname = go#package#FromPath(getcwd()) . l:pkgname[1:] + endif + else + let l:pkgname = go#package#FromPath(getcwd()) + endif + + let l:args = [] + if len(a:000) > 1 + let l:args = ['--'] + a:000[1:] + endif + + let l:cmd = [ + \ dlv, + \ (a:is_test ? 'test' : 'debug'), + \ l:pkgname, + \ '--output', tempname(), + \ '--headless', + \ '--api-version', '2', + \ '--log', 'debugger', + \ '--listen', go#config#DebugAddress(), + \ '--accept-multiclient', + \] + + let buildtags = go#config#BuildTags() + if buildtags isnot '' + let l:cmd += ['--build-flags', '--tags=' . buildtags] + endif + let l:cmd += l:args + + call go#util#EchoProgress('Starting GoDebug...') + let s:state['message'] = [] + let s:state['job'] = job_start(l:cmd, { + \ 'out_cb': function('s:out_cb'), + \ 'err_cb': function('s:err_cb'), + \ 'exit_cb': function('s:exit'), + \ 'stoponexit': 'kill', + \}) + catch + call go#util#EchoError(v:exception) + endtry +endfunction + +" Translate a reflect kind constant to a human string. +function! s:reflect_kind(k) + " Kind constants from Go's reflect package. + return [ + \ 'Invalid Kind', + \ 'Bool', + \ 'Int', + \ 'Int8', + \ 'Int16', + \ 'Int32', + \ 'Int64', + \ 'Uint', + \ 'Uint8', + \ 'Uint16', + \ 'Uint32', + \ 'Uint64', + \ 'Uintptr', + \ 'Float32', + \ 'Float64', + \ 'Complex64', + \ 'Complex128', + \ 'Array', + \ 'Chan', + \ 'Func', + \ 'Interface', + \ 'Map', + \ 'Ptr', + \ 'Slice', + \ 'String', + \ 'Struct', + \ 'UnsafePointer', + \ ][a:k] +endfunction + +function! s:eval_tree(var, nest) abort + if a:var.name =~ '^\~' + return '' + endif + let nest = a:nest + let v = '' + let kind = s:reflect_kind(a:var.kind) + if !empty(a:var.name) + let v .= repeat(' ', nest) . a:var.name . ': ' + + if kind == 'Bool' + let v .= printf("%s\n", a:var.value) + + elseif kind == 'Struct' + " Anonymous struct + if a:var.type[:8] == 'struct { ' + let v .= printf("%s\n", a:var.type) + else + let v .= printf("%s{...}\n", a:var.type) + endif + + elseif kind == 'String' + let v .= printf("%s[%d]%s\n", a:var.type, a:var.len, + \ len(a:var.value) > 0 ? ': ' . a:var.value : '') + + elseif kind == 'Slice' || kind == 'String' || kind == 'Map' || kind == 'Array' + let v .= printf("%s[%d]\n", a:var.type, a:var.len) + + elseif kind == 'Chan' || kind == 'Func' || kind == 'Interface' + let v .= printf("%s\n", a:var.type) + + elseif kind == 'Ptr' + " TODO: We can do something more useful here. + let v .= printf("%s\n", a:var.type) + + elseif kind == 'Complex64' || kind == 'Complex128' + let v .= printf("%s%s\n", a:var.type, a:var.value) + + " Int, Float + else + let v .= printf("%s(%s)\n", a:var.type, a:var.value) + endif + else + let nest -= 1 + endif + + if index(['Chan', 'Complex64', 'Complex128'], kind) == -1 && a:var.type != 'error' + for c in a:var.children + let v .= s:eval_tree(c, nest+1) + endfor + endif + return v +endfunction + +function! s:eval(arg) abort + try + let res = s:call_jsonrpc('RPCServer.State') + let goroutineID = res.result.State.currentThread.goroutineID + let res = s:call_jsonrpc('RPCServer.Eval', { + \ 'expr': a:arg, + \ 'scope': {'GoroutineID': goroutineID} + \ }) + return s:eval_tree(res.result.Variable, 0) + catch + call go#util#EchoError(v:exception) + return '' + endtry +endfunction + +function! go#debug#BalloonExpr() abort + silent! let l:v = s:eval(v:beval_text) + return l:v +endfunction + +function! go#debug#Print(arg) abort + try + echo substitute(s:eval(a:arg), "\n$", "", 0) + catch + call go#util#EchoError(v:exception) + endtry +endfunction + +function! s:update_variables() abort + " FollowPointers requests pointers to be automatically dereferenced. + " MaxVariableRecurse is how far to recurse when evaluating nested types. + " MaxStringLen is the maximum number of bytes read from a string + " MaxArrayValues is the maximum number of elements read from an array, a slice or a map. + " MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields. + let l:cfg = { + \ 'scope': {'GoroutineID': s:groutineID()}, + \ 'cfg': {'MaxStringLen': 20, 'MaxArrayValues': 20} + \ } + + try + let res = s:call_jsonrpc('RPCServer.ListLocalVars', l:cfg) + let s:state['localVars'] = res.result['Variables'] + catch + call go#util#EchoError(v:exception) + endtry + + try + let res = s:call_jsonrpc('RPCServer.ListFunctionArgs', l:cfg) + let s:state['functionArgs'] = res.result['Args'] + catch + call go#util#EchoError(v:exception) + endtry + + call s:show_variables() +endfunction + +function! go#debug#Set(symbol, value) abort + try + let res = s:call_jsonrpc('RPCServer.State') + let goroutineID = res.result.State.currentThread.goroutineID + call s:call_jsonrpc('RPCServer.Set', { + \ 'symbol': a:symbol, + \ 'value': a:value, + \ 'scope': {'GoroutineID': goroutineID} + \ }) + catch + call go#util#EchoError(v:exception) + endtry + + call s:update_variables() +endfunction + +function! s:update_stacktrace() abort + try + let res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5}) + call s:show_stacktrace(res) + catch + call go#util#EchoError(v:exception) + endtry +endfunction + +function! s:stack_cb(ch, json) abort + let s:stack_name = '' + let res = json_decode(a:json) + if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error) + call go#util#EchoError(res.error) + call s:clearState() + call go#debug#Restart() + return + endif + + if empty(res) || !has_key(res, 'result') + return + endif + call s:update_breakpoint(res) + call s:update_stacktrace() + call s:update_variables() +endfunction + +" Send a command to change the cursor location to Delve. +" +" a:name must be one of continue, next, step, or stepOut. +function! go#debug#Stack(name) abort + let l:name = a:name + + " Run continue if the program hasn't started yet. + if s:state.running is 0 + let s:state.running = 1 + let l:name = 'continue' + endif + + " Add a breakpoint to the main.Main if the user didn't define any. + if len(s:state['breakpoint']) is 0 + if go#debug#Breakpoint() isnot 0 + let s:state.running = 0 + return + endif + endif + + try + " TODO: document why this is needed. + if l:name is# 'next' && get(s:, 'stack_name', '') is# 'next' + call s:call_jsonrpc('RPCServer.CancelNext') + endif + let s:stack_name = l:name + call s:call_jsonrpc('RPCServer.Command', function('s:stack_cb'), {'name': l:name}) + catch + call go#util#EchoError(v:exception) + endtry +endfunction + +function! go#debug#Restart() abort + call go#cmd#autowrite() + + try + call job_stop(s:state['job']) + while has_key(s:state, 'job') && job_status(s:state['job']) is# 'run' + sleep 50m + endwhile + + let l:breaks = s:state['breakpoint'] + let s:state = { + \ 'rpcid': 1, + \ 'running': 0, + \ 'breakpoint': {}, + \ 'currentThread': {}, + \ 'localVars': {}, + \ 'functionArgs': {}, + \ 'message': [], + \} + + " Preserve breakpoints. + for bt in values(l:breaks) + " TODO: should use correct filename + exe 'sign unplace '. bt.id .' file=' . bt.file + call go#debug#Breakpoint(bt.line) + endfor + call call('go#debug#Start', s:start_args) + catch + call go#util#EchoError(v:exception) + endtry +endfunction + +" Report if debugger mode is active. +function! s:isActive() + return len(s:state['message']) > 0 +endfunction + +" Toggle breakpoint. Returns 0 on success and 1 on failure. +function! go#debug#Breakpoint(...) abort + let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!') + + " Get line number from argument. + if len(a:000) > 0 + let linenr = str2nr(a:1) + if linenr is 0 + call go#util#EchoError('not a number: ' . a:1) + return 0 + endif + else + let linenr = line('.') + endif + + try + " Check if we already have a breakpoint for this line. + let found = v:none + for k in keys(s:state.breakpoint) + let bt = s:state.breakpoint[k] + if bt.file == l:filename && bt.line == linenr + let found = bt + break + endif + endfor + + " Remove breakpoint. + if type(found) == v:t_dict + call remove(s:state['breakpoint'], bt.id) + exe 'sign unplace '. found.id .' file=' . found.file + if s:isActive() + let res = s:call_jsonrpc('RPCServer.ClearBreakpoint', {'id': found.id}) + endif + " Add breakpoint. + else + if s:isActive() + let res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': linenr}}) + let bt = res.result.Breakpoint + exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file + let s:state['breakpoint'][bt.id] = bt + else + let id = len(s:state['breakpoint']) + 1 + let s:state['breakpoint'][id] = {'id': id, 'file': l:filename, 'line': linenr} + exe 'sign place '. id .' line=' . linenr . ' name=godebugbreakpoint file=' . l:filename + endif + endif + catch + call go#util#EchoError(v:exception) + return 1 + endtry + + return 0 +endfunction + +sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint +sign define godebugcurline text== linehl=GoDebugCurrent texthl=GoDebugCurrent + +" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/decls.vim b/pack/plugins/start/fatih-vim-go/autoload/go/decls.vim index 493bbd37..1dcc4d3f 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/decls.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/decls.vim @@ -1,11 +1,8 @@ -if !exists('g:go_decls_mode') - let g:go_decls_mode = '' -endif - function! go#decls#Decls(mode, ...) abort - if g:go_decls_mode == 'ctrlp' + let decls_mode = go#config#DeclsMode() + if decls_mode == 'ctrlp' call ctrlp#init(call("ctrlp#decls#cmd", [a:mode] + a:000)) - elseif g:go_decls_mode == 'fzf' + elseif decls_mode == 'fzf' call call("fzf#decls#cmd", [a:mode] + a:000) else if globpath(&rtp, 'plugin/ctrlp.vim') != "" diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/def.vim b/pack/plugins/start/fatih-vim-go/autoload/go/def.vim index 7b5c86d8..97ef79c8 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/def.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/def.vim @@ -8,7 +8,7 @@ function! go#def#Jump(mode) abort " godef which also has it's own quirks. But this issue come up so many " times I've decided to support both. By default we still use guru as it " covers all edge cases, but now anyone can switch to godef if they wish - let bin_name = get(g:, 'go_def_mode', 'guru') + let bin_name = go#config#DefMode() if bin_name == 'godef' if &modified " Write current unsaved buffer to a temp file and use the modified content @@ -17,23 +17,21 @@ function! go#def#Jump(mode) abort let fname = l:tmpname endif - let bin_path = go#path#CheckBinPath("godef") - if empty(bin_path) - return - endif - let command = printf("%s -f=%s -o=%s -t", go#util#Shellescape(bin_path), - \ go#util#Shellescape(fname), go#util#OffsetCursor()) - let out = go#util#System(command) + let [l:out, l:err] = go#util#Exec(['godef', + \ '-f=' . l:fname, + \ '-o=' . go#util#OffsetCursor(), + \ '-t']) if exists("l:tmpname") call delete(l:tmpname) endif + elseif bin_name == 'guru' - let bin_path = go#path#CheckBinPath("guru") - if empty(bin_path) - return + let cmd = [go#path#CheckBinPath(bin_name)] + let buildtags = go#config#BuildTags() + if buildtags isnot '' + let cmd += ['-tags', buildtags] endif - let cmd = [bin_path] let stdin_content = "" if &modified @@ -42,13 +40,7 @@ function! go#def#Jump(mode) abort call add(cmd, "-modified") endif - if exists('g:go_build_tags') - let tags = get(g:, 'go_build_tags') - call extend(cmd, ["-tags", tags]) - endif - - let fname = fname.':#'.go#util#OffsetCursor() - call extend(cmd, ["definition", fname]) + call extend(cmd, ["definition", fname . ':#' . go#util#OffsetCursor()]) if go#util#has_job() let l:spawn_args = { @@ -66,18 +58,17 @@ function! go#def#Jump(mode) abort return endif - let command = join(cmd, " ") if &modified - let out = go#util#System(command, stdin_content) + let [l:out, l:err] = go#util#Exec(l:cmd, stdin_content) else - let out = go#util#System(command) + let [l:out, l:err] = go#util#Exec(l:cmd) endif else call go#util#EchoError('go_def_mode value: '. bin_name .' is not valid. Valid values are: [godef, guru]') return endif - if go#util#ShellError() != 0 + if l:err call go#util#EchoError(out) return endif @@ -138,7 +129,7 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort if filename != fnamemodify(expand("%"), ':p:gs?\\?/?') " jump to existing buffer if, 1. we have enabled it, 2. the buffer is loaded " and 3. there is buffer window number we switch to - if get(g:, 'go_def_reuse_buffer', 0) && bufloaded(filename) != 0 && bufwinnr(filename) != -1 + if go#config#DefReuseBuffer() && bufloaded(filename) != 0 && bufwinnr(filename) != -1 " jumpt to existing buffer if it exists execute bufwinnr(filename) . 'wincmd w' else @@ -292,13 +283,7 @@ function! go#def#Stack(...) abort endfunction function s:def_job(args) abort - let callbacks = go#job#Spawn(a:args) - - let start_options = { - \ 'callback': callbacks.callback, - \ 'exit_cb': callbacks.exit_cb, - \ 'close_cb': callbacks.close_cb, - \ } + let l:start_options = go#job#Options(a:args) if &modified let l:tmpname = tempname() @@ -307,7 +292,7 @@ function s:def_job(args) abort let l:start_options.in_name = l:tmpname endif - call job_start(a:args.cmd, start_options) + call go#job#Start(a:args.cmd, start_options) endfunction " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/doc.vim b/pack/plugins/start/fatih-vim-go/autoload/go/doc.vim index e06a7906..1b36aaf8 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/doc.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/doc.vim @@ -4,18 +4,14 @@ let s:buf_nr = -1 -if !exists("g:go_doc_command") - let g:go_doc_command = ["godoc"] -endif - function! go#doc#OpenBrowser(...) abort " check if we have gogetdoc as it gives us more and accurate information. " Only supported if we have json_decode as it's not worth to parse the plain " non-json output of gogetdoc let bin_path = go#path#CheckBinPath('gogetdoc') if !empty(bin_path) && exists('*json_decode') - let json_out = s:gogetdoc(1) - if go#util#ShellError() != 0 + let [l:json_out, l:err] = s:gogetdoc(1) + if l:err call go#util#EchoError(json_out) return endif @@ -29,14 +25,12 @@ function! go#doc#OpenBrowser(...) abort let name = out["name"] let decl = out["decl"] - let godoc_url = s:custom_godoc_url() + let godoc_url = go#config#DocUrl() let godoc_url .= "/" . import if decl !~ "^package" let godoc_url .= "#" . name endif - echo godoc_url - call go#tool#OpenBrowser(godoc_url) return endif @@ -50,28 +44,27 @@ function! go#doc#OpenBrowser(...) abort let exported_name = pkgs[1] " example url: https://godoc.org/github.com/fatih/set#Set - let godoc_url = s:custom_godoc_url() . "/" . pkg . "#" . exported_name + let godoc_url = go#config#DocUrl() . "/" . pkg . "#" . exported_name call go#tool#OpenBrowser(godoc_url) endfunction function! go#doc#Open(newmode, mode, ...) abort " With argument: run "godoc [arg]". if len(a:000) - if empty(go#path#CheckBinPath(g:go_doc_command[0])) + if empty(go#path#CheckBinPath(go#config#DocCommand()[0])) return endif - let command = printf("%s %s", go#util#Shelljoin(g:go_doc_command), join(a:000, ' ')) - let out = go#util#System(command) + let [l:out, l:err] = go#util#Exec(go#config#DocCommand() + a:000) " Without argument: run gogetdoc on cursor position. else - let out = s:gogetdoc(0) + let [l:out, l:err] = s:gogetdoc(0) if out == -1 return endif endif - if go#util#ShellError() != 0 + if l:err call go#util#EchoError(out) return endif @@ -97,7 +90,7 @@ function! s:GodocView(newposition, position, content) abort if !is_visible if a:position == "split" " cap window height to 20, but resize it for smaller contents - let max_height = get(g:, "go_doc_max_height", 20) + let max_height = go#config#DocMaxHeight() let content_height = len(split(a:content, "\n")) if content_height > max_height exe 'resize ' . max_height @@ -135,33 +128,20 @@ function! s:GodocView(newposition, position, content) abort endfunction function! s:gogetdoc(json) abort - " check if we have 'gogetdoc' and use it automatically - let bin_path = go#path#CheckBinPath('gogetdoc') - if empty(bin_path) - return -1 - endif - - let cmd = [go#util#Shellescape(bin_path)] - - let offset = go#util#OffsetCursor() - let fname = expand("%:p:gs!\\!/!") - let pos = shellescape(fname.':#'.offset) - - let cmd += ["-pos", pos] + let l:cmd = [ + \ 'gogetdoc', + \ '-tags', go#config#BuildTags(), + \ '-pos', expand("%:p:gs!\\!/!") . ':#' . go#util#OffsetCursor()] if a:json - let cmd += ["-json"] + let l:cmd += ['-json'] endif - let command = join(cmd, " ") - if &modified - let command .= " -modified" - let out = go#util#System(command, go#util#archive()) - else - let out = go#util#System(command) + let l:cmd += ['-modified'] + return go#util#Exec(l:cmd, go#util#archive()) endif - return out + return go#util#Exec(l:cmd) endfunction " returns the package and exported name. exported name might be empty. @@ -206,18 +186,4 @@ function! s:godocWord(args) abort return [pkg, exported_name] endfunction -function! s:custom_godoc_url() abort - let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org') - if godoc_url isnot 'https://godoc.org' - " strip last '/' character if available - let last_char = strlen(godoc_url) - 1 - if godoc_url[last_char] == '/' - let godoc_url = strpart(godoc_url, 0, last_char) - endif - " custom godoc installations expect /pkg before package names - let godoc_url .= "/pkg" - endif - return godoc_url -endfunction - " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/fillstruct.vim b/pack/plugins/start/fatih-vim-go/autoload/go/fillstruct.vim index ec88a761..7f1c4688 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/fillstruct.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/fillstruct.vim @@ -3,6 +3,8 @@ function! go#fillstruct#FillStruct() abort \ '-file', bufname(''), \ '-offset', go#util#OffsetCursor(), \ '-line', line('.')] + " Needs: https://github.com/davidrjenni/reftools/pull/14 + "\ '-tags', go#config#BuildTags()] " Read from stdin if modified. if &modified diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/fmt.vim b/pack/plugins/start/fatih-vim-go/autoload/go/fmt.vim index 42d04191..ce515487 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/fmt.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/fmt.vim @@ -5,22 +5,6 @@ " fmt.vim: Vim command to format Go files with gofmt (and gofmt compatible " toorls, such as goimports). -if !exists("g:go_fmt_command") - let g:go_fmt_command = "gofmt" -endif - -if !exists('g:go_fmt_options') - let g:go_fmt_options = '' -endif - -if !exists('g:go_fmt_fail_silently') - let g:go_fmt_fail_silently = 0 -endif - -if !exists("g:go_fmt_experimental") - let g:go_fmt_experimental = 0 -endif - " we have those problems : " http://stackoverflow.com/questions/12741977/prevent-vim-from-updating-its-undo-tree " http://stackoverflow.com/questions/18532692/golang-formatter-and-vim-how-to-destroy-history-record?rq=1 @@ -30,7 +14,7 @@ endif " this and have VimL experience, please look at the function for " improvements, patches are welcome :) function! go#fmt#Format(withGoimport) abort - if g:go_fmt_experimental == 1 + if go#config#FmtExperimental() " Using winsaveview to save/restore cursor state has the problem of " closing folds on save: " https://github.com/fatih/vim-go/issues/502 @@ -64,18 +48,18 @@ function! go#fmt#Format(withGoimport) abort let l:tmpname = tr(l:tmpname, '\', '/') endif - let bin_name = g:go_fmt_command + let bin_name = go#config#FmtCommand() if a:withGoimport == 1 let bin_name = "goimports" endif let current_col = col('.') - let out = go#fmt#run(bin_name, l:tmpname, expand('%')) + let [l:out, l:err] = go#fmt#run(bin_name, l:tmpname, expand('%')) let diff_offset = len(readfile(l:tmpname)) - line('$') - if go#util#ShellError() == 0 + if l:err == 0 call go#fmt#update_file(l:tmpname, expand('%')) - elseif g:go_fmt_fail_silently == 0 + elseif !go#config#FmtFailSilently() let errors = s:parse_errors(expand('%'), out) call s:show_errors(errors) endif @@ -83,7 +67,7 @@ function! go#fmt#Format(withGoimport) abort " We didn't use the temp file, so clean up call delete(l:tmpname) - if g:go_fmt_experimental == 1 + if go#config#FmtExperimental() " restore our undo history silent! exe 'rundo ' . tmpundofile call delete(tmpundofile) @@ -148,71 +132,33 @@ function! go#fmt#update_file(source, target) if has_key(l:list_title, "title") && l:list_title['title'] == "Format" call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) endif endfunction " run runs the gofmt/goimport command for the given source file and returns " the output of the executed command. Target is the real file to be formatted. function! go#fmt#run(bin_name, source, target) - let cmd = s:fmt_cmd(a:bin_name, a:source, a:target) - if empty(cmd) + let l:cmd = s:fmt_cmd(a:bin_name, a:source, a:target) + if empty(l:cmd) return endif - - let command = join(cmd, " ") - - " execute our command... - let out = go#util#System(command) - - return out + return go#util#Exec(l:cmd) endfunction -" fmt_cmd returns a dict that contains the command to execute gofmt (or -" goimports). args is dict with +" fmt_cmd returns the command to run as a list. function! s:fmt_cmd(bin_name, source, target) - " check if the user has installed command binary. - " For example if it's goimports, let us check if it's installed, - " if not the user get's a warning via go#path#CheckBinPath() - let bin_path = go#path#CheckBinPath(a:bin_name) - if empty(bin_path) - return [] - endif - - " start constructing the command - let bin_path = go#util#Shellescape(bin_path) - let cmd = [bin_path] - call add(cmd, "-w") + let l:cmd = [a:bin_name, '-w'] " add the options for binary (if any). go_fmt_options was by default of type " string, however to allow customization it's now a dictionary of binary " name mapping to options. - let opts = g:go_fmt_options - if type(g:go_fmt_options) == type({}) - let opts = has_key(g:go_fmt_options, a:bin_name) ? g:go_fmt_options[a:bin_name] : "" + let opts = go#config#FmtOptions() + if type(opts) == type({}) + let opts = has_key(opts, a:bin_name) ? opts[a:bin_name] : "" endif call extend(cmd, split(opts, " ")) - - if a:bin_name == "goimports" - " lazy check if goimports support `-srcdir`. We should eventually remove - " this in the future - if !exists('b:goimports_vendor_compatible') - let out = go#util#System(bin_path . " --help") - if out !~ "-srcdir" - call go#util#EchoWarning(printf("vim-go: goimports (%s) does not support srcdir. Update with: :GoUpdateBinaries", bin_path)) - else - let b:goimports_vendor_compatible = 1 - endif - endif - - if exists('b:goimports_vendor_compatible') && b:goimports_vendor_compatible - let ssl_save = &shellslash - set noshellslash - " use the filename without the fully qualified name if the tree is - " symlinked into the GOPATH, goimports won't work properly. - call extend(cmd, ["-srcdir", shellescape(a:target)]) - let &shellslash = ssl_save - endif + if a:bin_name is# 'goimports' + call extend(cmd, ["-srcdir", a:target]) endif call add(cmd, a:source) @@ -255,13 +201,13 @@ function! s:show_errors(errors) abort endfunction function! go#fmt#ToggleFmtAutoSave() abort - if get(g:, "go_fmt_autosave", 1) - let g:go_fmt_autosave = 0 + if go#config#FmtAutosave() + call go#config#SetFmtAutosave(0) call go#util#EchoProgress("auto fmt disabled") return end - let g:go_fmt_autosave = 1 + call go#config#SetFmtAutosave(1) call go#util#EchoProgress("auto fmt enabled") endfunction diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/guru.vim b/pack/plugins/start/fatih-vim-go/autoload/go/guru.vim index e8640325..8d5f5483 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/guru.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/guru.vim @@ -16,12 +16,6 @@ function! s:guru_cmd(args) range abort let selected = a:args.selected let result = {} - let pkg = go#package#ImportPath() - - " this is important, check it! - if pkg == -1 && needs_scope - return {'err': "current directory is not inside of a valid GOPATH"} - endif "return with a warning if the binary doesn't exist let bin_path = go#path#CheckBinPath("guru") @@ -30,9 +24,8 @@ function! s:guru_cmd(args) range abort endif " start constructing the command - let cmd = [bin_path] + let cmd = [bin_path, '-tags', go#config#BuildTags()] - let filename = fnamemodify(expand("%"), ':p:gs?\\?/?') if &modified let result.stdin_content = go#util#archive() call add(cmd, "-modified") @@ -43,44 +36,22 @@ function! s:guru_cmd(args) range abort call add(cmd, "-json") endif - " check for any tags - if exists('g:go_build_tags') - let tags = get(g:, 'go_build_tags') - call extend(cmd, ["-tags", tags]) - let result.tags = tags - endif - - " some modes require scope to be defined (such as callers). For these we - " choose a sensible setting, which is using the current file's package - let scopes = [] - if needs_scope - let scopes = [pkg] - endif - - " check for any user defined scope setting. users can define the scope, - " in package pattern form. examples: - " golang.org/x/tools/cmd/guru # a single package - " golang.org/x/tools/... # all packages beneath dir - " ... # the entire workspace. - if exists('g:go_guru_scope') - " check that the setting is of type list - if type(get(g:, 'go_guru_scope')) != type([]) - return {'err' : "go_guru_scope should of type list"} + let scopes = go#config#GuruScope() + if empty(scopes) + " some modes require scope to be defined (such as callers). For these we + " choose a sensible setting, which is using the current file's package + if needs_scope + let pkg = go#package#ImportPath() + if pkg == -1 + return {'err': "current directory is not inside of a valid GOPATH"} + endif + let scopes = [pkg] endif - - let scopes = get(g:, 'go_guru_scope') endif - " now add the scope to our command if there is any + " Add the scope. if !empty(scopes) - " strip trailing slashes for each path in scoped. bug: - " https://github.com/golang/go/issues/14584 - let scopes = go#util#StripTrailingSlash(scopes) - - " create shell-safe entries of the list - if !go#util#has_job() | let scopes = go#util#Shelllist(scopes) | endif - - " guru expect a comma-separated list of patterns, construct it + " guru expect a comma-separated list of patterns. let l:scope = join(scopes, ",") let result.scope = l:scope call extend(cmd, ["-scope", l:scope]) @@ -94,8 +65,8 @@ function! s:guru_cmd(args) range abort let pos = printf("#%s,#%s", pos1, pos2) endif - let filename .= ':'.pos - call extend(cmd, [mode, filename]) + let l:filename = fnamemodify(expand("%"), ':p:gs?\\?/?') . ':' . pos + call extend(cmd, [mode, l:filename]) let result.cmd = cmd return result @@ -119,24 +90,80 @@ function! s:sync_guru(args) abort endif endif - " run, forrest run!!! - let command = join(result.cmd, " ") - if has_key(result, 'stdin_content') - let out = go#util#System(command, result.stdin_content) + if has_key(l:result, 'stdin_content') + let [l:out, l:err] = go#util#Exec(l:result.cmd, l:result.stdin_content) else - let out = go#util#System(command) + let [l:out, l:err] = go#util#Exec(l:result.cmd) endif if has_key(a:args, 'custom_parse') - call a:args.custom_parse(go#util#ShellError(), out) + call a:args.custom_parse(l:err, l:out, a:args.mode) else - call s:parse_guru_output(go#util#ShellError(), out, a:args.mode) + call s:parse_guru_output(l:err, l:out, a:args.mode) endif - return out + return l:out endfunc +" use vim or neovim job api as appropriate +function! s:job_start(cmd, start_options) abort + if go#util#has_job() + return job_start(a:cmd, a:start_options) + endif + + let opts = {'stdout_buffered': v:true, 'stderr_buffered': v:true} + + let stdout_buf = "" + function opts.on_stdout(job_id, data, event) closure + let l:data = a:data + let l:data[0] = stdout_buf . l:data[0] + let stdout_buf = "" + + if l:data[-1] != "" + let stdout_buf = l:data[-1] + endif + + let l:data = l:data[:-2] + if len(l:data) == 0 + return + endif + + call a:start_options.callback(a:job_id, join(l:data, "\n")) + endfunction + + let stderr_buf = "" + function opts.on_stderr(job_id, data, event) closure + let l:data = a:data + let l:data[0] = stderr_buf . l:data[0] + let stderr_buf = "" + + if l:data[-1] != "" + let stderr_buf = l:data[-1] + endif + + let l:data = l:data[:-2] + if len(l:data) == 0 + return + endif + + call a:start_options.callback(a:job_id, join(l:data, "\n")) + endfunction + + function opts.on_exit(job_id, exit_code, event) closure + call a:start_options.exit_cb(a:job_id, a:exit_code) + call a:start_options.close_cb(a:job_id) + endfunction + + " use a shell for input redirection if needed + let cmd = a:cmd + if has_key(a:start_options, 'in_io') && a:start_options.in_io ==# 'file' && !empty(a:start_options.in_name) + let cmd = ['/bin/sh', '-c', go#util#Shelljoin(a:cmd) . ' <' . a:start_options.in_name] + endif + + return jobstart(cmd, opts) +endfunction + " async_guru runs guru in async mode with the given arguments function! s:async_guru(args) abort let result = s:guru_cmd(a:args) @@ -145,9 +172,6 @@ function! s:async_guru(args) abort return endif - let status_dir = expand('%:p:h') - let statusline_type = printf("%s", a:args.mode) - if !has_key(a:args, 'disable_progress') if a:args.needs_scope call go#util#EchoProgress("analysing with scope " . result.scope . @@ -155,60 +179,64 @@ function! s:async_guru(args) abort endif endif - let messages = [] - function! s:callback(chan, msg) closure - call add(messages, a:msg) + let state = { + \ 'status_dir': expand('%:p:h'), + \ 'statusline_type': printf("%s", a:args.mode), + \ 'mode': a:args.mode, + \ 'status': {}, + \ 'exitval': 0, + \ 'closed': 0, + \ 'exited': 0, + \ 'messages': [], + \ 'parse' : get(a:args, 'custom_parse', funcref("s:parse_guru_output")) + \ } + + function! s:callback(chan, msg) dict + call add(self.messages, a:msg) endfunction - let status = {} - let exitval = 0 - let closed = 0 - let exited = 0 - - function! s:exit_cb(job, exitval) closure - let exited = 1 + function! s:exit_cb(job, exitval) dict + let self.exited = 1 let status = { \ 'desc': 'last status', - \ 'type': statusline_type, + \ 'type': self.statusline_type, \ 'state': "finished", \ } if a:exitval - let exitval = a:exitval + let self.exitval = a:exitval let status.state = "failed" endif - call go#statusline#Update(status_dir, status) + call go#statusline#Update(self.status_dir, status) - if closed - call s:complete() + if self.closed + call self.complete() endif endfunction - function! s:close_cb(ch) closure - let closed = 1 + function! s:close_cb(ch) dict + let self.closed = 1 - if exited - call s:complete() + if self.exited + call self.complete() endif endfunction - function! s:complete() closure - let out = join(messages, "\n") + function state.complete() dict + let out = join(self.messages, "\n") - if has_key(a:args, 'custom_parse') - call a:args.custom_parse(exitval, out) - else - call s:parse_guru_output(exitval, out, a:args.mode) - endif + call self.parse(self.exitval, out, self.mode) endfunction + " explicitly bind the callbacks to state so that self within them always + " refers to state. See :help Partial for more information. let start_options = { - \ 'callback': funcref("s:callback"), - \ 'exit_cb': funcref("s:exit_cb"), - \ 'close_cb': funcref("s:close_cb"), - \ } + \ 'callback': function('s:callback', [], state), + \ 'exit_cb': function('s:exit_cb', [], state), + \ 'close_cb': function('s:close_cb', [], state) + \ } if has_key(result, 'stdin_content') let l:tmpname = tempname() @@ -217,18 +245,18 @@ function! s:async_guru(args) abort let l:start_options.in_name = l:tmpname endif - call go#statusline#Update(status_dir, { + call go#statusline#Update(state.status_dir, { \ 'desc': "current status", - \ 'type': statusline_type, + \ 'type': state.statusline_type, \ 'state': "analysing", \}) - return job_start(result.cmd, start_options) + return s:job_start(result.cmd, start_options) endfunc " run_guru runs the given guru argument function! s:run_guru(args) abort - if go#util#has_job() + if has('nvim') || go#util#has_job() let res = s:async_guru(a:args) else let res = s:sync_guru(a:args) @@ -249,6 +277,18 @@ function! go#guru#Implements(selected) abort call s:run_guru(args) endfunction +" Shows the set of possible objects to which a pointer may point. +function! go#guru#PointsTo(selected) abort + let l:args = { + \ 'mode': 'pointsto', + \ 'format': 'plain', + \ 'selected': a:selected, + \ 'needs_scope': 1, + \ } + + call s:run_guru(l:args) +endfunction + " Report the possible constants, global variables, and concrete types that may " appear in a value of type error function! go#guru#Whicherrs(selected) abort @@ -289,7 +329,7 @@ function! go#guru#DescribeInfo() abort return endif - function! s:info(exit_val, output) + function! s:info(exit_val, output, mode) if a:exit_val != 0 return endif @@ -464,10 +504,6 @@ function! go#guru#Referrers(selected) abort call s:run_guru(args) endfunction -function! go#guru#SameIdsTimer() abort - call timer_start(200, function('go#guru#SameIds'), {'repeat': -1}) -endfunction - function! go#guru#SameIds() abort " we use matchaddpos() which was introduce with 7.4.330, be sure we have " it: http://ftp.vim.org/vim/patches/7.4/7.4.330 @@ -495,24 +531,24 @@ function! go#guru#SameIds() abort call s:run_guru(args) endfunction -function! s:same_ids_highlight(exit_val, output) abort +function! s:same_ids_highlight(exit_val, output, mode) abort call go#guru#ClearSameIds() " run after calling guru to reduce flicker. if a:output[0] !=# '{' - if !get(g:, 'go_auto_sameids', 0) + if !go#config#AutoSameids() call go#util#EchoError(a:output) endif return endif let result = json_decode(a:output) - if type(result) != type({}) && !get(g:, 'go_auto_sameids', 0) + if type(result) != type({}) && !go#config#AutoSameids() call go#util#EchoError("malformed output from guru") return endif if !has_key(result, 'sameids') - if !get(g:, 'go_auto_sameids', 0) + if !go#config#AutoSameids() call go#util#EchoError("no same_ids founds for the given identifier") endif return @@ -538,7 +574,7 @@ function! s:same_ids_highlight(exit_val, output) abort call matchaddpos('goSameId', [[str2nr(pos[-2]), str2nr(pos[-1]), str2nr(poslen)]]) endfor - if get(g:, "go_auto_sameids", 0) + if go#config#AutoSameids() " re-apply SameIds at the current cursor position at the time the buffer " is redisplayed: e.g. :edit, :GoRename, etc. augroup vim-go-sameids @@ -580,15 +616,15 @@ function! go#guru#ToggleSameIds() abort endfunction function! go#guru#AutoToogleSameIds() abort - if get(g:, "go_auto_sameids", 0) + if go#config#AutoSameids() call go#util#EchoProgress("sameids auto highlighting disabled") call go#guru#ClearSameIds() - let g:go_auto_sameids = 0 + call go#config#SetAutoSameids(0) return endif call go#util#EchoSuccess("sameids auto highlighting enabled") - let g:go_auto_sameids = 1 + call go#config#SetAutoSameids(1) endfunction @@ -623,21 +659,26 @@ endfun function! go#guru#Scope(...) abort if a:0 + let scope = a:000 if a:0 == 1 && a:1 == '""' - unlet g:go_guru_scope + let scope = [] + endif + + call go#config#SetGuruScope(scope) + if empty(scope) call go#util#EchoSuccess("guru scope is cleared") else - let g:go_guru_scope = a:000 call go#util#EchoSuccess("guru scope changed to: ". join(a:000, ",")) endif return endif - if !exists('g:go_guru_scope') + let scope = go#config#GuruScope() + if empty(scope) call go#util#EchoError("guru scope is not set") else - call go#util#EchoSuccess("current guru scope: ". join(g:go_guru_scope, ",")) + call go#util#EchoSuccess("current guru scope: ". join(scope, ",")) endif endfunction diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/guru_test.vim b/pack/plugins/start/fatih-vim-go/autoload/go/guru_test.vim new file mode 100644 index 00000000..a4b5cc91 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/guru_test.vim @@ -0,0 +1,15 @@ +function Test_GuruScope_Set() abort + silent call go#guru#Scope("example.com/foo/bar") + let actual = go#config#GuruScope() + call assert_equal(["example.com/foo/bar"], actual) + + silent call go#guru#Scope('""') + silent let actual = go#config#GuruScope() + call assert_equal([], actual, "setting scope to empty string should clear") + + if exists('g:go_guru_scope') + unlet g:go_guru_scope + endif +endfunction + +" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/iferr.vim b/pack/plugins/start/fatih-vim-go/autoload/go/iferr.vim new file mode 100644 index 00000000..82d1ee5c --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/iferr.vim @@ -0,0 +1,16 @@ +function! go#iferr#Generate() + let [l:out, l:err] = go#util#Exec(['iferr', + \ '-pos=' . go#util#OffsetCursor()], go#util#GetLines()) + if len(l:out) == 1 + return + endif + if getline('.') =~ '^\s*$' + silent delete _ + silent normal! k + endif + let l:pos = getcurpos() + call append(l:pos[1], split(l:out, "\n")) + silent normal! j=2j + call setpos('.', l:pos) + silent normal! 4j +endfunction diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/impl.vim b/pack/plugins/start/fatih-vim-go/autoload/go/impl.vim index 73707fa1..7ce8c8be 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/impl.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/impl.vim @@ -101,13 +101,29 @@ function! s:root_dirs() abort return dirs endfunction -function! s:go_packages(dirs) abort +function! s:go_packages(dirs, arglead) abort let pkgs = [] - for d in a:dirs - let pkg_root = expand(d . '/pkg/' . go#util#osarch()) - call extend(pkgs, split(globpath(pkg_root, '**/*.a', 1), "\n")) + for dir in a:dirs + " this may expand to multiple lines + let scr_root = expand(dir . '/src/') + for pkg in split(globpath(scr_root, a:arglead.'*'), "\n") + if isdirectory(pkg) + let pkg .= '/' + elseif pkg !~ '\.a$' + continue + endif + + " without this the result can have duplicates in form of + " 'encoding/json' and '/encoding/json/' + let pkg = go#util#StripPathSep(pkg) + + " remove the scr root and keep the package in tact + let pkg = substitute(pkg, scr_root, "", "") + call add(pkgs, pkg) + endfor endfor - return map(pkgs, "fnamemodify(v:val, ':t:r')") + + return pkgs endfunction function! s:interface_list(pkg) abort @@ -124,13 +140,24 @@ endfunction " Complete package and interface for {interface} function! go#impl#Complete(arglead, cmdline, cursorpos) abort let words = split(a:cmdline, '\s\+', 1) + if words[-1] ==# '' - return s:uniq(sort(s:go_packages(s:root_dirs()))) - elseif words[-1] =~# '^\h\w*$' - return s:uniq(sort(filter(s:go_packages(s:root_dirs()), 'stridx(v:val, words[-1]) == 0'))) - elseif words[-1] =~# '^\h\w*\.\%(\h\w*\)\=$' - let [pkg, interface] = split(words[-1], '\.', 1) - echomsg pkg + " if no words are given, just start completing the first package we found + return s:uniq(sort(s:go_packages(s:root_dirs(), a:arglead))) + elseif words[-1] =~# '^\(\h\w.*\.\%(\h\w*\)\=$\)\@!\S*$' + " start matching go packages. It's negate match of the below match + return s:uniq(sort(s:go_packages(s:root_dirs(), a:arglead))) + elseif words[-1] =~# '^\h\w.*\.\%(\h\w*\)\=$' + " match the following, anything that could indicate an interface candidate + " + " io. + " io.Wr + " github.com/fatih/color. + " github.com/fatih/color.U + " github.com/fatih/color.Un + let splitted = split(words[-1], '\.', 1) + let pkg = join(splitted[:-2], '.') + let interface = splitted[-1] return s:uniq(sort(filter(s:interface_list(pkg), 'v:val =~? words[-1]'))) else return [] diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/import.vim b/pack/plugins/start/fatih-vim-go/autoload/go/import.vim index 8d9e8d4c..a10df2c8 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/import.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/import.vim @@ -27,8 +27,8 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort endif if a:bang == "!" - let out = go#util#System("go get -u -v ".shellescape(path)) - if go#util#ShellError() != 0 + let [l:out, l:err] = go#util#Exec(['go', 'get', '-u', '-v', path]) + if err != 0 call s:Error("Can't find import: " . path . ":" . out) endif endif diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/issue.vim b/pack/plugins/start/fatih-vim-go/autoload/go/issue.vim new file mode 100644 index 00000000..ab280410 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/issue.vim @@ -0,0 +1,34 @@ +let s:templatepath = go#util#Join(expand(':p:h:h:h'), '.github', 'ISSUE_TEMPLATE.md') + +function! go#issue#New() abort + let body = substitute(s:issuebody(), '[^A-Za-z0-9_.~-]', '\="%".printf("%02X",char2nr(submatch(0)))', 'g') + let url = "https://github.com/fatih/vim-go/issues/new?body=" . l:body + call go#tool#OpenBrowser(l:url) +endfunction + +function! s:issuebody() abort + let lines = readfile(s:templatepath) + + let rtrimpat = '[[:space:]]\+$' + let body = [] + for l in lines + let body = add(body, l) + + if l =~ '^\* Vim version' + redir => out + silent version + redir END + let body = extend(body, split(out, "\n")[0:2]) + elseif l =~ '^\* Go version' + let [out, err] = go#util#Exec(['go', 'version']) + let body = add(body, substitute(l:out, rtrimpat, '', '')) + elseif l =~ '^\* Go environment' + let [out, err] = go#util#Exec(['go', 'env']) + let body = add(body, substitute(l:out, rtrimpat, '', '')) + endif + endfor + + return join(body, "\n") +endfunction + +" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/job.vim b/pack/plugins/start/fatih-vim-go/autoload/go/job.vim index 987c0ec1..af139d39 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/job.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/job.vim @@ -1,7 +1,16 @@ -" Spawn returns callbacks to be used with job_start. It is abstracted to be +" Spawn starts an asynchronous job. See the description of go#job#Options to +" understand the args parameter. +" +" Spawn returns a job. +function! go#job#Spawn(cmd, args) + let l:options = go#job#Options(a:args) + return go#job#Start(a:cmd, l:options) +endfunction + +" Options returns callbacks to be used with job_start. It is abstracted to be " used with various go commands, such as build, test, install, etc.. This " allows us to avoid writing the same callback over and over for some -" commands. It's fully customizable so each command can change it to it's own +" commands. It's fully customizable so each command can change it to its own " logic. " " args is a dictionary with the these keys: @@ -10,19 +19,22 @@ " 'bang': " Set to 0 to jump to the first error in the error list. " Defaults to 0. +" 'statustype': +" The status type to use when updating the status. +" See statusline.vim. " 'for': " The g:go_list_type_command key to use to get the error list type to use. " Defaults to '_job' +" 'errorformat': +" The errorformat string to use when parsing errors. Defaults to +" &errorformat. +" See :help 'errorformat'. " 'complete': " A function to call after the job exits and the channel is closed. The " function will be passed three arguments: the job, its exit code, and the " list of messages received from the channel. The default value will " process the messages and manage the error list after the job exits and " the channel is closed. -" 'callback': -" A function to call when there is a message to read from the job's -" channel. The function will be passed two arguments: the channel and a -" message. See job-callback. " The return value is a dictionary with these keys: " 'callback': @@ -34,110 +46,361 @@ " 'close_cb': " A function suitable to be passed as a job close_cb handler. See " job-close_cb. -function go#job#Spawn(args) +" 'cwd': +" The path to the directory which contains the current buffer. +function! go#job#Options(args) let cbs = {} + let state = { + \ 'winid': win_getid(winnr()), + \ 'dir': getcwd(), + \ 'jobdir': fnameescape(expand("%:p:h")), + \ 'messages': [], + \ 'bang': 0, + \ 'for': "_job", + \ 'exited': 0, + \ 'exit_status': 0, + \ 'closed': 0, + \ 'errorformat': &errorformat, + \ 'statustype' : '' + \ } - let winnr = winnr() - let dir = getcwd() - let jobdir = fnameescape(expand("%:p:h")) - let messages = [] - let args = a:args.cmd - let bang = 0 - let for = "_job" + if has("patch-8.0.0902") || has('nvim') + let cbs.cwd = state.jobdir + endif if has_key(a:args, 'bang') - let l:bang = a:args.bang + let state.bang = a:args.bang endif if has_key(a:args, 'for') - let l:for = a:args.for + let state.for = a:args.for endif - let l:exited = 0 - let l:exit_status = 0 - let l:closed = 0 + if has_key(a:args, 'statustype') + let state.statustype = a:args.statustype + endif - function! s:NopComplete(job, exit_status, data) + if has_key(a:args, 'errorformat') + let state.errorformat = a:args.errorformat + endif + + " do nothing in state.complete by default. + function state.complete(job, exit_status, data) endfunction - let Complete = funcref('s:NopComplete') + function state.show_status(job, exit_status) dict + if go#config#EchoCommandInfo() + let prefix = "" + if self.statustype != '' + let prefix = '[' . self.statustype . '] ' + endif + if a:exit_status == 0 + call go#util#EchoSuccess(prefix . "SUCCESS") + else + call go#util#EchoError(prefix . "FAIL") + endif + endif + + if self.statustype == '' + return + endif + + let status = { + \ 'desc': 'last status', + \ 'type': self.statustype, + \ 'state': "success", + \ } + + if a:exit_status + let status.state = "failed" + endif + + if has_key(self, 'started_at') + let elapsed_time = reltimestr(reltime(self.started_at)) + " strip whitespace + let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') + let status.state .= printf(" (%ss)", elapsed_time) + endif + + call go#statusline#Update(self.jobdir, status) + endfunction if has_key(a:args, 'complete') - let Complete = a:args.complete + let state.complete = a:args.complete endif - function cbs.callback(chan, msg) dict closure - call add(messages, a:msg) + function! s:start(args) dict + if self.statustype != '' + let status = { + \ 'desc': 'current status', + \ 'type': self.statustype, + \ 'state': "started", + \ } + + call go#statusline#Update(self.jobdir, status) + endif + let self.started_at = reltime() endfunction + " explicitly bind _start to state so that within it, self will + " always refer to state. See :help Partial for more information. + " + " _start is intended only for internal use and should not be referenced + " outside of this file. + let cbs._start = function('s:start', [''], state) - function cbs.exit_cb(job, exitval) dict closure - let exit_status = a:exitval - let exited = 1 + function! s:callback(chan, msg) dict + call add(self.messages, a:msg) + endfunction + " explicitly bind callback to state so that within it, self will + " always refer to state. See :help Partial for more information. + let cbs.callback = function('s:callback', [], state) - if get(g:, 'go_echo_command_info', 1) - if a:exitval == 0 - call go#util#EchoSuccess("SUCCESS") - else - call go#util#EchoError("FAILED") - endif - endif + function! s:exit_cb(job, exitval) dict + let self.exit_status = a:exitval + let self.exited = 1 + + call self.show_status(a:job, a:exitval) - if closed - call Complete(a:job, exit_status, messages) - call s:show_errors(a:job, exit_status, messages) + if self.closed || has('nvim') + call self.complete(a:job, self.exit_status, self.messages) + call self.show_errors(a:job, self.exit_status, self.messages) endif endfunction + " explicitly bind exit_cb to state so that within it, self will always refer + " to state. See :help Partial for more information. + let cbs.exit_cb = function('s:exit_cb', [], state) - function cbs.close_cb(ch) dict closure - let closed = 1 + function! s:close_cb(ch) dict + let self.closed = 1 - if exited + if self.exited let job = ch_getjob(a:ch) - call Complete(job, exit_status, messages) - call s:show_errors(job, exit_status, messages) + call self.complete(job, self.exit_status, self.messages) + call self.show_errors(job, self.exit_status, self.messages) endif endfunction + " explicitly bind close_cb to state so that within it, self will + " always refer to state. See :help Partial for more information. + let cbs.close_cb = function('s:close_cb', [], state) + + function state.show_errors(job, exit_status, data) + let l:winid = win_getid(winnr()) + call win_gotoid(self.winid) - function! s:show_errors(job, exit_status, data) closure - let l:listtype = go#list#Type(for) + let l:listtype = go#list#Type(self.for) if a:exit_status == 0 call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) + call win_gotoid(l:winid) return endif - let l:listtype = go#list#Type(for) + let l:listtype = go#list#Type(self.for) if len(a:data) == 0 call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) + call win_gotoid(l:winid) return endif - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' + let out = join(self.messages, "\n") + + let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd' try - execute cd jobdir - let errors = go#tool#ParseErrors(a:data) - let errors = go#tool#FilterValids(errors) + " parse the errors relative to self.jobdir + execute l:cd self.jobdir + call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for) + let errors = go#list#Get(l:listtype) finally - execute cd . fnameescape(dir) + execute l:cd fnameescape(self.dir) endtry + if empty(errors) " failed to parse errors, output the original content - call go#util#EchoError(messages + [dir]) + call go#util#EchoError(self.messages + [self.dir]) + call win_gotoid(l:winid) return endif - if winnr == winnr() - call go#list#Populate(l:listtype, errors, join(args)) + if self.winid == l:winid call go#list#Window(l:listtype, len(errors)) - if !bang + if !self.bang call go#list#JumpToFirst(l:listtype) endif endif endfunction + if has('nvim') + return s:neooptions(cbs) + endif + return cbs endfunction +function! go#job#Start(cmd, options) + let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd' + let l:options = copy(a:options) + + if !has_key(l:options, 'cwd') + " pre start + let dir = getcwd() + execute l:cd fnameescape(expand("%:p:h")) + endif + + if has_key(l:options, '_start') + call l:options._start() + " remove _start to play nicely with vim (when vim encounters an unexpected + " job option it reports an "E475: invalid argument" error. + unlet l:options._start + endif + + if has('nvim') + let l:input = [] + if has_key(l:options, 'in_io') && l:options.in_io ==# 'file' && !empty(l:options.in_name) + let l:input = readfile(l:options.in_name, 1) + endif + + let job = jobstart(a:cmd, l:options) + + if len(l:input) > 0 + call chansend(job, l:input) + " close stdin to signal that no more bytes will be sent. + call chanclose(job, 'stdin') + endif + else + let job = job_start(a:cmd, l:options) + endif + + if !has_key(l:options, 'cwd') + " post start + execute l:cd fnameescape(dir) + endif + + return job +endfunction + +" s:neooptions returns a dictionary of job options suitable for use by Neovim +" based on a dictionary of job options suitable for Vim8. +function! s:neooptions(options) + let l:options = {} + let l:options['stdout_buf'] = '' + let l:options['stderr_buf'] = '' + + for key in keys(a:options) + if key == 'callback' + let l:options['callback'] = a:options['callback'] + + if !has_key(a:options, 'out_cb') + let l:options['stdout_buffered'] = v:true + + function! s:callback2on_stdout(ch, data, event) dict + let l:data = a:data + let l:data[0] = self.stdout_buf . l:data[0] + let self.stdout_buf = "" + + if l:data[-1] != "" + let self.stdout_buf = l:data[-1] + endif + + let l:data = l:data[:-2] + if len(l:data) == 0 + return + endif + + call self.callback(a:ch, join(l:data, "\n")) + endfunction + let l:options['on_stdout'] = function('s:callback2on_stdout', [], l:options) + endif + + if !has_key(a:options, 'err_cb') + let l:options['stderr_buffered'] = v:true + + function! s:callback2on_stderr(ch, data, event) dict + let l:data = a:data + let l:data[0] = self.stderr_buf . l:data[0] + let self.stderr_buf = "" + + if l:data[-1] != "" + let self.stderr_buf = l:data[-1] + endif + + let l:data = l:data[:-2] + if len(l:data) == 0 + return + endif + + call self.callback(a:ch, join(l:data, "\n")) + endfunction + let l:options['on_stderr'] = function('s:callback2on_stderr', [], l:options) + endif + + continue + endif + + if key == 'out_cb' + let l:options['out_cb'] = a:options['out_cb'] + let l:options['stdout_buffered'] = v:true + function! s:on_stdout(ch, data, event) dict + let l:data = a:data + let l:data[0] = self.stdout_buf . l:data[0] + let self.stdout_buf = "" + + if l:data[-1] != "" + let self.stdout_buf = l:data[-1] + endif + + let l:data = l:data[:-2] + if len(l:data) == 0 + return + endif + + call self.out_cb(a:ch, join(l:data, "\n")) + endfunction + let l:options['on_stdout'] = function('s:on_stdout', [], l:options) + + continue + endif + + if key == 'err_cb' + let l:options['err_cb'] = a:options['err_cb'] + let l:options['stderr_buffered'] = v:true + function! s:on_stderr(ch, data, event) dict + let l:data = a:data + let l:data[0] = self.stderr_buf . l:data[0] + let self.stderr_buf = "" + + if l:data[-1] != "" + let self.stderr_buf = l:data[-1] + endif + + let l:data = l:data[:-2] + if len(l:data) == 0 + return + endif + + call self.err_cb(a:ch, join(l:data, "\n")) + endfunction + let l:options['on_stderr'] = function('s:on_stderr', [], l:options) + + continue + endif + + if key == 'exit_cb' + let l:options['exit_cb'] = a:options['exit_cb'] + function! s:on_exit(jobid, exitval, event) dict + call self.exit_cb(a:jobid, a:exitval) + endfunction + let l:options['on_exit'] = function('s:on_exit', [], l:options) + + continue + endif + + if key == 'close_cb' + continue + endif + + endfor + return l:options +endfunction + + " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/jobcontrol.vim b/pack/plugins/start/fatih-vim-go/autoload/go/jobcontrol.vim deleted file mode 100644 index 57eab32c..00000000 --- a/pack/plugins/start/fatih-vim-go/autoload/go/jobcontrol.vim +++ /dev/null @@ -1,190 +0,0 @@ -" s:jobs is a global reference to all jobs started with Spawn() or with the -" internal function s:spawn -let s:jobs = {} - -" s:handlers is a global event handlers for all jobs started with Spawn() or -" with the internal function s:spawn -let s:handlers = {} - -" Spawn is a wrapper around s:spawn. It can be executed by other files and -" scripts if needed. Desc defines the description for printing the status -" during the job execution (useful for statusline integration). -function! go#jobcontrol#Spawn(bang, desc, for, args) abort - " autowrite is not enabled for jobs - call go#cmd#autowrite() - - let job = s:spawn(a:bang, a:desc, a:for, a:args) - return job.id -endfunction - -" AddHandler adds a on_exit callback handler and returns the id. -function! go#jobcontrol#AddHandler(handler) abort - let i = len(s:handlers) - while has_key(s:handlers, string(i)) - let i += 1 - break - endwhile - let s:handlers[string(i)] = a:handler - return string(i) -endfunction - -" RemoveHandler removes a callback handler by id. -function! go#jobcontrol#RemoveHandler(id) abort - unlet s:handlers[a:id] -endfunction - -" spawn spawns a go subcommand with the name and arguments with jobstart. Once a -" job is started a reference will be stored inside s:jobs. The job is started -" inside the current files folder. -function! s:spawn(bang, desc, for, args) abort - let status_type = a:args[0] - let status_dir = expand('%:p:h') - let started_at = reltime() - - call go#statusline#Update(status_dir, { - \ 'desc': "current status", - \ 'type': status_type, - \ 'state': "started", - \}) - - let job = { - \ 'desc': a:desc, - \ 'bang': a:bang, - \ 'winnr': winnr(), - \ 'importpath': go#package#ImportPath(), - \ 'state': "RUNNING", - \ 'stderr' : [], - \ 'stdout' : [], - \ 'on_stdout': function('s:on_stdout'), - \ 'on_stderr': function('s:on_stderr'), - \ 'on_exit' : function('s:on_exit'), - \ 'status_type' : status_type, - \ 'status_dir' : status_dir, - \ 'started_at' : started_at, - \ 'for' : a:for, - \ } - - " execute go build in the files directory - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - - " cleanup previous jobs for this file - for jb in values(s:jobs) - if jb.importpath == job.importpath - unlet s:jobs[jb.id] - endif - endfor - - let dir = getcwd() - let jobdir = fnameescape(expand("%:p:h")) - execute cd . jobdir - - " append the subcommand, such as 'build' - let argv = ['go'] + a:args - - " run, forrest, run! - let id = jobstart(argv, job) - let job.id = id - let job.dir = jobdir - let s:jobs[id] = job - - execute cd . fnameescape(dir) - - return job -endfunction - -" on_exit is the exit handler for jobstart(). It handles cleaning up the job -" references and also displaying errors in the quickfix window collected by -" on_stderr handler. If there are no errors and a quickfix window is open, -" it'll be closed. -function! s:on_exit(job_id, exit_status, event) dict abort - let status = { - \ 'desc': 'last status', - \ 'type': self.status_type, - \ 'state': "success", - \ } - - if a:exit_status - let status.state = "failed" - endif - - let elapsed_time = reltimestr(reltime(self.started_at)) - " strip whitespace - let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') - let status.state .= printf(" (%ss)", elapsed_time) - - call go#statusline#Update(self.status_dir, status) - - let std_combined = self.stderr + self.stdout - - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let dir = getcwd() - execute cd self.dir - - call s:callback_handlers_on_exit(s:jobs[a:job_id], a:exit_status, std_combined) - - let l:listtype = go#list#Type(self.for) - if a:exit_status == 0 - call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) - - let self.state = "SUCCESS" - - if get(g:, 'go_echo_command_info', 1) - call go#util#EchoSuccess("[" . self.status_type . "] SUCCESS") - endif - - execute cd . fnameescape(dir) - return - endif - - let self.state = "FAILED" - - if get(g:, 'go_echo_command_info', 1) - call go#util#EchoError("[" . self.status_type . "] FAILED") - endif - - let errors = go#tool#ParseErrors(std_combined) - let errors = go#tool#FilterValids(errors) - - execute cd . fnameescape(dir) - - if !len(errors) - " failed to parse errors, output the original content - call go#util#EchoError(std_combined[0]) - return - endif - - " if we are still in the same windows show the list - if self.winnr == winnr() - call go#list#Populate(l:listtype, errors, self.desc) - call go#list#Window(l:listtype, len(errors)) - if !empty(errors) && !self.bang - call go#list#JumpToFirst(l:listtype) - endif - endif -endfunction - -" callback_handlers_on_exit runs all handlers for job on exit event. -function! s:callback_handlers_on_exit(job, exit_status, data) abort - if empty(s:handlers) - return - endif - - for s:handler in values(s:handlers) - call s:handler(a:job, a:exit_status, a:data) - endfor -endfunction - -" on_stdout is the stdout handler for jobstart(). It collects the output of -" stderr and stores them to the jobs internal stdout list. -function! s:on_stdout(job_id, data, event) dict abort - call extend(self.stdout, a:data) -endfunction - -" on_stderr is the stderr handler for jobstart(). It collects the output of -" stderr and stores them to the jobs internal stderr list. -function! s:on_stderr(job_id, data, event) dict abort - call extend(self.stderr, a:data) -endfunction - -" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/keyify.vim b/pack/plugins/start/fatih-vim-go/autoload/go/keyify.vim index 23ca0f39..a77e8e24 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/keyify.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/keyify.vim @@ -1,20 +1,20 @@ function! go#keyify#Keyify() - let bin_path = go#path#CheckBinPath("keyify") - let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') - - if empty(bin_path) || !exists('*json_decode') + " Needs: https://github.com/dominikh/go-tools/pull/272 + "\ '-tags', go#config#BuildTags(), + let l:cmd = ['keyify', + \ '-json', + \ printf('%s:#%s', fnamemodify(expand('%'), ':p:gs?\\?/?'), go#util#OffsetCursor())] + + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err + call go#util#EchoError("non-zero exit code: " . l:out) return endif - - " Get result of command as json, that contains `start`, `end` and `replacement` - let command = printf("%s -json %s:#%s", go#util#Shellescape(bin_path), - \ go#util#Shellescape(fname), go#util#OffsetCursor()) - let output = go#util#System(command) - silent! let result = json_decode(output) + silent! let result = json_decode(l:out) " We want to output the error message in case the result isn't a JSON if type(result) != type({}) - call go#util#EchoError(s:chomp(output)) + call go#util#EchoError(s:chomp(l:out)) return endif diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/lint.vim b/pack/plugins/start/fatih-vim-go/autoload/go/lint.vim index f810237e..ee159050 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/lint.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/lint.vim @@ -1,27 +1,3 @@ -if !exists("g:go_metalinter_command") - let g:go_metalinter_command = "" -endif - -if !exists("g:go_metalinter_autosave_enabled") - let g:go_metalinter_autosave_enabled = ['vet', 'golint'] -endif - -if !exists("g:go_metalinter_enabled") - let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck'] -endif - -if !exists("g:go_metalinter_disabled") - let g:go_metalinter_disabled = [] -endif - -if !exists("g:go_golint_bin") - let g:go_golint_bin = "golint" -endif - -if !exists("g:go_errcheck_bin") - let g:go_errcheck_bin = "errcheck" -endif - function! go#lint#Gometa(autosave, ...) abort if a:0 == 0 let goargs = [expand('%:p:h')] @@ -37,14 +13,14 @@ function! go#lint#Gometa(autosave, ...) abort let cmd = [bin_path] let cmd += ["--disable-all"] - if a:autosave || empty(g:go_metalinter_command) + if a:autosave || empty(go#config#MetalinterCommand()) " linters - let linters = a:autosave ? g:go_metalinter_autosave_enabled : g:go_metalinter_enabled + let linters = a:autosave ? go#config#MetalinterAutosaveEnabled() : go#config#MetalinterEnabled() for linter in linters let cmd += ["--enable=".linter] endfor - for linter in g:go_metalinter_disabled + for linter in go#config#MetalinterDisabled() let cmd += ["--disable=".linter] endfor @@ -56,7 +32,7 @@ function! go#lint#Gometa(autosave, ...) abort let cmd += ["--tests"] else " the user wants something else, let us use it. - let cmd += split(g:go_metalinter_command, " ") + let cmd += split(go#config#MetalinterCommand(), " ") endif if a:autosave @@ -68,33 +44,19 @@ function! go#lint#Gometa(autosave, ...) abort let cmd += [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))] endif - " gometalinter has a default deadline of 5 seconds. - " - " For async mode (s:lint_job), we want to override the default deadline only - " if we have a deadline configured. - " - " For sync mode (go#util#System), always explicitly pass the 5 seconds - " deadline if there is no other deadline configured. If a deadline is - " configured, then use it. - " Call gometalinter asynchronously. - if go#util#has_job() && has('lambda') - let deadline = get(g:, 'go_metalinter_deadline', 0) - if deadline != 0 - let cmd += ["--deadline=" . deadline] - endif + let deadline = go#config#MetalinterDeadline() + if deadline != '' + let cmd += ["--deadline=" . deadline] + endif - let cmd += goargs + let cmd += goargs + if go#util#has_job() && has('lambda') call s:lint_job({'cmd': cmd}, a:autosave) return endif - " We're calling gometalinter synchronously. - let cmd += ["--deadline=" . get(g:, 'go_metalinter_deadline', "5s")] - - let cmd += goargs - let [l:out, l:err] = go#util#Exec(cmd) if a:autosave @@ -105,7 +67,6 @@ function! go#lint#Gometa(autosave, ...) abort if l:err == 0 call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None else " GoMetaLinter can output one of the two, so we look for both: @@ -129,27 +90,21 @@ endfunction " Golint calls 'golint' on the current directory. Any warnings are populated in " the location list function! go#lint#Golint(...) abort - let bin_path = go#path#CheckBinPath(g:go_golint_bin) - if empty(bin_path) - return - endif - let bin_path = go#util#Shellescape(bin_path) - if a:0 == 0 - let out = go#util#System(bin_path . " " . go#util#Shellescape(go#package#ImportPath())) + let [l:out, l:err] = go#util#Exec([go#config#GolintBin(), go#package#ImportPath()]) else - let out = go#util#System(bin_path . " " . go#util#Shelljoin(a:000)) + let [l:out, l:err] = go#util#Exec([go#config#GolintBin()] + a:000) endif - if empty(out) - echon "vim-go: " | echohl Function | echon "[lint] PASS" | echohl None + if empty(l:out) + call go#util#EchoSuccess('[lint] PASS') return endif let l:listtype = go#list#Type("GoLint") - call go#list#Parse(l:listtype, out) - let errors = go#list#Get(l:listtype) - call go#list#Window(l:listtype, len(errors)) + call go#list#Parse(l:listtype, l:out, "GoLint") + let l:errors = go#list#Get(l:listtype) + call go#list#Window(l:listtype, len(l:errors)) call go#list#JumpToFirst(l:listtype) endfunction @@ -157,26 +112,28 @@ endfunction " the location list function! go#lint#Vet(bang, ...) abort call go#cmd#autowrite() - echon "vim-go: " | echohl Identifier | echon "calling vet..." | echohl None + + call go#util#EchoProgress('calling vet...') + if a:0 == 0 - let out = go#util#System('go vet ' . go#util#Shellescape(go#package#ImportPath())) + let [l:out, l:err] = go#util#Exec(['go', 'vet', go#package#ImportPath()]) else - let out = go#util#System('go tool vet ' . go#util#Shelljoin(a:000)) + let [l:out, l:err] = go#util#Exec(['go', 'tool', 'vet'] + a:000) endif let l:listtype = go#list#Type("GoVet") - if go#util#ShellError() != 0 - let errors = go#tool#ParseErrors(split(out, '\n')) - call go#list#Populate(l:listtype, errors, 'Vet') + if l:err != 0 + let errorformat = "%-Gexit status %\\d%\\+," . &errorformat + call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet") + let errors = go#list#Get(l:listtype) call go#list#Window(l:listtype, len(errors)) if !empty(errors) && !a:bang call go#list#JumpToFirst(l:listtype) endif - echon "vim-go: " | echohl ErrorMsg | echon "[vet] FAIL" | echohl None + call go#util#EchoError('[vet] FAIL') else call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) - redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None + call go#util#EchoSuccess('[vet] PASS') endif endfunction @@ -184,42 +141,34 @@ endfunction " the location list function! go#lint#Errcheck(...) abort if a:0 == 0 - let import_path = go#package#ImportPath() + let l:import_path = go#package#ImportPath() if import_path == -1 - echohl Error | echomsg "vim-go: package is not inside GOPATH src" | echohl None + call go#util#EchoError('package is not inside GOPATH src') return endif else - let import_path = go#util#Shelljoin(a:000) + let l:import_path = join(a:000, ' ') endif - let bin_path = go#path#CheckBinPath(g:go_errcheck_bin) - if empty(bin_path) - return - endif - - echon "vim-go: " | echohl Identifier | echon "errcheck analysing ..." | echohl None + call go#util#EchoProgress('[errcheck] analysing ...') redraw - let command = go#util#Shellescape(bin_path) . ' -abspath ' . import_path - let out = go#tool#ExecuteInDir(command) + let [l:out, l:err] = go#util#Exec([go#config#ErrcheckBin(), '-abspath', l:import_path]) let l:listtype = go#list#Type("GoErrCheck") - if go#util#ShellError() != 0 + if l:err != 0 let errformat = "%f:%l:%c:\ %m, %f:%l:%c\ %#%m" " Parse and populate our location list call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'Errcheck') - let errors = go#list#Get(l:listtype) - if empty(errors) - echohl Error | echomsg "GoErrCheck returned error" | echohl None - echo out + let l:errors = go#list#Get(l:listtype) + if empty(l:errors) + call go#util#EchoError(l:out) return endif if !empty(errors) - echohl Error | echomsg "GoErrCheck found errors" | echohl None call go#list#Populate(l:listtype, errors, 'Errcheck') call go#list#Window(l:listtype, len(errors)) if !empty(errors) @@ -228,28 +177,34 @@ function! go#lint#Errcheck(...) abort endif else call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) - echon "vim-go: " | echohl Function | echon "[errcheck] PASS" | echohl None + call go#util#EchoSuccess('[errcheck] PASS') endif - endfunction function! go#lint#ToggleMetaLinterAutoSave() abort - if get(g:, "go_metalinter_autosave", 0) - let g:go_metalinter_autosave = 0 + if go#config#MetalinterAutosave() + call go#config#SetMetalinterAutosave(0) call go#util#EchoProgress("auto metalinter disabled") return end - let g:go_metalinter_autosave = 1 + call go#config#SetMetalinterAutosave(1) call go#util#EchoProgress("auto metalinter enabled") endfunction function! s:lint_job(args, autosave) - let status_dir = expand('%:p:h') - let started_at = reltime() - - call go#statusline#Update(status_dir, { + let state = { + \ 'status_dir': expand('%:p:h'), + \ 'started_at': reltime(), + \ 'messages': [], + \ 'exited': 0, + \ 'closed': 0, + \ 'exit_status': 0, + \ 'winid': win_getid(winnr()), + \ 'autosave': a:autosave + \ } + + call go#statusline#Update(state.status_dir, { \ 'desc': "current status", \ 'type': "gometalinter", \ 'state': "analysing", @@ -259,26 +214,18 @@ function! s:lint_job(args, autosave) call go#cmd#autowrite() if a:autosave - let l:listtype = go#list#Type("GoMetaLinterAutoSave") + let state.listtype = go#list#Type("GoMetaLinterAutoSave") else - let l:listtype = go#list#Type("GoMetaLinter") + let state.listtype = go#list#Type("GoMetaLinter") endif - let l:errformat = '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m' - - let l:messages = [] - let l:exited = 0 - let l:closed = 0 - let l:exit_status = 0 - let l:winnr = winnr() - - function! s:callback(chan, msg) closure - call add(messages, a:msg) + function! s:callback(chan, msg) dict closure + call add(self.messages, a:msg) endfunction - function! s:exit_cb(job, exitval) closure - let exited = 1 - let exit_status = a:exitval + function! s:exit_cb(job, exitval) dict + let self.exited = 1 + let self.exit_status = a:exitval let status = { \ 'desc': 'last status', @@ -290,55 +237,67 @@ function! s:lint_job(args, autosave) let status.state = "failed" endif - let elapsed_time = reltimestr(reltime(started_at)) + let elapsed_time = reltimestr(reltime(self.started_at)) " strip whitespace let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') let status.state .= printf(" (%ss)", elapsed_time) - call go#statusline#Update(status_dir, status) + call go#statusline#Update(self.status_dir, status) - if closed - call s:show_errors() + if self.closed + call self.show_errors() endif endfunction - function! s:close_cb(ch) closure - let closed = 1 + function! s:close_cb(ch) dict + let self.closed = 1 - if exited - call s:show_errors() + if self.exited + call self.show_errors() endif endfunction + function state.show_errors() + let l:winid = win_getid(winnr()) - function! s:show_errors() closure " make sure the current window is the window from which gometalinter was " run when the listtype is locationlist so that the location list for the " correct window will be populated. - if l:listtype == 'locationlist' - exe l:winnr . "wincmd w" + if self.listtype == 'locationlist' + call win_gotoid(self.winid) endif let l:errorformat = '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m' - call go#list#ParseFormat(l:listtype, l:errorformat, messages, 'GoMetaLinter') - - let errors = go#list#Get(l:listtype) - call go#list#Window(l:listtype, len(errors)) + call go#list#ParseFormat(self.listtype, l:errorformat, self.messages, 'GoMetaLinter') + + let errors = go#list#Get(self.listtype) + call go#list#Window(self.listtype, len(errors)) + + " move to the window that was active before processing the errors, because + " the user may have moved around within the window or even moved to a + " different window since saving. Moving back to current window as of the + " start of this function avoids the perception that the quickfix window + " steals focus when linting takes a while. + if self.autosave + call win_gotoid(self.winid) + endif - if get(g:, 'go_echo_command_info', 1) + if go#config#EchoCommandInfo() call go#util#EchoSuccess("linting finished") endif endfunction + " explicitly bind the callbacks to state so that self within them always + " refers to state. See :help Partial for more information. let start_options = { - \ 'callback': funcref("s:callback"), - \ 'exit_cb': funcref("s:exit_cb"), - \ 'close_cb': funcref("s:close_cb"), + \ 'callback': funcref("s:callback", [], state), + \ 'exit_cb': funcref("s:exit_cb", [], state), + \ 'close_cb': funcref("s:close_cb", [], state), \ } call job_start(a:args.cmd, start_options) - if get(g:, 'go_echo_command_info', 1) + if go#config#EchoCommandInfo() call go#util#EchoProgress("linting started ...") endif endfunction diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/lint_test.vim b/pack/plugins/start/fatih-vim-go/autoload/go/lint_test.vim index 06c48785..7432e748 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/lint_test.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/lint_test.vim @@ -3,7 +3,7 @@ func! Test_Gometa() abort silent exe 'e ' . $GOPATH . '/src/lint/lint.go' let expected = [ - \ {'lnum': 5, 'bufnr': 3, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'} + \ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'} \ ] " clear the quickfix lists @@ -12,11 +12,10 @@ func! Test_Gometa() abort " call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will " be autoloaded and the default for g:go_metalinter_enabled will be set so " we can capture it to restore it after the test is run. - call go#lint#ToggleMetaLinterAutoSave() + silent call go#lint#ToggleMetaLinterAutoSave() " And restore it back to its previous value - call go#lint#ToggleMetaLinterAutoSave() + silent call go#lint#ToggleMetaLinterAutoSave() - let orig_go_metalinter_enabled = g:go_metalinter_enabled let g:go_metalinter_enabled = ['golint'] call go#lint#Gometa(0, $GOPATH . '/src/foo') @@ -29,7 +28,7 @@ func! Test_Gometa() abort endwhile call gotest#assert_quickfix(actual, expected) - let g:go_metalinter_enabled = orig_go_metalinter_enabled + unlet g:go_metalinter_enabled endfunc func! Test_GometaWithDisabled() abort @@ -37,7 +36,7 @@ func! Test_GometaWithDisabled() abort silent exe 'e ' . $GOPATH . '/src/lint/lint.go' let expected = [ - \ {'lnum': 5, 'bufnr': 3, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'} + \ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'} \ ] " clear the quickfix lists @@ -46,11 +45,10 @@ func! Test_GometaWithDisabled() abort " call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will " be autoloaded and the default for g:go_metalinter_disabled will be set so " we can capture it to restore it after the test is run. - call go#lint#ToggleMetaLinterAutoSave() + silent call go#lint#ToggleMetaLinterAutoSave() " And restore it back to its previous value - call go#lint#ToggleMetaLinterAutoSave() + silent call go#lint#ToggleMetaLinterAutoSave() - let orig_go_metalinter_disabled = g:go_metalinter_disabled let g:go_metalinter_disabled = ['vet'] call go#lint#Gometa(0, $GOPATH . '/src/foo') @@ -63,7 +61,7 @@ func! Test_GometaWithDisabled() abort endwhile call gotest#assert_quickfix(actual, expected) - let g:go_metalinter_disabled = orig_go_metalinter_disabled + unlet g:go_metalinter_disabled endfunc func! Test_GometaAutoSave() abort @@ -71,7 +69,7 @@ func! Test_GometaAutoSave() abort silent exe 'e ' . $GOPATH . '/src/lint/lint.go' let expected = [ - \ {'lnum': 5, 'bufnr': 2, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'} + \ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'} \ ] let winnr = winnr() @@ -82,11 +80,10 @@ func! Test_GometaAutoSave() abort " call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will " be autoloaded and the default for g:go_metalinter_autosave_enabled will be " set so we can capture it to restore it after the test is run. - call go#lint#ToggleMetaLinterAutoSave() + silent call go#lint#ToggleMetaLinterAutoSave() " And restore it back to its previous value - call go#lint#ToggleMetaLinterAutoSave() + silent call go#lint#ToggleMetaLinterAutoSave() - let orig_go_metalinter_autosave_enabled = g:go_metalinter_autosave_enabled let g:go_metalinter_autosave_enabled = ['golint'] call go#lint#Gometa(1) @@ -99,7 +96,34 @@ func! Test_GometaAutoSave() abort endwhile call gotest#assert_quickfix(actual, expected) - let g:go_metalinter_autosave_enabled = orig_go_metalinter_autosave_enabled + unlet g:go_metalinter_autosave_enabled +endfunc + +func! Test_Vet() + let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint' + silent exe 'e ' . $GOPATH . '/src/vet/vet.go' + compiler go + + let expected = [ + \ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', + \ 'text': 'Printf format %d has arg str of wrong type string'} + \ ] + + let winnr = winnr() + + " clear the location lists + call setqflist([], 'r') + + call go#lint#Vet(1) + + let actual = getqflist() + let start = reltime() + while len(actual) == 0 && reltimefloat(reltime(start)) < 10 + sleep 100m + let actual = getqflist() + endwhile + + call gotest#assert_quickfix(actual, expected) endfunc " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/list.vim b/pack/plugins/start/fatih-vim-go/autoload/go/list.vim index 38a65faf..56c5c144 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/list.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/list.vim @@ -1,11 +1,3 @@ -if !exists("g:go_list_type") - let g:go_list_type = "" -endif - -if !exists("g:go_list_type_commands") - let g:go_list_type_commands = {} -endif - " Window opens the list with the given height up to 10 lines maximum. " Otherwise g:go_loclist_height is used. " @@ -18,18 +10,11 @@ function! go#list#Window(listtype, ...) abort " location list increases/decreases, cwindow will not resize when a new " updated height is passed. lopen in the other hand resizes the screen. if !a:0 || a:1 == 0 - let autoclose_window = get(g:, 'go_list_autoclose', 1) - if autoclose_window - if a:listtype == "locationlist" - lclose - else - cclose - endif - endif + call go#list#Close(a:listtype) return endif - let height = get(g:, "go_list_height", 0) + let height = go#config#ListHeight() if height == 0 " prevent creating a large location height for a large set of numbers if a:1 > 10 @@ -79,13 +64,7 @@ function! go#list#ParseFormat(listtype, errformat, items, title) abort " parse and populate the location list let &errorformat = a:errformat try - if a:listtype == "locationlist" - lgetexpr a:items - if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': a:title}) | endif - else - cgetexpr a:items - if has("patch-7.4.2200") | call setqflist([], 'a', {'title': a:title}) | endif - endif + call go#list#Parse(a:listtype, a:items, a:title) finally "restore back let &errorformat = old_errorformat @@ -94,11 +73,13 @@ endfunction " Parse parses the given items based on the global errorformat and " populates the list. -function! go#list#Parse(listtype, items) abort +function! go#list#Parse(listtype, items, title) abort if a:listtype == "locationlist" lgetexpr a:items + if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': a:title}) | endif else cgetexpr a:items + if has("patch-7.4.2200") | call setqflist([], 'a', {'title': a:title}) | endif endif endfunction @@ -111,23 +92,38 @@ function! go#list#JumpToFirst(listtype) abort endif endfunction -" Clean cleans the location list +" Clean cleans and closes the location list function! go#list#Clean(listtype) abort if a:listtype == "locationlist" lex [] else cex [] endif + + call go#list#Close(a:listtype) +endfunction + +" Close closes the location list +function! go#list#Close(listtype) abort + let autoclose_window = go#config#ListAutoclose() + if !autoclose_window + return + endif + + if a:listtype == "locationlist" + lclose + else + cclose + endif endfunction function! s:listtype(listtype) abort - if g:go_list_type == "locationlist" - return "locationlist" - elseif g:go_list_type == "quickfix" - return "quickfix" + let listtype = go#config#ListType() + if empty(listtype) + return a:listtype endif - return a:listtype + return listtype endfunction " s:default_list_type_commands is the defaults that will be used for each of @@ -164,7 +160,7 @@ function! go#list#Type(for) abort let l:listtype = "quickfix" endif - return get(g:go_list_type_commands, a:for, l:listtype) + return get(go#config#ListTypeCommands(), a:for, l:listtype) endfunction " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/package.vim b/pack/plugins/start/fatih-vim-go/autoload/go/package.vim index 940423f0..0edfc5c3 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/package.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/package.vim @@ -54,10 +54,16 @@ function! go#package#Paths() abort return dirs endfunction +let s:import_paths = {} " ImportPath returns the import path in the current directory it was executed function! go#package#ImportPath() abort - let out = go#tool#ExecuteInDir("go list") - if go#util#ShellError() != 0 + let dir = expand("%:p:h") + if has_key(s:import_paths, dir) + return s:import_paths[dir] + endif + + let [l:out, l:err] = go#tool#ExecuteInDir(['go', 'list']) + if l:err != 0 return -1 endif @@ -69,6 +75,8 @@ function! go#package#ImportPath() abort return -1 endif + let s:import_paths[dir] = import_path + return import_path endfunction @@ -99,10 +107,11 @@ function! go#package#FromPath(arg) abort endfunction function! go#package#CompleteMembers(package, member) abort - silent! let content = go#util#System('godoc ' . a:package) - if go#util#ShellError() || !len(content) + let [l:content, l:err] = go#util#Exec(['godoc', a:package]) + if l:err || !len(content) return [] endif + let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'") try let mx1 = '^\s\+\(\S+\)\s\+=\s\+.*' diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/path.vim b/pack/plugins/start/fatih-vim-go/autoload/go/path.vim index 49d647c6..5c6a4395 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/path.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/path.vim @@ -121,13 +121,14 @@ endfunction " BinPath returns the binary path of installed go tools. function! go#path#BinPath() abort - let bin_path = "" + let bin_path = go#config#BinPath() + if bin_path != "" + return bin_path + endif " check if our global custom path is set, if not check if $GOBIN is set so " we can use it, otherwise use default GOPATH - if exists("g:go_bin_path") - let bin_path = g:go_bin_path - elseif $GOBIN != "" + if $GOBIN != "" let bin_path = $GOBIN else let go_paths = split(go#path#Default(), go#util#PathListSep()) @@ -145,7 +146,8 @@ endfunction function! go#path#CheckBinPath(binpath) abort " remove whitespaces if user applied something like 'goimports ' let binpath = substitute(a:binpath, '^\s*\(.\{-}\)\s*$', '\1', '') - " save off original path + + " save original path let old_path = $PATH " check if we have an appropriate bin_path diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/play.vim b/pack/plugins/start/fatih-vim-go/autoload/go/play.vim index 2cf5009d..75aa55f4 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/play.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/play.vim @@ -1,8 +1,3 @@ -if !exists("g:go_play_open_browser") - let g:go_play_open_browser = 1 -endif - - function! go#play#Share(count, line1, line2) abort if !executable('curl') echohl ErrorMsg | echomsg "vim-go: require 'curl' command" | echohl None @@ -13,15 +8,16 @@ function! go#play#Share(count, line1, line2) abort let share_file = tempname() call writefile(split(content, "\n"), share_file, "b") - let command = "curl -s -X POST https://play.golang.org/share --data-binary '@".share_file."'" - let snippet_id = go#util#System(command) + let l:cmd = ['curl', '-s', '-X', 'POST', 'https://play.golang.org/share', + \ '--data-binary', '@' . l:share_file] + let [l:snippet_id, l:err] = go#util#Exec(l:cmd) " we can remove the temp file because it's now posted. call delete(share_file) - if go#util#ShellError() != 0 - echo 'A error has occurred. Run this command to see what the problem is:' - echo command + if l:err != 0 + echom 'A error has occurred. Run this command to see what the problem is:' + echom go#util#Shelljoin(l:cmd) return endif @@ -34,7 +30,7 @@ function! go#play#Share(count, line1, line2) abort let @+ = url endif - if g:go_play_open_browser != 0 + if go#config#PlayOpenBrowser() call go#tool#OpenBrowser(url) endif diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/rename.vim b/pack/plugins/start/fatih-vim-go/autoload/go/rename.vim index 6b61751c..1de4ea31 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/rename.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/rename.vim @@ -1,26 +1,10 @@ -if !exists("g:go_gorename_bin") - let g:go_gorename_bin = "gorename" -endif - -" Set the default value. A value of "1" is a shortcut for this, for -" compatibility reasons. -function! s:default() abort - if !exists("g:go_gorename_prefill") || g:go_gorename_prefill == 1 - let g:go_gorename_prefill = 'expand("") =~# "^[A-Z]"' . - \ '? go#util#pascalcase(expand(""))' . - \ ': go#util#camelcase(expand(""))' - endif -endfunction -call s:default() - function! go#rename#Rename(bang, ...) abort - call s:default() - let to_identifier = "" if a:0 == 0 let ask = printf("vim-go: rename '%s' to: ", expand("")) - if g:go_gorename_prefill != '' - let to_identifier = input(ask, eval(g:go_gorename_prefill)) + let prefill = go#config#GorenamePrefill() + if prefill != '' + let to_identifier = input(ask, eval(prefill)) else let to_identifier = input(ask) endif @@ -33,7 +17,7 @@ function! go#rename#Rename(bang, ...) abort endif " return with a warning if the bin doesn't exist - let bin_path = go#path#CheckBinPath(g:go_gorename_bin) + let bin_path = go#path#CheckBinPath(go#config#GorenameBin()) if empty(bin_path) return endif @@ -41,19 +25,7 @@ function! go#rename#Rename(bang, ...) abort let fname = expand('%:p') let pos = go#util#OffsetCursor() let offset = printf('%s:#%d', fname, pos) - - " no need to escape for job call - let bin_path = go#util#has_job() ? bin_path : shellescape(bin_path) - let offset = go#util#has_job() ? offset : shellescape(offset) - let to_identifier = go#util#has_job() ? to_identifier : shellescape(to_identifier) - - let cmd = [bin_path, "-offset", offset, "-to", to_identifier] - - " check for any tags - if exists('g:go_build_tags') - let tags = get(g:, 'go_build_tags') - call extend(cmd, ["-tags", tags]) - endif + let cmd = [bin_path, "-offset", offset, "-to", to_identifier, '-tags', go#config#BuildTags()] if go#util#has_job() call go#util#EchoProgress(printf("renaming to '%s' ...", to_identifier)) @@ -64,28 +36,27 @@ function! go#rename#Rename(bang, ...) abort return endif - let command = join(cmd, " ") - let out = go#tool#ExecuteInDir(command) - - let splitted = split(out, '\n') - call s:parse_errors(go#util#ShellError(), a:bang, splitted) + let [l:out, l:err] = go#tool#ExecuteInDir(l:cmd) + call s:parse_errors(l:err, a:bang, split(l:out, '\n')) endfunction function s:rename_job(args) - let exited = 0 - let closed = 0 - let exitval = 0 - let messages = [] - - function! s:callback(chan, msg) closure - call add(messages, a:msg) + let state = { + \ 'exited': 0, + \ 'closed': 0, + \ 'exitval': 0, + \ 'messages': [], + \ 'status_dir': expand('%:p:h'), + \ 'bang': a:args.bang + \ } + + function! s:callback(chan, msg) dict + call add(self.messages, a:msg) endfunction - let status_dir = expand('%:p:h') - - function! s:exit_cb(job, exitval) closure - let exited = 1 - let exitval = a:exitval + function! s:exit_cb(job, exitval) dict + let self.exited = 1 + let self.exitval = a:exitval let status = { \ 'desc': 'last status', @@ -97,28 +68,30 @@ function s:rename_job(args) let status.state = "failed" endif - call go#statusline#Update(status_dir, status) + call go#statusline#Update(self.status_dir, status) - if closed - call s:parse_errors(a:exitval, a:args.bang, messages) + if self.closed + call s:parse_errors(self.exitval, self.bang, self.messages) endif endfunction - function! s:close_cb(ch) closure - let closed = 1 + function! s:close_cb(ch) dict + let self.closed = 1 - if exited - call s:parse_errors(exitval, a:args.bang, messages) + if self.exited + call s:parse_errors(self.exitval, self.bang, self.messages) endif endfunction + " explicitly bind the callbacks to state so that self within them always + " refers to state. See :help Partial for more information. let start_options = { - \ 'callback': funcref("s:callback"), - \ 'exit_cb': funcref("s:exit_cb"), - \ 'close_cb': funcref("s:close_cb"), + \ 'callback': funcref("s:callback", [], state), + \ 'exit_cb': funcref("s:exit_cb", [], state), + \ 'close_cb': funcref("s:close_cb", [], state), \ } - call go#statusline#Update(status_dir, { + call go#statusline#Update(state.status_dir, { \ 'desc': "current status", \ 'type': "gorename", \ 'state': "started", @@ -156,7 +129,6 @@ function s:parse_errors(exit_val, bang, out) " strip out newline on the end that gorename puts. If we don't remove, it " will trigger the 'Hit ENTER to continue' prompt call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) call go#util#EchoSuccess(a:out[0]) " refresh the buffer so we can see the new content diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/statusline.vim b/pack/plugins/start/fatih-vim-go/autoload/go/statusline.vim index 6a4f0bf0..e98d7330 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/statusline.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/statusline.vim @@ -26,7 +26,7 @@ function! go#statusline#Show() abort " lazy initialiation of the cleaner if !s:timer_id " clean every 60 seconds all statuses - let interval = get(g:, 'go_statusline_duration', 60000) + let interval = go#config#StatuslineDuration() let s:timer_id = timer_start(interval, function('go#statusline#Clear'), {'repeat': -1}) endif diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/tags.vim b/pack/plugins/start/fatih-vim-go/autoload/go/tags.vim index af030d21..f9c0a6ad 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/tags.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/tags.vim @@ -31,24 +31,22 @@ function! go#tags#run(start, end, offset, mode, fname, test_mode, ...) abort let args["modified"] = 1 endif - let result = s:create_cmd(args) + let l:result = s:create_cmd(args) if has_key(result, 'err') call go#util#EchoError(result.err) return -1 endif - let command = join(result.cmd, " ") - if &modified let filename = expand("%:p:gs!\\!/!") let content = join(go#util#GetLines(), "\n") let in = filename . "\n" . strlen(content) . "\n" . content - let out = go#util#System(command, in) + let [l:out, l:err] = go#util#Exec(l:result.cmd, in) else - let out = go#util#System(command) + let [l:out, l:err] = go#util#Exec(l:result.cmd) endif - if go#util#ShellError() != 0 + if l:err != 0 call go#util#EchoError(out) return endif @@ -115,19 +113,18 @@ func s:create_cmd(args) abort if empty(bin_path) return {'err': "gomodifytags does not exist"} endif - let bin_path = go#util#Shellescape(bin_path) let l:start = a:args.start let l:end = a:args.end let l:offset = a:args.offset let l:mode = a:args.mode let l:cmd_args = a:args.cmd_args - let l:modifytags_transform = get(g:, 'go_addtags_transform', "snakecase") + let l:modifytags_transform = go#config#AddtagsTransform() " start constructing the command let cmd = [bin_path] call extend(cmd, ["-format", "json"]) - call extend(cmd, ["-file", go#util#Shellescape(a:args.fname)]) + call extend(cmd, ["-file", a:args.fname]) call extend(cmd, ["-transform", l:modifytags_transform]) if has_key(a:args, "modified") diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/template.vim b/pack/plugins/start/fatih-vim-go/autoload/go/template.vim index 11a91aef..437a49df 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/template.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/template.vim @@ -1,7 +1,7 @@ let s:current_file = expand("") function! go#template#create() abort - let l:go_template_use_pkg = get(g:, 'go_template_use_pkg', 0) + let l:go_template_use_pkg = go#config#TemplateUsePkg() let l:root_dir = fnamemodify(s:current_file, ':h:h:h') let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' @@ -19,12 +19,12 @@ function! go#template#create() abort if l:package_name == -1 && l:go_template_use_pkg != 1 let l:filename = fnamemodify(expand("%"), ':t') if l:filename =~ "_test.go$" - let l:template_file = get(g:, 'go_template_test_file', "hello_world_test.go") + let l:template_file = go#config#TemplateTestFile() else - let l:template_file = get(g:, 'go_template_file', "hello_world.go") + let l:template_file = go#config#TemplateFile() endif let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file) - silent exe '0r ' . fnameescape(l:template_path) + silent exe 'keepalt 0r ' . fnameescape(l:template_path) elseif l:package_name == -1 && l:go_template_use_pkg == 1 " cwd is now the dir of the package let l:path = fnamemodify(getcwd(), ':t') @@ -40,13 +40,13 @@ function! go#template#create() abort endfunction function! go#template#ToggleAutoCreate() abort - if get(g:, "go_template_autocreate", 1) - let g:go_template_autocreate = 0 + if go#config#TemplateAutocreate() + call go#config#SetTemplateAutocreate(0) call go#util#EchoProgress("auto template create disabled") return end - let g:go_template_autocreate = 1 + call go#config#SetTemplateAutocreate(1) call go#util#EchoProgress("auto template create enabled") endfunction diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/term.vim b/pack/plugins/start/fatih-vim-go/autoload/go/term.vim index 23ee18c4..a89c5fdc 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/term.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/term.vim @@ -1,25 +1,24 @@ -if has('nvim') && !exists("g:go_term_mode") - let g:go_term_mode = 'vsplit' -endif - -" s:jobs is a global reference to all jobs started with new() -let s:jobs = {} - " new creates a new terminal with the given command. Mode is set based on the " global variable g:go_term_mode, which is by default set to :vsplit function! go#term#new(bang, cmd) abort - return go#term#newmode(a:bang, a:cmd, g:go_term_mode) + return go#term#newmode(a:bang, a:cmd, go#config#TermMode()) endfunction " new creates a new terminal with the given command and window mode. function! go#term#newmode(bang, cmd, mode) abort let mode = a:mode if empty(mode) - let mode = g:go_term_mode + let mode = go#config#TermMode() endif + let state = { + \ 'cmd': a:cmd, + \ 'bang' : a:bang, + \ 'winid': win_getid(winnr()), + \ 'stdout': [] + \ } + " execute go build in the files directory - let l:winnr = winnr() let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let dir = getcwd() @@ -33,30 +32,27 @@ function! go#term#newmode(bang, cmd, mode) abort setlocal noswapfile setlocal nobuflisted + " explicitly bind callbacks to state so that within them, self will always + " refer to state. See :help Partial for more information. + " + " Don't set an on_stderr, because it will be passed the same data as + " on_stdout. See https://github.com/neovim/neovim/issues/2836 let job = { - \ 'stderr' : [], - \ 'stdout' : [], - \ 'bang' : a:bang, - \ 'on_stdout': function('s:on_stdout'), - \ 'on_stderr': function('s:on_stderr'), - \ 'on_exit' : function('s:on_exit'), - \ } + \ 'on_stdout': function('s:on_stdout', [], state), + \ 'on_exit' : function('s:on_exit', [], state), + \ } - let id = termopen(a:cmd, job) + let state.id = termopen(a:cmd, job) + let state.termwinid = win_getid(winnr()) execute cd . fnameescape(dir) - let job.id = id - let job.cmd = a:cmd - startinsert - " resize new term if needed. - let height = get(g:, 'go_term_height', winheight(0)) - let width = get(g:, 'go_term_width', winwidth(0)) - - " we are careful how to resize. for example it's vsplit we don't change - " the height. The below command resizes the buffer + let height = go#config#TermHeight() + let width = go#config#TermWidth() + " Adjust the window width or height depending on whether it's a vertical or + " horizontal split. if mode =~ "vertical" || mode =~ "vsplit" || mode =~ "vnew" exe 'vertical resize ' . width elseif mode =~ "split" || mode =~ "new" @@ -64,77 +60,60 @@ function! go#term#newmode(bang, cmd, mode) abort endif " we also need to resize the pty, so there you go... - call jobresize(id, width, height) - - let s:jobs[id] = job - stopinsert + call jobresize(state.id, width, height) - if l:winnr !=# winnr() - exe l:winnr . "wincmd w" - endif + call win_gotoid(state.winid) - return id + return state.id endfunction function! s:on_stdout(job_id, data, event) dict abort - if !has_key(s:jobs, a:job_id) - return - endif - let job = s:jobs[a:job_id] - - call extend(job.stdout, a:data) -endfunction - -function! s:on_stderr(job_id, data, event) dict abort - if !has_key(s:jobs, a:job_id) - return - endif - let job = s:jobs[a:job_id] - - call extend(job.stderr, a:data) + call extend(self.stdout, a:data) endfunction function! s:on_exit(job_id, exit_status, event) dict abort - if !has_key(s:jobs, a:job_id) - return - endif - let job = s:jobs[a:job_id] - let l:listtype = go#list#Type("_term") " usually there is always output so never branch into this clause - if empty(job.stdout) - call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) - unlet s:jobs[a:job_id] + if empty(self.stdout) + call s:cleanlist(self.winid, l:listtype) return endif - let errors = go#tool#ParseErrors(job.stdout) + let errors = go#tool#ParseErrors(self.stdout) let errors = go#tool#FilterValids(errors) if !empty(errors) - " close terminal we don't need it anymore + " close terminal; we don't need it anymore + call win_gotoid(self.termwinid) close - call go#list#Populate(l:listtype, errors, job.cmd) + call win_gotoid(self.winid) + + let title = self.cmd + if type(title) == v:t_list + let title = join(self.cmd) + endif + call go#list#Populate(l:listtype, errors, title) call go#list#Window(l:listtype, len(errors)) if !self.bang call go#list#JumpToFirst(l:listtype) endif - unlet s:jobs[a:job_id] + return endif - " tests are passing clean the list and close the list. But we only can - " close them from a normal view, so jump back, close the list and then - " again jump back to the terminal - wincmd p - call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) - wincmd p + call s:cleanlist(self.winid, l:listtype) +endfunction - unlet s:jobs[a:job_id] +function! s:cleanlist(winid, listtype) abort + " There are no errors. Clean and close the list. Jump to the window to which + " the location list is attached, close the list, and then jump back to the + " current window. + let winid = win_getid(winnr()) + call win_gotoid(a:winid) + call go#list#Clean(a:listtype) + call win_gotoid(l:winid) endfunction " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/term_test.vim b/pack/plugins/start/fatih-vim-go/autoload/go/term_test.vim new file mode 100644 index 00000000..7a630eef --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/term_test.vim @@ -0,0 +1,50 @@ +func! Test_GoTermNewMode() + if !has('nvim') + return + endif + + try + let l:filename = 'term/term.go' + let l:tmp = gotest#load_fixture(l:filename) + exe 'cd ' . l:tmp . '/src/term' + + let expected = expand('%:p') + + let cmd = "go run ". go#util#Shelljoin(go#tool#Files()) + + set nosplitright + call go#term#newmode(0, cmd, '') + let actual = expand('%:p') + call assert_equal(actual, l:expected) + + finally + call delete(l:tmp, 'rf') + endtry +endfunc + +func! Test_GoTermNewMode_SplitRight() + if !has('nvim') + return + endif + + try + let l:filename = 'term/term.go' + let l:tmp = gotest#load_fixture(l:filename) + exe 'cd ' . l:tmp . '/src/term' + + let expected = expand('%:p') + + let cmd = "go run ". go#util#Shelljoin(go#tool#Files()) + + set splitright + call go#term#newmode(0, cmd, '') + let actual = expand('%:p') + call assert_equal(actual, l:expected) + + finally + call delete(l:tmp, 'rf') + set nosplitright + endtry +endfunc + +" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/cmd/bad.go b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/cmd/bad.go new file mode 100644 index 00000000..a1cc46ea --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/cmd/bad.go @@ -0,0 +1,5 @@ +package main + +func main() { + notafunc() +} diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/lint/src/vet/vet.go b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/lint/src/vet/vet.go new file mode 100644 index 00000000..d3a8e04e --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/lint/src/vet/vet.go @@ -0,0 +1,8 @@ +package main + +import "fmt" + +func main() { + str := "hello world!" + fmt.Printf("%d\n", str) +} diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/term/term.go b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/term/term.go new file mode 100644 index 00000000..73d83e64 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/term/term.go @@ -0,0 +1,5 @@ +package main + +func main() { + println("hello, world") +} diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/test/src/testcompilerror/testcompilerror_test.go b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/test/src/testcompilerror/testcompilerror_test.go new file mode 100644 index 00000000..75fdade0 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/test/src/testcompilerror/testcompilerror_test.go @@ -0,0 +1,11 @@ +package main + +import ( + "io/ioutil" + "testing" +) + +func TestSomething(t *testing.T) { + r := struct{}{} + ioutil.ReadAll(r) +} diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/test/src/veterror/veterror.go b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/test/src/veterror/veterror.go new file mode 100644 index 00000000..8c585f22 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/autoload/go/test-fixtures/test/src/veterror/veterror.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Errorf("%v") +} diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/test.vim b/pack/plugins/start/fatih-vim-go/autoload/go/test.vim index ee36bc01..7d8c5ec6 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/test.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/test.vim @@ -2,7 +2,7 @@ " compile the tests instead of running them (useful to catch errors in the " test files). Any other argument is appended to the final `go test` command. function! go#test#Test(bang, compile, ...) abort - let args = ["test"] + let args = ["test", '-tags', go#config#BuildTags()] " don't run the test, only compile it. Useful to capture and fix errors. if a:compile @@ -10,11 +10,6 @@ function! go#test#Test(bang, compile, ...) abort call extend(args, ["-c", "-o", testfile]) endif - if exists('g:go_build_tags') - let tags = get(g:, 'go_build_tags') - call extend(args, ["-tags", tags]) - endif - if a:0 let goargs = a:000 @@ -24,18 +19,14 @@ function! go#test#Test(bang, compile, ...) abort let goargs = map(copy(a:000), "expand(v:val)") endif - if !(has('nvim') || go#util#has_job()) - let goargs = go#util#Shelllist(goargs, 1) - endif - call extend(args, goargs, 1) else " only add this if no custom flags are passed - let timeout = get(g:, 'go_test_timeout', '10s') + let timeout = go#config#TestTimeout() call add(args, printf("-timeout=%s", timeout)) endif - if get(g:, 'go_echo_command_info', 1) + if go#config#EchoCommandInfo() if a:compile call go#util#EchoProgress("compiling tests ...") else @@ -43,35 +34,29 @@ function! go#test#Test(bang, compile, ...) abort endif endif - if go#util#has_job() + if go#util#has_job() || has('nvim') " use vim's job functionality to call it asynchronously - let job_args = { - \ 'cmd': ['go'] + args, + let job_options = { \ 'bang': a:bang, - \ 'winnr': winnr(), - \ 'dir': getcwd(), - \ 'compile_test': a:compile, - \ 'jobdir': fnameescape(expand("%:p:h")), + \ 'for': 'GoTest', + \ 'statustype': 'test', + \ 'errorformat': s:errorformat(), \ } - call s:test_job(job_args) - return - elseif has('nvim') - " use nvims's job functionality - if get(g:, 'go_term_enabled', 0) - let id = go#term#new(a:bang, ["go"] + args) - else - let id = go#jobcontrol#Spawn(a:bang, "test", "GoTest", args) + if a:compile + let job_options.statustype = 'compile ' . job_options.statustype endif - return id + call s:test_job(['go'] + args, job_options) + return endif call go#cmd#autowrite() redraw - let command = "go " . join(args, ' ') - let out = go#tool#ExecuteInDir(command) + let l:cmd = ['go'] + l:args + + let [l:out, l:err] = go#tool#ExecuteInDir(l:cmd) " TODO(bc): When the output is JSON, the JSON should be run through a " filter to produce lines that are more easily described by errorformat. @@ -81,8 +66,8 @@ function! go#test#Test(bang, compile, ...) abort let dir = getcwd() execute cd fnameescape(expand("%:p:h")) - if go#util#ShellError() != 0 - call go#list#ParseFormat(l:listtype, s:errorformat(), split(out, '\n'), command) + if l:err != 0 + call go#list#ParseFormat(l:listtype, s:errorformat(), split(out, '\n'), l:cmd) let errors = go#list#Get(l:listtype) call go#list#Window(l:listtype, len(errors)) if !empty(errors) && !a:bang @@ -94,7 +79,6 @@ function! go#test#Test(bang, compile, ...) abort call go#util#EchoError("[test] FAIL") else call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) if a:compile call go#util#EchoSuccess("[test] SUCCESS") @@ -131,152 +115,22 @@ function! go#test#Func(bang, ...) abort call extend(args, a:000) else " only add this if no custom flags are passed - let timeout = get(g:, 'go_test_timeout', '10s') + let timeout = go#config#TestTimeout() call add(args, printf("-timeout=%s", timeout)) endif call call('go#test#Test', args) endfunction -function! s:test_job(args) abort - let status_dir = expand('%:p:h') - let started_at = reltime() - - let status = { - \ 'desc': 'current status', - \ 'type': "test", - \ 'state': "started", - \ } - - if a:args.compile_test - let status.state = "compiling" - endif - - call go#statusline#Update(status_dir, status) - +function! s:test_job(cmd, args) abort " autowrite is not enabled for jobs call go#cmd#autowrite() - let l:exited = 0 - let l:closed = 0 - let l:exitval = 0 - let messages = [] - - function! s:callback(chan, msg) closure - call add(messages, a:msg) - endfunction - - function! s:exit_cb(job, exitval) closure - let exited = 1 - let exitval = a:exitval - - let status = { - \ 'desc': 'last status', - \ 'type': "test", - \ 'state': "pass", - \ } - - if a:args.compile_test - let status.state = "success" - endif - - if a:exitval - let status.state = "failed" - endif - - if get(g:, 'go_echo_command_info', 1) - if a:exitval == 0 - if a:args.compile_test - call go#util#EchoSuccess("[test] SUCCESS") - else - call go#util#EchoSuccess("[test] PASS") - endif - else - call go#util#EchoError("[test] FAIL") - endif - endif - - let elapsed_time = reltimestr(reltime(started_at)) - " strip whitespace - let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') - let status.state .= printf(" (%ss)", elapsed_time) - - call go#statusline#Update(status_dir, status) - - if closed - call s:show_errors(a:args, l:exitval, messages) - endif - endfunction - - function! s:close_cb(ch) closure - let closed = 1 - - if exited - call s:show_errors(a:args, l:exitval, messages) - endif - endfunction - - let start_options = { - \ 'callback': funcref("s:callback"), - \ 'exit_cb': funcref("s:exit_cb"), - \ 'close_cb': funcref("s:close_cb"), - \ } - - " pre start - let dir = getcwd() - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let jobdir = fnameescape(expand("%:p:h")) - execute cd . jobdir - - call job_start(a:args.cmd, start_options) - - " post start - execute cd . fnameescape(dir) -endfunction - -" show_errors parses the given list of lines of a 'go test' output and returns -" a quickfix compatible list of errors. It's intended to be used only for go -" test output. -function! s:show_errors(args, exit_val, messages) abort - let l:listtype = go#list#Type("GoTest") - if a:exit_val == 0 - call go#list#Clean(l:listtype) - call go#list#Window(l:listtype) - return - endif - - " TODO(bc): When messages is JSON, the JSON should be run through a - " filter to produce lines that are more easily described by errorformat. - - let l:listtype = go#list#Type("GoTest") - - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - try - execute cd a:args.jobdir - call go#list#ParseFormat(l:listtype, s:errorformat(), a:messages, join(a:args.cmd)) - let errors = go#list#Get(l:listtype) - finally - execute cd . fnameescape(a:args.dir) - endtry - - if !len(errors) - " failed to parse errors, output the original content - call go#util#EchoError(a:messages) - call go#util#EchoError(a:args.dir) - return - endif - - if a:args.winnr == winnr() - call go#list#Window(l:listtype, len(errors)) - if !empty(errors) && !a:args.bang - call go#list#JumpToFirst(l:listtype) - endif - endif + call go#job#Spawn(a:cmd, a:args) endfunction - -let s:efm= "" -let s:go_test_show_name=0 +let s:efm = "" +let s:go_test_show_name = 0 function! s:errorformat() abort " NOTE(arslan): once we get JSON output everything will be easier :). @@ -285,7 +139,7 @@ function! s:errorformat() abort " https://github.com/golang/go/issues/2981. let goroot = go#util#goroot() - let show_name=get(g:, 'go_test_show_name', 0) + let show_name = go#config#TestShowName() if s:efm != "" && s:go_test_show_name == show_name return s:efm endif @@ -296,11 +150,8 @@ function! s:errorformat() abort " (e.g. \%(\)). let indent = '%\\%( %\\)%#' - " match compiler errors - let format = "%f:%l:%c: %m" - " ignore `go test -v` output for starting tests - let format .= ",%-G=== RUN %.%#" + let format = "%-G=== RUN %.%#" " ignore `go test -v` output for passing tests let format .= ",%-G" . indent . "--- PASS: %.%#" @@ -412,6 +263,20 @@ function! s:errorformat() abort let format .= ",%-CFAIL%\\t%.%#" "let format .= ",FAIL%\\t%.%#" + " match compiler errors + " These are very smilar to errors from test output, but lack leading tabs + " for the first line of an error, and subsequent lines only have one tab + " instead of two. + let format .= ",%A%f:%l:%c: %m" + let format .= ",%A%f:%l: %m" + " It would be nice if this weren't necessary, but panic lines from tests are + " prefixed with a single leading tab, making them very similar to 2nd and + " later lines of a multi-line compiler error. Swallow it so that it doesn't + " cause a quickfix entry since the next entry can add a quickfix entry for + " 2nd and later lines of a multi-line compiler error. + let format .= ",%-C%\\tpanic: %.%#" + let format .= ",%G%\\t%m" + " Match and ignore everything else in multi-line messages. let format .= ",%-C%.%#" " Match and ignore everything else not in a multi-line message: diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/test_test.vim b/pack/plugins/start/fatih-vim-go/autoload/go/test_test.vim index d0abc3c6..1bb69bab 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/test_test.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/test_test.vim @@ -69,19 +69,26 @@ func! Test_GoTestShowName() abort let g:go_test_show_name=1 call s:test('showname/showname_test.go', expected) - let g:go_test_show_name=0 + unlet g:go_test_show_name +endfunc + +func! Test_GoTestVet() abort + let expected = [ + \ {'lnum': 6, 'bufnr': 16, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'Errorf format %v reads arg #1, but call has only 0 args'}, + \ ] + call s:test('veterror/veterror.go', expected) +endfunc + +func! Test_GoTestTestCompilerError() abort + let expected = [ + \ {'lnum': 10, 'bufnr': 11, 'col': 16, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'cannot use r (type struct {}) as type io.Reader in argument to ioutil.ReadAll:'}, + \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'struct {} does not implement io.Reader (missing Read method)'} + \ ] + + call s:test('testcompilerror/testcompilerror_test.go', expected) endfunc func! s:test(file, expected, ...) abort - if has('nvim') - " nvim mostly shows test errors correctly, but the the expected errors are - " slightly different; buffer numbers are not the same and stderr doesn't - " seem to be redirected to the job, so the lines from the panic aren't in - " the output to be parsed, and hence are not in the quickfix lists. Once - " those two issues are resolved, this early return should be removed so - " the tests will run for Neovim, too. - return - endif let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/test' silent exe 'e ' . $GOPATH . '/src/' . a:file @@ -94,7 +101,7 @@ func! s:test(file, expected, ...) abort endif " run the tests - call call(function('go#test#Test'), args) + silent call call(function('go#test#Test'), args) let actual = getqflist() let start = reltime() diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/textobj.vim b/pack/plugins/start/fatih-vim-go/autoload/go/textobj.vim index 62a70427..164b7699 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/textobj.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/textobj.vim @@ -1,47 +1,128 @@ -if !exists("g:go_textobj_enabled") - let g:go_textobj_enabled = 1 -endif - -if !exists("g:go_textobj_include_function_doc") - let g:go_textobj_include_function_doc = 1 -endif - -if !exists("g:go_textobj_include_variable") - let g:go_textobj_include_variable = 1 -endif - " ( ) motions " { } motions " s for sentence -" p for parapgrah +" p for paragraph " < > " t for tag -function! go#textobj#Function(mode) abort - let offset = go#util#OffsetCursor() +function! go#textobj#Comment(mode) abort + let l:fname = expand('%:p') + + try + if &modified + let l:tmpname = tempname() + call writefile(go#util#GetLines(), l:tmpname) + let l:fname = l:tmpname + endif + + let l:cmd = ['motion', + \ '-format', 'json', + \ '-file', l:fname, + \ '-offset', go#util#OffsetCursor(), + \ '-mode', 'comment', + \ ] + + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err + call go#util#EchoError(l:out) + return + endif + finally + if exists("l:tmpname") + call delete(l:tmpname) + endif + endtry + + let l:result = json_decode(l:out) + if type(l:result) != 4 || !has_key(l:result, 'comment') + return + endif + + let l:info = l:result.comment + call cursor(l:info.startLine, l:info.startCol) + + " Adjust cursor to exclude start comment markers. Try to be a little bit + " clever when using multi-line '/*' markers. + if a:mode is# 'i' + " trim whitespace so matching below works correctly + let l:line = substitute(getline('.'), '^\s*\(.\{-}\)\s*$', '\1', '') + + " //text + if l:line[:2] is# '// ' + call cursor(l:info.startLine, l:info.startCol+3) + " // text + elseif l:line[:1] is# '//' + call cursor(l:info.startLine, l:info.startCol+2) + " /* + " text + elseif l:line =~# '^/\* *$' + call cursor(l:info.startLine+1, 0) + " /* + " * text + if getline('.')[:2] is# ' * ' + call cursor(l:info.startLine+1, 4) + " /* + " *text + elseif getline('.')[:1] is# ' *' + call cursor(l:info.startLine+1, 3) + endif + " /* text + elseif l:line[:2] is# '/* ' + call cursor(l:info.startLine, l:info.startCol+3) + " /*text + elseif l:line[:1] is# '/*' + call cursor(l:info.startLine, l:info.startCol+2) + endif + endif + + normal! v + + " Exclude trailing newline. + if a:mode is# 'i' + let l:info.endCol -= 1 + endif - let fname = shellescape(expand("%:p")) + call cursor(l:info.endLine, l:info.endCol) + + " Exclude trailing '*/'. + if a:mode is# 'i' + let l:line = getline('.') + " text + " */ + if l:line =~# '^ *\*/$' + call cursor(l:info.endLine - 1, len(getline(l:info.endLine - 1))) + " text */ + elseif l:line[-3:] is# ' */' + call cursor(l:info.endLine, l:info.endCol - 3) + " text*/ + elseif l:line[-2:] is# '*/' + call cursor(l:info.endLine, l:info.endCol - 2) + endif + endif +endfunction + +" Select a function in visual mode. +function! go#textobj#Function(mode) abort + let l:fname = expand("%:p") if &modified - " Write current unsaved buffer to a temp file and use the modified content let l:tmpname = tempname() call writefile(go#util#GetLines(), l:tmpname) - let fname = l:tmpname + let l:fname = l:tmpname endif - let bin_path = go#path#CheckBinPath('motion') - if empty(bin_path) - return - endif + let l:cmd = ['motion', + \ '-format', 'vim', + \ '-file', l:fname, + \ '-offset', go#util#OffsetCursor(), + \ '-mode', 'enclosing', + \ ] - let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset) - let command .= " -mode enclosing" - - if g:go_textobj_include_function_doc - let command .= " -parse-comments" + if go#config#TextobjIncludeFunctionDoc() + let l:cmd += ['-parse-comments'] endif - let out = go#util#System(command) - if go#util#ShellError() != 0 + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err call go#util#EchoError(out) return endif @@ -62,9 +143,9 @@ function! go#textobj#Function(mode) abort if a:mode == 'a' " anonymous functions doesn't have associated doc. Also check if the user " want's to include doc comments for function declarations - if has_key(info, 'doc') && g:go_textobj_include_function_doc + if has_key(info, 'doc') && go#config#TextobjIncludeFunctionDoc() call cursor(info.doc.line, info.doc.col) - elseif info['sig']['name'] == '' && g:go_textobj_include_variable + elseif info['sig']['name'] == '' && go#config#TextobjIncludeVariable() " one liner anonymous functions if info.lbrace.line == info.rbrace.line " jump to first nonblack char, to get the correct column @@ -98,53 +179,30 @@ function! go#textobj#Function(mode) abort call cursor(info.rbrace.line-1, 1) endfunction -function! go#textobj#FunctionJump(mode, direction) abort - " get count of the motion. This should be done before all the normal - " expressions below as those reset this value(because they have zero - " count!). We abstract -1 because the index starts from 0 in motion. - let l:cnt = v:count1 - 1 - - " set context mark so we can jump back with '' or `` - normal! m' - - " select already previously selected visual content and continue from there. - " If it's the first time starts with the visual mode. This is needed so - " after selecting something in visual mode, every consecutive motion - " continues. - if a:mode == 'v' - normal! gv - endif - - let offset = go#util#OffsetCursor() - - let fname = shellescape(expand("%:p")) +" Get the location of the previous or next function. +function! go#textobj#FunctionLocation(direction, cnt) abort + let l:fname = expand("%:p") if &modified " Write current unsaved buffer to a temp file and use the modified content let l:tmpname = tempname() call writefile(go#util#GetLines(), l:tmpname) - let fname = l:tmpname + let l:fname = l:tmpname endif - let bin_path = go#path#CheckBinPath('motion') - if empty(bin_path) - return - endif + let l:cmd = ['motion', + \ '-format', 'vim', + \ '-file', l:fname, + \ '-offset', go#util#OffsetCursor(), + \ '-shift', a:cnt, + \ '-mode', a:direction, + \ ] - let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset) - let command .= ' -shift ' . l:cnt - - if a:direction == 'next' - let command .= ' -mode next' - else " 'prev' - let command .= ' -mode prev' - endif - - if g:go_textobj_include_function_doc - let command .= " -parse-comments" + if go#config#TextobjIncludeFunctionDoc() + let l:cmd += ['-parse-comments'] endif - let out = go#util#System(command) - if go#util#ShellError() != 0 + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err call go#util#EchoError(out) return endif @@ -154,9 +212,33 @@ function! go#textobj#FunctionJump(mode, direction) abort call delete(l:tmpname) endif - " convert our string dict representation into native Vim dictionary type - let result = eval(out) - if type(result) != 4 || !has_key(result, 'fn') + let l:result = json_decode(out) + if type(l:result) != 4 || !has_key(l:result, 'fn') + return 0 + endif + + return l:result +endfunction + +function! go#textobj#FunctionJump(mode, direction) abort + " get count of the motion. This should be done before all the normal + " expressions below as those reset this value(because they have zero + " count!). We abstract -1 because the index starts from 0 in motion. + let l:cnt = v:count1 - 1 + + " set context mark so we can jump back with '' or `` + normal! m' + + " select already previously selected visual content and continue from there. + " If it's the first time starts with the visual mode. This is needed so + " after selecting something in visual mode, every consecutive motion + " continues. + if a:mode == 'v' + normal! gv + endif + + let l:result = go#textobj#FunctionLocation(a:direction, l:cnt) + if l:result is 0 return endif @@ -180,7 +262,7 @@ function! go#textobj#FunctionJump(mode, direction) abort endif if a:mode == 'v' && a:direction == 'prev' - if has_key(info, 'doc') && g:go_textobj_include_function_doc + if has_key(info, 'doc') && go#config#TextobjIncludeFunctionDoc() keepjumps call cursor(info.doc.line, 1) else keepjumps call cursor(info.func.line, 1) diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/tool.vim b/pack/plugins/start/fatih-vim-go/autoload/go/tool.vim index d163600a..a426cfbe 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/tool.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/tool.vim @@ -36,8 +36,8 @@ function! go#tool#Files(...) abort endif endfor - let out = go#tool#ExecuteInDir('go list -f ' . shellescape(combined)) - return split(out, '\n') + let [l:out, l:err] = go#tool#ExecuteInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:combined]) + return split(l:out, '\n') endfunction function! go#tool#Deps() abort @@ -46,9 +46,8 @@ function! go#tool#Deps() abort else let format = "{{range $f := .Deps}}{{$f}}\n{{end}}" endif - let command = 'go list -f '.shellescape(format) - let out = go#tool#ExecuteInDir(command) - return split(out, '\n') + let [l:out, l:err] = go#tool#ExecuteInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:format]) + return split(l:out, '\n') endfunction function! go#tool#Imports() abort @@ -58,16 +57,19 @@ function! go#tool#Imports() abort else let format = "{{range $f := .Imports}}{{$f}}{{printf \"\\n\"}}{{end}}" endif - let command = 'go list -f '.shellescape(format) - let out = go#tool#ExecuteInDir(command) - if go#util#ShellError() != 0 + let [l:out, l:err] = go#tool#ExecuteInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:format]) + if l:err != 0 echo out return imports endif for package_path in split(out, '\n') - let cmd = "go list -f '{{.Name}}' " . shellescape(package_path) - let package_name = substitute(go#tool#ExecuteInDir(cmd), '\n$', '', '') + let [l:out, l:err] = go#tool#ExecuteInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', '{{.Name}}', l:package_path]) + if l:err != 0 + echo out + return imports + endif + let package_name = substitute(l:out, '\n$', '', '') let imports[package_name] = package_path endfor @@ -75,7 +77,7 @@ function! go#tool#Imports() abort endfunction function! go#tool#Info(auto) abort - let l:mode = get(g:, 'go_info_mode', 'gocode') + let l:mode = go#config#InfoMode() if l:mode == 'gocode' call go#complete#Info(a:auto) elseif l:mode == 'guru' @@ -86,9 +88,8 @@ function! go#tool#Info(auto) abort endfunction function! go#tool#PackageName() abort - let command = "go list -f \"{{.Name}}\"" - let out = go#tool#ExecuteInDir(command) - if go#util#ShellError() != 0 + let [l:out, l:err] = go#tool#ExecuteInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', '{{.Name}}']) + if l:err != 0 return -1 endif @@ -117,7 +118,7 @@ function! go#tool#ParseErrors(lines) abort " Preserve indented lines. " This comes up especially with multi-line test output. if match(line, '^\s') >= 0 - call add(errors, {"text": line}) + call add(errors, {"text": substitute(line, '\r$', '', '')}) endif endif endfor @@ -162,67 +163,35 @@ function! go#tool#FilterValids(items) abort endfunction function! go#tool#ExecuteInDir(cmd) abort - " Verify that the directory actually exists. If the directory does not - " exist, then assume that the a:cmd should not be executed. Callers expect - " to check v:shell_error (via go#util#ShellError()), so execute a command - " that will return an error as if a:cmd was run and exited with an error. - " This helps avoid errors when working with plugins that use virtual files - " that don't actually exist on the file system (e.g. vim-fugitive's - " GitDiff). if !isdirectory(expand("%:p:h")) - let [out, err] = go#util#Exec(["false"]) - return '' + return ['', 1] endif let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let dir = getcwd() try execute cd . fnameescape(expand("%:p:h")) - let out = go#util#System(a:cmd) + let [l:out, l:err] = go#util#Exec(a:cmd) finally - execute cd . fnameescape(dir) + execute cd . fnameescape(l:dir) endtry - return out + return [l:out, l:err] endfunction " Exists checks whether the given importpath exists or not. It returns 0 if " the importpath exists under GOPATH. function! go#tool#Exists(importpath) abort - let command = "go list ". a:importpath - let out = go#tool#ExecuteInDir(command) - - if go#util#ShellError() != 0 + let [l:out, l:err] = go#tool#ExecuteInDir(['go', 'list', a:importpath]) + if l:err != 0 return -1 endif return 0 endfunction -" following two functions are from: https://github.com/mattn/gist-vim -" thanks @mattn -function! s:get_browser_command() abort - let go_play_browser_command = get(g:, 'go_play_browser_command', '') - if go_play_browser_command == '' - if go#util#IsWin() - let go_play_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%' - elseif go#util#IsMac() - let go_play_browser_command = 'open %URL%' - elseif executable('xdg-open') - let go_play_browser_command = 'xdg-open %URL%' - elseif executable('firefox') - let go_play_browser_command = 'firefox %URL% &' - elseif executable('chromium') - let go_play_browser_command = 'chromium %URL% &' - else - let go_play_browser_command = '' - endif - endif - return go_play_browser_command -endfunction - function! go#tool#OpenBrowser(url) abort - let cmd = s:get_browser_command() - if len(cmd) == 0 + 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." @@ -230,15 +199,17 @@ function! go#tool#OpenBrowser(url) abort echo a:url return endif - if cmd =~ '^!' - let cmd = substitute(cmd, '%URL%', '\=escape(shellescape(a:url),"#")', 'g') - silent! exec cmd + + " 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 cmd = substitute(cmd, '%URL%', '\=escape(a:url,"#")', 'g') - exec cmd + let l:cmd = substitute(l:cmd, '%URL%', '\=escape(a:url,"#")', 'g') + exec l:cmd else - let cmd = substitute(cmd, '%URL%', '\=shellescape(a:url)', 'g') - call go#util#System(cmd) + let l:cmd = substitute(l:cmd, '%URL%', '\=shellescape(a:url)', 'g') + call go#util#System(l:cmd) endif endfunction diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/tool_test.vim b/pack/plugins/start/fatih-vim-go/autoload/go/tool_test.vim index 1af3b7b9..0d51b148 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/tool_test.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/tool_test.vim @@ -1,8 +1,8 @@ func! Test_ExecuteInDir() abort let l:tmp = gotest#write_file('a/a.go', ['package a']) try - let l:out = go#tool#ExecuteInDir("pwd") - call assert_equal(l:tmp . "/src/a\n", l:out) + let l:out = go#tool#ExecuteInDir(['pwd']) + call assert_equal([l:tmp . "/src/a\n", 0], l:out) finally call delete(l:tmp, 'rf') endtry @@ -13,8 +13,8 @@ func! Test_ExecuteInDir_nodir() abort exe ':e ' . l:tmp . '/new-dir/a' try - let l:out = go#tool#ExecuteInDir("pwd") - call assert_equal('', l:out) + let l:out = go#tool#ExecuteInDir(['pwd']) + call assert_equal(['', 1], l:out) finally call delete(l:tmp, 'rf') endtry diff --git a/pack/plugins/start/fatih-vim-go/autoload/go/util.vim b/pack/plugins/start/fatih-vim-go/autoload/go/util.vim index bf72dadd..dbe39049 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/go/util.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/go/util.vim @@ -48,7 +48,7 @@ function! go#util#IsMac() abort return has('mac') || \ has('macunix') || \ has('gui_macvim') || - \ go#util#System('uname') =~? '^darwin' + \ go#util#Exec(['uname'])[0] =~? '^darwin' endfunction " Checks if using: @@ -59,7 +59,16 @@ function! go#util#IsUsingCygwinShell() return go#util#IsWin() && executable('cygpath') && &shell =~ '.*sh.*' endfunction -function! go#util#has_job() abort +" Check if Vim jobs API is supported. +" +" The (optional) first paramter 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 + " cwd and env parameters to job_start was added in this version. + if a:0 > 0 && a:1 is 1 + return has('job') && has("patch-8.0.0902") + endif + " job was introduced in 7.4.xxx however there are multiple bug fixes and one " of the latest is 8.0.0087 which is required for a stable async API. return has('job') && has("patch-8.0.0087") @@ -93,25 +102,25 @@ endfunction " goarch returns 'go env GOARCH'. This is an internal function and shouldn't " be used. Instead use 'go#util#env("goarch")' function! go#util#goarch() abort - return substitute(go#util#System('go env GOARCH'), '\n', '', 'g') + 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. Instead use 'go#util#env("goos")' function! go#util#goos() abort - return substitute(go#util#System('go env GOOS'), '\n', '', 'g') + 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. Instead use 'go#util#env("goroot")' function! go#util#goroot() abort - return substitute(go#util#System('go env GOROOT'), '\n', '', 'g') + 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. Instead use 'go#util#env("gopath")' function! go#util#gopath() abort - return substitute(go#util#System('go env GOPATH'), '\n', '', 'g') + return substitute(s:exec(['go', 'env', 'GOPATH'])[0], '\n', '', 'g') endfunction function! go#util#osarch() abort @@ -153,16 +162,31 @@ endfunction function! go#util#Exec(cmd, ...) abort if len(a:cmd) == 0 call go#util#EchoError("go#util#Exec() called with empty a:cmd") - return + 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(a:cmd[0]) + let l:bin = go#path#CheckBinPath(l:bin) if empty(l:bin) - return ["", 1] + 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 + +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', [go#util#Shelljoin([l:bin] + a:cmd[1:])] + a:000) + let l:out = call('s:system', [l:cmd] + a:000) return [l:out, go#util#ShellError()] endfunction @@ -259,7 +283,7 @@ endfunction " snippetcase converts the given word to given preferred snippet setting type " case. function! go#util#snippetcase(word) abort - let l:snippet_case = get(g:, 'go_addtags_transform', "snakecase") + let l:snippet_case = go#config#AddtagsTransform() if l:snippet_case == "snakecase" return go#util#snakecase(a:word) elseif l:snippet_case == "camelcase" @@ -330,6 +354,7 @@ 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) @@ -362,7 +387,6 @@ function! go#util#archive() 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; @@ -384,7 +408,7 @@ function! go#util#tempdir(prefix) abort endfor if l:dir == '' - echoerr 'Unable to find directory to store temporary directory in' + call go#util#EchoError('Unable to find directory to store temporary directory in') return endif @@ -395,4 +419,9 @@ function! go#util#tempdir(prefix) abort 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 + " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/autoload/unite/sources/decls.vim b/pack/plugins/start/fatih-vim-go/autoload/unite/sources/decls.vim index b4d5da80..31138085 100644 --- a/pack/plugins/start/fatih-vim-go/autoload/unite/sources/decls.vim +++ b/pack/plugins/start/fatih-vim-go/autoload/unite/sources/decls.vim @@ -28,7 +28,7 @@ function! s:source.gather_candidates(args, context) abort return [] endif - let l:include = get(g:, 'go_decls_includes', 'func,type') + let l:include = go#config#DeclsIncludes() let l:command = printf('%s -format vim -mode decls -include %s -%s %s', l:bin_path, l:include, l:mode, shellescape(l:path)) let l:candidates = [] try diff --git a/pack/plugins/start/fatih-vim-go/doc/tags b/pack/plugins/start/fatih-vim-go/doc/tags index 87f7be05..abea2c8a 100644 --- a/pack/plugins/start/fatih-vim-go/doc/tags +++ b/pack/plugins/start/fatih-vim-go/doc/tags @@ -6,6 +6,9 @@ 'g:go_autodetect_gopath' vim-go.txt /*'g:go_autodetect_gopath'* 'g:go_bin_path' vim-go.txt /*'g:go_bin_path'* 'g:go_build_tags' vim-go.txt /*'g:go_build_tags'* +'g:go_debug' vim-go.txt /*'g:go_debug'* +'g:go_debug_address' vim-go.txt /*'g:go_debug_address'* +'g:go_debug_windows' vim-go.txt /*'g:go_debug_windows'* 'g:go_decls_includes' vim-go.txt /*'g:go_decls_includes'* 'g:go_decls_mode' vim-go.txt /*'g:go_decls_mode'* 'g:go_def_mapping_enabled' vim-go.txt /*'g:go_def_mapping_enabled'* @@ -88,6 +91,13 @@ (go-coverage) vim-go.txt /*(go-coverage)* (go-coverage-clear) vim-go.txt /*(go-coverage-clear)* (go-coverage-toggle) vim-go.txt /*(go-coverage-toggle)* +(go-debug-breakpoint) vim-go.txt /*(go-debug-breakpoint)* +(go-debug-continue) vim-go.txt /*(go-debug-continue)* +(go-debug-next) vim-go.txt /*(go-debug-next)* +(go-debug-print) vim-go.txt /*(go-debug-print)* +(go-debug-step) vim-go.txt /*(go-debug-step)* +(go-debug-stepout) vim-go.txt /*(go-debug-stepout)* +(go-debug-stop) vim-go.txt /*(go-debug-stop)* (go-def) vim-go.txt /*(go-def)* (go-def-pop) vim-go.txt /*(go-def-pop)* (go-def-split) vim-go.txt /*(go-def-split)* @@ -105,6 +115,7 @@ (go-files) vim-go.txt /*(go-files)* (go-freevars) vim-go.txt /*(go-freevars)* (go-generate) vim-go.txt /*(go-generate)* +(go-iferr) vim-go.txt /*(go-iferr)* (go-implements) vim-go.txt /*(go-implements)* (go-import) vim-go.txt /*(go-import)* (go-imports) vim-go.txt /*(go-imports)* @@ -112,6 +123,7 @@ (go-install) vim-go.txt /*(go-install)* (go-lint) vim-go.txt /*(go-lint)* (go-metalinter) vim-go.txt /*(go-metalinter)* +(go-pointsto) vim-go.txt /*(go-pointsto)* (go-referrers) vim-go.txt /*(go-referrers)* (go-rename) vim-go.txt /*(go-rename)* (go-run) vim-go.txt /*(go-run)* @@ -137,6 +149,17 @@ :GoCoverageBrowser vim-go.txt /*:GoCoverageBrowser* :GoCoverageClear vim-go.txt /*:GoCoverageClear* :GoCoverageToggle vim-go.txt /*:GoCoverageToggle* +:GoDebugBreakpoint vim-go.txt /*:GoDebugBreakpoint* +:GoDebugContinue vim-go.txt /*:GoDebugContinue* +:GoDebugNext vim-go.txt /*:GoDebugNext* +:GoDebugPrint vim-go.txt /*:GoDebugPrint* +:GoDebugRestart vim-go.txt /*:GoDebugRestart* +:GoDebugSet vim-go.txt /*:GoDebugSet* +:GoDebugStart vim-go.txt /*:GoDebugStart* +:GoDebugStep vim-go.txt /*:GoDebugStep* +:GoDebugStepOut vim-go.txt /*:GoDebugStepOut* +:GoDebugStop vim-go.txt /*:GoDebugStop* +:GoDebugTest vim-go.txt /*:GoDebugTest* :GoDecls vim-go.txt /*:GoDecls* :GoDeclsDir vim-go.txt /*:GoDeclsDir* :GoDef vim-go.txt /*:GoDef* @@ -156,6 +179,7 @@ :GoFreevars vim-go.txt /*:GoFreevars* :GoGenerate vim-go.txt /*:GoGenerate* :GoGuruScope vim-go.txt /*:GoGuruScope* +:GoIfErr vim-go.txt /*:GoIfErr* :GoImpl vim-go.txt /*:GoImpl* :GoImplements vim-go.txt /*:GoImplements* :GoImport vim-go.txt /*:GoImport* @@ -170,9 +194,11 @@ :GoMetaLinterAutoSaveToggle vim-go.txt /*:GoMetaLinterAutoSaveToggle* :GoPath vim-go.txt /*:GoPath* :GoPlay vim-go.txt /*:GoPlay* +:GoPointsTo vim-go.txt /*:GoPointsTo* :GoReferrers vim-go.txt /*:GoReferrers* :GoRemoveTags vim-go.txt /*:GoRemoveTags* :GoRename vim-go.txt /*:GoRename* +:GoReportGitHubIssue vim-go.txt /*:GoReportGitHubIssue* :GoRun vim-go.txt /*:GoRun* :GoSameIds vim-go.txt /*:GoSameIds* :GoSameIdsAutoToggle vim-go.txt /*:GoSameIdsAutoToggle* @@ -193,14 +219,20 @@ go#complete#GetInfo() vim-go.txt /*go#complete#GetInfo()* go#statusline#Show() vim-go.txt /*go#statusline#Show()* go-[[ vim-go.txt /*go-[[* go-]] vim-go.txt /*go-]]* +go-ac vim-go.txt /*go-ac* go-af vim-go.txt /*go-af* go-commands vim-go.txt /*go-commands* go-contents vim-go.txt /*go-contents* go-credits vim-go.txt /*go-credits* +go-debug vim-go.txt /*go-debug* +go-debug-commands vim-go.txt /*go-debug-commands* +go-debug-intro vim-go.txt /*go-debug-intro* +go-debug-settings vim-go.txt /*go-debug-settings* go-development vim-go.txt /*go-development* go-donation vim-go.txt /*go-donation* go-functions vim-go.txt /*go-functions* go-guru-scope vim-go.txt /*go-guru-scope* +go-ic vim-go.txt /*go-ic* go-if vim-go.txt /*go-if* go-install vim-go.txt /*go-install* go-intro vim-go.txt /*go-intro* @@ -211,10 +243,14 @@ go-text-objects vim-go.txt /*go-text-objects* go-troubleshooting vim-go.txt /*go-troubleshooting* go-v_[[ vim-go.txt /*go-v_[[* go-v_]] vim-go.txt /*go-v_]]* +go-v_ac vim-go.txt /*go-v_ac* go-v_af vim-go.txt /*go-v_af* +go-v_ic vim-go.txt /*go-v_ic* go-v_if vim-go.txt /*go-v_if* gohtmltmpl vim-go.txt /*gohtmltmpl* gotexttmpl vim-go.txt /*gotexttmpl* +hl-GoDebugBreakpoint vim-go.txt /*hl-GoDebugBreakpoint* +hl-GoDebugCurrent vim-go.txt /*hl-GoDebugCurrent* unite-decls vim-go.txt /*unite-decls* vim-go vim-go.txt /*vim-go* vim-go-plugins vim-go.txt /*vim-go-plugins* diff --git a/pack/plugins/start/fatih-vim-go/doc/vim-go.txt b/pack/plugins/start/fatih-vim-go/doc/vim-go.txt index f2bedc95..aadaed36 100644 --- a/pack/plugins/start/fatih-vim-go/doc/vim-go.txt +++ b/pack/plugins/start/fatih-vim-go/doc/vim-go.txt @@ -22,10 +22,11 @@ CONTENTS *go-contents* 6. Functions....................................|go-functions| 7. Settings.....................................|go-settings| 8. Syntax highlighting..........................|go-syntax| - 9. FAQ/Troubleshooting..........................|go-troubleshooting| - 10. Development..................................|go-development| - 11. Donation.....................................|go-donation| - 12. Credits......................................|go-credits| + 9. Debugger.....................................|go-debug| + 10. FAQ/Troubleshooting..........................|go-troubleshooting| + 11. Development..................................|go-development| + 12. Donation.....................................|go-donation| + 13. Credits......................................|go-credits| ============================================================================== INTRO *go-intro* @@ -37,16 +38,16 @@ and individual features can be toggled easily. vim-go leverages a number of tools developed by the Go community to provide a seamless Vim experience. * Compile your package with |:GoBuild|, install it with |:GoInstall| or - test it with |:GoTest|. Run a single tests with |:GoTestFunc|). + test it with |:GoTest|. Run a single test with |:GoTestFunc|). * Quickly execute your current file(s) with |:GoRun|. * Improved syntax highlighting and folding. + * Debug programs with integrated `delve` support with |:GoDebugStart|. * Completion support via `gocode`. * `gofmt` or `goimports` on save keeps the cursor position and undo history. * Go to symbol/declaration with |:GoDef|. * Look up documentation with |:GoDoc| or |:GoDocBrowser|. * Easily import packages via |:GoImport|, remove them via |:GoDrop|. - * Automatic `GOPATH` detection which works with `gb` and `godep`. Change or - display `GOPATH` with |:GoPath|. + * Precise type-safe renaming of identifiers with |:GoRename|. * See which code is covered by tests with |:GoCoverage|. * Add or remove tags on struct fields with |:GoAddTags| and |:GoRemoveTags|. * Call `gometalinter` with |:GoMetaLinter| to invoke all possible linters @@ -56,7 +57,8 @@ tools developed by the Go community to provide a seamless Vim experience. static errors, or make sure errors are checked with |:GoErrCheck|. * Advanced source analysis tools utilizing `guru`, such as |:GoImplements|, |:GoCallees|, and |:GoReferrers|. - * Precise type-safe renaming of identifiers with |:GoRename|. + * Automatic `GOPATH` detection which works with `gb` and `godep`. Change or + display `GOPATH` with |:GoPath|. * Integrated and improved snippets, supporting `ultisnips`, `neosnippet`, and `vim-minisnip`. * Share your current code to play.golang.org with |:GoPlay|. @@ -74,6 +76,12 @@ tools developed by the Go community to provide a seamless Vim experience. ============================================================================== INSTALL *go-install* +vim-go requires at least Vim 7.4.1689 or Neovim 0.2.2. On macOS, if you are +still using your system version of vim, you can use homebrew to keep your +version of Vim up-to-date with the following terminal command: +> + brew install vim + The latest stable release, https://github.com/fatih/vim-go/releases/latest, is the recommended version to use. If you choose to use the master branch instead, please do so with caution; it is a _development_ branch. @@ -104,7 +112,7 @@ manager's install command. < * https://github.com/gmarik/vundle > - Plugin 'fatih/vim-go' + Plugin 'fatih/vim-go', { 'do': ':GoUpdateBinaries' } < * Manual (not recommended) > @@ -155,6 +163,11 @@ The following plugins are supported for use with vim-go: ============================================================================== COMMANDS *go-commands* + *:GoReportGitHubIssue* +:GoReportGitHubIssue + GoReportGitHubIssue opens the default browser and starts a new bug report + with useful system information. + *:GoPath* :GoPath [path] @@ -640,12 +653,12 @@ CTRL-t :GoBuildTags [tags] Changes the build tags for various commands. If you have any file that - uses a custom build tag, such as `//+build integration` , this command can - be used to pass it to all tools that accepts tags, such as guru, gorename, - etc.. + uses a custom build tag, such as `// +build integration` , this command + can be used to pass it to all tools that accepts tags, such as guru, + gorename, etc. - The build tags is cleared (unset) if `""` is given. If no arguments is - given it prints the current custom build tags. + The build tags is cleared (unset) if `""` is given. If no arguments are + given it prints the current build tags. *:AsmFmt* :AsmFmt @@ -674,6 +687,12 @@ CTRL-t \| command! -bang AS call go#alternate#Switch(0, 'split') augroup END < + + *:GoPointsTo* +:GoPointsTo + + Show all variables to which the pointer under the cursor may point to. + *:GoWhicherrs* :GoWhicherrs @@ -840,6 +859,27 @@ CTRL-t } < + *:GoIfErr* +:GoIfErr + + Generate if err != nil { return ... } automatically which infer the type + of return values and the numbers. + + For example: +> + func doSomething() (string, error) { + f, err := os.Open("file") + } +< + Becomes: +> + func doSomething() (string, error) { + f, err := os.Open("file") + if err != nil { + return "", err + } + } +< ============================================================================== MAPPINGS *go-mappings* @@ -1028,6 +1068,10 @@ Show send/receive corresponding to selected channel op Show all refs to entity denoted by selected identifier + *(go-pointsto)* + +Show all variables to which the pointer under the cursor may point to. + *(go-metalinter)* Calls `go-metalinter` for the current directory @@ -1048,6 +1092,10 @@ Alternates between the implementation and test code in a new vertical split Calls `:GoImport` for the current package + *(go-iferr)* + +Generate if err != nil { return ... } automatically which infer the type of +return values and the numbers. ============================================================================== TEXT OBJECTS *go-text-objects* @@ -1068,6 +1116,12 @@ if "inside a function", select contents of a function, excluding the function definition and the closing bracket. This text-object also supports literal functions + *go-v_ac* *go-ac* +ac "a comment", select contents of the current comment block. + + *go-v_ic* *go-ic* +ic "inner comment", select contents of the current comment block, + excluding the start and end comment markers. vim-go also defines the following text motion objects: @@ -1088,10 +1142,10 @@ FUNCTIONS *go-functions* *go#statusline#Show()* Shows the status of a job running asynchronously. Can be used to plug into the -statusline. It works to show the status per package instead of per -file. Assume you have three files open, all belonging to the same package, if -the package build (`:GoBuild`) is successful, all statusline's will show -`success`, if you it fails all file's statusline will show `failed`. +statusline. It works to show the status per package instead of per file. +Assume you have three files open, all belonging to the same package, if the +package build (`:GoBuild`) is successful, all statuslines will show `success`, +if it fails all windows' statuslines will show `failed`. To avoid always showing old status information, the status information is cleaned for each package after `60` seconds. This can be changed with the @@ -1100,7 +1154,7 @@ cleaned for each package after `60` seconds. This can be changed with the *go#complete#GetInfo()* Returns the description of the identifer under the cursor. Can be used to plug -into the statusline. This function is also used for |'g:go_auto_type_info'|. +into the statusline. ============================================================================== SETTINGS *go-settings* @@ -1334,10 +1388,10 @@ By default it's not set, so the relevant commands defaults are being used. < *'g:go_build_tags'* -These options that will be automatically passed to the `-tags` option of -various tools, such as `guru`, `gorename`, etc... This is a permanent -setting. A more useful way is to use |:GoBuildTags| to dynamically change or -remove build tags. By default it's not set. +Space-separated list of build tags passed to the `-tags` flag of tools that +support it. +There is also the |:GoBuildTags| convenience command to change or remove build +tags. > let g:go_build_tags = '' < @@ -1658,6 +1712,20 @@ By default "snakecase" is used. Current values are: ["snakecase", > let g:go_addtags_transform = 'snakecase' < + *'g:go_debug'* + +A list of options to debug; useful for development and/or reporting bugs. + +Currently accepted values: + + shell-commands Echo all shell commands that vim-go runs (does not + include async jobs). + debugger-state Expose debugger state in 'g:go_debug_diag'. + debugger-commands Echo communication between vim-go and `dlv`; requests and + responses are recorded in `g:go_debug_commands`. +> + let g:go_debug = [] +< ============================================================================== SYNTAX HIGHLIGHTING *ft-go-syntax* *go-syntax* @@ -1815,6 +1883,212 @@ The `gohtmltmpl` filetype is automatically set for `*.tmpl` files; the `gotexttmpl` is never automatically set and needs to be set manually. +============================================================================== +DEBUGGER *go-debug* + +Vim-go comes with a special "debugger mode". This starts a `dlv` process in +the background and provides various commands to communicate with it. + +This debugger is similar to Visual Studio or Eclipse and has the following +features: + + * Show stack trace and jumps. + * List local variables. + * List function arguments. + * Expand values of struct or array/slice. + * Show balloon on the symbol. + * Show output of stdout/stderr. + * Toggle breakpoint. + * Stack operation continue/next/step out. + +This feature requires Vim 8.0.0087 or newer with the |+job| feature. Neovim +does _not_ work (yet). +This requires Delve 1.0.0 or newer, and it is recommended to use Go 1.10 or +newer, as its new caching will speed up recompiles. + + *go-debug-intro* +GETTING STARTED WITH THE DEBUGGER~ + +Use |:GoDebugStart| or |:GoDebugTest| to start the debugger. The first +argument is the package name, and any arguments after that will be passed on +to the program; for example: +> + :GoDebugStart . -someflag value +< +This may take few seconds. After the code is compiled you'll see three new +windows: the stack trace on left side, the variable list on the bottom-left, +and program output at the bottom. + +You can add breakpoints with |:GoDebugBreakpoint| () and run your program +with |:GoDebugContinue| (). + +The program will halt on the breakpoint, at which point you can inspect the +program state. You can go to the next line with |:GoDebugNext| () or step +in with |:GoDebugStep| (). + +The variable window in the bottom left (`GODEBUG_VARIABLES`) will display all +local variables. Struct values are displayed as `{...}`, array/slices as +`[4]`. Use on the variable name to expand the values. + +The `GODEBUG_OUTPUT` window displays output from the program and the Delve +debugger. + +The `GODEBUG_STACKTRACE` window can be used to jump to different places in the +call stack. + +When you're done use |:GoDebugStop| to close the debugging windows and halt +the `dlv` process, or |:GoDebugRestart| to recompile the code. + + *go-debug-commands* +DEBUGGER COMMANDS~ + +Only |:GoDebugStart| and |:GoDebugBreakpoint| are available by default; the +rest of the commands and mappings become available after starting debug mode. + + *:GoDebugStart* +:GoDebugStart [pkg] [program-args] + + Start the debug mode for [pkg]; this does several things: + + * Setup the debug windows according to |'g:go_debug_windows'|. + * Make the `:GoDebug*` commands and `(go-debug-*)` mappings available. + + The current directory is used if [pkg] is empty. Any other arguments will + be passed to the program. + + Use |:GoDebugStop| to stop `dlv` and exit debugging mode. + + *:GoDebugTest* +:GoDebugTest [pkg] [program-args] + + Behaves the same as |:GoDebugStart| but runs `dlv test` instead of + `dlv debug` so you can debug tests. + + Use `-test.flag` to pass flags to `go test` when debugging a test; for + example `-test.v` or `-test.run TestFoo` + + + *:GoDebugRestart* +:GoDebugRestart + + Stop the program (if running) and restart `dlv` to recompile the package. + The current window layout and breakpoints will be left intact. + + *:GoDebugStop* + *(go-debug-stop)* +:GoDebugStop + + Stop `dlv` and remove all debug-specific commands, mappings, and windows. + + *:GoDebugBreakpoint* + *(go-debug-breakpoint)* +:GoDebugBreakpoint [linenr] + + Toggle breakpoint for the [linenr]. [linenr] defaults to the current line + if it is omitted. A line with a breakpoint will have the + {godebugbreakpoint} |:sign| placed on it. The line the program is + currently halted on will have the {godebugcurline} sign. + + *hl-GoDebugCurrent* *hl-GoDebugBreakpoint* + A line with a breakpoint will be highlighted with the {GoDebugBreakpoint} + group; the line the program is currently halted on will be highlighted + with {GoDebugCurrent}. + + Mapped to by default. + + *:GoDebugContinue* + *(go-debug-continue)* +:GoDebugContinue + + Continue execution until breakpoint or program termination. It will start + the program if it hasn't been started yet. + + Mapped to by default. + + *:GoDebugNext* + *(go-debug-next)* +:GoDebugNext + + Advance execution by one line, also called "step over" by some other + debuggers. + It will behave as |:GoDebugContinue| if the program isn't started. + + Mapped to by default. + + *:GoDebugStep* + *(go-debug-step)* +:GoDebugStep + + Advance execution by one step, stopping at the next line of code that will + be executed (regardless of location). + It will behave as |:GoDebugContinue| if the program isn't started. + + Mapped to by default. + + *:GoDebugStepOut* + *(go-debug-stepout)* + +:GoDebugStepOut + + Run all the code in the current function and halt when the function + returns ("step out of the current function"). + It will behave as |:GoDebugContinue| if the program isn't started. + + *:GoDebugSet* +:GoDebugSet {var} {value} + + Set the variable {var} to {value}. Example: +> + :GoDebugSet truth 42 +< + This only works for `float`, `int` and variants, `uint` and variants, + `bool`, and pointers (this is a `delve` limitation, not a vim-go + limitation). + + *:GoDebugPrint* + *(go-debug-print)* +:GoDebugPrint {expr} + + Print the result of a Go expression. +> + :GoDebugPrint truth == 42 + truth == 42 true +< + Mapped to by default, which will evaluate the under the + cursor. + + *go-debug-settings* +DEBUGGER SETTINGS~ + + *'g:go_debug_windows'* + +Controls the window layout for debugging mode. This is a |dict| with three +possible keys: "stack", "out", and "vars"; the windows will created in that +order with the commands in the value. +A window will not be created if a key is missing or empty. + +Defaults: +> + let g:go_debug_windows = { + \ 'stack': 'leftabove 20vnew', + \ 'out': 'botright 10new', + \ 'vars': 'leftabove 30vnew', + \ } +< +Show only variables on the right-hand side: > + + let g:go_debug_windows = { + \ 'vars': 'rightbelow 60vnew', + \ } +< + *'g:go_debug_address'* + +Server address `dlv` will listen on; must be in `hostname:port` format. +Defaults to `127.0.0.1:8181`: +> + let g:go_debug_address = '127.0.0.1:8181' +< + ============================================================================== FAQ TROUBLESHOOTING *go-troubleshooting* @@ -1847,7 +2121,7 @@ Many vim-go commands use the `guru` commandline tool to get information. Some a reasonable amount of performance `guru` limits this analysis to a selected list of packages. This is known as the "guru scope". -The default is to use the package the curent buffer belongs to, but this may +The default is to use the package the current buffer belongs to, but this may not always be correct. For example for the file `guthub.com/user/pkg/a/a.go` the scope will be set to `github.com/user/pkg/a`, but you probably want `github.com/user/pkg` @@ -1873,11 +2147,22 @@ Also see |:GoGuruScope| and |'g:go_guru_scope'|. Vim becomes slow while editing Go files~ -This is usually caused by `g:go_highlight_*` options. Try disabling them if -you've enabled some of them. - -Other common culprits are |'g:go_auto_sameids'| and |go#statusline#Show()|. - +The most common cause for this is using an older version of Vim that doesn't +support asynchronous jobs. |'g:go_auto_sameids'| and |'g:go_auto_type_info'| +run jobs that can cause noticable delays when used with vim74. The problem is +most pronounced on vim74, but can occur on vim8 and nvim. On vim8 and nvim, +the problem should be restricted to a short period when the first buffer in a +package is first loaded. + +If you see unexpected characters rendered in the current window, the problem +is most likely due to |'g:go_auto_sameids'| or |'g:go_auto_type_info'|. First, +try using another mode for |'g:go_info_mode'|. If that doesn't work, try +disabling |'g:go_auto_sameids'| and |'g:go_auto_type_info'|. + +To a lesser extent, this can be caused by `g:go_highlight_*` options. If Vim +is just slower than normal, but doesn't render unexpected characters in the +currrent window, then the problem is most likely the `g:go_highlight_*` +options. Try disabling them if you've enabled some of them. I get errors when using GoInstallBinaries~ diff --git a/pack/plugins/start/fatih-vim-go/ftplugin/go.vim b/pack/plugins/start/fatih-vim-go/ftplugin/go.vim index a0ecd324..572bb958 100644 --- a/pack/plugins/start/fatih-vim-go/ftplugin/go.vim +++ b/pack/plugins/start/fatih-vim-go/ftplugin/go.vim @@ -42,11 +42,17 @@ endif if get(g:, "go_textobj_enabled", 1) onoremap af :call go#textobj#Function('a') - onoremap if :call go#textobj#Function('i') - xnoremap af :call go#textobj#Function('a') + + onoremap if :call go#textobj#Function('i') xnoremap if :call go#textobj#Function('i') + onoremap ac :call go#textobj#Comment('a') + xnoremap ac :call go#textobj#Comment('a') + + onoremap ic :call go#textobj#Comment('i') + xnoremap ic :call go#textobj#Comment('i') + " Remap ]] and [[ to jump betweeen functions as they are useless in Go nnoremap ]] :call go#textobj#FunctionJump('n', 'next') nnoremap [[ :call go#textobj#FunctionJump('n', 'prev') @@ -58,7 +64,7 @@ if get(g:, "go_textobj_enabled", 1) xnoremap [[ :call go#textobj#FunctionJump('v', 'prev') endif -if get(g:, "go_auto_type_info", 0) || get(g:, "go_auto_sameids", 0) +if go#config#AutoTypeInfo() || go#config#AutoSameids() let &l:updatetime= get(g:, "go_updatetime", 800) endif diff --git a/pack/plugins/start/fatih-vim-go/ftplugin/go/commands.vim b/pack/plugins/start/fatih-vim-go/ftplugin/go/commands.vim index 9b983ef5..bc98e465 100644 --- a/pack/plugins/start/fatih-vim-go/ftplugin/go/commands.vim +++ b/pack/plugins/start/fatih-vim-go/ftplugin/go/commands.vim @@ -4,6 +4,7 @@ command! -nargs=? -complete=customlist,go#rename#Complete GoRename call go#renam " -- guru command! -nargs=* -complete=customlist,go#package#Complete GoGuruScope call go#guru#Scope() command! -range=% GoImplements call go#guru#Implements() +command! -range=% GoPointsTo call go#guru#PointsTo() command! -range=% GoWhicherrs call go#guru#Whicherrs() command! -range=% GoCallees call go#guru#Callees() command! -range=% GoDescribe call go#guru#Describe() @@ -98,4 +99,17 @@ command! -nargs=0 GoKeyify call go#keyify#Keyify() " -- fillstruct command! -nargs=0 GoFillStruct call go#fillstruct#FillStruct() +" -- debug +if !exists(':GoDebugStart') + command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start(0, ) + command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start(1, ) + command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint() +endif + +" -- issue +command! -nargs=0 GoReportGitHubIssue call go#issue#New() + +" -- iferr +command! -nargs=0 GoIfErr call go#iferr#Generate() + " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/ftplugin/go/mappings.vim b/pack/plugins/start/fatih-vim-go/ftplugin/go/mappings.vim index 4cb82789..92d3f5df 100644 --- a/pack/plugins/start/fatih-vim-go/ftplugin/go/mappings.vim +++ b/pack/plugins/start/fatih-vim-go/ftplugin/go/mappings.vim @@ -44,6 +44,7 @@ xnoremap (go-freevars) :call go#guru#Freevars(0) nnoremap (go-channelpeers) :call go#guru#ChannelPeers(-1) nnoremap (go-referrers) :call go#guru#Referrers(-1) nnoremap (go-sameids) :call go#guru#SameIds() +nnoremap (go-pointsto) :call go#guru#PointsTo(-1) nnoremap (go-whicherrs) :call go#guru#Whicherrs(-1) nnoremap (go-sameids-toggle) :call go#guru#ToggleSameIds() @@ -72,4 +73,6 @@ nnoremap (go-alternate-edit) :call go#alternate#Switch(0, "e nnoremap (go-alternate-vertical) :call go#alternate#Switch(0, "vsplit") nnoremap (go-alternate-split) :call go#alternate#Switch(0, "split") +nnoremap (go-iferr) :call go#iferr#Generate() + " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/ftplugin/go/snippets.vim b/pack/plugins/start/fatih-vim-go/ftplugin/go/snippets.vim index 33a3cd5a..f7a7d930 100644 --- a/pack/plugins/start/fatih-vim-go/ftplugin/go/snippets.vim +++ b/pack/plugins/start/fatih-vim-go/ftplugin/go/snippets.vim @@ -40,14 +40,14 @@ function! s:GoMinisnip() abort endif if exists('g:minisnip_dir') - let g:minisnip_dir .= ':' . globpath(&rtp, 'gosnippets/minisnip') + let g:minisnip_dir .= go#util#PathListSep() . globpath(&rtp, 'gosnippets/minisnip') else let g:minisnip_dir = globpath(&rtp, 'gosnippets/minisnip') endif endfunction -let s:engine = get(g:, 'go_snippet_engine', 'automatic') +let s:engine = go#config#SnippetEngine() if s:engine is? "automatic" if get(g:, 'did_plugin_ultisnips') is 1 call s:GoUltiSnips() diff --git a/pack/plugins/start/fatih-vim-go/gosnippets/UltiSnips/go.snippets b/pack/plugins/start/fatih-vim-go/gosnippets/UltiSnips/go.snippets index 1a78a04d..87a7b376 100644 --- a/pack/plugins/start/fatih-vim-go/gosnippets/UltiSnips/go.snippets +++ b/pack/plugins/start/fatih-vim-go/gosnippets/UltiSnips/go.snippets @@ -169,7 +169,7 @@ endsnippet # error multiple return snippet errn, "Error return with two return values" !b if err != nil { - return ${1:nil}, err + return ${1:nil}, ${2:err} } ${0} endsnippet @@ -252,6 +252,11 @@ snippet fn "fmt.Println(...)" fmt.Println("${1:${VISUAL}}") endsnippet +# Fmt Errorf debug +snippet fe "fmt.Errorf(...)" +fmt.Errorf("${1:${VISUAL}}") +endsnippet + # log printf snippet lf "log.Printf(...)" log.Printf("${1:${VISUAL}} = %+v\n", $1) diff --git a/pack/plugins/start/fatih-vim-go/gosnippets/snippets/go.snip b/pack/plugins/start/fatih-vim-go/gosnippets/snippets/go.snip index 40e6fe3e..7a22cd24 100644 --- a/pack/plugins/start/fatih-vim-go/gosnippets/snippets/go.snip +++ b/pack/plugins/start/fatih-vim-go/gosnippets/snippets/go.snip @@ -219,6 +219,10 @@ abbr fmt.Printf(...) snippet fn abbr fmt.Println(...) fmt.Println("${1}") +# Fmt Errorf +snippet fe +abbr fmt.Errorf(...) + fmt.Errorf("${1}") # log printf snippet lf abbr log.Printf(...) diff --git a/pack/plugins/start/fatih-vim-go/plugin/go.vim b/pack/plugins/start/fatih-vim-go/plugin/go.vim index bb88608f..22d80bc1 100644 --- a/pack/plugins/start/fatih-vim-go/plugin/go.vim +++ b/pack/plugins/start/fatih-vim-go/plugin/go.vim @@ -11,7 +11,7 @@ let g:go_loaded_install = 1 " Version 7.4.1689 was chosen because that's what the most recent Ubuntu LTS " release (16.04) uses. if - \ get(g:, 'go_version_warning', 1) != 0 && + \ go#config#VersionWarning() != 0 && \ (v:version < 704 || (v:version == 704 && !has('patch1689'))) \ && !has('nvim') echohl Error @@ -31,9 +31,10 @@ endif " needed by the user with GoInstallBinaries let s:packages = { \ 'asmfmt': ['github.com/klauspost/asmfmt/cmd/asmfmt'], + \ 'dlv': ['github.com/derekparker/delve/cmd/dlv'], \ 'errcheck': ['github.com/kisielk/errcheck'], \ 'fillstruct': ['github.com/davidrjenni/reftools/cmd/fillstruct'], - \ 'gocode': ['github.com/nsf/gocode', {'windows': '-ldflags -H=windowsgui'}], + \ 'gocode': ['github.com/nsf/gocode', {'windows': ['-ldflags', '-H=windowsgui']}], \ 'godef': ['github.com/rogpeppe/godef'], \ 'gogetdoc': ['github.com/zmb3/gogetdoc'], \ 'goimports': ['golang.org/x/tools/cmd/goimports'], @@ -46,6 +47,7 @@ let s:packages = { \ 'impl': ['github.com/josharian/impl'], \ 'keyify': ['github.com/dominikh/go-tools/cmd/keyify'], \ 'motion': ['github.com/fatih/motion'], + \ 'iferr': ['github.com/koron/iferr'], \ } " These commands are available on any filetypes @@ -96,16 +98,9 @@ function! s:GoInstallBinaries(updateBinaries, ...) set noshellslash endif - let cmd = "go get -v " + let l:cmd = ['go', 'get', '-v'] if get(g:, "go_get_update", 1) != 0 - let cmd .= "-u " - endif - - let s:go_version = matchstr(go#util#System("go version"), '\d.\d.\d') - - " https://github.com/golang/go/issues/10791 - if s:go_version > "1.4.0" && s:go_version < "1.5.0" - let cmd .= "-f " + let l:cmd += ['-u'] endif " Filter packages from arguments (if any). @@ -130,7 +125,11 @@ function! s:GoInstallBinaries(updateBinaries, ...) for [binary, pkg] in items(l:packages) let l:importPath = pkg[0] - let l:goGetFlags = len(pkg) > 1 ? get(pkg[1], l:platform, '') : '' + + let l:run_cmd = copy(l:cmd) + if len(l:pkg) > 1 && get(l:pkg[1], l:platform, '') isnot '' + let l:run_cmd += get(l:pkg[1], l:platform, '') + endif let binname = "go_" . binary . "_bin" @@ -146,9 +145,9 @@ function! s:GoInstallBinaries(updateBinaries, ...) echo "vim-go: ". binary ." not found. Installing ". importPath . " to folder " . go_bin_path endif - let out = go#util#System(printf('%s %s %s', cmd, l:goGetFlags, shellescape(importPath))) - if go#util#ShellError() != 0 - echom "Error installing " . importPath . ": " . out + let [l:out, l:err] = go#util#Exec(l:run_cmd + [l:importPath]) + if l:err + echom "Error installing " . l:importPath . ": " . l:out endif endif endfor @@ -262,7 +261,7 @@ augroup vim-go autocmd BufWinEnter *.go call go#guru#ClearSameIds() autocmd BufEnter *.go - \ if get(g:, 'go_autodetect_gopath', 0) && !exists('b:old_gopath') + \ if go#config#AutodetectGopath() && !exists('b:old_gopath') \| let b:old_gopath = exists('$GOPATH') ? $GOPATH : -1 \| let $GOPATH = go#path#Detect() \| endif diff --git a/pack/plugins/start/fatih-vim-go/scripts/bench-syntax b/pack/plugins/start/fatih-vim-go/scripts/bench-syntax new file mode 100755 index 00000000..25e89ee5 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/scripts/bench-syntax @@ -0,0 +1,44 @@ +#!/bin/sh +# +# Benchmark the syntax/go.vim file. +# +# The first argument is the Vim version to test (as in run-vim), the rest of the +# agument are g:go_highlight_* settings (without that prefix). You can use "ALL" +# to set all the options. +# + +set -euC +vimgodir=$(cd -P "$(dirname "$0")/.." > /dev/null && pwd) +cd "$vimgodir" + +if [ -z "${1:-}" ]; then + echo "unknown version: '${1:-}'" + echo "First argument must be 'vim-7.4', 'vim-8.0', or 'nvim'." + exit 1 +fi + +if [ -z "${2:-}" ]; then + echo "file not set" + echo "Second argument must be a Go file to benchmark, as 'filename:linenr'" + exit 1 +fi + +vim=$1 +file="$(echo "$2" | cut -d : -f 1)" +line="$(echo "$2" | cut -d : -f 2)" +if [ -z "$line" -o "$line" = "$file" ]; then + echo "Second argument must be a Go file to benchmark, as 'filename:linenr'" + exit 1 +fi + +shift; shift +export RUNBENCH_SETTINGS=$@ +export RUNBENCH_OUT="$(mktemp -p "${TMPDIR:-/tmp}" vimgo-bench.XXXXX)" + +"$vimgodir/scripts/run-vim" $vim \ + +"silent e $file" \ + +"normal! ${line}G" \ + -S ./scripts/runbench.vim + +echo "Report written to:" +echo "$RUNBENCH_OUT" diff --git a/pack/plugins/start/fatih-vim-go/scripts/install-vim b/pack/plugins/start/fatih-vim-go/scripts/install-vim index 7b4e7a44..caf9c0d9 100755 --- a/pack/plugins/start/fatih-vim-go/scripts/install-vim +++ b/pack/plugins/start/fatih-vim-go/scripts/install-vim @@ -25,7 +25,7 @@ case "$vim" in # This follows the version in Arch Linux. Vim's master branch isn't always # stable, and we don't want to have the build fail because Vim introduced a # bug. - tag="v8.0.1176" + tag="v8.0.1542" giturl="https://github.com/vim/vim" ;; diff --git a/pack/plugins/start/fatih-vim-go/scripts/run-vim b/pack/plugins/start/fatih-vim-go/scripts/run-vim index f020ea93..446bd235 100755 --- a/pack/plugins/start/fatih-vim-go/scripts/run-vim +++ b/pack/plugins/start/fatih-vim-go/scripts/run-vim @@ -34,13 +34,13 @@ fi if [ $coverage -eq 1 ]; then covimerage -q run --report-file /tmp/vim-go-test/cov-profile.txt --append \ $dir/bin/vim --noplugin -u NONE -N \ - +"set shm+=WAFI rtp=$dir/share/vim/vimgo packpath=$dir/share/vim/vimgo,$vimgodir" \ + +"set shm+=WAFI rtp^=$vimgodir packpath=$dir/share/vim/vimgo" \ +'filetype plugin indent on' \ +'packloadall!' \ "$@" else $dir/bin/vim --noplugin -u NONE -N \ - +"set shm+=WAFI rtp=$dir/share/vim/vimgo packpath=$dir/share/vim/vimgo,$vimgodir" \ + +"set shm+=WAFI rtp^=$vimgodir packpath=$dir/share/vim/vimgo" \ +'filetype plugin indent on' \ +'packloadall!' \ "$@" diff --git a/pack/plugins/start/fatih-vim-go/scripts/runbench.vim b/pack/plugins/start/fatih-vim-go/scripts/runbench.vim new file mode 100644 index 00000000..db582cb9 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/scripts/runbench.vim @@ -0,0 +1,26 @@ +" vint: -ProhibitSetNoCompatible +set nocompatible nomore shellslash encoding=utf-8 shortmess+=WIF +lang mess C + +if $RUNBENCH_SETTINGS is? 'all' + let $RUNBENCH_SETTINGS = join(['array_whitespace_error', 'build_constraints', + \ 'chan_whitespace_error', 'extra_types', 'fields', 'format_strings', + \ 'function_arguments', 'function_calls', 'functions', 'generate_tags', + \ 'operators', 'space_tab_error', 'string_spellcheck', + \ 'trailing_whitespace_error', 'types', 'variable_assignments', + \ 'variable_declarations'], ' ') +endif + +for s:s in split($RUNBENCH_SETTINGS, ' ') + call execute('let g:go_highlight_' . s:s . ' = 1') +endfor + +filetype plugin indent on +syntax on + +syntime on +redraw! +let s:report = execute('syntime report') +execute ':e ' . fnameescape($RUNBENCH_OUT) +call setline('.', split(s:report, '\n')) +wq diff --git a/pack/plugins/start/fatih-vim-go/scripts/runtest.vim b/pack/plugins/start/fatih-vim-go/scripts/runtest.vim index 23d91860..0da23d93 100644 --- a/pack/plugins/start/fatih-vim-go/scripts/runtest.vim +++ b/pack/plugins/start/fatih-vim-go/scripts/runtest.vim @@ -14,6 +14,7 @@ let s:gopath = $GOPATH if !exists('g:test_verbose') let g:test_verbose = 0 endif +let g:go_echo_command_info = 0 " Source the passed test file. source % diff --git a/pack/plugins/start/fatih-vim-go/syntax/go.vim b/pack/plugins/start/fatih-vim-go/syntax/go.vim index 8769dffb..8a5ec26c 100644 --- a/pack/plugins/start/fatih-vim-go/syntax/go.vim +++ b/pack/plugins/start/fatih-vim-go/syntax/go.vim @@ -9,102 +9,6 @@ if exists("b:current_syntax") finish endif -" Set settings to default values. -if !exists("g:go_highlight_array_whitespace_error") - let g:go_highlight_array_whitespace_error = 0 -endif - -if !exists("g:go_highlight_chan_whitespace_error") - let g:go_highlight_chan_whitespace_error = 0 -endif - -if !exists("g:go_highlight_extra_types") - let g:go_highlight_extra_types = 0 -endif - -if !exists("g:go_highlight_space_tab_error") - let g:go_highlight_space_tab_error = 0 -endif - -if !exists("g:go_highlight_trailing_whitespace_error") - let g:go_highlight_trailing_whitespace_error = 0 -endif - -if !exists("g:go_highlight_operators") - let g:go_highlight_operators = 0 -endif - -if !exists("g:go_highlight_functions") - let g:go_highlight_functions = 0 -endif - -if !exists("g:go_highlight_function_arguments") - let g:go_highlight_function_arguments = 0 -endif - -if !exists("g:go_highlight_function_calls") - let g:go_highlight_function_calls = 0 -endif - -if !exists("g:go_highlight_fields") - let g:go_highlight_fields = 0 -endif - -if !exists("g:go_highlight_types") - let g:go_highlight_types = 0 -endif - -if !exists("g:go_highlight_build_constraints") - let g:go_highlight_build_constraints = 0 -endif - -if !exists("g:go_highlight_string_spellcheck") - let g:go_highlight_string_spellcheck = 1 -endif - -if !exists("g:go_highlight_format_strings") - let g:go_highlight_format_strings = 1 -endif - -if !exists("g:go_highlight_generate_tags") - let g:go_highlight_generate_tags = 0 -endif - -if !exists("g:go_highlight_variable_assignments") - let g:go_highlight_variable_assignments = 0 -endif - -if !exists("g:go_highlight_variable_declarations") - let g:go_highlight_variable_declarations = 0 -endif - -let s:fold_block = 1 -let s:fold_import = 1 -let s:fold_varconst = 1 -let s:fold_package_comment = 1 -let s:fold_comment = 0 - -if exists("g:go_fold_enable") - " Enabled by default. - if index(g:go_fold_enable, 'block') == -1 - let s:fold_block = 0 - endif - if index(g:go_fold_enable, 'import') == -1 - let s:fold_import = 0 - endif - if index(g:go_fold_enable, 'varconst') == -1 - let s:fold_varconst = 0 - endif - if index(g:go_fold_enable, 'package_comment') == -1 - let s:fold_package_comment = 0 - endif - - " Disabled by default. - if index(g:go_fold_enable, 'comment') > -1 - let s:fold_comment = 1 - endif -endif - syn case match syn keyword goPackage package @@ -142,7 +46,6 @@ hi def link goUnsignedInts Type hi def link goFloats Type hi def link goComplexes Type - " Predefined functions and values syn match goBuiltins /\<\v(append|cap|close|complex|copy|delete|imag|len)\ze\(/ syn match goBuiltins /\<\v(make|new|panic|print|println|real|recover)\ze\(/ @@ -158,7 +61,7 @@ syn keyword goTodo contained TODO FIXME XXX BUG syn cluster goCommentGroup contains=goTodo syn region goComment start="//" end="$" contains=goGenerate,@goCommentGroup,@Spell -if s:fold_comment +if go#config#FoldEnable('comment') syn region goComment start="/\*" end="\*/" contains=@goCommentGroup,@Spell fold syn match goComment "\v(^\s*//.*\n)+" contains=goGenerate,@goCommentGroup,@Spell fold else @@ -168,7 +71,7 @@ endif hi def link goComment Comment hi def link goTodo Todo -if g:go_highlight_generate_tags != 0 +if go#config#HighlightGenerateTags() syn match goGenerateVariables contained /\(\$GOARCH\|\$GOOS\|\$GOFILE\|\$GOLINE\|\$GOPACKAGE\|\$DOLLAR\)\>/ syn region goGenerate start="^\s*//go:generate" end="$" contains=goGenerateVariables hi def link goGenerate PreProc @@ -193,7 +96,7 @@ hi def link goEscapeError Error " Strings and their contents syn cluster goStringGroup contains=goEscapeOctal,goEscapeC,goEscapeX,goEscapeU,goEscapeBigU,goEscapeError -if g:go_highlight_string_spellcheck != 0 +if go#config#HighlightStringSpellcheck() syn region goString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=@goStringGroup,@Spell syn region goRawString start=+`+ end=+`+ contains=@Spell else @@ -201,7 +104,7 @@ else syn region goRawString start=+`+ end=+`+ endif -if g:go_highlight_format_strings != 0 +if go#config#HighlightFormatStrings() " [n] notation is valid for specifying explicit argument indexes " 1. Match a literal % not preceded by a %. " 2. Match any number of -, #, 0, space, or + @@ -229,21 +132,21 @@ hi def link goCharacter Character " Regions syn region goParen start='(' end=')' transparent -if s:fold_block +if go#config#FoldEnable('block') syn region goBlock start="{" end="}" transparent fold else syn region goBlock start="{" end="}" transparent endif " import -if s:fold_import +if go#config#FoldEnable('import') syn region goImport start='import (' end=')' transparent fold contains=goImport,goString,goComment else syn region goImport start='import (' end=')' transparent contains=goImport,goString,goComment endif " var, const -if s:fold_varconst +if go#config#FoldEnable('varconst') syn region goVar start='var (' end='^\s*)$' transparent fold \ contains=ALLBUT,goParen,goBlock,goFunction,goTypeName,goReceiverType,goReceiverVar,goArgumentName,goArgumentType,goSimpleArguments syn region goConst start='const (' end='^\s*)$' transparent fold @@ -286,12 +189,12 @@ hi def link goImaginary Number hi def link goImaginaryFloat Float " Spaces after "[]" -if g:go_highlight_array_whitespace_error != 0 +if go#config#HighlightArrayWhitespaceError() syn match goSpaceError display "\(\[\]\)\@<=\s\+" endif " Spacing errors around the 'chan' keyword -if g:go_highlight_chan_whitespace_error != 0 +if go#config#HighlightChanWhitespaceError() " receive-only annotation on chan type " " \(\\)\@/ syn match goExtraType /\/ syn match goExtraType /\/ @@ -317,12 +220,12 @@ if g:go_highlight_extra_types != 0 endif " Space-tab error -if g:go_highlight_space_tab_error != 0 +if go#config#HighlightSpaceTabError() syn match goSpaceError display " \+\t"me=e-1 endif " Trailing white space error -if g:go_highlight_trailing_whitespace_error != 0 +if go#config#HighlightTrailingWhitespaceError() syn match goSpaceError display excludenl "\s\+$" endif @@ -340,7 +243,7 @@ hi def link goTodo Todo syn match goVarArgs /\.\.\./ " Operators; -if g:go_highlight_operators != 0 +if go#config#HighlightOperators() " match single-char operators: - + % < > ! & | ^ * = " and corresponding two-char operators: -= += %= <= >= != &= |= ^= *= == syn match goOperator /[-+%<>!&|^*=]=\?/ @@ -359,13 +262,13 @@ endif hi def link goOperator Operator " Functions; -if g:go_highlight_functions isnot 0 || g:go_highlight_function_arguments isnot 0 +if go#config#HighlightFunctions() || go#config#HighlightFunctionArguments() syn match goDeclaration /\/ nextgroup=goReceiver,goFunction,goSimpleArguments skipwhite skipnl syn match goReceiverVar /\w\+\ze\s\+\(\w\|\*\)/ nextgroup=goPointerOperator,goReceiverType skipwhite skipnl contained syn match goPointerOperator /\*/ nextgroup=goReceiverType contained skipwhite skipnl syn match goFunction /\w\+/ nextgroup=goSimpleArguments contained skipwhite skipnl syn match goReceiverType /\w\+/ contained -if g:go_highlight_function_arguments isnot 0 +if go#config#HighlightFunctionArguments() syn match goSimpleArguments /(\(\w\|\_s\|[*\.\[\],\{\}<>-]\)*)/ contained contains=goArgumentName nextgroup=goSimpleArguments skipwhite skipnl syn match goArgumentName /\w\+\(\s*,\s*\w\+\)*\ze\s\+\(\w\|\.\|\*\|\[\)/ contained nextgroup=goArgumentType skipwhite skipnl syn match goArgumentType /\([^,)]\|\_s\)\+,\?/ contained nextgroup=goArgumentName skipwhite skipnl @@ -380,19 +283,19 @@ endif hi def link goFunction Function " Function calls; -if g:go_highlight_function_calls != 0 +if go#config#HighlightFunctionCalls() syn match goFunctionCall /\w\+\ze(/ contains=goBuiltins,goDeclaration endif hi def link goFunctionCall Type " Fields; -if g:go_highlight_fields != 0 +if go#config#HighlightFields() syn match goField /\.\w\+\([.\ \n\r\:\)\[,]\)\@=/hs=s+1 endif hi def link goField Identifier " Structs & Interfaces; -if g:go_highlight_types != 0 +if go#config#HighlightTypes() syn match goTypeConstructor /\<\w\+{\@=/ syn match goTypeDecl /\/ nextgroup=goTypeName skipwhite skipnl syn match goTypeName /\w\+/ contained nextgroup=goDeclType skipwhite skipnl @@ -408,19 +311,19 @@ hi def link goTypeDecl Keyword hi def link goDeclType Keyword " Variable Assignments -if g:go_highlight_variable_assignments != 0 +if go#config#HighlightVariableAssignments() syn match goVarAssign /\v[_.[:alnum:]]+(,\s*[_.[:alnum:]]+)*\ze(\s*([-^+|^\/%&]|\*|\<\<|\>\>|\&\^)?\=[^=])/ hi def link goVarAssign Special endif " Variable Declarations -if g:go_highlight_variable_declarations != 0 +if go#config#HighlightVariableDeclarations() syn match goVarDefs /\v\w+(,\s*\w+)*\ze(\s*:\=)/ hi def link goVarDefs Special endif " Build Constraints -if g:go_highlight_build_constraints != 0 +if go#config#HighlightBuildConstraints() syn match goBuildKeyword display contained "+build" " Highlight the known values of GOOS, GOARCH, and other +build options. syn keyword goBuildDirectives contained @@ -442,7 +345,7 @@ if g:go_highlight_build_constraints != 0 hi def link goBuildKeyword PreProc endif -if g:go_highlight_build_constraints != 0 || s:fold_package_comment +if go#config#HighlightBuildConstraints() || go#config#FoldEnable('package_comment') " One or more line comments that are followed immediately by a "package" " declaration are treated like package documentation, so these must be " matched as comments to avoid looking like working build constraints. @@ -451,11 +354,11 @@ if g:go_highlight_build_constraints != 0 || s:fold_package_comment exe 'syn region goPackageComment start=/\v(\/\/.*\n)+\s*package/' \ . ' end=/\v\n\s*package/he=e-7,me=e-7,re=e-7' \ . ' contains=@goCommentGroup,@Spell' - \ . (s:fold_package_comment ? ' fold' : '') - exe 'syn region goPackageComment start=/\v\/\*.*\n(.*\n)*\s*\*\/\npackage/' + \ . (go#config#FoldEnable('package_comment') ? ' fold' : '') + exe 'syn region goPackageComment start=/\v^\s*\/\*.*\n(.*\n)*\s*\*\/\npackage/' \ . ' end=/\v\*\/\n\s*package/he=e-7,me=e-7,re=e-7' \ . ' contains=@goCommentGroup,@Spell' - \ . (s:fold_package_comment ? ' fold' : '') + \ . (go#config#FoldEnable('package_comment') ? ' fold' : '') hi def link goPackageComment Comment endif @@ -468,6 +371,10 @@ function! s:hi() " :GoCoverage commands hi def goCoverageCovered ctermfg=green guifg=#A6E22E hi def goCoverageUncover ctermfg=red guifg=#F92672 + + " :GoDebug commands + hi GoDebugBreakpoint term=standout ctermbg=117 ctermfg=0 guibg=#BAD4F5 guifg=Black + hi GoDebugCurrent term=reverse ctermbg=12 ctermfg=7 guibg=DarkBlue guifg=White endfunction augroup vim-go-hi diff --git a/pack/plugins/start/fatih-vim-go/syntax/godebugoutput.vim b/pack/plugins/start/fatih-vim-go/syntax/godebugoutput.vim new file mode 100644 index 00000000..b8e6f5ff --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/syntax/godebugoutput.vim @@ -0,0 +1,13 @@ +if exists("b:current_syntax") + finish +endif + +syn match godebugOutputErr '^ERR:.*' +syn match godebugOutputOut '^OUT:.*' + +let b:current_syntax = "godebugoutput" + +hi def link godebugOutputErr Comment +hi def link godebugOutputOut Normal + +" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/syntax/godebugstacktrace.vim b/pack/plugins/start/fatih-vim-go/syntax/godebugstacktrace.vim new file mode 100644 index 00000000..b0c53725 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/syntax/godebugstacktrace.vim @@ -0,0 +1,11 @@ +if exists("b:current_syntax") + finish +endif + +syn match godebugStacktrace '^\S\+' + +let b:current_syntax = "godebugoutput" + +hi def link godebugStacktrace SpecialKey + +" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/syntax/godebugvariables.vim b/pack/plugins/start/fatih-vim-go/syntax/godebugvariables.vim new file mode 100644 index 00000000..791705ba --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/syntax/godebugvariables.vim @@ -0,0 +1,23 @@ +if exists("b:current_syntax") + finish +endif + +syn match godebugTitle '^#.*' +syn match godebugVariables '^\s*\S\+\ze:' + +syn keyword goType chan map bool string error +syn keyword goSignedInts int int8 int16 int32 int64 rune +syn keyword goUnsignedInts byte uint uint8 uint16 uint32 uint64 uintptr +syn keyword goFloats float32 float64 +syn keyword goComplexes complex64 complex128 + +syn keyword goBoolean true false + +let b:current_syntax = "godebugvariables" + +hi def link godebugTitle Underlined +hi def link godebugVariables Statement +hi def link goType Type +hi def link goBoolean Boolean + +" vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/syntax/gohtmltmpl.vim b/pack/plugins/start/fatih-vim-go/syntax/gohtmltmpl.vim index fd2612a7..ea3dbf08 100644 --- a/pack/plugins/start/fatih-vim-go/syntax/gohtmltmpl.vim +++ b/pack/plugins/start/fatih-vim-go/syntax/gohtmltmpl.vim @@ -10,6 +10,8 @@ runtime! syntax/gotexttmpl.vim runtime! syntax/html.vim unlet b:current_syntax +syn cluster htmlPreproc add=gotplAction,goTplComment + let b:current_syntax = "gohtmltmpl" " vim: sw=2 ts=2 et diff --git a/pack/plugins/start/fatih-vim-go/test/parse.go b/pack/plugins/start/fatih-vim-go/test/parse.go new file mode 100644 index 00000000..7e296a79 --- /dev/null +++ b/pack/plugins/start/fatih-vim-go/test/parse.go @@ -0,0 +1,2096 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// golang.org/x/net/html/parse.go + +package html + +import ( + "errors" + "fmt" + "io" + "strings" + + a "golang.org/x/net/html/atom" +) + +// A parser implements the HTML5 parsing algorithm: +// https://html.spec.whatwg.org/multipage/syntax.html#tree-construction +type parser struct { + // tokenizer provides the tokens for the parser. + tokenizer *Tokenizer + // tok is the most recently read token. + tok Token + // Self-closing tags like
are treated as start tags, except that + // hasSelfClosingToken is set while they are being processed. + hasSelfClosingToken bool + // doc is the document root element. + doc *Node + // The stack of open elements (section 12.2.3.2) and active formatting + // elements (section 12.2.3.3). + oe, afe nodeStack + // Element pointers (section 12.2.3.4). + head, form *Node + // Other parsing state flags (section 12.2.3.5). + scripting, framesetOK bool + // im is the current insertion mode. + im insertionMode + // originalIM is the insertion mode to go back to after completing a text + // or inTableText insertion mode. + originalIM insertionMode + // fosterParenting is whether new elements should be inserted according to + // the foster parenting rules (section 12.2.5.3). + fosterParenting bool + // quirks is whether the parser is operating in "quirks mode." + quirks bool + // fragment is whether the parser is parsing an HTML fragment. + fragment bool + // context is the context element when parsing an HTML fragment + // (section 12.4). + context *Node +} + +func (p *parser) top() *Node { + if n := p.oe.top(); n != nil { + return n + } + return p.doc +} + +// Stop tags for use in popUntil. These come from section 12.2.3.2. +var ( + defaultScopeStopTags = map[string][]a.Atom{ + "": {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object, a.Template}, + "math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext}, + "svg": {a.Desc, a.ForeignObject, a.Title}, + } +) + +type scope int + +const ( + defaultScope scope = iota + listItemScope + buttonScope + tableScope + tableRowScope + tableBodyScope + selectScope +) + +// popUntil pops the stack of open elements at the highest element whose tag +// is in matchTags, provided there is no higher element in the scope's stop +// tags (as defined in section 12.2.3.2). It returns whether or not there was +// such an element. If there was not, popUntil leaves the stack unchanged. +// +// For example, the set of stop tags for table scope is: "html", "table". If +// the stack was: +// ["html", "body", "font", "table", "b", "i", "u"] +// then popUntil(tableScope, "font") would return false, but +// popUntil(tableScope, "i") would return true and the stack would become: +// ["html", "body", "font", "table", "b"] +// +// If an element's tag is in both the stop tags and matchTags, then the stack +// will be popped and the function returns true (provided, of course, there was +// no higher element in the stack that was also in the stop tags). For example, +// popUntil(tableScope, "table") returns true and leaves: +// ["html", "body", "font"] +func (p *parser) popUntil(s scope, matchTags ...a.Atom) bool { + if i := p.indexOfElementInScope(s, matchTags...); i != -1 { + p.oe = p.oe[:i] + return true + } + return false +} + +// indexOfElementInScope returns the index in p.oe of the highest element whose +// tag is in matchTags that is in scope. If no matching element is in scope, it +// returns -1. +func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + if p.oe[i].Namespace == "" { + for _, t := range matchTags { + if t == tagAtom { + return i + } + } + switch s { + case defaultScope: + // No-op. + case listItemScope: + if tagAtom == a.Ol || tagAtom == a.Ul { + return -1 + } + case buttonScope: + if tagAtom == a.Button { + return -1 + } + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table { + return -1 + } + case selectScope: + if tagAtom != a.Optgroup && tagAtom != a.Option { + return -1 + } + default: + panic("unreachable") + } + } + switch s { + case defaultScope, listItemScope, buttonScope: + for _, t := range defaultScopeStopTags[p.oe[i].Namespace] { + if t == tagAtom { + return -1 + } + } + } + } + return -1 +} + +// elementInScope is like popUntil, except that it doesn't modify the stack of +// open elements. +func (p *parser) elementInScope(s scope, matchTags ...a.Atom) bool { + return p.indexOfElementInScope(s, matchTags...) != -1 +} + +// clearStackToContext pops elements off the stack of open elements until a +// scope-defined element is found. +func (p *parser) clearStackToContext(s scope) { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + switch s { + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table { + p.oe = p.oe[:i+1] + return + } + case tableRowScope: + if tagAtom == a.Html || tagAtom == a.Tr { + p.oe = p.oe[:i+1] + return + } + case tableBodyScope: + if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead { + p.oe = p.oe[:i+1] + return + } + default: + panic("unreachable") + } + } +} + +// generateImpliedEndTags pops nodes off the stack of open elements as long as +// the top node has a tag name of dd, dt, li, option, optgroup, p, rp, or rt. +// If exceptions are specified, nodes with that name will not be popped off. +func (p *parser) generateImpliedEndTags(exceptions ...string) { + var i int +loop: + for i = len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + if n.Type == ElementNode { + switch n.DataAtom { + case a.Dd, a.Dt, a.Li, a.Option, a.Optgroup, a.P, a.Rp, a.Rt: + for _, except := range exceptions { + if n.Data == except { + break loop + } + } + continue + } + } + break + } + + p.oe = p.oe[:i+1] +} + +// addChild adds a child node n to the top element, and pushes n onto the stack +// of open elements if it is an element node. +func (p *parser) addChild(n *Node) { + if p.shouldFosterParent() { + p.fosterParent(n) + } else { + p.top().AppendChild(n) + } + + if n.Type == ElementNode { + p.oe = append(p.oe, n) + } +} + +// shouldFosterParent returns whether the next node to be added should be +// foster parented. +func (p *parser) shouldFosterParent() bool { + if p.fosterParenting { + switch p.top().DataAtom { + case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: + return true + } + } + return false +} + +// fosterParent adds a child node according to the foster parenting rules. +// Section 12.2.5.3, "foster parenting". +func (p *parser) fosterParent(n *Node) { + var table, parent, prev *Node + var i int + for i = len(p.oe) - 1; i >= 0; i-- { + if p.oe[i].DataAtom == a.Table { + table = p.oe[i] + break + } + } + + if table == nil { + // The foster parent is the html element. + parent = p.oe[0] + } else { + parent = table.Parent + } + if parent == nil { + parent = p.oe[i-1] + } + + if table != nil { + prev = table.PrevSibling + } else { + prev = parent.LastChild + } + if prev != nil && prev.Type == TextNode && n.Type == TextNode { + prev.Data += n.Data + return + } + + parent.InsertBefore(n, table) +} + +// addText adds text to the preceding node if it is a text node, or else it +// calls addChild with a new text node. +func (p *parser) addText(text string) { + if text == "" { + return + } + + if p.shouldFosterParent() { + p.fosterParent(&Node{ + Type: TextNode, + Data: text, + }) + return + } + + t := p.top() + if n := t.LastChild; n != nil && n.Type == TextNode { + n.Data += text + return + } + p.addChild(&Node{ + Type: TextNode, + Data: text, + }) +} + +// addElement adds a child element based on the current token. +func (p *parser) addElement() { + p.addChild(&Node{ + Type: ElementNode, + DataAtom: p.tok.DataAtom, + Data: p.tok.Data, + Attr: p.tok.Attr, + }) +} + +// Section 12.2.3.3. +func (p *parser) addFormattingElement() { + tagAtom, attr := p.tok.DataAtom, p.tok.Attr + p.addElement() + + // Implement the Noah's Ark clause, but with three per family instead of two. + identicalElements := 0 +findIdenticalElements: + for i := len(p.afe) - 1; i >= 0; i-- { + n := p.afe[i] + if n.Type == scopeMarkerNode { + break + } + if n.Type != ElementNode { + continue + } + if n.Namespace != "" { + continue + } + if n.DataAtom != tagAtom { + continue + } + if len(n.Attr) != len(attr) { + continue + } + compareAttributes: + for _, t0 := range n.Attr { + for _, t1 := range attr { + if t0.Key == t1.Key && t0.Namespace == t1.Namespace && t0.Val == t1.Val { + // Found a match for this attribute, continue with the next attribute. + continue compareAttributes + } + } + // If we get here, there is no attribute that matches a. + // Therefore the element is not identical to the new one. + continue findIdenticalElements + } + + identicalElements++ + if identicalElements >= 3 { + p.afe.remove(n) + } + } + + p.afe = append(p.afe, p.top()) +} + +// Section 12.2.3.3. +func (p *parser) clearActiveFormattingElements() { + for { + n := p.afe.pop() + if len(p.afe) == 0 || n.Type == scopeMarkerNode { + return + } + } +} + +// Section 12.2.3.3. +func (p *parser) reconstructActiveFormattingElements() { + n := p.afe.top() + if n == nil { + return + } + if n.Type == scopeMarkerNode || p.oe.index(n) != -1 { + return + } + i := len(p.afe) - 1 + for n.Type != scopeMarkerNode && p.oe.index(n) == -1 { + if i == 0 { + i = -1 + break + } + i-- + n = p.afe[i] + } + for { + i++ + clone := p.afe[i].clone() + p.addChild(clone) + p.afe[i] = clone + if i == len(p.afe)-1 { + break + } + } +} + +// Section 12.2.4. +func (p *parser) acknowledgeSelfClosingTag() { + p.hasSelfClosingToken = false +} + +// An insertion mode (section 12.2.3.1) is the state transition function from +// a particular state in the HTML5 parser's state machine. It updates the +// parser's fields depending on parser.tok (where ErrorToken means EOF). +// It returns whether the token was consumed. +type insertionMode func(*parser) bool + +// setOriginalIM sets the insertion mode to return to after completing a text or +// inTableText insertion mode. +// Section 12.2.3.1, "using the rules for". +func (p *parser) setOriginalIM() { + if p.originalIM != nil { + panic("html: bad parser state: originalIM was set twice") + } + p.originalIM = p.im +} + +// Section 12.2.3.1, "reset the insertion mode". +func (p *parser) resetInsertionMode() { + for i := len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + if i == 0 && p.context != nil { + n = p.context + } + + switch n.DataAtom { + case a.Select: + p.im = inSelectIM + case a.Td, a.Th: + p.im = inCellIM + case a.Tr: + p.im = inRowIM + case a.Tbody, a.Thead, a.Tfoot: + p.im = inTableBodyIM + case a.Caption: + p.im = inCaptionIM + case a.Colgroup: + p.im = inColumnGroupIM + case a.Table: + p.im = inTableIM + case a.Head: + p.im = inBodyIM + case a.Body: + p.im = inBodyIM + case a.Frameset: + p.im = inFramesetIM + case a.Html: + p.im = beforeHeadIM + default: + continue + } + return + } + p.im = inBodyIM +} + +const whitespace = " \t\r\n\f" + +// Section 12.2.5.4.1. +func initialIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + n, quirks := parseDoctype(p.tok.Data) + p.doc.AppendChild(n) + p.quirks = quirks + p.im = beforeHTMLIM + return true + } + p.quirks = true + p.im = beforeHTMLIM + return false +} + +// Section 12.2.5.4.2. +func beforeHTMLIM(p *parser) bool { + switch p.tok.Type { + case DoctypeToken: + // Ignore the token. + return true + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + if p.tok.DataAtom == a.Html { + p.addElement() + p.im = beforeHeadIM + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + } + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false +} + +// Section 12.2.5.4.3. +func beforeHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Head: + p.addElement() + p.head = p.top() + p.im = inHeadIM + return true + case a.Html: + return inBodyIM(p) + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false +} + +// Section 12.2.5.4.4. +func inHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + s := strings.TrimLeft(p.tok.Data, whitespace) + if len(s) < len(p.tok.Data) { + // Add the initial whitespace to the current node. + p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) + if s == "" { + return true + } + p.tok.Data = s + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Html: + return inBodyIM(p) + case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta: + p.addElement() + p.oe.pop() + p.acknowledgeSelfClosingTag() + return true + case a.Script, a.Title, a.Noscript, a.Noframes, a.Style: + p.addElement() + p.setOriginalIM() + p.im = textIM + return true + case a.Head: + // Ignore the token. + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head: + n := p.oe.pop() + if n.DataAtom != a.Head { + panic("html: bad parser state: element not found, in the in-head insertion mode") + } + p.im = afterHeadIM + return true + case a.Body, a.Html, a.Br: + p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) + return false +} + +// Section 12.2.5.4.6. +func afterHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + s := strings.TrimLeft(p.tok.Data, whitespace) + if len(s) < len(p.tok.Data) { + // Add the initial whitespace to the current node. + p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) + if s == "" { + return true + } + p.tok.Data = s + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Html: + return inBodyIM(p) + case a.Body: + p.addElement() + p.framesetOK = false + p.im = inBodyIM + return true + case a.Frameset: + p.addElement() + p.im = inFramesetIM + return true + case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title: + p.oe = append(p.oe, p.head) + defer p.oe.remove(p.head) + return inHeadIM(p) + case a.Head: + // Ignore the token. + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Body, a.Html, a.Br: + // Drop down to creating an implied tag. + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Body, a.Body.String()) + p.framesetOK = true + return false +} + +// copyAttributes copies attributes of src not found on dst to dst. +func copyAttributes(dst *Node, src Token) { + if len(src.Attr) == 0 { + return + } + attr := map[string]string{} + for _, t := range dst.Attr { + attr[t.Key] = t.Val + } + for _, t := range src.Attr { + if _, ok := attr[t.Key]; !ok { + dst.Attr = append(dst.Attr, t) + attr[t.Key] = t.Val + } + } +} + +// Section 12.2.5.4.7. +func inBodyIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + d := p.tok.Data + switch n := p.oe.top(); n.DataAtom { + case a.Pre, a.Listing: + if n.FirstChild == nil { + // Ignore a newline at the start of a
 block.
+				if d != "" && d[0] == '\r' {
+					d = d[1:]
+				}
+				if d != "" && d[0] == '\n' {
+					d = d[1:]
+				}
+			}
+		}
+		d = strings.Replace(d, "\x00", "", -1)
+		if d == "" {
+			return true
+		}
+		p.reconstructActiveFormattingElements()
+		p.addText(d)
+		if p.framesetOK && strings.TrimLeft(d, whitespace) != "" {
+			// There were non-whitespace characters inserted.
+			p.framesetOK = false
+		}
+	case StartTagToken:
+		switch p.tok.DataAtom {
+		case a.Html:
+			copyAttributes(p.oe[0], p.tok)
+		case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
+			return inHeadIM(p)
+		case a.Body:
+			if len(p.oe) >= 2 {
+				body := p.oe[1]
+				if body.Type == ElementNode && body.DataAtom == a.Body {
+					p.framesetOK = false
+					copyAttributes(body, p.tok)
+				}
+			}
+		case a.Frameset:
+			if !p.framesetOK || len(p.oe) < 2 || p.oe[1].DataAtom != a.Body {
+				// Ignore the token.
+				return true
+			}
+			body := p.oe[1]
+			if body.Parent != nil {
+				body.Parent.RemoveChild(body)
+			}
+			p.oe = p.oe[:1]
+			p.addElement()
+			p.im = inFramesetIM
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Menu, a.Nav, a.Ol, a.P, a.Section, a.Summary, a.Ul:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(buttonScope, a.P)
+			switch n := p.top(); n.DataAtom {
+			case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+				p.oe.pop()
+			}
+			p.addElement()
+		case a.Pre, a.Listing:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			// The newline, if any, will be dealt with by the TextToken case.
+			p.framesetOK = false
+		case a.Form:
+			if p.form == nil {
+				p.popUntil(buttonScope, a.P)
+				p.addElement()
+				p.form = p.top()
+			}
+		case a.Li:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Li:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Dd, a.Dt:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Dd, a.Dt:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Plaintext:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Button:
+			p.popUntil(defaultScope, a.Button)
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+		case a.A:
+			for i := len(p.afe) - 1; i >= 0 && p.afe[i].Type != scopeMarkerNode; i-- {
+				if n := p.afe[i]; n.Type == ElementNode && n.DataAtom == a.A {
+					p.inBodyEndTagFormatting(a.A)
+					p.oe.remove(n)
+					p.afe.remove(n)
+					break
+				}
+			}
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.Nobr:
+			p.reconstructActiveFormattingElements()
+			if p.elementInScope(defaultScope, a.Nobr) {
+				p.inBodyEndTagFormatting(a.Nobr)
+				p.reconstructActiveFormattingElements()
+			}
+			p.addFormattingElement()
+		case a.Applet, a.Marquee, a.Object:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.afe = append(p.afe, &scopeMarker)
+			p.framesetOK = false
+		case a.Table:
+			if !p.quirks {
+				p.popUntil(buttonScope, a.P)
+			}
+			p.addElement()
+			p.framesetOK = false
+			p.im = inTableIM
+			return true
+		case a.Area, a.Br, a.Embed, a.Img, a.Input, a.Keygen, a.Wbr:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			if p.tok.DataAtom == a.Input {
+				for _, t := range p.tok.Attr {
+					if t.Key == "type" {
+						if strings.ToLower(t.Val) == "hidden" {
+							// Skip setting framesetOK = false
+							return true
+						}
+					}
+				}
+			}
+			p.framesetOK = false
+		case a.Param, a.Source, a.Track:
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+		case a.Hr:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			p.framesetOK = false
+		case a.Image:
+			p.tok.DataAtom = a.Img
+			p.tok.Data = a.Img.String()
+			return false
+		case a.Isindex:
+			if p.form != nil {
+				// Ignore the token.
+				return true
+			}
+			action := ""
+			prompt := "This is a searchable index. Enter search keywords: "
+			attr := []Attribute{{Key: "name", Val: "isindex"}}
+			for _, t := range p.tok.Attr {
+				switch t.Key {
+				case "action":
+					action = t.Val
+				case "name":
+					// Ignore the attribute.
+				case "prompt":
+					prompt = t.Val
+				default:
+					attr = append(attr, t)
+				}
+			}
+			p.acknowledgeSelfClosingTag()
+			p.popUntil(buttonScope, a.P)
+			p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
+			if action != "" {
+				p.form.Attr = []Attribute{{Key: "action", Val: action}}
+			}
+			p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
+			p.parseImpliedToken(StartTagToken, a.Label, a.Label.String())
+			p.addText(prompt)
+			p.addChild(&Node{
+				Type:     ElementNode,
+				DataAtom: a.Input,
+				Data:     a.Input.String(),
+				Attr:     attr,
+			})
+			p.oe.pop()
+			p.parseImpliedToken(EndTagToken, a.Label, a.Label.String())
+			p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
+			p.parseImpliedToken(EndTagToken, a.Form, a.Form.String())
+		case a.Textarea:
+			p.addElement()
+			p.setOriginalIM()
+			p.framesetOK = false
+			p.im = textIM
+		case a.Xmp:
+			p.popUntil(buttonScope, a.P)
+			p.reconstructActiveFormattingElements()
+			p.framesetOK = false
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Iframe:
+			p.framesetOK = false
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Noembed, a.Noscript:
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Select:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+			p.im = inSelectIM
+			return true
+		case a.Optgroup, a.Option:
+			if p.top().DataAtom == a.Option {
+				p.oe.pop()
+			}
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		case a.Rp, a.Rt:
+			if p.elementInScope(defaultScope, a.Ruby) {
+				p.generateImpliedEndTags()
+			}
+			p.addElement()
+		case a.Math, a.Svg:
+			p.reconstructActiveFormattingElements()
+			if p.tok.DataAtom == a.Math {
+				adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments)
+			} else {
+				adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments)
+			}
+			adjustForeignAttributes(p.tok.Attr)
+			p.addElement()
+			p.top().Namespace = p.tok.Data
+			if p.hasSelfClosingToken {
+				p.oe.pop()
+				p.acknowledgeSelfClosingTag()
+			}
+			return true
+		case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
+			// Ignore the token.
+		default:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		}
+	case EndTagToken:
+		switch p.tok.DataAtom {
+		case a.Body:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.im = afterBodyIM
+			}
+		case a.Html:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.parseImpliedToken(EndTagToken, a.Body, a.Body.String())
+				return false
+			}
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.Form:
+			node := p.form
+			p.form = nil
+			i := p.indexOfElementInScope(defaultScope, a.Form)
+			if node == nil || i == -1 || p.oe[i] != node {
+				// Ignore the token.
+				return true
+			}
+			p.generateImpliedEndTags()
+			p.oe.remove(node)
+		case a.P:
+			if !p.elementInScope(buttonScope, a.P) {
+				p.parseImpliedToken(StartTagToken, a.P, a.P.String())
+			}
+			p.popUntil(buttonScope, a.P)
+		case a.Li:
+			p.popUntil(listItemScope, a.Li)
+		case a.Dd, a.Dt:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(defaultScope, a.H1, a.H2, a.H3, a.H4, a.H5, a.H6)
+		case a.A, a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.Nobr, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.inBodyEndTagFormatting(p.tok.DataAtom)
+		case a.Applet, a.Marquee, a.Object:
+			if p.popUntil(defaultScope, p.tok.DataAtom) {
+				p.clearActiveFormattingElements()
+			}
+		case a.Br:
+			p.tok.Type = StartTagToken
+			return false
+		default:
+			p.inBodyEndTagOther(p.tok.DataAtom)
+		}
+	case CommentToken:
+		p.addChild(&Node{
+			Type: CommentNode,
+			Data: p.tok.Data,
+		})
+	}
+
+	return true
+}
+
+func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
+	// This is the "adoption agency" algorithm, described at
+	// https://html.spec.whatwg.org/multipage/syntax.html#adoptionAgency
+
+	// TODO: this is a fairly literal line-by-line translation of that algorithm.
+	// Once the code successfully parses the comprehensive test suite, we should
+	// refactor this code to be more idiomatic.
+
+	// Steps 1-4. The outer loop.
+	for i := 0; i < 8; i++ {
+		// Step 5. Find the formatting element.
+		var formattingElement *Node
+		for j := len(p.afe) - 1; j >= 0; j-- {
+			if p.afe[j].Type == scopeMarkerNode {
+				break
+			}
+			if p.afe[j].DataAtom == tagAtom {
+				formattingElement = p.afe[j]
+				break
+			}
+		}
+		if formattingElement == nil {
+			p.inBodyEndTagOther(tagAtom)
+			return
+		}
+		feIndex := p.oe.index(formattingElement)
+		if feIndex == -1 {
+			p.afe.remove(formattingElement)
+			return
+		}
+		if !p.elementInScope(defaultScope, tagAtom) {
+			// Ignore the tag.
+			return
+		}
+
+		// Steps 9-10. Find the furthest block.
+		var furthestBlock *Node
+		for _, e := range p.oe[feIndex:] {
+			if isSpecialElement(e) {
+				furthestBlock = e
+				break
+			}
+		}
+		if furthestBlock == nil {
+			e := p.oe.pop()
+			for e != formattingElement {
+				e = p.oe.pop()
+			}
+			p.afe.remove(e)
+			return
+		}
+
+		// Steps 11-12. Find the common ancestor and bookmark node.
+		commonAncestor := p.oe[feIndex-1]
+		bookmark := p.afe.index(formattingElement)
+
+		// Step 13. The inner loop. Find the lastNode to reparent.
+		lastNode := furthestBlock
+		node := furthestBlock
+		x := p.oe.index(node)
+		// Steps 13.1-13.2
+		for j := 0; j < 3; j++ {
+			// Step 13.3.
+			x--
+			node = p.oe[x]
+			// Step 13.4 - 13.5.
+			if p.afe.index(node) == -1 {
+				p.oe.remove(node)
+				continue
+			}
+			// Step 13.6.
+			if node == formattingElement {
+				break
+			}
+			// Step 13.7.
+			clone := node.clone()
+			p.afe[p.afe.index(node)] = clone
+			p.oe[p.oe.index(node)] = clone
+			node = clone
+			// Step 13.8.
+			if lastNode == furthestBlock {
+				bookmark = p.afe.index(node) + 1
+			}
+			// Step 13.9.
+			if lastNode.Parent != nil {
+				lastNode.Parent.RemoveChild(lastNode)
+			}
+			node.AppendChild(lastNode)
+			// Step 13.10.
+			lastNode = node
+		}
+
+		// Step 14. Reparent lastNode to the common ancestor,
+		// or for misnested table nodes, to the foster parent.
+		if lastNode.Parent != nil {
+			lastNode.Parent.RemoveChild(lastNode)
+		}
+		switch commonAncestor.DataAtom {
+		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
+			p.fosterParent(lastNode)
+		default:
+			commonAncestor.AppendChild(lastNode)
+		}
+
+		// Steps 15-17. Reparent nodes from the furthest block's children
+		// to a clone of the formatting element.
+		clone := formattingElement.clone()
+		reparentChildren(clone, furthestBlock)
+		furthestBlock.AppendChild(clone)
+
+		// Step 18. Fix up the list of active formatting elements.
+		if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark {
+			// Move the bookmark with the rest of the list.
+			bookmark--
+		}
+		p.afe.remove(formattingElement)
+		p.afe.insert(bookmark, clone)
+
+		// Step 19. Fix up the stack of open elements.
+		p.oe.remove(formattingElement)
+		p.oe.insert(p.oe.index(furthestBlock)+1, clone)
+	}
+}
+
+// inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM.
+// "Any other end tag" handling from 12.2.5.5 The rules for parsing tokens in foreign content
+// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inforeign
+func (p *parser) inBodyEndTagOther(tagAtom a.Atom) {
+	for i := len(p.oe) - 1; i >= 0; i-- {
+		if p.oe[i].DataAtom == tagAtom {
+			p.oe = p.oe[:i]
+			break
+		}
+		if isSpecialElement(p.oe[i]) {
+			break
+		}
+	}
+}
+
+// Section 12.2.5.4.8.
+func textIM(p *parser) bool {
+	switch p.tok.Type {
+	case ErrorToken:
+		p.oe.pop()
+	case TextToken:
+		d := p.tok.Data
+		if n := p.oe.top(); n.DataAtom == a.Textarea && n.FirstChild == nil {
+			// Ignore a newline at the start of a