github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/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  	"go/token"
    11  
    12  	"golang.org/x/mod/modfile"
    13  	"github.com/powerman/golang-tools/internal/event"
    14  	"github.com/powerman/golang-tools/internal/lsp/protocol"
    15  	"github.com/powerman/golang-tools/internal/lsp/source"
    16  	errors "golang.org/x/xerrors"
    17  )
    18  
    19  func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) {
    20  	// We only provide hover information for the view's go.work file.
    21  	if fh.URI() != snapshot.WorkFile() {
    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, errors.Errorf("getting go.work file handle: %w", err)
    32  	}
    33  	spn, err := pw.Mapper.PointSpan(position)
    34  	if err != nil {
    35  		return nil, errors.Errorf("computing cursor position: %w", err)
    36  	}
    37  	hoverRng, err := spn.Range(pw.Mapper.Converter)
    38  	if err != nil {
    39  		return nil, errors.Errorf("computing hover range: %w", err)
    40  	}
    41  
    42  	// Confirm that the cursor is inside a use statement, and then find
    43  	// the position of the use statement's directory path.
    44  	use, pathStart, pathEnd := usePath(pw, hoverRng.Start)
    45  
    46  	// The cursor position is not on a use statement.
    47  	if use == nil {
    48  		return nil, nil
    49  	}
    50  
    51  	// Get the mod file denoted by the use.
    52  	modfh, err := snapshot.GetFile(ctx, modFileURI(pw, use))
    53  	if err != nil {
    54  		return nil, errors.Errorf("getting modfile handle: %w", err)
    55  	}
    56  	pm, err := snapshot.ParseMod(ctx, modfh)
    57  	if err != nil {
    58  		return nil, errors.Errorf("getting modfile handle: %w", err)
    59  	}
    60  	mod := pm.File.Module.Mod
    61  
    62  	// Get the range to highlight for the hover.
    63  	rng, err := source.ByteOffsetsToRange(pw.Mapper, fh.URI(), pathStart, pathEnd)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	options := snapshot.View().Options()
    68  	return &protocol.Hover{
    69  		Contents: protocol.MarkupContent{
    70  			Kind:  options.PreferredContentFormat,
    71  			Value: mod.Path,
    72  		},
    73  		Range: rng,
    74  	}, nil
    75  }
    76  
    77  func usePath(pw *source.ParsedWorkFile, pos token.Pos) (use *modfile.Use, pathStart, pathEnd int) {
    78  	for _, u := range pw.File.Use {
    79  		path := []byte(u.Path)
    80  		s, e := u.Syntax.Start.Byte, u.Syntax.End.Byte
    81  		i := bytes.Index(pw.Mapper.Content[s:e], path)
    82  		if i == -1 {
    83  			// This should not happen.
    84  			continue
    85  		}
    86  		// Shift the start position to the location of the
    87  		// module directory within the use statement.
    88  		pathStart, pathEnd = s+i, s+i+len(path)
    89  		if token.Pos(pathStart) <= pos && pos <= token.Pos(pathEnd) {
    90  			return u, pathStart, pathEnd
    91  		}
    92  	}
    93  	return nil, 0, 0
    94  }