gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/cmds/core/elvish/edit/api.go (about)

     1  package edit
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"unicode/utf8"
     7  
     8  	"github.com/u-root/u-root/cmds/core/elvish/edit/eddefs"
     9  	"github.com/u-root/u-root/cmds/core/elvish/edit/ui"
    10  	"github.com/u-root/u-root/cmds/core/elvish/eval"
    11  	"github.com/u-root/u-root/cmds/core/elvish/eval/vars"
    12  )
    13  
    14  // This file implements types and functions for interactions with the
    15  // Elvishscript runtime.
    16  
    17  var (
    18  	errNotNav             = errors.New("not in navigation mode")
    19  	errLineMustBeString   = errors.New("line must be string")
    20  	errDotMustBeString    = errors.New("dot must be string")
    21  	errDotMustBeInt       = errors.New("dot must be integer")
    22  	errDotOutOfRange      = errors.New("dot out of range")
    23  	errDotInsideCodepoint = errors.New("dot cannot be inside a codepoint")
    24  	errEditorInactive     = errors.New("editor inactive")
    25  )
    26  
    27  // makeNs makes the edit: namespace.
    28  func makeNs(ed *editor) eval.Ns {
    29  	ns := eval.NewNs()
    30  
    31  	// TODO(xiaq): Everything here should be registered to some registry instead
    32  	// of centralized here.
    33  
    34  	// Internal states.
    35  	ns["current-command"] = vars.FromSetGet(
    36  		func(v interface{}) error {
    37  			if !ed.active {
    38  				return errEditorInactive
    39  			}
    40  			if s, ok := v.(string); ok {
    41  				ed.buffer = s
    42  				ed.dot = len(ed.buffer)
    43  			} else {
    44  				return errLineMustBeString
    45  			}
    46  			return nil
    47  		},
    48  		func() interface{} { return ed.buffer },
    49  	)
    50  	ns["-dot"] = vars.FromSetGet(
    51  		func(v interface{}) error {
    52  			s, ok := v.(string)
    53  			if !ok {
    54  				return errDotMustBeString
    55  			}
    56  			i, err := strconv.Atoi(s)
    57  			if err != nil {
    58  				if err.(*strconv.NumError).Err == strconv.ErrRange {
    59  					return errDotOutOfRange
    60  				} else {
    61  					return errDotMustBeInt
    62  				}
    63  			}
    64  			if i < 0 || i > len(ed.buffer) {
    65  				return errDotOutOfRange
    66  			}
    67  			if i < len(ed.buffer) {
    68  				r, _ := utf8.DecodeRuneInString(ed.buffer[i:])
    69  				if r == utf8.RuneError {
    70  					return errDotInsideCodepoint
    71  				}
    72  			}
    73  			ed.dot = i
    74  			return nil
    75  		},
    76  		func() interface{} { return strconv.Itoa(ed.dot) },
    77  	)
    78  	ns["selected-file"] = vars.FromGet(
    79  		func() interface{} {
    80  			if !ed.active {
    81  				throw(errEditorInactive)
    82  			}
    83  			nav, ok := ed.mode.(*navigation)
    84  			if !ok {
    85  				throw(errNotNav)
    86  			}
    87  			return nav.current.selectedName()
    88  		},
    89  	)
    90  
    91  	// Functions.
    92  	fns := map[string]interface{}{
    93  		"insert-at-dot": ed.InsertAtDot,
    94  		"replace-input": ed.replaceInput,
    95  		"wordify":       wordifyBuiltin,
    96  		"-dump-buf":     ed.dumpBuf,
    97  	}
    98  	ns.AddBuiltinFns("edit:", fns)
    99  
   100  	ns.AddBuiltinFnCustom("edit:", "binding-table", eddefs.MakeBindingMapCallable())
   101  	ns.AddBuiltinFnCustom("edit:", "styled", &styledCallable{})
   102  	ns.AddBuiltinFnCustom("edit:", "key", &uiToKeyCallable{})
   103  
   104  	return ns
   105  }
   106  
   107  type styledCallable struct {
   108  }
   109  
   110  func (*styledCallable) Target() interface{} {
   111  	return styled
   112  }
   113  
   114  func (*styledCallable) Call(
   115  	f *eval.Frame, args []interface{}, opts eval.RawOptions, inputs eval.Inputs) ([]interface{}, error) {
   116  	out, err := styled(args[0].(string), args[1])
   117  	return []interface{}{out}, err
   118  }
   119  
   120  type uiToKeyCallable struct {
   121  }
   122  
   123  func (*uiToKeyCallable) Target() interface{} {
   124  	return ui.ToKey
   125  }
   126  
   127  func (*uiToKeyCallable) Call(f *eval.Frame, args []interface{}, opts eval.RawOptions, inputs eval.Inputs) ([]interface{}, error) {
   128  	out := ui.ToKey(args[0])
   129  	return []interface{}{out}, nil
   130  }
   131  
   132  // CallFn calls an Fn, displaying its outputs and possible errors as editor
   133  // notifications. It is the preferred way to call a Fn while the editor is
   134  // active.
   135  func (ed *editor) CallFn(fn eval.Callable, args ...interface{}) {
   136  	ports := []*eval.Port{
   137  		eval.DevNullClosedChan, ed.notifyPort, ed.notifyPort,
   138  	}
   139  	// XXX There is no source to pass to NewTopEvalCtx.
   140  	ec := eval.NewTopFrame(ed.evaler, eval.NewInternalSource("[editor]"), ports)
   141  	ex := ec.Call(fn, args, eval.NoOpts)
   142  	if ex != nil {
   143  		ed.Notify("function error: %s", ex.Error())
   144  	}
   145  
   146  	// XXX Concurrency-dangerous!
   147  	ed.refresh(true, true)
   148  }