Skip to content
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

eglot-completion-at-point: Follow specification more closely #235

Closed
wants to merge 3 commits into from

Conversation

nemethf
Copy link
Collaborator

@nemethf nemethf commented Feb 24, 2019

This is a WIP.

It seems eglot assumed that the triggerChacters couldn't be inside the
completion bounds. This assumption does not hold with the
vscode-json-languageserver. So the patched version of
eglot-completion-at-point asks the server for possible completions and
calculates the bounds from them (if the completions are consistent in
this regard.)

Additionally, vscode-json-languageserver returns completions with
different filterText and label fields. The LSP specification is
usually quite vague, but not this time:

filterText: A string that should be used when filtering a set of
completion items. When `falsy` the label is used.

Although it never defines what it means by filtering. Anyway, the new
test json-basic succeeds when the patch is applied, and fails
otherwise. It assumes the server installed as root, e.g.:

sudo npm install -g vscode-json-languageserver

Unfortunately, the code needs polishing. eglot calls
textDocument/completion twice at the beginning of a completion. But I
don't understand why it is necessary to fetch the completions from the
server more than once, so I haven't modified this part of the code.

I haven't run the rust and the eclipse tests, but hopefully those are
unaffected by this change.

Feedback on how to proceed is obviously welcome. Thanks.

This is a WIP.

It seems eglot assumed that the triggerChacters couldn't be inside the
completion bounds.  This assumption does not hold with the
vscode-json-languageserver.  So the patched version of
eglot-completion-at-point asks the server for possible completions and
calculates the bounds from them (if the completions are consistent in
this regard.)

Additionally, vscode-json-languageserver returns completions with
different filterText and label fields.  The LSP specification is
usually quite vague, but not this time:

   filterText: A string that should be used when filtering a set of
   completion items. When `falsy` the label is used.

Although it never defines what it means by filtering.  Anyway, the new
test `json-basic` succeeds when the patch is applied, and fails
otherwise.  It assumes the server installed as root, e.g.:

   `sudo npm install -g vscode-json-languageserver`

Unfortunately, the code needs polishing.  eglot calls
textDocument/completion twice at the beginning of a completion.  But I
don't understand why it is necessary to fetch the completions from the
server more than once, so I haven't modified this part of the code.

I haven't run the rust and the eclipse tests, but hopefully those are
unaffected by this change.

Feedback on how to proceed is obviously welcome.  Thanks.
@nemethf
Copy link
Collaborator Author

nemethf commented Feb 24, 2019

Travis says:

FAILED  basic-completions
FAILED  hover-after-completions

So I need to install pyls :(

Handle the case when there's no textEdit in the
textDocument/completion reply.
@nemethf
Copy link
Collaborator Author

nemethf commented Feb 24, 2019

I fixed the test named basic-completions. The failure with other one seems to predate this PR. Nevertheless, I modified the hover-after-completions test, because it stuck in an infinite loop in case of failure.

@joaotavora
Copy link
Owner

@nemethf thanks for working on this. I haven't looked at it closely since I am extremely busy these weeks but these improvements (as well as the improvements to the test's stability) are greatly appreciated, especially since you've already proven you know your stuff :-)

@joaotavora
Copy link
Owner

Unfortunately, the code needs polishing. eglot calls
textDocument/completion twice at the beginning of a completion.

This would be a bug, if indeed it still happens. I vaguely remember fixing some related situation.

@nemethf
Copy link
Collaborator Author

nemethf commented Apr 11, 2019

Probably, I wasn't clear enough. This PR is not finished partially because eglot calls textDocument/completion twice after applying the proposed patch. However, it demonstrates a problem of the current code and shows a way to fix it. Hopefully.

I haven't finished the PR mainly because I don't understand when and how emacs calls textDocument/completion.

@easbarba
Copy link

easbarba commented Apr 11, 2019

@nemethf

NPM packages can be installed locally under $HOME folder. Just add it $PATH. sudo is a system utility.

npm config set prefix="$XDG_CONFIG_HOME/npm/" # Install under .config/npm

@nemethf
Copy link
Collaborator Author

nemethf commented Apr 11, 2019

Thanks @eabarbosa, I didn't know that. (I know almost nothing about modern javascript.)

@nemethf
Copy link
Collaborator Author

nemethf commented Apr 11, 2019

Thanks @eabarbosa, I didn't know that. (I know almost nothing about modern javascript.)

Somehow, I commented the same thing twice, sorry :(

@joaotavora
Copy link
Owner

Hi @nemethf,

I'm going through these old PR's and cherry-picking interesting bits (sorry for the very long delay).

This is one is promising and I think I understand how it works. I'm going to try and rebase it to the latest eglot-completion-at-point in master. Do you still have this LSP installed so you can re-test with your new test? I'll also try to rewrite it a bit so that there aren't two textDocument/completion calls for each completion attempt.

joaotavora added a commit that referenced this pull request Oct 16, 2019
Reworked important parts of eglot-completion-at-point.

One of the tasks was to cleanup the nomenclature so it's easier to
spot how LSP and Emacs's views of completion techniques differ.  When
reading this rather long function, remember an "item" is a plist
representing the LSP completionItem object, and "proxy" is a
propertized string that Emacs's frontends will use to represent that
completion.  When the completion is close to done, the :exit-function
is called, to potentially rework the inserted text so that the final
result might be quite different from the proxy (it might be a snippet,
or even a suprising text edit).

The most important change in this commit reworks the way the
completion "bounds" are calculated in the buffer.  This is the region
that Emacs needs to know that is being targeted for the completion.  A
server can specify this region by using textEdit-based completions all
consistently pointing to the same range.  If it does so, Emacs will
use that region instead of its own understanding of symbol
boundaries (provided by thingatpt.el and syntax tables).

To implement server-side completion filtering, the server can also
provide a filterText "cookie" in each completion, which, when
prefix-matched to the intended region, selects or rejects the
completion.  Given the feedback in
microsoft/language-server-protocol#651, we
have no choice but to play along with that inneficient and grotesque
strategy to implement flex-style matching.  Like ever in LSP, we do so
while being backward-compatible to all previously supported behaviour.

* eglot.el (eglot-completion-at-point): rework.
@nemethf
Copy link
Collaborator Author

nemethf commented Oct 17, 2019

This is one is promising and I think I understand how it works. I'm going to try and rebase it to the latest eglot-completion-at-point in master.

I tried to create something similar to my old test environment and ran some tests with the recent master branch and vscode-json-languageserver. Completion works with company-mode, but it does not work with C-M-i.

Do you still have this LSP installed so you can re-test with your new test?

I created a docker image and I use eglot-x to connect its exposed 8080 port. Additionally, I need to tweak eglot-workspace-configuration. Let me know if you interested in running your own tests and I share all the details.

(It is unfortunate that I cannot devote more time to eglot now when you are more active here.)

@joaotavora
Copy link
Owner

joaotavora commented Oct 17, 2019 via email

@nemethf
Copy link
Collaborator Author

nemethf commented Oct 17, 2019

I tried to create something similar to my old test environment and ran some tests with the recent master branch and vscode-json-languageserver. Completion works with company-mode, but it does not work with C-M-i.
In all servers, or just vscode-json-languageserver?

An older pyls works in a similar situation, but vscode-json-languageserver silently fails if there's only one candidate with "No match". However, if I want to complete foo, which has to candidates (foo.bar and foo.foo), I get the following backtrace:

Debugger entered--Lisp error: (error #("Internal error: foo.bar doesn’t match \\`\"foo" 16 17 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item #2)) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))))
  signal(error (#("Internal error: foo.bar doesn’t match \\`\"foo" 16 17 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item #2)) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))))
  error("Internal error: %s doesn't match %s" #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) "\\`\"foo")
  completion-pcm--merge-completions((#("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (#("\"foo" 0 4 (fontified t face font-lock-string-face))))
  completion-pcm--merge-try((#("\"foo" 0 4 (fontified t face font-lock-string-face))) (#("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) "" "")
  completion-pcm-try-completion(#("\"foo" 0 4 (fontified t face font-lock-string-face)) (closure ((bounds 3 . 7) (proxies #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (items (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))) (response :isIncomplete :json-false :items [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))]) (metadata metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))) (sort-completions closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) "")))))) (server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (probe pred action) (cond ((eq action 'metadata) metadata) ((eq action 'lambda) (member probe proxies)) ((eq (car-safe action) 'boundaries) nil) ((and (null action) (member probe proxies) t)) ((eq action t) (cl-remove-if-not (function (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) (and (or (null pred) (funcall pred proxy)) (string-prefix-p probe (or filterText proxy) completion-ignore-case))))) proxies)))) nil 4)
  #f(compiled-function (style) #<bytecode 0x3f64459>)(partial-completion)
  completion--some(#f(compiled-function (style) #<bytecode 0x3f64459>) (basic partial-completion emacs22))
  completion--nth-completion(1 #("\"foo" 0 4 (fontified t face font-lock-string-face)) (closure ((bounds 3 . 7) (proxies #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (items (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))) (response :isIncomplete :json-false :items [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))]) (metadata metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))) (sort-completions closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) "")))))) (server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (probe pred action) (cond ((eq action 'metadata) metadata) ((eq action 'lambda) (member probe proxies)) ((eq (car-safe action) 'boundaries) nil) ((and (null action) (member probe proxies) t)) ((eq action t) (cl-remove-if-not (function (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) (and (or (null pred) (funcall pred proxy)) (string-prefix-p probe (or filterText proxy) completion-ignore-case))))) proxies)))) nil 4 (metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))))
  completion-try-completion(#("\"foo" 0 4 (fontified t face font-lock-string-face)) (closure ((bounds 3 . 7) (proxies #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (items (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))) (response :isIncomplete :json-false :items [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))]) (metadata metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))) (sort-completions closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) "")))))) (server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (probe pred action) (cond ((eq action 'metadata) metadata) ((eq action 'lambda) (member probe proxies)) ((eq (car-safe action) 'boundaries) nil) ((and (null action) (member probe proxies) t)) ((eq action t) (cl-remove-if-not (function (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) (and (or (null pred) (funcall pred proxy)) (string-prefix-p probe (or filterText proxy) completion-ignore-case))))) proxies)))) nil 4 (metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))))
  funcall(completion-try-completion #("\"foo" 0 4 (fontified t face font-lock-string-face)) (closure ((bounds 3 . 7) (proxies #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (items (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))) (response :isIncomplete :json-false :items [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))]) (metadata metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))) (sort-completions closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) "")))))) (server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (probe pred action) (cond ((eq action 'metadata) metadata) ((eq action 'lambda) (member probe proxies)) ((eq (car-safe action) 'boundaries) nil) ((and (null action) (member probe proxies) t)) ((eq action t) (cl-remove-if-not (function (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) (and (or (null pred) (funcall pred proxy)) (string-prefix-p probe (or filterText proxy) completion-ignore-case))))) proxies)))) nil 4 (metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))))
  (let* ((string (buffer-substring beg end)) (md (completion--field-metadata beg)) (comp (funcall (or try-completion-function 'completion-try-completion) string minibuffer-completion-table minibuffer-completion-predicate (- (point) beg) md))) (cond ((null comp) (minibuffer-hide-completions) (if completion-fail-discreetly nil (ding) (completion--message "No match")) (minibuffer--bitset nil nil nil)) ((eq t comp) (minibuffer-hide-completions) (goto-char end) (completion--done string 'finished (if expect-exact nil "Sole completion")) (minibuffer--bitset nil nil t)) (t (let* ((comp-pos (cdr comp)) (completion (car comp)) (completed (not (eq t (compare-strings completion nil nil string nil nil t)))) (unchanged (eq t (compare-strings completion nil nil string nil nil nil)))) (if unchanged (goto-char end) (completion--replace beg end completion) (setq end (+ beg (length completion)))) (forward-char (- comp-pos (length completion))) (if (not (or unchanged completed)) (completion--do-completion beg end try-completion-function expect-exact) (let* ((exact (test-completion completion minibuffer-completion-table minibuffer-completion-predicate)) (threshold (completion--cycle-threshold md)) (comps (if (and threshold (or (not completed) (< (car (completion-boundaries (substring completion 0 comp-pos) minibuffer-completion-table minibuffer-completion-predicate "")) comp-pos))) (progn (completion-all-sorted-completions beg end))))) (completion--flush-all-sorted-completions) (cond ((and (consp (cdr comps)) (not (condition-case nil (progn (consp (nthcdr threshold comps))) (error nil)))) (setq completed t exact t) (completion--cache-all-sorted-completions beg end comps) (minibuffer-force-complete beg end)) (completed (minibuffer-hide-completions) (if exact (completion--done completion (if (< comp-pos (length completion)) 'exact 'unknown)))) ((not exact) (if (if (eq completion-auto-help 'lazy) (progn (eq this-command last-command)) completion-auto-help) (minibuffer-completion-help beg end) (completion--message "Next char not unique"))) (t (if (and (eq this-command last-command) completion-auto-help) (minibuffer-completion-help beg end)) (completion--done completion 'exact (if expect-exact nil "Complete, but not unique")))) (minibuffer--bitset completed t exact)))))))
  completion--do-completion(#<marker at 3 in p.json> 7)
  completion--in-region-1(#<marker at 3 in p.json> 7)
  #f(compiled-function (start end collection predicate) #<bytecode 0x1c9e77>)(#<marker at 3 in p.json> 7 (closure ((bounds 3 . 7) (proxies #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (items (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))) (response :isIncomplete :json-false :items [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))]) (metadata metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))) (sort-completions closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) "")))))) (server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (probe pred action) (cond ((eq action 'metadata) metadata) ((eq action 'lambda) (member probe proxies)) ((eq (car-safe action) 'boundaries) nil) ((and (null action) (member probe proxies) t)) ((eq action t) (cl-remove-if-not (function (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) (and (or (null pred) (funcall pred proxy)) (string-prefix-p probe (or filterText proxy) completion-ignore-case))))) proxies)))) nil)
  apply(#f(compiled-function (start end collection predicate) #<bytecode 0x1c9e77>) (#<marker at 3 in p.json> 7 (closure ((bounds 3 . 7) (proxies #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (items (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))) (response :isIncomplete :json-false :items [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))]) (metadata metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))) (sort-completions closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) "")))))) (server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (probe pred action) (cond ((eq action 'metadata) metadata) ((eq action 'lambda) (member probe proxies)) ((eq (car-safe action) 'boundaries) nil) ((and (null action) (member probe proxies) t)) ((eq action t) (cl-remove-if-not (function (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) (and (or (null pred) (funcall pred proxy)) (string-prefix-p probe (or filterText proxy) completion-ignore-case))))) proxies)))) nil))
  #f(compiled-function (funs global args) #<bytecode 0x3f64431>)(nil nil (#<marker at 3 in p.json> 7 (closure ((bounds 3 . 7) (proxies #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (items (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))) (response :isIncomplete :json-false :items [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))]) (metadata metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))) (sort-completions closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) "")))))) (server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (probe pred action) (cond ((eq action 'metadata) metadata) ((eq action 'lambda) (member probe proxies)) ((eq (car-safe action) 'boundaries) nil) ((and (null action) (member probe proxies) t)) ((eq action t) (cl-remove-if-not (function (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) (and (or (null pred) (funcall pred proxy)) (string-prefix-p probe (or filterText proxy) completion-ignore-case))))) proxies)))) nil))
  completion--in-region(#<marker at 3 in p.json> 7 (closure ((bounds 3 . 7) (proxies #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (items (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))) (response :isIncomplete :json-false :items [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))]) (metadata metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))) (sort-completions closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) "")))))) (server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (probe pred action) (cond ((eq action 'metadata) metadata) ((eq action 'lambda) (member probe proxies)) ((eq (car-safe action) 'boundaries) nil) ((and (null action) (member probe proxies) t)) ((eq action t) (cl-remove-if-not (function (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) (and (or (null pred) (funcall pred proxy)) (string-prefix-p probe (or filterText proxy) completion-ignore-case))))) proxies)))) nil)
  completion-in-region(#<marker at 3 in p.json> 7 (closure ((bounds 3 . 7) (proxies #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))))) (items (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))) (response :isIncomplete :json-false :items [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #("foo.bar" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.bar\": \"${1:fb}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))) (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #("foo.foo" 0 1 (eglot--lsp-item (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label #0 :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0)))))) :textEdit (:newText "\"foo.foo\": \"${1:ff}\"" :range (:end (:character 6 :line 0) :start (:character 2 :line 0))))]) (metadata metadata (display-sort-function closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) ""))))))) (sort-completions closure ((server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (completions) (sort completions (function (lambda (a b) (string-lessp (or (get-text-property 0 :sortText a) "") (or (get-text-property 0 :sortText b) "")))))) (server . #<eglot-lsp-server eglot-lsp-server>) (completion-capability :resolveProvider t :triggerCharacters ["\"" ":"]) t) (probe pred action) (cond ((eq action 'metadata) metadata) ((eq action 'lambda) (member probe proxies)) ((eq (car-safe action) 'boundaries) nil) ((and (null action) (member probe proxies) t)) ((eq action t) (cl-remove-if-not (function (lambda (proxy) (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) (filterText (plist-get item :filterText))) (and (or (null pred) (funcall pred proxy)) (string-prefix-p probe (or filterText proxy) completion-ignore-case))))) proxies)))) nil)
  completion-at-point()
  complete-symbol(nil)
  funcall-interactively(complete-symbol nil)
  call-interactively(complete-symbol nil nil)
  command-execute(complete-symbol)

This is the relevant LSP message:

(:id 13 :result
     (:isIncomplete :json-false :items
                    [(:documentation "" :filterText "\"foo.bar\"" :insertText "\"foo.bar\": \"${1:fb}\"" :insertTextFormat 2 :kind 10 :label "foo.bar" :textEdit
                                     (:newText "\"foo.bar\": \"${1:fb}\"" :range
                                               (:end
                                                (:character 6 :line 0)
                                                :start
                                                (:character 2 :line 0))))
                     (:documentation "" :filterText "\"foo.foo\"" :insertText "\"foo.foo\": \"${1:ff}\"" :insertTextFormat 2 :kind 10 :label "foo.foo" :textEdit
                                     (:newText "\"foo.foo\": \"${1:ff}\"" :range
                                               (:end
                                                (:character 6 :line 0)
                                                :start
                                                (:character 2 :line 0))))])
     :jsonrpc "2.0")

Soon I'll start negotiating for Eglot, or at least parts of it, to go into Emacs

That's interesting. I might be short-sighted here, but I don't see immediate benefits for eglot to be in Emacs instead of GNU ELPA. But it is an advantage if core developers start to enhance it.

@joaotavora
Copy link
Owner

But, to clarify: the pyls situation is fine, it is only vscode-json-languageserver that's getting you the backtrace, right?

eglot to be in Emacs instead of GNU ELPA.

Instead of eglot-server-programs, you get every mode author to add eglot-server-program to his/her major mode instead. Also any non-conformances can move out of eglot.el.

But it is an advantage if core developers start to enhance it.

That is also a huge advantage.

@nemethf
Copy link
Collaborator Author

nemethf commented Oct 17, 2019

But, to clarify: the pyls situation is fine, it is only vscode-json-languageserver that's getting you the backtrace, right?

Yes, that's right. Sorry I wasn't clear enough.

bhankas pushed a commit to bhankas/emacs that referenced this pull request Sep 18, 2022
Reworked important parts of eglot-completion-at-point.

One of the tasks was to cleanup the nomenclature so it's easier to
spot how LSP and Emacs's views of completion techniques differ.  When
reading this rather long function, remember an "item" is a plist
representing the LSP completionItem object, and "proxy" is a
propertized string that Emacs's frontends will use to represent that
completion.  When the completion is close to done, the :exit-function
is called, to potentially rework the inserted text so that the final
result might be quite different from the proxy (it might be a snippet,
or even a suprising text edit).

The most important change in this commit reworks the way the
completion "bounds" are calculated in the buffer.  This is the region
that Emacs needs to know that is being targeted for the completion.  A
server can specify this region by using textEdit-based completions all
consistently pointing to the same range.  If it does so, Emacs will
use that region instead of its own understanding of symbol
boundaries (provided by thingatpt.el and syntax tables).

To implement server-side completion filtering, the server can also
provide a filterText "cookie" in each completion, which, when
prefix-matched to the intended region, selects or rejects the
completion.  Given the feedback in
microsoft/language-server-protocol#651, we
have no choice but to play along with that inneficient and grotesque
strategy to implement flex-style matching.  Like ever in LSP, we do so
while being backward-compatible to all previously supported behaviour.

* eglot.el (eglot-completion-at-point): rework.
bhankas pushed a commit to bhankas/emacs that referenced this pull request Sep 19, 2022
Reworked important parts of eglot-completion-at-point.

One of the tasks was to cleanup the nomenclature so it's easier to
spot how LSP and Emacs's views of completion techniques differ.  When
reading this rather long function, remember an "item" is a plist
representing the LSP completionItem object, and "proxy" is a
propertized string that Emacs's frontends will use to represent that
completion.  When the completion is close to done, the :exit-function
is called, to potentially rework the inserted text so that the final
result might be quite different from the proxy (it might be a snippet,
or even a suprising text edit).

The most important change in this commit reworks the way the
completion "bounds" are calculated in the buffer.  This is the region
that Emacs needs to know that is being targeted for the completion.  A
server can specify this region by using textEdit-based completions all
consistently pointing to the same range.  If it does so, Emacs will
use that region instead of its own understanding of symbol
boundaries (provided by thingatpt.el and syntax tables).

To implement server-side completion filtering, the server can also
provide a filterText "cookie" in each completion, which, when
prefix-matched to the intended region, selects or rejects the
completion.  Given the feedback in
microsoft/language-server-protocol#651, we
have no choice but to play along with that inneficient and grotesque
strategy to implement flex-style matching.  Like ever in LSP, we do so
while being backward-compatible to all previously supported behaviour.

* eglot.el (eglot-completion-at-point): rework.
bhankas pushed a commit to bhankas/emacs that referenced this pull request Sep 19, 2022
Reworked important parts of eglot-completion-at-point.

One of the tasks was to cleanup the nomenclature so it's easier to
spot how LSP and Emacs's views of completion techniques differ.  When
reading this rather long function, remember an "item" is a plist
representing the LSP completionItem object, and "proxy" is a
propertized string that Emacs's frontends will use to represent that
completion.  When the completion is close to done, the :exit-function
is called, to potentially rework the inserted text so that the final
result might be quite different from the proxy (it might be a snippet,
or even a suprising text edit).

The most important change in this commit reworks the way the
completion "bounds" are calculated in the buffer.  This is the region
that Emacs needs to know that is being targeted for the completion.  A
server can specify this region by using textEdit-based completions all
consistently pointing to the same range.  If it does so, Emacs will
use that region instead of its own understanding of symbol
boundaries (provided by thingatpt.el and syntax tables).

To implement server-side completion filtering, the server can also
provide a filterText "cookie" in each completion, which, when
prefix-matched to the intended region, selects or rejects the
completion.  Given the feedback in
microsoft/language-server-protocol#651, we
have no choice but to play along with that inneficient and grotesque
strategy to implement flex-style matching.  Like ever in LSP, we do so
while being backward-compatible to all previously supported behaviour.

* eglot.el (eglot-completion-at-point): rework.

#235: joaotavora/eglot#235
jollaitbot pushed a commit to sailfishos-mirror/emacs that referenced this pull request Oct 12, 2022
Reworked important parts of eglot-completion-at-point.

One of the tasks was to cleanup the nomenclature so it's easier to
spot how LSP and Emacs's views of completion techniques differ.  When
reading this rather long function, remember an "item" is a plist
representing the LSP completionItem object, and "proxy" is a
propertized string that Emacs's frontends will use to represent that
completion.  When the completion is close to done, the :exit-function
is called, to potentially rework the inserted text so that the final
result might be quite different from the proxy (it might be a snippet,
or even a suprising text edit).

The most important change in this commit reworks the way the
completion "bounds" are calculated in the buffer.  This is the region
that Emacs needs to know that is being targeted for the completion.  A
server can specify this region by using textEdit-based completions all
consistently pointing to the same range.  If it does so, Emacs will
use that region instead of its own understanding of symbol
boundaries (provided by thingatpt.el and syntax tables).

To implement server-side completion filtering, the server can also
provide a filterText "cookie" in each completion, which, when
prefix-matched to the intended region, selects or rejects the
completion.  Given the feedback in
microsoft/language-server-protocol#651, we
have no choice but to play along with that inneficient and grotesque
strategy to implement flex-style matching.  Like ever in LSP, we do so
while being backward-compatible to all previously supported behaviour.

* eglot.el (eglot-completion-at-point): rework.

GitHub-reference: close joaotavora/eglot#235
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