Skip to content

feat: customizable lsp event handler and lsp server log #339

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,31 @@ For example:
(setq copilot-network-proxy '(:host "127.0.0.1" :port 7890))
```

### copilot-on-request
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, add blank lines after each heading.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And before the code snippets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

; 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
Expand All @@ -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
Expand Down
94 changes: 70 additions & 24 deletions copilot.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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*")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea to extra the buffer names to constants.

Copy link
Contributor Author

@knilink knilink Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ATM, I was trying to stay consistent with the existing code and I think this can be addressed in a different PR.
There were inconsistencies as well such *copilot stderr* doesn't use hyphen like the rest which can be also fixed together.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a fair point.

(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."
Expand Down