123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- // Package edit implements the line editor for Elvish.
- //
- // The line editor is based on the cli package, which implements a general,
- // Elvish-agnostic line editor, and multiple "addon" packages. This package
- // glues them together and provides Elvish bindings for them.
- package edit
- import (
- _ "embed"
- "fmt"
- "sync"
- "src.elv.sh/pkg/cli"
- "src.elv.sh/pkg/eval"
- "src.elv.sh/pkg/eval/vals"
- "src.elv.sh/pkg/eval/vars"
- "src.elv.sh/pkg/parse"
- "src.elv.sh/pkg/store/storedefs"
- "src.elv.sh/pkg/ui"
- )
- // Editor is the interactive line editor for Elvish.
- type Editor struct {
- app cli.App
- ns *eval.Ns
- excMutex sync.RWMutex
- excList vals.List
- // Maybe move this to another type that represents the REPL cycle as a whole, not just the
- // read/edit portion represented by the Editor type.
- AfterCommand []func(src parse.Source, duration float64, err error)
- }
- // An interface that wraps notifyf and notifyError. It is only implemented by
- // the *Editor type; functions may take a notifier instead of *Editor argument
- // to make it clear that they do not depend on other parts of *Editor.
- type notifier interface {
- notifyf(format string, args ...any)
- notifyError(ctx string, e error)
- }
- // NewEditor creates a new editor. The TTY is used for input and output. The
- // Evaler is used for syntax highlighting, completion, and calling callbacks.
- // The Store is used for saving and retrieving command and directory history.
- func NewEditor(tty cli.TTY, ev *eval.Evaler, st storedefs.Store) *Editor {
- // Declare the Editor with a nil App first; some initialization functions
- // require a notifier as an argument, but does not use it immediately.
- ed := &Editor{excList: vals.EmptyList}
- nb := eval.BuildNsNamed("edit")
- appSpec := cli.AppSpec{TTY: tty}
- hs, err := newHistStore(st)
- if err != nil {
- _ = err // TODO(xiaq): Report the error.
- }
- initHighlighter(&appSpec, ev)
- initMaxHeight(&appSpec, nb)
- initReadlineHooks(&appSpec, ev, nb)
- initAddCmdFilters(&appSpec, ev, nb, hs)
- initGlobalBindings(&appSpec, ed, ev, nb)
- initInsertAPI(&appSpec, ed, ev, nb)
- initPrompts(&appSpec, ed, ev, nb)
- ed.app = cli.NewApp(appSpec)
- initExceptionsAPI(ed, nb)
- initVarsAPI(ed, nb)
- initCommandAPI(ed, ev, nb)
- initListings(ed, ev, st, hs, nb)
- initNavigation(ed, ev, nb)
- initCompletion(ed, ev, nb)
- initHistWalk(ed, ev, hs, nb)
- initInstant(ed, ev, nb)
- initMinibuf(ed, ev, nb)
- initRepl(ed, ev, nb)
- initBufferBuiltins(ed.app, nb)
- initTTYBuiltins(ed.app, tty, nb)
- initMiscBuiltins(ed.app, nb)
- initStateAPI(ed.app, nb)
- initStoreAPI(ed.app, nb, hs)
- ed.ns = nb.Ns()
- initElvishState(ev, ed.ns)
- return ed
- }
- func initExceptionsAPI(ed *Editor, nb eval.NsBuilder) {
- nb.AddVar("exceptions", vars.FromPtrWithMutex(&ed.excList, &ed.excMutex))
- }
- //go:embed init.elv
- var initElv string
- // Initialize the `edit` module by executing the pre-defined Elvish code for the module.
- func initElvishState(ev *eval.Evaler, ns *eval.Ns) {
- src := parse.Source{Name: "[init.elv]", Code: initElv}
- err := ev.Eval(src, eval.EvalCfg{Global: ns})
- if err != nil {
- panic(err)
- }
- }
- // ReadCode reads input from the user.
- func (ed *Editor) ReadCode() (string, error) {
- return ed.app.ReadCode()
- }
- // Notify adds a note to the notification buffer.
- func (ed *Editor) Notify(note ui.Text) {
- ed.app.Notify(note)
- }
- // RunAfterCommandHooks runs callbacks involving the interactive completion of a command line.
- func (ed *Editor) RunAfterCommandHooks(src parse.Source, duration float64, err error) {
- for _, f := range ed.AfterCommand {
- f(src, duration, err)
- }
- }
- // Ns returns a namespace for manipulating the editor from Elvish code.
- //
- // See https://elv.sh/ref/edit.html for the Elvish API.
- func (ed *Editor) Ns() *eval.Ns {
- return ed.ns
- }
- func (ed *Editor) notifyf(format string, args ...any) {
- ed.app.Notify(ui.T(fmt.Sprintf(format, args...)))
- }
- func (ed *Editor) notifyError(ctx string, e error) {
- if exc, ok := e.(eval.Exception); ok {
- ed.excMutex.Lock()
- defer ed.excMutex.Unlock()
- ed.excList = ed.excList.Conj(exc)
- ed.notifyf("[%v error] %v\n"+
- `see stack trace with "show $edit:exceptions[%d]"`,
- ctx, e, ed.excList.Len()-1)
- } else {
- ed.notifyf("[%v error] %v", ctx, e)
- }
- }
|