package argvpb // This is where the actual autocomplete happens // lots of the fun magic is in here import ( "errors" "os" "strings" "time" "go.wit.com/lib/cobol" "go.wit.com/lib/config" "go.wit.com/log" durationpb "google.golang.org/protobuf/types/known/durationpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" ) func Autocomplete(dest any) *Argv { me = new(AutoArgs) // todo: redo this findAppInfo(dest) // parses back to main() for argv info // todo: figure this out if me.guiFunc != nil { // register gui args me.guiFunc() // log.Info("gui init") } else { // log.Info("no gui init") } // parses os.Args into a protobuf me.pb = parseArgv(me.ARGNAME) // the argv history all := NewArgvs() // initializes the application config file config.Init(me.ARGNAME, me.VERSION, cobol.Time(me.BUILDTIME), me.pb.Real) // loads the autocomplete history file err := config.LoadCache(all, "argv", me.ARGNAME) // if err != nil { // there is no history. // todo: initialize the history file // todo: check if this is automatically done already } // set the start time of the binary now := time.Now() me.pb.Ctime = timestamppb.New(now) // try to register bash args for go-args // arg.Register(&ArgvBash) // user is trying to setup bash or zsh autocomplete // --bash or --zsh is the first os.Args if me.pb.SetupAuto { // --bash or --zsh was passed. try to configure bash-completion MakeAutocompleteFiles(me.ARGNAME) // never forget to run this our you will hate yourself and the choices you have made os.Exit(0) } // not autocompleting. return go-arg & the application if !me.pb.IsAuto { // save the pb & history all.Clone(me.pb) errors.Join(err, all.Save()) // me.pp = arg.MustParse(dest) me.err = me.mustParseFunc() return me.pb } // EVERYTHING PAST HERE IS FOR AUTOCOMPLETE // everything sent to STDOUT and STDERR matters past this point // set the duration since the last auto complete // find the last entry. this is dumb way to do it last := new(Argv) last.Ctime = timestamppb.New(time.Now().Add(-time.Second)) for found := range all.IterAll() { last = found } dur := time.Since(last.Ctime.AsTime()) me.pb.Duration = durationpb.New(dur) if me.pb.Debug { // dump debug info me.pb.PrintDebug() all.PrintHistory() } // roll the autocomplete file if all.Len() > 15 { me.pb.Debugf("DEBUG: trim() history is over 100 len=%d vs new=%d", all.Len(), all.Len()-90) all.Argvs = all.Argvs[all.Len()-10:] // newall.Autos = all.Autos[0:10] // for _, found := range all.Autos[0:10] { // newall.Append(found) // } } me.pb.Debugf("WRITE DEBUG: write PB='%s' len(pb)=%d config.Save().err=%v", all.Filename, all.Len(), err) // turn on debugging if duration < 200 milliseconds dur = me.pb.Duration.AsDuration() if dur < time.Millisecond*200 { me.pb.Debug = true me.pb.Fast = true me.pb.Fastcmd = me.pb.Cmd if last.Fast { if me.pb.Fastcmd == last.Fastcmd { // do the smart something here } } } flags := []string{} for _, s := range me.pb.Real { if s == "--autodebug" { continue } if strings.TrimSpace(s) == "" { // skip anything blank continue } flags = append(flags, s) } // use go-args to parse the structs so we can use them here // me.pp, err = arg.ParseFlags(flags, dest) if me.parseFlagsFunc == nil { panic("argv.parseFlags() is nil") } if err := me.parseFlagsFunc(flags); err != nil { log.Info("application parseFlags() err", err) panic("argv.parseFlags() err. probably cmd doesn't really exist in struct") } if len(flags) == 0 { // error is normal if there are no command line args } else { if err != nil { // users has command line arguments that won't parse with go-args me.pb.Debugf("DEBUG: Parse error: %v flags%v", err, flags) } } // if me.pp == nil { // me.pb.Debugf("DEBUG: me.pp == nil after ParseFlags()") // } else { // me.pb.Debugf("DEBUG: me.pp is ok after ParseFlags()") // } // save now. this is near the end probably all.Clone(me.pb) errors.Join(err, all.Save()) // this is a work in progress if me.pb.Last == "--gui" { me.pb.Debugf("DEBUG: last=%s found --gui", me.pb.Last) me.pb.SendString("andlabs gogui") os.Exit(0) } else { // me.pb.Debugf("DEBUG: NO MATCH last='%s' found key '%s' = %s", me.pb.Last, key, val) } if me.pb.Fast { if last.Fast { os.Exit(0) } else { // this means the user is pressing tab. no longer doing stderr if me.pb.Cmd == "" { // me.pp.WriteHelp(os.Stderr) me.writeHelpFunc() } else { // me.pp.WriteHelpForSubcommand(os.Stderr, me.pb.Cmd) me.writeHelpForSubcommandFunc(me.pb.Cmd) } } } else { } if me.autoFunc == nil { me.pb.SubCommand(me.pb.Real...) } else { me.autoFunc(me.pb) // run the autocomplete function the user made for their application } if me.pb.Debug { // TODO: // check here to see if there was any completion text sent // if not, send "reset bash newline\n" to cause bash to redraw PS1 for the user } os.Exit(0) return nil }