on main help sign in
fix: introduce notify ignore files
9a132dcRaphael Amorim
go.mod+1−1
@@ -112,8 +112,8 @@ require (
112 github.com/ncruces/julianday v1.0.0 // indirect
112 github.com/ncruces/julianday v1.0.0 // indirect
113 github.com/pierrec/lz4/v4 v4.1.22 // indirect
113 github.com/pierrec/lz4/v4 v4.1.22 // indirect
114 github.com/pmezard/go-difflib v1.0.0 // indirect
114 github.com/pmezard/go-difflib v1.0.0 // indirect
115+ github.com/raphamorim/notify v0.9.3
115 github.com/rivo/uniseg v0.4.7
116 github.com/rivo/uniseg v0.4.7
116− github.com/rjeczalik/notify v0.9.3
117 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
117 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
118 github.com/sethvargo/go-retry v0.3.0 // indirect
118 github.com/sethvargo/go-retry v0.3.0 // indirect
119 github.com/spf13/cast v1.7.1 // indirect
119 github.com/spf13/cast v1.7.1 // indirect
go.sum+4−4
@@ -86,7 +86,7 @@ github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqI
86github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
86github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
87github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 h1:+Cz+VfxD5DO+JT1LlswXWhre0HYLj6l2HW8HVGfMuC0=
87github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 h1:+Cz+VfxD5DO+JT1LlswXWhre0HYLj6l2HW8HVGfMuC0=
88github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674/go.mod h1:9gCUAHmVx5BwSafeyNr3GI0GgvlB1WYjL21SkPp1jyU=
88github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674/go.mod h1:9gCUAHmVx5BwSafeyNr3GI0GgvlB1WYjL21SkPp1jyU=
89−github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018 h1:PU4Zvpagsk5sgaDxn5W4sxHuLp9QRMBZB3bFSk40A4w=
89+github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018 h1:5KgReOUbYf1O8+dIiGF0JVirb5NJNjE0gLQMwxDJap4=
90github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018/go.mod h1:Z/GLmp9fzaqX4ze3nXG7StgWez5uBM5XtlLHK8V/qSk=
90github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018/go.mod h1:Z/GLmp9fzaqX4ze3nXG7StgWez5uBM5XtlLHK8V/qSk=
91github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0 h1:sWRGoSw/JsO2S4t2+fmmEkRbkOxphI0AxZkQPQVKWbs=
91github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0 h1:sWRGoSw/JsO2S4t2+fmmEkRbkOxphI0AxZkQPQVKWbs=
92github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0/go.mod h1:XIuqKpZTUXtVyeyiN1k9Tc/U7EzfaDnVc34feFHfBws=
92github.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
94github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM=
94github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM=
95github.com/charmbracelet/ultraviolet v0.0.0-20250912143111-9785ff826cbf h1:2fs3BT8BFjpJ4134Tq4VoBm/fE9FB2f2P/FhmzsWelQ=
95github.com/charmbracelet/ultraviolet v0.0.0-20250912143111-9785ff826cbf h1:2fs3BT8BFjpJ4134Tq4VoBm/fE9FB2f2P/FhmzsWelQ=
96github.com/charmbracelet/ultraviolet v0.0.0-20250912143111-9785ff826cbf/go.mod h1:V21rZtvULxJyG8tUsRC8caTBvKNHOuRJVxH+G6ghH0Y=
96github.com/charmbracelet/ultraviolet v0.0.0-20250912143111-9785ff826cbf/go.mod h1:V21rZtvULxJyG8tUsRC8caTBvKNHOuRJVxH+G6ghH0Y=
97−github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
97+github.com/charmbracelet/x/ansi v0.10.1 h1:LT77A3bpevRD0yZ5NDR5nonS7N83mxzzGwuZcTGezLE=
98github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
98github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
99github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a h1:zYSNtEJM9jwHbJts2k+Hroj+xQwsW1yxc4Wopdv7KaI=
99github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a h1:zYSNtEJM9jwHbJts2k+Hroj+xQwsW1yxc4Wopdv7KaI=
100github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a/go.mod h1:rc2bsPC6MWae3LdOxNO1mOb443NlMrrDL0xEya48NNc=
100github.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
232github.com/pressly/goose/v3 v3.25.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
232github.com/pressly/goose/v3 v3.25.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
233github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c h1:kmzxiX+OB0knCo1V0dkEkdPelzCdAzCURCfmFArn2/A=
233github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c h1:kmzxiX+OB0knCo1V0dkEkdPelzCdAzCURCfmFArn2/A=
234github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4=
234github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4=
235+github.com/raphamorim/notify v0.9.3 h1:sOUIE8U6wtt93QA3/2HOXsGsrsVvT7US5Ye01+Hzl9E=
236+github.com/raphamorim/notify v0.9.3/go.mod h1:3FXSIPyrunV10GCnLGPrpSxoY/Dxi+saeQb9hf+TDSo=
235github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
237github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
236github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
238github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
237github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
239github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
238github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
240github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
239github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
241github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
240−github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
241−github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
242github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
242github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
243github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
243github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
244github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
244github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
internal/lsp/watcher/global_watcher.go+38−8
@@ -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"
20)
20)
21
21
22// global manages file watching shared across all LSP clients.
22// global manages file watching shared across all LSP clients.
23//
23//
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) {
83
84
84// Start sets up recursive watching on the workspace root.
85// Start sets up recursive watching on the workspace root.
85//
86//
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)
105
106
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+
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() {
162
169
163 path := event.Path()
170 path := event.Path()
164
171
165− // Skip ignored files
166− if fsext.ShouldExcludeFile(gw.root, path) {
167− continue
168− }
169−
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())
172 }
174 }
@@ -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)
364}
366}
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+}
internal/lsp/watcher/global_watcher_test.go+1−1
@@ -8,7 +8,7 @@ import (
8 "time"
8 "time"
9
9
10 "github.com/charmbracelet/crush/internal/csync"
10 "github.com/charmbracelet/crush/internal/csync"
11− "github.com/rjeczalik/notify"
11+ "github.com/raphamorim/notify"
12)
12)
13
13
14func TestGlobalWatcher(t *testing.T) {
14func TestGlobalWatcher(t *testing.T) {