summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-26 19:40:22 -0500
committerCraig Jennings <c@cjennings.net>2026-04-26 19:40:22 -0500
commit781b46e4eb7c08a3db01b1c1d89677ba8e21fd98 (patch)
tree7cc93086d8f644d861aa1c81d46c13a47e524ae8
parentfbca79c1e06b41099c1a4d18b587bc8c4611e777 (diff)
downloaddotemacs-781b46e4eb7c08a3db01b1c1d89677ba8e21fd98.tar.gz
dotemacs-781b46e4eb7c08a3db01b1c1d89677ba8e21fd98.zip
feat(lsp): add common build/cache dirs to file-watch ignore list
Extends `lsp-file-watch-ignored-directories' with thirteen build, cache, and tooling directories: `node_modules', `dist', `coverage', `target', `__pycache__', `.venv', `venv', `.pytest_cache', `.mypy_cache', `.ruff_cache', `test-results', `playwright-report', `tf/.terraform'. Uses `add-to-list', so lsp-mode's own defaults (`.git', `.svn', `.idea', etc.) stay in place. Setting these in a project's `.dir-locals.el' doesn't work. lsp-mode reads `lsp-file-watch-ignored-directories' once at workspace init, from the global value, so a buffer-local override never reaches the watch list. I confirmed this today: in a Python buffer where dir-locals had applied, `M-: lsp-file-watch-ignored-directories' returned the lsp-mode default, not the project's overrides. Setting it globally is what works. The goal is to push typical workspaces under `lsp-file-watch-threshold' (1000), so the "watch all files? (y or n)" prompt stops firing on every fresh LSP start. Also added a forward defvar for `lsp-enable-remote' to silence the matching free-variable warning under `make compile'.
-rw-r--r--modules/prog-lsp.el39
-rw-r--r--tests/test-prog-lsp--add-file-watch-ignored-extras.el87
2 files changed, 125 insertions, 1 deletions
diff --git a/modules/prog-lsp.el b/modules/prog-lsp.el
index c976a83e..a6037db8 100644
--- a/modules/prog-lsp.el
+++ b/modules/prog-lsp.el
@@ -8,6 +8,42 @@
;;; Code:
+;; Forward declarations for byte-compile and let-binding under lexical scope.
+;; Real definitions are lsp-mode's defcustoms.
+(defvar lsp-file-watch-ignored-directories)
+(defvar lsp-enable-remote)
+
+;;;;; --------------------- File-Watch Ignore Patterns ---------------------
+;; lsp-mode prompts when a workspace exceeds `lsp-file-watch-threshold' (1000)
+;; directories. Real source repos cross that line easily once node_modules,
+;; build outputs, and language caches are counted. These patterns extend the
+;; lsp-mode defaults (.git, .svn, .idea, ...) instead of replacing them, so the
+;; built-in VC/IDE excludes still apply. Buffer-local overrides via
+;; `.dir-locals.el' don't work — lsp-mode reads the global value at workspace
+;; init, not the buffer-local one. Hence: global defaults here.
+
+(defvar cj/lsp-file-watch-ignored-extras
+ '("[/\\\\]node_modules\\'"
+ "[/\\\\]\\.ruff_cache\\'"
+ "[/\\\\]dist\\'"
+ "[/\\\\]coverage\\'"
+ "[/\\\\]test-results\\'"
+ "[/\\\\]playwright-report\\'"
+ "[/\\\\]tf[/\\\\]\\.terraform\\'"
+ "[/\\\\]__pycache__\\'"
+ "[/\\\\]\\.venv\\'"
+ "[/\\\\]venv\\'"
+ "[/\\\\]\\.pytest_cache\\'"
+ "[/\\\\]\\.mypy_cache\\'"
+ "[/\\\\]target\\'")
+ "Build/cache directory patterns to add to `lsp-file-watch-ignored-directories'.
+Each entry is an Emacs regex matching a path ending in the named directory.")
+
+(defun cj/lsp--add-file-watch-ignored-extras ()
+ "Append `cj/lsp-file-watch-ignored-extras' to lsp-mode's ignore list.
+Idempotent — `add-to-list' skips patterns already present."
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (add-to-list 'lsp-file-watch-ignored-directories pattern)))
;;;;; ---------------------------- LSP Mode ---------------------------
@@ -38,7 +74,8 @@
(setq lsp-enable-imenu nil)
(setq lsp-enable-snippet nil)
(setq read-process-output-max (* 1024 1024)) ;; 1MB
- (setq lsp-idle-delay 0.5))
+ (setq lsp-idle-delay 0.5)
+ (cj/lsp--add-file-watch-ignored-extras))
;;;;; ----------------------------- LSP UI ----------------------------
diff --git a/tests/test-prog-lsp--add-file-watch-ignored-extras.el b/tests/test-prog-lsp--add-file-watch-ignored-extras.el
new file mode 100644
index 00000000..255832e4
--- /dev/null
+++ b/tests/test-prog-lsp--add-file-watch-ignored-extras.el
@@ -0,0 +1,87 @@
+;;; test-prog-lsp--add-file-watch-ignored-extras.el --- Tests for cj/lsp--add-file-watch-ignored-extras -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for cj/lsp--add-file-watch-ignored-extras in prog-lsp.el.
+;; The function adds project-agnostic build/cache directory patterns to
+;; `lsp-file-watch-ignored-directories' without replacing lsp-mode's
+;; defaults. Patterns are sourced from `cj/lsp-file-watch-ignored-extras'.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+
+;; Declare lsp-mode's defcustom as a special variable so `let' binds it
+;; dynamically. Real definition is lsp-mode's; loaded only when use-package
+;; activates lsp-mode. In the test environment, this stub provides the value
+;; cell `add-to-list' needs.
+(defvar lsp-file-watch-ignored-directories nil)
+
+(require 'prog-lsp)
+
+;;; Normal Cases
+
+(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-normal-adds-all-patterns ()
+ "Normal: every entry from `cj/lsp-file-watch-ignored-extras' lands in the list."
+ (let ((lsp-file-watch-ignored-directories nil))
+ (cj/lsp--add-file-watch-ignored-extras)
+ (should (= (length lsp-file-watch-ignored-directories)
+ (length cj/lsp-file-watch-ignored-extras)))
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (should (member pattern lsp-file-watch-ignored-directories)))))
+
+(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-normal-extends-not-replaces ()
+ "Normal: pre-existing entries (lsp-mode defaults) are preserved."
+ (let ((lsp-file-watch-ignored-directories
+ '("[/\\\\]\\.git\\'" "[/\\\\]\\.svn\\'" "[/\\\\]\\.idea\\'")))
+ (cj/lsp--add-file-watch-ignored-extras)
+ (should (member "[/\\\\]\\.git\\'" lsp-file-watch-ignored-directories))
+ (should (member "[/\\\\]\\.svn\\'" lsp-file-watch-ignored-directories))
+ (should (member "[/\\\\]\\.idea\\'" lsp-file-watch-ignored-directories))
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (should (member pattern lsp-file-watch-ignored-directories)))))
+
+(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-normal-key-patterns-present ()
+ "Normal: specific expected directory names appear in the constant."
+ (dolist (name '("node_modules" "target" "__pycache__" ".venv" "venv"
+ "dist" "coverage" "test-results" "playwright-report"
+ ".terraform" ".ruff_cache" ".pytest_cache" ".mypy_cache"))
+ (should (cl-some (lambda (p) (string-match-p (regexp-quote name) p))
+ cj/lsp-file-watch-ignored-extras))))
+
+;;; Boundary Cases
+
+(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-boundary-idempotent ()
+ "Boundary: calling twice doesn't duplicate entries."
+ (let ((lsp-file-watch-ignored-directories nil))
+ (cj/lsp--add-file-watch-ignored-extras)
+ (cj/lsp--add-file-watch-ignored-extras)
+ (should (= (length lsp-file-watch-ignored-directories)
+ (length cj/lsp-file-watch-ignored-extras)))))
+
+(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-boundary-all-patterns-non-empty ()
+ "Boundary: every pattern is a non-empty string."
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (should (stringp pattern))
+ (should (not (string-empty-p pattern)))))
+
+(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-boundary-all-patterns-valid-regex ()
+ "Boundary: every pattern compiles as a valid Emacs regex."
+ (dolist (pattern cj/lsp-file-watch-ignored-extras)
+ (condition-case err
+ ;; string-match-p compiles the regex; invalid syntax raises invalid-regexp.
+ (string-match-p pattern "/some/sample/path")
+ (invalid-regexp
+ (ert-fail (format "Invalid regex %S: %s"
+ pattern (error-message-string err)))))))
+
+;;; Error Cases
+
+(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-error-non-list-target ()
+ "Error: non-list target value triggers `add-to-list' wrong-type-argument."
+ (let ((lsp-file-watch-ignored-directories "not-a-list"))
+ (should-error (cj/lsp--add-file-watch-ignored-extras)
+ :type 'wrong-type-argument)))
+
+(provide 'test-prog-lsp--add-file-watch-ignored-extras)
+;;; test-prog-lsp--add-file-watch-ignored-extras.el ends here