github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/edit/editor.go (about) 1 // Package edit implements the line editor for Elvish. 2 // 3 // The line editor is based on the cli package, which implements a general, 4 // Elvish-agnostic line editor, and multiple "addon" packages. This package 5 // glues them together and provides Elvish bindings for them. 6 package edit 7 8 import ( 9 "fmt" 10 "sync" 11 12 "src.elv.sh/pkg/cli" 13 "src.elv.sh/pkg/eval" 14 "src.elv.sh/pkg/eval/vals" 15 "src.elv.sh/pkg/eval/vars" 16 "src.elv.sh/pkg/parse" 17 "src.elv.sh/pkg/store" 18 ) 19 20 // Editor is the interactive line editor for Elvish. 21 type Editor struct { 22 app cli.App 23 ns *eval.Ns 24 25 excMutex sync.RWMutex 26 excList vals.List 27 28 // Maybe move this to another type that represents the REPL cycle as a whole, not just the 29 // read/edit portion represented by the Editor type. 30 AfterCommand []func(src parse.Source, duration float64, err error) 31 } 32 33 // An interface that wraps notifyf and notifyError. It is only implemented by 34 // the *Editor type; functions may take a notifier instead of *Editor argument 35 // to make it clear that they do not depend on other parts of *Editor. 36 type notifier interface { 37 notifyf(format string, args ...interface{}) 38 notifyError(ctx string, e error) 39 } 40 41 // NewEditor creates a new editor. The TTY is used for input and output. The 42 // Evaler is used for syntax highlighting, completion, and calling callbacks. 43 // The Store is used for saving and retrieving command and directory history. 44 func NewEditor(tty cli.TTY, ev *eval.Evaler, st store.Store) *Editor { 45 // Declare the Editor with a nil App first; some initialization functions 46 // require a notifier as an argument, but does not use it immediately. 47 ed := &Editor{excList: vals.EmptyList} 48 nb := eval.NsBuilder{} 49 appSpec := cli.AppSpec{TTY: tty} 50 51 hs, err := newHistStore(st) 52 if err != nil { 53 // TODO(xiaq): Report the error. 54 } 55 56 initHighlighter(&appSpec, ev) 57 initMaxHeight(&appSpec, nb) 58 initReadlineHooks(&appSpec, ev, nb) 59 initAddCmdFilters(&appSpec, ev, nb, hs) 60 initGlobalBindings(&appSpec, ed, ev, nb) 61 initInsertAPI(&appSpec, ed, ev, nb) 62 initPrompts(&appSpec, ed, ev, nb) 63 ed.app = cli.NewApp(appSpec) 64 65 initExceptionsAPI(ed, nb) 66 initVarsAPI(ed, nb) 67 initCommandAPI(ed, ev, nb) 68 initListings(ed, ev, st, hs, nb) 69 initNavigation(ed, ev, nb) 70 initCompletion(ed, ev, nb) 71 initHistWalk(ed, ev, hs, nb) 72 initInstant(ed, ev, nb) 73 initMinibuf(ed, ev, nb) 74 75 initRepl(ed, ev, nb) 76 initBufferBuiltins(ed.app, nb) 77 initTTYBuiltins(ed.app, tty, nb) 78 initMiscBuiltins(ed.app, nb) 79 initStateAPI(ed.app, nb) 80 initStoreAPI(ed.app, nb, hs) 81 82 ed.ns = nb.Ns() 83 initElvishState(ev, ed.ns) 84 85 return ed 86 } 87 88 //elvdoc:var exceptions 89 // 90 // A list of exceptions thrown from callbacks such as prompts. Useful for 91 // examining tracebacks and other metadata. 92 93 func initExceptionsAPI(ed *Editor, nb eval.NsBuilder) { 94 nb.Add("exceptions", vars.FromPtrWithMutex(&ed.excList, &ed.excMutex)) 95 } 96 97 // Initialize the `edit` module by executing the pre-defined Elvish code for the module. 98 func initElvishState(ev *eval.Evaler, ns *eval.Ns) { 99 src := parse.Source{Name: "[RC file]", Code: elvInit} 100 err := ev.Eval(src, eval.EvalCfg{Global: ns}) 101 if err != nil { 102 panic(err) 103 } 104 } 105 106 // ReadCode reads input from the user. 107 func (ed *Editor) ReadCode() (string, error) { 108 return ed.app.ReadCode() 109 } 110 111 // RunAfterCommandHooks runs callbacks involving the interactive completion of a command line. 112 func (ed *Editor) RunAfterCommandHooks(src parse.Source, duration float64, err error) { 113 for _, f := range ed.AfterCommand { 114 f(src, duration, err) 115 } 116 } 117 118 // Ns returns a namespace for manipulating the editor from Elvish code. 119 // 120 // See https://elv.sh/ref/edit.html for the Elvish API. 121 func (ed *Editor) Ns() *eval.Ns { 122 return ed.ns 123 } 124 125 func (ed *Editor) notifyf(format string, args ...interface{}) { 126 ed.app.Notify(fmt.Sprintf(format, args...)) 127 } 128 129 func (ed *Editor) notifyError(ctx string, e error) { 130 if exc, ok := e.(eval.Exception); ok { 131 ed.excMutex.Lock() 132 defer ed.excMutex.Unlock() 133 ed.excList = ed.excList.Cons(exc) 134 ed.notifyf("[%v error] %v\n"+ 135 `see stack trace with "show $edit:exceptions[%d]"`, 136 ctx, e, ed.excList.Len()-1) 137 } else { 138 ed.notifyf("[%v error] %v", ctx, e) 139 } 140 }