Skip to content

make the copilot-panel interactive - #463

Open
robert-zaremba wants to merge 8 commits into
copilot-emacs:mainfrom
robert-zaremba:interactive-panel
Open

make the copilot-panel interactive#463
robert-zaremba wants to merge 8 commits into
copilot-emacs:mainfrom
robert-zaremba:interactive-panel

Conversation

@robert-zaremba

@robert-zaremba robert-zaremba commented Mar 17, 2026

Copy link
Copy Markdown

Previously: calling copilot-panel-complete simply generated a buffer with a formatted list of suggested solutions. However, there was no built-in way to interact with those solutions - usere had to manually select and copy them.

This update: interactive commands and local keybindings to easily extract, copy, and insert these suggested code blocks directly from the panel.

Local binding in the copilot-panel:

  • C-c i: copilot-panel-insert-suggestion - insert suggestion at point.
  • C-c y: copilot-panel-copy-suggestion - copy suggestion at point
  • C-c s: copilot-panel-select-suggestion - prompt user to select a suggestion from the panel and copy it. Initializes copilot-panel if it's absent.
  • C-c g: copilot-panel-kill. A quick helper to cleanly kill the panel buffer.

Previously: calling copilot-panel-complete simply generated a buffer
with a formatted list of suggested solutions. However, there was no
built-in way to interact with those solutions - usere had to manually
select and copy them.

This update: interactive commands and local keybindings to easily
extract, copy, and insert these suggested code blocks directly from the panel.

Local binding in the copilot-panel:

- C-c C-c: copilot-insert-suggestion-at-point.
- C-c C-y: copilot-copy-function-at-point.
- C-c C-s: copilot-select-and-copy-solution.
- C-c C-g: copilot-kill-panel-buffer. A quick helper to cleanly kill the panel buffer.

@bbatsov bbatsov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for contributing! The idea of making the panel interactive is good, but there are several issues to address before this can be merged. See inline comments.

Comment thread copilot.el
(string :tag "Specific Version"))
(string :tag "Specific Version"))
:group 'copilot
:package-version '(copilot . "0.1"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Unrelated whitespace change — please drop this hunk (and the two similar ones for copilot-completion-model and copilot--async-request).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

formatting was done automatically in my Emacs (apheleia). I 've rolling back the formatting changes.

Comment thread copilot.el Outdated
(org-mode)
(erase-buffer)))
(erase-buffer)
(copilot--setup-panel-keybindings)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

local-set-key mutates the shared org-mode keymap, which will leak these bindings into all org buffers. Define a minor mode with its own keymap instead (or a derived mode like copilot-chat-mode does).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment thread copilot.el Outdated

(defun copilot--setup-panel-keybindings ()
"Setup keybindings for the copilot panel buffer."
(local-set-key (kbd "C-c C-c") 'copilot-insert-suggestion-at-point)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These should use the copilot-panel- prefix to match the existing copilot-panel-complete convention. E.g. copilot-panel-insert-suggestion, copilot-panel-copy-suggestion, etc.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done. I've updated the PR description as well

Comment thread copilot.el
"Insert the suggesetion at point from the copilot panel to the original buffer."
(interactive)
(let ((solution-text (copilot--get-current-solution-text)))
(when solution-text

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Typo: "suggesetion" → "suggestion"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment thread copilot.el Outdated
(other-window 1) ; Switch to previous window (original buffer)
(yank) ; Insert the solution
(message "Solution inserted and copied to clipboard"))))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Using (other-window 1) is fragile — it assumes the source buffer is in the adjacent window. Track the source buffer explicitly (similar to how copilot-chat--source-buffer works) and switch to it by name.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment thread copilot.el Outdated
(let ((heading-start (match-beginning 0))
(solution-text (copilot--get-current-solution-text-at-pos (point))))
(when solution-text
(push (list :title (format "Solution at line %d" (line-number-at-pos heading-start))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

copilot--get-current-solution-text and copilot--get-current-solution-text-at-pos are nearly identical. The former should just call the latter:

(defun copilot--get-current-solution-text ()
  (copilot--get-current-solution-text-at-pos (point)))

Also: the start binding on the first line of the let is immediately overwritten inside the when block, and end is set but never used ((line-beginning-position) is used instead). Please clean up the unused variables.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

reworked

Signed-off-by: Robert Zaremba <robert@zaremba.ch>
Created copilot-panel-mode minor mode with its own keymap (copilot-panel-mode-map) instead of using local-set-ke

Renamed functions with copilot-panel- prefix:
   - copilot-insert-suggestion-at-point → copilot-panel-insert-suggestion
   - copilot-copy-suggestion-at-point → copilot-panel-copy-suggestion
   - copilot-select-and-copy-suggestion → copilot-panel-select-and-copy-suggestion
   - copilot-kill-panel-buffer → copilot-panel-kill-buffer

Added copilot-panel--source-buffer buffer-local variable to track the source buffer explicitly. Updated copilot-

Refactored copilot--get-current-solution-text to simply call
copilot--get-current-solution-text-at-pos with (point).

Cleaned up unused variables (start and end) in copilot--get-current-solution-text-at-pos.
- Update suggestion selection list to add a sequence number to the headers
Copilot AI review requested due to automatic review settings March 22, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR makes the *copilot-panel* output actionable by adding interactive commands and keybindings intended to let users copy/insert panel suggestions back into the originating buffer.

Changes:

  • Add panel-oriented interactive commands (select/copy/insert/kill) and attempt to add a dedicated panel keymap.
  • Adjust panel notification handling to support sorting/renaming solutions and to allow buffer modifications when needed.
  • Document the new commands and suggested keybindings in README and CHANGELOG.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 11 comments.

File Description
copilot.el Adds panel interaction commands, keymap scaffolding, and updates panel notification handling/sorting logic.
README.md Updates configuration examples and command list to include the new panel commands/bindings.
CHANGELOG.md Notes the new interactive panel feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread copilot.el
Comment thread copilot.el Outdated
Comment thread copilot.el Outdated
Comment thread copilot.el
Comment on lines +1193 to +1196
(erase-buffer))
(org-mode) ; Keep org-mode as the true major mode
(copilot-panel-mode 1) ; Activate our custom keybindings safely
(setq copilot-panel--source-buffer source-buffer))))

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

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

copilot-panel-mode is enabled in copilot-panel-complete, but there is no define-minor-mode (or other definition) for copilot-panel-mode in this file. Calling (copilot-panel-mode 1) will signal a void-function error and the panel keybindings won't work. Define copilot-panel-mode as a minor mode (with :keymap copilot-panel-mode-map) or stop calling it here.

Copilot uses AI. Check for mistakes.
Comment thread copilot.el
Comment on lines +1016 to 1021
;; Safely clear old handlers from memory to prevent duplicate firing / timer errors
(setq copilot--notification-handlers (make-hash-table :test 'equal))

(defvar copilot--notification-handlers (make-hash-table :test 'equal)
"Hash table storing lists of notification handlers.")

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

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

This unconditional (setq copilot--notification-handlers ...) happens before the defvar, which will trigger "assignment to free variable" warnings when byte-compiling. If you need to clear handlers on reload, prefer defining the var first and using (clrhash copilot--notification-handlers) (or reinitializing it) in a dedicated init/reset function.

Suggested change
;; Safely clear old handlers from memory to prevent duplicate firing / timer errors
(setq copilot--notification-handlers (make-hash-table :test 'equal))
(defvar copilot--notification-handlers (make-hash-table :test 'equal)
"Hash table storing lists of notification handlers.")
(defvar copilot--notification-handlers (make-hash-table :test 'equal)
"Hash table storing lists of notification handlers.")
;; Safely clear old handlers from memory to prevent duplicate firing / timer errors
(when (hash-table-p copilot--notification-handlers)
(clrhash copilot--notification-handlers))

Copilot uses AI. Check for mistakes.
Comment thread copilot.el
(message "Solution copied to clipboard"))))

(defun copilot-panel-insert-suggestion ()
"Insert the suggesetion at point from the copilot panel to the original buffer."

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

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

Docstring typo: "suggesetion" should be "suggestion".

Suggested change
"Insert the suggesetion at point from the copilot panel to the original buffer."
"Insert the suggestion at point from the copilot panel to the original buffer."

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment thread README.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread copilot.el

@robert-zaremba robert-zaremba left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the review @bbatsov . I've updated the code to address all suggestions. Moreover:

  • Renamed copilot-panel-select-and-copy-suggestion to copilot-panel-select-suggestion: it will insert selected suggestion directly, rather than copying it. It will also launch copilot-panel if it's missing. NOTE: user must run copilot-panel-select-suggestion again once the suggestions are ready.
  • Updated readme to suggest 2 keybindings to launch copilot-panel.

Comment thread copilot.el
(string :tag "Specific Version"))
(string :tag "Specific Version"))
:group 'copilot
:package-version '(copilot . "0.1"))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

formatting was done automatically in my Emacs (apheleia). I 've rolling back the formatting changes.

Comment thread copilot.el Outdated
(org-mode)
(erase-buffer)))
(erase-buffer)
(copilot--setup-panel-keybindings)))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment thread copilot.el Outdated

(defun copilot--setup-panel-keybindings ()
"Setup keybindings for the copilot panel buffer."
(local-set-key (kbd "C-c C-c") 'copilot-insert-suggestion-at-point)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done. I've updated the PR description as well

Comment thread copilot.el
"Insert the suggesetion at point from the copilot panel to the original buffer."
(interactive)
(let ((solution-text (copilot--get-current-solution-text)))
(when solution-text

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment thread copilot.el Outdated
(other-window 1) ; Switch to previous window (original buffer)
(yank) ; Insert the solution
(message "Solution inserted and copied to clipboard"))))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment thread copilot.el Outdated
(let ((heading-start (match-beginning 0))
(solution-text (copilot--get-current-solution-text-at-pos (point))))
(when solution-text
(push (list :title (format "Solution at line %d" (line-number-at-pos heading-start))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

reworked

Signed-off-by: Robert Zaremba <robert@zaremba.ch>
@robert-zaremba
robert-zaremba requested a review from bbatsov March 22, 2026 16:16

@bbatsov bbatsov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the updates. The core feature is useful but there are still several issues.

Blocker: copilot-panel-mode is called at line 1193 but never defined with define-minor-mode — the panel keybindings won't work at all (void-function error at runtime).

Noise: The diff still has large hunks of pure re-indentation (notification handlers, copilot-login, copilot--show-completion, window/showDocument, $/progress) that are unrelated to the panel feature. Please revert those so the actual changes are reviewable.

See inline comments for details.

Comment thread copilot.el
@@ -197,8 +197,10 @@ will not be called."
(let ((was-bound (boundp symbol)))
(set-default symbol value)
(when (and was-bound (copilot--connection-alivep))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This changes copilot--notify to a direct jsonrpc-notify call with a comment about macro definition order. This is unrelated to the panel feature and should be in a separate commit/PR if needed.

Comment thread copilot.el
(copilot--log 'info "Authenticated as GitHub user %s." user))))

(defun copilot-logout ()
"Logout from Copilot."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This entire function is just re-indented with no functional change. The diff still has large hunks of pure whitespace changes here, in all the notification handlers, and in copilot--show-completion. Please revert these so only the actual panel changes are in the diff.

Comment thread copilot.el
@@ -1011,6 +1013,9 @@ Each request METHOD can have only one HANDLER."
(when handler
(funcall handler msg))))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

setq before defvar will produce a byte-compile "assignment to free variable" warning. Remove this line — the defvar on line 1018 already initializes the variable. If you need to clear handlers on reload, do it after the defvar with (clrhash copilot--notification-handlers).

Comment thread copilot.el
(3 'success)
(2 'warning)
(1 'error)))))))))
(with-current-buffer (get-buffer-create "*copilot-language-server-log*")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Switching from (with-current-buffer "*copilot-panel*" ...) to (get-buffer-create "*copilot-panel*") means if a notification arrives before copilot-panel-complete creates and initializes the buffer, this will create a fresh buffer without org-mode. The org-map-entries call on the next line will then fail. Either use get-buffer and bail if nil, or ensure org-mode is active before calling org functions.

Comment thread copilot.el
(copilot--log 'info "Synthesizing %d solutions..." solutionCountTarget)))
(with-current-buffer (get-buffer-create "*copilot-panel*")
(let ((inhibit-read-only t))
(erase-buffer))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's no define-minor-mode for copilot-panel-mode anywhere — this will signal a void-function error at runtime and the keybindings won't work at all. This is the main blocker. You need:

(define-minor-mode copilot-panel-mode
  "Minor mode for the Copilot panel buffer."
  :keymap copilot-panel-mode-map)

Place it right after the copilot-panel-mode-map definition.

Comment thread copilot.el
(interactive)
(let ((solution-text (copilot--get-current-solution-text))
(source-buffer copilot-panel--source-buffer))
(when solution-text

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Typo still present from the first review round: "suggesetion" -> "suggestion".

Comment thread copilot.el
(pop-to-buffer source-buffer)
(yank)
(message "Solution inserted and copied to clipboard"))
(message "Source buffer is no longer alive")))))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

kill-new + yank pollutes the user's kill ring as a side effect. Just use (insert solution-text) directly in the source buffer — copilot-panel-select-suggestion already does this correctly.

Comment thread copilot.el

(defun copilot--get-all-solutions ()
"Extract all solutions from the copilot panel buffer."
(with-current-buffer "*copilot-panel*"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

kill-buffer-and-window will error if the panel buffer isn't displayed in a window. Use something like:

(when-let ((buf (get-buffer "*copilot-panel*")))
  (when-let ((win (get-buffer-window buf)))
    (delete-window win))
  (kill-buffer buf))

Comment thread copilot.el
(let* ((heading-text (match-string-no-properties 1))
(solution-text (copilot--get-current-solution-text-at-pos (line-beginning-position))))
(when solution-text
(push (list :title heading-text

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This only matches Solution [0-9]+ (numbered headings), but solutions are initially inserted as * Solution without a number. They only get numbered in PanelSolutionsDone. If the user calls this while solutions are still arriving, no matches will be found and it will incorrectly report "still generating". Match both forms:

(while (re-search-forward "^\\*+ \\(Solution\\(?: [0-9]+\\)?\\)" nil t)

Comment thread README.md
(keymap-set copilot-completion-map "C-c s" #'copilot-panel-select-suggestion)
(keymap-set copilot-completion-map "C-c p" #'copilot-panel-complete)
```

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Binding C-c s and C-c p in copilot-completion-map doesn't make sense — the completion map is only active when an inline completion overlay is visible. Panel commands should be usable anytime. Suggest documenting these as user-level keybinding examples only (e.g. in a personal keymap), not in copilot-completion-map.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants