github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/edit/complete_getopt.go (about) 1 package edit 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "unicode/utf8" 8 9 "github.com/markusbkk/elvish/pkg/eval" 10 "github.com/markusbkk/elvish/pkg/eval/vals" 11 "github.com/markusbkk/elvish/pkg/getopt" 12 "github.com/markusbkk/elvish/pkg/parse" 13 "github.com/markusbkk/elvish/pkg/ui" 14 ) 15 16 //elvdoc:fn complete-getopt 17 // 18 // ```elvish 19 // edit:complete-getopt $args $opt-specs $arg-handlers 20 // ``` 21 // Produces completions according to a specification of accepted command-line 22 // options (both short and long options are handled), positional handler 23 // functions for each command position, and the current arguments in the command 24 // line. The arguments are as follows: 25 // 26 // * `$args` is an array containing the current arguments in the command line 27 // (without the command itself). These are the arguments as passed to the 28 // [Argument Completer](#argument-completer) function. 29 // 30 // * `$opt-specs` is an array of maps, each one containing the definition of 31 // one possible command-line option. Matching options will be provided as 32 // completions when the last element of `$args` starts with a dash, but not 33 // otherwise. Each map can contain the following keys (at least one of `short` 34 // or `long` needs to be specified): 35 // 36 // - `short` contains the one-letter short option, if any, without the dash. 37 // 38 // - `long` contains the long option name, if any, without the initial two 39 // dashes. 40 // 41 // - `arg-optional`, if set to `$true`, specifies that the option receives an 42 // optional argument. 43 // 44 // - `arg-required`, if set to `$true`, specifies that the option receives a 45 // mandatory argument. Only one of `arg-optional` or `arg-required` can be 46 // set to `$true`. 47 // 48 // - `desc` can be set to a human-readable description of the option which 49 // will be displayed in the completion menu. 50 // 51 // - `completer` can be set to a function to generate possible completions for 52 // the option argument. The function receives as argument the element at 53 // that position and return zero or more candidates. 54 // 55 // * `$arg-handlers` is an array of functions, each one returning the possible 56 // completions for that position in the arguments. Each function receives 57 // as argument the last element of `$args`, and should return zero or more 58 // possible values for the completions at that point. The returned values can 59 // be plain strings or the output of `edit:complex-candidate`. If the last 60 // element of the list is the string `...`, then the last handler is reused 61 // for all following arguments. 62 // 63 // Example: 64 // 65 // ```elvish-transcript 66 // ~> fn complete {|@args| 67 // opt-specs = [ [&short=a &long=all &desc="Show all"] 68 // [&short=n &desc="Set name" &arg-required=$true 69 // &completer= {|_| put name1 name2 }] ] 70 // arg-handlers = [ {|_| put first1 first2 } 71 // {|_| put second1 second2 } ... ] 72 // edit:complete-getopt $args $opt-specs $arg-handlers 73 // } 74 // ~> complete '' 75 // ▶ first1 76 // ▶ first2 77 // ~> complete '-' 78 // ▶ (edit:complex-candidate -a &display='-a (Show all)') 79 // ▶ (edit:complex-candidate --all &display='--all (Show all)') 80 // ▶ (edit:complex-candidate -n &display='-n (Set name)') 81 // ~> complete -n '' 82 // ▶ name1 83 // ▶ name2 84 // ~> complete -a '' 85 // ▶ first1 86 // ▶ first2 87 // ~> complete arg1 '' 88 // ▶ second1 89 // ▶ second2 90 // ~> complete arg1 arg2 '' 91 // ▶ second1 92 // ▶ second2 93 // ``` 94 // 95 // @cf flag:parse-getopt 96 97 func completeGetopt(fm *eval.Frame, vArgs, vOpts, vArgHandlers interface{}) error { 98 args, err := parseGetoptArgs(vArgs) 99 if err != nil { 100 return err 101 } 102 opts, err := parseGetoptOptSpecs(vOpts) 103 if err != nil { 104 return err 105 } 106 argHandlers, variadic, err := parseGetoptArgHandlers(vArgHandlers) 107 if err != nil { 108 return err 109 } 110 111 // TODO: Make the Config field configurable 112 _, parsedArgs, ctx := getopt.Complete(args, opts.opts, getopt.GNU) 113 114 out := fm.ValueOutput() 115 putShortOpt := func(opt *getopt.OptionSpec) error { 116 c := complexItem{Stem: "-" + string(opt.Short)} 117 if d, ok := opts.desc[opt]; ok { 118 if e, ok := opts.argDesc[opt]; ok { 119 c.Display = ui.T(c.Stem + " " + e + " (" + d + ")") 120 } else { 121 c.Display = ui.T(c.Stem + " (" + d + ")") 122 } 123 } 124 return out.Put(c) 125 } 126 putLongOpt := func(opt *getopt.OptionSpec) error { 127 c := complexItem{Stem: "--" + opt.Long} 128 if d, ok := opts.desc[opt]; ok { 129 if e, ok := opts.argDesc[opt]; ok { 130 c.Display = ui.T(c.Stem + " " + e + " (" + d + ")") 131 } else { 132 c.Display = ui.T(c.Stem + " (" + d + ")") 133 } 134 } 135 return out.Put(c) 136 } 137 call := func(fn eval.Callable, args ...interface{}) error { 138 return fn.Call(fm, args, eval.NoOpts) 139 } 140 141 switch ctx.Type { 142 case getopt.OptionOrArgument, getopt.Argument: 143 // Find argument handler. 144 var argHandler eval.Callable 145 if len(parsedArgs) < len(argHandlers) { 146 argHandler = argHandlers[len(parsedArgs)] 147 } else if variadic { 148 argHandler = argHandlers[len(argHandlers)-1] 149 } 150 if argHandler != nil { 151 return call(argHandler, ctx.Text) 152 } 153 // TODO(xiaq): Notify that there is no suitable argument completer. 154 case getopt.AnyOption: 155 for _, opt := range opts.opts { 156 if opt.Short != 0 { 157 err := putShortOpt(opt) 158 if err != nil { 159 return err 160 } 161 } 162 if opt.Long != "" { 163 err := putLongOpt(opt) 164 if err != nil { 165 return err 166 } 167 } 168 } 169 case getopt.LongOption: 170 for _, opt := range opts.opts { 171 if opt.Long != "" && strings.HasPrefix(opt.Long, ctx.Text) { 172 err := putLongOpt(opt) 173 if err != nil { 174 return err 175 } 176 } 177 } 178 case getopt.ChainShortOption: 179 for _, opt := range opts.opts { 180 if opt.Short != 0 { 181 // TODO(xiaq): Loses chained options. 182 err := putShortOpt(opt) 183 if err != nil { 184 return err 185 } 186 } 187 } 188 case getopt.OptionArgument: 189 gen := opts.argGenerator[ctx.Option.Spec] 190 if gen != nil { 191 return call(gen, ctx.Option.Argument) 192 } 193 } 194 return nil 195 } 196 197 // TODO(xiaq): Simplify most of the parsing below with reflection. 198 199 func parseGetoptArgs(v interface{}) ([]string, error) { 200 var args []string 201 var err error 202 errIterate := vals.Iterate(v, func(v interface{}) bool { 203 arg, ok := v.(string) 204 if !ok { 205 err = fmt.Errorf("arg should be string, got %s", vals.Kind(v)) 206 return false 207 } 208 args = append(args, arg) 209 return true 210 }) 211 if errIterate != nil { 212 err = errIterate 213 } 214 return args, err 215 } 216 217 type parsedOptSpecs struct { 218 opts []*getopt.OptionSpec 219 desc map[*getopt.OptionSpec]string 220 argDesc map[*getopt.OptionSpec]string 221 argGenerator map[*getopt.OptionSpec]eval.Callable 222 } 223 224 func parseGetoptOptSpecs(v interface{}) (parsedOptSpecs, error) { 225 result := parsedOptSpecs{ 226 nil, map[*getopt.OptionSpec]string{}, 227 map[*getopt.OptionSpec]string{}, map[*getopt.OptionSpec]eval.Callable{}} 228 229 var err error 230 errIterate := vals.Iterate(v, func(v interface{}) bool { 231 m, ok := v.(vals.Map) 232 if !ok { 233 err = fmt.Errorf("opt should be map, got %s", vals.Kind(v)) 234 return false 235 } 236 237 opt := &getopt.OptionSpec{} 238 239 getStringField := func(k string) (string, bool, error) { 240 v, ok := m.Index(k) 241 if !ok { 242 return "", false, nil 243 } 244 if vs, ok := v.(string); ok { 245 return vs, true, nil 246 } 247 return "", false, 248 fmt.Errorf("%s should be string, got %s", k, vals.Kind(v)) 249 } 250 getCallableField := func(k string) (eval.Callable, bool, error) { 251 v, ok := m.Index(k) 252 if !ok { 253 return nil, false, nil 254 } 255 if vb, ok := v.(eval.Callable); ok { 256 return vb, true, nil 257 } 258 return nil, false, 259 fmt.Errorf("%s should be fn, got %s", k, vals.Kind(v)) 260 } 261 getBoolField := func(k string) (bool, bool, error) { 262 v, ok := m.Index(k) 263 if !ok { 264 return false, false, nil 265 } 266 if vb, ok := v.(bool); ok { 267 return vb, true, nil 268 } 269 return false, false, 270 fmt.Errorf("%s should be bool, got %s", k, vals.Kind(v)) 271 } 272 273 if s, ok, errGet := getStringField("short"); ok { 274 r, size := utf8.DecodeRuneInString(s) 275 if r == utf8.RuneError || size != len(s) { 276 err = fmt.Errorf( 277 "short should be exactly one rune, got %v", parse.Quote(s)) 278 return false 279 } 280 opt.Short = r 281 } else if errGet != nil { 282 err = errGet 283 return false 284 } 285 if s, ok, errGet := getStringField("long"); ok { 286 opt.Long = s 287 } else if errGet != nil { 288 err = errGet 289 return false 290 } 291 if opt.Short == 0 && opt.Long == "" { 292 err = errors.New( 293 "opt should have at least one of short and long forms") 294 return false 295 } 296 297 argRequired, _, errGet := getBoolField("arg-required") 298 if errGet != nil { 299 err = errGet 300 return false 301 } 302 argOptional, _, errGet := getBoolField("arg-optional") 303 if errGet != nil { 304 err = errGet 305 return false 306 } 307 switch { 308 case argRequired && argOptional: 309 err = errors.New( 310 "opt cannot have both arg-required and arg-optional") 311 return false 312 case argRequired: 313 opt.Arity = getopt.RequiredArgument 314 case argOptional: 315 opt.Arity = getopt.OptionalArgument 316 } 317 318 if s, ok, errGet := getStringField("desc"); ok { 319 result.desc[opt] = s 320 } else if errGet != nil { 321 err = errGet 322 return false 323 } 324 if s, ok, errGet := getStringField("arg-desc"); ok { 325 result.argDesc[opt] = s 326 } else if errGet != nil { 327 err = errGet 328 return false 329 } 330 if f, ok, errGet := getCallableField("completer"); ok { 331 result.argGenerator[opt] = f 332 } else if errGet != nil { 333 err = errGet 334 return false 335 } 336 337 result.opts = append(result.opts, opt) 338 return true 339 }) 340 if errIterate != nil { 341 err = errIterate 342 } 343 return result, err 344 } 345 346 func parseGetoptArgHandlers(v interface{}) ([]eval.Callable, bool, error) { 347 var argHandlers []eval.Callable 348 var variadic bool 349 var err error 350 errIterate := vals.Iterate(v, func(v interface{}) bool { 351 sv, ok := v.(string) 352 if ok { 353 if sv == "..." { 354 variadic = true 355 return true 356 } 357 err = fmt.Errorf( 358 "string except for ... not allowed as argument handler, got %s", 359 parse.Quote(sv)) 360 return false 361 } 362 argHandler, ok := v.(eval.Callable) 363 if !ok { 364 err = fmt.Errorf( 365 "argument handler should be fn, got %s", vals.Kind(v)) 366 } 367 argHandlers = append(argHandlers, argHandler) 368 return true 369 }) 370 if errIterate != nil { 371 err = errIterate 372 } 373 return argHandlers, variadic, err 374 }