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 support #25

Merged
merged 1 commit into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: "CI"
on:
push:
paths-ignore:
- "README.rst"
branches:
- "main"
pull_request:
branches:
- "*"

jobs:
ci:
runs-on: "ubuntu-latest"
continue-on-error: "${{ matrix.experimental }}"
strategy:
fail-fast: false
matrix:
emacs-version:
- "26.1"
- "26.2"
- "26.3"
- "27.1"
- "27.2"
- "28.1"
- "28.2"
- "29.1"
experimental: [false]
include:
- emacs-version: "snapshot"
experimental: true

steps:
- uses: "actions/checkout@v4"

- uses: "purcell/setup-emacs@master"
with:
version: "${{ matrix.emacs-version }}"

- uses: "cask/setup-cask@master"
with:
version: "0.9.0"

- name: "Compile"
run: |
make compile

- uses: "actions/setup-go@v4"
with:
go-version: "stable"
check-latest: true
cache: false

- name: "Install tomljson"
run: |
go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest

- name: "Test"
run: |
make test
64 changes: 0 additions & 64 deletions .github/workflows/test.yml

This file was deleted.

2 changes: 2 additions & 0 deletions Cask
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
(source melpa)

(depends-on "f" "0.6.0")
(depends-on "map" "3.3.1")
(depends-on "seq" "2.24")

(development
(depends-on "buttercup")
Expand Down
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
154 changes: 151 additions & 3 deletions pet.el
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
;; Author: Jimmy Yuen Ho Wong <[email protected]>
;; Maintainer: Jimmy Yuen Ho Wong <[email protected]>
;; Version: 1.1.0
;; Package-Requires: ((emacs "26.1") (f "0.6.0"))
;; Package-Requires: ((emacs "26.1") (f "0.6.0") (map "3.3.1") (seq "2.24"))
;; Homepage: https://github.com/wyuenho/emacs-pet/
;; Keywords: tools

Expand Down Expand Up @@ -37,6 +37,7 @@
(require 'f)
(require 'filenotify)
(require 'let-alist)
(require 'map)
(require 'pcase)
(require 'project)
(require 'python)
Expand Down Expand Up @@ -729,6 +730,149 @@ 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-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))))
((string-match-p "jedi-language-server" command)
`(:jedi
(:executable
(:command
,(pet-executable-find "jedi-language-server"))
:workspace
(:environmentPath
,(pet-executable-find "python")))))
(t nil)))

(defalias 'pet--proper-list-p 'proper-list-p)
(eval-when-compile
(when (and (not (functionp 'proper-list-p))
(functionp 'format-proper-list-p))
(defun pet--proper-list-p (l)
(and (format-proper-list-p l)
(length l)))))

(defun pet--plistp (object)
"Non-nil if and only if OBJECT is a valid plist."
(let ((len (pet--proper-list-p object)))
(and len
(zerop (% len 2))
(seq-every-p
(lambda (kvp)
(keywordp (car kvp)))
(seq-split object 2)))))

(defun pet-merge-eglot-initialization-options (a b)
"Deep merge plists A and B."
(map-merge-with 'plist
(lambda (c d)
(cond ((and (pet--plistp c) (pet--plistp d))
(pet-merge-eglot-initialization-options c d))
((and (vectorp c) (vectorp d))
(vconcat (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* ((path (cadr args))
(canonical-path (if (and path (file-directory-p path))
(file-name-as-directory path)
path))
(server (car args))
(command (process-command (jsonrpc--process server)))
(program (and (listp command) (car command)))
(pet-config (pet-lookup-eglot-server-initialization-options program))
(user-config (apply fn server (and canonical-path (cons canonical-path (cddr args))))))
(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))
(probe (seq-position contact :initializationOptions))
(program-with-args (seq-subseq contact 0 (or probe (length contact))))
(program (car program-with-args))
(init-opts (plist-get (seq-subseq contact (or probe 0)) :initializationOptions)))
(if init-opts
(append (seq-subseq result 0 3)
(list
(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--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--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 +906,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 +928,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
Loading