fix: introduce notify ignore files

Raphael Amorim created

Change summary

go.mod                                      |  2 
go.sum                                      |  8 ++--
internal/lsp/watcher/global_watcher.go      | 46 +++++++++++++++++++----
internal/lsp/watcher/global_watcher_test.go |  2 
4 files changed, 44 insertions(+), 14 deletions(-)

Detailed changes

go.mod 🔗

@@ -112,8 +112,8 @@ require (
 	github.com/ncruces/julianday v1.0.0 // indirect
 	github.com/pierrec/lz4/v4 v4.1.22 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/raphamorim/notify v0.9.3
 	github.com/rivo/uniseg v0.4.7
-	github.com/rjeczalik/notify v0.9.3
 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
 	github.com/sethvargo/go-retry v0.3.0 // indirect
 	github.com/spf13/cast v1.7.1 // indirect

go.sum 🔗

@@ -86,7 +86,7 @@ github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqI
 github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
 github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 h1:+Cz+VfxD5DO+JT1LlswXWhre0HYLj6l2HW8HVGfMuC0=
 github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674/go.mod h1:9gCUAHmVx5BwSafeyNr3GI0GgvlB1WYjL21SkPp1jyU=
-github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018 h1:PU4Zvpagsk5sgaDxn5W4sxHuLp9QRMBZB3bFSk40A4w=
+github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018 h1:5KgReOUbYf1O8+dIiGF0JVirb5NJNjE0gLQMwxDJap4=
 github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018/go.mod h1:Z/GLmp9fzaqX4ze3nXG7StgWez5uBM5XtlLHK8V/qSk=
 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0 h1:sWRGoSw/JsO2S4t2+fmmEkRbkOxphI0AxZkQPQVKWbs=
 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0/go.mod h1:XIuqKpZTUXtVyeyiN1k9Tc/U7EzfaDnVc34feFHfBws=
@@ -94,7 +94,7 @@ github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 h1:WkwO6Ks3mS
 github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM=
 github.com/charmbracelet/ultraviolet v0.0.0-20250912143111-9785ff826cbf h1:2fs3BT8BFjpJ4134Tq4VoBm/fE9FB2f2P/FhmzsWelQ=
 github.com/charmbracelet/ultraviolet v0.0.0-20250912143111-9785ff826cbf/go.mod h1:V21rZtvULxJyG8tUsRC8caTBvKNHOuRJVxH+G6ghH0Y=
-github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
+github.com/charmbracelet/x/ansi v0.10.1 h1:LT77A3bpevRD0yZ5NDR5nonS7N83mxzzGwuZcTGezLE=
 github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
 github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a h1:zYSNtEJM9jwHbJts2k+Hroj+xQwsW1yxc4Wopdv7KaI=
 github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a/go.mod h1:rc2bsPC6MWae3LdOxNO1mOb443NlMrrDL0xEya48NNc=
@@ -232,13 +232,13 @@ github.com/pressly/goose/v3 v3.25.0 h1:6WeYhMWGRCzpyd89SpODFnCBCKz41KrVbRT58nVjG
 github.com/pressly/goose/v3 v3.25.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
 github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c h1:kmzxiX+OB0knCo1V0dkEkdPelzCdAzCURCfmFArn2/A=
 github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4=
+github.com/raphamorim/notify v0.9.3 h1:sOUIE8U6wtt93QA3/2HOXsGsrsVvT7US5Ye01+Hzl9E=
+github.com/raphamorim/notify v0.9.3/go.mod h1:3FXSIPyrunV10GCnLGPrpSxoY/Dxi+saeQb9hf+TDSo=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
-github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=

internal/lsp/watcher/global_watcher.go 🔗

@@ -16,12 +16,12 @@ import (
 	"github.com/charmbracelet/crush/internal/csync"
 	"github.com/charmbracelet/crush/internal/fsext"
 	"github.com/charmbracelet/crush/internal/lsp/protocol"
-	"github.com/rjeczalik/notify"
+	"github.com/raphamorim/notify"
 )
 
 // global manages file watching shared across all LSP clients.
 //
-// IMPORTANT: This implementation uses github.com/rjeczalik/notify which provides
+// IMPORTANT: This implementation uses github.com/raphamorim/notify which provides
 // recursive watching on all platforms. On macOS it uses FSEvents, on Linux it
 // uses inotify (with recursion handled by the library), and on Windows it uses
 // ReadDirectoryChangesW.
@@ -30,6 +30,7 @@ import (
 // - Single watch point for entire directory tree
 // - Automatic recursive watching without manually adding subdirectories
 // - No file descriptor exhaustion issues
+// - Built-in ignore system for filtering file events
 type global struct {
 	// Channel for receiving file system events
 	events chan notify.EventInfo
@@ -83,7 +84,7 @@ func (gw *global) unregister(name string) {
 
 // Start sets up recursive watching on the workspace root.
 //
-// Note: We use github.com/rjeczalik/notify which provides recursive watching
+// Note: We use github.com/raphamorim/notify which provides recursive watching
 // with a single watch point. The "..." suffix means watch recursively.
 // This is much more efficient than manually walking and watching each directory.
 func Start() error {
@@ -103,6 +104,12 @@ func Start() error {
 	gw.root = root
 	gw.started.Store(true)
 
+	// Set up ignore system
+	if err := setupIgnoreSystem(root); err != nil {
+		slog.Warn("lsp watcher: Failed to set up ignore system", "error", err)
+		// Continue anyway, but without ignore functionality
+	}
+
 	// Start the event processing goroutine
 	gw.wg.Add(1)
 	go gw.processEvents()
@@ -162,11 +169,6 @@ func (gw *global) processEvents() {
 
 			path := event.Path()
 
-			// Skip ignored files
-			if fsext.ShouldExcludeFile(gw.root, path) {
-				continue
-			}
-
 			if cfg != nil && cfg.Options.DebugLSP {
 				slog.Debug("lsp watcher: Global watcher received event", "path", path, "event", event.Event().String())
 			}
@@ -362,3 +364,31 @@ func isFileLimitError(err error) bool {
 	// Check for common file limit errors
 	return errors.Is(err, syscall.EMFILE) || errors.Is(err, syscall.ENFILE)
 }
+
+// setupIgnoreSystem configures the notify library's ignore system
+// to use .crushignore and .gitignore files for filtering file events
+func setupIgnoreSystem(root string) error {
+	// Create a new ignore matcher for the workspace root
+	im := notify.NewIgnoreMatcher(root)
+
+	// Load .crushignore file if it exists
+	crushignorePath := filepath.Join(root, ".crushignore")
+	if _, err := os.Stat(crushignorePath); err == nil {
+		if err := im.LoadIgnoreFile(crushignorePath); err != nil {
+			slog.Warn("lsp watcher: Failed to load .crushignore file", "error", err)
+		}
+	}
+
+	// Load .gitignore file if it exists
+	gitignorePath := filepath.Join(root, ".gitignore")
+	if _, err := os.Stat(gitignorePath); err == nil {
+		if err := im.LoadIgnoreFile(gitignorePath); err != nil {
+			slog.Warn("lsp watcher: Failed to load .gitignore file", "error", err)
+		}
+	}
+
+	// Set as the global ignore matcher
+	notify.SetIgnoreMatcher(im)
+
+	return nil
+}

internal/lsp/watcher/global_watcher_test.go 🔗

@@ -8,7 +8,7 @@ import (
 	"time"
 
 	"github.com/charmbracelet/crush/internal/csync"
-	"github.com/rjeczalik/notify"
+	"github.com/raphamorim/notify"
 )
 
 func TestGlobalWatcher(t *testing.T) {