-
Notifications
You must be signed in to change notification settings - Fork 126
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
[Improvement] Defer sending doc changes to the agent to an idle timer. #176
Open
raymond-w-ko
wants to merge
23
commits into
copilot-emacs:main
Choose a base branch
from
raymond-w-ko:fix-gh-172
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
fee129e
queue doc changes to copilot agent until after-change-functions
raymond-w-ko 2254e99
refactor out pending changes and flush in multiple places
raymond-w-ko 0c9be53
fix wrong usage of nreverse in copilot--flush-pending-doc-changes
raymond-w-ko a53e6b8
add copilot--flush-pending-doc-changes to a few more places
raymond-w-ko 0e00a1f
rewrite doc change fn to nest save-* properly
raymond-w-ko a0a685f
add a copilot-resync-buffer command
raymond-w-ko c5a10f8
Merge branch 'main' of github.com:copilot-emacs/copilot.el into fix-g…
emil-vdw 9e3474b
Remove commented code
emil-vdw 1fbd323
Improve formatting
emil-vdw 89746d1
Use more descriptive variable name
emil-vdw 0a3be71
Remove unused vars and functions
emil-vdw 301609a
Remove unnecessary flush doc change calls
emil-vdw 1ec9954
Improve docstrings
emil-vdw 677a6e8
Merge branch 'main' of github.com:copilot-emacs/copilot.el into fix-g…
emil-vdw 292fb41
Rework flush change queue mechanism
emil-vdw 7de1053
Flush doc changes before request
emil-vdw 2eb6366
Add group property
emil-vdw 9eafc05
Improve idle timer set check
emil-vdw ce63aac
Use nil instead of empty list
emil-vdw 2ad7b95
Make renamed defcustom obsolete
emil-vdw 4a6b626
Fix broken sync-doc function
emil-vdw 5f46c6e
Remove unnecessary widen calls
emil-vdw 072faa0
Flush changes before generating panel completions
emil-vdw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
;;; copilot.el --- An unofficial Copilot plugin for Emacs -*- lexical-binding:t -*- | ||
|
||
;; Package-Requires: ((emacs "27.2") (s "1.12.0") (dash "2.19.1") (editorconfig "0.8.2") (jsonrpc "1.0.14") (f "0.20.0")) | ||
;; Version: 0.0.1 | ||
;; Version: 0.1.0 | ||
;; URL: https://github.com/copilot-emacs/copilot.el | ||
|
||
;;; Commentary: | ||
|
@@ -26,7 +26,7 @@ | |
:group 'completion | ||
:prefix "copilot-") | ||
|
||
(defcustom copilot-idle-delay 0 | ||
(defcustom copilot-completion-idle-delay 0 | ||
"Time in seconds to wait before starting completion. | ||
|
||
Complete immediately if set to 0. | ||
|
@@ -36,6 +36,9 @@ Disable idle completion if set to nil." | |
(const :tag "Idle completion disabled" nil)) | ||
:group 'copilot) | ||
|
||
(defvaralias 'copilot-idle-delay 'copilot-completion-idle-delay) | ||
(make-obsolete-variable 'copilot-idle-delay 'copilot-completion-idle-delay "0.1.0") | ||
|
||
(defcustom copilot-network-proxy nil | ||
"Network proxy to use for Copilot. Nil means no proxy. | ||
Format: \='(:host \"127.0.0.1\" :port 80 | ||
|
@@ -117,6 +120,11 @@ You may adjust this variable at your own risk." | |
:type 'string | ||
:group 'copilot) | ||
|
||
(defcustom copilot-send-changes-idle-time 0.5 | ||
"Don't tell server of changes before Emacs's been idle for this many seconds." | ||
:type 'number | ||
:group 'copilot) | ||
|
||
(defvar-local copilot--overlay nil | ||
"Overlay for Copilot completion.") | ||
|
||
|
@@ -130,8 +138,10 @@ You may adjust this variable at your own risk." | |
"Line bias for Copilot completion.") | ||
|
||
(defvar copilot--post-command-timer nil) | ||
|
||
(defvar-local copilot--last-doc-version 0 | ||
"The document version of the last completion.") | ||
|
||
(defvar-local copilot--doc-version 0 | ||
"The document version of the current buffer. Incremented after each change.") | ||
|
||
|
@@ -558,13 +568,130 @@ automatically, browse to %s." user-code verification-uri)) | |
(setq copilot--last-doc-version copilot--doc-version) | ||
(setq copilot--panel-lang (copilot--get-language-id)) | ||
|
||
(when (or copilot--change-idle-timer copilot--doc-change-queue) | ||
(copilot--ensure-server-up-to-date)) | ||
|
||
(copilot--get-panel-completions | ||
(jsonrpc-lambda (&key solutionCountTarget) | ||
(message "Copilot: Synthesizing %d solutions..." solutionCountTarget))) | ||
(with-current-buffer (get-buffer-create "*copilot-panel*") | ||
(org-mode) | ||
(erase-buffer))) | ||
|
||
;; | ||
;; Doc changes | ||
;; | ||
(cl-defmacro copilot--widening (&rest body) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels like this change doens't quite fit the PR description? Separate PR? Not clear to me why it's necessary since the point should be within the narrowed region, right? |
||
"Save excursion and restriction. Widen. Then run BODY." (declare (debug t)) | ||
`(save-excursion (save-restriction (widen) ,@body))) | ||
|
||
(defvar-local copilot--doc-change-queue nil | ||
"Pending queue of document changes to be sent to the copilot agent. | ||
Each element is a list of document change parameters to be sent to the agent.") | ||
|
||
(defvar-local copilot--change-idle-timer nil | ||
"Idle timer for didChange signals.") | ||
|
||
(defun copilot--ensure-server-up-to-date () | ||
"Ensure that the copilot server is ready by immediately sending any | ||
pending changes." | ||
(when (timerp copilot--change-idle-timer) | ||
(cancel-timer copilot--change-idle-timer) | ||
;; We flush the changes using the lambda constructed in copilot--on-doc-change | ||
;; so that we can ensure that the buffer is still alive and in copilot mode. | ||
(funcall (timer--function copilot--change-idle-timer)) | ||
(setq copilot--change-idle-timer nil))) | ||
|
||
(defun copilot-sync-doc () | ||
"Re-sync the document with the copilot agent." | ||
(interactive) | ||
(copilot--ensure-server-up-to-date) | ||
(cl-incf copilot--doc-version) | ||
(copilot--notify | ||
'textDocument/didChange | ||
(list :textDocument | ||
(list :uri (copilot--get-uri) :version copilot--doc-version) | ||
:contentChanges | ||
(vector `(:text ,(copilot--widening | ||
(buffer-substring-no-properties (point-min) | ||
(point-max)))))))) | ||
|
||
(defun copilot--flush-pending-doc-changes () | ||
"Flush the pending document changes to the copilot agent." | ||
(dolist (doc-change (nreverse copilot--doc-change-queue)) | ||
(copilot--notify 'textDocument/didChange doc-change)) | ||
(setq copilot--doc-change-queue nil)) | ||
|
||
(defun copilot--on-doc-change (&optional beg end chars-replaced) | ||
"Notify that the document has changed." | ||
(let* ((is-before-change (eq chars-replaced nil)) | ||
(is-after-change (not is-before-change)) | ||
;; for a deletion, the post-change beginning and end are at the same place. | ||
(is-insertion (and is-after-change (not (equal beg end)))) | ||
(is-deletion (and is-before-change (not (equal beg end))))) | ||
(when (or is-insertion is-deletion) | ||
(copilot--widening | ||
(let* ((range-start-line (- (line-number-at-pos beg) copilot--line-bias)) | ||
(range-end-line (- (line-number-at-pos end) copilot--line-bias)) | ||
(range-start (list :line range-start-line | ||
:character (- beg (progn (goto-char beg) | ||
(line-beginning-position))))) | ||
(range-end (if is-insertion | ||
range-start | ||
(list :line range-end-line | ||
:character (- end (progn (goto-char end) | ||
(line-beginning-position)))))) | ||
(text (if is-insertion (buffer-substring-no-properties beg end) "")) | ||
(content-changes (vector (list :range (list :start range-start :end range-end) | ||
:text text)))) | ||
(cl-incf copilot--doc-version) | ||
(push (list :textDocument (list :uri (copilot--get-uri) :version copilot--doc-version) | ||
:contentChanges content-changes) | ||
copilot--doc-change-queue)))) | ||
|
||
;; If this is the after change hook, we need to set/reset the idle timer | ||
;; that will flush the pending changes to the copilot agent. | ||
(when is-after-change | ||
;; If the idle timer is already set, cancel it and set a new one. | ||
(when (timerp copilot--change-idle-timer) | ||
(cancel-timer copilot--change-idle-timer)) | ||
(let ((buf (current-buffer))) | ||
(setq copilot--change-idle-timer | ||
(run-with-idle-timer | ||
copilot-send-changes-idle-time | ||
nil (lambda () | ||
;; Only flush the pending changes if the buffer is still | ||
;; alive and in copilot mode. | ||
(when (buffer-live-p buf) | ||
(with-current-buffer buf | ||
(when copilot-mode | ||
(copilot--flush-pending-doc-changes) | ||
(setq copilot--change-idle-timer nil))))))))))) | ||
|
||
(defun copilot--on-doc-focus (window) | ||
"Notify that the document has been focussed or opened." | ||
;; When switching windows, this function is called twice, once for the | ||
;; window losing focus and once for the window gaining focus. We only want to | ||
;; send a notification for the window gaining focus and only if the buffer has | ||
;; copilot-mode enabled. | ||
(when (and copilot-mode (eq window (selected-window))) | ||
(if (-contains-p copilot--opened-buffers (current-buffer)) | ||
(copilot--notify ':textDocument/didFocus | ||
(list :textDocument (list :uri (copilot--get-uri)))) | ||
(add-to-list 'copilot--opened-buffers (current-buffer)) | ||
(copilot--notify ':textDocument/didOpen | ||
(list :textDocument (list :uri (copilot--get-uri) | ||
:languageId (copilot--get-language-id) | ||
:version copilot--doc-version | ||
:text (copilot--get-source))))))) | ||
|
||
(defun copilot--on-doc-close (&rest _args) | ||
"Notify that the document has been closed." | ||
(when (-contains-p copilot--opened-buffers (current-buffer)) | ||
(copilot--notify 'textDocument/didClose | ||
(list :textDocument (list :uri (copilot--get-uri)))) | ||
(setq copilot--opened-buffers (delete (current-buffer) copilot--opened-buffers)))) | ||
|
||
;; | ||
;; UI | ||
;; | ||
|
@@ -710,84 +837,37 @@ Use TRANSFORM-FN to transform completion if provided." | |
:end (:character end-char))) | ||
completion-data | ||
(when (= doc-version copilot--doc-version) | ||
(save-excursion | ||
(save-restriction | ||
(widen) | ||
(let* ((p (point)) | ||
(goto-line! (lambda () | ||
(goto-char (point-min)) | ||
(forward-line (1- (+ line copilot--line-bias))))) | ||
(start (progn | ||
(funcall goto-line!) | ||
(forward-char start-char) | ||
(let* ((cur-line (buffer-substring-no-properties (point) (line-end-position))) | ||
(common-prefix-len (length (s-shared-start text cur-line)))) | ||
(setq text (substring text common-prefix-len)) | ||
(forward-char common-prefix-len) | ||
(point)))) | ||
(end (progn | ||
(copilot--widening | ||
(let* ((p (point)) | ||
emil-vdw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(goto-line! (lambda () | ||
(goto-char (point-min)) | ||
(forward-line (1- (+ line copilot--line-bias))))) | ||
(start (progn | ||
(funcall goto-line!) | ||
(forward-char end-char) | ||
(point))) | ||
(fixed-completion (copilot-balancer-fix-completion start end text))) | ||
(goto-char p) | ||
(pcase-let ((`(,start ,end ,balanced-text) fixed-completion)) | ||
(copilot--display-overlay-completion balanced-text uuid start end))))))))) | ||
|
||
(defun copilot--on-doc-focus (window) | ||
"Notify that the document has been focussed or opened." | ||
;; When switching windows, this function is called twice, once for the | ||
;; window losing focus and once for the window gaining focus. We only want to | ||
;; send a notification for the window gaining focus and only if the buffer has | ||
;; copilot-mode enabled. | ||
(when (and copilot-mode (eq window (selected-window))) | ||
(if (-contains-p copilot--opened-buffers (current-buffer)) | ||
(copilot--notify ':textDocument/didFocus | ||
(list :textDocument (list :uri (copilot--get-uri)))) | ||
(add-to-list 'copilot--opened-buffers (current-buffer)) | ||
(copilot--notify ':textDocument/didOpen | ||
(list :textDocument (list :uri (copilot--get-uri) | ||
:languageId (copilot--get-language-id) | ||
:version copilot--doc-version | ||
:text (copilot--get-source))))))) | ||
|
||
(defun copilot--on-doc-change (&optional beg end chars-replaced) | ||
"Notify that the document has changed." | ||
(let* ((is-before-change (eq chars-replaced nil)) | ||
(is-after-change (not is-before-change)) | ||
;; for a deletion, the post-change beginning and end are at the same place. | ||
(is-insertion (and is-after-change (not (equal beg end)))) | ||
(is-deletion (and is-before-change (not (equal beg end))))) | ||
(when (or is-insertion is-deletion) | ||
(save-restriction | ||
(widen) | ||
(let* ((range-start (list :line (- (line-number-at-pos beg) copilot--line-bias) | ||
:character (- beg (save-excursion (goto-char beg) (line-beginning-position))))) | ||
(range-end (if is-insertion range-start | ||
(list :line (- (line-number-at-pos end) copilot--line-bias) | ||
:character (- end (save-excursion (goto-char end) (line-beginning-position)))))) | ||
(text (if is-insertion (buffer-substring-no-properties beg end) "")) | ||
(content-changes (vector (list :range (list :start range-start :end range-end) | ||
:text text)))) | ||
(cl-incf copilot--doc-version) | ||
(copilot--notify 'textDocument/didChange | ||
(list :textDocument (list :uri (copilot--get-uri) :version copilot--doc-version) | ||
:contentChanges content-changes))))))) | ||
|
||
(defun copilot--on-doc-close (&rest _args) | ||
"Notify that the document has been closed." | ||
(when (-contains-p copilot--opened-buffers (current-buffer)) | ||
(copilot--notify 'textDocument/didClose | ||
(list :textDocument (list :uri (copilot--get-uri)))) | ||
(setq copilot--opened-buffers (delete (current-buffer) copilot--opened-buffers)))) | ||
(forward-char start-char) | ||
(let* ((cur-line (buffer-substring-no-properties (point) (line-end-position))) | ||
(common-prefix-len (length (s-shared-start text cur-line)))) | ||
(setq text (substring text common-prefix-len)) | ||
(forward-char common-prefix-len) | ||
(point)))) | ||
(end (progn | ||
(funcall goto-line!) | ||
(forward-char end-char) | ||
(point))) | ||
(fixed-completion (copilot-balancer-fix-completion start end text))) | ||
(goto-char p) | ||
(pcase-let ((`(,start ,end ,balanced-text) fixed-completion)) | ||
(copilot--display-overlay-completion balanced-text uuid start end)))))))) | ||
|
||
|
||
;;;###autoload | ||
(defun copilot-complete () | ||
"Complete at the current point." | ||
(interactive) | ||
;; Make sure the server is up to date before sending the completion request. | ||
(when (or copilot--change-idle-timer copilot--doc-change-queue) | ||
(copilot--ensure-server-up-to-date)) | ||
(setq copilot--last-doc-version copilot--doc-version) | ||
|
||
(setq copilot--completion-cache nil) | ||
(setq copilot--completion-idx 0) | ||
|
||
|
@@ -913,9 +993,9 @@ Use this for custom bindings in `copilot-mode'.") | |
(copilot-clear-overlay) | ||
(when copilot--post-command-timer | ||
(cancel-timer copilot--post-command-timer)) | ||
(when (numberp copilot-idle-delay) | ||
(when (numberp copilot-completion-idle-delay) | ||
(setq copilot--post-command-timer | ||
(run-with-idle-timer copilot-idle-delay | ||
(run-with-idle-timer copilot-completion-idle-delay | ||
nil | ||
#'copilot--post-command-debounce | ||
(current-buffer)))))) | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the motivation for this change?
Personally, I think
copilot-idle-delay
is fine and probably doesn't warrant the complexity of adding a deprecated variable.