| 1 | #!/usr/bin/env python2
 | 
| 2 | from __future__ import print_function
 | 
| 3 | 
 | 
| 4 | from _devbuild.gen import arg_types
 | 
| 5 | from _devbuild.gen.syntax_asdl import loc
 | 
| 6 | from _devbuild.gen.value_asdl import (value, value_e)
 | 
| 7 | 
 | 
| 8 | from core import completion
 | 
| 9 | from core import error
 | 
| 10 | from core import state
 | 
| 11 | from core import ui
 | 
| 12 | from core import vm
 | 
| 13 | from mycpp import mylib
 | 
| 14 | from mycpp.mylib import log, print_stderr
 | 
| 15 | from frontend import flag_util
 | 
| 16 | from frontend import args
 | 
| 17 | from frontend import consts
 | 
| 18 | 
 | 
| 19 | _ = log
 | 
| 20 | 
 | 
| 21 | from typing import Dict, List, Iterator, cast, TYPE_CHECKING
 | 
| 22 | if TYPE_CHECKING:
 | 
| 23 |     from _devbuild.gen.runtime_asdl import cmd_value
 | 
| 24 |     from core.completion import Lookup, OptionState, Api, UserSpec
 | 
| 25 |     from core.ui import ErrorFormatter
 | 
| 26 |     from frontend.args import _Attributes
 | 
| 27 |     from frontend.parse_lib import ParseContext
 | 
| 28 |     from osh.cmd_eval import CommandEvaluator
 | 
| 29 |     from osh.split import SplitContext
 | 
| 30 |     from osh.word_eval import NormalWordEvaluator
 | 
| 31 | 
 | 
| 32 | 
 | 
| 33 | class _FixedWordsAction(completion.CompletionAction):
 | 
| 34 | 
 | 
| 35 |     def __init__(self, d):
 | 
| 36 |         # type: (List[str]) -> None
 | 
| 37 |         self.d = d
 | 
| 38 | 
 | 
| 39 |     def Matches(self, comp):
 | 
| 40 |         # type: (Api) -> Iterator[str]
 | 
| 41 |         for name in sorted(self.d):
 | 
| 42 |             if name.startswith(comp.to_complete):
 | 
| 43 |                 yield name
 | 
| 44 | 
 | 
| 45 |     def Print(self, f):
 | 
| 46 |         # type: (mylib.BufWriter) -> None
 | 
| 47 |         f.write('FixedWordsAction ')
 | 
| 48 | 
 | 
| 49 | 
 | 
| 50 | class _DynamicProcDictAction(completion.CompletionAction):
 | 
| 51 |     """For completing from proc and aliases dicts, which are mutable.
 | 
| 52 | 
 | 
| 53 |     Note: this is the same as _FixedWordsAction now, but won't be when the code
 | 
| 54 |     is statically typed!
 | 
| 55 |     """
 | 
| 56 | 
 | 
| 57 |     def __init__(self, d):
 | 
| 58 |         # type: (state.Procs) -> None
 | 
| 59 |         self.d = d
 | 
| 60 | 
 | 
| 61 |     def Matches(self, comp):
 | 
| 62 |         # type: (Api) -> Iterator[str]
 | 
| 63 |         for name in self.d.GetNames():
 | 
| 64 |             if name.startswith(comp.to_complete):
 | 
| 65 |                 yield name
 | 
| 66 | 
 | 
| 67 |     def Print(self, f):
 | 
| 68 |         # type: (mylib.BufWriter) -> None
 | 
| 69 |         f.write('DynamicProcDictAction ')
 | 
| 70 | 
 | 
| 71 | 
 | 
| 72 | class _DynamicStrDictAction(completion.CompletionAction):
 | 
| 73 |     """For completing from proc and aliases dicts, which are mutable.
 | 
| 74 | 
 | 
| 75 |     Note: this is the same as _FixedWordsAction now, but won't be when the code
 | 
| 76 |     is statically typed!
 | 
| 77 |     """
 | 
| 78 | 
 | 
| 79 |     def __init__(self, d):
 | 
| 80 |         # type: (Dict[str, str]) -> None
 | 
| 81 |         self.d = d
 | 
| 82 | 
 | 
| 83 |     def Matches(self, comp):
 | 
| 84 |         # type: (Api) -> Iterator[str]
 | 
| 85 |         for name in sorted(self.d):
 | 
| 86 |             if name.startswith(comp.to_complete):
 | 
| 87 |                 yield name
 | 
| 88 | 
 | 
| 89 |     def Print(self, f):
 | 
| 90 |         # type: (mylib.BufWriter) -> None
 | 
| 91 |         f.write('DynamicStrDictAction ')
 | 
| 92 | 
 | 
| 93 | 
 | 
| 94 | class SpecBuilder(object):
 | 
| 95 | 
 | 
| 96 |     def __init__(
 | 
| 97 |             self,
 | 
| 98 |             cmd_ev,  # type: CommandEvaluator
 | 
| 99 |             parse_ctx,  # type: ParseContext
 | 
| 100 |             word_ev,  # type: NormalWordEvaluator
 | 
| 101 |             splitter,  # type: SplitContext
 | 
| 102 |             comp_lookup,  # type: Lookup
 | 
| 103 |             help_data,  # type: Dict[str, str]
 | 
| 104 |             errfmt  # type: ui.ErrorFormatter
 | 
| 105 |     ):
 | 
| 106 |         # type: (...) -> None
 | 
| 107 |         """
 | 
| 108 |         Args:
 | 
| 109 |           cmd_ev: CommandEvaluator for compgen -F
 | 
| 110 |           parse_ctx, word_ev, splitter: for compgen -W
 | 
| 111 |         """
 | 
| 112 |         self.cmd_ev = cmd_ev
 | 
| 113 |         self.parse_ctx = parse_ctx
 | 
| 114 |         self.word_ev = word_ev
 | 
| 115 |         self.splitter = splitter
 | 
| 116 |         self.comp_lookup = comp_lookup
 | 
| 117 | 
 | 
| 118 |         self.help_data = help_data
 | 
| 119 |         # lazily initialized
 | 
| 120 |         self.topic_list = None  # type: List[str]
 | 
| 121 | 
 | 
| 122 |         self.errfmt = errfmt
 | 
| 123 | 
 | 
| 124 |     def Build(self, argv, attrs, base_opts):
 | 
| 125 |         # type: (List[str], _Attributes, Dict[str, bool]) -> UserSpec
 | 
| 126 |         """Given flags to complete/compgen, return a UserSpec.
 | 
| 127 | 
 | 
| 128 |         Args:
 | 
| 129 |           argv: only used for error message
 | 
| 130 |         """
 | 
| 131 |         cmd_ev = self.cmd_ev
 | 
| 132 | 
 | 
| 133 |         # arg_types.compgen is a subset of arg_types.complete (the two users of this
 | 
| 134 |         # function), so we use the generate type for compgen here.
 | 
| 135 |         arg = arg_types.compgen(attrs.attrs)
 | 
| 136 |         actions = []  # type: List[completion.CompletionAction]
 | 
| 137 | 
 | 
| 138 |         # NOTE: bash doesn't actually check the name until completion time, but
 | 
| 139 |         # obviously it's better to check here.
 | 
| 140 |         if arg.F is not None:
 | 
| 141 |             func_name = arg.F
 | 
| 142 |             func = cmd_ev.procs.Get(func_name)
 | 
| 143 |             if func is None:
 | 
| 144 |                 raise error.Usage('function %r not found' % func_name,
 | 
| 145 |                                   loc.Missing)
 | 
| 146 |             actions.append(
 | 
| 147 |                 completion.ShellFuncAction(cmd_ev, func, self.comp_lookup))
 | 
| 148 | 
 | 
| 149 |         if arg.C is not None:
 | 
| 150 |             # this can be a shell FUNCTION too, not just an external command
 | 
| 151 |             # Honestly seems better than -F?  Does it also get COMP_CWORD?
 | 
| 152 |             command = arg.C
 | 
| 153 |             actions.append(completion.CommandAction(cmd_ev, command))
 | 
| 154 |             print_stderr('osh warning: complete -C not implemented')
 | 
| 155 | 
 | 
| 156 |         # NOTE: We need completion for -A action itself!!!  bash seems to have it.
 | 
| 157 |         for name in attrs.actions:
 | 
| 158 |             if name == 'alias':
 | 
| 159 |                 a = _DynamicStrDictAction(
 | 
| 160 |                     self.parse_ctx.aliases
 | 
| 161 |                 )  # type: completion.CompletionAction
 | 
| 162 | 
 | 
| 163 |             elif name == 'binding':
 | 
| 164 |                 # TODO: Where do we get this from?
 | 
| 165 |                 a = _FixedWordsAction(['vi-delete'])
 | 
| 166 | 
 | 
| 167 |             elif name == 'builtin':
 | 
| 168 |                 a = _FixedWordsAction(consts.BUILTIN_NAMES)
 | 
| 169 | 
 | 
| 170 |             elif name == 'command':
 | 
| 171 |                 # compgen -A command in bash is SIX things: aliases, builtins,
 | 
| 172 |                 # functions, keywords, external commands relative to the current
 | 
| 173 |                 # directory, and external commands in $PATH.
 | 
| 174 | 
 | 
| 175 |                 actions.append(_FixedWordsAction(consts.BUILTIN_NAMES))
 | 
| 176 |                 actions.append(_DynamicStrDictAction(self.parse_ctx.aliases))
 | 
| 177 |                 actions.append(_DynamicProcDictAction(cmd_ev.procs))
 | 
| 178 |                 actions.append(_FixedWordsAction(consts.OSH_KEYWORD_NAMES))
 | 
| 179 |                 actions.append(completion.FileSystemAction(False, True, False))
 | 
| 180 | 
 | 
| 181 |                 # Look on the file system.
 | 
| 182 |                 a = completion.ExternalCommandAction(cmd_ev.mem)
 | 
| 183 | 
 | 
| 184 |             elif name == 'directory':
 | 
| 185 |                 a = completion.FileSystemAction(True, False, False)
 | 
| 186 | 
 | 
| 187 |             elif name == 'export':
 | 
| 188 |                 a = completion.ExportedVarsAction(cmd_ev.mem)
 | 
| 189 | 
 | 
| 190 |             elif name == 'file':
 | 
| 191 |                 a = completion.FileSystemAction(False, False, False)
 | 
| 192 | 
 | 
| 193 |             elif name == 'function':
 | 
| 194 |                 a = _DynamicProcDictAction(cmd_ev.procs)
 | 
| 195 | 
 | 
| 196 |             elif name == 'job':
 | 
| 197 |                 a = _FixedWordsAction(['jobs-not-implemented'])
 | 
| 198 | 
 | 
| 199 |             elif name == 'keyword':
 | 
| 200 |                 a = _FixedWordsAction(consts.OSH_KEYWORD_NAMES)
 | 
| 201 | 
 | 
| 202 |             elif name == 'user':
 | 
| 203 |                 a = completion.UsersAction()
 | 
| 204 | 
 | 
| 205 |             elif name == 'variable':
 | 
| 206 |                 a = completion.VariablesAction(cmd_ev.mem)
 | 
| 207 | 
 | 
| 208 |             elif name == 'helptopic':
 | 
| 209 |                 # Lazy initialization
 | 
| 210 |                 if self.topic_list is None:
 | 
| 211 |                     self.topic_list = self.help_data.keys()
 | 
| 212 |                 a = _FixedWordsAction(self.topic_list)
 | 
| 213 | 
 | 
| 214 |             elif name == 'setopt':
 | 
| 215 |                 a = _FixedWordsAction(consts.SET_OPTION_NAMES)
 | 
| 216 | 
 | 
| 217 |             elif name == 'shopt':
 | 
| 218 |                 a = _FixedWordsAction(consts.SHOPT_OPTION_NAMES)
 | 
| 219 | 
 | 
| 220 |             elif name == 'signal':
 | 
| 221 |                 a = _FixedWordsAction(['TODO:signals'])
 | 
| 222 | 
 | 
| 223 |             elif name == 'stopped':
 | 
| 224 |                 a = _FixedWordsAction(['jobs-not-implemented'])
 | 
| 225 | 
 | 
| 226 |             else:
 | 
| 227 |                 raise AssertionError(name)
 | 
| 228 | 
 | 
| 229 |             actions.append(a)
 | 
| 230 | 
 | 
| 231 |         # e.g. -W comes after -A directory
 | 
| 232 |         if arg.W is not None:  # could be ''
 | 
| 233 |             # NOTES:
 | 
| 234 |             # - Parsing is done at REGISTRATION time, but execution and splitting is
 | 
| 235 |             #   done at COMPLETION time (when the user hits tab).  So parse errors
 | 
| 236 |             #   happen early.
 | 
| 237 |             w_parser = self.parse_ctx.MakeWordParserForPlugin(arg.W)
 | 
| 238 | 
 | 
| 239 |             try:
 | 
| 240 |                 arg_word = w_parser.ReadForPlugin()
 | 
| 241 |             except error.Parse as e:
 | 
| 242 |                 self.errfmt.PrettyPrintError(e)
 | 
| 243 |                 raise  # Let 'complete' or 'compgen' return 2
 | 
| 244 | 
 | 
| 245 |             a = completion.DynamicWordsAction(self.word_ev, self.splitter,
 | 
| 246 |                                               arg_word, self.errfmt)
 | 
| 247 |             actions.append(a)
 | 
| 248 | 
 | 
| 249 |         extra_actions = []  # type: List[completion.CompletionAction]
 | 
| 250 |         if base_opts.get('plusdirs', False):
 | 
| 251 |             extra_actions.append(
 | 
| 252 |                 completion.FileSystemAction(True, False, False))
 | 
| 253 | 
 | 
| 254 |         # These only happen if there were zero shown.
 | 
| 255 |         else_actions = []  # type: List[completion.CompletionAction]
 | 
| 256 |         if base_opts.get('default', False):
 | 
| 257 |             else_actions.append(
 | 
| 258 |                 completion.FileSystemAction(False, False, False))
 | 
| 259 |         if base_opts.get('dirnames', False):
 | 
| 260 |             else_actions.append(completion.FileSystemAction(
 | 
| 261 |                 True, False, False))
 | 
| 262 | 
 | 
| 263 |         if len(actions) == 0 and len(else_actions) == 0:
 | 
| 264 |             raise error.Usage(
 | 
| 265 |                 'No actions defined in completion: %s' % ' '.join(argv),
 | 
| 266 |                 loc.Missing)
 | 
| 267 | 
 | 
| 268 |         p = completion.DefaultPredicate()  # type: completion._Predicate
 | 
| 269 |         if arg.X is not None:
 | 
| 270 |             filter_pat = arg.X
 | 
| 271 |             if filter_pat.startswith('!'):
 | 
| 272 |                 p = completion.GlobPredicate(False, filter_pat[1:])
 | 
| 273 |             else:
 | 
| 274 |                 p = completion.GlobPredicate(True, filter_pat)
 | 
| 275 | 
 | 
| 276 |         # mycpp: rewrite of or
 | 
| 277 |         prefix = arg.P
 | 
| 278 |         if prefix is None:
 | 
| 279 |             prefix = ''
 | 
| 280 | 
 | 
| 281 |         # mycpp: rewrite of or
 | 
| 282 |         suffix = arg.S
 | 
| 283 |         if suffix is None:
 | 
| 284 |             suffix = ''
 | 
| 285 | 
 | 
| 286 |         return completion.UserSpec(actions, extra_actions, else_actions, p,
 | 
| 287 |                                    prefix, suffix)
 | 
| 288 | 
 | 
| 289 | 
 | 
| 290 | class Complete(vm._Builtin):
 | 
| 291 |     """complete builtin - register a completion function.
 | 
| 292 | 
 | 
| 293 |   NOTE: It's has an CommandEvaluator because it creates a ShellFuncAction, which
 | 
| 294 |   needs an CommandEvaluator.
 | 
| 295 |   """
 | 
| 296 | 
 | 
| 297 |     def __init__(self, spec_builder, comp_lookup):
 | 
| 298 |         # type: (SpecBuilder, Lookup) -> None
 | 
| 299 |         self.spec_builder = spec_builder
 | 
| 300 |         self.comp_lookup = comp_lookup
 | 
| 301 | 
 | 
| 302 |     def Run(self, cmd_val):
 | 
| 303 |         # type: (cmd_value.Argv) -> int
 | 
| 304 |         arg_r = args.Reader(cmd_val.argv, cmd_val.arg_locs)
 | 
| 305 |         arg_r.Next()
 | 
| 306 | 
 | 
| 307 |         attrs = flag_util.ParseMore('complete', arg_r)
 | 
| 308 |         arg = arg_types.complete(attrs.attrs)
 | 
| 309 |         # TODO: process arg.opt_changes
 | 
| 310 | 
 | 
| 311 |         commands = arg_r.Rest()
 | 
| 312 | 
 | 
| 313 |         if arg.D:
 | 
| 314 |             # if the command doesn't match anything
 | 
| 315 |             commands.append('__fallback')
 | 
| 316 |         if arg.E:
 | 
| 317 |             commands.append('__first')  # empty line
 | 
| 318 | 
 | 
| 319 |         if len(commands) == 0:
 | 
| 320 |             if len(cmd_val.argv) == 1:  # nothing passed at all
 | 
| 321 |                 assert cmd_val.argv[0] == 'complete'
 | 
| 322 | 
 | 
| 323 |                 self.comp_lookup.PrintSpecs()
 | 
| 324 |                 return 0
 | 
| 325 |             else:
 | 
| 326 |                 # complete -F f is an error
 | 
| 327 |                 raise error.Usage('expected 1 or more commands', loc.Missing)
 | 
| 328 | 
 | 
| 329 |         base_opts = dict(attrs.opt_changes)
 | 
| 330 |         try:
 | 
| 331 |             user_spec = self.spec_builder.Build(cmd_val.argv, attrs, base_opts)
 | 
| 332 |         except error.Parse as e:
 | 
| 333 |             # error printed above
 | 
| 334 |             return 2
 | 
| 335 | 
 | 
| 336 |         for command in commands:
 | 
| 337 |             self.comp_lookup.RegisterName(command, base_opts, user_spec)
 | 
| 338 | 
 | 
| 339 |         # TODO: Hook this up
 | 
| 340 |         patterns = []  # type: List[str]
 | 
| 341 |         for pat in patterns:
 | 
| 342 |             self.comp_lookup.RegisterGlob(pat, base_opts, user_spec)
 | 
| 343 | 
 | 
| 344 |         return 0
 | 
| 345 | 
 | 
| 346 | 
 | 
| 347 | class CompGen(vm._Builtin):
 | 
| 348 |     """Print completions on stdout."""
 | 
| 349 | 
 | 
| 350 |     def __init__(self, spec_builder):
 | 
| 351 |         # type: (SpecBuilder) -> None
 | 
| 352 |         self.spec_builder = spec_builder
 | 
| 353 | 
 | 
| 354 |     def Run(self, cmd_val):
 | 
| 355 |         # type: (cmd_value.Argv) -> int
 | 
| 356 |         arg_r = args.Reader(cmd_val.argv, cmd_val.arg_locs)
 | 
| 357 |         arg_r.Next()
 | 
| 358 | 
 | 
| 359 |         arg = flag_util.ParseMore('compgen', arg_r)
 | 
| 360 | 
 | 
| 361 |         if arg_r.AtEnd():
 | 
| 362 |             to_complete = ''
 | 
| 363 |         else:
 | 
| 364 |             to_complete = arg_r.Peek()
 | 
| 365 |             arg_r.Next()
 | 
| 366 |             # bash allows extra arguments here.
 | 
| 367 |             #if not arg_r.AtEnd():
 | 
| 368 |             #  raise error.Usage('Extra arguments')
 | 
| 369 | 
 | 
| 370 |         matched = False
 | 
| 371 | 
 | 
| 372 |         base_opts = dict(arg.opt_changes)
 | 
| 373 |         try:
 | 
| 374 |             user_spec = self.spec_builder.Build(cmd_val.argv, arg, base_opts)
 | 
| 375 |         except error.Parse as e:
 | 
| 376 |             # error printed above
 | 
| 377 |             return 2
 | 
| 378 | 
 | 
| 379 |         # NOTE: Matching bash in passing dummy values for COMP_WORDS and
 | 
| 380 |         # COMP_CWORD, and also showing ALL COMPREPLY results, not just the ones
 | 
| 381 |         # that start
 | 
| 382 |         # with the word to complete.
 | 
| 383 |         matched = False
 | 
| 384 |         comp = completion.Api('', 0, 0)  # empty string
 | 
| 385 |         comp.Update('compgen', to_complete, '', -1, None)
 | 
| 386 |         try:
 | 
| 387 |             for m, _ in user_spec.AllMatches(comp):
 | 
| 388 |                 matched = True
 | 
| 389 |                 print(m)
 | 
| 390 |         except error.FatalRuntime:
 | 
| 391 |             # - DynamicWordsAction: We already printed an error, so return failure.
 | 
| 392 |             return 1
 | 
| 393 | 
 | 
| 394 |         # - ShellFuncAction: We do NOT get FatalRuntimeError.  We printed an error
 | 
| 395 |         # in the executor, but RunFuncForCompletion swallows failures.  See test
 | 
| 396 |         # case in builtin-completion.test.sh.
 | 
| 397 | 
 | 
| 398 |         # TODO:
 | 
| 399 |         # - need to dedupe results.
 | 
| 400 | 
 | 
| 401 |         return 0 if matched else 1
 | 
| 402 | 
 | 
| 403 | 
 | 
| 404 | class CompOpt(vm._Builtin):
 | 
| 405 |     """Adjust options inside user-defined completion functions."""
 | 
| 406 | 
 | 
| 407 |     def __init__(self, comp_state, errfmt):
 | 
| 408 |         # type: (OptionState, ErrorFormatter) -> None
 | 
| 409 |         self.comp_state = comp_state
 | 
| 410 |         self.errfmt = errfmt
 | 
| 411 | 
 | 
| 412 |     def Run(self, cmd_val):
 | 
| 413 |         # type: (cmd_value.Argv) -> int
 | 
| 414 |         arg_r = args.Reader(cmd_val.argv, cmd_val.arg_locs)
 | 
| 415 |         arg_r.Next()
 | 
| 416 | 
 | 
| 417 |         arg = flag_util.ParseMore('compopt', arg_r)
 | 
| 418 | 
 | 
| 419 |         if not self.comp_state.currently_completing:  # bash also checks this.
 | 
| 420 |             self.errfmt.Print_(
 | 
| 421 |                 'compopt: not currently executing a completion function')
 | 
| 422 |             return 1
 | 
| 423 | 
 | 
| 424 |         self.comp_state.dynamic_opts.update(arg.opt_changes)
 | 
| 425 |         #log('compopt: %s', arg)
 | 
| 426 |         #log('compopt %s', base_opts)
 | 
| 427 |         return 0
 | 
| 428 | 
 | 
| 429 | 
 | 
| 430 | class CompAdjust(vm._Builtin):
 | 
| 431 |     """Uses COMP_ARGV and flags produce the 'words' array.  Also sets $cur,
 | 
| 432 | 
 | 
| 433 |     $prev,
 | 
| 434 | 
 | 
| 435 |     $cword, and $split.
 | 
| 436 | 
 | 
| 437 |     Note that we do not use COMP_WORDS, which already has splitting applied.
 | 
| 438 |     bash-completion does a hack to undo or "reassemble" words after erroneous
 | 
| 439 |     splitting.
 | 
| 440 |     """
 | 
| 441 | 
 | 
| 442 |     def __init__(self, mem):
 | 
| 443 |         # type: (state.Mem) -> None
 | 
| 444 |         self.mem = mem
 | 
| 445 | 
 | 
| 446 |     def Run(self, cmd_val):
 | 
| 447 |         # type: (cmd_value.Argv) -> int
 | 
| 448 |         arg_r = args.Reader(cmd_val.argv, cmd_val.arg_locs)
 | 
| 449 |         arg_r.Next()
 | 
| 450 | 
 | 
| 451 |         attrs = flag_util.ParseMore('compadjust', arg_r)
 | 
| 452 |         arg = arg_types.compadjust(attrs.attrs)
 | 
| 453 |         var_names = arg_r.Rest()  # Output variables to set
 | 
| 454 |         for name in var_names:
 | 
| 455 |             # Ironically we could complete these
 | 
| 456 |             if name not in ['cur', 'prev', 'words', 'cword']:
 | 
| 457 |                 raise error.Usage('Invalid output variable name %r' % name,
 | 
| 458 |                                   loc.Missing)
 | 
| 459 |         #print(arg)
 | 
| 460 | 
 | 
| 461 |         # TODO: How does the user test a completion function programmatically?  Set
 | 
| 462 |         # COMP_ARGV?
 | 
| 463 |         val = self.mem.GetValue('COMP_ARGV')
 | 
| 464 |         if val.tag() != value_e.BashArray:
 | 
| 465 |             raise error.Usage("COMP_ARGV should be an array", loc.Missing)
 | 
| 466 |         comp_argv = cast(value.BashArray, val).strs
 | 
| 467 | 
 | 
| 468 |         # These are the ones from COMP_WORDBREAKS that we care about.  The rest occur
 | 
| 469 |         # "outside" of words.
 | 
| 470 |         break_chars = [':', '=']
 | 
| 471 |         if arg.s:  # implied
 | 
| 472 |             break_chars.remove('=')
 | 
| 473 |         # NOTE: The syntax is -n := and not -n : -n =.
 | 
| 474 |         # mycpp: rewrite of or
 | 
| 475 |         omit_chars = arg.n
 | 
| 476 |         if omit_chars is None:
 | 
| 477 |             omit_chars = ''
 | 
| 478 | 
 | 
| 479 |         for c in omit_chars:
 | 
| 480 |             if c in break_chars:
 | 
| 481 |                 break_chars.remove(c)
 | 
| 482 | 
 | 
| 483 |         # argv adjusted according to 'break_chars'.
 | 
| 484 |         adjusted_argv = []  # type: List[str]
 | 
| 485 |         for a in comp_argv:
 | 
| 486 |             completion.AdjustArg(a, break_chars, adjusted_argv)
 | 
| 487 | 
 | 
| 488 |         if 'words' in var_names:
 | 
| 489 |             state.BuiltinSetArray(self.mem, 'words', adjusted_argv)
 | 
| 490 | 
 | 
| 491 |         n = len(adjusted_argv)
 | 
| 492 |         cur = adjusted_argv[-1]
 | 
| 493 |         prev = '' if n < 2 else adjusted_argv[-2]
 | 
| 494 | 
 | 
| 495 |         if arg.s:
 | 
| 496 |             if cur.startswith('--') and '=' in cur:
 | 
| 497 |                 # Split into flag name and value
 | 
| 498 |                 prev, cur = mylib.split_once(cur, '=')
 | 
| 499 |                 split = 'true'
 | 
| 500 |             else:
 | 
| 501 |                 split = 'false'
 | 
| 502 |             # Do NOT set 'split' without -s.  Caller might not have declared it.
 | 
| 503 |             # Also does not respect var_names, because we don't need it.
 | 
| 504 |             state.BuiltinSetString(self.mem, 'split', split)
 | 
| 505 | 
 | 
| 506 |         if 'cur' in var_names:
 | 
| 507 |             state.BuiltinSetString(self.mem, 'cur', cur)
 | 
| 508 |         if 'prev' in var_names:
 | 
| 509 |             state.BuiltinSetString(self.mem, 'prev', prev)
 | 
| 510 |         if 'cword' in var_names:
 | 
| 511 |             # Same weird invariant after adjustment
 | 
| 512 |             state.BuiltinSetString(self.mem, 'cword', str(n - 1))
 | 
| 513 | 
 | 
| 514 |         return 0
 |