aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/zsh_completion/_bazel
blob: 59588308a0d87005fa073eaf9a43c3d77091ed70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
#compdef bazel

# Copyright 2015 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Installation
# ------------
#
# 1. Add this script to a directory on your $fpath:
#     fpath[1,0]=~/.zsh/completion/
#     mkdir -p ~/.zsh/completion/
#     cp scripts/zsh_completion/_bazel ~/.zsh/completion
#
# 2. Optionally, add the following to your .zshrc.
#     zstyle ':completion:*' use-cache on
#     zstyle ':completion:*' cache-path ~/.zsh/cache
#
#   This way, the completion script does not have to parse Bazel's options
#   repeatedly.  The directory in cache-path must be created manually.
#
# 3. Restart the shell
#
# Options
# -------
#  completion:init:bazel:* cache-lifetime
#    Lifetime for the completion cache (if turned on, default: 1 week)

local curcontext="$curcontext" state line

: ${BAZEL_COMPLETION_PACKAGE_PATH:=%workspace%}
: ${BAZEL:=bazel}
b() { ${BAZEL} --noblock_for_lock "$@" 2>/dev/null; }

# Default cache lifetime is 1 week
zstyle -s ":completion:${curcontext}:" cache-lifetime lifetime
if [[ -z "${lifetime}" ]]; then
  lifetime=$((60*60*24*7))
fi

_bazel_cache_policy() {
  local -a oldp
  oldp=( "$1"(Nms+${lifetime}) )
  (( $#oldp ))
}

_set_cache_policy() {
  zstyle -s ":completion:*:$curcontext*" cache-policy update_policy

  if [[ -z "$update_policy" ]]; then
    zstyle ":completion:$curcontext*" cache-policy _bazel_cache_policy
  fi
}

# Skips over all global arguments.  After invocation, OFFSET contains the
# position of the bazel command in $words.
_adapt_subcommand_offset() {
  OFFSET=2
  for w in ${words[2,-1]}; do
    if [[ $w == (#b)-* ]]; then
      (( OFFSET++ ))
    else
      return
    fi
  done
}

# Retrieve the cache but also check that the value is not empty.
_bazel_safe_retrieve_cache() {
  _retrieve_cache $1 && [[ ${(P)#2} -gt 0 ]]
}

# Puts the name of the variable that contains the options for the bazel
# subcommand handed in as the first argument into the global variable
# _bazel_cmd_options.
_bazel_get_options() {
  local lcmd=$1
  _bazel_cmd_options=_bazel_${lcmd}_options
  _bazel_cmd_args=_bazel_${lcmd}_args
  if [[ ${(P)#_bazel_cmd_options} != 0 ]]; then
    return
  fi
  if _cache_invalid BAZEL_${lcmd}_options || _cache_invalid BAZEL_${lcmd}_args \
    || ! _bazel_safe_retrieve_cache BAZEL_${lcmd}_options ${_bazel_cmd_options} \
    || ! _retrieve_cache BAZEL_${lcmd}_args ${_bazel_cmd_args}; then
    if ! eval "$(b help completion)"; then
      return
    fi
    local opts_var
    if [[ $lcmd == "startup_options" ]]; then
      opts_var="BAZEL_STARTUP_OPTIONS"
    else
      opts_var="BAZEL_COMMAND_${lcmd:u}_FLAGS"
    fi
    local -a raw_options
    if ! eval "raw_options=(\${(@f)$opts_var})"; then
      return
    fi

    local -a option_list
    for opt in $raw_options; do
      case $opt in
        --*"={"*)
          local lst="${${opt##*"={"}%"}"}"
          local opt="${opt%%=*}="
          option_list+=("${opt}:string:_values '' ${lst//,/ }") ;;
        --*=path)
          option_list+=("${opt%path}:path:_files") ;;
        --*=label)
          option_list+=("${opt%label}:target:_bazel_complete_target") ;;
        --*=*)
          option_list+=("${opt}:string:") ;;
        *)
          option_list+=("$opt") ;;
      esac
    done

    local -a cmd_args
    local cmd_type
    if eval "cmd_type=\${BAZEL_COMMAND_${lcmd:u}_ARGUMENT}" && [[ -n $cmd_type ]]; then
      case $cmd_type in
        label|label-*)
          cmd_args+=("*::${cmd_type}:_bazel_complete_target_${cmd_type//-/_}") ;;
        info-key)
          cmd_args+=('1::key:_bazel_info_key') ;;
        path)
          cmd_args+=('1::profile:_path_files') ;;
        "command|{"*"}")
          local lst=${${cmd_type#"command|{"}%"}"}
          cmd_args+=("1::topic:_bazel_help_topic -- ${lst//,/ }") ;;
      esac
    fi

    typeset -g "${_bazel_cmd_options}"="${(pj:|:)option_list[*]}"
    _store_cache BAZEL_${lcmd}_options ${_bazel_cmd_options}
    typeset -g "${_bazel_cmd_args}"="${(pj:|:)cmd_args[*]}"
    _store_cache BAZEL_${lcmd}_args ${_bazel_cmd_args}
  fi
}

_get_build_targets() {
  local pkg=$1
  local rule_re
  typeset -a completions
  case $target_type in
    test)
      rule_re=".*_test"
      ;;
    build)
      rule_re=".*"
      ;;
    bin)
      rule_re=".*_test|.*_binary"
      ;;
  esac
  completions=(${$(b query "kind(\"${rule_re}\", ${pkg}:all)" 2>/dev/null)##*:})
  if ( (( ${#completions} > 0 )) && [[ $target_type != run ]] ); then
    completions+=(all)
  fi
  echo ${completions[*]}
}

# Returns all packages that match $PREFIX.  PREFIX may start with //, in which
# case the workspace roots are searched.  Otherwise, they are completed based on
# PWD.
_get_build_packages() {
  local workspace pfx
  typeset -a package_roots paths final_paths
  workspace=$PWD
  package_roots=(${(ps.:.)BAZEL_COMPLETION_PACKAGE_PATH})
  package_roots=(${^package_roots//\%workspace\%/$workspace})
  if [[ "${(e)PREFIX}" == //* ]]; then
    pfx=${(e)PREFIX[2,-1]}
  else
    pfx=${(e)PREFIX}
  fi
  paths=(${^package_roots}/${pfx}*(/))
  for p in ${paths[*]}; do
    if [[ -f ${p}/BUILD ]]; then
      final_paths+=(${p##*/}:)
    fi
    final_paths+=(${p##*/}/)
  done
  echo ${final_paths[*]}
}

_package_remove_slash() {
  if [[ $KEYS == ':' && $LBUFFER == */ ]]; then
    LBUFFER=${LBUFFER[1,-2]}
  fi
}

# Completion function for BUILD targets, called by the completion system.
_bazel_complete_target() {
  local expl
  typeset -a packages targets
  if [[ "${(e)PREFIX}" != *:* ]]; then
    # There is no : in the prefix, completion can be either
    # a package or a target, if the cwd is a package itself.
    if [[ -f $PWD/BUILD ]]; then
      targets=($(_get_build_targets ""))
      _description build_target expl "BUILD target"
      compadd "${expl[@]}" -a targets
    fi
    packages=($(_get_build_packages))
    _description build_package expl "BUILD package"
    # Chop of the leading path segments from the prefix for display.
    compset -P '*/'
    compadd -R _package_remove_slash -S '' "${expl[@]}" -a packages
  else
    targets=($(_get_build_targets "${${(e)PREFIX}%:*}"))
    _description build_target expl "BUILD target"
    # Ignore the current prefix for the upcoming completion, since we only list
    # the names of the targets, not the full path.
    compset -P '*:'
    compadd "${expl[@]}" -a targets
  fi
}

_bazel_complete_target_label() {
  typeset -g target_type=build
  _bazel_complete_target
}

_bazel_complete_target_label_test() {
  typeset -g target_type=test
  _bazel_complete_target
}

_bazel_complete_target_label_bin() {
  typeset -g target_type=bin
  _bazel_complete_target
}

### Actual completion commands

_bazel() {
  _adapt_subcommand_offset
  if (( CURRENT - OFFSET > 0 )); then
    # Remember the subcommand name, stored globally so we can access it
    # from any subsequent function
    cmd=${words[OFFSET]//-/_}

    # Set the context for the subcommand.
    curcontext="${curcontext%:*:*}:bazel-$cmd:"
    _set_cache_policy

    # Narrow the range of words we are looking at to exclude cmd
    # name and any leading options
    (( CURRENT = CURRENT - OFFSET + 1 ))
    shift $((OFFSET - 1)) words
    # Run the completion for the subcommand
    _bazel_get_options $cmd
    _arguments : \
      ${(Pps:|:)_bazel_cmd_options} \
      ${(Pps:|:)_bazel_cmd_args}
  else
    _set_cache_policy
    # Start special handling for global options,
    # which can be retrieved by calling
    # $ bazel help startup_options
    _bazel_get_options startup_options
    _arguments : \
      ${(Pps:|:)_bazel_cmd_options} \
      "*:commands:_bazel_commands"
  fi
  return
}

_get_commands() {
  # bazel_cmd_list is a global (g) array (a)
  typeset -ga _bazel_cmd_list
  # Use `bazel help` instead of `bazel help completion` to get command
  # descriptions.
  if _bazel_cmd_list=("${(@f)$(b help | awk '
/Available commands/ { command=1; }
/  [-a-z]+[ \t]+.+/ { if (command) { printf "%s:", $1; for (i=2; i<=NF; i++) printf "%s ", $i; print "" } }
/^$/ { command=0; }')}"); then
    _store_cache BAZEL_commands _bazel_cmd_list
  fi
}

# Completion function for bazel subcommands, called by the completion system.
_bazel_commands() {
  if [[ ${#_bazel_cmd_list} == 0 ]]; then
    if _cache_invalid BAZEL_commands \
      || ! _bazel_safe_retrieve_cache BAZEL_commands _bazel_cmd_list; then
      _get_commands
    fi
  fi

  _describe -t bazel-commands 'Bazel command' _bazel_cmd_list
}

# Completion function for bazel help options, called by the completion system.
_bazel_help_topic() {
  if [[ ${#_bazel_cmd_list} == 0 ]]; then
    if _cache_invalid BAZEL_commands \
      || ! _bazel_safe_retrieve_cache BAZEL_commands _bazel_cmd_list; then
      _get_commands
    fi
  fi

  while [[ $# -gt 0 ]]; do
    if [[ $1 == -- ]]; then
      shift
      break
    fi
    shift
  done
  _bazel_help_list=($@)
  _bazel_help_list+=($_bazel_cmd_list)
  _describe -t bazel-help 'Help topic' _bazel_help_list
}

# Completion function for bazel info keys, called by the completion system.
_bazel_info_key() {
  if [[ ${#_bazel_info_keys_list} == 0 ]]; then
    if _cache_invalid BAZEL_info_keys \
      || ! _bazel_safe_retrieve_cache BAZEL_info_keys _bazel_info_keys_list; then
      typeset -ga _bazel_info_keys_list
      # Use `bazel help` instead of `bazel help completion` to get info-key
      # descriptions.
      if _bazel_info_keys_list=("${(@f)$(b help info-keys | awk '
  { printf "%s:", $1; for (i=2; i<=NF; i++) printf "%s ", $i; print "" }')}"); then
        _store_cache BAZEL_info_keys _bazel_info_keys_list
      fi
    fi
  fi
  _describe -t bazel-info 'Key' _bazel_info_keys_list
}