Skip to content

Commit

Permalink
Eglot support
Browse files Browse the repository at this point in the history
  • Loading branch information
wyuenho committed Aug 30, 2023
1 parent c0e4637 commit fc951bf
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 6 deletions.
20 changes: 16 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Supported Emacs Packages
- Built-in `project.el <https://www.gnu.org/software/emacs/manual/html_node/emacs/Projects.html>`_
- `projectile <https://docs.projectile.mx/projectile/index.html>`_
- `envrc <https://github.com/purcell/envrc>`_ (`direnv caveats`_)
- `eglot <https://github.com/joaotavora/eglot>`_
- `flycheck <https://www.flycheck.org/en/latest/>`_
- `lsp-jedi <https://github.com/fredcamps/lsp-jedi>`_
- `lsp-pyright <https://github.com/emacs-lsp/lsp-pyright>`_
Expand Down Expand Up @@ -196,6 +197,8 @@ Complete Example
(use-package dap-python
:after lsp)
(use-package eglot)
(use-package python-pytest)
(use-package python-black)
Expand All @@ -210,6 +213,9 @@ Complete Example
(setq-local python-shell-interpreter (pet-executable-find "python")
python-shell-virtualenv-root (pet-virtualenv-root))
;; (pet-eglot-setup)
;; (eglot-ensure)
(pet-flycheck-setup)
(flycheck-mode)
Expand Down Expand Up @@ -305,12 +311,18 @@ setting the corresponding ``flycheck`` checker executable variable to the
intended absolute path.


``pet`` can't find my virtualenvs, how do I debug it?
+++++++++++++++++++++++++++++++++++++++++++++++++++++
My package didn't pick up the correct paths, how do I debug ``pet``?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

You can turn on ``pet-debug`` and watch what comes out in the ``*Messages*``
buffer. In addition, you can use ``M-x pet-verify-setup`` in your Python
buffers to find out what was detected.
buffer. In addition, you can use ``M-x pet-verify-setup`` in your Python buffers
to find out what was detected.

For ``lsp``, use ``lsp-describe-session``.

For ``eglot``, use ``eglot-show-workspace-configuration``.

For ``flycheck``, use ``flycheck-verify-setup``.


Do I still need any of the 11+ virtualenv Emacs packages?
Expand Down
156 changes: 154 additions & 2 deletions pet.el
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,154 @@ default otherwise."



(defvar eglot-workspace-configuration)
(declare-function jsonrpc--process "ext:jsonrpc")
(declare-function eglot--executable-find "ext:eglot")
(declare-function eglot--uri-to-path "ext:eglot")
(declare-function eglot--workspace-configuration-plist "ext:eglot")
(declare-function eglot--guess-contact "ext:eglot")

(defun pet-eglot--executable-find-advice (fn &rest args)
"Look up Python language servers using `pet-executable-find'.
FN is `eglot--executable-find', ARGS is the arguments to
`eglot--executable-find'."
(pcase-let ((`(,command . ,_) args))
(if (member command '("pylsp" "pyls" "pyright-langserver" "jedi-language-server"))
(pet-executable-find command)
(apply fn args))))

(defun pet-eglot--uri-to-path-advice (fn &rest args)
"This is used to fix an Eglot bug.
See description of: https://github.com/joaotavora/eglot/pull/1281
FN is `eglot--uri-to-path', and ARGS is the arguments to
`eglot--uri-to-path'."
(let ((result (apply fn args)))
(if (file-directory-p result)
(file-name-as-directory result)
result)))

(defun pet-lookup-eglot-server-initialization-options (command)
"Return LSP initializationOptions for Eglot.
COMMAND is the name of the Python language server command."
(cond ((string-match-p "pylsp" command)
`(:pylsp
(:plugins
(:jedi
(:environment
,(pet-virtualenv-root))
:flake8
(:executable
,(pet-executable-find "flake8"))
:pylint
(:executable
,(pet-executable-find "pylint"))))))
((string-match-p "pyls" command)
`(:pyls
(:plugins
(:jedi
(:environment
,(pet-virtualenv-root))
:pylint
(:executable
,(pet-executable-find "pylint"))))))
((string-match-p "pyright-langserver" command)
`(:python
(:pythonPath
,(pet-executable-find "python")
:venvPath
,(pet-virtualenv-root))))
(t nil)))

(defalias 'pet--seq-contains-p 'seq-contains-p)
(eval-when-compile
(when (< emacs-major-version 27)
(defalias 'pet--seq-contains-p 'seq-contains)))

(defalias 'pet--seq-union 'seq-union)
(eval-when-compile
(when (< emacs-major-version 28)
(cl-defgeneric pet--seq-union (sequence1 sequence2 &optional testfn)
"Return a list of all the elements that appear in either SEQUENCE1 or SEQUENCE2.
\"Equality\" of elements is defined by the function TESTFN, which
defaults to `equal'."
(let* ((accum (lambda (acc elt)
(if (pet--seq-contains-p acc elt testfn)
acc
(cons elt acc))))
(result (seq-reduce accum sequence2
(seq-reduce accum sequence1 '()))))
(nreverse result)))))

(defun pet-merge-eglot-initialization-options (a b)
"Deep merge plists A and B."
(map-merge-with 'plist
(lambda (c d)
(cond ((and (listp c) (listp d))
(pet-merge-eglot-initialization-options c d))
((and (vectorp c) (vectorp d))
(pet--seq-union c d))
(t d)))
(copy-tree a t)
(copy-tree b t)))

(defun pet-eglot--workspace-configuration-plist-advice (fn &rest args)
"Enrich `eglot-workspace-configuration' with paths found by `pet'.
FN is `eglot--workspace-configuration-plist', ARGS is the
arguments to `eglot--workspace-configuration-plist'."
(let* ((user-config (apply fn args))
(server (car args))
(command (car (process-command (jsonrpc--process server))))
(pet-config (pet-lookup-eglot-server-initialization-options command)))
(pet-merge-eglot-initialization-options user-config pet-config)))

(defun pet-eglot--guess-contact-advice (fn &rest args)
"Enrich `eglot--guess-contact' with paths found by `pet'.
FN is `eglot--guess-contact', ARGS is the arguments to
`eglot--guess-contact'."
(let* ((result (apply fn args))
(contact (nth 3 result))
(program-with-args (seq-subseq
contact
0
(or (seq-position contact :initializationOptions)
(length contact))))
(program (car program-with-args))
(init-opts (plist-get contact :initializationOptions)))
(if init-opts
(append (seq-subseq result 0 3)
(append
program-with-args
(list
:initializationOptions
(pet-merge-eglot-initialization-options
init-opts
(pet-lookup-eglot-server-initialization-options
program))))
(seq-subseq result 4))
result)))

(defun pet-eglot-setup ()
"Setup Eglot to use server executables and virtualenvs found by PET."
(advice-add 'eglot--executable-find :around #'pet-eglot--executable-find-advice)
(advice-add 'eglot--uri-to-path :around #'pet-eglot--uri-to-path-advice)
(advice-add 'eglot--workspace-configuration-plist :around #'pet-eglot--workspace-configuration-plist-advice)
(advice-add 'eglot--guess-contact :around #'pet-eglot--guess-contact-advice))

(defun pet-eglot-teardown ()
"Setup PET advices to Eglot."
(advice-remove 'eglot--executable-find #'pet-eglot--executable-find-advice)
(advice-remove 'eglot--uri-to-path #'pet-eglot--uri-to-path-advice)
(advice-remove 'eglot--workspace-configuration-plist #'pet-eglot--workspace-configuration-plist-advice)
(advice-remove 'eglot--guess-contact #'pet-eglot--guess-contact-advice))



(defvar lsp-jedi-executable-command)
(defvar lsp-pyls-plugins-jedi-environment)
(defvar lsp-pylsp-plugins-jedi-environment)
Expand Down Expand Up @@ -762,7 +910,9 @@ buffer local values."
(setq-local python-black-command (pet-executable-find "black"))
(setq-local python-isort-command (pet-executable-find "isort"))
(setq-local blacken-executable python-black-command)
(setq-local yapfify-executable (pet-executable-find "yapf")))
(setq-local yapfify-executable (pet-executable-find "yapf"))

(pet-eglot-setup))

(defun pet-buffer-local-vars-teardown ()
"Reset all supported buffer local variable values to default."
Expand All @@ -782,7 +932,9 @@ buffer local values."
(kill-local-variable 'python-black-command)
(kill-local-variable 'python-isort-command)
(kill-local-variable 'blacken-executable)
(kill-local-variable 'yapfify-executable))
(kill-local-variable 'yapfify-executable)

(pet-eglot-teardown))

(defun pet-verify-setup ()
"Verify the values of buffer local variables visually.
Expand Down

0 comments on commit fc951bf

Please sign in to comment.