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	"fmt"
11	"os"
12	"path"
13
14	"golang.org/x/tools/internal/jsonrpc2"
15	"golang.org/x/tools/internal/lsp/debug"
16	"golang.org/x/tools/internal/lsp/protocol"
17	"golang.org/x/tools/internal/lsp/source"
18	"golang.org/x/tools/internal/span"
19	"golang.org/x/tools/internal/telemetry/log"
20	errors "golang.org/x/xerrors"
21)
22
23func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
24	s.stateMu.Lock()
25	state := s.state
26	s.stateMu.Unlock()
27	if state >= serverInitializing {
28		return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server already initialized")
29	}
30	s.stateMu.Lock()
31	s.state = serverInitializing
32	s.stateMu.Unlock()
33
34	options := s.session.Options()
35	defer func() { s.session.SetOptions(options) }()
36
37	// TODO: Handle results here.
38	source.SetOptions(&options, params.InitializationOptions)
39	options.ForClientCapabilities(params.Capabilities)
40
41	s.pendingFolders = params.WorkspaceFolders
42	if len(s.pendingFolders) == 0 {
43		if params.RootURI != "" {
44			s.pendingFolders = []protocol.WorkspaceFolder{{
45				URI:  string(params.RootURI),
46				Name: path.Base(params.RootURI.SpanURI().Filename()),
47			}}
48		} else {
49			// No folders and no root--we are in single file mode.
50			// TODO: https://golang.org/issue/34160.
51			return nil, errors.Errorf("gopls does not yet support editing a single file. Please open a directory.")
52		}
53	}
54
55	var codeActionProvider interface{} = true
56	if ca := params.Capabilities.TextDocument.CodeAction; len(ca.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 {
57		// If the client has specified CodeActionLiteralSupport,
58		// send the code actions we support.
59		//
60		// Using CodeActionOptions is only valid if codeActionLiteralSupport is set.
61		codeActionProvider = &protocol.CodeActionOptions{
62			CodeActionKinds: s.getSupportedCodeActions(),
63		}
64	}
65	var renameOpts interface{} = true
66	if r := params.Capabilities.TextDocument.Rename; r.PrepareSupport {
67		renameOpts = protocol.RenameOptions{
68			PrepareProvider: r.PrepareSupport,
69		}
70	}
71	return &protocol.InitializeResult{
72		Capabilities: protocol.ServerCapabilities{
73			CodeActionProvider: codeActionProvider,
74			CompletionProvider: protocol.CompletionOptions{
75				TriggerCharacters: []string{"."},
76			},
77			DefinitionProvider:         true,
78			TypeDefinitionProvider:     true,
79			ImplementationProvider:     true,
80			DocumentFormattingProvider: true,
81			DocumentSymbolProvider:     true,
82			WorkspaceSymbolProvider:    true,
83			ExecuteCommandProvider: protocol.ExecuteCommandOptions{
84				Commands: options.SupportedCommands,
85			},
86			FoldingRangeProvider:      true,
87			HoverProvider:             true,
88			DocumentHighlightProvider: true,
89			DocumentLinkProvider:      protocol.DocumentLinkOptions{},
90			ReferencesProvider:        true,
91			RenameProvider:            renameOpts,
92			SignatureHelpProvider: protocol.SignatureHelpOptions{
93				TriggerCharacters: []string{"(", ","},
94			},
95			TextDocumentSync: &protocol.TextDocumentSyncOptions{
96				Change:    protocol.Incremental,
97				OpenClose: true,
98				Save: protocol.SaveOptions{
99					IncludeText: false,
100				},
101			},
102			Workspace: protocol.WorkspaceGn{
103				WorkspaceFolders: protocol.WorkspaceFoldersGn{
104					Supported:           true,
105					ChangeNotifications: "workspace/didChangeWorkspaceFolders",
106				},
107			},
108		},
109	}, nil
110}
111
112func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
113	s.stateMu.Lock()
114	s.state = serverInitialized
115	s.stateMu.Unlock()
116
117	options := s.session.Options()
118	defer func() { s.session.SetOptions(options) }()
119
120	var registrations []protocol.Registration
121	if options.ConfigurationSupported && options.DynamicConfigurationSupported {
122		registrations = append(registrations,
123			protocol.Registration{
124				ID:     "workspace/didChangeConfiguration",
125				Method: "workspace/didChangeConfiguration",
126			},
127			protocol.Registration{
128				ID:     "workspace/didChangeWorkspaceFolders",
129				Method: "workspace/didChangeWorkspaceFolders",
130			},
131		)
132	}
133
134	if options.DynamicWatchedFilesSupported {
135		registrations = append(registrations, protocol.Registration{
136			ID:     "workspace/didChangeWatchedFiles",
137			Method: "workspace/didChangeWatchedFiles",
138			RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{
139				Watchers: []protocol.FileSystemWatcher{{
140					GlobPattern: "**/*.go",
141					Kind:        float64(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate),
142				}},
143			},
144		})
145	}
146
147	if len(registrations) > 0 {
148		s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
149			Registrations: registrations,
150		})
151	}
152
153	buf := &bytes.Buffer{}
154	debug.PrintVersionInfo(buf, true, debug.PlainText)
155	log.Print(ctx, buf.String())
156
157	s.addFolders(ctx, s.pendingFolders)
158	s.pendingFolders = nil
159
160	return nil
161}
162
163func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) {
164	originalViews := len(s.session.Views())
165	viewErrors := make(map[span.URI]error)
166
167	for _, folder := range folders {
168		uri := span.URIFromURI(folder.URI)
169		_, snapshot, err := s.addView(ctx, folder.Name, uri)
170		if err != nil {
171			viewErrors[uri] = err
172			continue
173		}
174		go s.diagnoseDetached(snapshot)
175	}
176	if len(viewErrors) > 0 {
177		errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(folders), len(s.session.Views())-originalViews)
178		for uri, err := range viewErrors {
179			errMsg += fmt.Sprintf("failed to load view for %s: %v\n", uri, err)
180		}
181		s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
182			Type:    protocol.Error,
183			Message: errMsg,
184		})
185	}
186}
187
188func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, o *source.Options) error {
189	if !s.session.Options().ConfigurationSupported {
190		return nil
191	}
192	v := protocol.ParamConfiguration{
193		ConfigurationParams: protocol.ConfigurationParams{
194			Items: []protocol.ConfigurationItem{{
195				ScopeURI: string(folder),
196				Section:  "gopls",
197			}, {
198				ScopeURI: string(folder),
199				Section:  fmt.Sprintf("gopls-%s", name),
200			}},
201		},
202	}
203	configs, err := s.client.Configuration(ctx, &v)
204	if err != nil {
205		return err
206	}
207	for _, config := range configs {
208		results := source.SetOptions(o, config)
209		for _, result := range results {
210			if result.Error != nil {
211				s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
212					Type:    protocol.Error,
213					Message: result.Error.Error(),
214				})
215			}
216			switch result.State {
217			case source.OptionUnexpected:
218				s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
219					Type:    protocol.Error,
220					Message: fmt.Sprintf("unexpected config %s", result.Name),
221				})
222			case source.OptionDeprecated:
223				msg := fmt.Sprintf("config %s is deprecated", result.Name)
224				if result.Replacement != "" {
225					msg = fmt.Sprintf("%s, use %s instead", msg, result.Replacement)
226				}
227				s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
228					Type:    protocol.Warning,
229					Message: msg,
230				})
231			}
232		}
233	}
234	return nil
235}
236
237// beginFileRequest checks preconditions for a file-oriented request and routes
238// it to a snapshot.
239// We don't want to return errors for benign conditions like wrong file type,
240// so callers should do if !ok { return err } rather than if err != nil.
241func (s *Server) beginFileRequest(pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.FileHandle, bool, error) {
242	uri := pURI.SpanURI()
243	if !uri.IsFile() {
244		// Not a file URI. Stop processing the request, but don't return an error.
245		return nil, nil, false, nil
246	}
247	view, err := s.session.ViewOf(uri)
248	if err != nil {
249		return nil, nil, false, err
250	}
251	snapshot := view.Snapshot()
252	fh, err := snapshot.GetFile(uri)
253	if err != nil {
254		return nil, nil, false, err
255	}
256	if expectKind != source.UnknownKind && fh.Identity().Kind != expectKind {
257		// Wrong kind of file. Nothing to do.
258		return nil, nil, false, nil
259	}
260	return snapshot, fh, true, nil
261}
262
263func (s *Server) shutdown(ctx context.Context) error {
264	s.stateMu.Lock()
265	defer s.stateMu.Unlock()
266	if s.state < serverInitialized {
267		return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not initialized")
268	}
269	// drop all the active views
270	s.session.Shutdown(ctx)
271	s.state = serverShutDown
272	return nil
273}
274
275func (s *Server) exit(ctx context.Context) error {
276	s.stateMu.Lock()
277	defer s.stateMu.Unlock()
278	if s.state != serverShutDown {
279		os.Exit(1)
280	}
281	os.Exit(0)
282	return nil
283}
284
285func setBool(b *bool, m map[string]interface{}, name string) {
286	if v, ok := m[name].(bool); ok {
287		*b = v
288	}
289}
290
291func setNotBool(b *bool, m map[string]interface{}, name string) {
292	if v, ok := m[name].(bool); ok {
293		*b = !v
294	}
295}
296