Skip to content

Commit

Permalink
Ensure completion terminates in correct buffer
Browse files Browse the repository at this point in the history
To design a completion-in-region-function replacement that leverages
the elements in completion-at-point-functions, we must ensure that
their :exit-function parts execute in the correct buffer.  That is the
buffer where the text to be completed lives, not necessarily the
buffer being used for user interaction.

Later on, this guarantee should be provided by Emacs itself, perhaps
by putting the correct with-current-buffer call in completion--done.

Copyright-paperwork-exempt: yes
Co-authored-by: João Távora <joaotavora@gmail.com>

* eglot.el (eglot-completion-at-point): Ensure :exit-function's
buffer is where the source is.

GitHub-reference: close joaotavora/eglot#505
  • Loading branch information
andyleejordan authored and joaotavora committed Jul 27, 2020
1 parent ad70179 commit 836127f
Showing 1 changed file with 47 additions and 39 deletions.
86 changes: 47 additions & 39 deletions lisp/progmodes/eglot.el
Original file line number Diff line number Diff line change
Expand Up @@ -2175,45 +2175,53 @@ is not active."
(line-beginning-position))))
:exit-function
(lambda (proxy _status)
(eglot--dbind ((CompletionItem) insertTextFormat
insertText textEdit additionalTextEdits label)
(funcall
resolve-maybe
(or (get-text-property 0 'eglot--lsp-item proxy)
;; When selecting from the *Completions*
;; buffer, `proxy' won't have any properties.
;; A lookup should fix that (github#148)
(get-text-property
0 'eglot--lsp-item
(cl-find proxy (funcall proxies) :test #'string=))))
(let ((snippet-fn (and (eql insertTextFormat 2)
(eglot--snippet-expansion-fn))))
(cond (textEdit
;; Undo (yes, undo) the newly inserted completion.
;; If before completion the buffer was "foo.b" and
;; now is "foo.bar", `proxy' will be "bar". We
;; want to delete only "ar" (`proxy' minus the
;; symbol whose bounds we've calculated before)
;; (github#160).
(delete-region (+ (- (point) (length proxy))
(if bounds (- (cdr bounds) (car bounds)) 0))
(point))
(eglot--dbind ((TextEdit) range newText) textEdit
(pcase-let ((`(,beg . ,end) (eglot--range-region range)))
(delete-region beg end)
(goto-char beg)
(funcall (or snippet-fn #'insert) newText)))
(when (cl-plusp (length additionalTextEdits))
(eglot--apply-text-edits additionalTextEdits)))
(snippet-fn
;; A snippet should be inserted, but using plain
;; `insertText'. This requires us to delete the
;; whole completion, since `insertText' is the full
;; completion's text.
(delete-region (- (point) (length proxy)) (point))
(funcall snippet-fn (or insertText label)))))
(eglot--signal-textDocument/didChange)
(eldoc)))))))
;; To assist in using this whole `completion-at-point'
;; function inside `completion-in-region', ensure the exit
;; function runs in the buffer where the completion was
;; triggered from. This should probably be in Emacs itself.
;; (github#505)
(with-current-buffer (if (minibufferp)
(window-buffer (minibuffer-selected-window))
(current-buffer))
(eglot--dbind ((CompletionItem) insertTextFormat
insertText textEdit additionalTextEdits label)
(funcall
resolve-maybe
(or (get-text-property 0 'eglot--lsp-item proxy)
;; When selecting from the *Completions*
;; buffer, `proxy' won't have any properties.
;; A lookup should fix that (github#148)
(get-text-property
0 'eglot--lsp-item
(cl-find proxy (funcall proxies) :test #'string=))))
(let ((snippet-fn (and (eql insertTextFormat 2)
(eglot--snippet-expansion-fn))))
(cond (textEdit
;; Undo (yes, undo) the newly inserted completion.
;; If before completion the buffer was "foo.b" and
;; now is "foo.bar", `proxy' will be "bar". We
;; want to delete only "ar" (`proxy' minus the
;; symbol whose bounds we've calculated before)
;; (github#160).
(delete-region (+ (- (point) (length proxy))
(if bounds (- (cdr bounds) (car bounds)) 0))
(point))
(eglot--dbind ((TextEdit) range newText) textEdit
(pcase-let ((`(,beg . ,end) (eglot--range-region range)))
(delete-region beg end)
(goto-char beg)
(funcall (or snippet-fn #'insert) newText)))
(when (cl-plusp (length additionalTextEdits))
(eglot--apply-text-edits additionalTextEdits)))
(snippet-fn
;; A snippet should be inserted, but using plain
;; `insertText'. This requires us to delete the
;; whole completion, since `insertText' is the full
;; completion's text.
(delete-region (- (point) (length proxy)) (point))
(funcall snippet-fn (or insertText label)))))
(eglot--signal-textDocument/didChange)
(eldoc))))))))

(defun eglot--hover-info (contents &optional range)
(let ((heading (and range (pcase-let ((`(,beg . ,end) (eglot--range-region range)))
Expand Down

0 comments on commit 836127f

Please sign in to comment.