mirror of https://github.com/golang/go.git
internal/lsp: add hover for go.work use statements
Show the module path of the module being used. For golang/go#50930 Change-Id: I90a67e12182ffb457876b1fbc95aeb56ff632878 Reviewed-on: https://go-review.googlesource.com/c/tools/+/389301 Trust: Michael Matloob <matloob@golang.org> Run-TryBot: Michael Matloob <matloob@golang.org> Reviewed-by: Robert Findley <rfindley@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
121d1e448f
commit
e562276505
|
|
@ -829,6 +829,42 @@ replace
|
|||
})
|
||||
}
|
||||
|
||||
func TestUseGoWorkHover(t *testing.T) {
|
||||
const files = `
|
||||
-- go.work --
|
||||
go 1.18
|
||||
|
||||
use ./foo
|
||||
use (
|
||||
./bar
|
||||
./bar/baz
|
||||
)
|
||||
-- foo/go.mod --
|
||||
module example.com/foo
|
||||
-- bar/go.mod --
|
||||
module example.com/bar
|
||||
-- bar/baz/go.mod --
|
||||
module example.com/bar/baz
|
||||
`
|
||||
Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("go.work")
|
||||
|
||||
tcs := map[string]string{
|
||||
`\./foo`: "example.com/foo",
|
||||
`(?m)\./bar$`: "example.com/bar",
|
||||
`\./bar/baz`: "example.com/bar/baz",
|
||||
}
|
||||
|
||||
for hoverRE, want := range tcs {
|
||||
pos := env.RegexpSearch("go.work", hoverRE)
|
||||
got, _ := env.Hover("go.work", pos)
|
||||
if got.Value != want {
|
||||
t.Errorf(`hover on %q: got %q, want %q`, hoverRE, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNonWorkspaceFileCreation(t *testing.T) {
|
||||
testenv.NeedsGo1Point(t, 13)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/lsp/template"
|
||||
"golang.org/x/tools/internal/lsp/work"
|
||||
)
|
||||
|
||||
func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
|
||||
|
|
@ -26,6 +27,8 @@ func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*prot
|
|||
return source.Hover(ctx, snapshot, fh, params.Position)
|
||||
case source.Tmpl:
|
||||
return template.Hover(ctx, snapshot, fh, params.Position)
|
||||
case source.Work:
|
||||
return work.Hover(ctx, snapshot, fh, params.Position)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import (
|
|||
"golang.org/x/tools/internal/event"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
|
|
@ -85,20 +84,10 @@ func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle,
|
|||
}
|
||||
|
||||
// Get the range to highlight for the hover.
|
||||
line, col, err := pm.Mapper.Converter.ToPosition(startPos)
|
||||
rng, err := source.ByteOffsetsToRange(pm.Mapper, fh.URI(), startPos, endPos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
start := span.NewPoint(line, col, startPos)
|
||||
|
||||
line, col, err = pm.Mapper.Converter.ToPosition(endPos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
end := span.NewPoint(line, col, endPos)
|
||||
|
||||
spn = span.New(fh.URI(), start, end)
|
||||
rng, err := pm.Mapper.Range(spn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -567,15 +567,20 @@ func InRange(tok *token.File, pos token.Pos) bool {
|
|||
|
||||
// LineToRange creates a Range spanning start and end.
|
||||
func LineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) {
|
||||
line, col, err := m.Converter.ToPosition(start.Byte)
|
||||
return ByteOffsetsToRange(m, uri, start.Byte, end.Byte)
|
||||
}
|
||||
|
||||
// ByteOffsetsToRange creates a range spanning start and end.
|
||||
func ByteOffsetsToRange(m *protocol.ColumnMapper, uri span.URI, start, end int) (protocol.Range, error) {
|
||||
line, col, err := m.Converter.ToPosition(start)
|
||||
if err != nil {
|
||||
return protocol.Range{}, err
|
||||
}
|
||||
s := span.NewPoint(line, col, start.Byte)
|
||||
line, col, err = m.Converter.ToPosition(end.Byte)
|
||||
s := span.NewPoint(line, col, start)
|
||||
line, col, err = m.Converter.ToPosition(end)
|
||||
if err != nil {
|
||||
return protocol.Range{}, err
|
||||
}
|
||||
e := span.NewPoint(line, col, end.Byte)
|
||||
e := span.NewPoint(line, col, end)
|
||||
return m.Range(span.New(uri, s, e))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
"golang.org/x/tools/internal/event"
|
||||
"golang.org/x/tools/internal/lsp/debug/tag"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
|
|
@ -57,19 +58,13 @@ func DiagnosticsForWork(ctx context.Context, snapshot source.Snapshot, fh source
|
|||
|
||||
// Add diagnostic if a directory does not contain a module.
|
||||
var diagnostics []*source.Diagnostic
|
||||
workdir := filepath.Dir(pw.URI.Filename())
|
||||
for _, use := range pw.File.Use {
|
||||
modroot := filepath.FromSlash(use.Path)
|
||||
if !filepath.IsAbs(modroot) {
|
||||
modroot = filepath.Join(workdir, modroot)
|
||||
}
|
||||
|
||||
rng, err := source.LineToRange(pw.Mapper, fh.URI(), use.Syntax.Start, use.Syntax.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modfh, err := snapshot.GetFile(ctx, span.URIFromPath(filepath.Join(modroot, "go.mod")))
|
||||
modfh, err := snapshot.GetFile(ctx, modFileURI(pw, use))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -85,3 +80,14 @@ func DiagnosticsForWork(ctx context.Context, snapshot source.Snapshot, fh source
|
|||
}
|
||||
return diagnostics, nil
|
||||
}
|
||||
|
||||
func modFileURI(pw *source.ParsedWorkFile, use *modfile.Use) span.URI {
|
||||
workdir := filepath.Dir(pw.URI.Filename())
|
||||
|
||||
modroot := filepath.FromSlash(use.Path)
|
||||
if !filepath.IsAbs(modroot) {
|
||||
modroot = filepath.Join(workdir, modroot)
|
||||
}
|
||||
|
||||
return span.URIFromPath(filepath.Join(modroot, "go.mod"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package work
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
"golang.org/x/tools/internal/event"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) {
|
||||
// We only provide hover information for the view's go.work file.
|
||||
if fh.URI() != snapshot.WorkFile() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ctx, done := event.Start(ctx, "work.Hover")
|
||||
defer done()
|
||||
|
||||
// Get the position of the cursor.
|
||||
pw, err := snapshot.ParseWork(ctx, fh)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("getting go.work file handle: %w", err)
|
||||
}
|
||||
spn, err := pw.Mapper.PointSpan(position)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("computing cursor position: %w", err)
|
||||
}
|
||||
hoverRng, err := spn.Range(pw.Mapper.Converter)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("computing hover range: %w", err)
|
||||
}
|
||||
|
||||
// Confirm that the cursor is inside a use statement, and then find
|
||||
// the position of the use statement's directory path.
|
||||
var use *modfile.Use
|
||||
var pathStart, pathEnd int
|
||||
for _, u := range pw.File.Use {
|
||||
dep := []byte(u.Path)
|
||||
s, e := u.Syntax.Start.Byte, u.Syntax.End.Byte
|
||||
i := bytes.Index(pw.Mapper.Content[s:e], dep)
|
||||
if i == -1 {
|
||||
// This should not happen.
|
||||
continue
|
||||
}
|
||||
// Shift the start position to the location of the
|
||||
// module directory within the use statement.
|
||||
pathStart, pathEnd = s+i, s+i+len(dep)
|
||||
if token.Pos(pathStart) <= hoverRng.Start && hoverRng.Start <= token.Pos(pathEnd) {
|
||||
use = u
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The cursor position is not on a use statement.
|
||||
if use == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get the mod file denoted by the use.
|
||||
modfh, err := snapshot.GetFile(ctx, modFileURI(pw, use))
|
||||
pm, err := snapshot.ParseMod(ctx, modfh)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("getting modfile handle: %w", err)
|
||||
}
|
||||
mod := pm.File.Module.Mod
|
||||
|
||||
// Get the range to highlight for the hover.
|
||||
rng, err := source.ByteOffsetsToRange(pw.Mapper, fh.URI(), pathStart, pathEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options := snapshot.View().Options()
|
||||
return &protocol.Hover{
|
||||
Contents: protocol.MarkupContent{
|
||||
Kind: options.PreferredContentFormat,
|
||||
Value: mod.Path,
|
||||
},
|
||||
Range: rng,
|
||||
}, nil
|
||||
}
|
||||
Loading…
Reference in New Issue