1// Copyright 2019 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 5package cmd 6 7import ( 8 "context" 9 "flag" 10 "fmt" 11 "io/ioutil" 12 13 "golang.org/x/tools/internal/lsp/diff" 14 "golang.org/x/tools/internal/lsp/protocol" 15 "golang.org/x/tools/internal/lsp/source" 16 "golang.org/x/tools/internal/span" 17 "golang.org/x/tools/internal/tool" 18 errors "golang.org/x/xerrors" 19) 20 21// imports implements the import verb for gopls. 22type imports struct { 23 Diff bool `flag:"d" help:"display diffs instead of rewriting files"` 24 Write bool `flag:"w" help:"write result to (source) file instead of stdout"` 25 26 app *Application 27} 28 29func (t *imports) Name() string { return "imports" } 30func (t *imports) Usage() string { return "<filename>" } 31func (t *imports) ShortHelp() string { return "updates import statements" } 32func (t *imports) DetailedHelp(f *flag.FlagSet) { 33 fmt.Fprintf(f.Output(), ` 34Example: update imports statements in a file: 35 36 $ gopls imports -w internal/lsp/cmd/check.go 37 38gopls imports flags are: 39`) 40 f.PrintDefaults() 41} 42 43// Run performs diagnostic checks on the file specified and either; 44// - if -w is specified, updates the file in place; 45// - if -d is specified, prints out unified diffs of the changes; or 46// - otherwise, prints the new versions to stdout. 47func (t *imports) Run(ctx context.Context, args ...string) error { 48 if len(args) != 1 { 49 return tool.CommandLineErrorf("imports expects 1 argument") 50 } 51 conn, err := t.app.connect(ctx) 52 if err != nil { 53 return err 54 } 55 defer conn.terminate(ctx) 56 57 from := span.Parse(args[0]) 58 uri := from.URI() 59 file := conn.AddFile(ctx, uri) 60 if file.err != nil { 61 return file.err 62 } 63 actions, err := conn.CodeAction(ctx, &protocol.CodeActionParams{ 64 TextDocument: protocol.TextDocumentIdentifier{ 65 URI: protocol.URIFromSpanURI(uri), 66 }, 67 }) 68 if err != nil { 69 return errors.Errorf("%v: %v", from, err) 70 } 71 var edits []protocol.TextEdit 72 for _, a := range actions { 73 if a.Title != "Organize Imports" { 74 continue 75 } 76 for _, c := range a.Edit.DocumentChanges { 77 if fileURI(c.TextDocument.URI) == uri { 78 edits = append(edits, c.Edits...) 79 } 80 } 81 } 82 sedits, err := source.FromProtocolEdits(file.mapper, edits) 83 if err != nil { 84 return errors.Errorf("%v: %v", edits, err) 85 } 86 newContent := diff.ApplyEdits(string(file.mapper.Content), sedits) 87 88 filename := file.uri.Filename() 89 switch { 90 case t.Write: 91 if len(edits) > 0 { 92 ioutil.WriteFile(filename, []byte(newContent), 0644) 93 } 94 case t.Diff: 95 diffs := diff.ToUnified(filename+".orig", filename, string(file.mapper.Content), sedits) 96 fmt.Print(diffs) 97 default: 98 fmt.Print(string(newContent)) 99 } 100 return nil 101} 102