golang.org/x/tools/gopls@v0.15.3/internal/work/hover.go (about) 1 // Copyright 2022 The Go Authors. 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 work 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 12 "golang.org/x/mod/modfile" 13 "golang.org/x/tools/gopls/internal/cache" 14 "golang.org/x/tools/gopls/internal/file" 15 "golang.org/x/tools/gopls/internal/protocol" 16 "golang.org/x/tools/internal/event" 17 ) 18 19 func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) { 20 // We only provide hover information for the view's go.work file. 21 if fh.URI() != snapshot.View().GoWork() { 22 return nil, nil 23 } 24 25 ctx, done := event.Start(ctx, "work.Hover") 26 defer done() 27 28 // Get the position of the cursor. 29 pw, err := snapshot.ParseWork(ctx, fh) 30 if err != nil { 31 return nil, fmt.Errorf("getting go.work file handle: %w", err) 32 } 33 offset, err := pw.Mapper.PositionOffset(position) 34 if err != nil { 35 return nil, fmt.Errorf("computing cursor offset: %w", err) 36 } 37 38 // Confirm that the cursor is inside a use statement, and then find 39 // the position of the use statement's directory path. 40 use, pathStart, pathEnd := usePath(pw, offset) 41 42 // The cursor position is not on a use statement. 43 if use == nil { 44 return nil, nil 45 } 46 47 // Get the mod file denoted by the use. 48 modfh, err := snapshot.ReadFile(ctx, modFileURI(pw, use)) 49 if err != nil { 50 return nil, fmt.Errorf("getting modfile handle: %w", err) 51 } 52 pm, err := snapshot.ParseMod(ctx, modfh) 53 if err != nil { 54 return nil, fmt.Errorf("getting modfile handle: %w", err) 55 } 56 if pm.File.Module == nil { 57 return nil, fmt.Errorf("modfile has no module declaration") 58 } 59 mod := pm.File.Module.Mod 60 61 // Get the range to highlight for the hover. 62 rng, err := pw.Mapper.OffsetRange(pathStart, pathEnd) 63 if err != nil { 64 return nil, err 65 } 66 options := snapshot.Options() 67 return &protocol.Hover{ 68 Contents: protocol.MarkupContent{ 69 Kind: options.PreferredContentFormat, 70 Value: mod.Path, 71 }, 72 Range: rng, 73 }, nil 74 } 75 76 func usePath(pw *cache.ParsedWorkFile, offset int) (use *modfile.Use, pathStart, pathEnd int) { 77 for _, u := range pw.File.Use { 78 path := []byte(u.Path) 79 s, e := u.Syntax.Start.Byte, u.Syntax.End.Byte 80 i := bytes.Index(pw.Mapper.Content[s:e], path) 81 if i == -1 { 82 // This should not happen. 83 continue 84 } 85 // Shift the start position to the location of the 86 // module directory within the use statement. 87 pathStart, pathEnd = s+i, s+i+len(path) 88 if pathStart <= offset && offset <= pathEnd { 89 return u, pathStart, pathEnd 90 } 91 } 92 return nil, 0, 0 93 }