editor.go 4.1 KB

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