@@ -62,6 +62,11 @@ type Client struct {
63 // Server state
63 // Server state
64 serverState atomic.Value
64 serverState atomic.Value
66+ // Server capabilities as returned by initialize
67+ caps protocol.ServerCapabilities
67// NewClient creates a new LSP client.
72// NewClient creates a new LSP client.
@@ -143,7 +148,7 @@ func (c *Client) RegisterServerRequestHandler(method string, handler ServerReque
145func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (*protocol.InitializeResult, error) {
150func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (*protocol.InitializeResult, error) {
146− initParams := &protocol.InitializeParams{
151+ initParams := protocol.ParamInitialize{
147 WorkspaceFoldersInitializeParams: protocol.WorkspaceFoldersInitializeParams{
152 WorkspaceFoldersInitializeParams: protocol.WorkspaceFoldersInitializeParams{
148 WorkspaceFolders: []protocol.WorkspaceFolder{
153 WorkspaceFolders: []protocol.WorkspaceFolder{
@@ -220,12 +225,14 @@ func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (
223− var result protocol.InitializeResult
224− if err := c.Call(ctx, "initialize", initParams, &result); err != nil {
228+ result, err := c.Initialize(ctx, initParams)
225 return nil, fmt.Errorf("initialize failed: %w", err)
230 return nil, fmt.Errorf("initialize failed: %w", err)
228− if err := c.Notify(ctx, "initialized", struct{}{}); err != nil {
233+ c.setCapabilities(result.Capabilities)
235+ if err := c.Initialized(ctx, protocol.InitializedParams{}); err != nil {
229 return nil, fmt.Errorf("initialized notification failed: %w", err)
236 return nil, fmt.Errorf("initialized notification failed: %w", err)
@@ -234,14 +241,9 @@ func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (
234 c.RegisterServerRequestHandler("workspace/configuration", HandleWorkspaceConfiguration)
241 c.RegisterServerRequestHandler("workspace/configuration", HandleWorkspaceConfiguration)
235 c.RegisterServerRequestHandler("client/registerCapability", HandleRegisterCapability)
242 c.RegisterServerRequestHandler("client/registerCapability", HandleRegisterCapability)
236 c.RegisterNotificationHandler("window/showMessage", HandleServerMessage)
243 c.RegisterNotificationHandler("window/showMessage", HandleServerMessage)
237− c.RegisterNotificationHandler("textDocument/publishDiagnostics",
244+ c.RegisterNotificationHandler("textDocument/publishDiagnostics", func(params json.RawMessage) {
238− func(params json.RawMessage) { HandleDiagnostics(c, params) })
245+ HandleDiagnostics(c, params)
240− // Notify the LSP server
241− err := c.Initialized(ctx, protocol.InitializedParams{})
243− return nil, fmt.Errorf("initialization failed: %w", err)
246 return &result, nil
248 return &result, nil
@@ -329,16 +331,7 @@ func (c *Client) WaitForServerReady(ctx context.Context) error {
329 slog.Debug("Waiting for LSP server to be ready...")
331 slog.Debug("Waiting for LSP server to be ready...")
332− // Determine server type for specialized initialization
333− serverType := c.detectServerType()
335− // For TypeScript-like servers, we need to open some key files first
336− if serverType == ServerTypeTypeScript {
337− if cfg.Options.DebugLSP {
338− slog.Debug("TypeScript-like server detected, opening key configuration files")
340− c.openKeyConfigFiles(ctx)
334+ c.openKeyConfigFiles(ctx)
@@ -347,21 +340,19 @@ func (c *Client) WaitForServerReady(ctx context.Context) error {
347 return fmt.Errorf("timeout waiting for LSP server to be ready")
340 return fmt.Errorf("timeout waiting for LSP server to be ready")
348 case <-ticker.C:
341 case <-ticker.C:
349 // Try a ping method appropriate for this server type
342 // Try a ping method appropriate for this server type
350− err := c.pingServerByType(ctx, serverType)
351− if err == nil {
343+ if err := c.ping(ctx); err != nil {
352− // Server responded successfully
353− c.SetServerState(StateReady)
354 if cfg.Options.DebugLSP {
344 if cfg.Options.DebugLSP {
355− slog.Debug("LSP server is ready")
345+ slog.Debug("LSP server not ready yet", "error", err, "server", c.name)
357− return nil
347+ continue
359− slog.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
350+ // Server responded successfully
351+ c.SetServerState(StateReady)
362 if cfg.Options.DebugLSP {
352 if cfg.Options.DebugLSP {
363− slog.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
353+ slog.Debug("LSP server is ready")
@@ -443,86 +434,13 @@ func (c *Client) openKeyConfigFiles(ctx context.Context) {
446−// pingServerByType sends a ping request appropriate for the server type
437+// ping sends a ping request...
447−func (c *Client) pingServerByType(ctx context.Context, serverType ServerType) error {
449− case ServerTypeTypeScript:
450− // For TypeScript, try a document symbol request on an open file
451− return c.pingTypeScriptServer(ctx)
453− // For Go, workspace/symbol works well
454− return c.pingWithWorkspaceSymbol(ctx)
455− case ServerTypeRust:
456− // For Rust, workspace/symbol works well
457− return c.pingWithWorkspaceSymbol(ctx)
459− // Default ping method
460− return c.pingWithWorkspaceSymbol(ctx)
464−// pingTypeScriptServer tries to ping a TypeScript server with appropriate methods
465−func (c *Client) pingTypeScriptServer(ctx context.Context) error {
438+func (c *Client) ping(ctx context.Context) error {
466− // First try workspace/symbol which works for many servers
439+ if _, err := c.Symbol(ctx, protocol.WorkspaceSymbolParams{}); err == nil {
467− if err := c.pingWithWorkspaceSymbol(ctx); err == nil {
471− // If that fails, try to find an open file and request document symbols
472− c.openFilesMu.RLock()
473− defer c.openFilesMu.RUnlock()
475− // If we have any open files, try to get document symbols for one
476− for uri := range c.openFiles {
477− filePath, err := protocol.DocumentURI(uri).Path()
479− slog.Error("Failed to convert URI to path for TypeScript symbol collection", "uri", uri, "error", err)
483− if strings.HasSuffix(filePath, ".ts") || strings.HasSuffix(filePath, ".js") ||
484− strings.HasSuffix(filePath, ".tsx") || strings.HasSuffix(filePath, ".jsx") {
485− var symbols []protocol.DocumentSymbol
486− err := c.Call(ctx, "textDocument/documentSymbol", protocol.DocumentSymbolParams{
487− TextDocument: protocol.TextDocumentIdentifier{
488− URI: protocol.DocumentURI(uri),
497− // If we have no open TypeScript files, try to find and open one
498− workDir := config.Get().WorkingDir()
499− err := filepath.WalkDir(workDir, func(path string, d os.DirEntry, err error) error {
504− // Skip directories and non-TypeScript files
509− ext := filepath.Ext(path)
510− if ext == ".ts" || ext == ".js" || ext == ".tsx" || ext == ".jsx" {
511− // Found a TypeScript file, try to open it
512− if err := c.OpenFile(ctx, path); err == nil {
513− // Successfully opened, stop walking
514− return filepath.SkipAll
518 return nil
440 return nil
521− slog.Debug("Error walking directory for TypeScript files", "error", err)
523−
442+ // This is a very lightweight request that should work for most servers
524− // Final fallback - just try a generic capability
443+ return c.Notify(ctx, "$/cancelRequest", protocol.CancelParams{ID: "1"})
525− return c.pingWithServerCapabilities(ctx)
528// openTypeScriptFiles finds and opens TypeScript files to help initialize the server
446// openTypeScriptFiles finds and opens TypeScript files to help initialize the server
@@ -597,20 +515,6 @@ func shouldSkipDir(path string) bool {
597 return skipDirs[dirName]
515 return skipDirs[dirName]
600−// pingWithWorkspaceSymbol tries a workspace/symbol request
601−func (c *Client) pingWithWorkspaceSymbol(ctx context.Context) error {
602− var result []protocol.SymbolInformation
603− return c.Call(ctx, "workspace/symbol", protocol.WorkspaceSymbolParams{
608−// pingWithServerCapabilities tries to get server capabilities
609−func (c *Client) pingWithServerCapabilities(ctx context.Context) error {
610− // This is a very lightweight request that should work for most servers
611− return c.Notify(ctx, "$/cancelRequest", struct{ ID int }{ID: -1})
614type OpenFileInfo struct {
518type OpenFileInfo struct {
615 Version int32
519 Version int32
616 URI protocol.DocumentURI
520 URI protocol.DocumentURI
@@ -668,7 +572,7 @@ func (c *Client) OpenFile(ctx context.Context, filepath string) error {
671− if err := c.Notify(ctx, "textDocument/didOpen", params); err != nil {
575+ if err := c.DidOpen(ctx, params); err != nil {
672 return err
576 return err
@@ -718,7 +622,7 @@ func (c *Client) NotifyChange(ctx context.Context, filepath string) error {
721− return c.Notify(ctx, "textDocument/didChange", params)
625+ return c.DidChange(ctx, params)
724func (c *Client) CloseFile(ctx context.Context, filepath string) error {
628func (c *Client) CloseFile(ctx context.Context, filepath string) error {
@@ -741,7 +645,7 @@ func (c *Client) CloseFile(ctx context.Context, filepath string) error {
741 if cfg.Options.DebugLSP {
645 if cfg.Options.DebugLSP {
742 slog.Debug("Closing file", "file", filepath)
646 slog.Debug("Closing file", "file", filepath)
744− if err := c.Notify(ctx, "textDocument/didClose", params); err != nil {
648+ if err := c.DidClose(ctx, params); err != nil {
745 return err
649 return err