config_api.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package edit
  2. import (
  3. "fmt"
  4. "os"
  5. "strings"
  6. "src.elv.sh/pkg/cli"
  7. "src.elv.sh/pkg/cli/histutil"
  8. "src.elv.sh/pkg/diag"
  9. "src.elv.sh/pkg/eval"
  10. "src.elv.sh/pkg/eval/vals"
  11. "src.elv.sh/pkg/eval/vars"
  12. "src.elv.sh/pkg/store/storedefs"
  13. )
  14. func initMaxHeight(appSpec *cli.AppSpec, nb eval.NsBuilder) {
  15. maxHeight := newIntVar(-1)
  16. appSpec.MaxHeight = func() int { return maxHeight.GetRaw().(int) }
  17. nb.AddVar("max-height", maxHeight)
  18. }
  19. func initReadlineHooks(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder) {
  20. initBeforeReadline(appSpec, ev, nb)
  21. initAfterReadline(appSpec, ev, nb)
  22. }
  23. func initBeforeReadline(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder) {
  24. hook := newListVar(vals.EmptyList)
  25. nb.AddVar("before-readline", hook)
  26. appSpec.BeforeReadline = append(appSpec.BeforeReadline, func() {
  27. callHooks(ev, "$<edit>:before-readline", hook.Get().(vals.List))
  28. })
  29. }
  30. func initAfterReadline(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder) {
  31. hook := newListVar(vals.EmptyList)
  32. nb.AddVar("after-readline", hook)
  33. appSpec.AfterReadline = append(appSpec.AfterReadline, func(code string) {
  34. callHooks(ev, "$<edit>:after-readline", hook.Get().(vals.List), code)
  35. })
  36. }
  37. func initAddCmdFilters(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder, s histutil.Store) {
  38. ignoreLeadingSpace := eval.NewGoFn("<ignore-cmd-with-leading-space>",
  39. func(s string) bool { return !strings.HasPrefix(s, " ") })
  40. filters := newListVar(vals.MakeList(ignoreLeadingSpace))
  41. nb.AddVar("add-cmd-filters", filters)
  42. appSpec.AfterReadline = append(appSpec.AfterReadline, func(code string) {
  43. if code != "" &&
  44. callFilters(ev, "$<edit>:add-cmd-filters",
  45. filters.Get().(vals.List), code) {
  46. s.AddCmd(storedefs.Cmd{Text: code, Seq: -1})
  47. }
  48. // TODO(xiaq): Handle the error.
  49. })
  50. }
  51. func initGlobalBindings(appSpec *cli.AppSpec, nt notifier, ev *eval.Evaler, nb eval.NsBuilder) {
  52. bindingVar := newBindingVar(emptyBindingsMap)
  53. appSpec.GlobalBindings = newMapBindings(nt, ev, bindingVar)
  54. nb.AddVar("global-binding", bindingVar)
  55. }
  56. func callHooks(ev *eval.Evaler, name string, hook vals.List, args ...any) {
  57. if hook.Len() == 0 {
  58. return
  59. }
  60. ports, cleanup := eval.PortsFromStdFiles(ev.ValuePrefix())
  61. evalCfg := eval.EvalCfg{Ports: ports[:]}
  62. defer cleanup()
  63. i := -1
  64. for it := hook.Iterator(); it.HasElem(); it.Next() {
  65. i++
  66. name := fmt.Sprintf("%s[%d]", name, i)
  67. fn, ok := it.Elem().(eval.Callable)
  68. if !ok {
  69. // TODO(xiaq): This is not testable as it depends on stderr.
  70. // Make it testable.
  71. diag.Complainf(os.Stderr, "%s not function", name)
  72. continue
  73. }
  74. err := ev.Call(fn, eval.CallCfg{Args: args, From: name}, evalCfg)
  75. if err != nil {
  76. diag.ShowError(os.Stderr, err)
  77. }
  78. }
  79. }
  80. func callFilters(ev *eval.Evaler, name string, filters vals.List, args ...any) bool {
  81. if filters.Len() == 0 {
  82. return true
  83. }
  84. i := -1
  85. for it := filters.Iterator(); it.HasElem(); it.Next() {
  86. i++
  87. name := fmt.Sprintf("%s[%d]", name, i)
  88. fn, ok := it.Elem().(eval.Callable)
  89. if !ok {
  90. // TODO(xiaq): This is not testable as it depends on stderr.
  91. // Make it testable.
  92. diag.Complainf(os.Stderr, "%s not function", name)
  93. continue
  94. }
  95. port1, collect, err := eval.CapturePort()
  96. if err != nil {
  97. diag.Complainf(os.Stderr, "cannot create pipe to run filter")
  98. return true
  99. }
  100. err = ev.Call(fn, eval.CallCfg{Args: args, From: name},
  101. // TODO: Supply the Chan component of port 2.
  102. eval.EvalCfg{Ports: []*eval.Port{nil, port1, {File: os.Stderr}}})
  103. out := collect()
  104. if err != nil {
  105. diag.Complainf(os.Stderr, "%s return error", name)
  106. continue
  107. }
  108. if len(out) != 1 {
  109. diag.Complainf(os.Stderr, "filter %s should only return $true or $false", name)
  110. continue
  111. }
  112. p, ok := out[0].(bool)
  113. if !ok {
  114. diag.Complainf(os.Stderr, "filter %s should return bool", name)
  115. continue
  116. }
  117. if !p {
  118. return false
  119. }
  120. }
  121. return true
  122. }
  123. func newIntVar(i int) vars.PtrVar { return vars.FromPtr(&i) }
  124. func newFloatVar(f float64) vars.PtrVar { return vars.FromPtr(&f) }
  125. func newBoolVar(b bool) vars.PtrVar { return vars.FromPtr(&b) }
  126. func newListVar(l vals.List) vars.PtrVar { return vars.FromPtr(&l) }
  127. func newMapVar(m vals.Map) vars.PtrVar { return vars.FromPtr(&m) }
  128. func newFnVar(c eval.Callable) vars.PtrVar { return vars.FromPtr(&c) }
  129. func newBindingVar(b bindingsMap) vars.PtrVar { return vars.FromPtr(&b) }