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