github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/edit/config_api.go (about) 1 package edit 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 8 "github.com/markusbkk/elvish/pkg/cli" 9 "github.com/markusbkk/elvish/pkg/cli/histutil" 10 "github.com/markusbkk/elvish/pkg/diag" 11 "github.com/markusbkk/elvish/pkg/eval" 12 "github.com/markusbkk/elvish/pkg/eval/vals" 13 "github.com/markusbkk/elvish/pkg/eval/vars" 14 "github.com/markusbkk/elvish/pkg/store/storedefs" 15 ) 16 17 //elvdoc:var max-height 18 // 19 // Maximum height the editor is allowed to use, defaults to `+Inf`. 20 // 21 // By default, the height of the editor is only restricted by the terminal 22 // height. Some modes like location mode can use a lot of lines; as a result, 23 // it can often occupy the entire terminal, and push up your scrollback buffer. 24 // Change this variable to a finite number to restrict the height of the editor. 25 26 func initMaxHeight(appSpec *cli.AppSpec, nb eval.NsBuilder) { 27 maxHeight := newIntVar(-1) 28 appSpec.MaxHeight = func() int { return maxHeight.GetRaw().(int) } 29 nb.AddVar("max-height", maxHeight) 30 } 31 32 func initReadlineHooks(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder) { 33 initBeforeReadline(appSpec, ev, nb) 34 initAfterReadline(appSpec, ev, nb) 35 } 36 37 //elvdoc:var before-readline 38 // 39 // A list of functions to call before each readline cycle. Each function is 40 // called without any arguments. 41 42 func initBeforeReadline(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder) { 43 hook := newListVar(vals.EmptyList) 44 nb.AddVar("before-readline", hook) 45 appSpec.BeforeReadline = append(appSpec.BeforeReadline, func() { 46 callHooks(ev, "$<edit>:before-readline", hook.Get().(vals.List)) 47 }) 48 } 49 50 //elvdoc:var after-readline 51 // 52 // A list of functions to call after each readline cycle. Each function is 53 // called with a single string argument containing the code that has been read. 54 55 func initAfterReadline(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder) { 56 hook := newListVar(vals.EmptyList) 57 nb.AddVar("after-readline", hook) 58 appSpec.AfterReadline = append(appSpec.AfterReadline, func(code string) { 59 callHooks(ev, "$<edit>:after-readline", hook.Get().(vals.List), code) 60 }) 61 } 62 63 //elvdoc:var add-cmd-filters 64 // 65 // List of filters to run before adding a command to history. 66 // 67 // A filter is a function that takes a command as argument and outputs 68 // a boolean value. If any of the filters outputs `$false`, the 69 // command is not saved to history, and the rest of the filters are 70 // not run. The default value of this list contains a filter which 71 // ignores command starts with space. 72 73 func initAddCmdFilters(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder, s histutil.Store) { 74 ignoreLeadingSpace := eval.NewGoFn("<ignore-cmd-with-leading-space>", 75 func(s string) bool { return !strings.HasPrefix(s, " ") }) 76 filters := newListVar(vals.MakeList(ignoreLeadingSpace)) 77 nb.AddVar("add-cmd-filters", filters) 78 79 appSpec.AfterReadline = append(appSpec.AfterReadline, func(code string) { 80 if code != "" && 81 callFilters(ev, "$<edit>:add-cmd-filters", 82 filters.Get().(vals.List), code) { 83 s.AddCmd(storedefs.Cmd{Text: code, Seq: -1}) 84 } 85 // TODO(xiaq): Handle the error. 86 }) 87 } 88 89 //elvdoc:var global-binding 90 // 91 // Global keybindings, consulted for keys not handled by mode-specific bindings. 92 // 93 // See [Keybindings](#keybindings). 94 95 func initGlobalBindings(appSpec *cli.AppSpec, nt notifier, ev *eval.Evaler, nb eval.NsBuilder) { 96 bindingVar := newBindingVar(emptyBindingsMap) 97 appSpec.GlobalBindings = newMapBindings(nt, ev, bindingVar) 98 nb.AddVar("global-binding", bindingVar) 99 } 100 101 func callHooks(ev *eval.Evaler, name string, hook vals.List, args ...interface{}) { 102 if hook.Len() == 0 { 103 return 104 } 105 106 ports, cleanup := eval.PortsFromStdFiles(ev.ValuePrefix()) 107 evalCfg := eval.EvalCfg{Ports: ports[:]} 108 defer cleanup() 109 110 i := -1 111 for it := hook.Iterator(); it.HasElem(); it.Next() { 112 i++ 113 name := fmt.Sprintf("%s[%d]", name, i) 114 fn, ok := it.Elem().(eval.Callable) 115 if !ok { 116 // TODO(xiaq): This is not testable as it depends on stderr. 117 // Make it testable. 118 diag.Complainf(os.Stderr, "%s not function", name) 119 continue 120 } 121 122 err := ev.Call(fn, eval.CallCfg{Args: args, From: name}, evalCfg) 123 if err != nil { 124 diag.ShowError(os.Stderr, err) 125 } 126 } 127 } 128 129 func callFilters(ev *eval.Evaler, name string, filters vals.List, args ...interface{}) bool { 130 if filters.Len() == 0 { 131 return true 132 } 133 134 i := -1 135 for it := filters.Iterator(); it.HasElem(); it.Next() { 136 i++ 137 name := fmt.Sprintf("%s[%d]", name, i) 138 fn, ok := it.Elem().(eval.Callable) 139 if !ok { 140 // TODO(xiaq): This is not testable as it depends on stderr. 141 // Make it testable. 142 diag.Complainf(os.Stderr, "%s not function", name) 143 continue 144 } 145 146 port1, collect, err := eval.CapturePort() 147 if err != nil { 148 diag.Complainf(os.Stderr, "cannot create pipe to run filter") 149 return true 150 } 151 err = ev.Call(fn, eval.CallCfg{Args: args, From: name}, 152 // TODO: Supply the Chan component of port 2. 153 eval.EvalCfg{Ports: []*eval.Port{nil, port1, {File: os.Stderr}}}) 154 out := collect() 155 156 if err != nil { 157 diag.Complainf(os.Stderr, "%s return error", name) 158 continue 159 } 160 if len(out) != 1 { 161 diag.Complainf(os.Stderr, "filter %s should only return $true or $false", name) 162 continue 163 } 164 p, ok := out[0].(bool) 165 if !ok { 166 diag.Complainf(os.Stderr, "filter %s should return bool", name) 167 continue 168 } 169 if !p { 170 return false 171 } 172 } 173 return true 174 } 175 176 func newIntVar(i int) vars.PtrVar { return vars.FromPtr(&i) } 177 func newFloatVar(f float64) vars.PtrVar { return vars.FromPtr(&f) } 178 func newBoolVar(b bool) vars.PtrVar { return vars.FromPtr(&b) } 179 func newListVar(l vals.List) vars.PtrVar { return vars.FromPtr(&l) } 180 func newMapVar(m vals.Map) vars.PtrVar { return vars.FromPtr(&m) } 181 func newFnVar(c eval.Callable) vars.PtrVar { return vars.FromPtr(&c) } 182 func newBindingVar(b bindingsMap) vars.PtrVar { return vars.FromPtr(&b) }