this repo has no description
at master 172 lines 6.1 kB view raw
1// Copyright 2025 CUE Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package server 16 17import ( 18 "context" 19 "fmt" 20 "path" 21 22 "cuelang.org/go/internal/cueversion" 23 "cuelang.org/go/internal/golangorgx/gopls/progress" 24 "cuelang.org/go/internal/golangorgx/gopls/protocol" 25 "cuelang.org/go/internal/golangorgx/gopls/settings" 26 "cuelang.org/go/internal/golangorgx/tools/event" 27 "cuelang.org/go/internal/golangorgx/tools/jsonrpc2" 28 "cuelang.org/go/internal/lsp/cache" 29) 30 31func validateWorkspaceFolders(folders []protocol.WorkspaceFolder) (map[protocol.WorkspaceFolder]protocol.DocumentURI, error) { 32 withParsedUri := make(map[protocol.WorkspaceFolder]protocol.DocumentURI) 33 for _, folder := range folders { 34 if folder.URI == "" { 35 return nil, fmt.Errorf("empty WorkspaceFolder.URI") 36 } 37 uri, err := protocol.ParseDocumentURI(folder.URI) 38 if err != nil { 39 return nil, fmt.Errorf("invalid WorkspaceFolder.URI: %v", err) 40 } 41 withParsedUri[folder] = uri 42 } 43 return withParsedUri, nil 44} 45 46// Initialize is a request from the editor/client to initialize the 47// workspace. It gets a response. Once the response is sent, the 48// client needs to send an Initialized async message before any work 49// starts. 50// 51// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize 52func (s *server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { 53 ctx, done := event.Start(ctx, "lsp.Server.initialize") 54 defer done() 55 56 if s.state != serverCreated { 57 return nil, fmt.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state) 58 } 59 s.state = serverInitializing 60 61 // TODO(myitcv): need to better understand events, and the mechanisms that 62 // hang off that. At least for now we know that the integration expectation 63 // setup relies on the progress messaging titles to track things being done. 64 s.progress = progress.NewTracker(s.client, params.Capabilities.Window.WorkDoneProgress) 65 66 // Clone the existing (default?) options, and update from the params. 67 options := s.Options().Clone() 68 69 // TODO(myitcv): review and slim down option handling code 70 if err := s.handleOptionResults(ctx, settings.SetOptions(options, params.InitializationOptions)); err != nil { 71 return nil, err 72 } 73 options.ForClientCapabilities(params.ClientInfo, params.Capabilities) 74 s.SetOptions(options) 75 76 folders := params.WorkspaceFolders 77 if len(folders) == 0 && params.RootURI != "" { 78 folders = []protocol.WorkspaceFolder{{ 79 URI: string(params.RootURI), 80 Name: path.Base(params.RootURI.Path()), 81 }} 82 } 83 84 validFolders, err := validateWorkspaceFolders(folders) 85 if err != nil { 86 return nil, err 87 } 88 s.eventuallyUseWorkspaceFolders(validFolders) 89 90 var renameOpts any = true 91 if r := params.Capabilities.TextDocument.Rename; r != nil && r.PrepareSupport { 92 renameOpts = protocol.RenameOptions{ 93 PrepareProvider: r.PrepareSupport, 94 } 95 } 96 97 return &protocol.InitializeResult{ 98 ServerInfo: &protocol.ServerInfo{ 99 Name: "cuelsp", 100 Version: cueversion.ModuleVersion(), 101 }, 102 103 Capabilities: protocol.ServerCapabilities{ 104 CompletionProvider: &protocol.CompletionOptions{ 105 TriggerCharacters: []string{"."}, 106 }, 107 DefinitionProvider: &protocol.Or_ServerCapabilities_definitionProvider{Value: true}, 108 DocumentFormattingProvider: &protocol.Or_ServerCapabilities_documentFormattingProvider{Value: true}, 109 DocumentSymbolProvider: &protocol.Or_ServerCapabilities_documentSymbolProvider{Value: true}, 110 HoverProvider: &protocol.Or_ServerCapabilities_hoverProvider{Value: true}, 111 ReferencesProvider: &protocol.Or_ServerCapabilities_referencesProvider{Value: true}, 112 RenameProvider: renameOpts, 113 TextDocumentSync: &protocol.TextDocumentSyncOptions{ 114 Change: protocol.Incremental, 115 OpenClose: true, 116 Save: &protocol.SaveOptions{ 117 IncludeText: false, 118 }, 119 }, 120 121 Workspace: &protocol.WorkspaceOptions{ 122 WorkspaceFolders: &protocol.WorkspaceFolders5Gn{ 123 Supported: true, 124 ChangeNotifications: "workspace/didChangeWorkspaceFolders", 125 }, 126 }, 127 }, 128 }, nil 129} 130 131// Initialized is the handler for the async message from the 132// client. The client should send this only after it's received our 133// InitializeResult message. 134// 135// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialized 136func (s *server) Initialized(ctx context.Context, params *protocol.InitializedParams) error { 137 ctx, done := event.Start(ctx, "lsp.Server.initialized") 138 defer done() 139 140 if s.state != serverInitializing { 141 return fmt.Errorf("%w: initialized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state) 142 } 143 s.state = serverInitialized 144 145 err := s.maybeShowPendingMessages(ctx) 146 if err != nil { 147 return err 148 } 149 150 options := s.Options() 151 if options.ConfigurationSupported && options.DynamicConfigurationSupported { 152 err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{ 153 Registrations: []protocol.Registration{{ 154 ID: "workspace/didChangeConfiguration", 155 Method: "workspace/didChangeConfiguration", 156 }}, 157 }) 158 if err != nil { 159 return err 160 } 161 } 162 163 s.workspace = cache.NewWorkspace(s.cache, s.client, s.debugLog) 164 165 err = s.maybeUseWorkspaceFolders(ctx) 166 // Initialized is a notification, so if there's an error, we show 167 // it rather than return it. 168 if err != nil { 169 s.client.ShowMessage(ctx, &protocol.ShowMessageParams{Type: protocol.Error, Message: err.Error()}) 170 } 171 return nil 172}