diff --git a/README.md b/README.md index 0424cf2..b96d41c 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,31 @@ For example: (setq copilot-network-proxy '(:host "127.0.0.1" :port 7890)) ``` +### copilot-on-request + +Register a handler to be called when a request of type method is received. Return JSON serializable as result or calling `jsonrpc-error` for errors. [readmore](https://www.gnu.org/software/emacs/manual/html_node/elisp/JSONRPC-Overview.html) + +For example: + +```elisp +; Display desktop notification if emacs is built with d-bus +(copilot-on-request + 'window/showMessageRequest + (lambda (msg) (notifications-notify :title "Emacs Copilot" :body (plist-get msg :message)))) +``` + +### copilot-on-notification + +Register a listener for copilot notifications. + +For example: + +```elisp +(copilot-on-notification + 'window/logMessage + (lambda (msg) (message (plist-get msg :message)) +``` + ## Known Issues ### Wrong Position of Other Completion Popups @@ -334,7 +359,7 @@ But I decided to allow them to coexist, allowing you to choose a better one at a ## Reporting Bugs + Make sure you have restarted your Emacs (and rebuild the plugin if necessary) after updating the plugin. -+ Please enable event logging by customize `copilot-log-max` (to e.g. 1000), then paste related logs in the `*copilot events*` and `*copilot stderr*` buffer. ++ Please enable event logging by customize `copilot-log-max` (to e.g. 1000) and enable debug log `(setq copilot-server-args '("--stdio" "--debug"))`, then paste related logs in the `*copilot events*`, `*copilot stderr*` and `*copilot-language-server-log*` buffer. + If an exception is thrown, please also paste the stack trace (use `M-x toggle-debug-on-error` to enable stack trace). ## Roadmap diff --git a/copilot.el b/copilot.el index 1b76edc..e8d0644 100644 --- a/copilot.el +++ b/copilot.el @@ -405,6 +405,7 @@ SUCCESS-FN is the CALLBACK." #'make-instance 'jsonrpc-process-connection :name "copilot" + :request-dispatcher #'copilot--handle-request :notification-dispatcher #'copilot--handle-notification :process (make-process :name "copilot server" :command (copilot--command) @@ -718,31 +719,76 @@ automatically, browse to %s." user-code verification-uri)) (defvar copilot--panel-lang nil "Language of current panel solutions.") +(defvar copilot--request-handlers (make-hash-table :test 'equal) + "Hash table storing request handlers.") + +(defun copilot-on-request (method handler) + "Register a request HANDLER for the given METHOD. +Each request METHOD can have only one HANDLER." + (puthash method handler copilot--request-handlers)) + +(defun copilot--handle-request (_ method msg) + "Handle MSG of type METHOD by calling the appropriate registered handler." + (let ((handler (gethash method copilot--request-handlers))) + (when handler + (funcall handler msg)))) + +(defvar copilot--notification-handlers (make-hash-table :test 'equal) + "Hash table storing lists of notification handlers.") + +(defun copilot-on-notification (method handler) + "Register a notification HANDLER for the given METHOD." + (let ((handlers (gethash method copilot--notification-handlers '()))) + (puthash method (cons handler handlers) copilot--notification-handlers))) + (defun copilot--handle-notification (_ method msg) - "Handle MSG of type METHOD." - (when (eql method 'PanelSolution) - (copilot--dbind (((:completionText completion-text)) ((:score completion-score))) msg - (with-current-buffer "*copilot-panel*" - (unless (member (secure-hash 'sha256 completion-text) - (org-map-entries (lambda () (org-entry-get nil "SHA")))) - (save-excursion - (goto-char (point-max)) - (insert "* Solution\n" - " :PROPERTIES:\n" - " :SCORE: " (number-to-string completion-score) "\n" - " :SHA: " (secure-hash 'sha256 completion-text) "\n" - " :END:\n" - "#+BEGIN_SRC " copilot--panel-lang "\n" - completion-text "\n#+END_SRC\n\n") - (call-interactively #'mark-whole-buffer) - (org-sort-entries nil ?R nil nil "SCORE")))))) - (when (eql method 'PanelSolutionsDone) - (message "Copilot: Finish synthesizing solutions.") - (display-buffer "*copilot-panel*") - (with-current-buffer "*copilot-panel*" - (save-excursion - (goto-char (point-max)) - (insert "End of solutions.\n"))))) + "Handle MSG of type METHOD by calling all appropriate registered handlers." + (let ((handlers (gethash method copilot--notification-handlers '()))) + (dolist (handler handlers) + (funcall handler msg)))) + +(copilot-on-notification + 'window/logMessage + (lambda (msg) + (copilot--dbind (:type log-level :message log-msg) msg + (with-current-buffer (get-buffer-create "*copilot-language-server-log*") + (save-excursion + (goto-char (point-max)) + (insert (propertize (concat log-msg "\n") + 'face (pcase log-level + (4 'shadow) + (3 'success) + (2 'warning) + (1 'error))))))))) + +(copilot-on-notification + 'PanelSolution + (lambda (msg) + (copilot--dbind (((:completionText completion-text)) ((:score completion-score))) msg + (with-current-buffer "*copilot-panel*" + (unless (member (secure-hash 'sha256 completion-text) + (org-map-entries (lambda () (org-entry-get nil "SHA")))) + (save-excursion + (goto-char (point-max)) + (insert "* Solution\n" + " :PROPERTIES:\n" + " :SCORE: " (number-to-string completion-score) "\n" + " :SHA: " (secure-hash 'sha256 completion-text) "\n" + " :END:\n" + "#+BEGIN_SRC " copilot--panel-lang "\n" + completion-text "\n#+END_SRC\n\n") + (call-interactively #'mark-whole-buffer) + (org-sort-entries nil ?R nil nil "SCORE"))))))) + +(copilot-on-notification + 'PanelSolutionsDone + (lambda (_msg) + (message "Copilot: Finish synthesizing solutions.") + (display-buffer "*copilot-panel*") + (with-current-buffer "*copilot-panel*" + (save-excursion + (goto-char (point-max)) + (insert "End of solutions.\n"))))) (defun copilot--get-panel-completions (callback) "Get panel completions with CALLBACK."