├── .gitignore ├── README.md ├── autoload ├── bazel.vim └── health │ └── bazel.vim ├── compiler └── bazel.vim ├── doc └── bazel.txt └── plugin └── bazel.vim /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-bazel 2 | 3 | This plugin allows invoking bazel from vim 4 | 5 | ## Commands 6 | 7 | ``` 8 | :Bazel {command} [arguments...] 9 | ``` 10 | 11 | This is identical to calling :!bazel {command} [arguments...] except 12 | when the command is "build" or "test", in which case, it provides some 13 | niceties 14 | 15 | * Errors from the compiler are loaded into the quickfix list 16 | * Test failure logs are loaded into the quickfix list 17 | * Parses errors due to missing dependencies in bazel target definitions 18 | * If no targets are specified, it calls bazel for the current file 19 | * If the current file is a BUILD file, calls the command for all the 20 | targets in the BUILD file 21 | 22 | Some other general improvements: 23 | 24 | * Adds bazel-bin and bazel- to the path so `gf` and other 25 | related command work seamlessly 26 | * Provides the :Bld command to jump to BUILD files 27 | 28 | Note: It is currently tuned for C++ development in the sense that the 29 | errorformat is set to recognize error messages from gcc/clang 30 | 31 | ## Command completion 32 | 33 | To enable completion of targets in bazel commands, vim-bazel tries to 34 | determine the location of the bazel bash completion script. If target 35 | completion does not work, set g:bazel_bash_completion_path to the path 36 | of the bazel bash completion script on your system. To locate the script 37 | on your system see: https://bazel.build/install/completion 38 | 39 | ``` 40 | let g:bazel_bash_completion_path = "/usr/local/etc/bash_completion.d/bazel-complete.bash" 41 | ``` 42 | 43 | ## Asynchronous builds 44 | 45 | vim-bazel can be used with async plugins to run builds in the background. 46 | This can be done by setting g:bazel_make_command. Here are some examples 47 | on how to set this up with various plugins: 48 | 49 | ``` 50 | tpope/vim-dispatch let g:bazel_make_command = "Make" 51 | skywind3000/asyncrun.vim let g:bazel_make_command = "AsyncRun -program=make" 52 | hauleth/asyncdo.vim let g:bazel_make_command = "AsyncDo bazel" 53 | ``` 54 | 55 | ## Might do in the future 56 | 57 | * Unit tests 58 | * Support for languages besides C++ 59 | 60 | ## License 61 | 62 | Copyright (c) Lakshay Garg. Distributed under the same terms as Vim itself. See `:help license`. 63 | -------------------------------------------------------------------------------- /autoload/bazel.vim: -------------------------------------------------------------------------------- 1 | function! bazel#ModifyPath(bazel_root) 2 | if !isdirectory(a:bazel_root . "/bazel-bin") 3 | return 4 | endif 5 | 6 | let project_dir = fnamemodify(a:bazel_root, ":t") 7 | let &path = &path . "," . resolve(a:bazel_root . "/bazel-bin") 8 | let &path = &path . "," . resolve(a:bazel_root . "/bazel-" . project_dir) 9 | endfunction 10 | 11 | " Jump to build file and search for filename where this command was called 12 | function! bazel#JumpToBuildFile() 13 | let current_file = expand("%:t") 14 | let pattern = "\\V\\<" . current_file . "\\>" 15 | exe "edit" findfile("BUILD", ".;") 16 | call search(pattern, "w", 0, 500) 17 | endfunction 18 | 19 | 20 | function! s:PathRelativeToWsRoot(path) abort 21 | let full_path = fnamemodify(a:path, ":p") 22 | " cd into the WORKSPACE root 23 | exe "cd" fnamemodify(findfile("WORKSPACE", ".;"), ":p:h") 24 | " get path to file relative to current dir (WORKSPACE root) 25 | let rel_path = fnamemodify(full_path, ":.") 26 | cd - 27 | return rel_path 28 | endfunction 29 | 30 | function! s:GetTargetsFromContext() abort 31 | let rel_fname = PathRelativeToWsRoot(expand('%')) 32 | 33 | " Is the current file a BUILD file? 34 | if fnamemodify(rel_fname, ":t") ==# "BUILD" 35 | let package_path = fnamemodify(rel_fname, ":h") 36 | return "//" . package_path . ":all" 37 | endif 38 | 39 | " Assume that the current file is a source file 40 | let build_file_path = findfile("BUILD", ".;") 41 | let relative_path = PathRelativeToWsRoot(build_file_path) 42 | let package_path = fnamemodify(relative_path, ":h") 43 | let package_spec = "//" . package_path . "/..." 44 | let fmt = "$(bazel cquery --collapse_duplicate_defines --noshow_timestamps --output=starlark 'kind(rule, rdeps(%s, %s, 1))' || echo CQUERY_FAILED)" 45 | return printf(fmt, package_spec, rel_fname) 46 | endfunction 47 | 48 | function! bazel#Execute(action, ...) abort 49 | let flags = ['--collapse_duplicate_defines', '--noshow_timestamps', '--color=no'] 50 | let targets = [] 51 | 52 | let i = 0 53 | 54 | " Add all arguments that start with "--" to flags 55 | while i < a:0 && a:000[i] =~ "^--." 56 | call add(flags, a:000[i]) 57 | let i = i + 1 58 | endwhile 59 | 60 | " Add everything until we find "--" to targets 61 | while i < a:0 && a:000[i] !~ "^--" 62 | call add(targets, a:000[i]) 63 | let i = i + 1 64 | endwhile 65 | 66 | " Everything that remains gets added to this list 67 | let rest = a:000[i:] 68 | 69 | if empty(targets) 70 | let targets = [GetTargetsFromContext()] 71 | endif 72 | 73 | compiler bazel 74 | exe get(g:, "bazel_make_command", "make") join( 75 | \ [a:action] + flags + targets + rest) 76 | endfunction 77 | 78 | " Completions for the :Bazel command {{{ 79 | " Completions are extracted from the bash bazel completion function. 80 | " Taken from https://github.com/bazelbuild/vim-bazel/blob/master/autoload/bazel.vim 81 | " with minor modifications 82 | function! s:CompletionsFromBash(arglead, line, pos) abort 83 | " The bash complete script does not truly support autocompleting within a 84 | " word, return nothing here rather than returning bad suggestions. 85 | if a:pos + 1 < strlen(a:line) 86 | return [] 87 | endif 88 | 89 | let cmd = substitute(a:line[0:a:pos], '\v\w+', 'bazel', '') 90 | 91 | let comp_words = split(cmd, '\v\s+') 92 | if cmd =~# '\v\s$' 93 | call add(comp_words, '') 94 | endif 95 | let comp_line = join(comp_words) 96 | 97 | " Note: Bashisms below are intentional. We invoke this via bash explicitly, 98 | " and it should work correctly even if &shell is actually not bash-compatible. 99 | 100 | " Extracts the bash completion command, should be something like: 101 | " _bazel__complete 102 | let complete_wrapper_command = ' $(complete -p ' . comp_words[0] . 103 | \ ' | sed "s/.*-F \\([^ ]*\\) .*/\\1/")' 104 | 105 | " Build a list of all the arguments that have to be passed in to autocomplete. 106 | let comp_arguments = { 107 | \ 'COMP_LINE' : '"' .comp_line . '"', 108 | \ 'COMP_WORDS' : '(' . comp_line . ')', 109 | \ 'COMP_CWORD' : string(len(comp_words) - 1), 110 | \ 'COMP_POINT' : string(strlen(comp_line)), 111 | \ } 112 | let comp_arguments_string = 113 | \ join(map(items(comp_arguments), 'v:val[0] . "=" . v:val[1]')) 114 | 115 | " Build the command to run with bash 116 | let shell_script = shellescape(printf( 117 | \ 'source %s; export %s; %s && echo ${COMPREPLY[*]}', 118 | \ g:bazel_bash_completion_path, 119 | \ comp_arguments_string, 120 | \ complete_wrapper_command)) 121 | 122 | let bash_command = 'bash -norc -i -c ' . shell_script . ' 2>/dev/null' 123 | let result = system(bash_command) 124 | 125 | let bash_suggestions = split(result) 126 | " The bash complete not include anything before the colon, add it. 127 | let word_prefix = substitute(comp_words[-1], '\v[^:]+$', '', '') 128 | return map(bash_suggestions, 'word_prefix . v:val') 129 | endfunction 130 | 131 | 132 | let s:bazel_commands=[] 133 | function! bazel#Completions(arglead, cmdline, cursorpos) abort 134 | " Initialize s:bazel_commands if it hasn't been initialized 135 | if empty(s:bazel_commands) 136 | let s:bazel_commands = split(system("bazel help completion | awk -F'\"' '/BAZEL_COMMAND_LIST=/ { print $2 }'")) 137 | endif 138 | 139 | " Complete commands 140 | let cmdlist = split(a:cmdline) 141 | if len(cmdlist) == 1 || (len(cmdlist) == 2 && index(s:bazel_commands, cmdlist[-1]) < 0) 142 | return filter(deepcopy(s:bazel_commands), printf('v:val =~ "^%s"', a:arglead)) 143 | endif 144 | 145 | " Complete targets by using the bash completion logic 146 | " We wrap this function because if completions from bash are used directly, 147 | " they also include commandline flags which users don't need in most cases 148 | return exists("g:bazel_bash_completion_path") 149 | \ ? CompletionsFromBash(a:arglead, a:cmdline, a:cursorpos) 150 | \ : [] 151 | endfunction 152 | " }}} 153 | 154 | " Test cases 155 | " ============================================================================== 156 | " * Build/test current file without passing args to Bazel build/test 157 | " * Build/test current file when pwd is not the bazel project root 158 | " * Build/test another file by passing args to Bazel build/test 159 | " * Build/test from a BUILD file 160 | " * Bazel run a binary 161 | 162 | " vim:foldmethod=marker 163 | -------------------------------------------------------------------------------- /autoload/health/bazel.vim: -------------------------------------------------------------------------------- 1 | function! health#bazel#check() abort 2 | if executable('bazel') 3 | call health#report_ok("bazel is installed") 4 | else 5 | call health#report_error("bazel is NOT installed") 6 | endif 7 | 8 | if exists("g:bazel_bash_completion_path") 9 | if filereadable(g:bazel_bash_completion_path) 10 | call health#report_ok(printf("Found bazel bash completion script at: %s", g:bazel_bash_completion_path)) 11 | else 12 | call health#report_error( 13 | \ printf("g:bazel_bash_completion_path (%s) does NOT exist", g:bazel_bash_completion_path), 14 | \ [ 15 | \ "Ensure that g:bazel_bash_completion_path points to a valid file", 16 | \ "unset g:bazel_bash_completion_path and let the plugin find the completion script" 17 | \ ]) 18 | endif 19 | else 20 | call health#report_warn( 21 | \ "A bazel bash completion script was not found", 22 | \ [ 23 | \ "See https://bazel.build/install/completion for instructions on locating the completion script", 24 | \ "Set g:bazel_bash_completion_path to point to the completion script" 25 | \ ]) 26 | endif 27 | 28 | if exists('g:bazel_make_command') 29 | call health#report_info(printf("g:bazel_make_command is set (%s)", g:bazel_make_command)) 30 | else 31 | call health#report_info("g:bazel_make_command is NOT set (builds will be synchronous)") 32 | endif 33 | endfunction 34 | -------------------------------------------------------------------------------- /compiler/bazel.vim: -------------------------------------------------------------------------------- 1 | if exists("current_compiler") 2 | finish 3 | endif 4 | let current_compiler = "bazel" 5 | 6 | let s:cpo_save = &cpo 7 | set cpo&vim 8 | 9 | if exists(":CompilerSet") != 2 10 | command -nargs=* CompilerSet setlocal 11 | endif 12 | 13 | CompilerSet makeprg=bazel 14 | 15 | " Errorformat settings 16 | " 17 | " * errorformat reference: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#errorformat 18 | " * look for message without consuming: https://stackoverflow.com/a/36959245/10923940 19 | " * errorformat explanation: https://stackoverflow.com/a/29102995/10923940 20 | " 21 | " Use %f\\ instead of %f so that isfname setting is used to match filenames 22 | " See: https://vi.stackexchange.com/a/8085/29769 23 | 24 | " Ignore this error message, it is always redundant 25 | " ERROR: ::: C++ compilation of rule '' failed (Exit 1) 26 | CompilerSet errorformat=%-GERROR:\ %f:%l:%c:\ C++\ compilation\ of\ rule\ %m 27 | CompilerSet errorformat+=%tRROR:\ %f:%l:%c:\ %m " Generic bazel error handler 28 | CompilerSet errorformat+=%tARNING:\ %f:%l:%c:\ %m " Generic bazel warning handler 29 | CompilerSet errorformat+=%tEBUG:\ %f:%l:%c:\ %m " Generic bazel debug handler 30 | 31 | " this rule is missing dependency declarations for the following files included by '' 32 | " '' 33 | " '' 34 | " ... 35 | CompilerSet errorformat+=%Ethis\ rule\ is\ %m\ the\ following\ files\ included\ by\ \'%f\': 36 | CompilerSet errorformat+=%C\ \ \'%m\' 37 | CompilerSet errorformat+=%Z 38 | 39 | " Test failures 40 | CompilerSet errorformat+=FAIL:\ %m\ (see\ %f) " FAIL: (see ) 41 | 42 | " test failures in async builds 43 | CompilerSet errorformat+=%E%*[\ ]FAILED\ in%m 44 | CompilerSet errorformat+=%C\ \ %f 45 | CompilerSet errorformat+=%Z 46 | 47 | " Errors in the build stage 48 | CompilerSet errorformat+=%f:%l:%c:\ fatal\ %trror:\ %m " ::: fatal error: 49 | CompilerSet errorformat+=%f:%l:%c:\ %trror:\ %m " ::: error: 50 | CompilerSet errorformat+=%f:%l:%c:\ %tarning:\ %m " ::: warning: 51 | CompilerSet errorformat+=%f:%l:%c:\ note:\ %m " ::: note: 52 | CompilerSet errorformat+=%f:%l:%c:\ \ \ requ%tred\ from\ here " ::: 53 | CompilerSet errorformat+=%f(%l):\ %tarning:\ %m " (): warning: 54 | CompilerSet errorformat+=%f(%l):\ %trror:\ %m " (): error: 55 | CompilerSet errorformat+=%f:%l:%c:\ %m " ::: 56 | CompilerSet errorformat+=%f\\:%l:\ %m " :: 57 | 58 | let &cpo = s:cpo_save 59 | unlet s:cpo_save 60 | -------------------------------------------------------------------------------- /doc/bazel.txt: -------------------------------------------------------------------------------- 1 | *bazel.txt* bazel integration for vim. 2 | 3 | Author: Lakshay Garg 4 | License: Same terms as Vim itself (see |license|) 5 | 6 | INTRODUCTION *bazel* *compiler-bazel* 7 | 8 | This plugin essentially provides a bazel |compiler| plugin and some plumbing 9 | around it to handle bazel quirks and make it work nicely with vim. 10 | 11 | Note: The compiler plugin is set to recognize gcc / clang error messages and 12 | will not be very useful for other programming languages in its current state. 13 | 14 | COMMANDS *bazel-commands* 15 | 16 | *bazel-:Bazel* *:Bazel* 17 | :Bazel build [targets] These commands are the only reason to use this plugin. 18 | :Bazel test [targets] They activate the bazel compiler plugin and forward to 19 | `:make`. [targets] are the bazel targets to build / test 20 | and can be auto-completed using the key. If left 21 | unspecified, the plugin tries to determine the target 22 | for the current buffer using a bazel query. When the 23 | current buffer is a BUILD file, all the targets in the 24 | file are built / tested. 25 | 26 | *bazel-:Bld* 27 | :Bld Jumps to the BUILD file for the current buffer. 28 | 29 | VARIABLES *bazel-variables* 30 | 31 | *bazel-g:bazel_bash_completion_path* 32 | g:bazel_bash_completion_path The plugin uses the bazel bash completion script 33 | for target completions. If the script is not found in 34 | its usual locations, the user can locate it manually & 35 | set this variable to the full path of the script. See: 36 | https://bazel.build/install/completion 37 | 38 | *bazel-g:bazel_make_command* 39 | g:bazel_make_command This plugin can be used with async plugins to run 40 | bazel asynchronously. This variable must be set to 41 | the `:make` replacement provided by the async plugin. 42 | Here are some examples: 43 | 44 | > 45 | tpope/vim-dispatch => "Make" 46 | skywind3000/asyncrun.vim => "AsyncRun -program=make" 47 | hauleth/asyncdo.vim => "AsyncDo bazel" 48 | < 49 | 50 | vim:tw=78:et:ft=help:norl: 51 | -------------------------------------------------------------------------------- /plugin/bazel.vim: -------------------------------------------------------------------------------- 1 | let s:bazel_workspace_file = findfile("WORKSPACE", ".;") 2 | 3 | " Bash completion script path for bazel. 4 | " In most cases, the user should not need to set it. In case you 5 | " need to, consider sending a PR to add it to the search list. 6 | " let g:bazel_bash_completion_path = ... 7 | if !exists("g:bazel_bash_completion_path") 8 | let candidates = filter([ 9 | \ "/etc/bash_completion.d/bazel", 10 | \ "/usr/local/lib/bazel/bin/bazel-complete.bash" 11 | \ ], 'filereadable(v:val)') 12 | if !empty(candidates) 13 | let g:bazel_bash_completion_path = candidates[0] 14 | endif 15 | endif 16 | 17 | " No need to continue if we are not in a bazel project 18 | if empty(s:bazel_workspace_file) 19 | finish 20 | endif 21 | 22 | " Add bazel-bin and bazel- to the path 23 | call bazel#ModifyPath(fnamemodify(s:bazel_workspace_file, ":p:h")) 24 | 25 | " Jump to the BUILD file corresponding to current source file 26 | command! Bld :call bazel#JumpToBuildFile() 27 | 28 | " Call bazel command 29 | command! -complete=customlist,bazel#Completions -nargs=+ 30 | \ Bazel :call bazel#Execute() 31 | --------------------------------------------------------------------------------