Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / internal / lsp / command.go
1 // Copyright 2020 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 lsp
6
7 import (
8         "bytes"
9         "context"
10         "encoding/json"
11         "fmt"
12         "io"
13         "io/ioutil"
14         "path/filepath"
15
16         "golang.org/x/tools/internal/event"
17         "golang.org/x/tools/internal/lsp/protocol"
18         "golang.org/x/tools/internal/lsp/source"
19         "golang.org/x/tools/internal/span"
20         "golang.org/x/tools/internal/xcontext"
21         errors "golang.org/x/xerrors"
22 )
23
24 func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
25         var command *source.Command
26         for _, c := range source.Commands {
27                 if c.ID() == params.Command {
28                         command = c
29                         break
30                 }
31         }
32         if command == nil {
33                 return nil, fmt.Errorf("no known command")
34         }
35         var match bool
36         for _, name := range s.session.Options().SupportedCommands {
37                 if command.ID() == name {
38                         match = true
39                         break
40                 }
41         }
42         if !match {
43                 return nil, fmt.Errorf("%s is not a supported command", command.ID())
44         }
45         title := command.Title
46         if title == "" {
47                 title = command.Name
48         }
49         // Some commands require that all files are saved to disk. If we detect
50         // unsaved files, warn the user instead of running the commands.
51         unsaved := false
52         for _, overlay := range s.session.Overlays() {
53                 if !overlay.Saved() {
54                         unsaved = true
55                         break
56                 }
57         }
58         if unsaved {
59                 switch params.Command {
60                 case source.CommandTest.ID(), source.CommandGenerate.ID(), source.CommandToggleDetails.ID():
61                         // TODO(PJW): for Toggle, not an error if it is being disabled
62                         err := errors.New("unsaved files in the view")
63                         s.showCommandError(ctx, title, err)
64                         return nil, err
65                 }
66         }
67         // If the command has a suggested fix function available, use it and apply
68         // the edits to the workspace.
69         if command.IsSuggestedFix() {
70                 err := s.runSuggestedFixCommand(ctx, command, params.Arguments)
71                 if err != nil {
72                         s.showCommandError(ctx, title, err)
73                 }
74                 return nil, err
75         }
76         ctx, cancel := context.WithCancel(xcontext.Detach(ctx))
77         // Start progress prior to spinning off a goroutine specifically so that
78         // clients are aware of the work item before the command completes. This
79         // matters for regtests, where having a continuous thread of work is
80         // convenient for assertions.
81         work := s.progress.start(ctx, title, "Running...", params.WorkDoneToken, cancel)
82         if command.Synchronous {
83                 return nil, s.runCommand(ctx, work, command, params.Arguments)
84         }
85         go func() {
86                 defer cancel()
87                 err := s.runCommand(ctx, work, command, params.Arguments)
88                 switch {
89                 case errors.Is(err, context.Canceled):
90                         work.end(title + ": canceled")
91                 case err != nil:
92                         event.Error(ctx, fmt.Sprintf("%s: command error", title), err)
93                         work.end(title + ": failed")
94                         // Show a message when work completes with error, because the progress end
95                         // message is typically dismissed immediately by LSP clients.
96                         s.showCommandError(ctx, title, err)
97                 default:
98                         work.end(command.ID() + ": completed")
99                 }
100         }()
101         return nil, nil
102 }
103
104 func (s *Server) runSuggestedFixCommand(ctx context.Context, command *source.Command, args []json.RawMessage) error {
105         var uri protocol.DocumentURI
106         var rng protocol.Range
107         if err := source.UnmarshalArgs(args, &uri, &rng); err != nil {
108                 return err
109         }
110         snapshot, fh, ok, release, err := s.beginFileRequest(ctx, uri, source.Go)
111         defer release()
112         if !ok {
113                 return err
114         }
115         edits, err := command.SuggestedFix(ctx, snapshot, fh, rng)
116         if err != nil {
117                 return err
118         }
119         r, err := s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
120                 Edit: protocol.WorkspaceEdit{
121                         DocumentChanges: edits,
122                 },
123         })
124         if err != nil {
125                 return err
126         }
127         if !r.Applied {
128                 return errors.New(r.FailureReason)
129         }
130         return nil
131 }
132
133 func (s *Server) showCommandError(ctx context.Context, title string, err error) {
134         // Command error messages should not be cancelable.
135         ctx = xcontext.Detach(ctx)
136         if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
137                 Type:    protocol.Error,
138                 Message: fmt.Sprintf("%s failed: %v", title, err),
139         }); err != nil {
140                 event.Error(ctx, title+": failed to show message", err)
141         }
142 }
143
144 func (s *Server) runCommand(ctx context.Context, work *workDone, command *source.Command, args []json.RawMessage) error {
145         switch command {
146         case source.CommandTest:
147                 var uri protocol.DocumentURI
148                 var tests, benchmarks []string
149                 if err := source.UnmarshalArgs(args, &uri, &tests, &benchmarks); err != nil {
150                         return err
151                 }
152                 snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
153                 defer release()
154                 if !ok {
155                         return err
156                 }
157                 return s.runTests(ctx, snapshot, uri, work, tests, benchmarks)
158         case source.CommandGenerate:
159                 var uri protocol.DocumentURI
160                 var recursive bool
161                 if err := source.UnmarshalArgs(args, &uri, &recursive); err != nil {
162                         return err
163                 }
164                 snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
165                 defer release()
166                 if !ok {
167                         return err
168                 }
169                 return s.runGoGenerate(ctx, snapshot, uri.SpanURI(), recursive, work)
170         case source.CommandRegenerateCgo:
171                 var uri protocol.DocumentURI
172                 if err := source.UnmarshalArgs(args, &uri); err != nil {
173                         return err
174                 }
175                 mod := source.FileModification{
176                         URI:    uri.SpanURI(),
177                         Action: source.InvalidateMetadata,
178                 }
179                 return s.didModifyFiles(ctx, []source.FileModification{mod}, FromRegenerateCgo)
180         case source.CommandTidy, source.CommandVendor:
181                 var uri protocol.DocumentURI
182                 if err := source.UnmarshalArgs(args, &uri); err != nil {
183                         return err
184                 }
185                 // The flow for `go mod tidy` and `go mod vendor` is almost identical,
186                 // so we combine them into one case for convenience.
187                 a := "tidy"
188                 if command == source.CommandVendor {
189                         a = "vendor"
190                 }
191                 return s.directGoModCommand(ctx, uri, "mod", []string{a}...)
192         case source.CommandUpgradeDependency:
193                 var uri protocol.DocumentURI
194                 var goCmdArgs []string
195                 if err := source.UnmarshalArgs(args, &uri, &goCmdArgs); err != nil {
196                         return err
197                 }
198                 return s.directGoModCommand(ctx, uri, "get", goCmdArgs...)
199         case source.CommandToggleDetails:
200                 var fileURI span.URI
201                 if err := source.UnmarshalArgs(args, &fileURI); err != nil {
202                         return err
203                 }
204                 pkgDir := span.URIFromPath(filepath.Dir(fileURI.Filename()))
205                 s.gcOptimizationDetailsMu.Lock()
206                 if _, ok := s.gcOptimizatonDetails[pkgDir]; ok {
207                         delete(s.gcOptimizatonDetails, pkgDir)
208                 } else {
209                         s.gcOptimizatonDetails[pkgDir] = struct{}{}
210                 }
211                 s.gcOptimizationDetailsMu.Unlock()
212                 // need to recompute diagnostics.
213                 // so find the snapshot
214                 sv, err := s.session.ViewOf(fileURI)
215                 if err != nil {
216                         return err
217                 }
218                 snapshot, release := sv.Snapshot(ctx)
219                 defer release()
220                 s.diagnoseSnapshot(snapshot, nil)
221         case source.CommandGenerateGoplsMod:
222                 var v source.View
223                 if len(args) == 0 {
224                         views := s.session.Views()
225                         if len(views) != 1 {
226                                 return fmt.Errorf("cannot resolve view: have %d views", len(views))
227                         }
228                         v = views[0]
229                 } else {
230                         var uri protocol.DocumentURI
231                         if err := source.UnmarshalArgs(args, &uri); err != nil {
232                                 return err
233                         }
234                         var err error
235                         v, err = s.session.ViewOf(uri.SpanURI())
236                         if err != nil {
237                                 return err
238                         }
239                 }
240                 snapshot, release := v.Snapshot(ctx)
241                 defer release()
242                 modFile, err := snapshot.BuildWorkspaceModFile(ctx)
243                 if err != nil {
244                         return errors.Errorf("getting workspace mod file: %w", err)
245                 }
246                 content, err := modFile.Format()
247                 if err != nil {
248                         return errors.Errorf("formatting mod file: %w", err)
249                 }
250                 filename := filepath.Join(v.Folder().Filename(), "gopls.mod")
251                 if err := ioutil.WriteFile(filename, content, 0644); err != nil {
252                         return errors.Errorf("writing mod file: %w", err)
253                 }
254         default:
255                 return fmt.Errorf("unsupported command: %s", command.ID())
256         }
257         return nil
258 }
259
260 func (s *Server) directGoModCommand(ctx context.Context, uri protocol.DocumentURI, verb string, args ...string) error {
261         view, err := s.session.ViewOf(uri.SpanURI())
262         if err != nil {
263                 return err
264         }
265         wdir := filepath.Dir(uri.SpanURI().Filename())
266         snapshot, release := view.Snapshot(ctx)
267         defer release()
268         return snapshot.RunGoCommandDirect(ctx, wdir, verb, args)
269 }
270
271 func (s *Server) runTests(ctx context.Context, snapshot source.Snapshot, uri protocol.DocumentURI, work *workDone, tests, benchmarks []string) error {
272         pkgs, err := snapshot.PackagesForFile(ctx, uri.SpanURI(), source.TypecheckWorkspace)
273         if err != nil {
274                 return err
275         }
276         if len(pkgs) == 0 {
277                 return fmt.Errorf("package could not be found for file: %s", uri.SpanURI().Filename())
278         }
279         pkgPath := pkgs[0].PkgPath()
280
281         // create output
282         buf := &bytes.Buffer{}
283         ew := &eventWriter{ctx: ctx, operation: "test"}
284         out := io.MultiWriter(ew, workDoneWriter{work}, buf)
285
286         wdir := filepath.Dir(uri.SpanURI().Filename())
287
288         // Run `go test -run Func` on each test.
289         var failedTests int
290         for _, funcName := range tests {
291                 args := []string{pkgPath, "-v", "-count=1", "-run", fmt.Sprintf("^%s$", funcName)}
292                 if err := snapshot.RunGoCommandPiped(ctx, wdir, "test", args, out, out); err != nil {
293                         if errors.Is(err, context.Canceled) {
294                                 return err
295                         }
296                         failedTests++
297                 }
298         }
299
300         // Run `go test -run=^$ -bench Func` on each test.
301         var failedBenchmarks int
302         for _, funcName := range benchmarks {
303                 args := []string{pkgPath, "-v", "-run=^$", "-bench", fmt.Sprintf("^%s$", funcName)}
304                 if err := snapshot.RunGoCommandPiped(ctx, wdir, "test", args, out, out); err != nil {
305                         if errors.Is(err, context.Canceled) {
306                                 return err
307                         }
308                         failedBenchmarks++
309                 }
310         }
311
312         var title string
313         if len(tests) > 0 && len(benchmarks) > 0 {
314                 title = "tests and benchmarks"
315         } else if len(tests) > 0 {
316                 title = "tests"
317         } else if len(benchmarks) > 0 {
318                 title = "benchmarks"
319         } else {
320                 return errors.New("No functions were provided")
321         }
322         message := fmt.Sprintf("all %s passed", title)
323         if failedTests > 0 && failedBenchmarks > 0 {
324                 message = fmt.Sprintf("%d / %d tests failed and %d / %d benchmarks failed", failedTests, len(tests), failedBenchmarks, len(benchmarks))
325         } else if failedTests > 0 {
326                 message = fmt.Sprintf("%d / %d tests failed", failedTests, len(tests))
327         } else if failedBenchmarks > 0 {
328                 message = fmt.Sprintf("%d / %d benchmarks failed", failedBenchmarks, len(benchmarks))
329         }
330         messageType := protocol.Info
331         if failedTests > 0 || failedBenchmarks > 0 {
332                 messageType = protocol.Error
333                 message += "\n" + buf.String()
334         }
335
336         return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
337                 Type:    messageType,
338                 Message: message,
339         })
340 }
341
342 func (s *Server) runGoGenerate(ctx context.Context, snapshot source.Snapshot, dir span.URI, recursive bool, work *workDone) error {
343         ctx, cancel := context.WithCancel(ctx)
344         defer cancel()
345
346         er := &eventWriter{ctx: ctx, operation: "generate"}
347         args := []string{"-x"}
348         pattern := "."
349         if recursive {
350                 pattern = "..."
351         }
352         args = append(args, pattern)
353
354         stderr := io.MultiWriter(er, workDoneWriter{work})
355
356         if err := snapshot.RunGoCommandPiped(ctx, dir.Filename(), "generate", args, er, stderr); err != nil {
357                 return err
358         }
359         return nil
360 }