@@ -6,8 +6,12 @@ import (
6
6
"fmt"
7
7
"go/ast"
8
8
"go/build"
9
+ "go/format"
10
+ "go/parser"
9
11
"go/token"
10
12
"go/types"
13
+ "path/filepath"
14
+ "sort"
11
15
"strings"
12
16
13
17
doc "github.com/slimsag/godocmd"
@@ -16,6 +20,10 @@ import (
16
20
)
17
21
18
22
func (h * LangHandler ) handleHover (ctx context.Context , conn jsonrpc2.JSONRPC2 , req * jsonrpc2.Request , params lsp.TextDocumentPositionParams ) (* lsp.Hover , error ) {
23
+ if UseBinaryPkgCache {
24
+ return h .handleHoverGodef (ctx , conn , req , params )
25
+ }
26
+
19
27
if ! isFileURI (params .TextDocument .URI ) {
20
28
return nil , & jsonrpc2.Error {
21
29
Code : jsonrpc2 .CodeInvalidParams ,
@@ -245,3 +253,272 @@ func prettyPrintTypesString(s string) string {
245
253
}
246
254
return b .String ()
247
255
}
256
+
257
+ func (h * LangHandler ) handleHoverGodef (ctx context.Context , conn jsonrpc2.JSONRPC2 , req * jsonrpc2.Request , params lsp.TextDocumentPositionParams ) (* lsp.Hover , error ) {
258
+ // First perform the equivalent of a textDocument/definition request in
259
+ // order to resolve the definition position.
260
+ fset , res , _ , err := h .definitionGodef (ctx , params )
261
+ if err != nil {
262
+ return nil , err
263
+ }
264
+
265
+ // If our target is a package import statement or package selector, then we
266
+ // handle that separately now.
267
+ if res .Package != nil {
268
+ // res.Package.Name is invalid since it was imported with FindOnly, so
269
+ // import normally now.
270
+ bpkg , err := build .Default .ImportDir (res .Package .Dir , 0 )
271
+ if err != nil {
272
+ return nil , err
273
+ }
274
+
275
+ // Parse the entire dir into its respective AST packages.
276
+ pkgs , err := parser .ParseDir (fset , res .Package .Dir , nil , parser .ParseComments )
277
+ if err != nil {
278
+ return nil , err
279
+ }
280
+ pkg := pkgs [bpkg .Name ]
281
+
282
+ // Find the package doc comments.
283
+ pkgFiles := make ([]* ast.File , 0 , len (pkg .Files ))
284
+ for _ , f := range pkg .Files {
285
+ pkgFiles = append (pkgFiles , f )
286
+ }
287
+ comments := packageDoc (pkgFiles , bpkg .Name )
288
+
289
+ return & lsp.Hover {
290
+ Contents : maybeAddComments (comments , []lsp.MarkedString {{Language : "go" , Value : fmt .Sprintf ("package %s (%q)" , bpkg .Name , bpkg .ImportPath )}}),
291
+
292
+ // TODO(slimsag): I think we can add Range here, but not exactly
293
+ // sure. res.Start and res.End are only present if it's a package
294
+ // selector, not an import statement. Since Range is optional,
295
+ // we're omitting it here.
296
+ }, nil
297
+ }
298
+
299
+ loc := goRangeToLSPLocation (fset , res .Start , res .End )
300
+
301
+ if loc .URI == "file://" {
302
+ // TODO: builtins do not have valid URIs or locations.
303
+ return & lsp.Hover {}, nil
304
+ }
305
+
306
+ filename := uriToFilePath (loc .URI )
307
+
308
+ // Parse the entire dir into its respective AST packages.
309
+ pkgs , err := parser .ParseDir (fset , filepath .Dir (filename ), nil , parser .ParseComments )
310
+ if err != nil {
311
+ return nil , err
312
+ }
313
+
314
+ // Locate the AST package that contains the file we're interested in.
315
+ foundImportPath , foundPackage , err := packageForFile (pkgs , filename )
316
+ if err != nil {
317
+ return nil , err
318
+ }
319
+
320
+ // Create documentation for the package.
321
+ docPkg := doc .New (foundPackage , foundImportPath , doc .AllDecls )
322
+
323
+ // Locate the target in the docs.
324
+ target := fset .Position (res .Start )
325
+ docObject := findDocTarget (fset , target , docPkg )
326
+ if docObject == nil {
327
+ return nil , fmt .Errorf ("failed to find doc object for %s" , target )
328
+ }
329
+
330
+ contents , node := fmtDocObject (fset , docObject , target )
331
+ r := rangeForNode (fset , node )
332
+ return & lsp.Hover {
333
+ Contents : contents ,
334
+ Range : & r ,
335
+ }, nil
336
+ }
337
+
338
+ // packageForFile returns the import path and pkg from pkgs that contains the
339
+ // named file.
340
+ func packageForFile (pkgs map [string ]* ast.Package , filename string ) (string , * ast.Package , error ) {
341
+ for path , pkg := range pkgs {
342
+ for pkgFile := range pkg .Files {
343
+ if pkgFile == filename {
344
+ return path , pkg , nil
345
+ }
346
+ }
347
+ }
348
+ return "" , nil , fmt .Errorf ("failed to find %q in packages %q" , filename , pkgs )
349
+ }
350
+
351
+ // inRange tells if x is in the range of a-b inclusive.
352
+ func inRange (x , a , b token.Position ) bool {
353
+ if x .Filename != a .Filename || x .Filename != b .Filename {
354
+ return false
355
+ }
356
+ return x .Offset >= a .Offset && x .Offset <= b .Offset
357
+ }
358
+
359
+ // findDocTarget walks an input *doc.Package and locates the *doc.Value,
360
+ // *doc.Type, or *doc.Func for the given target position.
361
+ func findDocTarget (fset * token.FileSet , target token.Position , in interface {}) interface {} {
362
+ switch v := in .(type ) {
363
+ case * doc.Package :
364
+ for _ , x := range v .Consts {
365
+ if r := findDocTarget (fset , target , x ); r != nil {
366
+ return r
367
+ }
368
+ }
369
+ for _ , x := range v .Types {
370
+ if r := findDocTarget (fset , target , x ); r != nil {
371
+ return r
372
+ }
373
+ }
374
+ for _ , x := range v .Vars {
375
+ if r := findDocTarget (fset , target , x ); r != nil {
376
+ return r
377
+ }
378
+ }
379
+ for _ , x := range v .Funcs {
380
+ if r := findDocTarget (fset , target , x ); r != nil {
381
+ return r
382
+ }
383
+ }
384
+ return nil
385
+ case * doc.Value :
386
+ if inRange (target , fset .Position (v .Decl .Pos ()), fset .Position (v .Decl .End ())) {
387
+ return v
388
+ }
389
+ return nil
390
+ case * doc.Type :
391
+ if inRange (target , fset .Position (v .Decl .Pos ()), fset .Position (v .Decl .End ())) {
392
+ return v
393
+ }
394
+ return nil
395
+ case * doc.Func :
396
+ if inRange (target , fset .Position (v .Decl .Pos ()), fset .Position (v .Decl .End ())) {
397
+ return v
398
+ }
399
+ return nil
400
+ default :
401
+ panic ("unreachable" )
402
+ }
403
+ }
404
+
405
+ // fmtDocObject formats one of:
406
+ //
407
+ // *doc.Value
408
+ // *doc.Type
409
+ // *doc.Func
410
+ //
411
+ func fmtDocObject (fset * token.FileSet , x interface {}, target token.Position ) ([]lsp.MarkedString , ast.Node ) {
412
+ switch v := x .(type ) {
413
+ case * doc.Value : // Vars and Consts
414
+ // Sort the specs by distance to find the one nearest to target.
415
+ sort .Sort (byDistance {v .Decl .Specs , fset , target })
416
+ spec := v .Decl .Specs [0 ].(* ast.ValueSpec )
417
+
418
+ // Use the doc directly above the var inside a var() block, or if there
419
+ // is none, fall back to the doc directly above the var() block.
420
+ doc := spec .Doc .Text ()
421
+ if doc == "" {
422
+ doc = v .Doc
423
+ }
424
+
425
+ // Create a copy of the spec with no doc for formatting separately.
426
+ cpy := * spec
427
+ cpy .Doc = nil
428
+ value := v .Decl .Tok .String () + " " + fmtNode (fset , & cpy )
429
+ return maybeAddComments (doc , []lsp.MarkedString {{Language : "go" , Value : value }}), spec
430
+
431
+ case * doc.Type : // Type declarations
432
+ spec := v .Decl .Specs [0 ].(* ast.TypeSpec )
433
+
434
+ // Handle interfaces methods and struct fields separately now.
435
+ switch s := spec .Type .(type ) {
436
+ case * ast.InterfaceType :
437
+ // Find the method that is an exact match for our target position.
438
+ for _ , field := range s .Methods .List {
439
+ if fset .Position (field .Pos ()).Offset == target .Offset {
440
+ // An exact match.
441
+ value := fmt .Sprintf ("func (%s).%s%s" , spec .Name .Name , field .Names [0 ].Name , strings .TrimPrefix (fmtNode (fset , field .Type ), "func" ))
442
+ return maybeAddComments (field .Doc .Text (), []lsp.MarkedString {{Language : "go" , Value : value }}), field
443
+ }
444
+ }
445
+
446
+ case * ast.StructType :
447
+ // Find the field that is an exact match for our target position.
448
+ for _ , field := range s .Fields .List {
449
+ if fset .Position (field .Pos ()).Offset == target .Offset {
450
+ // An exact match.
451
+ value := fmt .Sprintf ("struct field %s %s" , field .Names [0 ], fmtNode (fset , field .Type ))
452
+ return maybeAddComments (field .Doc .Text (), []lsp.MarkedString {{Language : "go" , Value : value }}), field
453
+ }
454
+ }
455
+ }
456
+
457
+ // Formatting of all type declarations: structs, interfaces, integers, etc.
458
+ name := v .Decl .Tok .String () + " " + spec .Name .Name + " " + typeName (fset , spec .Type )
459
+ res := []lsp.MarkedString {{Language : "go" , Value : name }}
460
+
461
+ doc := spec .Doc .Text ()
462
+ if doc == "" {
463
+ doc = v .Doc
464
+ }
465
+ res = maybeAddComments (doc , res )
466
+
467
+ if n := typeName (fset , spec .Type ); n == "interface" || n == "struct" {
468
+ res = append (res , lsp.MarkedString {Language : "go" , Value : fmtNode (fset , spec .Type )})
469
+ }
470
+ return res , spec
471
+
472
+ case * doc.Func : // Functions
473
+ return maybeAddComments (v .Doc , []lsp.MarkedString {{Language : "go" , Value : fmtNode (fset , v .Decl )}}), v .Decl
474
+ default :
475
+ panic ("unreachable" )
476
+ }
477
+ }
478
+
479
+ // typeName returns the name of typ, shortening interface and struct types to
480
+ // just "interface" and "struct" rather than their full contents (incl. methods
481
+ // and fields).
482
+ func typeName (fset * token.FileSet , typ ast.Expr ) string {
483
+ switch typ .(type ) {
484
+ case * ast.InterfaceType :
485
+ return "interface"
486
+ case * ast.StructType :
487
+ return "struct"
488
+ default :
489
+ return fmtNode (fset , typ )
490
+ }
491
+ }
492
+
493
+ // fmtNode formats the given node as a string.
494
+ func fmtNode (fset * token.FileSet , n ast.Node ) string {
495
+ var buf bytes.Buffer
496
+ err := format .Node (& buf , fset , n )
497
+ if err != nil {
498
+ panic ("unreachable" )
499
+ }
500
+ return buf .String ()
501
+ }
502
+
503
+ // byDistance sorts specs by distance to the target position.
504
+ type byDistance struct {
505
+ specs []ast.Spec
506
+ fset * token.FileSet
507
+ target token.Position
508
+ }
509
+
510
+ func (b byDistance ) Len () int { return len (b .specs ) }
511
+ func (b byDistance ) Swap (i , j int ) { b .specs [i ], b .specs [j ] = b .specs [j ], b .specs [i ] }
512
+ func (b byDistance ) Less (ii , jj int ) bool {
513
+ i := b .fset .Position (b .specs [ii ].Pos ())
514
+ j := b .fset .Position (b .specs [jj ].Pos ())
515
+ return abs (b .target .Offset - i .Offset ) < abs (b .target .Offset - j .Offset )
516
+ }
517
+
518
+ // abs returns the absolute value of x.
519
+ func abs (x int ) int {
520
+ if x < 0 {
521
+ return - x
522
+ }
523
+ return x
524
+ }
0 commit comments