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  }