@@ -16,12 +16,12 @@ import (
1616 "github.com/charmbracelet/crush/internal/csync"
1717 "github.com/charmbracelet/crush/internal/fsext"
1818 "github.com/charmbracelet/crush/internal/lsp/protocol"
19− "github.com/rjeczalik/notify"
19+ "github.com/raphamorim/notify"
2020)
2121
2222// global manages file watching shared across all LSP clients.
2323//
24−// IMPORTANT: This implementation uses github.com/rjeczalik/notify which provides
24+// IMPORTANT: This implementation uses github.com/raphamorim/notify which provides
2525// recursive watching on all platforms. On macOS it uses FSEvents, on Linux it
2626// uses inotify (with recursion handled by the library), and on Windows it uses
2727// ReadDirectoryChangesW.
@@ -30,6 +30,7 @@ import (
3030// - Single watch point for entire directory tree
3131// - Automatic recursive watching without manually adding subdirectories
3232// - No file descriptor exhaustion issues
33+// - Built-in ignore system for filtering file events
3334type global struct {
3435 // Channel for receiving file system events
3536 events chan notify.EventInfo
@@ -83,7 +84,7 @@ func (gw *global) unregister(name string) {
8384
8485// Start sets up recursive watching on the workspace root.
8586//
86−// Note: We use github.com/rjeczalik/notify which provides recursive watching
87+// Note: We use github.com/raphamorim/notify which provides recursive watching
8788// with a single watch point. The "..." suffix means watch recursively.
8889// This is much more efficient than manually walking and watching each directory.
8990func Start() error {
@@ -103,6 +104,12 @@ func Start() error {
103104 gw.root = root
104105 gw.started.Store(true)
105106
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
111+ }
112+
106113 // Start the event processing goroutine
107114 gw.wg.Add(1)
108115 go gw.processEvents()
@@ -162,11 +169,6 @@ func (gw *global) processEvents() {
162169
163170 path := event.Path()
164171
165− // Skip ignored files
166− if fsext.ShouldExcludeFile(gw.root, path) {
167− continue
168− }
169−
170172 if cfg != nil && cfg.Options.DebugLSP {
171173 slog.Debug("lsp watcher: Global watcher received event", "path", path, "event", event.Event().String())
172174 }
@@ -362,3 +364,31 @@ func isFileLimitError(err error) bool {
362364 // Check for common file limit errors
363365 return errors.Is(err, syscall.EMFILE) || errors.Is(err, syscall.ENFILE)
364366}
367+
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)
373+
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)
379+ }
380+ }
381+
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)
387+ }
388+ }
389+
390+ // Set as the global ignore matcher
391+ notify.SetIgnoreMatcher(im)
392+
393+ return nil
394+}