src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/edit/config_api.go (about)

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