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