mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-14 19:27:58 +08:00
3501b945aa
- Organize code around two interfaces, Store and Cursor. - Ensure that a session store is always available, even if the DB is not accessible. This fixes #909.
138 lines
3.7 KiB
Go
138 lines
3.7 KiB
Go
// 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 (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/elves/elvish/pkg/cli"
|
|
"github.com/elves/elvish/pkg/eval"
|
|
"github.com/elves/elvish/pkg/eval/vals"
|
|
"github.com/elves/elvish/pkg/eval/vars"
|
|
"github.com/elves/elvish/pkg/parse"
|
|
"github.com/elves/elvish/pkg/store"
|
|
)
|
|
|
|
// Editor is the interface line editor for Elvish.
|
|
type Editor struct {
|
|
app cli.App
|
|
ns eval.Ns
|
|
|
|
excMutex sync.RWMutex
|
|
excList vals.List
|
|
}
|
|
|
|
// 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 ...interface{})
|
|
notifyError(ctx string, e error)
|
|
}
|
|
|
|
// NewEditor creates a new editor from input and output terminal files.
|
|
func NewEditor(tty cli.TTY, ev *eval.Evaler, st store.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{ns: eval.Ns{}, excList: vals.EmptyList}
|
|
appSpec := cli.AppSpec{TTY: tty}
|
|
|
|
hs, err := newHistStore(st)
|
|
if err != nil {
|
|
// TODO(xiaq): Report the error.
|
|
}
|
|
|
|
initHighlighter(&appSpec, ev)
|
|
initMaxHeight(&appSpec, ed.ns)
|
|
initReadlineHooks(&appSpec, ev, ed.ns)
|
|
initAddCmdFilters(&appSpec, ev, ed.ns, hs)
|
|
initInsertAPI(&appSpec, ed, ev, ed.ns)
|
|
initPrompts(&appSpec, ed, ev, ed.ns)
|
|
ed.app = cli.NewApp(appSpec)
|
|
|
|
initExceptionsAPI(ed)
|
|
initCommandAPI(ed, ev)
|
|
initListings(ed, ev, st, hs)
|
|
initNavigation(ed, ev)
|
|
initCompletion(ed, ev)
|
|
initHistWalk(ed, ev, hs)
|
|
initInstant(ed, ev)
|
|
initMinibuf(ed, ev)
|
|
|
|
initBufferBuiltins(ed.app, ed.ns)
|
|
initTTYBuiltins(ed.app, tty, ed.ns)
|
|
initMiscBuiltins(ed.app, ed.ns)
|
|
initStateAPI(ed.app, ed.ns)
|
|
initStoreAPI(ed.app, ed.ns, hs)
|
|
evalDefaultBinding(ev, ed.ns)
|
|
|
|
return ed
|
|
}
|
|
|
|
//elvdoc:var exceptions
|
|
//
|
|
// A list of exceptions thrown from callbacks such as prompts. Useful for
|
|
// examining tracebacks and other metadata.
|
|
|
|
func initExceptionsAPI(ed *Editor) {
|
|
ed.ns.Add("exceptions", vars.FromPtrWithMutex(&ed.excList, &ed.excMutex))
|
|
}
|
|
|
|
func evalDefaultBinding(ev *eval.Evaler, ns eval.Ns) {
|
|
// TODO(xiaq): The evaler API should accodomate the use case of evaluating a
|
|
// piece of code in an alternative global namespace.
|
|
|
|
src := parse.Source{Name: "[default bindings]", Code: defaultBindingsElv}
|
|
tree, err := parse.Parse(src)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
op, err := ev.CompileWithGlobal(tree, ns, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// TODO(xiaq): Use stdPorts when it is possible to do so.
|
|
fm := eval.NewTopFrame(ev, src, []*eval.Port{
|
|
{File: os.Stdin}, {File: os.Stdout}, {File: os.Stderr},
|
|
})
|
|
fm.SetLocal(ns)
|
|
err = fm.Eval(op)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// ReadCode reads input from the user.
|
|
func (ed *Editor) ReadCode() (string, error) {
|
|
return ed.app.ReadCode()
|
|
}
|
|
|
|
// Ns returns a namespace for manipulating the editor from Elvish code.
|
|
func (ed *Editor) Ns() eval.Ns {
|
|
return ed.ns
|
|
}
|
|
|
|
func (ed *Editor) notify(s string) { ed.app.Notify(s) }
|
|
|
|
func (ed *Editor) notifyf(format string, args ...interface{}) {
|
|
ed.app.Notify(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.Cons(exc)
|
|
ed.notifyf("[%v error] %v\n"+
|
|
`see stack trace with "use exc; exc:show $edit:exceptions[%d]"`,
|
|
ctx, e, ed.excList.Len()-1)
|
|
} else {
|
|
ed.notifyf("[%v error] %v", ctx, e)
|
|
}
|
|
}
|