Skip to content

Commit 88978a2

Browse files
committed
init
0 parents  commit 88978a2

File tree

4 files changed

+504
-0
lines changed

4 files changed

+504
-0
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# bazel.vim - [Bazel](https://bazel.build/) build system integration for Vim
2+
3+
* <kbd>Tab</kbd> autocompletion for targets
4+
* goto-definition for BUILD files
5+
* listing references
6+
7+
and more.
8+
9+
## Default mappings
10+
11+
* `gb` - Go to `BUILD` file corresponding to the current buffer.
12+
* `gd` - Go to label definition under cursor (for `BUILD` and `.bzl` files).
13+
* `gr` - List references for label under cursor (for `BUILD` and `.bzl` files).
14+
* `<leader>p` - Print label that corresponds to current buffer.
15+
* `b<C-G>` - Print current buffer path relative to workspace.
16+
17+
## Commands
18+
19+
* `Bazel {args}` - Run Bazel command and open QuickFix when done.
20+
21+
Example:
22+
```
23+
:Bazel build //main:hello-world
24+
```
25+
26+
* `BazelDefinition {target}` - Jump to target definition.
27+
28+
Example:
29+
```
30+
:BazelDefinition //main:hello-world
31+
```
32+
33+
* `BazelReferences {target}` - List target references.
34+
35+
Example:
36+
```
37+
:BazelReferences //lib:hello-time
38+
```
39+

autoload/bazel.vim

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
vim9script noclear
2+
3+
var s_INIT = 'init'
4+
var s_NOT_BAZEL = 'not bazel'
5+
var s_DETECTED = 'detected'
6+
var s_CONFIGURED = 'configured'
7+
var s_FAILED = 'failed'
8+
var s_PENDING = 'pending'
9+
10+
var s_extra_args = ' --noshow_progress --curses=no --ui_event_filters=-info,-warning,-stderr 2>&1'
11+
12+
if !exists('g:bazel')
13+
g:bazel = {
14+
status: s_INIT,
15+
info: {},
16+
command_winid: -1,
17+
}
18+
endif
19+
20+
def S__backtrace(): list<string>
21+
var backtrace_items = expand('<stack>')->substitute('^\(\<function\>\|\<script\>\) ', '', '')->split('\.\.')[: -1]
22+
backtrace_items->remove(-1) # remove 'S__backtrace()' entry
23+
backtrace_items[-1] ..= ':'
24+
25+
var backtrace: list<string>
26+
var indent = ''
27+
for item in backtrace_items
28+
backtrace += [indent .. item]
29+
indent ..= ' '
30+
endfor
31+
return backtrace
32+
enddef
33+
34+
def S__abort(msg__a: list<string>)
35+
echohl Error
36+
for m in S__backtrace()[: -2] # -2 to remove 'S__abort()' entry
37+
echomsg m
38+
endfor
39+
echohl None
40+
41+
for m in msg__a
42+
echomsg m
43+
endfor
44+
45+
silent! interrupt()
46+
enddef
47+
48+
# removes double and trailing slashes
49+
def S__clean_path(path__a: string): string
50+
return path__a->substitute('\/\+', '/', 'g')->substitute('\/$', '', '')
51+
enddef
52+
53+
# returns file path relative to dir
54+
def S__rel_path(file_abs__a: string, dir_abs__a: string): string
55+
var path = file_abs__a
56+
->fnamemodify(':p')
57+
->fnamemodify(':s#^' .. dir_abs__a->fnamemodify(':p') .. '##')
58+
->fnamemodify(':s#^/##')->substitute('\/\+', '/', 'g')
59+
if path->empty()
60+
path = '.'
61+
endif
62+
return path->S__clean_path()
63+
enddef
64+
65+
# extracts bazel label or file path under cursor with quotes removed, if any
66+
def S__token_under_cursor(): string
67+
var isfname = &isfname
68+
set isfname+=:,@-@
69+
var path = expand('<cfile>')
70+
&isfname = isfname
71+
72+
return path
73+
enddef
74+
75+
# for bazel files returns label that corresponds to item under cursor, otherwise label for the current buffer
76+
def S__label(): string
77+
if !empty(&bt) # not a file
78+
return ''
79+
endif
80+
81+
if &ft == 'bzl'
82+
var path = expand('%:p:h')
83+
var label_prefix = path->substitute(g:bazel.info.workspace, '/', '')
84+
85+
var label = S__token_under_cursor()
86+
if label =~ '^//' || label =~ '^@' # label
87+
return label
88+
elseif label =~ '^:' # relative label
89+
label = label[1 :]
90+
endif
91+
return label_prefix .. ':' .. label
92+
else
93+
S__configure()
94+
95+
if g:bazel.status != s_CONFIGURED
96+
return ''
97+
endif
98+
99+
var path = expand('%:p')->S__rel_path(g:bazel.info.workspace)
100+
var output = systemlist($'cd {g:bazel.info.workspace}; set -o pipefail; bazel query {path} --output label {s_extra_args}')
101+
102+
if v:shell_error != 0 || empty(output)
103+
S__abort(output)
104+
endif
105+
106+
return output[0]
107+
endif
108+
enddef
109+
110+
# parses <path>:<lnum>:<col> and opens the file in current buffer
111+
def S__jump_to_location(location__a: string)
112+
var tokens = matchlist(location__a, '^\(.*\):\(\d\+\):\(\d\+\): .*$')
113+
execute 'edit' tokens[1]
114+
var lnum = tokens[2]->str2nr()
115+
if lnum > 0
116+
cursor(lnum, tokens[3]->str2nr())
117+
endif
118+
enddef
119+
120+
# opens location where the label points to in current buffer, or opens quickfix if more than one occurrence
121+
def S__jump_to_label(label__a: string)
122+
S__configure()
123+
124+
var path = label__a->substitute(':__subpackages__', '/...', '')
125+
echo 'Fetching...'
126+
var output = systemlist($'cd {g:bazel.info.workspace}; set -o pipefail; bazel query {path} --output location {s_extra_args}')
127+
echo '' | redraw
128+
129+
if v:shell_error != 0 || empty(output)
130+
S__abort(output)
131+
endif
132+
133+
if len(output) == 1
134+
S__jump_to_location(output[0])
135+
else
136+
cgetexpr output
137+
copen
138+
endif
139+
enddef
140+
141+
# runs command in terminal, then opens quickfix
142+
def S__run_command(cmd__a: string)
143+
S__configure()
144+
145+
var cmd = 'bazel ' .. cmd__a
146+
147+
if win_id2win(g:bazel.command_winid) == 0
148+
botright split
149+
g:bazel.command_winid = win_getid()
150+
else
151+
win_gotoid(g:bazel.command_winid)
152+
endif
153+
154+
var term_bufnr: number
155+
term_bufnr = term_start(cmd, {
156+
curwin: true,
157+
exit_cb: (job, exit_code) => {
158+
term_wait(term_bufnr)
159+
cgetbuffer
160+
set bt=quickfix
161+
copen
162+
setqflist([], 'r', {title: cmd})
163+
},
164+
})
165+
enddef
166+
167+
export def DetectWorkspace()
168+
if g:bazel.status != s_INIT && g:bazel.status != s_NOT_BAZEL
169+
return
170+
endif
171+
172+
var cwd = getcwd()
173+
var workspace_file = findfile('WORKSPACE', cwd .. ';')
174+
if empty(workspace_file)
175+
g:bazel.status = s_NOT_BAZEL
176+
return
177+
endif
178+
179+
g:bazel.status = s_DETECTED
180+
g:bazel.info.workspace = workspace_file->fnamemodify(':h')
181+
182+
command! -nargs=+ -complete=customlist,bazel#CompleteList Bazel S__run_command(<q-args>)
183+
command! -nargs=1 -complete=customlist,bazel#CompleteList BazelDefinition S__jump_to_label(<q-args>)
184+
command! -nargs=1 -complete=customlist,bazel#CompleteList BazelReferences S__show_references(<q-args>)
185+
186+
if !exists('g:bazel_no_default_mappings') || !g:bazel_no_default_mappings
187+
nnoremap <silent> gb <Plug>(bazel-goto-build)
188+
nnoremap <silent> b<C-G> <Plug>(bazel-print-rel)
189+
nnoremap <silent> <leader>p <Plug>(bazel-print-label)
190+
autocmd FileType bzl nnoremap <silent> <buffer> gd <Plug>(bazel-definition)
191+
autocmd FileType bzl nnoremap <silent> <buffer> gr <Plug>(bazel-references)
192+
endif
193+
194+
set errorformat^=%t%*[^:]:\ %f:%l:%c:\ %m # recognize error type in "<TYPE>: <file><lnum>:<col>: <message>" form
195+
enddef
196+
197+
def S__configure()
198+
if g:bazel.status == s_CONFIGURED || g:bazel.status == s_PENDING || g:bazel.status == s_NOT_BAZEL
199+
return
200+
endif
201+
202+
g:bazel.status = s_PENDING
203+
204+
echo 'Configuring bazel...'
205+
var output = systemlist($'set -o pipefail; bazel info {s_extra_args}')
206+
echo '' | redraw
207+
if v:shell_error != 0
208+
g:bazel.status = s_FAILED
209+
S__abort(output)
210+
endif
211+
212+
for line in output
213+
if empty(line) || line == 'Starting local Bazel server and connecting to it...'
214+
continue
215+
endif
216+
if line !~ '^\S\+: '
217+
S__abort(['Wrong line format: ' .. line])
218+
endif
219+
var [key, value; _] = line->split(': ')
220+
g:bazel.info[key] = value
221+
endfor
222+
g:bazel.status = s_CONFIGURED
223+
224+
execute $'set path^={g:bazel.info.output_path}/**'
225+
execute $'set path^={g:bazel.info.workspace}/**'
226+
# make sure '.' is first:
227+
set path-=.
228+
set path^=.
229+
enddef
230+
231+
# for non-file buffers jumps to location the label points to, otherwise jumps to BUILD file corresponding to the current buffer
232+
def S__goto_build()
233+
if !empty(&bt) # quickfix or terminal
234+
var token = S__token_under_cursor()
235+
if empty(token)
236+
return
237+
endif
238+
if token =~ '^//' || token =~ '^@' # label
239+
wincmd w
240+
S__jump_to_label(token)
241+
endif
242+
else
243+
S__configure()
244+
var path = expand('%:p')->S__rel_path(g:bazel.info.workspace)
245+
var output = systemlist($'cd {g:bazel.info.workspace}; set -o pipefail; bazel query {path} --output location --noincompatible_display_source_file_location {s_extra_args}')
246+
247+
if v:shell_error != 0 || empty(output)
248+
S__abort(output)
249+
endif
250+
251+
S__jump_to_location(output[0])
252+
endif
253+
enddef
254+
255+
def S__goto_definition()
256+
var token = S__token_under_cursor()
257+
if empty(token)
258+
return
259+
endif
260+
261+
if token =~ '^//' || token =~ '^@' # label
262+
S__jump_to_label(token)
263+
elseif token =~ '^:' # relative label
264+
search('name = "' .. token[1 :] .. '"', 'bW')
265+
else
266+
var path = expand('%:p:h') .. '/' .. token
267+
if filereadable(path) # file
268+
execute 'edit' expand('%:p:h') .. '/' .. token
269+
else
270+
return
271+
endif
272+
endif
273+
enddef
274+
275+
def S__show_references(label__a = '')
276+
S__configure()
277+
278+
var label = label__a
279+
if empty(label)
280+
label = S__label()
281+
endif
282+
if empty(label)
283+
return
284+
endif
285+
286+
echo 'Fetching...'
287+
var output = systemlist($'cd {g:bazel.info.workspace}; set -o pipefail; bazel query "rdeps(//..., {label})" --output location {s_extra_args}')
288+
echo '' | redraw
289+
cgetexpr output
290+
copen
291+
enddef
292+
293+
def S__print_label()
294+
var label = S__label()
295+
echo S__label()
296+
enddef
297+
298+
def S__print_relative_path()
299+
S__configure()
300+
echo expand('%:p')->S__rel_path(g:bazel.info.workspace)
301+
enddef
302+
303+
export def CompleteList(arg_lead__a: string, cmd_line__a: string, cursor_pos__a: number): list<string>
304+
S__configure()
305+
306+
var arg_lead = arg_lead__a
307+
if empty(arg_lead)
308+
arg_lead = '//'
309+
endif
310+
311+
if arg_lead[: 1] == '//'
312+
var separator_pos = len(arg_lead) - 1 - split(arg_lead, '\zs')->reverse()->match('[:/]')
313+
var target_prefix = arg_lead[: separator_pos - 1]
314+
315+
var target = target_prefix .. '/...'
316+
if target == '//...'
317+
target = '...'
318+
endif
319+
320+
var targets = systemlist($'cd {g:bazel.info.workspace}; set -o pipefail; bazel query {target} {s_extra_args}')
321+
if v:shell_error != 0
322+
echoerr 'Bazel query error: ' .. v:shell_error
323+
return []
324+
endif
325+
326+
# leave only one extra component
327+
targets->map((_, val) => val->substitute($'^\({target_prefix}[:/][^:/]\+[:/]*\).*', "\\1", ''))
328+
targets->sort()->uniq()
329+
330+
# filter
331+
targets->filter((_, val) => val =~ '^' .. arg_lead)
332+
333+
return targets
334+
else
335+
return []
336+
endif
337+
enddef
338+
339+
nnoremap <unique> <Plug>(bazel-goto-build) <ScriptCmd>S__goto_build()<CR>
340+
nnoremap <unique> <Plug>(bazel-print-rel) <ScriptCmd>S__print_relative_path()<CR>
341+
nnoremap <unique> <Plug>(bazel-print-label) <ScriptCmd>S__print_label()<CR>
342+
nnoremap <unique> <Plug>(bazel-references) <ScriptCmd>S__show_references()<CR>
343+
nnoremap <unique> <Plug>(bazel-definition) <ScriptCmd>S__goto_definition()<CR>
344+

0 commit comments

Comments
 (0)