blob: dd3603269d15259c952646b92342abef1e9c1b2c [file] [log] [blame]
Robert Findleyb15dac22022-08-30 14:40:12 -04001// 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 lsp
6
7import (
Robert Findleyb15dac22022-08-30 14:40:12 -04008 "context"
9 "encoding/json"
10 "fmt"
11 "log"
12 "os"
13 "path"
14 "path/filepath"
Robert Findley89b43352022-10-07 13:43:27 -040015 "sort"
16 "strings"
Robert Findleyb15dac22022-08-30 14:40:12 -040017 "sync"
18
Robert Findleyb15dac22022-08-30 14:40:12 -040019 "golang.org/x/tools/gopls/internal/lsp/debug"
20 "golang.org/x/tools/gopls/internal/lsp/protocol"
21 "golang.org/x/tools/gopls/internal/lsp/source"
Alan Donovan26a95e62022-10-07 10:40:32 -040022 "golang.org/x/tools/gopls/internal/span"
Peter Weinberger9250e222022-08-16 11:12:59 -040023 "golang.org/x/tools/internal/bug"
24 "golang.org/x/tools/internal/event"
25 "golang.org/x/tools/internal/jsonrpc2"
Robert Findleyb15dac22022-08-30 14:40:12 -040026)
27
28func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
Robert Findleyf7963612023-03-28 21:34:10 -040029 ctx, done := event.Start(ctx, "lsp.Server.initialize")
30 defer done()
31
Robert Findleyb15dac22022-08-30 14:40:12 -040032 s.stateMu.Lock()
33 if s.state >= serverInitializing {
34 defer s.stateMu.Unlock()
35 return nil, fmt.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
36 }
37 s.state = serverInitializing
38 s.stateMu.Unlock()
39
40 // For uniqueness, use the gopls PID rather than params.ProcessID (the client
41 // pid). Some clients might start multiple gopls servers, though they
42 // probably shouldn't.
43 pid := os.Getpid()
44 s.tempDir = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.%s", pid, s.session.ID()))
45 err := os.Mkdir(s.tempDir, 0700)
46 if err != nil {
47 // MkdirTemp could fail due to permissions issues. This is a problem with
48 // the user's environment, but should not block gopls otherwise behaving.
49 // All usage of s.tempDir should be predicated on having a non-empty
50 // s.tempDir.
51 event.Error(ctx, "creating temp dir", err)
52 s.tempDir = ""
53 }
54 s.progress.SetSupportsWorkDoneProgress(params.Capabilities.Window.WorkDoneProgress)
55
56 options := s.session.Options()
57 defer func() { s.session.SetOptions(options) }()
58
59 if err := s.handleOptionResults(ctx, source.SetOptions(options, params.InitializationOptions)); err != nil {
60 return nil, err
61 }
62 options.ForClientCapabilities(params.Capabilities)
63
64 if options.ShowBugReports {
65 // Report the next bug that occurs on the server.
66 bugCh := bug.Notify()
67 go func() {
68 b := <-bugCh
69 msg := &protocol.ShowMessageParams{
70 Type: protocol.Error,
71 Message: fmt.Sprintf("A bug occurred on the server: %s\nLocation:%s", b.Description, b.Key),
72 }
73 if err := s.eventuallyShowMessage(context.Background(), msg); err != nil {
74 log.Printf("error showing bug: %v", err)
75 }
76 }()
77 }
78
79 folders := params.WorkspaceFolders
80 if len(folders) == 0 {
81 if params.RootURI != "" {
82 folders = []protocol.WorkspaceFolder{{
83 URI: string(params.RootURI),
84 Name: path.Base(params.RootURI.SpanURI().Filename()),
85 }}
86 }
87 }
88 for _, folder := range folders {
89 uri := span.URIFromURI(folder.URI)
90 if !uri.IsFile() {
91 continue
92 }
93 s.pendingFolders = append(s.pendingFolders, folder)
94 }
95 // gopls only supports URIs with a file:// scheme, so if we have no
96 // workspace folders with a supported scheme, fail to initialize.
97 if len(folders) > 0 && len(s.pendingFolders) == 0 {
98 return nil, fmt.Errorf("unsupported URI schemes: %v (gopls only supports file URIs)", folders)
99 }
100
101 var codeActionProvider interface{} = true
102 if ca := params.Capabilities.TextDocument.CodeAction; len(ca.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 {
103 // If the client has specified CodeActionLiteralSupport,
104 // send the code actions we support.
105 //
106 // Using CodeActionOptions is only valid if codeActionLiteralSupport is set.
107 codeActionProvider = &protocol.CodeActionOptions{
108 CodeActionKinds: s.getSupportedCodeActions(),
109 }
110 }
111 var renameOpts interface{} = true
Peter Weinberger49420522023-03-01 14:46:20 -0500112 if r := params.Capabilities.TextDocument.Rename; r != nil && r.PrepareSupport {
Robert Findleyb15dac22022-08-30 14:40:12 -0400113 renameOpts = protocol.RenameOptions{
114 PrepareProvider: r.PrepareSupport,
115 }
116 }
117
118 versionInfo := debug.VersionInfo()
119
120 // golang/go#45732: Warn users who've installed sergi/go-diff@v1.2.0, since
121 // it will corrupt the formatting of their files.
122 for _, dep := range versionInfo.Deps {
123 if dep.Path == "github.com/sergi/go-diff" && dep.Version == "v1.2.0" {
124 if err := s.eventuallyShowMessage(ctx, &protocol.ShowMessageParams{
125 Message: `It looks like you have a bad gopls installation.
126Please reinstall gopls by running 'GO111MODULE=on go install golang.org/x/tools/gopls@latest'.
127See https://github.com/golang/go/issues/45732 for more information.`,
128 Type: protocol.Error,
129 }); err != nil {
130 return nil, err
131 }
132 }
133 }
134
135 goplsVersion, err := json.Marshal(versionInfo)
136 if err != nil {
137 return nil, err
138 }
139
140 return &protocol.InitializeResult{
141 Capabilities: protocol.ServerCapabilities{
Peter Weinbergere85b5332023-02-18 15:25:18 -0500142 CallHierarchyProvider: &protocol.Or_ServerCapabilities_callHierarchyProvider{Value: true},
Robert Findleyb15dac22022-08-30 14:40:12 -0400143 CodeActionProvider: codeActionProvider,
Suzy Mueller15782442022-09-19 14:13:01 -0400144 CodeLensProvider: &protocol.CodeLensOptions{}, // must be non-nil to enable the code lens capability
Peter Weinberger49420522023-03-01 14:46:20 -0500145 CompletionProvider: &protocol.CompletionOptions{
Robert Findleyb15dac22022-08-30 14:40:12 -0400146 TriggerCharacters: []string{"."},
147 },
Peter Weinbergere85b5332023-02-18 15:25:18 -0500148 DefinitionProvider: &protocol.Or_ServerCapabilities_definitionProvider{Value: true},
149 TypeDefinitionProvider: &protocol.Or_ServerCapabilities_typeDefinitionProvider{Value: true},
150 ImplementationProvider: &protocol.Or_ServerCapabilities_implementationProvider{Value: true},
151 DocumentFormattingProvider: &protocol.Or_ServerCapabilities_documentFormattingProvider{Value: true},
152 DocumentSymbolProvider: &protocol.Or_ServerCapabilities_documentSymbolProvider{Value: true},
153 WorkspaceSymbolProvider: &protocol.Or_ServerCapabilities_workspaceSymbolProvider{Value: true},
Peter Weinberger49420522023-03-01 14:46:20 -0500154 ExecuteCommandProvider: &protocol.ExecuteCommandOptions{
Peter Weinberger64920582023-04-08 18:42:05 -0400155 Commands: nonNilSliceString(options.SupportedCommands),
Robert Findleyb15dac22022-08-30 14:40:12 -0400156 },
Peter Weinbergere85b5332023-02-18 15:25:18 -0500157 FoldingRangeProvider: &protocol.Or_ServerCapabilities_foldingRangeProvider{Value: true},
158 HoverProvider: &protocol.Or_ServerCapabilities_hoverProvider{Value: true},
159 DocumentHighlightProvider: &protocol.Or_ServerCapabilities_documentHighlightProvider{Value: true},
Peter Weinberger49420522023-03-01 14:46:20 -0500160 DocumentLinkProvider: &protocol.DocumentLinkOptions{},
Robert Findleyb15dac22022-08-30 14:40:12 -0400161 InlayHintProvider: protocol.InlayHintOptions{},
Peter Weinbergere85b5332023-02-18 15:25:18 -0500162 ReferencesProvider: &protocol.Or_ServerCapabilities_referencesProvider{Value: true},
Robert Findleyb15dac22022-08-30 14:40:12 -0400163 RenameProvider: renameOpts,
Peter Weinbergere85b5332023-02-18 15:25:18 -0500164 SelectionRangeProvider: &protocol.Or_ServerCapabilities_selectionRangeProvider{Value: true},
Peter Weinbergerb5d65e02023-01-14 15:58:41 -0500165 SemanticTokensProvider: protocol.SemanticTokensOptions{
Peter Weinbergere85b5332023-02-18 15:25:18 -0500166 Range: &protocol.Or_SemanticTokensOptions_range{Value: true},
167 Full: &protocol.Or_SemanticTokensOptions_full{Value: true},
Peter Weinbergerb5d65e02023-01-14 15:58:41 -0500168 Legend: protocol.SemanticTokensLegend{
Peter Weinberger64920582023-04-08 18:42:05 -0400169 TokenTypes: nonNilSliceString(s.session.Options().SemanticTypes),
170 TokenModifiers: nonNilSliceString(s.session.Options().SemanticMods),
Peter Weinbergerb5d65e02023-01-14 15:58:41 -0500171 },
172 },
Peter Weinberger49420522023-03-01 14:46:20 -0500173 SignatureHelpProvider: &protocol.SignatureHelpOptions{
Robert Findleyb15dac22022-08-30 14:40:12 -0400174 TriggerCharacters: []string{"(", ","},
175 },
176 TextDocumentSync: &protocol.TextDocumentSyncOptions{
177 Change: protocol.Incremental,
178 OpenClose: true,
Peter Weinberger49420522023-03-01 14:46:20 -0500179 Save: &protocol.SaveOptions{
Robert Findleyb15dac22022-08-30 14:40:12 -0400180 IncludeText: false,
181 },
182 },
Peter Weinberger49420522023-03-01 14:46:20 -0500183 Workspace: &protocol.Workspace6Gn{
184 WorkspaceFolders: &protocol.WorkspaceFolders5Gn{
Robert Findleyb15dac22022-08-30 14:40:12 -0400185 Supported: true,
186 ChangeNotifications: "workspace/didChangeWorkspaceFolders",
187 },
188 },
189 },
Peter Weinberger49420522023-03-01 14:46:20 -0500190 ServerInfo: &protocol.PServerInfoMsg_initialize{
Robert Findleyb15dac22022-08-30 14:40:12 -0400191 Name: "gopls",
192 Version: string(goplsVersion),
193 },
194 }, nil
195}
196
197func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
Robert Findleyf7963612023-03-28 21:34:10 -0400198 ctx, done := event.Start(ctx, "lsp.Server.initialized")
199 defer done()
200
Robert Findleyb15dac22022-08-30 14:40:12 -0400201 s.stateMu.Lock()
202 if s.state >= serverInitialized {
203 defer s.stateMu.Unlock()
204 return fmt.Errorf("%w: initialized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
205 }
206 s.state = serverInitialized
207 s.stateMu.Unlock()
208
209 for _, not := range s.notifications {
210 s.client.ShowMessage(ctx, not)
211 }
212 s.notifications = nil
213
214 options := s.session.Options()
215 defer func() { s.session.SetOptions(options) }()
216
217 if err := s.addFolders(ctx, s.pendingFolders); err != nil {
218 return err
219 }
220 s.pendingFolders = nil
Robert Findley20c1ee72022-09-28 13:10:58 -0400221 s.checkViewGoVersions()
Robert Findleyb15dac22022-08-30 14:40:12 -0400222
223 var registrations []protocol.Registration
224 if options.ConfigurationSupported && options.DynamicConfigurationSupported {
225 registrations = append(registrations, protocol.Registration{
226 ID: "workspace/didChangeConfiguration",
227 Method: "workspace/didChangeConfiguration",
228 })
229 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400230 if len(registrations) > 0 {
231 if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
232 Registrations: registrations,
233 }); err != nil {
234 return err
235 }
236 }
237 return nil
238}
239
Robert Findley42cb7be2022-10-26 18:57:08 -0400240// GoVersionTable maps Go versions to the gopls version in which support will
241// be deprecated, and the final gopls version supporting them without warnings.
242// Keep this in sync with gopls/README.md
Robert Findley20c1ee72022-09-28 13:10:58 -0400243//
Robert Findley42cb7be2022-10-26 18:57:08 -0400244// Must be sorted in ascending order of Go version.
245//
246// Mutable for testing.
247var GoVersionTable = []GoVersionSupport{
248 {12, "", "v0.7.5"},
249 {15, "v0.11.0", "v0.9.5"},
250}
251
252// GoVersionSupport holds information about end-of-life Go version support.
253type GoVersionSupport struct {
254 GoVersion int
255 DeprecatedVersion string // if unset, the version is already deprecated
256 InstallGoplsVersion string
257}
258
259// OldestSupportedGoVersion is the last X in Go 1.X that this version of gopls
260// supports.
261func OldestSupportedGoVersion() int {
262 return GoVersionTable[len(GoVersionTable)-1].GoVersion + 1
263}
264
Robert Findleye074ef82022-10-28 09:11:20 -0400265// versionMessage returns the warning/error message to display if the user is
266// on the given Go version, if any. The goVersion variable is the X in Go 1.X.
267//
268// If goVersion is invalid (< 0), it returns "", 0.
269func versionMessage(goVersion int) (string, protocol.MessageType) {
270 if goVersion < 0 {
271 return "", 0
272 }
273
Robert Findley42cb7be2022-10-26 18:57:08 -0400274 for _, v := range GoVersionTable {
Robert Findleye074ef82022-10-28 09:11:20 -0400275 if goVersion <= v.GoVersion {
Robert Findley42cb7be2022-10-26 18:57:08 -0400276 var msgBuilder strings.Builder
277
278 mType := protocol.Error
Robert Findleye074ef82022-10-28 09:11:20 -0400279 fmt.Fprintf(&msgBuilder, "Found Go version 1.%d", goVersion)
Robert Findley42cb7be2022-10-26 18:57:08 -0400280 if v.DeprecatedVersion != "" {
281 // not deprecated yet, just a warning
282 fmt.Fprintf(&msgBuilder, ", which will be unsupported by gopls %s. ", v.DeprecatedVersion)
283 mType = protocol.Warning
284 } else {
285 fmt.Fprint(&msgBuilder, ", which is not supported by this version of gopls. ")
286 }
287 fmt.Fprintf(&msgBuilder, "Please upgrade to Go 1.%d or later and reinstall gopls. ", OldestSupportedGoVersion())
288 fmt.Fprintf(&msgBuilder, "If you can't upgrade and want this message to go away, please install gopls %s. ", v.InstallGoplsVersion)
289 fmt.Fprint(&msgBuilder, "See https://go.dev/s/gopls-support-policy for more details.")
290
291 return msgBuilder.String(), mType
292 }
293 }
294 return "", 0
295}
Robert Findley20c1ee72022-09-28 13:10:58 -0400296
297// checkViewGoVersions checks whether any Go version used by a view is too old,
298// raising a showMessage notification if so.
299//
300// It should be called after views change.
301func (s *Server) checkViewGoVersions() {
302 oldestVersion := -1
303 for _, view := range s.session.Views() {
304 viewVersion := view.GoVersion()
305 if oldestVersion == -1 || viewVersion < oldestVersion {
306 oldestVersion = viewVersion
307 }
308 }
309
Robert Findley42cb7be2022-10-26 18:57:08 -0400310 if msg, mType := versionMessage(oldestVersion); msg != "" {
Robert Findley20c1ee72022-09-28 13:10:58 -0400311 s.eventuallyShowMessage(context.Background(), &protocol.ShowMessageParams{
Robert Findley42cb7be2022-10-26 18:57:08 -0400312 Type: mType,
Robert Findley20c1ee72022-09-28 13:10:58 -0400313 Message: msg,
314 })
315 }
316}
317
Robert Findleyb15dac22022-08-30 14:40:12 -0400318func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) error {
319 originalViews := len(s.session.Views())
320 viewErrors := make(map[span.URI]error)
321
Alan Donovan32e1cb72022-10-27 12:24:51 -0400322 var ndiagnose sync.WaitGroup // number of unfinished diagnose calls
Robert Findleyb15dac22022-08-30 14:40:12 -0400323 if s.session.Options().VerboseWorkDoneProgress {
324 work := s.progress.Start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil)
325 defer func() {
326 go func() {
Alan Donovan32e1cb72022-10-27 12:24:51 -0400327 ndiagnose.Wait()
Robert Findleyb15dac22022-08-30 14:40:12 -0400328 work.End(ctx, "Done.")
329 }()
330 }()
331 }
332 // Only one view gets to have a workspace.
Alan Donovan32e1cb72022-10-27 12:24:51 -0400333 var nsnapshots sync.WaitGroup // number of unfinished snapshot initializations
Robert Findleyb15dac22022-08-30 14:40:12 -0400334 for _, folder := range folders {
335 uri := span.URIFromURI(folder.URI)
336 // Ignore non-file URIs.
337 if !uri.IsFile() {
338 continue
339 }
340 work := s.progress.Start(ctx, "Setting up workspace", "Loading packages...", nil, nil)
341 snapshot, release, err := s.addView(ctx, folder.Name, uri)
342 if err != nil {
343 if err == source.ErrViewExists {
344 continue
345 }
346 viewErrors[uri] = err
347 work.End(ctx, fmt.Sprintf("Error loading packages: %s", err))
348 continue
349 }
350 // Inv: release() must be called once.
351
Alan Donovan32e1cb72022-10-27 12:24:51 -0400352 // Initialize snapshot asynchronously.
353 initialized := make(chan struct{})
354 nsnapshots.Add(1)
355 go func() {
356 snapshot.AwaitInitialized(ctx)
357 work.End(ctx, "Finished loading packages.")
358 nsnapshots.Done()
359 close(initialized) // signal
360 }()
361
362 // Diagnose the newly created view asynchronously.
363 ndiagnose.Add(1)
Robert Findleyb15dac22022-08-30 14:40:12 -0400364 go func() {
365 s.diagnoseDetached(snapshot)
Alan Donovan32e1cb72022-10-27 12:24:51 -0400366 <-initialized
Robert Findleyb15dac22022-08-30 14:40:12 -0400367 release()
Alan Donovan32e1cb72022-10-27 12:24:51 -0400368 ndiagnose.Done()
Robert Findleyb15dac22022-08-30 14:40:12 -0400369 }()
370 }
371
Alan Donovan32e1cb72022-10-27 12:24:51 -0400372 // Wait for snapshots to be initialized so that all files are known.
373 // (We don't need to wait for diagnosis to finish.)
374 nsnapshots.Wait()
375
Robert Findleyb15dac22022-08-30 14:40:12 -0400376 // Register for file watching notifications, if they are supported.
Robert Findleyb15dac22022-08-30 14:40:12 -0400377 if err := s.updateWatchedDirectories(ctx); err != nil {
378 event.Error(ctx, "failed to register for file watching notifications", err)
379 }
380
381 if len(viewErrors) > 0 {
382 errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(folders), len(s.session.Views())-originalViews)
383 for uri, err := range viewErrors {
384 errMsg += fmt.Sprintf("failed to load view for %s: %v\n", uri, err)
385 }
386 return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
387 Type: protocol.Error,
388 Message: errMsg,
389 })
390 }
391 return nil
392}
393
394// updateWatchedDirectories compares the current set of directories to watch
395// with the previously registered set of directories. If the set of directories
396// has changed, we unregister and re-register for file watching notifications.
397// updatedSnapshots is the set of snapshots that have been updated.
398func (s *Server) updateWatchedDirectories(ctx context.Context) error {
399 patterns := s.session.FileWatchingGlobPatterns(ctx)
400
401 s.watchedGlobPatternsMu.Lock()
402 defer s.watchedGlobPatternsMu.Unlock()
403
404 // Nothing to do if the set of workspace directories is unchanged.
405 if equalURISet(s.watchedGlobPatterns, patterns) {
406 return nil
407 }
408
409 // If the set of directories to watch has changed, register the updates and
410 // unregister the previously watched directories. This ordering avoids a
411 // period where no files are being watched. Still, if a user makes on-disk
412 // changes before these updates are complete, we may miss them for the new
413 // directories.
414 prevID := s.watchRegistrationCount - 1
415 if err := s.registerWatchedDirectoriesLocked(ctx, patterns); err != nil {
416 return err
417 }
418 if prevID >= 0 {
419 return s.client.UnregisterCapability(ctx, &protocol.UnregistrationParams{
420 Unregisterations: []protocol.Unregistration{{
421 ID: watchedFilesCapabilityID(prevID),
422 Method: "workspace/didChangeWatchedFiles",
423 }},
424 })
425 }
426 return nil
427}
428
429func watchedFilesCapabilityID(id int) string {
430 return fmt.Sprintf("workspace/didChangeWatchedFiles-%d", id)
431}
432
433func equalURISet(m1, m2 map[string]struct{}) bool {
434 if len(m1) != len(m2) {
435 return false
436 }
437 for k := range m1 {
438 _, ok := m2[k]
439 if !ok {
440 return false
441 }
442 }
443 return true
444}
445
446// registerWatchedDirectoriesLocked sends the workspace/didChangeWatchedFiles
447// registrations to the client and updates s.watchedDirectories.
448func (s *Server) registerWatchedDirectoriesLocked(ctx context.Context, patterns map[string]struct{}) error {
449 if !s.session.Options().DynamicWatchedFilesSupported {
450 return nil
451 }
452 for k := range s.watchedGlobPatterns {
453 delete(s.watchedGlobPatterns, k)
454 }
Peter Weinberger64920582023-04-08 18:42:05 -0400455 watchers := []protocol.FileSystemWatcher{} // must be a slice
Peter Weinberger49420522023-03-01 14:46:20 -0500456 val := protocol.WatchChange | protocol.WatchDelete | protocol.WatchCreate
Robert Findleyb15dac22022-08-30 14:40:12 -0400457 for pattern := range patterns {
458 watchers = append(watchers, protocol.FileSystemWatcher{
459 GlobPattern: pattern,
Peter Weinberger49420522023-03-01 14:46:20 -0500460 Kind: &val,
Robert Findleyb15dac22022-08-30 14:40:12 -0400461 })
462 }
463
464 if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
465 Registrations: []protocol.Registration{{
466 ID: watchedFilesCapabilityID(s.watchRegistrationCount),
467 Method: "workspace/didChangeWatchedFiles",
468 RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{
469 Watchers: watchers,
470 },
471 }},
472 }); err != nil {
473 return err
474 }
475 s.watchRegistrationCount++
476
477 for k, v := range patterns {
478 s.watchedGlobPatterns[k] = v
479 }
480 return nil
481}
482
483func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, o *source.Options) error {
484 if !s.session.Options().ConfigurationSupported {
485 return nil
486 }
487 configs, err := s.client.Configuration(ctx, &protocol.ParamConfiguration{
Peter Weinberger9250e222022-08-16 11:12:59 -0400488 Items: []protocol.ConfigurationItem{{
489 ScopeURI: string(folder),
490 Section: "gopls",
491 }},
492 },
493 )
Robert Findleyb15dac22022-08-30 14:40:12 -0400494 if err != nil {
495 return fmt.Errorf("failed to get workspace configuration from client (%s): %v", folder, err)
496 }
497 for _, config := range configs {
498 if err := s.handleOptionResults(ctx, source.SetOptions(o, config)); err != nil {
499 return err
500 }
501 }
502 return nil
503}
504
505func (s *Server) eventuallyShowMessage(ctx context.Context, msg *protocol.ShowMessageParams) error {
506 s.stateMu.Lock()
507 defer s.stateMu.Unlock()
508 if s.state == serverInitialized {
509 return s.client.ShowMessage(ctx, msg)
510 }
511 s.notifications = append(s.notifications, msg)
512 return nil
513}
514
515func (s *Server) handleOptionResults(ctx context.Context, results source.OptionResults) error {
Robert Findley89b43352022-10-07 13:43:27 -0400516 var warnings, errors []string
Robert Findleyb15dac22022-08-30 14:40:12 -0400517 for _, result := range results {
Robert Findleyb15dac22022-08-30 14:40:12 -0400518 switch result.Error.(type) {
519 case nil:
520 // nothing to do
521 case *source.SoftError:
Robert Findley89b43352022-10-07 13:43:27 -0400522 warnings = append(warnings, result.Error.Error())
Robert Findleyb15dac22022-08-30 14:40:12 -0400523 default:
Robert Findley89b43352022-10-07 13:43:27 -0400524 errors = append(errors, result.Error.Error())
Robert Findleyb15dac22022-08-30 14:40:12 -0400525 }
526 }
Robert Findley89b43352022-10-07 13:43:27 -0400527
528 // Sort messages, but put errors first.
529 //
530 // Having stable content for the message allows clients to de-duplicate. This
531 // matters because we may send duplicate warnings for clients that support
532 // dynamic configuration: one for the initial settings, and then more for the
533 // individual view settings.
534 var msgs []string
535 msgType := protocol.Warning
536 if len(errors) > 0 {
537 msgType = protocol.Error
538 sort.Strings(errors)
539 msgs = append(msgs, errors...)
540 }
541 if len(warnings) > 0 {
542 sort.Strings(warnings)
543 msgs = append(msgs, warnings...)
544 }
545
546 if len(msgs) > 0 {
547 // Settings
548 combined := "Invalid settings: " + strings.Join(msgs, "; ")
549 params := &protocol.ShowMessageParams{
550 Type: msgType,
551 Message: combined,
552 }
553 return s.eventuallyShowMessage(ctx, params)
554 }
555
Robert Findleyb15dac22022-08-30 14:40:12 -0400556 return nil
557}
558
559// beginFileRequest checks preconditions for a file-oriented request and routes
560// it to a snapshot.
561// We don't want to return errors for benign conditions like wrong file type,
562// so callers should do if !ok { return err } rather than if err != nil.
563// The returned cleanup function is non-nil even in case of false/error result.
Robert Findleya7f033a2023-01-19 18:12:18 -0500564func (s *Server) beginFileRequest(ctx context.Context, pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.FileHandle, bool, func(), error) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400565 uri := pURI.SpanURI()
566 if !uri.IsFile() {
567 // Not a file URI. Stop processing the request, but don't return an error.
568 return nil, nil, false, func() {}, nil
569 }
570 view, err := s.session.ViewOf(uri)
571 if err != nil {
572 return nil, nil, false, func() {}, err
573 }
Robert Findleyc4c6aa62023-01-19 20:24:55 -0500574 snapshot, release, err := view.Snapshot()
575 if err != nil {
576 return nil, nil, false, func() {}, err
577 }
Alan Donovan36ed0b12023-03-13 14:20:23 -0400578 fh, err := snapshot.ReadFile(ctx, uri)
Robert Findleyb15dac22022-08-30 14:40:12 -0400579 if err != nil {
580 release()
581 return nil, nil, false, func() {}, err
582 }
583 if expectKind != source.UnknownKind && view.FileKind(fh) != expectKind {
584 // Wrong kind of file. Nothing to do.
585 release()
586 return nil, nil, false, func() {}, nil
587 }
588 return snapshot, fh, true, release, nil
589}
590
591// shutdown implements the 'shutdown' LSP handler. It releases resources
592// associated with the server and waits for all ongoing work to complete.
593func (s *Server) shutdown(ctx context.Context) error {
Robert Findleyf7963612023-03-28 21:34:10 -0400594 ctx, done := event.Start(ctx, "lsp.Server.shutdown")
595 defer done()
596
Robert Findleyb15dac22022-08-30 14:40:12 -0400597 s.stateMu.Lock()
598 defer s.stateMu.Unlock()
599 if s.state < serverInitialized {
600 event.Log(ctx, "server shutdown without initialization")
601 }
602 if s.state != serverShutDown {
603 // drop all the active views
604 s.session.Shutdown(ctx)
605 s.state = serverShutDown
606 if s.tempDir != "" {
607 if err := os.RemoveAll(s.tempDir); err != nil {
608 event.Error(ctx, "removing temp dir", err)
609 }
610 }
611 }
612 return nil
613}
614
615func (s *Server) exit(ctx context.Context) error {
Robert Findleyf7963612023-03-28 21:34:10 -0400616 ctx, done := event.Start(ctx, "lsp.Server.exit")
617 defer done()
618
Robert Findleyb15dac22022-08-30 14:40:12 -0400619 s.stateMu.Lock()
620 defer s.stateMu.Unlock()
621
622 s.client.Close()
623
624 if s.state != serverShutDown {
625 // TODO: We should be able to do better than this.
626 os.Exit(1)
627 }
628 // we don't terminate the process on a normal exit, we just allow it to
629 // close naturally if needed after the connection is closed.
630 return nil
631}
Peter Weinberger64920582023-04-08 18:42:05 -0400632
633// TODO: when we can assume go1.18, replace with generic
634// (after retiring support for go1.17)
635func nonNilSliceString(x []string) []string {
636 if x == nil {
637 return []string{}
638 }
639 return x
640}
641func nonNilSliceTextEdit(x []protocol.TextEdit) []protocol.TextEdit {
642 if x == nil {
643 return []protocol.TextEdit{}
644 }
645
646 return x
647}
648func nonNilSliceCompletionItemTag(x []protocol.CompletionItemTag) []protocol.CompletionItemTag {
649 if x == nil {
650 return []protocol.CompletionItemTag{}
651 }
652 return x
653}
654func emptySliceDiagnosticTag(x []protocol.DiagnosticTag) []protocol.DiagnosticTag {
655 if x == nil {
656 return []protocol.DiagnosticTag{}
657 }
658 return x
659}