@@ -16,12 +16,12 @@ import (
16 "github.com/charmbracelet/crush/internal/csync"
16 "github.com/charmbracelet/crush/internal/csync"
17 "github.com/charmbracelet/crush/internal/fsext"
17 "github.com/charmbracelet/crush/internal/fsext"
18 "github.com/charmbracelet/crush/internal/lsp/protocol"
18 "github.com/charmbracelet/crush/internal/lsp/protocol"
19− "github.com/rjeczalik/notify"
19+ "github.com/raphamorim/notify"
22// global manages file watching shared across all LSP clients.
22// global manages file watching shared across all LSP clients.
24−// IMPORTANT: This implementation uses github.com/rjeczalik/notify which provides
24+// IMPORTANT: This implementation uses github.com/raphamorim/notify which provides
25// recursive watching on all platforms. On macOS it uses FSEvents, on Linux it
25// recursive watching on all platforms. On macOS it uses FSEvents, on Linux it
26// uses inotify (with recursion handled by the library), and on Windows it uses
26// uses inotify (with recursion handled by the library), and on Windows it uses
27// ReadDirectoryChangesW.
27// ReadDirectoryChangesW.
@@ -30,6 +30,7 @@ import (
30// - Single watch point for entire directory tree
30// - Single watch point for entire directory tree
31// - Automatic recursive watching without manually adding subdirectories
31// - Automatic recursive watching without manually adding subdirectories
32// - No file descriptor exhaustion issues
32// - No file descriptor exhaustion issues
33+// - Built-in ignore system for filtering file events
33type global struct {
34type global struct {
34 // Channel for receiving file system events
35 // Channel for receiving file system events
35 events chan notify.EventInfo
36 events chan notify.EventInfo
@@ -83,7 +84,7 @@ func (gw *global) unregister(name string) {
84// Start sets up recursive watching on the workspace root.
85// Start sets up recursive watching on the workspace root.
86−// Note: We use github.com/rjeczalik/notify which provides recursive watching
87+// Note: We use github.com/raphamorim/notify which provides recursive watching
87// with a single watch point. The "..." suffix means watch recursively.
88// with a single watch point. The "..." suffix means watch recursively.
88// This is much more efficient than manually walking and watching each directory.
89// This is much more efficient than manually walking and watching each directory.
89func Start() error {
90func Start() error {
@@ -103,6 +104,12 @@ func Start() error {
103 gw.root = root
104 gw.root = root
104 gw.started.Store(true)
105 gw.started.Store(true)
107+ // Set up ignore system
108+ if err := setupIgnoreSystem(root); err != nil {
109+ slog.Warn("lsp watcher: Failed to set up ignore system", "error", err)
110+ // Continue anyway, but without ignore functionality
106 // Start the event processing goroutine
113 // Start the event processing goroutine
107 gw.wg.Add(1)
114 gw.wg.Add(1)
108 go gw.processEvents()
115 go gw.processEvents()
@@ -162,11 +169,6 @@ func (gw *global) processEvents() {
163 path := event.Path()
170 path := event.Path()
165− // Skip ignored files
166− if fsext.ShouldExcludeFile(gw.root, path) {
170 if cfg != nil && cfg.Options.DebugLSP {
172 if cfg != nil && cfg.Options.DebugLSP {
171 slog.Debug("lsp watcher: Global watcher received event", "path", path, "event", event.Event().String())
173 slog.Debug("lsp watcher: Global watcher received event", "path", path, "event", event.Event().String())
@@ -362,3 +364,31 @@ func isFileLimitError(err error) bool {
362 // Check for common file limit errors
364 // Check for common file limit errors
363 return errors.Is(err, syscall.EMFILE) || errors.Is(err, syscall.ENFILE)
365 return errors.Is(err, syscall.EMFILE) || errors.Is(err, syscall.ENFILE)
368+// setupIgnoreSystem configures the notify library's ignore system
369+// to use .crushignore and .gitignore files for filtering file events
370+func setupIgnoreSystem(root string) error {
371+ // Create a new ignore matcher for the workspace root
372+ im := notify.NewIgnoreMatcher(root)
374+ // Load .crushignore file if it exists
375+ crushignorePath := filepath.Join(root, ".crushignore")
376+ if _, err := os.Stat(crushignorePath); err == nil {
377+ if err := im.LoadIgnoreFile(crushignorePath); err != nil {
378+ slog.Warn("lsp watcher: Failed to load .crushignore file", "error", err)
382+ // Load .gitignore file if it exists
383+ gitignorePath := filepath.Join(root, ".gitignore")
384+ if _, err := os.Stat(gitignorePath); err == nil {
385+ if err := im.LoadIgnoreFile(gitignorePath); err != nil {
386+ slog.Warn("lsp watcher: Failed to load .gitignore file", "error", err)
390+ // Set as the global ignore matcher
391+ notify.SetIgnoreMatcher(im)