]> git.eshelyaron.com Git - emacs.git/commitdiff
Jsonrpc: overhaul logging mechanics
authorJoão Távora <joaotavora@gmail.com>
Thu, 21 Dec 2023 20:49:02 +0000 (14:49 -0600)
committerJoão Távora <joaotavora@gmail.com>
Fri, 22 Dec 2023 00:29:50 +0000 (18:29 -0600)
* lisp/jsonrpc.el (jsonrpc-connection): Rework.
(initialize-instance :after jsonrpc-connection): New method.
(slot-missing jsonrpc-connection :events-buffer-scrollback-size oset):
New hack.
(jsonrpc-connection-receive): Rework.
(initialize-instance :after jsonrpc-process-connection): Rework
from non-after version.
(jsonrpc-connection-send)
(jsonrpc--call-deferred)
(jsonrpc--process-sentinel)
(jsonrpc--async-request-1, jsonrpc--debug, jsonrpc--log-event)
(jsonrpc--forwarding-buffer): Rework.
(jsonrpc--run-event-hook): New helper.
(jsonrpc-event-hook): New hook.

* lisp/progmodes/eglot.el (eglot-lsp-server): Fix project slot
initform.
(eglot--connect): Use new jsonrpc-connection initarg.

* test/lisp/progmodes/eglot-tests.el (eglot--sniffing): Use
jsonrpc-event-hook.
(eglot-test-basic-completions): Fix test.

lisp/jsonrpc.el
lisp/progmodes/eglot.el
test/lisp/progmodes/eglot-tests.el

index 8b34728fb95d51b758404a4f93835b22deda66f2..453452b45209872d5a05ee1a004118d8891a935a 100644 (file)
    (-request-continuations
     :initform nil
     :accessor jsonrpc--request-continuations
-    :documentation "An alist of request IDs to continuation lambdas.")
+    :documentation "An alist of request IDs to continuation specs.")
    (-events-buffer
     :initform nil
     :accessor jsonrpc--events-buffer
     :documentation "A buffer pretty-printing the JSONRPC events")
-   (-events-buffer-scrollback-size
-    :initform nil
-    :initarg :events-buffer-scrollback-size
-    :accessor jsonrpc--events-buffer-scrollback-size
-    :documentation "Max size of events buffer.  0 disables, nil means infinite.")
+   (-events-buffer-config
+    :initform '(:size nil :format full)
+    :initarg :events-buffer-config
+    :documentation "Plist configuring the events buffer functions.")
    (-deferred-actions
     :initform (make-hash-table :test #'equal)
     :accessor jsonrpc--deferred-actions
@@ -98,7 +97,7 @@ request that is higher up in the stack but couldn't run.")
     :accessor jsonrpc--next-request-id
     :documentation "Next number used for a request"))
   :documentation "Base class representing a JSONRPC connection.
-The following initargs are accepted:
+The following keyword argument initargs are accepted:
 
 :NAME (mandatory), a string naming the connection
 
@@ -112,7 +111,33 @@ RESULT) or signal an error of type `jsonrpc-error'.
 :NOTIFICATION-DISPATCHER (optional), a function of three
 arguments (CONN METHOD PARAMS) for handling JSONRPC
 notifications.  CONN, METHOD and PARAMS are the same as in
-:REQUEST-DISPATCHER.")
+:REQUEST-DISPATCHER.
+
+:EVENTS-BUFFER-CONFIG is a plist.  Its `:size' stipulates the
+size of the log buffer (0 disables, nil means infinite).  The
+`:format' property is a symbol for choosing the log entry format.")
+
+(cl-defmethod initialize-instance :after
+  ((c jsonrpc-connection) ((&key (events-buffer-scrollback-size
+                                  nil
+                                  e-b-s-s-supplied-p)
+                                 &allow-other-keys)
+                           t))
+  (when e-b-s-s-supplied-p
+    (warn
+     "`:events-buffer-scrollback-size' deprecated. Use `events-buffer-config'.")
+    (with-slots ((plist -events-buffer-config)) c
+      (setf plist (copy-sequence plist)
+            plist (plist-put plist :size events-buffer-scrollback-size)))))
+
+(cl-defmethod slot-missing ((_c jsonrpc-connection)
+                            (_n (eql :events-buffer-scrollback-size))
+                            (_op (eql oset))
+                            _)
+  ;; Yuck!  But this just coerces EIEIO to backward-compatibly accept
+  ;; the :e-b-s-s initarg that is no longer associated with a slot
+  ;; #pineForCLOS..
+  )
 
 ;;; API mandatory
 (cl-defgeneric jsonrpc-connection-send (conn &key id method params result error)
@@ -169,7 +194,10 @@ Return a plist of jsonrpc.el's internal representation of a
 JSONRPC message."
   ;; TODO: describe representations and serialization in manual and
   ;; link here.
-  (:method (_s remote-message) remote-message))
+  (:method (_s remote-message)
+           (cl-loop for (k v) on remote-message by #'cddr
+                    unless (eq k :jsonrpc-json)
+                    collect k and collect v)))
 
 \f
 ;;; Convenience
@@ -207,48 +235,64 @@ circumvent that.")
   "Process MESSAGE just received from CONN.
 This function will destructure MESSAGE and call the appropriate
 dispatcher in CONN."
-  (cl-destructuring-bind (&key method id error params result _jsonrpc)
+  (cl-destructuring-bind (&rest whole &key method id error params result _jsonrpc)
       (jsonrpc-convert-from-endpoint conn message)
-    (jsonrpc--log-event conn message 'server
-                        (cond ((and method id)       'request)
-                              (method                'notification)
-                              (id                    'reply)))
-    (with-slots (last-error
-                 (rdispatcher -request-dispatcher)
-                 (ndispatcher -notification-dispatcher)
-                 (sr-alist -sync-request-alist))
-        conn
-      (setf last-error error)
-      (cond
-       (;; A remote request
-        (and method id)
-        (let* ((debug-on-error (and debug-on-error
-                                    (not jsonrpc-inhibit-debug-on-error)))
-               (reply
-                (condition-case-unless-debug _ignore
-                    (condition-case oops
-                        `(:result ,(funcall rdispatcher conn (intern method) params))
-                      (jsonrpc-error
-                       `(:error
-                         (:code
-                          ,(or (alist-get 'jsonrpc-error-code (cdr oops)) -32603)
-                          :message ,(or (alist-get 'jsonrpc-error-message
-                                                   (cdr oops))
-                                        "Internal error")))))
-                  (error
-                   '(:error (:code -32603 :message "Internal error"))))))
-          (apply #'jsonrpc--reply conn id method reply)))
-       (;; A remote notification
-        method
-        (funcall ndispatcher conn (intern method) params))
-       (;; A remote response, but it can't run yet, because there's an
-        ;; outstanding sync request (bug#67945)
-        (and id sr-alist (not (eq id (caar sr-alist))))
-        (push (cons (jsonrpc--remove conn id) (list result error))
-              (cdr (car sr-alist))))
-       (;; A remote response that can run
-        (jsonrpc--continue conn id result error))))
-    (jsonrpc--call-deferred conn)))
+    (unwind-protect
+        (with-slots (last-error
+                     (rdispatcher -request-dispatcher)
+                     (ndispatcher -notification-dispatcher)
+                     (sr-alist -sync-request-alist))
+            conn
+          (setf last-error error)
+          (cond
+           (;; A remote request
+            (and method id)
+            (let* ((debug-on-error (and debug-on-error
+                                        (not jsonrpc-inhibit-debug-on-error)))
+                   (reply
+                    (condition-case-unless-debug _ignore
+                        (condition-case oops
+                            `(:result ,(funcall rdispatcher conn (intern method)
+                                                params))
+                          (jsonrpc-error
+                           `(:error
+                             (:code
+                              ,(or (alist-get 'jsonrpc-error-code (cdr oops))
+                                   -32603)
+                              :message ,(or (alist-get 'jsonrpc-error-message
+                                                       (cdr oops))
+                                            "Internal error")))))
+                      (error
+                       '(:error (:code -32603 :message "Internal error"))))))
+              (apply #'jsonrpc--reply conn id method reply)))
+           (;; A remote notification
+            method
+            (funcall ndispatcher conn (intern method) params))
+           (id
+            (let ((cont
+                   ;; remove the continuation
+                   (jsonrpc--remove conn id)))
+              (pcase-let ((`(,_ ,method ,_ ,_ ,_) cont))
+                (if (keywordp method)
+                    (setq method (substring (symbol-name method) 1)))
+                (setq whole (plist-put whole :method method)))
+              (cond (;; A remote response, but it can't run yet,
+                     ;; because there's an outstanding sync request
+                     ;; (bug#67945)
+                     (and sr-alist (not (eq id (caar sr-alist))))
+                     (push (cons cont (list result error))
+                           (cdr (car sr-alist))))
+                    (;; A remote response that can run
+                     (jsonrpc--continue conn id cont result error)))))))
+      (jsonrpc--run-event-hook
+       conn 'server
+       :json (plist-get message :jsonrpc-json)
+       :kind (cond ((and method id) 'request)
+                   (method          'notification)
+                   (id              'reply))
+       :message whole
+       :foreign-message message)
+      (jsonrpc--call-deferred conn))))
 
 \f
 ;;; Contacting the remote endpoint
@@ -369,10 +413,11 @@ ignored."
             ;; to protect against user-quit (C-g) or the
             ;; `cancel-on-input' case.
             (pcase-let* ((`(,id ,_) id-and-timer))
+              ;; Discard the continuation
               (jsonrpc--remove connection id (list deferred (current-buffer)))
               ;; We still call `jsonrpc--continue' to run any
               ;; "anxious" continuations.
-              (jsonrpc--continue connection id nil nil)))))
+              (jsonrpc--continue connection id)))))
     (when (eq 'error (car retval))
       (signal 'jsonrpc-error
               (cons
@@ -426,8 +471,7 @@ headers such as \"Content-Length:\".
 :ON-SHUTDOWN (optional), a function of one argument, the
 connection object, called when the process dies.")
 
-(cl-defmethod initialize-instance ((conn jsonrpc-process-connection) slots)
-  (cl-call-next-method)
+(cl-defmethod initialize-instance :after ((conn jsonrpc-process-connection) slots)
   (cl-destructuring-bind (&key ((:process proc)) name &allow-other-keys) slots
     ;; FIXME: notice the undocumented bad coupling in the stderr
     ;; buffer name, it must be named exactly like this we expect when
@@ -437,7 +481,7 @@ connection object, called when the process dies.")
     ;; `after-change-functions'.  Alternatively, we need a new initarg
     ;; (but maybe not a slot).
     (let* ((stderr-buffer-name (format "*%s stderr*" name))
-           (stderr-buffer (jsonrpc--forwarding-buffer stderr-buffer-name "[stderr]" conn))
+           (stderr-buffer (jsonrpc--forwarding-buffer stderr-buffer-name "[stderr] " conn))
            (hidden-name (concat " " stderr-buffer-name)))
       ;; If we are correctly coupled to the client, the process now
       ;; created should pick up the `stderr-buffer' just created, which
@@ -475,15 +519,17 @@ connection object, called when the process dies.")
                                        _partial)
   "Send MESSAGE, a JSON object, to CONNECTION."
   (when method
-    (plist-put args :method
-               (cond ((keywordp method) (substring (symbol-name method) 1))
-                     ((symbolp method) (symbol-name method))
-                     ((stringp method) method)
-                     (t (error "[jsonrpc] invalid method %s" method)))))
-  (let* ((subtype (cond ((or result-supplied-p error) 'reply)
+    ;; sanitize method into a string
+    (setq args
+          (plist-put args :method
+                     (cond ((keywordp method) (substring (symbol-name method) 1))
+                           ((symbolp method) (symbol-name method))
+                           ((stringp method) method)
+                           (t (error "[jsonrpc] invalid method %s" method))))))
+  (let* ((kind (cond ((or result-supplied-p error) 'reply)
                         (id                    'request)
                         (method                'notification)))
-         (converted (jsonrpc-convert-to-endpoint connection args subtype))
+         (converted (jsonrpc-convert-to-endpoint connection args kind))
          (json (jsonrpc--json-encode converted))
          (headers
           `(("Content-Length" . ,(format "%d" (string-bytes json)))
@@ -494,7 +540,13 @@ connection object, called when the process dies.")
      (cl-loop for (header . value) in headers
               concat (concat header ": " value "\r\n") into header-section
               finally return (format "%s\r\n%s" header-section json)))
-    (jsonrpc--log-event connection converted 'client subtype)))
+    (jsonrpc--run-event-hook
+     connection
+     'client
+     :json json
+     :kind  kind
+     :message args
+     :foreign-message converted)))
 
 (defun jsonrpc-process-type (conn)
   "Return the `process-type' of JSONRPC connection CONN."
@@ -572,20 +624,22 @@ With optional CLEANUP, kill any associated buffers."
 (defun jsonrpc--call-deferred (connection)
   "Call CONNECTION's deferred actions, who may again defer themselves."
   (when-let ((actions (hash-table-values (jsonrpc--deferred-actions connection))))
-    (jsonrpc--debug connection `(:maybe-run-deferred
-                                 ,(mapcar (apply-partially #'nth 2) actions)))
+    (jsonrpc--run-event-hook
+     connection 'internal
+     :log-text (format "re-attempting deffered requests %s"
+                       (mapcar (apply-partially #'nth 2) actions)))
     (mapc #'funcall (mapcar #'car actions))))
 
 (defun jsonrpc--process-sentinel (proc change)
   "Called when PROC undergoes CHANGE."
   (let ((connection (process-get proc 'jsonrpc-connection)))
-    (jsonrpc--debug connection `(:message "Connection state changed" :change ,change))
+    (jsonrpc--debug connection "Connection state change: `%s'" change)
     (when (not (process-live-p proc))
       (with-current-buffer (jsonrpc-events-buffer connection)
         (let ((inhibit-read-only t))
           (insert "\n----------b---y---e---b---y---e----------\n")))
       ;; Cancel outstanding timers
-      (mapc (jsonrpc-lambda (_id _success _error timer)
+      (mapc (jsonrpc-lambda (_id _method _success-fn _error-fn timer)
               (when timer (cancel-timer timer)))
             (jsonrpc--request-continuations connection))
       (maphash (lambda (_ triplet)
@@ -595,8 +649,8 @@ With optional CLEANUP, kill any associated buffers."
       (process-put proc 'jsonrpc-sentinel-cleanup-started t)
       (unwind-protect
           ;; Call all outstanding error handlers
-          (mapc (jsonrpc-lambda (_id _success error _timer)
-                  (funcall error '(:code -1 :message "Server died")))
+          (mapc (jsonrpc-lambda (_id _method _success-fn error-fn _timer)
+                  (funcall error-fn '(:code -1 :message "Server died")))
                 (jsonrpc--request-continuations connection))
         (jsonrpc--message "Server exited with status %s" (process-exit-status proc))
         (delete-process proc)
@@ -657,6 +711,9 @@ With optional CLEANUP, kill any associated buffers."
                                                       (cdr oops) (buffer-string))
                                        nil)))
                               (when message
+                                (setq message
+                                      (plist-put message :jsonrpc-json
+                                                 (buffer-string)))
                                 (process-put proc 'jsonrpc-mqueue
                                              (nconc (process-get proc
                                                                  'jsonrpc-mqueue)
@@ -692,21 +749,22 @@ Return the full continuation (ID SUCCESS-FN ERROR-FN TIMER)"
   (with-slots ((conts -request-continuations) (defs -deferred-actions)) conn
     (if deferred-spec (remhash deferred-spec defs))
     (when-let ((ass (assq id conts)))
-      (cancel-timer (elt (cdr ass) 2))
+      (cl-destructuring-bind (_ _ _ _ timer) ass
+          (cancel-timer timer))
       (setf conts (delete ass conts))
       ass)))
 
-(defun jsonrpc--schedule (conn id success-fn error-fn timer)
-  (push (list id success-fn error-fn timer)
+(defun jsonrpc--schedule (conn id method success-fn error-fn timer)
+  (push (list id method success-fn error-fn timer)
         (jsonrpc--request-continuations conn)))
 
-(defun jsonrpc--continue (conn id result error)
-  (pcase-let* ((`(,cont-id ,success-fn ,error-fn ,_timer)
-                (jsonrpc--remove conn id))
+(defun jsonrpc--continue (conn id &optional cont result error)
+  (pcase-let* ((`(,cont-id ,_method ,success-fn ,error-fn ,_timer)
+                cont)
                (head (pop (jsonrpc--sync-request-alist conn)))
                (anxious (cdr head)))
     (cond (anxious
-           (unless (= (car head) id)
+           (when (not (= (car head) id)) ; sanity check
              (error "internal error: please report this bug"))
            ;; If there are "anxious" `jsonrpc-request' continuations
            ;; that should already have been run, they should run now.
@@ -719,7 +777,7 @@ Return the full continuation (ID SUCCESS-FN ERROR-FN TIMER)"
                (if error (later error-fn error)
                  (later success-fn result)))
              (cl-loop for (acont ares aerr) in anxious
-                      for (_id success-fn error-fn) = acont
+                      for (_id _method success-fn error-fn) = acont
                       if aerr do (later error-fn aerr)
                       else do (later success-fn ares))))
           (cont-id
@@ -760,17 +818,20 @@ TIMEOUT is nil)."
                           (lambda ()
                             (jsonrpc--remove connection id (list deferred buf))
                             (if timeout-fn (funcall timeout-fn)
-                              (jsonrpc--debug
-                               connection `(:timed-out ,method :id ,id
-                                                       :params ,params)))))))))))
+                              (jsonrpc--run-event-hook
+                               connection 'internal
+                               :log-text (format "timed-out '%s' (id=%s)" method id)
+                               :id id))))))))))
     (when deferred
       (if (jsonrpc-connection-ready-p connection deferred)
           ;; Server is ready, we jump below and send it immediately.
           (remhash (list deferred buf) (jsonrpc--deferred-actions connection))
         ;; Otherwise, save in `jsonrpc--deferred-actions' and exit non-locally
         (unless old-id
-          (jsonrpc--debug connection `(:deferring ,method :id ,id :params
-                                                  ,params)))
+          (jsonrpc--run-event-hook
+           connection 'internal
+           :log-text (format "deferring '%s' (id=%s)" method id)
+           :id id))
         (puthash (list deferred buf)
                  (list (lambda ()
                          (when (buffer-live-p buf)
@@ -793,22 +854,22 @@ TIMEOUT is nil)."
     (when sync-request
       (push (list id) (jsonrpc--sync-request-alist connection)))
 
-    (jsonrpc--schedule connection
-                       id
-                       (or success-fn
-                           (lambda (&rest _ignored)
-                             (jsonrpc--debug
-                              connection (list :message "success ignored"
-                                               :id id))))
-                       (or error-fn
-                           (jsonrpc-lambda (&key code message &allow-other-keys)
-                             (jsonrpc--debug
-                              connection (list
-                                          :message
-                                          (format "error ignored, status set (%s)"
-                                                  message)
-                                          :id id :error code))))
-                       (funcall maybe-timer))
+    (jsonrpc--schedule
+     connection id method
+     (or success-fn
+         (lambda (&rest _ignored)
+           (jsonrpc--run-event-hook
+            connection 'internal
+            :log-text (format "success ignored")
+            :id id)))
+     (or error-fn
+         (jsonrpc-lambda (&key code message &allow-other-keys)
+           (jsonrpc--run-event-hook
+            connection 'internal
+            :log-text (format "error %s ignored: %s ignored"
+                              code message)
+            :id id)))
+     (funcall maybe-timer))
     (list id timer)))
 
 (defun jsonrpc--message (format &rest args)
@@ -817,10 +878,11 @@ TIMEOUT is nil)."
 
 (defun jsonrpc--debug (server format &rest args)
   "Debug message for SERVER with FORMAT and ARGS."
-  (jsonrpc--log-event
-   server (if (stringp format)
-              `(:message ,(apply #'format format args))
-            format)))
+  (with-current-buffer (jsonrpc-events-buffer server)
+    (jsonrpc--log-event
+     server 'internal
+     :log-text (apply #'format format args)
+     :type 'debug)))
 
 (defun jsonrpc--warn (format &rest args)
   "Warning message with FORMAT and ARGS."
@@ -830,39 +892,97 @@ TIMEOUT is nil)."
                      (apply #'format format args)
                      :warning)))
 
-(defun jsonrpc--log-event (connection message &optional origin subtype)
-  "Log a JSONRPC-related event.
-CONNECTION is the current connection.  MESSAGE is a JSON-like
-plist.  ORIGIN is a symbol saying where event originated.
-SUBTYPE tells more about the event."
-  (let ((max (jsonrpc--events-buffer-scrollback-size connection)))
+(cl-defun jsonrpc--run-event-hook (connection
+                                   origin
+                                   &rest plist
+                                   &key _kind _json _message _foreign-message _log-text
+                                   &allow-other-keys)
+  (with-current-buffer (jsonrpc-events-buffer connection)
+    (run-hook-wrapped 'jsonrpc-event-hook
+                      (lambda (fn)
+                        (apply fn connection origin plist)))))
+
+(defvar jsonrpc-event-hook (list #'jsonrpc--log-event)
+  "Hook run when JSON-RPC events are emitted.
+This hooks runs in the events buffer of every  `jsonrpc-connection'
+when an event is originated by either endpoint.  Each hook function
+is passed the arguments described by the lambda list:
+
+  (CONNECTION ORIGIN &key JSON KIND MESSAGE FOREIGN-MESSAGE LOG-TEXT
+                     &allow-other-keys)
+
+  CONNECTION       the `jsonrpc-connection' instance.
+  ORIGIN           one of the symbols `client' ,`server'.
+  JSON             the raw JSON string content.
+  KIND             one of the symbols `request' ,`notification',
+                   `reply'.
+  MESSAGE          a plist representing the exchanged message in
+                   jsonrpc.el's internal format
+  FOREIGN-MESSAGE  a plist representing the exchanged message in
+                   the remote endpoint's format.
+  LOG-TEXT         text used for events of `internal' origin.
+  ID               id of a message that this event refers to.
+  TYPE             `error', `debug' or the default `info'.
+
+Except for CONNECTION and ORIGIN all other keys are optional.
+Unlisted keys may appear in the plist.
+
+Do not use this hook to write JSON-RPC protocols, use other parts
+of the API instead.")
+
+(cl-defun jsonrpc--log-event (connection origin
+                                         &key kind message
+                                         foreign-message log-text json
+                                         type
+                                         &allow-other-keys)
+  "Log a JSONRPC-related event.  Installed in `jsonrpc-event-hook'."
+  (let* ((props (slot-value connection '-events-buffer-config))
+         (max (plist-get props :size))
+         (format (plist-get props :format)))
     (when (or (null max) (cl-plusp max))
-      (with-current-buffer (jsonrpc-events-buffer connection)
-        (cl-destructuring-bind (&key _method id error &allow-other-keys) message
-          (let* ((inhibit-read-only t)
-                 (type
-                  (concat (format "%s" (or origin 'internal))
-                          (if origin (format "-%s" (or subtype 'message))))))
-            (goto-char (point-max))
-            (prog1
-                (let ((msg (format "[%s]%s%s %s:\n%s"
-                                   type
-                                   (if id (format " (id:%s)" id) "")
-                                   (if error " ERROR" "")
-                                   (current-time-string)
-                                   (pp-to-string message))))
-                  (when error
-                    (setq msg (propertize msg 'face 'error)))
-                  (insert-before-markers msg))
-              ;; Trim the buffer if it's too large
-              (when max
-                (save-excursion
-                  (goto-char (point-min))
-                  (while (> (buffer-size) max)
-                    (delete-region (point) (progn (forward-line 1)
-                                                  (forward-sexp 1)
-                                                  (forward-line 2)
-                                                  (point)))))))))))))
+      (cl-destructuring-bind (&key method id error &allow-other-keys) message
+        (let* ((inhibit-read-only t)
+               (depth (length (jsonrpc--sync-request-alist connection)))
+               (msg
+                (cond ((eq format 'full)
+                       (format "[jsonrpc] %s[%s]%s %s\n"
+                               (pcase type ('error "E") ('debug "D") (_ "e"))
+                               (format-time-string "%H:%M:%S.%3N")
+                               (if (eq origin 'internal)
+                                   ""
+                                 (format " %s%s %s%s"
+                                         (make-string (* 2 depth) ? )
+                                         (pcase origin
+                                           ('client "-->")
+                                           ('server "<--")
+                                           (_ ""))
+                                         (or method "")
+                                         (if id (format "(%s)" id) "")))
+                               (or json log-text)))
+                      (t
+                       (format "[%s]%s%s %s:\n%s"
+                               (concat (format "%s" (or origin 'internal))
+                                       (if origin (format "-%s" (or kind 'message))))
+                               (if id (format " (id:%s)" id) "")
+                               (if error " ERROR" "")
+                               (format-time-string "%H:%M:%S.%3N")
+                               (if foreign-message (pp-to-string foreign-message)
+                                 log-text))))))
+          (goto-char (point-max))
+          ;; XXX: could use `run-at-time' to delay server logs
+          ;; slightly to play nice with verbose servers' stderr.
+          (when error
+            (setq msg (propertize msg 'face 'error)))
+          (insert-before-markers msg)
+          ;; Trim the buffer if it's too large
+          (when max
+              (save-excursion
+                (goto-char (point-min))
+                (while (> (buffer-size) max)
+                  (delete-region (point) (progn (forward-line 1)
+                                                (forward-sexp 1)
+                                                (forward-line 2)
+                                                (point)))))))))))
 
 (defun jsonrpc--forwarding-buffer (name prefix conn)
   "Helper for `jsonrpc-process-connection' helpers.
@@ -885,7 +1005,9 @@ PREFIX to CONN's events buffer."
                   do (with-current-buffer (jsonrpc-events-buffer conn)
                        (goto-char (point-max))
                        (let ((inhibit-read-only t))
-                         (insert (format "%s %s\n" prefix line))))
+                         (insert
+                          (propertize (format "%s %s\n" prefix line)
+                                      'face 'shadow))))
                   until (eobp)))
        nil t))
     (current-buffer)))
index 2a3c2201e2158d7414ccecedbe3a194328975d38..c849ff5c37e2f4fc21f5ef285757c86a7d86ea61 100644 (file)
@@ -993,6 +993,7 @@ ACTION is an LSP object of either `CodeAction' or `Command' type."
     :documentation "Flag set when server is shutting down."
     :accessor eglot--shutdown-requested)
    (project
+    :initform nil
     :documentation "Project associated with server."
     :accessor eglot--project)
    (progress-reporters
@@ -1512,7 +1513,7 @@ This docstring appeases checkdoc, that's all."
           (apply
            #'make-instance class
            :name readable-name
-           :events-buffer-scrollback-size eglot-events-buffer-size
+           :events-buffer-config `(:size ,eglot-events-buffer-size :format full)
            :notification-dispatcher (funcall spread #'eglot-handle-notification)
            :request-dispatcher (funcall spread #'eglot-handle-request)
            :on-shutdown #'eglot--on-shutdown
index 996ff276e685c23f1b51cc5bbda331ba095223da..f2da3295b49cf2a05599f5589c07959ee40eb860 100644 (file)
@@ -199,38 +199,40 @@ directory hierarchy."
                               &rest body)
   "Run BODY saving LSP JSON messages in variables, most recent first."
   (declare (indent 1) (debug (sexp &rest form)))
-  (let ((log-event-ad-sym (make-symbol "eglot--event-sniff")))
-    `(unwind-protect
-         (let ,(delq nil (list server-requests
-                               server-notifications
-                               server-replies
-                               client-requests
-                               client-notifications
-                               client-replies))
-           (advice-add
-            #'jsonrpc--log-event :before
-            (lambda (_proc message &optional origin subtype)
-              (let ((req-p (eq subtype 'request))
-                    (notif-p (eq subtype 'notification))
-                    (reply-p (eql subtype 'reply)))
-                (cond
-                 ((eq origin 'server)
-                  (cond (req-p ,(when server-requests
-                                  `(push message ,server-requests)))
-                        (notif-p ,(when server-notifications
-                                    `(push message ,server-notifications)))
-                        (reply-p ,(when server-replies
-                                    `(push message ,server-replies)))))
-                 ((eq origin 'client)
-                  (cond (req-p ,(when client-requests
-                                  `(push message ,client-requests)))
-                        (notif-p ,(when client-notifications
-                                    `(push message ,client-notifications)))
-                        (reply-p ,(when client-replies
-                                    `(push message ,client-replies))))))))
-            '((name . ,log-event-ad-sym)))
-           ,@body)
-       (advice-remove #'jsonrpc--log-event ',log-event-ad-sym))))
+  (let ((log-event-hook-sym (make-symbol "eglot--event-sniff")))
+    `(let* (,@(delq nil (list server-requests
+                              server-notifications
+                              server-replies
+                              client-requests
+                              client-notifications
+                              client-replies)))
+       (cl-flet ((,log-event-hook-sym (_connection
+                                       origin
+                                       &key _json kind message _foreign-message
+                                       &allow-other-keys)
+                   (let ((req-p (eq kind 'request))
+                         (notif-p (eq kind 'notification))
+                         (reply-p (eql kind 'reply)))
+                     (cond
+                      ((eq origin 'server)
+                       (cond (req-p ,(when server-requests
+                                       `(push message ,server-requests)))
+                             (notif-p ,(when server-notifications
+                                         `(push message ,server-notifications)))
+                             (reply-p ,(when server-replies
+                                         `(push message ,server-replies)))))
+                      ((eq origin 'client)
+                       (cond (req-p ,(when client-requests
+                                       `(push message ,client-requests)))
+                             (notif-p ,(when client-notifications
+                                         `(push message ,client-notifications)))
+                             (reply-p ,(when client-replies
+                                         `(push message ,client-replies)))))))))
+         (unwind-protect
+             (progn
+               (add-hook 'jsonrpc-event-hook #',log-event-hook-sym)
+               ,@body)
+           (remove-hook 'jsonrpc-event-hook #',log-event-hook-sym))))))
 
 (cl-defmacro eglot--wait-for ((events-sym &optional (timeout 1) message) args &body body)
   (declare (indent 2) (debug (sexp sexp sexp &rest form)))
@@ -542,10 +544,7 @@ directory hierarchy."
       `(("project" . (("coiso.c" . "#include <stdio.h>\nint main () {fprin"))))
     (with-current-buffer
         (eglot--find-file-noselect "project/coiso.c")
-      (eglot--sniffing (:server-notifications s-notifs)
-        (eglot--wait-for-clangd)
-        (eglot--wait-for (s-notifs 20) (&key method &allow-other-keys)
-          (string= method "textDocument/publishDiagnostics")))
+      (eglot--wait-for-clangd)
       (goto-char (point-max))
       (completion-at-point)
       (message (buffer-string))