forked from nsf/gocode
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcursorcontext.go
383 lines (358 loc) · 9.69 KB
/
cursorcontext.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
package main
import (
"bytes"
"go/ast"
"go/parser"
"go/scanner"
"go/token"
"log"
)
type cursor_context struct {
decl *decl
partial string
struct_field bool
decl_import bool
}
type token_iterator struct {
tokens []token_item
token_index int
}
type token_item struct {
off int
tok token.Token
lit string
}
func (i token_item) literal() string {
if i.tok.IsLiteral() {
return i.lit
} else {
return i.tok.String()
}
return ""
}
func new_token_iterator(src []byte, cursor int) token_iterator {
tokens := make([]token_item, 0, 1000)
var s scanner.Scanner
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
s.Init(file, src, nil, 0)
for {
pos, tok, lit := s.Scan()
off := fset.Position(pos).Offset
if tok == token.EOF || cursor <= off {
break
}
tokens = append(tokens, token_item{
off: off,
tok: tok,
lit: lit,
})
}
return token_iterator{
tokens: tokens,
token_index: len(tokens) - 1,
}
}
func (this *token_iterator) token() token_item {
return this.tokens[this.token_index]
}
func (this *token_iterator) go_back() bool {
if this.token_index <= 0 {
return false
}
this.token_index--
return true
}
var bracket_pairs_map = map[token.Token]token.Token{
token.RPAREN: token.LPAREN,
token.RBRACK: token.LBRACK,
token.RBRACE: token.LBRACE,
}
func (ti *token_iterator) skip_to_left(left, right token.Token) bool {
if ti.token().tok == left {
return true
}
balance := 1
for balance != 0 {
if !ti.go_back() {
return false
}
switch ti.token().tok {
case right:
balance++
case left:
balance--
}
}
return true
}
// when the cursor is at the ')' or ']' or '}', move the cursor to an opposite
// bracket pair, this functions takes nested bracket pairs into account
func (this *token_iterator) skip_to_balanced_pair() bool {
right := this.token().tok
left := bracket_pairs_map[right]
return this.skip_to_left(left, right)
}
// Move the cursor to the open brace of the current block, taking nested blocks
// into account.
func (this *token_iterator) skip_to_left_curly() bool {
return this.skip_to_left(token.LBRACE, token.RBRACE)
}
// Extract the type expression right before the enclosing curly bracket block.
// Examples (# - the cursor):
// &lib.Struct{Whatever: 1, Hel#} // returns "lib.Struct"
// X{#} // returns X
// The idea is that we check if this type expression is a type and it is, we
// can apply special filtering for autocompletion results.
// Sadly, this doesn't cover anonymous structs.
func (ti *token_iterator) extract_struct_type() string {
if !ti.skip_to_left_curly() {
return ""
}
if !ti.go_back() {
return ""
}
if ti.token().tok != token.IDENT {
return ""
}
b := ti.token().literal()
if !ti.go_back() {
return b
}
if ti.token().tok != token.PERIOD {
return b
}
if !ti.go_back() {
return b
}
if ti.token().tok != token.IDENT {
return b
}
return ti.token().literal() + "." + b
}
// Starting from the token under the cursor move back and extract something
// that resembles a valid Go primary expression. Examples of primary expressions
// from Go spec:
// x
// 2
// (s + ".txt")
// f(3.1415, true)
// Point{1, 2}
// m["foo"]
// s[i : j + 1]
// obj.color
// f.p[i].x()
//
// As you can see we can move through all of them using balanced bracket
// matching and applying simple rules
// E.g.
// Point{1, 2}.m["foo"].s[i : j + 1].MethodCall(a, func(a, b int) int { return a + b }).
// Can be seen as:
// Point{ }.m[ ].s[ ].MethodCall( ).
// Which boils the rules down to these connected via dots:
// ident
// ident[]
// ident{}
// ident()
// Of course there are also slightly more complicated rules for brackets:
// ident{}.ident()[5][4](), etc.
func (this *token_iterator) extract_go_expr() string {
orig := this.token_index
// Contains the type of the previously scanned token (initialized with
// the token right under the cursor). This is the token to the *right* of
// the current one.
prev := this.token().tok
loop:
for {
if !this.go_back() {
return token_items_to_string(this.tokens[:orig])
}
switch this.token().tok {
case token.PERIOD:
// If the '.' is not followed by IDENT, it's invalid.
if prev != token.IDENT {
break loop
}
case token.IDENT:
// Valid tokens after IDENT are '.', '[', '{' and '('.
switch prev {
case token.PERIOD, token.LBRACK, token.LBRACE, token.LPAREN:
// all ok
default:
break loop
}
case token.RBRACE:
// This one can only be a part of type initialization, like:
// Dummy{}.Hello()
// It is valid Go if Hello method is defined on a non-pointer receiver.
if prev != token.PERIOD {
break loop
}
this.skip_to_balanced_pair()
case token.RPAREN, token.RBRACK:
// After ']' and ')' their opening counterparts are valid '[', '(',
// as well as the dot.
switch prev {
case token.PERIOD, token.LBRACK, token.LPAREN:
// all ok
default:
break loop
}
this.skip_to_balanced_pair()
default:
break loop
}
prev = this.token().tok
}
expr := token_items_to_string(this.tokens[this.token_index+1 : orig])
if *g_debug {
log.Printf("extracted expression tokens: %s", expr)
}
return expr
}
// Given a slice of token_item, reassembles them into the original literal
// expression.
func token_items_to_string(tokens []token_item) string {
var buf bytes.Buffer
for _, t := range tokens {
buf.WriteString(t.literal())
}
return buf.String()
}
// this function is called when the cursor is at the '.' and you need to get the
// declaration before that dot
func (c *auto_complete_context) deduce_cursor_decl(iter *token_iterator) *decl {
expr, err := parser.ParseExpr(iter.extract_go_expr())
if err != nil {
return nil
}
return expr_to_decl(expr, c.current.scope)
}
// try to find and extract the surrounding struct literal type
func (c *auto_complete_context) deduce_struct_type_decl(iter *token_iterator) *decl {
typ := iter.extract_struct_type()
if typ == "" {
return nil
}
expr, err := parser.ParseExpr(typ)
if err != nil {
return nil
}
decl := type_to_decl(expr, c.current.scope)
if decl == nil {
return nil
}
if _, ok := decl.typ.(*ast.StructType); !ok {
return nil
}
return decl
}
// Entry point from autocompletion, the function looks at text before the cursor
// and figures out the declaration the cursor is on. This declaration is
// used in filtering the resulting set of autocompletion suggestions.
func (c *auto_complete_context) deduce_cursor_context(file []byte, cursor int) (cursor_context, bool) {
if cursor <= 0 {
return cursor_context{}, true
}
iter := new_token_iterator(file, cursor)
if len(iter.tokens) == 0 {
return cursor_context{}, false
}
// figure out what is just before the cursor
switch tok := iter.token(); tok.tok {
case token.STRING:
// make sure cursor is inside the string
s := tok.literal()
if len(s) > 1 && s[len(s)-1] == '"' && tok.off+len(s) <= cursor {
return cursor_context{}, true
}
// now figure out if inside an import declaration
var ptok = token.STRING
for iter.go_back() {
itok := iter.token().tok
switch itok {
case token.STRING:
switch ptok {
case token.SEMICOLON, token.IDENT, token.PERIOD:
default:
return cursor_context{}, true
}
case token.LPAREN, token.SEMICOLON:
switch ptok {
case token.STRING, token.IDENT, token.PERIOD:
default:
return cursor_context{}, true
}
case token.IDENT, token.PERIOD:
switch ptok {
case token.STRING:
default:
return cursor_context{}, true
}
case token.IMPORT:
switch ptok {
case token.STRING, token.IDENT, token.PERIOD, token.LPAREN:
path_len := cursor - tok.off
path := s[1:path_len]
return cursor_context{decl_import: true, partial: path}, true
default:
return cursor_context{}, true
}
default:
return cursor_context{}, true
}
ptok = itok
}
case token.PERIOD:
// we're '<whatever>.'
// figure out decl, Partial is ""
decl := c.deduce_cursor_decl(&iter)
return cursor_context{decl: decl}, decl != nil
case token.IDENT, token.TYPE, token.CONST, token.VAR, token.FUNC, token.PACKAGE:
// we're '<whatever>.<ident>'
// parse <ident> as Partial and figure out decl
var partial string
if tok.tok == token.IDENT {
// Calculate the offset of the cursor position within the identifier.
// For instance, if we are 'ab#c', we want partial_len = 2 and partial = ab.
partial_len := cursor - tok.off
// If it happens that the cursor is past the end of the literal,
// means there is a space between the literal and the cursor, think
// of it as no context, because that's what it really is.
if partial_len > len(tok.literal()) {
return cursor_context{}, true
}
partial = tok.literal()[0:partial_len]
} else {
// Do not try to truncate if it is not an identifier.
partial = tok.literal()
}
iter.go_back()
switch iter.token().tok {
case token.PERIOD:
decl := c.deduce_cursor_decl(&iter)
return cursor_context{decl: decl, partial: partial}, decl != nil
case token.COMMA, token.LBRACE:
// This can happen for struct fields:
// &Struct{Hello: 1, Wor#} // (# - the cursor)
// Let's try to find the struct type
decl := c.deduce_struct_type_decl(&iter)
return cursor_context{
decl: decl,
partial: partial,
struct_field: decl != nil,
}, true
default:
return cursor_context{partial: partial}, true
}
case token.COMMA, token.LBRACE:
// Try to parse the current expression as a structure initialization.
decl := c.deduce_struct_type_decl(&iter)
return cursor_context{
decl: decl,
partial: "",
struct_field: decl != nil,
}, true
}
return cursor_context{}, true
}