src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/edit/binding_map.go (about) 1 package edit 2 3 import ( 4 "errors" 5 "sort" 6 7 "src.elv.sh/pkg/eval" 8 "src.elv.sh/pkg/eval/vals" 9 "src.elv.sh/pkg/parse" 10 "src.elv.sh/pkg/ui" 11 ) 12 13 var errValueShouldBeFn = errors.New("value should be function") 14 15 // A special Map that converts its key to ui.Key and ensures that its values 16 // satisfy eval.CallableValue. 17 type bindingsMap struct { 18 vals.Map 19 } 20 21 var emptyBindingsMap = bindingsMap{vals.EmptyMap} 22 23 // Repr returns the representation of the binding table as if it were an 24 // ordinary map keyed by strings. 25 func (bt bindingsMap) Repr(indent int) string { 26 var keys ui.Keys 27 for it := bt.Map.Iterator(); it.HasElem(); it.Next() { 28 k, _ := it.Elem() 29 keys = append(keys, k.(ui.Key)) 30 } 31 sort.Sort(keys) 32 33 builder := vals.NewMapReprBuilder(indent) 34 35 for _, k := range keys { 36 v, _ := bt.Map.Index(k) 37 builder.WritePair(parse.Quote(k.String()), indent+2, vals.Repr(v, indent+2)) 38 } 39 40 return builder.String() 41 } 42 43 // Index converts the index to ui.Key and uses the Index of the inner Map. 44 func (bt bindingsMap) Index(index any) (any, error) { 45 key, err := toKey(index) 46 if err != nil { 47 return nil, err 48 } 49 return vals.Index(bt.Map, key) 50 } 51 52 func (bt bindingsMap) HasKey(k any) bool { 53 _, ok := bt.Map.Index(k) 54 return ok 55 } 56 57 func (bt bindingsMap) GetKey(k ui.Key) eval.Callable { 58 v, ok := bt.Map.Index(k) 59 if !ok { 60 panic("get called when key not present") 61 } 62 return v.(eval.Callable) 63 } 64 65 // Assoc converts the index to ui.Key, ensures that the value is CallableValue, 66 // uses the Assoc of the inner Map and converts the result to a BindingTable. 67 func (bt bindingsMap) Assoc(k, v any) (any, error) { 68 key, err := toKey(k) 69 if err != nil { 70 return nil, err 71 } 72 f, ok := v.(eval.Callable) 73 if !ok { 74 return nil, errValueShouldBeFn 75 } 76 map2 := bt.Map.Assoc(key, f) 77 return bindingsMap{map2}, nil 78 } 79 80 // Dissoc converts the key to ui.Key and calls the Dissoc method of the inner 81 // map. 82 func (bt bindingsMap) Dissoc(k any) any { 83 key, err := toKey(k) 84 if err != nil { 85 // Key is invalid; dissoc is no-op. 86 return bt 87 } 88 return bindingsMap{bt.Map.Dissoc(key)} 89 } 90 91 func makeBindingMap(raw vals.Map) (bindingsMap, error) { 92 converted := vals.EmptyMap 93 for it := raw.Iterator(); it.HasElem(); it.Next() { 94 k, v := it.Elem() 95 f, ok := v.(eval.Callable) 96 if !ok { 97 return emptyBindingsMap, errValueShouldBeFn 98 } 99 key, err := toKey(k) 100 if err != nil { 101 return bindingsMap{}, err 102 } 103 converted = converted.Assoc(key, f) 104 } 105 106 return bindingsMap{converted}, nil 107 } 108 109 type bindingTipEntry struct { 110 text string 111 fnNames []string 112 } 113 114 func bindingTip(text string, fnNames ...string) bindingTipEntry { 115 return bindingTipEntry{text, fnNames} 116 } 117 118 // Given a binding map and a list of function groups, returns a text describing 119 // the keys that are bound to any function in each group. 120 // 121 // This uses Elvish qnames for both the binding map and the functions because 122 // the place that calls bindingTips may not have direct access to them. 123 func bindingTips(ns *eval.Ns, binding string, entries ...bindingTipEntry) ui.Text { 124 m := getVar(ns, binding).(bindingsMap) 125 var t ui.Text 126 for _, entry := range entries { 127 values := make([]any, len(entry.fnNames)) 128 for i, fnName := range entry.fnNames { 129 values[i] = getVar(ns, fnName+eval.FnSuffix) 130 } 131 keys := keysBoundTo(m, values) 132 if len(keys) == 0 { 133 continue 134 } 135 if len(t) > 0 { 136 t = ui.Concat(t, ui.T(" ")) 137 } 138 for _, k := range keys { 139 t = ui.Concat(t, ui.T(k.String(), ui.Inverse), ui.T(" ")) 140 } 141 t = ui.Concat(t, ui.T(entry.text)) 142 } 143 return t 144 } 145 146 func getVar(ns *eval.Ns, qname string) any { 147 segs := eval.SplitQNameSegs(qname) 148 for _, seg := range segs[:len(segs)-1] { 149 ns = ns.IndexString(seg).Get().(*eval.Ns) 150 } 151 return ns.IndexString(segs[len(segs)-1]).Get() 152 } 153 154 func keysBoundTo(m bindingsMap, values []any) []ui.Key { 155 var keys []ui.Key 156 for it := m.Iterator(); it.HasElem(); it.Next() { 157 k, v := it.Elem() 158 for _, value := range values { 159 if v == value { 160 keys = append(keys, k.(ui.Key)) 161 continue 162 } 163 } 164 } 165 return keys 166 }