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:  params.RootURI,
46				Name: path.Base(params.RootURI),
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.NewURI(folder.URI)
169		_, snapshot, err := s.addView(ctx, folder.Name, span.NewURI(folder.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: protocol.NewURI(folder),
196				Section:  "gopls",
197			}, {
198				ScopeURI: protocol.NewURI(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
237func (s *Server) shutdown(ctx context.Context) error {
238	s.stateMu.Lock()
239	defer s.stateMu.Unlock()
240	if s.state < serverInitialized {
241		return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not initialized")
242	}
243	// drop all the active views
244	s.session.Shutdown(ctx)
245	s.state = serverShutDown
246	return nil
247}
248
249func (s *Server) exit(ctx context.Context) error {
250	s.stateMu.Lock()
251	defer s.stateMu.Unlock()
252	if s.state != serverShutDown {
253		os.Exit(1)
254	}
255	os.Exit(0)
256	return nil
257}
258
259func setBool(b *bool, m map[string]interface{}, name string) {
260	if v, ok := m[name].(bool); ok {
261		*b = v
262	}
263}
264
265func setNotBool(b *bool, m map[string]interface{}, name string) {
266	if v, ok := m[name].(bool); ok {
267		*b = !v
268	}
269}
270