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 lsp
6
7import (
8	"bytes"
9	"context"
10	"encoding/json"
11	"fmt"
12	"io"
13	"os"
14	"path"
15	"path/filepath"
16	"sync"
17
18	"golang.org/x/tools/internal/event"
19	"golang.org/x/tools/internal/jsonrpc2"
20	"golang.org/x/tools/internal/lsp/debug"
21	"golang.org/x/tools/internal/lsp/protocol"
22	"golang.org/x/tools/internal/lsp/source"
23	"golang.org/x/tools/internal/span"
24	errors "golang.org/x/xerrors"
25)
26
27func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
28	s.stateMu.Lock()
29	if s.state >= serverInitializing {
30		defer s.stateMu.Unlock()
31		return nil, errors.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
32	}
33	s.state = serverInitializing
34	s.stateMu.Unlock()
35
36	s.clientPID = int(params.ProcessID)
37	s.progress.supportsWorkDoneProgress = params.Capabilities.Window.WorkDoneProgress
38
39	options := s.session.Options()
40	defer func() { s.session.SetOptions(options) }()
41
42	if err := s.handleOptionResults(ctx, source.SetOptions(options, params.InitializationOptions)); err != nil {
43		return nil, err
44	}
45	options.ForClientCapabilities(params.Capabilities)
46
47	folders := params.WorkspaceFolders
48	if len(folders) == 0 {
49		if params.RootURI != "" {
50			folders = []protocol.WorkspaceFolder{{
51				URI:  string(params.RootURI),
52				Name: path.Base(params.RootURI.SpanURI().Filename()),
53			}}
54		}
55	}
56	for _, folder := range folders {
57		uri := span.URIFromURI(folder.URI)
58		if !uri.IsFile() {
59			continue
60		}
61		s.pendingFolders = append(s.pendingFolders, folder)
62	}
63	// gopls only supports URIs with a file:// scheme, so if we have no
64	// workspace folders with a supported scheme, fail to initialize.
65	if len(folders) > 0 && len(s.pendingFolders) == 0 {
66		return nil, fmt.Errorf("unsupported URI schemes: %v (gopls only supports file URIs)", folders)
67	}
68
69	var codeActionProvider interface{} = true
70	if ca := params.Capabilities.TextDocument.CodeAction; len(ca.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 {
71		// If the client has specified CodeActionLiteralSupport,
72		// send the code actions we support.
73		//
74		// Using CodeActionOptions is only valid if codeActionLiteralSupport is set.
75		codeActionProvider = &protocol.CodeActionOptions{
76			CodeActionKinds: s.getSupportedCodeActions(),
77		}
78	}
79	var renameOpts interface{} = true
80	if r := params.Capabilities.TextDocument.Rename; r.PrepareSupport {
81		renameOpts = protocol.RenameOptions{
82			PrepareProvider: r.PrepareSupport,
83		}
84	}
85
86	goplsVersion, err := json.Marshal(debug.VersionInfo())
87	if err != nil {
88		return nil, err
89	}
90
91	return &protocol.InitializeResult{
92		Capabilities: protocol.ServerCapabilities{
93			CallHierarchyProvider: true,
94			CodeActionProvider:    codeActionProvider,
95			CompletionProvider: protocol.CompletionOptions{
96				TriggerCharacters: []string{"."},
97			},
98			DefinitionProvider:         true,
99			TypeDefinitionProvider:     true,
100			ImplementationProvider:     true,
101			DocumentFormattingProvider: true,
102			DocumentSymbolProvider:     true,
103			WorkspaceSymbolProvider:    true,
104			ExecuteCommandProvider: protocol.ExecuteCommandOptions{
105				Commands: options.SupportedCommands,
106			},
107			FoldingRangeProvider:      true,
108			HoverProvider:             true,
109			DocumentHighlightProvider: true,
110			DocumentLinkProvider:      protocol.DocumentLinkOptions{},
111			ReferencesProvider:        true,
112			RenameProvider:            renameOpts,
113			SignatureHelpProvider: protocol.SignatureHelpOptions{
114				TriggerCharacters: []string{"(", ","},
115			},
116			TextDocumentSync: &protocol.TextDocumentSyncOptions{
117				Change:    protocol.Incremental,
118				OpenClose: true,
119				Save: protocol.SaveOptions{
120					IncludeText: false,
121				},
122			},
123			Workspace: protocol.WorkspaceGn{
124				WorkspaceFolders: protocol.WorkspaceFoldersGn{
125					Supported:           true,
126					ChangeNotifications: "workspace/didChangeWorkspaceFolders",
127				},
128			},
129		},
130		ServerInfo: struct {
131			Name    string `json:"name"`
132			Version string `json:"version,omitempty"`
133		}{
134			Name:    "gopls",
135			Version: string(goplsVersion),
136		},
137	}, nil
138}
139
140func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
141	s.stateMu.Lock()
142	if s.state >= serverInitialized {
143		defer s.stateMu.Unlock()
144		return errors.Errorf("%w: initialized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
145	}
146	s.state = serverInitialized
147	s.stateMu.Unlock()
148
149	for _, not := range s.notifications {
150		s.client.ShowMessage(ctx, not)
151	}
152	s.notifications = nil
153
154	options := s.session.Options()
155	defer func() { s.session.SetOptions(options) }()
156
157	if err := s.addFolders(ctx, s.pendingFolders); err != nil {
158		return err
159	}
160	s.pendingFolders = nil
161
162	if options.ConfigurationSupported && options.DynamicConfigurationSupported {
163		registrations := []protocol.Registration{
164			{
165				ID:     "workspace/didChangeConfiguration",
166				Method: "workspace/didChangeConfiguration",
167			},
168			{
169				ID:     "workspace/didChangeWorkspaceFolders",
170				Method: "workspace/didChangeWorkspaceFolders",
171			},
172		}
173		if options.SemanticTokens {
174			registrations = append(registrations, semanticTokenRegistration())
175		}
176		if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
177			Registrations: registrations,
178		}); err != nil {
179			return err
180		}
181	}
182	return nil
183}
184
185func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) error {
186	originalViews := len(s.session.Views())
187	viewErrors := make(map[span.URI]error)
188
189	var wg sync.WaitGroup
190	if s.session.Options().VerboseWorkDoneProgress {
191		work := s.progress.start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil)
192		defer func() {
193			go func() {
194				wg.Wait()
195				work.end("Done.")
196			}()
197		}()
198	}
199	// Only one view gets to have a workspace.
200	assignedWorkspace := false
201	var allFoldersWg sync.WaitGroup
202	for _, folder := range folders {
203		uri := span.URIFromURI(folder.URI)
204		// Ignore non-file URIs.
205		if !uri.IsFile() {
206			continue
207		}
208		work := s.progress.start(ctx, "Setting up workspace", "Loading packages...", nil, nil)
209		var workspaceURI span.URI = ""
210		if !assignedWorkspace && s.clientPID != 0 {
211			// For quick-and-dirty testing, set the temp workspace file to
212			// $TMPDIR/gopls-<client PID>.workspace.
213			//
214			// This has a couple limitations:
215			//  + If there are multiple workspace roots, only the first one gets
216			//    written to this dir (and the client has no way to know precisely
217			//    which one).
218			//  + If a single client PID spawns multiple gopls sessions, they will
219			//    clobber eachother's temp workspace.
220			wsdir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.workspace", s.clientPID))
221			workspaceURI = span.URIFromPath(wsdir)
222			assignedWorkspace = true
223		}
224		snapshot, release, err := s.addView(ctx, folder.Name, uri, workspaceURI)
225		if err != nil {
226			viewErrors[uri] = err
227			work.end(fmt.Sprintf("Error loading packages: %s", err))
228			continue
229		}
230		var swg sync.WaitGroup
231		swg.Add(1)
232		allFoldersWg.Add(1)
233		go func() {
234			defer swg.Done()
235			defer allFoldersWg.Done()
236			snapshot.AwaitInitialized(ctx)
237			work.end("Finished loading packages.")
238		}()
239
240		// Print each view's environment.
241		buf := &bytes.Buffer{}
242		if err := snapshot.WriteEnv(ctx, buf); err != nil {
243			viewErrors[uri] = err
244			continue
245		}
246		event.Log(ctx, buf.String())
247
248		// Diagnose the newly created view.
249		wg.Add(1)
250		go func() {
251			s.diagnoseDetached(snapshot)
252			swg.Wait()
253			release()
254			wg.Done()
255		}()
256	}
257
258	// Register for file watching notifications, if they are supported.
259	// Wait for all snapshots to be initialized first, since all files might
260	// not yet be known to the snapshots.
261	allFoldersWg.Wait()
262	if err := s.updateWatchedDirectories(ctx); err != nil {
263		event.Error(ctx, "failed to register for file watching notifications", err)
264	}
265
266	if len(viewErrors) > 0 {
267		errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(folders), len(s.session.Views())-originalViews)
268		for uri, err := range viewErrors {
269			errMsg += fmt.Sprintf("failed to load view for %s: %v\n", uri, err)
270		}
271		return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
272			Type:    protocol.Error,
273			Message: errMsg,
274		})
275	}
276	return nil
277}
278
279// updateWatchedDirectories compares the current set of directories to watch
280// with the previously registered set of directories. If the set of directories
281// has changed, we unregister and re-register for file watching notifications.
282// updatedSnapshots is the set of snapshots that have been updated.
283func (s *Server) updateWatchedDirectories(ctx context.Context) error {
284	patterns := s.session.FileWatchingGlobPatterns(ctx)
285
286	s.watchedGlobPatternsMu.Lock()
287	defer s.watchedGlobPatternsMu.Unlock()
288
289	// Nothing to do if the set of workspace directories is unchanged.
290	if equalURISet(s.watchedGlobPatterns, patterns) {
291		return nil
292	}
293
294	// If the set of directories to watch has changed, register the updates and
295	// unregister the previously watched directories. This ordering avoids a
296	// period where no files are being watched. Still, if a user makes on-disk
297	// changes before these updates are complete, we may miss them for the new
298	// directories.
299	prevID := s.watchRegistrationCount - 1
300	if err := s.registerWatchedDirectoriesLocked(ctx, patterns); err != nil {
301		return err
302	}
303	if prevID >= 0 {
304		return s.client.UnregisterCapability(ctx, &protocol.UnregistrationParams{
305			Unregisterations: []protocol.Unregistration{{
306				ID:     watchedFilesCapabilityID(prevID),
307				Method: "workspace/didChangeWatchedFiles",
308			}},
309		})
310	}
311	return nil
312}
313
314func watchedFilesCapabilityID(id int) string {
315	return fmt.Sprintf("workspace/didChangeWatchedFiles-%d", id)
316}
317
318func equalURISet(m1, m2 map[string]struct{}) bool {
319	if len(m1) != len(m2) {
320		return false
321	}
322	for k := range m1 {
323		_, ok := m2[k]
324		if !ok {
325			return false
326		}
327	}
328	return true
329}
330
331// registerWatchedDirectoriesLocked sends the workspace/didChangeWatchedFiles
332// registrations to the client and updates s.watchedDirectories.
333func (s *Server) registerWatchedDirectoriesLocked(ctx context.Context, patterns map[string]struct{}) error {
334	if !s.session.Options().DynamicWatchedFilesSupported {
335		return nil
336	}
337	for k := range s.watchedGlobPatterns {
338		delete(s.watchedGlobPatterns, k)
339	}
340	var watchers []protocol.FileSystemWatcher
341	for pattern := range patterns {
342		watchers = append(watchers, protocol.FileSystemWatcher{
343			GlobPattern: pattern,
344			Kind:        float64(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate),
345		})
346	}
347
348	if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
349		Registrations: []protocol.Registration{{
350			ID:     watchedFilesCapabilityID(s.watchRegistrationCount),
351			Method: "workspace/didChangeWatchedFiles",
352			RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{
353				Watchers: watchers,
354			},
355		}},
356	}); err != nil {
357		return err
358	}
359	s.watchRegistrationCount++
360
361	for k, v := range patterns {
362		s.watchedGlobPatterns[k] = v
363	}
364	return nil
365}
366
367func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, o *source.Options) error {
368	if !s.session.Options().ConfigurationSupported {
369		return nil
370	}
371	configs, err := s.client.Configuration(ctx, &protocol.ParamConfiguration{
372		ConfigurationParams: protocol.ConfigurationParams{
373			Items: []protocol.ConfigurationItem{{
374				ScopeURI: string(folder),
375				Section:  "gopls",
376			}},
377		},
378	})
379	if err != nil {
380		return fmt.Errorf("failed to get workspace configuration from client (%s): %v", folder, err)
381	}
382	for _, config := range configs {
383		if err := s.handleOptionResults(ctx, source.SetOptions(o, config)); err != nil {
384			return err
385		}
386	}
387	return nil
388}
389
390func (s *Server) eventuallyShowMessage(ctx context.Context, msg *protocol.ShowMessageParams) error {
391	s.stateMu.Lock()
392	defer s.stateMu.Unlock()
393	if s.state == serverInitialized {
394		return s.client.ShowMessage(ctx, msg)
395	}
396	s.notifications = append(s.notifications, msg)
397	return nil
398}
399
400func (s *Server) handleOptionResults(ctx context.Context, results source.OptionResults) error {
401	for _, result := range results {
402		if result.Error != nil {
403			msg := &protocol.ShowMessageParams{
404				Type:    protocol.Error,
405				Message: result.Error.Error(),
406			}
407			if err := s.eventuallyShowMessage(ctx, msg); err != nil {
408				return err
409			}
410		}
411		switch result.State {
412		case source.OptionUnexpected:
413			msg := &protocol.ShowMessageParams{
414				Type:    protocol.Error,
415				Message: fmt.Sprintf("unexpected gopls setting %q", result.Name),
416			}
417			if err := s.eventuallyShowMessage(ctx, msg); err != nil {
418				return err
419			}
420		case source.OptionDeprecated:
421			msg := fmt.Sprintf("gopls setting %q is deprecated", result.Name)
422			if result.Replacement != "" {
423				msg = fmt.Sprintf("%s, use %q instead", msg, result.Replacement)
424			}
425			if err := s.eventuallyShowMessage(ctx, &protocol.ShowMessageParams{
426				Type:    protocol.Warning,
427				Message: msg,
428			}); err != nil {
429				return err
430			}
431		}
432	}
433	return nil
434}
435
436// beginFileRequest checks preconditions for a file-oriented request and routes
437// it to a snapshot.
438// We don't want to return errors for benign conditions like wrong file type,
439// so callers should do if !ok { return err } rather than if err != nil.
440func (s *Server) beginFileRequest(ctx context.Context, pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.VersionedFileHandle, bool, func(), error) {
441	uri := pURI.SpanURI()
442	if !uri.IsFile() {
443		// Not a file URI. Stop processing the request, but don't return an error.
444		return nil, nil, false, func() {}, nil
445	}
446	view, err := s.session.ViewOf(uri)
447	if err != nil {
448		return nil, nil, false, func() {}, err
449	}
450	snapshot, release := view.Snapshot(ctx)
451	fh, err := snapshot.GetVersionedFile(ctx, uri)
452	if err != nil {
453		release()
454		return nil, nil, false, func() {}, err
455	}
456	if expectKind != source.UnknownKind && fh.Kind() != expectKind {
457		// Wrong kind of file. Nothing to do.
458		release()
459		return nil, nil, false, func() {}, nil
460	}
461	return snapshot, fh, true, release, nil
462}
463
464func (s *Server) shutdown(ctx context.Context) error {
465	s.stateMu.Lock()
466	defer s.stateMu.Unlock()
467	if s.state < serverInitialized {
468		event.Log(ctx, "server shutdown without initialization")
469	}
470	if s.state != serverShutDown {
471		// drop all the active views
472		s.session.Shutdown(ctx)
473		s.state = serverShutDown
474	}
475	return nil
476}
477
478func (s *Server) exit(ctx context.Context) error {
479	s.stateMu.Lock()
480	defer s.stateMu.Unlock()
481
482	// TODO: We need a better way to find the conn close method.
483	s.client.(io.Closer).Close()
484
485	if s.state != serverShutDown {
486		// TODO: We should be able to do better than this.
487		os.Exit(1)
488	}
489	// we don't terminate the process on a normal exit, we just allow it to
490	// close naturally if needed after the connection is closed.
491	return nil
492}
493