func (fac FnAsArgCompleter) Complete(words []string, ed *Editor) ([]*candidate, error) { in, err := makeClosedStdin() if err != nil { return nil, err } ports := []*eval.Port{in, &eval.Port{File: os.Stdout}, &eval.Port{File: os.Stderr}} wordValues := make([]eval.Value, len(words)) for i, word := range words { wordValues[i] = eval.String(word) } // XXX There is no source to pass to NewTopEvalCtx. ec := eval.NewTopEvalCtx(ed.evaler, "[editor completer]", "", ports) values, err := ec.PCaptureOutput(fac.Fn, wordValues) if err != nil { ed.notify("completer error: %v", err) return nil, err } cands := make([]*candidate, len(values)) for i, v := range values { s := eval.ToString(v) cands[i] = &candidate{text: s} } return cands, nil }
func callFnForCandidates(fn eval.FnValue, ev *eval.Evaler, args []string) ([]*candidate, error) { ports := []*eval.Port{eval.DevNullClosedChan, &eval.Port{File: os.Stdout}, &eval.Port{File: os.Stderr}} argValues := make([]eval.Value, len(args)) for i, arg := range args { argValues[i] = eval.String(arg) } // XXX There is no source to pass to NewTopEvalCtx. ec := eval.NewTopEvalCtx(ev, "[editor completer]", "", ports) values, err := ec.PCaptureOutput(fn, argValues, eval.NoOpts) if err != nil { return nil, errors.New("completer error: " + err.Error()) } cands := make([]*candidate, len(values)) for i, v := range values { switch v := v.(type) { case eval.String: cands[i] = &candidate{text: string(v)} case *candidate: cands[i] = v default: return nil, errors.New("completer must output string or candidate") } } return cands, nil }
func (hv History) IndexOne(idx eval.Value) eval.Value { hv.mutex.RLock() defer hv.mutex.RUnlock() slice, i, j := eval.ParseAndFixListIndex(eval.ToString(idx), hv.Len()) if slice { cmds, err := hv.st.Cmds(i+1, j+1) maybeThrow(err) vs := make([]eval.Value, len(cmds)) for i := range cmds { vs[i] = eval.String(cmds[i]) } return eval.NewList(vs...) } s, err := hv.st.Cmd(i + 1) maybeThrow(err) return eval.String(s) }
func complFilenameFn(ec *eval.EvalCtx, word string) { cands, err := complFilenameInner(word, false) maybeThrow(err) out := ec.OutputChan() for _, cand := range cands { // TODO Preserve other parts of the candidate. out <- eval.String(cand.text) } }
// makeModule builds a module from an Editor. func makeModule(ed *Editor) eval.Namespace { ns := eval.Namespace{} // Populate builtins. for _, b := range builtins { ns[eval.FnPrefix+b.name] = eval.NewPtrVariable(&EditBuiltin{b, ed}) } // Populate binding tables in the variable $binding. // TODO Make binding specific to the Editor. binding := map[eval.Value]eval.Value{ eval.String("insert"): BindingTable{keyBindings[modeInsert]}, eval.String("command"): BindingTable{keyBindings[modeCommand]}, eval.String("completion"): BindingTable{keyBindings[modeCompletion]}, eval.String("navigation"): BindingTable{keyBindings[modeNavigation]}, eval.String("history"): BindingTable{keyBindings[modeHistory]}, } ns["binding"] = eval.NewPtrVariable(eval.NewMap(binding)) return ns }
func (hv History) Iterate(f func(eval.Value) bool) { hv.mutex.RLock() defer hv.mutex.RUnlock() n := hv.Len() err := hv.st.IterateCmds(1, n+1, func(cmd string) bool { return f(eval.String(cmd)) }) maybeThrow(err) }
func (bt BindingTable) Index(idx eval.Value) eval.Value { key := keyIndex(idx) switch f := bt.inner[key].(type) { case Builtin: return eval.String(f.name) case EvalCaller: return f.Caller } throw(errors.New("bug")) panic("unreachable") }
func (ct CompleterTable) IndexOne(idx eval.Value) eval.Value { head, ok := idx.(eval.String) if !ok { throw(ErrCompleterIndexMustBeString) } v := ct[string(head)] if fac, ok := v.(FnAsArgCompleter); ok { return fac.Fn } return eval.String("<get not implemented yet>") }
// finishReadLine puts the terminal in a state suitable for other programs to // use. func (ed *Editor) finishReadLine(addError func(error)) { ed.activeMutex.Lock() defer ed.activeMutex.Unlock() ed.active = false ed.mode = &ed.insert ed.tips = nil ed.dot = len(ed.line) if !ed.rpromptPersistent { ed.rprompt = nil } addError(ed.refresh(false, false)) ed.file.WriteString("\n") // ed.reader.Stop() ed.reader.Quit() // Turn on autowrap. ed.file.WriteString("\033[?7h") // Turn off mouse tracking. //ed.file.WriteString("\033[?1000;1006l") // Disable bracketed paste. ed.file.WriteString("\033[?2004l") // restore termios err := ed.savedTermios.ApplyToFd(int(ed.file.Fd())) if err != nil { addError(fmt.Errorf("can't restore terminal attribute: %s", err)) } ed.savedTermios = nil line := ed.line ed.editorState = editorState{} afterReadLine := ed.afterReadLine.Get().(eval.ListLike) if afterReadLine.Len() > 0 { opfunc := func(ec *eval.EvalCtx) { afterReadLine.Iterate(func(v eval.Value) bool { fn := v.(eval.FnValue) ex := ec.PCall(fn, []eval.Value{eval.String(line)}, eval.NoOpts) // TODO Pretty print. if ex != nil { fmt.Fprintln(os.Stderr, "function error: %s", ex.Error()) } return true }) } ed.evaler.Eval(eval.Op{opfunc, -1, -1}, "[after-readline]", "no source") } }
func complGetopt(ec *eval.EvalCtx, elemsv eval.IteratorValue, optsv eval.IteratorValue, argsv eval.IteratorValue) { var ( elems []string opts []*getopt.Option args []eval.FnValue variadic bool ) desc := make(map[*getopt.Option]string) // Convert arguments. elemsv.Iterate(func(v eval.Value) bool { elem, ok := v.(eval.String) if !ok { throwf("arg should be string, got %s", v.Kind()) } elems = append(elems, string(elem)) return true }) optsv.Iterate(func(v eval.Value) bool { m, ok := v.(eval.MapLike) if !ok { throwf("opt should be map-like, got %s", v.Kind()) } get := func(ks string) (string, bool) { kv := eval.String(ks) if !m.HasKey(kv) { return "", false } vv := m.IndexOne(kv) if vs, ok := vv.(eval.String); ok { return string(vs), true } else { throwf("%s should be string, got %s", ks, vs.Kind()) panic("unreachable") } } opt := &getopt.Option{} if s, ok := get("short"); ok { r, size := utf8.DecodeRuneInString(s) if r == utf8.RuneError || size != len(s) { throwf("short option should be exactly one rune, got %v", parse.Quote(s)) } opt.Short = r } if s, ok := get("long"); ok { opt.Long = s } if opt.Short == 0 && opt.Long == "" { throwf("opt should have at least one of short and long forms") } if s, ok := get("desc"); ok { desc[opt] = s } opts = append(opts, opt) return true }) argsv.Iterate(func(v eval.Value) bool { sv, ok := v.(eval.String) if ok { if string(sv) == "..." { variadic = true return true } throwf("string except for ... not allowed as argument handler, got %s", parse.Quote(string(sv))) } arg, ok := v.(eval.FnValue) if !ok { throwf("argument handler should be fn, got %s", v.Kind()) } args = append(args, arg) return true }) // TODO Configurable config g := getopt.Getopt{opts, getopt.GNUGetoptLong} _, parsedArgs, ctx := g.Parse(elems) out := ec.OutputChan() putShortOpt := func(opt *getopt.Option) { c := &candidate{text: "-" + string(opt.Short)} if d, ok := desc[opt]; ok { c.display.text = c.text + " (" + d + ")" } out <- c } putLongOpt := func(opt *getopt.Option) { c := &candidate{text: "--" + string(opt.Long)} if d, ok := desc[opt]; ok { c.display.text = c.text + " (" + d + ")" } out <- c } switch ctx.Type { case getopt.NewOptionOrArgument, getopt.Argument: // Find argument completer var argCompl eval.FnValue if len(parsedArgs) < len(args) { argCompl = args[len(parsedArgs)] } else if variadic { argCompl = args[len(args)-1] } if argCompl != nil { cands, err := callFnForCandidates(argCompl, ec.Evaler, []string{ctx.Text}) maybeThrow(err) for _, cand := range cands { out <- cand } } // TODO Notify that there is no suitable argument completer case getopt.NewOption: for _, opt := range opts { if opt.Short != 0 { putShortOpt(opt) } if opt.Long != "" { putLongOpt(opt) } } case getopt.NewLongOption: for _, opt := range opts { if opt.Long != "" { putLongOpt(opt) } } case getopt.LongOption: for _, opt := range opts { if strings.HasPrefix(opt.Long, ctx.Text) { putLongOpt(opt) } } case getopt.ChainShortOption: for _, opt := range opts { if opt.Short != 0 { // XXX loses chained options putShortOpt(opt) } } case getopt.OptionArgument: } }
// makeModule builds a module from an Editor. func makeModule(ed *Editor) eval.Namespace { ns := eval.Namespace{} // Populate builtins. for _, b := range builtins { ns[eval.FnPrefix+b.name] = eval.NewPtrVariable(b) } // Populate binding tables in the variable $binding. // TODO Make binding specific to the Editor. binding := &eval.Struct{ []string{"insert", "command", "completion", "navigation", "history"}, []eval.Variable{ eval.NewRoVariable(BindingTable{keyBindings[modeInsert]}), eval.NewRoVariable(BindingTable{keyBindings[modeCommand]}), eval.NewRoVariable(BindingTable{keyBindings[modeCompletion]}), eval.NewRoVariable(BindingTable{keyBindings[modeNavigation]}), eval.NewRoVariable(BindingTable{keyBindings[modeHistory]}), }, } ns["binding"] = eval.NewRoVariable(binding) ns["completer"] = eval.NewRoVariable(CompleterTable(argCompleter)) ns[eval.FnPrefix+"complete-getopt"] = eval.NewRoVariable( // XXX Repr is "&le:complete-getopt" instead of "le:&complete-getopt" &eval.BuiltinFn{"le:complete-getopt", eval.WrapFn(complGetopt)}) ns[eval.FnPrefix+"complete-files"] = eval.NewRoVariable( &eval.BuiltinFn{"le:complete-filename", eval.WrapFn(complFilenameFn)}) ns["prompt"] = ed.ps1 ns["rprompt"] = ed.rps1 ns["rprompt-persistent"] = BoolExposer{&ed.rpromptPersistent} ns["history"] = eval.NewRoVariable(History{&ed.historyMutex, ed.store}) ns["current-command"] = eval.MakeVariableFromCallback( func(v eval.Value) { if !ed.active { throw(ErrEditorInactive) } if s, ok := v.(eval.String); ok { ed.line = string(s) ed.dot = len(ed.line) } else { throw(errMustBeString) } }, func() eval.Value { return eval.String(ed.line) }, ) ns["selected-file"] = eval.MakeRoVariableFromCallback( func() eval.Value { if !ed.active { throw(ErrEditorInactive) } if ed.mode.Mode() != modeNavigation { throw(errNotNav) } return eval.String(ed.navigation.current.selectedName()) }, ) ns["abbr"] = eval.NewRoVariable(eval.MapStringString(ed.abbreviations)) ns["before-readline"] = ed.beforeReadLine ns["after-readline"] = ed.afterReadLine ns[eval.FnPrefix+"styled"] = eval.NewRoVariable(&eval.BuiltinFn{"le:styled", eval.WrapFn(styledBuiltin)}) return ns }
func (se StringExposer) Get() eval.Value { return eval.String(*se.valuePtr) }
func complGetopt(ec *eval.EvalCtx, elemsv eval.IteratorValue, optsv eval.IteratorValue, argsv eval.IteratorValue) { var ( elems []string opts []*getopt.Option args []eval.FnValue variadic bool ) // Convert arguments. elemsv.Iterate(func(v eval.Value) bool { elem, ok := v.(eval.String) if !ok { throwf("arg should be string, got %s", v.Kind()) } elems = append(elems, string(elem)) return true }) optsv.Iterate(func(v eval.Value) bool { m, ok := v.(eval.MapLike) if !ok { throwf("opt should be map-like, got %s", v.Kind()) } opt := &getopt.Option{} vshort := maybeIndex(m, eval.String("short")) if vshort != nil { sv, ok := vshort.(eval.String) if !ok { throwf("short option should be string, got %s", vshort.Kind()) } s := string(sv) r, size := utf8.DecodeRuneInString(s) if r == utf8.RuneError || size != len(s) { throwf("short option should be exactly one rune, got %v", parse.Quote(s)) } opt.Short = r } vlong := maybeIndex(m, eval.String("long")) if vlong != nil { s, ok := vlong.(eval.String) if !ok { throwf("long option should be string, got %s", vlong.Kind()) } opt.Long = string(s) } if vshort == nil && vlong == nil { throwf("opt should have at least one of short and long as keys") } // TODO support &desc opts = append(opts, opt) return true }) argsv.Iterate(func(v eval.Value) bool { sv, ok := v.(eval.String) if ok { if string(sv) == "..." { variadic = true return true } throwf("string except for ... not allowed as argument handler, got %s", parse.Quote(string(sv))) } arg, ok := v.(eval.FnValue) if !ok { throwf("argument handler should be fn, got %s", v.Kind()) } args = append(args, arg) return true }) // TODO Configurable config g := getopt.Getopt{opts, getopt.GNUGetoptLong} _, _, ctx := g.Parse(elems) out := ec.OutputChan() _ = variadic // XXX switch ctx.Type { case getopt.NewOptionOrArgument, getopt.Argument: case getopt.NewOption: for _, opt := range opts { if opt.Short != 0 { out <- eval.String("-" + string(opt.Short)) } if opt.Long != "" { out <- eval.String("--" + opt.Long) } } case getopt.NewLongOption: for _, opt := range opts { if opt.Long != "" { out <- eval.String("--" + opt.Long) } } case getopt.LongOption: for _, opt := range opts { if strings.HasPrefix(opt.Long, ctx.Text) { out <- eval.String("--" + opt.Long) } } case getopt.ChainShortOption: for _, opt := range opts { if opt.Short != 0 { // XXX loses chained options out <- eval.String("-" + string(opt.Short)) } } case getopt.OptionArgument: } }