github.com/elves/elvish@v0.15.0/pkg/edit/config_api.go (about)

     1  package edit
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/elves/elvish/pkg/cli"
     9  	"github.com/elves/elvish/pkg/cli/histutil"
    10  	"github.com/elves/elvish/pkg/diag"
    11  	"github.com/elves/elvish/pkg/eval"
    12  	"github.com/elves/elvish/pkg/eval/vals"
    13  	"github.com/elves/elvish/pkg/eval/vars"
    14  	"github.com/elves/elvish/pkg/store"
    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.Add("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["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["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["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(store.Cmd{Text: code, Seq: -1})
    84  		}
    85  		// TODO(xiaq): Handle the error.
    86  	})
    87  }
    88  
    89  func callHooks(ev *eval.Evaler, name string, hook vals.List, args ...interface{}) {
    90  	if hook.Len() == 0 {
    91  		return
    92  	}
    93  
    94  	ports, cleanup := eval.PortsFromStdFiles(ev.ValuePrefix())
    95  	evalCfg := eval.EvalCfg{Ports: ports[:]}
    96  	defer cleanup()
    97  
    98  	i := -1
    99  	for it := hook.Iterator(); it.HasElem(); it.Next() {
   100  		i++
   101  		name := fmt.Sprintf("%s[%d]", name, i)
   102  		fn, ok := it.Elem().(eval.Callable)
   103  		if !ok {
   104  			// TODO(xiaq): This is not testable as it depends on stderr.
   105  			// Make it testable.
   106  			diag.Complainf(os.Stderr, "%s not function", name)
   107  			continue
   108  		}
   109  
   110  		err := ev.Call(fn, eval.CallCfg{Args: args, From: name}, evalCfg)
   111  		if err != nil {
   112  			diag.ShowError(os.Stderr, err)
   113  		}
   114  	}
   115  }
   116  
   117  func callFilters(ev *eval.Evaler, name string, filters vals.List, args ...interface{}) bool {
   118  	if filters.Len() == 0 {
   119  		return true
   120  	}
   121  
   122  	i := -1
   123  	for it := filters.Iterator(); it.HasElem(); it.Next() {
   124  		i++
   125  		name := fmt.Sprintf("%s[%d]", name, i)
   126  		fn, ok := it.Elem().(eval.Callable)
   127  		if !ok {
   128  			// TODO(xiaq): This is not testable as it depends on stderr.
   129  			// Make it testable.
   130  			diag.Complainf(os.Stderr, "%s not function", name)
   131  			continue
   132  		}
   133  
   134  		port1, collect, err := eval.CapturePort()
   135  		if err != nil {
   136  			diag.Complainf(os.Stderr, "cannot create pipe to run filter")
   137  			return true
   138  		}
   139  		err = ev.Call(fn, eval.CallCfg{Args: args, From: name},
   140  			// TODO: Supply the Chan component of port 2.
   141  			eval.EvalCfg{Ports: []*eval.Port{nil, port1, {File: os.Stderr}}})
   142  		out := collect()
   143  
   144  		if err != nil {
   145  			diag.Complainf(os.Stderr, "%s return error", name)
   146  			continue
   147  		}
   148  		if len(out) != 1 {
   149  			diag.Complainf(os.Stderr, "filter %s should only return $true or $false", name)
   150  			continue
   151  		}
   152  		p, ok := out[0].(bool)
   153  		if !ok {
   154  			diag.Complainf(os.Stderr, "filter %s should return bool", name)
   155  			continue
   156  		}
   157  		if !p {
   158  			return false
   159  		}
   160  	}
   161  	return true
   162  }
   163  
   164  func newIntVar(i int) vars.PtrVar            { return vars.FromPtr(&i) }
   165  func newFloatVar(f float64) vars.PtrVar      { return vars.FromPtr(&f) }
   166  func newBoolVar(b bool) vars.PtrVar          { return vars.FromPtr(&b) }
   167  func newListVar(l vals.List) vars.PtrVar     { return vars.FromPtr(&l) }
   168  func newMapVar(m vals.Map) vars.PtrVar       { return vars.FromPtr(&m) }
   169  func newFnVar(c eval.Callable) vars.PtrVar   { return vars.FromPtr(&c) }
   170  func newBindingVar(b BindingMap) vars.PtrVar { return vars.FromPtr(&b) }