@@ -65,6 +65,8 @@ func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, po
65
65
return ranges , nil
66
66
}
67
67
68
+ // highlightPath returns ranges to highlight for the given enclosing path,
69
+ // which should be the result of astutil.PathEnclosingInterval.
68
70
func highlightPath (path []ast.Node , file * ast.File , info * types.Info ) (map [posRange ]struct {}, error ) {
69
71
result := make (map [posRange ]struct {})
70
72
switch node := path [0 ].(type ) {
@@ -133,115 +135,167 @@ type posRange struct {
133
135
start , end token.Pos
134
136
}
135
137
136
- func highlightFuncControlFlow (path []ast.Node , result map [posRange ]struct {}) {
137
- var enclosingFunc ast.Node
138
- var returnStmt * ast.ReturnStmt
139
- var resultsList * ast.FieldList
140
- inReturnList := false
141
-
142
- Outer:
143
- // Reverse walk the path till we get to the func block.
138
+ // highlightFuncControlFlow adds highlight ranges to the result map to
139
+ // associate results and result parameters.
140
+ //
141
+ // Specifically, if the cursor is in a result or result parameter, all
142
+ // results and result parameters with the same index are highlighted. If the
143
+ // cursor is in a 'func' or 'return' keyword, the func keyword as well as all
144
+ // returns from that func are highlighted.
145
+ //
146
+ // As a special case, if the cursor is within a complicated expression, control
147
+ // flow highlighting is disabled, as it would highlight too much.
148
+ func highlightFuncControlFlow (path []ast.Node , result map [posRange ]unit ) {
149
+
150
+ var (
151
+ funcType * ast.FuncType // type of enclosing func, or nil
152
+ funcBody * ast.BlockStmt // body of enclosing func, or nil
153
+ returnStmt * ast.ReturnStmt // enclosing ReturnStmt within the func, or nil
154
+ )
155
+
156
+ findEnclosingFunc:
144
157
for i , n := range path {
145
- switch node := n .(type ) {
158
+ switch n := n .(type ) {
159
+ // TODO(rfindley, low priority): these pre-existing cases for KeyValueExpr
160
+ // and CallExpr appear to avoid highlighting when the cursor is in a
161
+ // complicated expression. However, the basis for this heuristic is
162
+ // unclear. Can we formalize a rationale?
146
163
case * ast.KeyValueExpr :
147
- // If cursor is in a key: value expr, we don't want control flow highlighting
164
+ // If cursor is in a key: value expr, we don't want control flow highlighting.
148
165
return
166
+
149
167
case * ast.CallExpr :
150
168
// If cursor is an arg in a callExpr, we don't want control flow highlighting.
151
169
if i > 0 {
152
- for _ , arg := range node .Args {
170
+ for _ , arg := range n .Args {
153
171
if arg == path [i - 1 ] {
154
172
return
155
173
}
156
174
}
157
175
}
158
- case * ast.Field :
159
- inReturnList = true
176
+
160
177
case * ast.FuncLit :
161
- enclosingFunc = n
162
- resultsList = node .Type .Results
163
- break Outer
178
+ funcType = n .Type
179
+ funcBody = n .Body
180
+ break findEnclosingFunc
181
+
164
182
case * ast.FuncDecl :
165
- enclosingFunc = n
166
- resultsList = node .Type .Results
167
- break Outer
183
+ funcType = n .Type
184
+ funcBody = n .Body
185
+ break findEnclosingFunc
186
+
168
187
case * ast.ReturnStmt :
169
- returnStmt = node
170
- // If the cursor is not directly in a *ast.ReturnStmt, then
171
- // we need to know if it is within one of the values that is being returned.
172
- inReturnList = inReturnList || path [0 ] != returnStmt
173
- }
174
- }
175
- // Cursor is not in a function.
176
- if enclosingFunc == nil {
177
- return
178
- }
179
- // If the cursor is on a "return" or "func" keyword, we should highlight all of the exit
180
- // points of the function, including the "return" and "func" keywords.
181
- highlightAllReturnsAndFunc := path [0 ] == returnStmt || path [0 ] == enclosingFunc
182
- switch path [0 ].(type ) {
183
- case * ast.Ident , * ast.BasicLit :
184
- // Cursor is in an identifier and not in a return statement or in the results list.
185
- if returnStmt == nil && ! inReturnList {
186
- return
188
+ returnStmt = n
187
189
}
188
- case * ast.FuncType :
189
- highlightAllReturnsAndFunc = true
190
190
}
191
- // The user's cursor may be within the return statement of a function,
192
- // or within the result section of a function's signature.
193
- // index := -1
194
- var nodes []ast.Node
195
- if returnStmt != nil {
196
- for _ , n := range returnStmt .Results {
197
- nodes = append (nodes , n )
198
- }
199
- } else if resultsList != nil {
200
- for _ , n := range resultsList .List {
201
- nodes = append (nodes , n )
202
- }
203
- }
204
- _ , index := nodeAtPos (nodes , path [0 ].Pos ())
205
191
206
- // Highlight the correct argument in the function declaration return types.
207
- if resultsList != nil && - 1 < index && index < len (resultsList .List ) {
208
- rng := posRange {
209
- start : resultsList .List [index ].Pos (),
210
- end : resultsList .List [index ].End (),
211
- }
212
- result [rng ] = struct {}{}
192
+ if funcType == nil {
193
+ return // cursor is not in a function
213
194
}
214
- // Add the "func" part of the func declaration.
215
- if highlightAllReturnsAndFunc {
216
- r := posRange {
217
- start : enclosingFunc .Pos (),
218
- end : enclosingFunc .Pos () + token .Pos (len ("func" )),
195
+
196
+ // Helper functions for inspecting the current location.
197
+ var (
198
+ pos = path [0 ].Pos ()
199
+ inSpan = func (start , end token.Pos ) bool { return start <= pos && pos < end }
200
+ inNode = func (n ast.Node ) bool { return inSpan (n .Pos (), n .End ()) }
201
+ )
202
+
203
+ inResults := funcType .Results != nil && inNode (funcType .Results )
204
+
205
+ // If the cursor is on a "return" or "func" keyword, but not highlighting any
206
+ // specific field or expression, we should highlight all of the exit points
207
+ // of the function, including the "return" and "func" keywords.
208
+ funcEnd := funcType .Func + token .Pos (len ("func" ))
209
+ highlightAll := path [0 ] == returnStmt || inSpan (funcType .Func , funcEnd )
210
+ var highlightIndexes map [int ]bool
211
+
212
+ if highlightAll {
213
+ // Add the "func" part of the func declaration.
214
+ result [posRange {
215
+ start : funcType .Func ,
216
+ end : funcEnd ,
217
+ }] = unit {}
218
+ } else if returnStmt == nil && ! inResults {
219
+ return // nothing to highlight
220
+ } else {
221
+ // If we're not highighting the entire return statement, we need to collect
222
+ // specific result indexes to highlight. This may be more than one index if
223
+ // the cursor is on a multi-name result field, but not in any specific name.
224
+ if ! highlightAll {
225
+ highlightIndexes = make (map [int ]bool )
226
+ if returnStmt != nil {
227
+ for i , n := range returnStmt .Results {
228
+ if inNode (n ) {
229
+ highlightIndexes [i ] = true
230
+ break
231
+ }
232
+ }
233
+ }
234
+
235
+ // Scan fields, either adding highlights according to the highlightIndexes
236
+ // computed above, or accounting for the cursor position within the result
237
+ // list.
238
+ // (We do both at once to avoid repeating the cumbersome field traversal.)
239
+ i := 0
240
+ findField:
241
+ for _ , field := range funcType .Results .List {
242
+ for j , name := range field .Names {
243
+ if inNode (name ) || highlightIndexes [i + j ] {
244
+ result [posRange {name .Pos (), name .End ()}] = unit {}
245
+ highlightIndexes [i + j ] = true
246
+ break findField // found/highlighted the specific name
247
+ }
248
+ }
249
+ // If the cursor is in a field but not in a name (e.g. in the space, or
250
+ // the type), highlight the whole field.
251
+ //
252
+ // Note that this may not be ideal if we're at e.g.
253
+ //
254
+ // (x,‸y int, z int8)
255
+ //
256
+ // ...where it would make more sense to highlight only y. But we don't
257
+ // reach this function if not in a func, return, ident, or basiclit.
258
+ if inNode (field ) || highlightIndexes [i ] {
259
+ result [posRange {field .Pos (), field .End ()}] = unit {}
260
+ highlightIndexes [i ] = true
261
+ if inNode (field ) {
262
+ for j := range field .Names {
263
+ highlightIndexes [i + j ] = true
264
+ }
265
+ }
266
+ break findField // found/highlighted the field
267
+ }
268
+
269
+ n := len (field .Names )
270
+ if n == 0 {
271
+ n = 1
272
+ }
273
+ i += n
274
+ }
219
275
}
220
- result [r ] = struct {}{}
221
276
}
222
- ast . Inspect ( enclosingFunc , func ( n ast. Node ) bool {
223
- // Don't traverse any other functions.
224
- switch n .(type ) {
277
+
278
+ ast . Inspect ( funcBody , func ( n ast. Node ) bool {
279
+ switch n := n .(type ) {
225
280
case * ast.FuncDecl , * ast.FuncLit :
226
- return enclosingFunc == n
227
- }
228
- ret , ok := n .(* ast.ReturnStmt )
229
- if ! ok {
230
- return true
231
- }
232
- var toAdd ast.Node
233
- // Add the entire return statement, applies when highlight the word "return" or "func".
234
- if highlightAllReturnsAndFunc {
235
- toAdd = n
236
- }
237
- // Add the relevant field within the entire return statement.
238
- if - 1 < index && index < len (ret .Results ) {
239
- toAdd = ret .Results [index ]
240
- }
241
- if toAdd != nil {
242
- result [posRange {start : toAdd .Pos (), end : toAdd .End ()}] = struct {}{}
281
+ // Don't traverse into any functions other than enclosingFunc.
282
+ return false
283
+ case * ast.ReturnStmt :
284
+ if highlightAll {
285
+ // Add the entire return statement.
286
+ result [posRange {n .Pos (), n .End ()}] = unit {}
287
+ } else {
288
+ // Add the highlighted indexes.
289
+ for i , expr := range n .Results {
290
+ if highlightIndexes [i ] {
291
+ result [posRange {expr .Pos (), expr .End ()}] = unit {}
292
+ }
293
+ }
294
+ }
295
+ return false
296
+
243
297
}
244
- return false
298
+ return true
245
299
})
246
300
}
247
301
0 commit comments