From: João Távora Date: Thu, 8 Jun 2023 01:19:06 +0000 (+0100) Subject: Eglot: try reuse file watchers when server over-watches X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=433a452814fb9eb443819d5a21324364d2600116;p=emacs.git Eglot: try reuse file watchers when server over-watches GitHub-reference: https://github.com/joaotavora/eglot/pull/1228 GitHub-reference: https://github.com/joaotavora/eglot/discussions/1226 The Pyright language server issues very heavy file watching requests, which sometimes exceed the OS limit. Most of these file watches are useless, but Pyright insists on issuing them. What's more, for some (absurd?) reason, Pyright issues two file watching requests for the _same_ directories, only to then almost immediately ask to undo the effects of one of these requests. This change to Eglot makes it so that if a single server requests to watch a specific directory twice, only one file watch object is used. Suggested by: https://github.com/thejeffphil * lisp/progmodes/eglot.el (eglot-lsp-server): Change structure of file-watches field. (eglot--on-shutdown): Adapt to new structure. (eglot-register-capability): Rework. (eglot-unregister-capability): Rework. * etc/EGLOT-NEWS: Mention change --- diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS index 569481a8dc1..6a2e9051ddc 100644 --- a/etc/EGLOT-NEWS +++ b/etc/EGLOT-NEWS @@ -17,6 +17,16 @@ This refers to https://github.com/joaotavora/eglot/issues/. That is, to look up issue github#1234, go to https://github.com/joaotavora/eglot/issues/1234. + +* Changes in upcoming Eglot + +** Optimized file-watching capability + +Some servers, like the Pyright language server, issue too many file +watching requests. This change slightly reduces the number of file +watcher objects requested from the operating system, which can be a +problem, particularly on Mac OS. See github#1228 and github#1226. + * Changes in Eglot 1.15 (29/4/2023) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 0140db0c4b3..c171cc2597a 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -888,7 +888,7 @@ ACTION is an LSP object of either `CodeAction' or `Command' type." :documentation "Generalized boolean inhibiting auto-reconnection if true." :accessor eglot--inhibit-autoreconnect) (file-watches - :documentation "Map ID to list of WATCHES for `didChangeWatchedFiles'." + :documentation "Map (DIR -> (WATCH ID1 ID2...)) for `didChangeWatchedFiles'." :initform (make-hash-table :test #'equal) :accessor eglot--file-watches) (managed-buffers :documentation "List of buffers managed by server." @@ -959,8 +959,8 @@ PRESERVE-BUFFERS as in `eglot-shutdown', which see." (eglot-autoshutdown nil)) (eglot--when-live-buffer buffer (eglot--managed-mode-off)))) ;; Kill any expensive watches - (maphash (lambda (_id watches) - (mapcar #'file-notify-rm-watch watches)) + (maphash (lambda (_dir watch-and-ids) + (file-notify-rm-watch (car watch-and-ids))) (eglot--file-watches server)) ;; Kill any autostarted inferior processes (when-let (proc (eglot--inferior-process server)) @@ -3564,10 +3564,13 @@ at point. With prefix argument, prompt for ACTION-KIND." (handle-event `(,desc 'created ,file1))))))) (unwind-protect (progn - (dolist (dir dirs-to-watch) - (when (file-readable-p dir) - (push (file-notify-add-watch dir '(change) #'handle-event) - (gethash id (eglot--file-watches server))))) + (cl-loop for dir in dirs-to-watch + for probe = + (and (file-readable-p dir) + (or (gethash dir (eglot--file-watches server)) + (puthash dir (list (file-notify-add-watch dir '(change) #'handle-event)) + (eglot--file-watches server)))) + when probe do (push id (cdr probe))) (setq success `(:message ,(format "OK, watching %s directories in %s watchers" @@ -3578,8 +3581,13 @@ at point. With prefix argument, prompt for ACTION-KIND." (cl-defmethod eglot-unregister-capability (server (_method (eql workspace/didChangeWatchedFiles)) id) "Handle dynamic unregistration of workspace/didChangeWatchedFiles." - (mapc #'file-notify-rm-watch (gethash id (eglot--file-watches server))) - (remhash id (eglot--file-watches server)) + (maphash (lambda (dir watch-and-ids) + (when (member id (cdr watch-and-ids)) + (setcdr watch-and-ids (delete id (cdr watch-and-ids))) + (when (null (cdr watch-and-ids)) + (file-notify-rm-watch (car watch-and-ids)) + (remhash dir (eglot--file-watches server))))) + (eglot--file-watches server)) (list t "OK"))