blob: 08b65b1bc848bb05fab5c6c28c19bd82666db27f [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
Alan Donovan6e052bb2023-11-29 09:27:28 -05005package server
Robert Findleyb15dac22022-08-30 14:40:12 -04006
Alan Donovan147b88d2023-11-29 10:03:49 -05007// This file defines server methods related to initialization,
8// options, shutdown, and exit.
9
Robert Findleyb15dac22022-08-30 14:40:12 -040010import (
Robert Findleyb15dac22022-08-30 14:40:12 -040011 "context"
12 "encoding/json"
Alan Donovan693d7fe2024-06-16 16:21:21 -040013 "errors"
Robert Findleyb15dac22022-08-30 14:40:12 -040014 "fmt"
Rob Findley5ff5cbb2023-05-22 13:24:35 -040015 "go/build"
Robert Findleyb15dac22022-08-30 14:40:12 -040016 "os"
17 "path"
18 "path/filepath"
Robert Findley89b43352022-10-07 13:43:27 -040019 "sort"
20 "strings"
Robert Findleyb15dac22022-08-30 14:40:12 -040021 "sync"
22
Alan Donovan283fce22024-02-27 12:32:21 -050023 "golang.org/x/telemetry/counter"
Alan Donovanf872b3d2024-01-23 15:32:26 -050024 "golang.org/x/tools/gopls/internal/cache"
Alan Donovanc21f2672023-11-30 11:03:30 -050025 "golang.org/x/tools/gopls/internal/debug"
Hana (Hyang-Ah) Kim9795fac2024-05-07 09:06:19 -040026 debuglog "golang.org/x/tools/gopls/internal/debug/log"
Rob Findleyc8234882023-11-17 16:14:35 -050027 "golang.org/x/tools/gopls/internal/file"
Alan Donovan6d109d12024-01-23 15:35:40 -050028 "golang.org/x/tools/gopls/internal/protocol"
Rob Findley42d97792023-11-17 17:45:31 -050029 "golang.org/x/tools/gopls/internal/settings"
Alan Donovandb5acf62023-11-29 10:17:12 -050030 "golang.org/x/tools/gopls/internal/util/bug"
Alan Donovan5995d312023-11-30 10:58:27 -050031 "golang.org/x/tools/gopls/internal/util/goversion"
Rob Findleyeed19972024-01-22 21:18:15 -050032 "golang.org/x/tools/gopls/internal/util/maps"
Peter Weinberger9250e222022-08-16 11:12:59 -040033 "golang.org/x/tools/internal/event"
34 "golang.org/x/tools/internal/jsonrpc2"
Robert Findleyb15dac22022-08-30 14:40:12 -040035)
36
Alan Donovan25294812023-11-19 13:24:36 -050037func (s *server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
Robert Findleyf7963612023-03-28 21:34:10 -040038 ctx, done := event.Start(ctx, "lsp.Server.initialize")
39 defer done()
40
Alan Donovane211e0f2024-01-24 18:00:21 -050041 var clientName string
42 if params != nil && params.ClientInfo != nil {
43 clientName = params.ClientInfo.Name
44 }
Alan Donovan283fce22024-02-27 12:32:21 -050045 recordClientInfo(clientName)
Hana (Hyang-Ah) Kim4b271f92023-07-19 14:18:26 -040046
Robert Findleyb15dac22022-08-30 14:40:12 -040047 s.stateMu.Lock()
48 if s.state >= serverInitializing {
49 defer s.stateMu.Unlock()
50 return nil, fmt.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
51 }
52 s.state = serverInitializing
53 s.stateMu.Unlock()
54
55 // For uniqueness, use the gopls PID rather than params.ProcessID (the client
56 // pid). Some clients might start multiple gopls servers, though they
57 // probably shouldn't.
58 pid := os.Getpid()
59 s.tempDir = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.%s", pid, s.session.ID()))
60 err := os.Mkdir(s.tempDir, 0700)
61 if err != nil {
62 // MkdirTemp could fail due to permissions issues. This is a problem with
63 // the user's environment, but should not block gopls otherwise behaving.
64 // All usage of s.tempDir should be predicated on having a non-empty
65 // s.tempDir.
66 event.Error(ctx, "creating temp dir", err)
67 s.tempDir = ""
68 }
69 s.progress.SetSupportsWorkDoneProgress(params.Capabilities.Window.WorkDoneProgress)
70
Rob Findleyfb4bd112023-09-06 15:44:41 -040071 options := s.Options().Clone()
Alan Donovan693d7fe2024-06-16 16:21:21 -040072 // TODO(rfindley): eliminate this defer.
Rob Findleyfb4bd112023-09-06 15:44:41 -040073 defer func() { s.SetOptions(options) }()
Robert Findleyb15dac22022-08-30 14:40:12 -040074
Alan Donovan693d7fe2024-06-16 16:21:21 -040075 s.handleOptionErrors(ctx, options.Set(params.InitializationOptions))
Rob Findley3a5dbf32023-05-21 11:03:28 -040076 options.ForClientCapabilities(params.ClientInfo, params.Capabilities)
Robert Findleyb15dac22022-08-30 14:40:12 -040077
78 if options.ShowBugReports {
79 // Report the next bug that occurs on the server.
Alan Donovane2f3b252023-04-25 15:21:16 -040080 bug.Handle(func(b bug.Bug) {
Robert Findleyb15dac22022-08-30 14:40:12 -040081 msg := &protocol.ShowMessageParams{
82 Type: protocol.Error,
83 Message: fmt.Sprintf("A bug occurred on the server: %s\nLocation:%s", b.Description, b.Key),
84 }
Alan Donovan693d7fe2024-06-16 16:21:21 -040085 go s.eventuallyShowMessage(context.Background(), msg)
Alan Donovane2f3b252023-04-25 15:21:16 -040086 })
Robert Findleyb15dac22022-08-30 14:40:12 -040087 }
88
89 folders := params.WorkspaceFolders
90 if len(folders) == 0 {
91 if params.RootURI != "" {
92 folders = []protocol.WorkspaceFolder{{
93 URI: string(params.RootURI),
Alan Donovan1cab1272023-11-17 16:16:59 -050094 Name: path.Base(params.RootURI.Path()),
Robert Findleyb15dac22022-08-30 14:40:12 -040095 }}
96 }
97 }
Hana (Hyang-Ah) Kim9795fac2024-05-07 09:06:19 -040098 s.pendingFolders = append(s.pendingFolders, folders...)
Robert Findleyb15dac22022-08-30 14:40:12 -040099
100 var codeActionProvider interface{} = true
101 if ca := params.Capabilities.TextDocument.CodeAction; len(ca.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 {
102 // If the client has specified CodeActionLiteralSupport,
103 // send the code actions we support.
104 //
105 // Using CodeActionOptions is only valid if codeActionLiteralSupport is set.
106 codeActionProvider = &protocol.CodeActionOptions{
107 CodeActionKinds: s.getSupportedCodeActions(),
Suzy Mueller592d9e12023-12-07 16:51:04 -0800108 ResolveProvider: true,
Robert Findleyb15dac22022-08-30 14:40:12 -0400109 }
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
Robert Findleyb15dac22022-08-30 14:40:12 -0400120 goplsVersion, err := json.Marshal(versionInfo)
121 if err != nil {
122 return nil, err
123 }
124
125 return &protocol.InitializeResult{
126 Capabilities: protocol.ServerCapabilities{
Peter Weinbergere85b5332023-02-18 15:25:18 -0500127 CallHierarchyProvider: &protocol.Or_ServerCapabilities_callHierarchyProvider{Value: true},
Robert Findleyb15dac22022-08-30 14:40:12 -0400128 CodeActionProvider: codeActionProvider,
Suzy Mueller15782442022-09-19 14:13:01 -0400129 CodeLensProvider: &protocol.CodeLensOptions{}, // must be non-nil to enable the code lens capability
Peter Weinberger49420522023-03-01 14:46:20 -0500130 CompletionProvider: &protocol.CompletionOptions{
Robert Findleyb15dac22022-08-30 14:40:12 -0400131 TriggerCharacters: []string{"."},
132 },
Peter Weinbergere85b5332023-02-18 15:25:18 -0500133 DefinitionProvider: &protocol.Or_ServerCapabilities_definitionProvider{Value: true},
134 TypeDefinitionProvider: &protocol.Or_ServerCapabilities_typeDefinitionProvider{Value: true},
135 ImplementationProvider: &protocol.Or_ServerCapabilities_implementationProvider{Value: true},
136 DocumentFormattingProvider: &protocol.Or_ServerCapabilities_documentFormattingProvider{Value: true},
137 DocumentSymbolProvider: &protocol.Or_ServerCapabilities_documentSymbolProvider{Value: true},
138 WorkspaceSymbolProvider: &protocol.Or_ServerCapabilities_workspaceSymbolProvider{Value: true},
Peter Weinberger49420522023-03-01 14:46:20 -0500139 ExecuteCommandProvider: &protocol.ExecuteCommandOptions{
Alan Donovan147b88d2023-11-29 10:03:49 -0500140 Commands: protocol.NonNilSlice(options.SupportedCommands),
Robert Findleyb15dac22022-08-30 14:40:12 -0400141 },
Peter Weinbergere85b5332023-02-18 15:25:18 -0500142 FoldingRangeProvider: &protocol.Or_ServerCapabilities_foldingRangeProvider{Value: true},
143 HoverProvider: &protocol.Or_ServerCapabilities_hoverProvider{Value: true},
144 DocumentHighlightProvider: &protocol.Or_ServerCapabilities_documentHighlightProvider{Value: true},
Peter Weinberger49420522023-03-01 14:46:20 -0500145 DocumentLinkProvider: &protocol.DocumentLinkOptions{},
Robert Findleyb15dac22022-08-30 14:40:12 -0400146 InlayHintProvider: protocol.InlayHintOptions{},
Peter Weinbergere85b5332023-02-18 15:25:18 -0500147 ReferencesProvider: &protocol.Or_ServerCapabilities_referencesProvider{Value: true},
Robert Findleyb15dac22022-08-30 14:40:12 -0400148 RenameProvider: renameOpts,
Peter Weinbergere85b5332023-02-18 15:25:18 -0500149 SelectionRangeProvider: &protocol.Or_ServerCapabilities_selectionRangeProvider{Value: true},
Peter Weinbergerb5d65e02023-01-14 15:58:41 -0500150 SemanticTokensProvider: protocol.SemanticTokensOptions{
Peter Weinbergere85b5332023-02-18 15:25:18 -0500151 Range: &protocol.Or_SemanticTokensOptions_range{Value: true},
152 Full: &protocol.Or_SemanticTokensOptions_full{Value: true},
Peter Weinbergerb5d65e02023-01-14 15:58:41 -0500153 Legend: protocol.SemanticTokensLegend{
Alan Donovan147b88d2023-11-29 10:03:49 -0500154 TokenTypes: protocol.NonNilSlice(options.SemanticTypes),
155 TokenModifiers: protocol.NonNilSlice(options.SemanticMods),
Peter Weinbergerb5d65e02023-01-14 15:58:41 -0500156 },
157 },
Peter Weinberger49420522023-03-01 14:46:20 -0500158 SignatureHelpProvider: &protocol.SignatureHelpOptions{
Robert Findleyb15dac22022-08-30 14:40:12 -0400159 TriggerCharacters: []string{"(", ","},
160 },
161 TextDocumentSync: &protocol.TextDocumentSyncOptions{
162 Change: protocol.Incremental,
163 OpenClose: true,
Peter Weinberger49420522023-03-01 14:46:20 -0500164 Save: &protocol.SaveOptions{
Robert Findleyb15dac22022-08-30 14:40:12 -0400165 IncludeText: false,
166 },
167 },
Peter Weinbergerf291bf82023-11-11 08:38:32 -0500168 Workspace: &protocol.WorkspaceOptions{
Peter Weinberger49420522023-03-01 14:46:20 -0500169 WorkspaceFolders: &protocol.WorkspaceFolders5Gn{
Robert Findleyb15dac22022-08-30 14:40:12 -0400170 Supported: true,
171 ChangeNotifications: "workspace/didChangeWorkspaceFolders",
172 },
173 },
174 },
Peter Weinbergerf291bf82023-11-11 08:38:32 -0500175 ServerInfo: &protocol.ServerInfo{
Robert Findleyb15dac22022-08-30 14:40:12 -0400176 Name: "gopls",
177 Version: string(goplsVersion),
178 },
179 }, nil
180}
181
Alan Donovan25294812023-11-19 13:24:36 -0500182func (s *server) Initialized(ctx context.Context, params *protocol.InitializedParams) error {
Robert Findleyf7963612023-03-28 21:34:10 -0400183 ctx, done := event.Start(ctx, "lsp.Server.initialized")
184 defer done()
185
Robert Findleyb15dac22022-08-30 14:40:12 -0400186 s.stateMu.Lock()
187 if s.state >= serverInitialized {
188 defer s.stateMu.Unlock()
189 return fmt.Errorf("%w: initialized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
190 }
191 s.state = serverInitialized
192 s.stateMu.Unlock()
193
194 for _, not := range s.notifications {
195 s.client.ShowMessage(ctx, not)
196 }
197 s.notifications = nil
198
Alan Donovan73e70de2023-11-09 13:07:56 -0500199 s.addFolders(ctx, s.pendingFolders)
200
Robert Findleyb15dac22022-08-30 14:40:12 -0400201 s.pendingFolders = nil
Robert Findley20c1ee72022-09-28 13:10:58 -0400202 s.checkViewGoVersions()
Robert Findleyb15dac22022-08-30 14:40:12 -0400203
204 var registrations []protocol.Registration
Rob Findleyfda3fe32023-10-09 09:33:23 -0400205 options := s.Options()
Robert Findleyb15dac22022-08-30 14:40:12 -0400206 if options.ConfigurationSupported && options.DynamicConfigurationSupported {
207 registrations = append(registrations, protocol.Registration{
208 ID: "workspace/didChangeConfiguration",
209 Method: "workspace/didChangeConfiguration",
210 })
211 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400212 if len(registrations) > 0 {
213 if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
214 Registrations: registrations,
215 }); err != nil {
216 return err
217 }
218 }
Rob Findleya3c6fd82023-09-12 13:08:54 -0400219
220 // Ask (maybe) about enabling telemetry. Do this asynchronously, as it's OK
221 // for users to ignore or dismiss the question.
Hana (Hyang-Ah) Kim64beb952023-09-22 09:19:20 -0400222 go s.maybePromptForTelemetry(ctx, options.TelemetryPrompt)
Rob Findleya3c6fd82023-09-12 13:08:54 -0400223
Robert Findleyb15dac22022-08-30 14:40:12 -0400224 return nil
225}
226
Robert Findley20c1ee72022-09-28 13:10:58 -0400227// checkViewGoVersions checks whether any Go version used by a view is too old,
228// raising a showMessage notification if so.
229//
230// It should be called after views change.
Alan Donovan79546892023-11-13 16:59:44 -0500231func (s *server) checkViewGoVersions() {
Rob Findley5ff5cbb2023-05-22 13:24:35 -0400232 oldestVersion, fromBuild := go1Point(), true
Robert Findley20c1ee72022-09-28 13:10:58 -0400233 for _, view := range s.session.Views() {
234 viewVersion := view.GoVersion()
235 if oldestVersion == -1 || viewVersion < oldestVersion {
Rob Findley5ff5cbb2023-05-22 13:24:35 -0400236 oldestVersion, fromBuild = viewVersion, false
Robert Findley20c1ee72022-09-28 13:10:58 -0400237 }
Alan Donovan283fce22024-02-27 12:32:21 -0500238 if viewVersion >= 0 {
239 counter.Inc(fmt.Sprintf("gopls/goversion:1.%d", viewVersion))
240 }
Robert Findley20c1ee72022-09-28 13:10:58 -0400241 }
242
Alan Donovancae49bd2023-11-13 18:08:16 -0500243 if msg, isError := goversion.Message(oldestVersion, fromBuild); msg != "" {
244 mType := protocol.Warning
245 if isError {
246 mType = protocol.Error
247 }
Robert Findley20c1ee72022-09-28 13:10:58 -0400248 s.eventuallyShowMessage(context.Background(), &protocol.ShowMessageParams{
Robert Findley42cb7be2022-10-26 18:57:08 -0400249 Type: mType,
Robert Findley20c1ee72022-09-28 13:10:58 -0400250 Message: msg,
251 })
252 }
253}
254
Rob Findley5ff5cbb2023-05-22 13:24:35 -0400255// go1Point returns the x in Go 1.x. If an error occurs extracting the go
256// version, it returns -1.
257//
258// Copied from the testenv package.
259func go1Point() int {
260 for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- {
261 var version int
262 if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil {
263 continue
264 }
265 return version
266 }
267 return -1
268}
269
Alan Donovan73e70de2023-11-09 13:07:56 -0500270// addFolders adds the specified list of "folders" (that's Windows for
271// directories) to the session. It does not return an error, though it
272// may report an error to the client over LSP if one or more folders
Hana (Hyang-Ah) Kim9795fac2024-05-07 09:06:19 -0400273// had problems, for example, folders with unsupported file system.
Alan Donovan73e70de2023-11-09 13:07:56 -0500274func (s *server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400275 originalViews := len(s.session.Views())
Alan Donovanc04fbc02023-11-28 12:50:06 -0500276 viewErrors := make(map[protocol.URI]error)
Robert Findleyb15dac22022-08-30 14:40:12 -0400277
Hana (Hyang-Ah) Kim9795fac2024-05-07 09:06:19 -0400278 // Skip non-'file' scheme, or invalid workspace folders,
279 // and log them form error reports.
280 // VS Code's file system API
281 // (https://code.visualstudio.com/api/references/vscode-api#FileSystem)
282 // allows extension to define their own schemes and register
283 // them with the workspace. We've seen gitlens://, decompileFs://, etc
284 // but the list can grow over time.
285 var filtered []protocol.WorkspaceFolder
286 for _, f := range folders {
287 if _, err := protocol.ParseDocumentURI(f.URI); err != nil {
288 debuglog.Warning.Logf(ctx, "skip adding virtual folder %q - invalid folder URI: %v", f.Name, err)
289 continue
290 }
291 filtered = append(filtered, f)
292 }
293 folders = filtered
294
Alan Donovan32e1cb72022-10-27 12:24:51 -0400295 var ndiagnose sync.WaitGroup // number of unfinished diagnose calls
Rob Findleyfb4bd112023-09-06 15:44:41 -0400296 if s.Options().VerboseWorkDoneProgress {
Robert Findleyb15dac22022-08-30 14:40:12 -0400297 work := s.progress.Start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil)
298 defer func() {
299 go func() {
Alan Donovan32e1cb72022-10-27 12:24:51 -0400300 ndiagnose.Wait()
Robert Findleyb15dac22022-08-30 14:40:12 -0400301 work.End(ctx, "Done.")
302 }()
303 }()
304 }
305 // Only one view gets to have a workspace.
Alan Donovan32e1cb72022-10-27 12:24:51 -0400306 var nsnapshots sync.WaitGroup // number of unfinished snapshot initializations
Robert Findleyb15dac22022-08-30 14:40:12 -0400307 for _, folder := range folders {
Alan Donovaneee280c2023-11-18 14:43:57 -0500308 uri, err := protocol.ParseDocumentURI(folder.URI)
309 if err != nil {
Alan Donovanc04fbc02023-11-28 12:50:06 -0500310 viewErrors[folder.URI] = fmt.Errorf("invalid folder URI: %v", err)
Robert Findleyb15dac22022-08-30 14:40:12 -0400311 continue
312 }
313 work := s.progress.Start(ctx, "Setting up workspace", "Loading packages...", nil, nil)
314 snapshot, release, err := s.addView(ctx, folder.Name, uri)
315 if err != nil {
Rob Findleye7d61d92023-11-20 15:54:11 -0500316 if err == cache.ErrViewExists {
Robert Findleyb15dac22022-08-30 14:40:12 -0400317 continue
318 }
Alan Donovanc04fbc02023-11-28 12:50:06 -0500319 viewErrors[folder.URI] = err
Robert Findleyb15dac22022-08-30 14:40:12 -0400320 work.End(ctx, fmt.Sprintf("Error loading packages: %s", err))
321 continue
322 }
323 // Inv: release() must be called once.
324
Alan Donovan32e1cb72022-10-27 12:24:51 -0400325 // Initialize snapshot asynchronously.
326 initialized := make(chan struct{})
327 nsnapshots.Add(1)
328 go func() {
329 snapshot.AwaitInitialized(ctx)
330 work.End(ctx, "Finished loading packages.")
331 nsnapshots.Done()
332 close(initialized) // signal
333 }()
334
335 // Diagnose the newly created view asynchronously.
336 ndiagnose.Add(1)
Robert Findleyb15dac22022-08-30 14:40:12 -0400337 go func() {
Robert Findley79df9712024-04-09 14:07:32 -0400338 s.diagnoseSnapshot(snapshot.BackgroundContext(), snapshot, nil, 0)
Alan Donovan32e1cb72022-10-27 12:24:51 -0400339 <-initialized
Robert Findleyb15dac22022-08-30 14:40:12 -0400340 release()
Alan Donovan32e1cb72022-10-27 12:24:51 -0400341 ndiagnose.Done()
Robert Findleyb15dac22022-08-30 14:40:12 -0400342 }()
343 }
344
Alan Donovan32e1cb72022-10-27 12:24:51 -0400345 // Wait for snapshots to be initialized so that all files are known.
346 // (We don't need to wait for diagnosis to finish.)
347 nsnapshots.Wait()
348
Robert Findleyb15dac22022-08-30 14:40:12 -0400349 // Register for file watching notifications, if they are supported.
Robert Findleyb15dac22022-08-30 14:40:12 -0400350 if err := s.updateWatchedDirectories(ctx); err != nil {
351 event.Error(ctx, "failed to register for file watching notifications", err)
352 }
353
Alan Donovan73e70de2023-11-09 13:07:56 -0500354 // Report any errors using the protocol.
Robert Findleyb15dac22022-08-30 14:40:12 -0400355 if len(viewErrors) > 0 {
356 errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(folders), len(s.session.Views())-originalViews)
357 for uri, err := range viewErrors {
358 errMsg += fmt.Sprintf("failed to load view for %s: %v\n", uri, err)
359 }
Alan Donovan73e70de2023-11-09 13:07:56 -0500360 showMessage(ctx, s.client, protocol.Error, errMsg)
Robert Findleyb15dac22022-08-30 14:40:12 -0400361 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400362}
363
364// updateWatchedDirectories compares the current set of directories to watch
365// with the previously registered set of directories. If the set of directories
366// has changed, we unregister and re-register for file watching notifications.
367// updatedSnapshots is the set of snapshots that have been updated.
Alan Donovan79546892023-11-13 16:59:44 -0500368func (s *server) updateWatchedDirectories(ctx context.Context) error {
Robert Findleyb15dac22022-08-30 14:40:12 -0400369 patterns := s.session.FileWatchingGlobPatterns(ctx)
370
371 s.watchedGlobPatternsMu.Lock()
372 defer s.watchedGlobPatternsMu.Unlock()
373
374 // Nothing to do if the set of workspace directories is unchanged.
Rob Findleyeed19972024-01-22 21:18:15 -0500375 if maps.SameKeys(s.watchedGlobPatterns, patterns) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400376 return nil
377 }
378
379 // If the set of directories to watch has changed, register the updates and
380 // unregister the previously watched directories. This ordering avoids a
381 // period where no files are being watched. Still, if a user makes on-disk
382 // changes before these updates are complete, we may miss them for the new
383 // directories.
384 prevID := s.watchRegistrationCount - 1
385 if err := s.registerWatchedDirectoriesLocked(ctx, patterns); err != nil {
386 return err
387 }
388 if prevID >= 0 {
389 return s.client.UnregisterCapability(ctx, &protocol.UnregistrationParams{
390 Unregisterations: []protocol.Unregistration{{
391 ID: watchedFilesCapabilityID(prevID),
392 Method: "workspace/didChangeWatchedFiles",
393 }},
394 })
395 }
396 return nil
397}
398
399func watchedFilesCapabilityID(id int) string {
400 return fmt.Sprintf("workspace/didChangeWatchedFiles-%d", id)
401}
402
Robert Findleyb15dac22022-08-30 14:40:12 -0400403// registerWatchedDirectoriesLocked sends the workspace/didChangeWatchedFiles
404// registrations to the client and updates s.watchedDirectories.
Alan Donovan2ec42992023-05-18 13:45:43 -0400405// The caller must not subsequently mutate patterns.
Rob Findleyeed19972024-01-22 21:18:15 -0500406func (s *server) registerWatchedDirectoriesLocked(ctx context.Context, patterns map[protocol.RelativePattern]unit) error {
Rob Findleyfb4bd112023-09-06 15:44:41 -0400407 if !s.Options().DynamicWatchedFilesSupported {
Robert Findleyb15dac22022-08-30 14:40:12 -0400408 return nil
409 }
Rob Findleyeed19972024-01-22 21:18:15 -0500410
411 supportsRelativePatterns := s.Options().RelativePatternsSupported
412
Alan Donovan2ec42992023-05-18 13:45:43 -0400413 s.watchedGlobPatterns = patterns
414 watchers := make([]protocol.FileSystemWatcher, 0, len(patterns)) // must be a slice
Peter Weinberger49420522023-03-01 14:46:20 -0500415 val := protocol.WatchChange | protocol.WatchDelete | protocol.WatchCreate
Robert Findleyb15dac22022-08-30 14:40:12 -0400416 for pattern := range patterns {
Rob Findleyeed19972024-01-22 21:18:15 -0500417 var value any
418 if supportsRelativePatterns && pattern.BaseURI != "" {
419 value = pattern
420 } else {
421 p := pattern.Pattern
422 if pattern.BaseURI != "" {
423 p = path.Join(filepath.ToSlash(pattern.BaseURI.Path()), p)
424 }
425 value = p
426 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400427 watchers = append(watchers, protocol.FileSystemWatcher{
Rob Findleyeed19972024-01-22 21:18:15 -0500428 GlobPattern: protocol.GlobPattern{Value: value},
Peter Weinberger49420522023-03-01 14:46:20 -0500429 Kind: &val,
Robert Findleyb15dac22022-08-30 14:40:12 -0400430 })
431 }
432
433 if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
434 Registrations: []protocol.Registration{{
435 ID: watchedFilesCapabilityID(s.watchRegistrationCount),
436 Method: "workspace/didChangeWatchedFiles",
437 RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{
438 Watchers: watchers,
439 },
440 }},
441 }); err != nil {
442 return err
443 }
444 s.watchRegistrationCount++
Robert Findleyb15dac22022-08-30 14:40:12 -0400445 return nil
446}
447
Rob Findleyfb4bd112023-09-06 15:44:41 -0400448// Options returns the current server options.
449//
450// The caller must not modify the result.
Rob Findley42d97792023-11-17 17:45:31 -0500451func (s *server) Options() *settings.Options {
Rob Findleyfb4bd112023-09-06 15:44:41 -0400452 s.optionsMu.Lock()
453 defer s.optionsMu.Unlock()
454 return s.options
455}
456
457// SetOptions sets the current server options.
458//
459// The caller must not subsequently modify the options.
Rob Findley42d97792023-11-17 17:45:31 -0500460func (s *server) SetOptions(opts *settings.Options) {
Rob Findleyfb4bd112023-09-06 15:44:41 -0400461 s.optionsMu.Lock()
462 defer s.optionsMu.Unlock()
463 s.options = opts
464}
465
Robert Findleye388fff2024-04-11 13:39:34 -0400466func (s *server) newFolder(ctx context.Context, folder protocol.DocumentURI, name string, opts *settings.Options) (*cache.Folder, error) {
Rob Findley025ebe62023-12-21 17:26:48 -0500467 env, err := cache.FetchGoEnv(ctx, folder, opts)
468 if err != nil {
469 return nil, err
470 }
Rob Findleyfcf54632024-06-28 15:49:12 +0000471
472 // Increment folder counters.
473 switch {
474 case env.GOTOOLCHAIN == "auto" || strings.Contains(env.GOTOOLCHAIN, "+auto"):
475 counter.New("gopls/gotoolchain:auto").Inc()
476 case env.GOTOOLCHAIN == "path" || strings.Contains(env.GOTOOLCHAIN, "+path"):
477 counter.New("gopls/gotoolchain:path").Inc()
478 case env.GOTOOLCHAIN == "local": // local+auto and local+path handled above
479 counter.New("gopls/gotoolchain:local").Inc()
480 default:
481 counter.New("gopls/gotoolchain:other").Inc()
482 }
483
Rob Findley025ebe62023-12-21 17:26:48 -0500484 return &cache.Folder{
485 Dir: folder,
486 Name: name,
487 Options: opts,
Robert Findleye388fff2024-04-11 13:39:34 -0400488 Env: *env,
Rob Findley025ebe62023-12-21 17:26:48 -0500489 }, nil
490}
491
Robert Findleyc5643e92024-02-12 12:43:03 -0500492// fetchFolderOptions makes a workspace/configuration request for the given
493// folder, and populates options with the result.
494//
495// If folder is "", fetchFolderOptions makes an unscoped request.
Rob Findley42d97792023-11-17 17:45:31 -0500496func (s *server) fetchFolderOptions(ctx context.Context, folder protocol.DocumentURI) (*settings.Options, error) {
Rob Findley025ebe62023-12-21 17:26:48 -0500497 opts := s.Options()
498 if !opts.ConfigurationSupported {
Rob Findleyfb4bd112023-09-06 15:44:41 -0400499 return opts, nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400500 }
Robert Findleyc5643e92024-02-12 12:43:03 -0500501 var scopeURI *string
502 if folder != "" {
503 scope := string(folder)
504 scopeURI = &scope
505 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400506 configs, err := s.client.Configuration(ctx, &protocol.ParamConfiguration{
Peter Weinberger9250e222022-08-16 11:12:59 -0400507 Items: []protocol.ConfigurationItem{{
Robert Findleyc5643e92024-02-12 12:43:03 -0500508 ScopeURI: scopeURI,
Peter Weinberger9250e222022-08-16 11:12:59 -0400509 Section: "gopls",
510 }},
511 },
512 )
Robert Findleyb15dac22022-08-30 14:40:12 -0400513 if err != nil {
Rob Findleyfb4bd112023-09-06 15:44:41 -0400514 return nil, fmt.Errorf("failed to get workspace configuration from client (%s): %v", folder, err)
Robert Findleyb15dac22022-08-30 14:40:12 -0400515 }
Rob Findleyfb4bd112023-09-06 15:44:41 -0400516
Rob Findley025ebe62023-12-21 17:26:48 -0500517 opts = opts.Clone()
Robert Findleyb15dac22022-08-30 14:40:12 -0400518 for _, config := range configs {
Alan Donovan693d7fe2024-06-16 16:21:21 -0400519 s.handleOptionErrors(ctx, opts.Set(config))
Robert Findleyb15dac22022-08-30 14:40:12 -0400520 }
Rob Findley025ebe62023-12-21 17:26:48 -0500521 return opts, nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400522}
523
Alan Donovan693d7fe2024-06-16 16:21:21 -0400524func (s *server) eventuallyShowMessage(ctx context.Context, msg *protocol.ShowMessageParams) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400525 s.stateMu.Lock()
526 defer s.stateMu.Unlock()
527 if s.state == serverInitialized {
Alan Donovan693d7fe2024-06-16 16:21:21 -0400528 _ = s.client.ShowMessage(ctx, msg) // ignore error
Robert Findleyb15dac22022-08-30 14:40:12 -0400529 }
530 s.notifications = append(s.notifications, msg)
Robert Findleyb15dac22022-08-30 14:40:12 -0400531}
532
Alan Donovan693d7fe2024-06-16 16:21:21 -0400533func (s *server) handleOptionErrors(ctx context.Context, optionErrors []error) {
534 var warnings, errs []string
535 for _, err := range optionErrors {
536 if err == nil {
537 panic("nil error passed to handleOptionErrors")
538 }
539 if errors.Is(err, new(settings.SoftError)) {
540 warnings = append(warnings, err.Error())
541 } else {
542 errs = append(errs, err.Error())
Robert Findleyb15dac22022-08-30 14:40:12 -0400543 }
544 }
Robert Findley89b43352022-10-07 13:43:27 -0400545
546 // Sort messages, but put errors first.
547 //
548 // Having stable content for the message allows clients to de-duplicate. This
549 // matters because we may send duplicate warnings for clients that support
550 // dynamic configuration: one for the initial settings, and then more for the
Rob Findley42d97792023-11-17 17:45:31 -0500551 // individual viewsettings.
Robert Findley89b43352022-10-07 13:43:27 -0400552 var msgs []string
553 msgType := protocol.Warning
Alan Donovan693d7fe2024-06-16 16:21:21 -0400554 if len(errs) > 0 {
Robert Findley89b43352022-10-07 13:43:27 -0400555 msgType = protocol.Error
Alan Donovan693d7fe2024-06-16 16:21:21 -0400556 sort.Strings(errs)
557 msgs = append(msgs, errs...)
Robert Findley89b43352022-10-07 13:43:27 -0400558 }
559 if len(warnings) > 0 {
560 sort.Strings(warnings)
561 msgs = append(msgs, warnings...)
562 }
563
564 if len(msgs) > 0 {
565 // Settings
566 combined := "Invalid settings: " + strings.Join(msgs, "; ")
567 params := &protocol.ShowMessageParams{
568 Type: msgType,
569 Message: combined,
570 }
Alan Donovan693d7fe2024-06-16 16:21:21 -0400571 s.eventuallyShowMessage(ctx, params)
Robert Findley89b43352022-10-07 13:43:27 -0400572 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400573}
574
Alan Donovanbaf6fd22023-12-21 12:54:46 -0500575// fileOf returns the file for a given URI and its snapshot.
576// On success, the returned function must be called to release the snapshot.
577func (s *server) fileOf(ctx context.Context, uri protocol.DocumentURI) (file.Handle, *cache.Snapshot, func(), error) {
Rob Findley59384bc2023-12-18 16:29:41 -0500578 snapshot, release, err := s.session.SnapshotOf(ctx, uri)
Robert Findleyc4c6aa62023-01-19 20:24:55 -0500579 if err != nil {
Alan Donovanbaf6fd22023-12-21 12:54:46 -0500580 return nil, nil, nil, err
Robert Findleyc4c6aa62023-01-19 20:24:55 -0500581 }
Alan Donovan36ed0b12023-03-13 14:20:23 -0400582 fh, err := snapshot.ReadFile(ctx, uri)
Robert Findleyb15dac22022-08-30 14:40:12 -0400583 if err != nil {
584 release()
Alan Donovanbaf6fd22023-12-21 12:54:46 -0500585 return nil, nil, nil, err
Robert Findleyb15dac22022-08-30 14:40:12 -0400586 }
Alan Donovanbaf6fd22023-12-21 12:54:46 -0500587 return fh, snapshot, release, nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400588}
589
590// shutdown implements the 'shutdown' LSP handler. It releases resources
591// associated with the server and waits for all ongoing work to complete.
Alan Donovan25294812023-11-19 13:24:36 -0500592func (s *server) Shutdown(ctx context.Context) error {
Robert Findleyf7963612023-03-28 21:34:10 -0400593 ctx, done := event.Start(ctx, "lsp.Server.shutdown")
594 defer done()
595
Robert Findleyb15dac22022-08-30 14:40:12 -0400596 s.stateMu.Lock()
597 defer s.stateMu.Unlock()
598 if s.state < serverInitialized {
599 event.Log(ctx, "server shutdown without initialization")
600 }
601 if s.state != serverShutDown {
Alan Donovan8669bfc2024-03-11 15:07:06 -0400602 // Wait for the webserver (if any) to finish.
603 if s.web != nil {
604 s.web.server.Shutdown(ctx)
605 }
606
Robert Findleyb15dac22022-08-30 14:40:12 -0400607 // drop all the active views
608 s.session.Shutdown(ctx)
609 s.state = serverShutDown
610 if s.tempDir != "" {
611 if err := os.RemoveAll(s.tempDir); err != nil {
612 event.Error(ctx, "removing temp dir", err)
613 }
614 }
615 }
616 return nil
617}
618
Alan Donovan25294812023-11-19 13:24:36 -0500619func (s *server) Exit(ctx context.Context) error {
Robert Findleyf7963612023-03-28 21:34:10 -0400620 ctx, done := event.Start(ctx, "lsp.Server.exit")
621 defer done()
622
Robert Findleyb15dac22022-08-30 14:40:12 -0400623 s.stateMu.Lock()
624 defer s.stateMu.Unlock()
625
626 s.client.Close()
627
628 if s.state != serverShutDown {
629 // TODO: We should be able to do better than this.
630 os.Exit(1)
631 }
Rob Findleye286d222023-04-20 17:21:15 -0400632 // We don't terminate the process on a normal exit, we just allow it to
Robert Findleyb15dac22022-08-30 14:40:12 -0400633 // close naturally if needed after the connection is closed.
634 return nil
635}
Alan Donovan283fce22024-02-27 12:32:21 -0500636
637// recordClientInfo records gopls client info.
638func recordClientInfo(clientName string) {
639 key := "gopls/client:other"
640 switch clientName {
641 case "Visual Studio Code":
642 key = "gopls/client:vscode"
643 case "Visual Studio Code - Insiders":
644 key = "gopls/client:vscode-insiders"
645 case "VSCodium":
646 key = "gopls/client:vscodium"
647 case "code-server":
648 // https://github.com/coder/code-server/blob/3cb92edc76ecc2cfa5809205897d93d4379b16a6/ci/build/build-vscode.sh#L19
649 key = "gopls/client:code-server"
650 case "Eglot":
651 // https://lists.gnu.org/archive/html/bug-gnu-emacs/2023-03/msg00954.html
652 key = "gopls/client:eglot"
653 case "govim":
654 // https://github.com/govim/govim/pull/1189
655 key = "gopls/client:govim"
656 case "Neovim":
657 // https://github.com/neovim/neovim/blob/42333ea98dfcd2994ee128a3467dfe68205154cd/runtime/lua/vim/lsp.lua#L1361
658 key = "gopls/client:neovim"
659 case "coc.nvim":
660 // https://github.com/neoclide/coc.nvim/blob/3dc6153a85ed0f185abec1deb972a66af3fbbfb4/src/language-client/client.ts#L994
661 key = "gopls/client:coc.nvim"
662 case "Sublime Text LSP":
663 // https://github.com/sublimelsp/LSP/blob/e608f878e7e9dd34aabe4ff0462540fadcd88fcc/plugin/core/sessions.py#L493
664 key = "gopls/client:sublimetext"
665 default:
666 // Accumulate at least a local counter for an unknown
667 // client name, but also fall through to count it as
668 // ":other" for collection.
669 if clientName != "" {
670 counter.New(fmt.Sprintf("gopls/client-other:%s", clientName)).Inc()
671 }
672 }
673 counter.Inc(key)
674}