package prep // This is where the actual autocomplete happens // lots of the fun magic is in here import ( "errors" "os" "time" "go.wit.com/dev/alexflint/arg" "go.wit.com/lib/config" durationpb "google.golang.org/protobuf/types/known/durationpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" ) func Autocomplete(dest any) *Auto { myAuto = new(AutoArgs) // todo: redo this findAppInfo(dest) // parses back to main() for argv info // load the argv history from the protobuf file all := NewAutos() err := config.LoadCache(all, "argv", myAuto.appName) // loads ~/.cache/argv/forge.pb if err != nil { // there is no history. do something here(?) } pb := parseArgv(myAuto.appName) // parses os.Args into a protobuf // not autocompleting. return go-arg & the application if !pb.IsAuto { // save the pb & history all.Clone(pb) errors.Join(err, all.Save()) arg.Register(&argBash) myAuto.pp = arg.MustParse(dest) myAuto.err = err return pb } // user is trying to setup bash or zsh autocomplete if pb.SetupAuto { // --bash was passed. try to configure bash-completion MakeAutocompleteFiles(myAuto.appName) os.Exit(0) } // EVERYTHING PAST HERE IS FOR AUTOCOMPLETE // everything sent to STDOUT and STDERR matters past this point // set the start time of the binary now := time.Now() pb.Ctime = timestamppb.New(now) // set the duration since the last auto complete // find the last entry. this is dumb way to do it var last *Auto for found := range all.IterAll() { last = found } dur := time.Since(last.Ctime.AsTime()) pb.Duration = durationpb.New(dur) if pb.Debug { // dump debug info pb.PrintDebug() all.PrintHistory() } // roll the autocomplete file if all.Len() > 15 { pb.Debugf("DEBUG: trim() history is over 100 len=%d vs new=%d", all.Len(), all.Len()-90) all.Autos = all.Autos[all.Len()-10:] // newall.Autos = all.Autos[0:10] // for _, found := range all.Autos[0:10] { // newall.Append(found) // } } 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 = pb.Duration.AsDuration() if dur < time.Millisecond*200 { pb.Debug = true pb.Fast = true } arg.Register(&argBash) flags := []string{} for _, s := range pb.Argv { if s == "--autodebug" { continue } flags = append(flags, s) } // pb.Debug = true // pb.Debugf("DEBUG: MustParse(%v)", flags) myAuto.pp, err = arg.ParseFlags(flags, dest) if err != nil { pb.Debugf("DEBUG: Parse error: %v", err) } if myAuto.pp == nil { pb.Debugf("DEBUG: myAuto.pp == nil after ParseFlags()") } else { // pb.Debugf("DEBUG: myAuto.pp is ok after ParseFlags()") } // save now. this is near the end probably all.Clone(pb) errors.Join(err, all.Save()) // this is a work in progress if pb.Last == "--gui" { pb.Debugf("DEBUG: last=%s found --gui", pb.Last) pb.Autocomplete2("andlabs gogui") os.Exit(0) } else { // pb.Debugf("DEBUG: NO MATCH last='%s' found key '%s' = %s", pb.Last, key, val) } if myAuto.autoFunc == nil { pb.SubCommand(pb.Argv...) } else { myAuto.autoFunc(pb) // run the autocomplete function the user made for their application } if 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 }