gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/cmds/core/elvish/edit/api.go (about) 1 package edit 2 3 import ( 4 "errors" 5 "strconv" 6 "unicode/utf8" 7 8 "github.com/u-root/u-root/cmds/core/elvish/edit/eddefs" 9 "github.com/u-root/u-root/cmds/core/elvish/edit/ui" 10 "github.com/u-root/u-root/cmds/core/elvish/eval" 11 "github.com/u-root/u-root/cmds/core/elvish/eval/vars" 12 ) 13 14 // This file implements types and functions for interactions with the 15 // Elvishscript runtime. 16 17 var ( 18 errNotNav = errors.New("not in navigation mode") 19 errLineMustBeString = errors.New("line must be string") 20 errDotMustBeString = errors.New("dot must be string") 21 errDotMustBeInt = errors.New("dot must be integer") 22 errDotOutOfRange = errors.New("dot out of range") 23 errDotInsideCodepoint = errors.New("dot cannot be inside a codepoint") 24 errEditorInactive = errors.New("editor inactive") 25 ) 26 27 // makeNs makes the edit: namespace. 28 func makeNs(ed *editor) eval.Ns { 29 ns := eval.NewNs() 30 31 // TODO(xiaq): Everything here should be registered to some registry instead 32 // of centralized here. 33 34 // Internal states. 35 ns["current-command"] = vars.FromSetGet( 36 func(v interface{}) error { 37 if !ed.active { 38 return errEditorInactive 39 } 40 if s, ok := v.(string); ok { 41 ed.buffer = s 42 ed.dot = len(ed.buffer) 43 } else { 44 return errLineMustBeString 45 } 46 return nil 47 }, 48 func() interface{} { return ed.buffer }, 49 ) 50 ns["-dot"] = vars.FromSetGet( 51 func(v interface{}) error { 52 s, ok := v.(string) 53 if !ok { 54 return errDotMustBeString 55 } 56 i, err := strconv.Atoi(s) 57 if err != nil { 58 if err.(*strconv.NumError).Err == strconv.ErrRange { 59 return errDotOutOfRange 60 } else { 61 return errDotMustBeInt 62 } 63 } 64 if i < 0 || i > len(ed.buffer) { 65 return errDotOutOfRange 66 } 67 if i < len(ed.buffer) { 68 r, _ := utf8.DecodeRuneInString(ed.buffer[i:]) 69 if r == utf8.RuneError { 70 return errDotInsideCodepoint 71 } 72 } 73 ed.dot = i 74 return nil 75 }, 76 func() interface{} { return strconv.Itoa(ed.dot) }, 77 ) 78 ns["selected-file"] = vars.FromGet( 79 func() interface{} { 80 if !ed.active { 81 throw(errEditorInactive) 82 } 83 nav, ok := ed.mode.(*navigation) 84 if !ok { 85 throw(errNotNav) 86 } 87 return nav.current.selectedName() 88 }, 89 ) 90 91 // Functions. 92 fns := map[string]interface{}{ 93 "insert-at-dot": ed.InsertAtDot, 94 "replace-input": ed.replaceInput, 95 "wordify": wordifyBuiltin, 96 "-dump-buf": ed.dumpBuf, 97 } 98 ns.AddBuiltinFns("edit:", fns) 99 100 ns.AddBuiltinFnCustom("edit:", "binding-table", eddefs.MakeBindingMapCallable()) 101 ns.AddBuiltinFnCustom("edit:", "styled", &styledCallable{}) 102 ns.AddBuiltinFnCustom("edit:", "key", &uiToKeyCallable{}) 103 104 return ns 105 } 106 107 type styledCallable struct { 108 } 109 110 func (*styledCallable) Target() interface{} { 111 return styled 112 } 113 114 func (*styledCallable) Call( 115 f *eval.Frame, args []interface{}, opts eval.RawOptions, inputs eval.Inputs) ([]interface{}, error) { 116 out, err := styled(args[0].(string), args[1]) 117 return []interface{}{out}, err 118 } 119 120 type uiToKeyCallable struct { 121 } 122 123 func (*uiToKeyCallable) Target() interface{} { 124 return ui.ToKey 125 } 126 127 func (*uiToKeyCallable) Call(f *eval.Frame, args []interface{}, opts eval.RawOptions, inputs eval.Inputs) ([]interface{}, error) { 128 out := ui.ToKey(args[0]) 129 return []interface{}{out}, nil 130 } 131 132 // CallFn calls an Fn, displaying its outputs and possible errors as editor 133 // notifications. It is the preferred way to call a Fn while the editor is 134 // active. 135 func (ed *editor) CallFn(fn eval.Callable, args ...interface{}) { 136 ports := []*eval.Port{ 137 eval.DevNullClosedChan, ed.notifyPort, ed.notifyPort, 138 } 139 // XXX There is no source to pass to NewTopEvalCtx. 140 ec := eval.NewTopFrame(ed.evaler, eval.NewInternalSource("[editor]"), ports) 141 ex := ec.Call(fn, args, eval.NoOpts) 142 if ex != nil { 143 ed.Notify("function error: %s", ex.Error()) 144 } 145 146 // XXX Concurrency-dangerous! 147 ed.refresh(true, true) 148 }