diff --git a/internal/app/lsp.go b/internal/app/lsp.go index afe76a68460d262a3f57f214ad3c0c153ddbd807..eeb00a365d32d8f4079788f2acdfd1a47e119176 100644 --- a/internal/app/lsp.go +++ b/internal/app/lsp.go @@ -23,7 +23,7 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman slog.Info("Creating LSP client", "name", name, "command", command, "args", args) // Create LSP client. - lspClient, err := lsp.NewClient(ctx, command, args...) + lspClient, err := lsp.NewClient(ctx, app.config.WorkingDir(), command, args...) if err != nil { slog.Error("Failed to create LSP client for", name, err) return @@ -60,7 +60,7 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman watchCtx, cancelFunc := context.WithCancel(ctx) // Create the workspace watcher. - workspaceWatcher := watcher.NewWorkspaceWatcher(name, lspClient) + workspaceWatcher := watcher.NewWorkspaceWatcher(name, lspClient, app.config.Options.DebugLSP) // Store the cancel function to be called during cleanup. app.watcherCancelFuncs.Append(cancelFunc) diff --git a/internal/llm/tools/diagnostics.go b/internal/llm/tools/diagnostics.go index fc9bd211735afd4d1f8a536a90d5705d88bd9790..214e966503a91e67d9e3cb042caac57bd7c449ba 100644 --- a/internal/llm/tools/diagnostics.go +++ b/internal/llm/tools/diagnostics.go @@ -113,7 +113,7 @@ func waitForLspDiagnostics(ctx context.Context, filePath string, lsps map[string maps.Copy(originalDiags, client.GetDiagnostics()) handler := func(params json.RawMessage) { - lsp.HandleDiagnostics(client, params) + client.HandleDiagnostics(params) var diagParams protocol.PublishDiagnosticsParams if err := json.Unmarshal(params, &diagParams); err != nil { return diff --git a/internal/lsp/client.go b/internal/lsp/client.go index 219a5df5fb87197f0490f218cddc24ab3b138371..1c35693e7fa5604067dd0fd45518cd3aed76e83a 100644 --- a/internal/lsp/client.go +++ b/internal/lsp/client.go @@ -3,7 +3,6 @@ package lsp import ( "bufio" "context" - "encoding/json" "fmt" "io" "log/slog" @@ -15,16 +14,17 @@ import ( "sync/atomic" "time" - "github.com/charmbracelet/crush/internal/config" "github.com/charmbracelet/crush/internal/log" "github.com/charmbracelet/crush/internal/lsp/protocol" ) type Client struct { - Cmd *exec.Cmd - stdin io.WriteCloser - stdout *bufio.Reader - stderr io.ReadCloser + Cmd *exec.Cmd + stdin io.WriteCloser + stdout *bufio.Reader + stderr io.ReadCloser + workDir string + debug bool // Request ID counter nextID atomic.Int32 @@ -53,7 +53,7 @@ type Client struct { serverState atomic.Value } -func NewClient(ctx context.Context, command string, args ...string) (*Client, error) { +func NewClient(ctx context.Context, workDir string, command string, args ...string) (*Client, error) { cmd := exec.CommandContext(ctx, command, args...) // Copy env cmd.Env = os.Environ() @@ -78,6 +78,7 @@ func NewClient(ctx context.Context, command string, args ...string) (*Client, er stdin: stdin, stdout: bufio.NewReader(stdout), stderr: stderr, + workDir: workDir, handlers: make(map[int32]chan *Message), notificationHandlers: make(map[string]NotificationHandler), serverRequestHandlers: make(map[string]ServerRequestHandler), @@ -215,12 +216,11 @@ func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) ( } // Register handlers - c.RegisterServerRequestHandler("workspace/applyEdit", HandleApplyEdit) - c.RegisterServerRequestHandler("workspace/configuration", HandleWorkspaceConfiguration) - c.RegisterServerRequestHandler("client/registerCapability", HandleRegisterCapability) - c.RegisterNotificationHandler("window/showMessage", HandleServerMessage) - c.RegisterNotificationHandler("textDocument/publishDiagnostics", - func(params json.RawMessage) { HandleDiagnostics(c, params) }) + c.RegisterServerRequestHandler("workspace/applyEdit", c.HandleApplyEdit) + c.RegisterServerRequestHandler("workspace/configuration", c.HandleWorkspaceConfiguration) + c.RegisterServerRequestHandler("client/registerCapability", c.HandleRegisterCapability) + c.RegisterNotificationHandler("window/showMessage", c.HandleServerMessage) + c.RegisterNotificationHandler("textDocument/publishDiagnostics", c.HandleDiagnostics) // Notify the LSP server err := c.Initialized(ctx, protocol.InitializedParams{}) @@ -287,8 +287,6 @@ func (c *Client) SetServerState(state ServerState) { // WaitForServerReady waits for the server to be ready by polling the server // with a simple request until it responds successfully or times out func (c *Client) WaitForServerReady(ctx context.Context) error { - cfg := config.Get() - // Set initial state c.SetServerState(StateStarting) @@ -300,7 +298,7 @@ func (c *Client) WaitForServerReady(ctx context.Context) error { ticker := time.NewTicker(500 * time.Millisecond) defer ticker.Stop() - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Waiting for LSP server to be ready...") } @@ -309,7 +307,7 @@ func (c *Client) WaitForServerReady(ctx context.Context) error { // For TypeScript-like servers, we need to open some key files first if serverType == ServerTypeTypeScript { - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("TypeScript-like server detected, opening key configuration files") } c.openKeyConfigFiles(ctx) @@ -326,7 +324,7 @@ func (c *Client) WaitForServerReady(ctx context.Context) error { if err == nil { // Server responded successfully c.SetServerState(StateReady) - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("LSP server is ready") } return nil @@ -334,7 +332,7 @@ func (c *Client) WaitForServerReady(ctx context.Context) error { slog.Debug("LSP server not ready yet", "error", err, "serverType", serverType) } - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("LSP server not ready yet", "error", err, "serverType", serverType) } } @@ -377,7 +375,6 @@ func (c *Client) detectServerType() ServerType { // openKeyConfigFiles opens important configuration files that help initialize the server func (c *Client) openKeyConfigFiles(ctx context.Context) { - workDir := config.Get().WorkingDir() serverType := c.detectServerType() var filesToOpen []string @@ -386,22 +383,22 @@ func (c *Client) openKeyConfigFiles(ctx context.Context) { case ServerTypeTypeScript: // TypeScript servers need these config files to properly initialize filesToOpen = []string{ - filepath.Join(workDir, "tsconfig.json"), - filepath.Join(workDir, "package.json"), - filepath.Join(workDir, "jsconfig.json"), + filepath.Join(c.workDir, "tsconfig.json"), + filepath.Join(c.workDir, "package.json"), + filepath.Join(c.workDir, "jsconfig.json"), } // Also find and open a few TypeScript files to help the server initialize - c.openTypeScriptFiles(ctx, workDir) + c.openTypeScriptFiles(ctx, c.workDir) case ServerTypeGo: filesToOpen = []string{ - filepath.Join(workDir, "go.mod"), - filepath.Join(workDir, "go.sum"), + filepath.Join(c.workDir, "go.mod"), + filepath.Join(c.workDir, "go.sum"), } case ServerTypeRust: filesToOpen = []string{ - filepath.Join(workDir, "Cargo.toml"), - filepath.Join(workDir, "Cargo.lock"), + filepath.Join(c.workDir, "Cargo.toml"), + filepath.Join(c.workDir, "Cargo.lock"), } } @@ -470,8 +467,7 @@ func (c *Client) pingTypeScriptServer(ctx context.Context) error { } // If we have no open TypeScript files, try to find and open one - workDir := config.Get().WorkingDir() - err := filepath.WalkDir(workDir, func(path string, d os.DirEntry, err error) error { + err := filepath.WalkDir(c.workDir, func(path string, d os.DirEntry, err error) error { if err != nil { return err } @@ -502,7 +498,6 @@ func (c *Client) pingTypeScriptServer(ctx context.Context) error { // openTypeScriptFiles finds and opens TypeScript files to help initialize the server func (c *Client) openTypeScriptFiles(ctx context.Context, workDir string) { - cfg := config.Get() filesOpened := 0 maxFilesToOpen := 5 // Limit to a reasonable number of files @@ -532,7 +527,7 @@ func (c *Client) openTypeScriptFiles(ctx context.Context, workDir string) { // Try to open the file if err := c.OpenFile(ctx, path); err == nil { filesOpened++ - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Opened TypeScript file for initialization", "file", path) } } @@ -541,11 +536,11 @@ func (c *Client) openTypeScriptFiles(ctx context.Context, workDir string) { return nil }) - if err != nil && cfg.Options.DebugLSP { + if err != nil && c.debug { slog.Debug("Error walking directory for TypeScript files", "error", err) } - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Opened TypeScript files for initialization", "count", filesOpened) } } @@ -670,7 +665,6 @@ func (c *Client) NotifyChange(ctx context.Context, filepath string) error { } func (c *Client) CloseFile(ctx context.Context, filepath string) error { - cfg := config.Get() uri := string(protocol.URIFromPath(filepath)) c.openFilesMu.Lock() @@ -686,7 +680,7 @@ func (c *Client) CloseFile(ctx context.Context, filepath string) error { }, } - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Closing file", "file", filepath) } if err := c.Notify(ctx, "textDocument/didClose", params); err != nil { @@ -710,7 +704,6 @@ func (c *Client) IsFileOpen(filepath string) bool { // CloseAllFiles closes all currently open files func (c *Client) CloseAllFiles(ctx context.Context) { - cfg := config.Get() c.openFilesMu.Lock() filesToClose := make([]string, 0, len(c.openFiles)) @@ -729,12 +722,12 @@ func (c *Client) CloseAllFiles(ctx context.Context) { // Then close them all for _, filePath := range filesToClose { err := c.CloseFile(ctx, filePath) - if err != nil && cfg.Options.DebugLSP { + if err != nil && c.debug { slog.Warn("Error closing file", "file", filePath, "error", err) } } - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Closed all files", "files", filesToClose) } } @@ -792,3 +785,7 @@ func (c *Client) ClearDiagnosticsForURI(uri protocol.DocumentURI) { defer c.diagnosticsMu.Unlock() delete(c.diagnostics, uri) } + +func (c *Client) DebugMode() { + c.debug = true +} diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index 725d3c3c77ffba465b3e644a9948a1ce56c3eeaa..e75d208ef8d91c0a1105c80597cc9724fb81b980 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -4,19 +4,17 @@ import ( "encoding/json" "log/slog" - "github.com/charmbracelet/crush/internal/config" - "github.com/charmbracelet/crush/internal/lsp/protocol" "github.com/charmbracelet/crush/internal/lsp/util" ) // Requests -func HandleWorkspaceConfiguration(params json.RawMessage) (any, error) { +func (c *Client) HandleWorkspaceConfiguration(params json.RawMessage) (any, error) { return []map[string]any{{}}, nil } -func HandleRegisterCapability(params json.RawMessage) (any, error) { +func (c *Client) HandleRegisterCapability(params json.RawMessage) (any, error) { var registerParams protocol.RegistrationParams if err := json.Unmarshal(params, ®isterParams); err != nil { slog.Error("Error unmarshaling registration params", "error", err) @@ -47,7 +45,7 @@ func HandleRegisterCapability(params json.RawMessage) (any, error) { return nil, nil } -func HandleApplyEdit(params json.RawMessage) (any, error) { +func (c *Client) HandleApplyEdit(params json.RawMessage) (any, error) { var edit protocol.ApplyWorkspaceEditParams if err := json.Unmarshal(params, &edit); err != nil { return nil, err @@ -82,28 +80,27 @@ func notifyFileWatchRegistration(id string, watchers []protocol.FileSystemWatche // Notifications -func HandleServerMessage(params json.RawMessage) { - cfg := config.Get() +func (c *Client) HandleServerMessage(params json.RawMessage) { var msg struct { Type int `json:"type"` Message string `json:"message"` } if err := json.Unmarshal(params, &msg); err == nil { - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Server message", "type", msg.Type, "message", msg.Message) } } } -func HandleDiagnostics(client *Client, params json.RawMessage) { +func (c *Client) HandleDiagnostics(params json.RawMessage) { var diagParams protocol.PublishDiagnosticsParams if err := json.Unmarshal(params, &diagParams); err != nil { slog.Error("Error unmarshaling diagnostics params", "error", err) return } - client.diagnosticsMu.Lock() - defer client.diagnosticsMu.Unlock() + c.diagnosticsMu.Lock() + defer c.diagnosticsMu.Unlock() - client.diagnostics[diagParams.URI] = diagParams.Diagnostics + c.diagnostics[diagParams.URI] = diagParams.Diagnostics } diff --git a/internal/lsp/transport.go b/internal/lsp/transport.go index b468101dbc36537c9f306399b4af6cbbe451d96f..314aba97d41448073fe7a5c74502ca32936bd96d 100644 --- a/internal/lsp/transport.go +++ b/internal/lsp/transport.go @@ -8,19 +8,16 @@ import ( "io" "log/slog" "strings" - - "github.com/charmbracelet/crush/internal/config" ) // WriteMessage writes an LSP message to the given writer -func WriteMessage(w io.Writer, msg *Message) error { +func WriteMessage(w io.Writer, msg *Message, debug bool) error { data, err := json.Marshal(msg) if err != nil { return fmt.Errorf("failed to marshal message: %w", err) } - cfg := config.Get() - if cfg.Options.DebugLSP { + if debug { slog.Debug("Sending message to server", "method", msg.Method, "id", msg.ID) } @@ -38,8 +35,7 @@ func WriteMessage(w io.Writer, msg *Message) error { } // ReadMessage reads a single LSP message from the given reader -func ReadMessage(r *bufio.Reader) (*Message, error) { - cfg := config.Get() +func ReadMessage(r *bufio.Reader, debug bool) (*Message, error) { // Read headers var contentLength int for { @@ -49,7 +45,7 @@ func ReadMessage(r *bufio.Reader) (*Message, error) { } line = strings.TrimSpace(line) - if cfg.Options.DebugLSP { + if debug { slog.Debug("Received header", "line", line) } @@ -65,7 +61,7 @@ func ReadMessage(r *bufio.Reader) (*Message, error) { } } - if cfg.Options.DebugLSP { + if debug { slog.Debug("Content-Length", "length", contentLength) } @@ -76,7 +72,7 @@ func ReadMessage(r *bufio.Reader) (*Message, error) { return nil, fmt.Errorf("failed to read content: %w", err) } - if cfg.Options.DebugLSP { + if debug { slog.Debug("Received content", "content", string(content)) } @@ -91,11 +87,10 @@ func ReadMessage(r *bufio.Reader) (*Message, error) { // handleMessages reads and dispatches messages in a loop func (c *Client) handleMessages() { - cfg := config.Get() for { - msg, err := ReadMessage(c.stdout) + msg, err := ReadMessage(c.stdout, c.debug) if err != nil { - if cfg.Options.DebugLSP { + if c.debug { slog.Error("Error reading message", "error", err) } return @@ -103,7 +98,7 @@ func (c *Client) handleMessages() { // Handle server->client request (has both Method and ID) if msg.Method != "" && msg.ID != 0 { - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Received request from server", "method", msg.Method, "id", msg.ID) } @@ -143,7 +138,7 @@ func (c *Client) handleMessages() { } // Send response back to server - if err := WriteMessage(c.stdin, response); err != nil { + if err := WriteMessage(c.stdin, response, c.debug); err != nil { slog.Error("Error sending response to server", "error", err) } @@ -157,11 +152,11 @@ func (c *Client) handleMessages() { c.notificationMu.RUnlock() if ok { - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Handling notification", "method", msg.Method) } go handler(msg.Params) - } else if cfg.Options.DebugLSP { + } else if c.debug { slog.Debug("No handler for notification", "method", msg.Method) } continue @@ -174,12 +169,12 @@ func (c *Client) handleMessages() { c.handlersMu.RUnlock() if ok { - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Received response for request", "id", msg.ID) } ch <- msg close(ch) - } else if cfg.Options.DebugLSP { + } else if c.debug { slog.Debug("No handler for response", "id", msg.ID) } } @@ -188,10 +183,9 @@ func (c *Client) handleMessages() { // Call makes a request and waits for the response func (c *Client) Call(ctx context.Context, method string, params any, result any) error { - cfg := config.Get() id := c.nextID.Add(1) - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Making call", "method", method, "id", id) } @@ -213,11 +207,11 @@ func (c *Client) Call(ctx context.Context, method string, params any, result any }() // Send request - if err := WriteMessage(c.stdin, msg); err != nil { + if err := WriteMessage(c.stdin, msg, c.debug); err != nil { return fmt.Errorf("failed to send request: %w", err) } - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Request sent", "method", method, "id", id) } @@ -226,7 +220,7 @@ func (c *Client) Call(ctx context.Context, method string, params any, result any case <-ctx.Done(): return ctx.Err() case resp := <-ch: - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Received response", "id", id) } @@ -252,8 +246,7 @@ func (c *Client) Call(ctx context.Context, method string, params any, result any // Notify sends a notification (a request without an ID that doesn't expect a response) func (c *Client) Notify(ctx context.Context, method string, params any) error { - cfg := config.Get() - if cfg.Options.DebugLSP { + if c.debug { slog.Debug("Sending notification", "method", method) } @@ -262,7 +255,7 @@ func (c *Client) Notify(ctx context.Context, method string, params any) error { return fmt.Errorf("failed to create notification: %w", err) } - if err := WriteMessage(c.stdin, msg); err != nil { + if err := WriteMessage(c.stdin, msg, c.debug); err != nil { return fmt.Errorf("failed to send notification: %w", err) } diff --git a/internal/lsp/watcher/watcher.go b/internal/lsp/watcher/watcher.go index 6173d6e18e046345cc097052f6a06ff44b3e1e61..7b06f5cbf87a119bcbacd1e84fd76dd85fe0129d 100644 --- a/internal/lsp/watcher/watcher.go +++ b/internal/lsp/watcher/watcher.go @@ -11,9 +11,8 @@ import ( "time" "github.com/bmatcuk/doublestar/v4" - "github.com/charmbracelet/crush/internal/config" - "github.com/charmbracelet/crush/internal/csync" + "github.com/charmbracelet/crush/internal/csync" "github.com/charmbracelet/crush/internal/lsp" "github.com/charmbracelet/crush/internal/lsp/protocol" "github.com/fsnotify/fsnotify" @@ -23,6 +22,7 @@ import ( type WorkspaceWatcher struct { client *lsp.Client name string + debug bool workspacePath string debounceTime time.Duration @@ -41,10 +41,11 @@ func init() { } // NewWorkspaceWatcher creates a new workspace watcher -func NewWorkspaceWatcher(name string, client *lsp.Client) *WorkspaceWatcher { +func NewWorkspaceWatcher(name string, client *lsp.Client, debub bool) *WorkspaceWatcher { return &WorkspaceWatcher{ name: name, client: client, + debug: debub, debounceTime: 300 * time.Millisecond, debounceMap: csync.NewMap[string, *time.Timer](), registrations: []protocol.FileSystemWatcher{}, @@ -53,8 +54,6 @@ func NewWorkspaceWatcher(name string, client *lsp.Client) *WorkspaceWatcher { // AddRegistrations adds file watchers to track func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watchers []protocol.FileSystemWatcher) { - cfg := config.Get() - slog.Debug("Adding file watcher registrations") w.registrationMu.Lock() defer w.registrationMu.Unlock() @@ -63,7 +62,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc w.registrations = append(w.registrations, watchers...) // Print detailed registration information for debugging - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Adding file watcher registrations", "id", id, "watchers", len(watchers), @@ -132,7 +131,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc highPriorityFilesOpened := w.openHighPriorityFiles(ctx, serverName) filesOpened += highPriorityFilesOpened - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Opened high-priority files", "count", highPriorityFilesOpened, "serverName", serverName) @@ -140,7 +139,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc // If we've already opened enough high-priority files, we might not need more if filesOpened >= maxFilesToOpen { - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Reached file limit with high-priority files", "filesOpened", filesOpened, "maxFiles", maxFilesToOpen) @@ -158,7 +157,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc // Skip directories that should be excluded if d.IsDir() { if path != w.workspacePath && shouldExcludeDir(path) { - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Skipping excluded directory", "path", path) } return filepath.SkipDir @@ -186,7 +185,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc }) elapsedTime := time.Since(startTime) - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Limited workspace scan complete", "filesOpened", filesOpened, "maxFiles", maxFilesToOpen, @@ -195,11 +194,11 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc ) } - if err != nil && cfg.Options.DebugLSP { + if err != nil && w.debug { slog.Debug("Error scanning workspace for files to open", "error", err) } }() - } else if cfg.Options.DebugLSP { + } else if w.debug { slog.Debug("Using on-demand file loading for server", "server", serverName) } } @@ -207,7 +206,6 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc // openHighPriorityFiles opens important files for the server type // Returns the number of files opened func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName string) int { - cfg := config.Get() filesOpened := 0 // Define patterns for high-priority files based on server type @@ -275,7 +273,7 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName // Use doublestar.Glob to find files matching the pattern (supports ** patterns) matches, err := doublestar.Glob(os.DirFS(w.workspacePath), pattern) if err != nil { - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Error finding high-priority files", "pattern", pattern, "error", err) } continue @@ -287,7 +285,7 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName // Skip directories and excluded files info, err := os.Stat(fullPath) - if err != nil || info.IsDir() || shouldExcludeFile(fullPath) { + if err != nil || info.IsDir() || shouldExcludeFile(fullPath, w.debug) { continue } @@ -309,12 +307,12 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName for j := i; j < end; j++ { fullPath := filesToOpen[j] if err := w.client.OpenFile(ctx, fullPath); err != nil { - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Error opening high-priority file", "path", fullPath, "error", err) } } else { filesOpened++ - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Opened high-priority file", "path", fullPath) } } @@ -331,7 +329,6 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName // WatchWorkspace sets up file watching for a workspace func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath string) { - cfg := config.Get() w.workspacePath = workspacePath slog.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", w.name) @@ -356,7 +353,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str // Skip excluded directories (except workspace root) if d.IsDir() && path != workspacePath { if shouldExcludeDir(path) { - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Skipping excluded directory", "path", path) } return filepath.SkipDir @@ -401,7 +398,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str } } else { // For newly created files - if !shouldExcludeFile(event.Name) { + if !shouldExcludeFile(event.Name, w.debug) { w.openMatchingFile(ctx, event.Name) } } @@ -409,7 +406,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str } // Debug logging - if cfg.Options.DebugLSP { + if w.debug { matched, kind := w.isPathWatched(event.Name) slog.Debug("File event", "path", event.Name, @@ -650,8 +647,6 @@ func (w *WorkspaceWatcher) debounceHandleFileEvent(ctx context.Context, uri stri // Create new timer w.debounceMap.Set(key, time.AfterFunc(w.debounceTime, func() { w.handleFileEvent(ctx, uri, changeType) - - // Cleanup timer after execution w.debounceMap.Del(key) })) } @@ -684,8 +679,7 @@ func (w *WorkspaceWatcher) handleFileEvent(ctx context.Context, uri string, chan // notifyFileEvent sends a didChangeWatchedFiles notification for a file event func (w *WorkspaceWatcher) notifyFileEvent(ctx context.Context, uri string, changeType protocol.FileChangeType) error { - cfg := config.Get() - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Notifying file event", "uri", uri, "changeType", changeType, @@ -798,9 +792,8 @@ func shouldExcludeDir(dirPath string) bool { } // shouldExcludeFile returns true if the file should be excluded from opening -func shouldExcludeFile(filePath string) bool { +func shouldExcludeFile(filePath string, debug bool) bool { fileName := filepath.Base(filePath) - cfg := config.Get() // Skip dot files if strings.HasPrefix(fileName, ".") { return true @@ -826,12 +819,11 @@ func shouldExcludeFile(filePath string) bool { // Skip large files if info.Size() > maxFileSize { - if cfg.Options.DebugLSP { + if debug { slog.Debug("Skipping large file", "path", filePath, "size", info.Size(), "maxSize", maxFileSize, - "debug", cfg.Options.Debug, "sizeMB", float64(info.Size())/(1024*1024), "maxSizeMB", float64(maxFileSize)/(1024*1024), ) @@ -844,7 +836,6 @@ func shouldExcludeFile(filePath string) bool { // openMatchingFile opens a file if it matches any of the registered patterns func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) { - cfg := config.Get() // Skip directories info, err := os.Stat(path) if err != nil || info.IsDir() { @@ -852,7 +843,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) { } // Skip excluded files - if shouldExcludeFile(path) { + if shouldExcludeFile(path, w.debug) { return } @@ -867,10 +858,10 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) { // Check if the file is a high-priority file that should be opened immediately // This helps with project initialization for certain language servers if isHighPriorityFile(path, serverName) { - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Opening high-priority file", "path", path, "serverName", serverName) } - if err := w.client.OpenFile(ctx, path); err != nil && cfg.Options.DebugLSP { + if err := w.client.OpenFile(ctx, path); err != nil && w.debug { slog.Error("Error opening high-priority file", "path", path, "error", err) } return @@ -884,7 +875,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) { // Check file size - for preloading we're more conservative if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files - if cfg.Options.DebugLSP { + if w.debug { slog.Debug("Skipping large file for preloading", "path", path, "size", info.Size()) } return @@ -912,7 +903,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) { if shouldOpen { // Don't need to check if it's already open - the client.OpenFile handles that - if err := w.client.OpenFile(ctx, path); err != nil && cfg.Options.DebugLSP { + if err := w.client.OpenFile(ctx, path); err != nil && w.debug { slog.Error("Error opening file", "path", path, "error", err) } }