-
-
Notifications
You must be signed in to change notification settings - Fork 893
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
Feature: inline completions #4623
base: master
Are you sure you want to change the base?
Conversation
…e buffer When a overlay is displayed at the end of the buffer, (window-end) may return a value that is past the end of the buffer. Calling line-number-at-pos with a value outside of the buffer bounds raises an error.
Only the parser
bf4ab0a
to
71c81dd
Compare
71c81dd
to
f5b572b
Compare
9dea79c
to
f9783fd
Compare
c32fa58
to
828875b
Compare
Check if company is active before displaying the overlay -- hide it if so
A note about company mode and the inline completion overlay: Since the completions are not provided by company, we may end up with the company popup displayed alongside (or over) the inline completion overlay (as it happened already with copilot.el). To workaround this issue, I've added code to cancel company before showing the overlay and then restore it on cancel. Kooha-2024-11-26-14-40-17.webmSwitching between the two completion UIs can be achieved by binding Nonetheless, I've added a variable that can inhibit inline completion mode on certain conditions, such as company being active. For example, I have the following snippet in my user settings: (defun lsp-inline-completion-inhibit-if-company-active ()
(and (bound-and-true-p company-mode) (company--active-p)))
(push 'lsp-inline-completion-inhibit-if-company-active lsp-inline-completion-inhibit-predicates) |
Add a list of predicates that can inhibit triggering inline completions -- e.g.: ```elisp (defun lsp-inline-completion-inhibit-if-company-active () (and (bound-and-true-p company-mode) (company--active-p))) (push 'lsp-inline-completion-inhibit-if-company-active lsp-inline-completion-inhibit-predicates) ```
04fa88a
to
da2d8c2
Compare
lsp-protocol.el
Outdated
@@ -624,6 +626,8 @@ See `-let' for a description of the destructuring mechanism." | |||
(CompletionItemKindCapabilities nil (:valueSet)) | |||
(CompletionItemTagSupportCapabilities (:valueSet) nil) | |||
(CompletionOptions nil (:resolveProvider :triggerCharacters :allCommitCharacters)) | |||
(InlineCompletionItem (:insertText) (:filterText :range :command)) |
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.
Please sort this
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.
I've added the relevant interfaces to the end of the list with the version of the protocol that supports them.
Let me know if you want to have the whole interface definition sorted.
lsp-inline-completion.el
Outdated
lsp/inline-completion-trigger-automatic | ||
lsp/inline-completion-trigger-invoked))))) | ||
|
||
;;;###autoload |
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.
I don't think we need to make this function public and have autoload
. Shouldn't it be used for internal function only?
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.
Yeah, I was trying to make the test suite happy when I have the minor mode defined in lsp-mode.el
; forgot to remove it later
lsp-inline-completion.el
Outdated
;; Kludge to workaround multiple backends responding -- it may | ||
;; come as a list (multiple servers) or as a single ht (single | ||
;; server). Promote it to list and move on | ||
(if (ht-p it) (list it) it) |
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.
The type can be ht
or plist
depend on lsp-use-plists
so it would be better to just check the lsp-inline-completion-list?
and then merge or extract and merge items.
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.
Good call!
Though we must check if it is a completion-list or a completion-list-items
result: InlineCompletionItem[] | InlineCompletionList | null defined as follows:
I've refactored the parse function, should not depend on ht anymore
map) | ||
"Keymap active when showing inline code suggestions") | ||
|
||
;;;###autoload |
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.
nit: I think we don't need to make this autoload
lsp-inline-completion.el
Outdated
lsp-inline-completion--overlay) | ||
|
||
|
||
;;;###autoload |
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.
Same here. If this is not an interactive command, then we don't need to make it autoload as if it's called from another package, we can just (require 'lsp-inline-completion)
instead
lsp-inline-completion.el
Outdated
|
||
(lsp-inline-completion--clear-overlay) | ||
|
||
(setq lsp-inline-completion--showing-company |
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.
I wonder if we want to include company-mode
integration here as we can have multiple alternative package provide the same functionality as company-mode
, corfu
for example.
What will happen if we don't cancel company mode here? The overlay from company-mode
will be displayed over the inline completion? Can that be mitigate by making the overlay from inline completion topmost or bottommost?
Also, we can put it as a tip for the user (a simple function that the user can put to their init file) instead? Or even suggest the user to use a different company-mode's front end?
I've been using copilot.el
with company-mode
and haven't really notice issue so far though.
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.
I wonder if we want to include company-mode integration here as we can have multiple alternative package provide the same functionality as company-mode, corfu for example.
Hmmm I agree that having no way of plugging other completion frontends may be an issue -- corfu users would have to advise the inline completion functions to have a similar level of integration, so not very ideal.
What will happen if we don't cancel company mode here?
It depends on what frontends will be active at the time we display the overlay. With the default company tooltip frontend active, I only managed to get one-liners such as in the video below. It does not obsure the company completion list, but pressing tab or whatever uses the inline completion overlay active map, so this would cause some confusion on the user side.
Kooha-2024-11-27-15-58-38.webm
What happens if company-preview frontend is active? Not sure, probably the inline completion would obscure the preview completely.
If the user uses company-box or company-posframe (my case), then overlay priority makes no difference because company displays completions in a child frame :/
a tip for the user (a simple function that the user can put to their init file) instead?
Good one!
I've refactored the company integration into a minor mode. The user can then set up their code such as in the following snippet:
(require 'lsp-inline-completion)
;; Show the current/total plus keys in the
(add-hook 'lsp-inline-completion-shown-hook #'lsp-inline-completion-show-keys)
;; Make inline completions play nice with company mode
(add-hook 'lsp-inline-completion-mode-hook #'lsp-inline-completion-company-integration-mode)
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.
but pressing tab or whatever uses the inline completion overlay active map, so this would cause some confusion on the user side.
Maybe we should bind to other keys for that? I think Vs Code use other key bindings to accept inline completion item compare to the normal completion items as well?
For example, I binded something like this for copilot.el
.
;; http://endlessparentheses.com/define-context-aware-keys-in-emacs.html
(defmacro define-conditional-key (keymap key def cond &rest bindings)
"In KEYMAP, define key sequence KEY as DEF conditionally.
This is like `define-key', except the definition disappears
whenever COND evaluates to nil.
BINDINGS is a list of (key def cond)."
(declare (indent defun)
(debug (form form form form &rest sexp)))
(cl-labels ((partition (n seq)
(seq-partition seq n)))
(thread-last
bindings
(append `(,key ,def ,cond))
(partition 3)
(mapcar (pcase-lambda (`(,key ,def ,cond))
`(define-key ,keymap ,key
'(menu-item
,(format "maybe-%s" (or (car (cdr-safe def)) def))
nil
:filter (lambda (&optional _)
(if ,cond ,def))))))
macroexp-progn)))
(define-conditional-key copilot-completion-map
(kbd "C-<return>") #'copilot-accept-completion (copilot--overlay-visible)
(kbd "C-j") #'copilot-accept-completion (copilot--overlay-visible)
(kbd "M-<return>") #'copilot-accept-completion-by-line (copilot--overlay-visible)
(kbd "C-n") #'copilot-next-completion (copilot--overlay-visible)
(kbd "C-p") #'copilot-previous-completion (copilot--overlay-visible))
lsp-mode.el
Outdated
@@ -9934,6 +9957,43 @@ string." | |||
(setf window-scroll-functions | |||
(delete #'lsp--update-inlay-hints-scroll-function window-scroll-functions))))) | |||
|
|||
|
|||
;; Inline Completions |
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.
nit: If possible we should move this to lsp-inline-completion.el
instead.
An auto hook can be added to lsp-inline-completion
to autoload this feature if it's on.
Example:
;;;###autoload
(add-hook 'lsp-configure-hook (lambda ()
(when (and lsp-auto-configure
lsp-completion-enable)
(lsp-completion--enable))))
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.
Oh, that's a neat trick!
lsp-mode.el
Outdated
"Mode automatically displaying inline completions." | ||
:lighter nil | ||
(cond | ||
((and lsp-inline-completion-mode lsp--buffer-workspaces) |
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.
Can we use lsp-on-idle-hook
instead? The lsp-inlay-hints-mode
is using that and it has been working well so far. It also should fire less frequently compared to on-change-hook
as when typing, we're likely don't want to request inline completion anyway.
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.
Hmm, I don't think that's a good idea.
I've tested here, and triggering it via lsp-idle-hook
means we get suggestions after moving the cursor around -- I don't think a user would expect that (I, for one, don't).
Regarding the change hook being more aggressive than idle: Since lsp's idle timer is set both on post-command-hook
and on after-change-hook
, I don't see how that could be.
In my implementation, what actually happens in after-change
is a cancel-timer
and run-with-timer
(i.e. timer reschedule). We don't ping the server after each change -- we ping the server after lsp-inline-completion-idle-delay
seconds have elapsed without any change in the buffer.
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.
Ah okay. The Visual Studio will trigger inline completion on mouse move as well while the VS Code seems to only trigger it on buffer modification. Whichever as default is fine for me :)
Thank you @kassick for your contribution. I've left some comments in the PR. The feature looks good to me :) |
Also, refactor how we parse the response, using pcase for a more explicit approach
Users can enable the lsp-inline-completion-company-integration-mode if they see fit. Other completion frontends may be customized via hooks, just as this minor mode does
(cancel-timer lsp-inline-completion--idle-timer)) | ||
(let ((buffer (current-buffer))) | ||
(setq lsp-inline-completion--idle-timer | ||
(run-with-timer lsp-inline-completion-idle-delay |
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.
nit: run-with-idle-timer
?
|
||
(defun lsp-inline-completion--after-change (&rest _) | ||
(when (and lsp-inline-completion-mode lsp--buffer-workspaces) | ||
(when lsp-inline-completion--idle-timer |
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.
perhaps we can move this outside of the when
check to make sure lsp-inline-completion--idle-timer
always be cancelled?
(defvar-local lsp-inline-completion--idle-timer nil | ||
"The idle timer used by lsp-inline-completion-mode") | ||
|
||
(define-minor-mode lsp-inline-completion-mode |
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.
I think we need to mark this as autoload
as well so the add-hook
autoload below, when called will know how to trigger this.
(mapconcat 'string-trim-right | ||
(split-string (lsp--render-on-hover-content contents t) "\n") | ||
"\n")))) | ||
(mapconcat 'string-trim-right |
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.
Is the indentation here correct?
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.
I think this looks good now, just a few nit comments you might want to consider for a smooth load of this mode.
lsp-inline-completion--overlay) | ||
|
||
|
||
(defun lsp-inline-completion-show-keys () |
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.
nit: is this called from somewhere? Should we make this a command instead of just a function?
(setq lsp-inline-completion--start-point (point)) | ||
(lsp-inline-completion-show-overlay)) | ||
(unless implicit | ||
(message "No Suggestions!"))) |
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.
nit: lsp--info
or lsp--warn
?
As discussed in #4581, this PR adds support for Inline Completions
Using some server with
inlineCompletionProvider
capability (I'm using the copilot-node-server with this implementation), the inline completion is triggered automatically iflsp-inline-completion-enable
is set tot
. The user can also calllsp-inline-completion-display
explicitly to have the completion displayed.Optionally, the user can add
lsp-inline-completion-show-keys
to thelsp-inline-completion-shown-hook
and the number of available completions and the keys used to interact with the tooltips are shown in the minibuffer.When the inline completion tooltip is being displayed, pressing any key that is not mapped in
lsp-inline-completion-active-map
causes the tooltip to be hidden and the key is processed with the current active keymap. This way, if the completion is shown automatically, the user can continue typing to have the completion ignored.