github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/starlib/completions.go (about) 1 // Copyright 2022 Edward McFarlane. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package starlib 6 7 import ( 8 "sort" 9 "strconv" 10 "strings" 11 "unicode/utf8" 12 13 "go.starlark.net/starlark" 14 ) 15 16 // finxPrefix assumes sorted arrays of keys 17 func findPrefix(line string, depth int, pfx string, keyss ...[]string) (c []string) { 18 pfx = strings.TrimSpace(pfx) // ignore any whitespacing 19 for _, keys := range keyss { 20 i := sort.SearchStrings(keys, pfx) 21 j := i 22 for ; j < len(keys); j++ { 23 if !strings.HasPrefix(keys[j], pfx) { 24 break 25 } 26 } 27 c = append(c, keys[i:j]...) 28 } 29 if len(keyss) > 1 { 30 sort.Strings(c) 31 } 32 33 // Add line start 34 for i := range c { 35 c[i] = line[:depth] + c[i] 36 } 37 return c 38 } 39 40 // An Args is a starlark Callable with arguments. 41 type Args interface { 42 starlark.Callable 43 ArgNames() []string 44 } 45 46 // Completer is an experimental autocompletion for starlark lines. 47 // TODO: drop and switch to a proper language server. 48 type Completer struct { 49 starlark.StringDict 50 } 51 52 type typ int 53 54 const ( 55 unknown typ = iota - 1 56 root // 57 dot // . 58 brack // [] 59 paren // () 60 brace // {} 61 ) 62 63 func (t typ) String() string { 64 switch t { 65 case dot: 66 return "." 67 case brack: 68 return "[" 69 case paren: 70 return "(" 71 case brace: 72 return "{" 73 default: 74 return "?" 75 } 76 } 77 78 func enclosed(line string) (typ, int) { 79 k := len(line) 80 var parens, bracks, braces int 81 for size := 0; k > 0; k -= size { 82 var r rune 83 r, size = utf8.DecodeLastRuneInString(line[:k]) 84 switch r { 85 case '(': 86 parens += 1 87 case ')': 88 parens -= 1 89 case '[': 90 bracks += 1 91 case ']': 92 bracks -= 1 93 case '{': 94 braces += 1 95 case '}': 96 braces -= 1 97 } 98 if parens > 0 { 99 return paren, k - size 100 } 101 if bracks > 0 { 102 return brack, k - size 103 } 104 if braces > 0 { 105 return brace, k - size 106 } 107 } 108 return unknown, -1 109 } 110 111 // Complete tries to resolve a starlark line variable to global named values. 112 // TODO: use a proper parser to resolve values. 113 func (c Completer) Complete(line string) (values []string) { 114 if strings.Count(line, " ") == len(line) { 115 // tab complete indent 116 return []string{strings.Repeat(" ", (len(line)/4)*4+4)} 117 } 118 119 type x struct { 120 typ typ 121 value string 122 depth int 123 } 124 125 var xs []x 126 127 i := len(line) 128 j := i 129 130 Loop: 131 for size := 0; i > 0; i -= size { 132 var r rune 133 switch r, size = utf8.DecodeLastRuneInString(line[:i]); r { 134 case '.': // attr 135 xs = append(xs, x{dot, line[i:j], i}) 136 case '[': // index 137 xs = append(xs, x{brack, line[i:j], i}) 138 case '(': // functions 139 xs = append(xs, x{paren, line[i:j], i}) 140 case ' ', ',': 141 typ, k := enclosed(line[:i-size]) 142 143 // Use ArgNames as possible completion 144 if typ == paren { 145 xs = append(xs, x{typ, line[i:j], i}) 146 i, j = k, k 147 continue // loop 148 } 149 150 break Loop 151 case ';', '=', '{', '}': 152 break Loop // EOF 153 default: 154 continue // capture 155 } 156 j = i - size 157 } 158 xs = append(xs, x{root, line[i:j], i}) 159 160 var cursor starlark.Value 161 for i := len(xs) - 1; i >= 0; i-- { 162 x := xs[i] 163 164 switch x.typ { 165 case root: 166 if i == 0 { 167 keys := [][]string{c.Keys(), starlark.Universe.Keys()} 168 return findPrefix(line, x.depth, x.value, keys...) 169 } 170 171 if g := c.StringDict[x.value]; g != nil { 172 cursor = g 173 } else if u := starlark.Universe[x.value]; u != nil { 174 cursor = u 175 } 176 case dot: 177 v, ok := cursor.(starlark.HasAttrs) 178 if !ok { 179 return 180 } 181 182 if i == 0 { 183 return findPrefix(line, x.depth, x.value, v.AttrNames()) 184 } 185 186 p, err := v.Attr(x.value) 187 if p == nil || err != nil { 188 return 189 } 190 cursor = p 191 case brack: 192 if i != 0 { 193 // TODO: resolve arg? fmt.Printf("TODO: resolve arg %s\n", x.value) 194 return 195 } 196 197 if strings.HasPrefix(x.value, "\"") { 198 v, ok := cursor.(starlark.IterableMapping) 199 if !ok { 200 return 201 } 202 203 iter := v.Iterate() 204 var keys []string 205 var p starlark.Value 206 for iter.Next(&p) { 207 s, ok := starlark.AsString(p) 208 if !ok { 209 continue // skip 210 } 211 keys = append(keys, strconv.Quote(s)+"]") 212 } 213 return findPrefix(line, x.depth, x.value, keys) 214 } 215 keys := [][]string{c.Keys(), starlark.Universe.Keys()} 216 return findPrefix(line, x.depth, x.value, keys...) 217 218 case paren: 219 if i != 0 { 220 return // Functions aren't evalutated 221 } 222 223 keys := [][]string{c.Keys(), starlark.Universe.Keys()} 224 v, ok := cursor.(Args) 225 if ok { 226 args := v.ArgNames() 227 for i := range args { 228 args[i] = args[i] + " = " 229 } 230 keys = append(keys, args) 231 } 232 233 return findPrefix(line, x.depth, x.value, keys...) 234 default: 235 return 236 } 237 } 238 return 239 }