Skip to content

Commit 4061312

Browse files
marwan-at-workstamblerre
authored andcommitted
internal/lsp: add list_known_packages and add_import commands
This CL adds two new commands that let a client request a list of importable packages relative to a Go file and then select which import a programmer would like to add to said file. Updates golang/go#43351 Change-Id: If12518874a92ed4167bdd711a92e03ee21c7b949 Reviewed-on: https://go-review.googlesource.com/c/tools/+/281412 Trust: Rebecca Stambler <[email protected]> Trust: Robert Findley <[email protected]> Run-TryBot: Rebecca Stambler <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 1e0c960 commit 4061312

File tree

18 files changed

+380
-64
lines changed

18 files changed

+380
-64
lines changed

gopls/doc/commands.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,21 @@ Args:
2121
}
2222
```
2323

24-
### ****
24+
### **asks the server to add an import path to a given Go file.**
2525
Identifier: `gopls.add_import`
2626

27-
27+
The method will call applyEdit on the client so that clients don't have
28+
to apply the edit themselves.
2829

2930
Args:
3031

3132
```
3233
{
34+
// ImportPath is the target import path that should
35+
// be added to the URI file
3336
"ImportPath": string,
37+
// URI is the file that the ImportPath should be
38+
// added to
3439
"URI": string,
3540
}
3641
```
@@ -136,10 +141,10 @@ Args:
136141
}
137142
```
138143

139-
### ****
144+
### **retrieves a list of packages**
140145
Identifier: `gopls.list_known_packages`
141146

142-
147+
that are importable from the given URI.
143148

144149
Args:
145150

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2021 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 misc
6+
7+
import (
8+
"testing"
9+
10+
"golang.org/x/tools/internal/lsp/command"
11+
"golang.org/x/tools/internal/lsp/protocol"
12+
. "golang.org/x/tools/internal/lsp/regtest"
13+
"golang.org/x/tools/internal/lsp/tests"
14+
)
15+
16+
func TestAddImport(t *testing.T) {
17+
const before = `package main
18+
19+
import "fmt"
20+
21+
func main() {
22+
fmt.Println("hello world")
23+
}
24+
`
25+
26+
const want = `package main
27+
28+
import (
29+
"bytes"
30+
"fmt"
31+
)
32+
33+
func main() {
34+
fmt.Println("hello world")
35+
}
36+
`
37+
38+
Run(t, "", func(t *testing.T, env *Env) {
39+
env.CreateBuffer("main.go", before)
40+
cmd, err := command.NewAddImportCommand("Add Import", command.AddImportArgs{
41+
URI: protocol.URIFromSpanURI(env.Sandbox.Workdir.URI("main.go").SpanURI()),
42+
ImportPath: "bytes",
43+
})
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
_, err = env.Editor.ExecuteCommand(env.Ctx, &protocol.ExecuteCommandParams{
48+
Command: "gopls.add_import",
49+
Arguments: cmd.Arguments,
50+
})
51+
if err != nil {
52+
t.Fatal(err)
53+
}
54+
got := env.Editor.BufferText("main.go")
55+
if got != want {
56+
t.Fatalf("gopls.add_import failed\n%s", tests.Diff(t, want, got))
57+
}
58+
})
59+
}

internal/lsp/cache/check.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode sour
503503
if dep == nil {
504504
return nil, snapshot.missingPkgError(pkgPath)
505505
}
506-
if !isValidImport(m.pkgPath, dep.m.pkgPath) {
506+
if !source.IsValidImport(string(m.pkgPath), string(dep.m.pkgPath)) {
507507
return nil, errors.Errorf("invalid use of internal package %s", pkgPath)
508508
}
509509
depPkg, err := dep.check(ctx, snapshot)
@@ -834,17 +834,6 @@ func resolveImportPath(importPath string, pkg *pkg, deps map[packagePath]*packag
834834
return nil
835835
}
836836

837-
func isValidImport(pkgPath, importPkgPath packagePath) bool {
838-
i := strings.LastIndex(string(importPkgPath), "/internal/")
839-
if i == -1 {
840-
return true
841-
}
842-
if isCommandLineArguments(string(pkgPath)) {
843-
return true
844-
}
845-
return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i]))
846-
}
847-
848837
// An importFunc is an implementation of the single-method
849838
// types.Importer interface based on a function value.
850839
type importerFunc func(path string) (*types.Package, error)

internal/lsp/cache/load.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interf
5555
for _, scope := range scopes {
5656
switch scope := scope.(type) {
5757
case packagePath:
58-
if isCommandLineArguments(string(scope)) {
58+
if source.IsCommandLineArguments(string(scope)) {
5959
panic("attempted to load command-line-arguments")
6060
}
6161
// The only time we pass package paths is when we're doing a

internal/lsp/cache/snapshot.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ func (s *snapshot) addID(uri span.URI, id packageID) {
995995
}
996996
// If the package previously only had a command-line-arguments ID,
997997
// we should just replace it.
998-
if isCommandLineArguments(string(existingID)) {
998+
if source.IsCommandLineArguments(string(existingID)) {
999999
s.ids[uri][i] = id
10001000
// Delete command-line-arguments if it was a workspace package.
10011001
delete(s.workspacePackages, existingID)
@@ -1012,14 +1012,6 @@ func (s *snapshot) addID(uri span.URI, id packageID) {
10121012
s.ids[uri] = append(newIDs, id)
10131013
}
10141014

1015-
// isCommandLineArguments reports whether a given value denotes
1016-
// "command-line-arguments" package, which is a package with an unknown ID
1017-
// created by the go command. It can have a test variant, which is why callers
1018-
// should not check that a value equals "command-line-arguments" directly.
1019-
func isCommandLineArguments(s string) bool {
1020-
return strings.Contains(s, "command-line-arguments")
1021-
}
1022-
10231015
func (s *snapshot) isWorkspacePackage(id packageID) bool {
10241016
s.mu.Lock()
10251017
defer s.mu.Unlock()
@@ -1169,7 +1161,7 @@ func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Pack
11691161

11701162
func containsCommandLineArguments(pkgs []source.Package) bool {
11711163
for _, pkg := range pkgs {
1172-
if isCommandLineArguments(pkg.ID()) {
1164+
if source.IsCommandLineArguments(pkg.ID()) {
11731165
return true
11741166
}
11751167
}
@@ -1237,7 +1229,7 @@ func (s *snapshot) reloadWorkspace(ctx context.Context) error {
12371229
missingMetadata = true
12381230

12391231
// Don't try to reload "command-line-arguments" directly.
1240-
if isCommandLineArguments(string(pkgPath)) {
1232+
if source.IsCommandLineArguments(string(pkgPath)) {
12411233
continue
12421234
}
12431235
pkgPathSet[pkgPath] = struct{}{}
@@ -1646,7 +1638,7 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
16461638
// go command when the user is outside of GOPATH and outside of a
16471639
// module. Do not cache them as workspace packages for longer than
16481640
// necessary.
1649-
if isCommandLineArguments(string(id)) {
1641+
if source.IsCommandLineArguments(string(id)) {
16501642
if invalidateMetadata, ok := idsToInvalidate[id]; invalidateMetadata && ok {
16511643
continue
16521644
}

internal/lsp/cmd/test/cmdtest.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span
100100
//TODO: function extraction not supported on command line
101101
}
102102

103+
func (r *runner) AddImport(t *testing.T, uri span.URI, expectedImport string) {
104+
//TODO: import addition not supported on command line
105+
}
106+
103107
func (r *runner) runGoplsCmd(t testing.TB, args ...string) (string, string) {
104108
rStdout, wStdout, err := os.Pipe()
105109
if err != nil {

internal/lsp/command.go

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type commandHandler struct {
5454

5555
// commandConfig configures common command set-up and execution.
5656
type commandConfig struct {
57-
async bool // whether to run the command asynchronously. Async commands cannot return results.
57+
async bool // whether to run the command asynchronously. Async commands can only return errors.
5858
requireSave bool // whether all files must be saved for the command to work
5959
progress string // title to use for progress reporting. If empty, no progress will be reported.
6060
forURI protocol.DocumentURI // URI to resolve to a snapshot. If unset, snapshot will be nil.
@@ -108,7 +108,16 @@ func (c *commandHandler) run(ctx context.Context, cfg commandConfig, run command
108108
return err
109109
}
110110
if cfg.async {
111-
go runcmd()
111+
go func() {
112+
if err := runcmd(); err != nil {
113+
if showMessageErr := c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
114+
Type: protocol.Error,
115+
Message: err.Error(),
116+
}); showMessageErr != nil {
117+
event.Error(ctx, fmt.Sprintf("failed to show message: %q", err.Error()), showMessageErr)
118+
}
119+
}
120+
}()
112121
return nil
113122
}
114123
return runcmd()
@@ -335,15 +344,8 @@ func (c *commandHandler) RunTests(ctx context.Context, args command.RunTestsArgs
335344
forURI: args.URI,
336345
}, func(ctx context.Context, deps commandDeps) error {
337346
if err := c.runTests(ctx, deps.snapshot, deps.work, args.URI, args.Tests, args.Benchmarks); err != nil {
338-
if err := c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
339-
Type: protocol.Error,
340-
Message: fmt.Sprintf("Running tests failed: %v", err),
341-
}); err != nil {
342-
event.Error(ctx, "running tests: failed to show message", err)
343-
}
347+
return errors.Errorf("running tests failed: %w", err)
344348
}
345-
// Since we're running asynchronously, any error returned here would be
346-
// ignored.
347349
return nil
348350
})
349351
}
@@ -664,26 +666,33 @@ func (c *commandHandler) GenerateGoplsMod(ctx context.Context, args command.URIA
664666
func (c *commandHandler) ListKnownPackages(ctx context.Context, args command.URIArg) (command.ListKnownPackagesResult, error) {
665667
var result command.ListKnownPackagesResult
666668
err := c.run(ctx, commandConfig{
667-
progress: "Listing packages", // optional, causes a progress report during command execution
668-
forURI: args.URI, // optional, populates deps.snapshot and deps.fh
669+
progress: "Listing packages",
670+
forURI: args.URI,
669671
}, func(ctx context.Context, deps commandDeps) error {
670-
// Marwan: add implementation here. deps.snapshot and deps.fh are available for use.
671-
result.Packages = []string{}
672-
return nil
672+
var err error
673+
result.Packages, err = source.KnownPackages(ctx, deps.snapshot, deps.fh)
674+
return err
673675
})
674676
return result, err
675677
}
676-
677-
func (c *commandHandler) AddImport(ctx context.Context, args command.AddImportArgs) (command.AddImportResult, error) {
678-
var result command.AddImportResult
679-
err := c.run(ctx, commandConfig{
678+
func (c *commandHandler) AddImport(ctx context.Context, args command.AddImportArgs) error {
679+
return c.run(ctx, commandConfig{
680680
progress: "Adding import",
681681
forURI: args.URI,
682682
}, func(ctx context.Context, deps commandDeps) error {
683-
result.Edits = nil
683+
edits, err := source.AddImport(ctx, deps.snapshot, deps.fh, args.ImportPath)
684+
if err != nil {
685+
return fmt.Errorf("could not add import: %v", err)
686+
}
687+
if _, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
688+
Edit: protocol.WorkspaceEdit{
689+
DocumentChanges: documentChanges(deps.fh, edits),
690+
},
691+
}); err != nil {
692+
return fmt.Errorf("could not apply import edits: %v", err)
693+
}
684694
return nil
685695
})
686-
return result, err
687696
}
688697

689698
func (c *commandHandler) WorkspaceMetadata(ctx context.Context) (command.WorkspaceMetadataResult, error) {

internal/lsp/command/command_gen.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/lsp/command/interface.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,14 @@ type Interface interface {
115115
// (Re)generate the gopls.mod file for a workspace.
116116
GenerateGoplsMod(context.Context, URIArg) error
117117

118+
// ListKnownPackages: retrieves a list of packages
119+
// that are importable from the given URI.
118120
ListKnownPackages(context.Context, URIArg) (ListKnownPackagesResult, error)
119121

120-
AddImport(context.Context, AddImportArgs) (AddImportResult, error)
122+
// AddImport: asks the server to add an import path to a given Go file.
123+
// The method will call applyEdit on the client so that clients don't have
124+
// to apply the edit themselves.
125+
AddImport(context.Context, AddImportArgs) error
121126

122127
WorkspaceMetadata(context.Context) (WorkspaceMetadataResult, error)
123128

@@ -196,18 +201,21 @@ type GoGetPackageArgs struct {
196201
AddRequire bool
197202
}
198203

199-
// TODO (Marwan): document :)
200-
201204
type AddImportArgs struct {
205+
// ImportPath is the target import path that should
206+
// be added to the URI file
202207
ImportPath string
203-
URI protocol.DocumentURI
204-
}
205-
206-
type AddImportResult struct {
207-
Edits []protocol.TextDocumentEdit
208+
// URI is the file that the ImportPath should be
209+
// added to
210+
URI protocol.DocumentURI
208211
}
209212

210213
type ListKnownPackagesResult struct {
214+
// Packages is a list of packages relative
215+
// to the URIArg passed by the command request.
216+
// In other words, it omits paths that are already
217+
// imported or cannot be imported due to compiler
218+
// restrictions.
211219
Packages []string
212220
}
213221

0 commit comments

Comments
 (0)