From 9d14742138d50f23cf37dec40a90a90e3fe4e61d Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Fri, 25 Feb 2022 17:12:45 -0800 Subject: [PATCH] Mode-specific keybindings for Python Now the LSP seems to be working better with pyright. --- ha-programming-python.org | 210 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 199 insertions(+), 11 deletions(-) diff --git a/ha-programming-python.org b/ha-programming-python.org index 1c92013..6e7c7ec 100644 --- a/ha-programming-python.org +++ b/ha-programming-python.org @@ -24,6 +24,34 @@ A literate programming file for configuring Python. #+END_SRC * Introduction The critical part of Python integration with Emacs is running LSP in Python using [[file:ha-programming.org::*direnv][direnv]]. And the only question to ask is if the Python we run it in Docker or in a virtual environment. + +#+BEGIN_SRC emacs-lisp + (general-create-definer ha-python-leader + :states '(normal visual motion) + :keymaps 'python-mode-map + :prefix "SPC m" + :global-prefix "" + :non-normal-prefix "S-SPC") +#+END_SRC +While Emacs supplies a Python editing environment, we’ll still use =use-package= to grab the latest: + +#+BEGIN_SRC emacs-lisp + (use-package python + :after projectile + :mode ("[./]flake8\\'" . conf-mode) + :mode ("/Pipfile\\'" . conf-mode) + :init + (setq python-indent-guess-indent-offset-verbose nil) + :config + (when (and (executable-find "python3") + (string= python-shell-interpreter "python")) + (setq python-shell-interpreter "python3")) + + ;; While `setup.py' and `requirements.txt' are already added, I often + ;; create these files for my Python projects: + (add-to-list 'projectile-project-root-files "requirements-dev.txt") + (add-to-list 'projectile-project-root-files "requirements-test.txt")) +#+END_SRC ** Virtual Environment For a local virtual machine, simply put the following in your =.envrc= file: #+begin_src conf @@ -69,9 +97,26 @@ CONTAINER_EXTRA_ARGS="--env SOME_ENV_VAR=${SOME_ENV_VAR}" container_layout #+end_src +** Unit Tests +#+BEGIN_SRC emacs-lisp + (use-package python-pytest + :after python + :commands python-pytest-dispatch + :init + (ha-python-leader + "t" '(:ignore t :which-key "tests") + "t a" '("all" . python-pytest) + "t f" '("file dwim" . python-pytest-file-dwim) + "t F" '("file" . python-pytest-file) + "t t" '("function-dwim" . python-pytest-function-dwim) + "t T" '("function" . python-pytest-function) + "t r" '("repeat" . python-pytest-repeat) + "t p" '("dispatch" . python-pytest-dispatch))) +#+END_SRC ** Python Dependencies -Whew. Each Python project's =requirements-dev.txt= file would reference the [[https://pypi.org/project/python-lsp-server/][python-lsp-server]] (not the /unmaintained/ =python-language-server=): -#+begin_src conf +Each Python project's =requirements-dev.txt= file would reference the [[https://pypi.org/project/python-lsp-server/][python-lsp-server]] (not the /unmaintained/ project, =python-language-server=): + +#+begin_src conf :tangle no python-lsp-server[all] #+end_src @@ -91,14 +136,19 @@ python-lsp-server[all] stestr slowest # ... #+END_SRC -*** Using Jedi Instead -Do we want to use the Jedi version of LSP? Not sure what it buys us. -#+BEGIN_SRC emacs-lisp :tangle no -(use-package lsp-jedi - :config - (with-eval-after-load "lsp-mode" - (add-to-list 'lsp-disabled-clients 'pyls) - (add-to-list 'lsp-enabled-clients 'jedi))) +*** Pyright +I’m using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead. Adding this to my =requirements.txt= files: +#+begin_src conf :tangle no +pyright +#+end_src + +The [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP. + +#+BEGIN_SRC emacs-lisp +(use-package lsp-pyright + :hook (python-mode . (lambda () (require 'lsp-pyright))) + :init (when (executable-find "python3") + (setq lsp-pyright-python-executable-cmd "python3"))) #+END_SRC * LSP Integration of Python Now that the [[file:ha-programming.org::*Language Server Protocol (LSP) Integration][LSP Integration]] is complete, we can stitch the two projects together: @@ -107,7 +157,145 @@ Now that the [[file:ha-programming.org::*Language Server Protocol (LSP) Integrat (use-package lsp-mode :hook ((python-mode . lsp))) #+END_SRC -And we're done. + +And we're done. Except that I would like a select collection of LSP keybindings for Python. + +#+BEGIN_SRC emacs-lisp + (ha-python-leader + "0" '("treemacs" . lsp-treemacs-symbols) + + "/" '("complete" . completion-at-point) + "k" '("check code" . python-check) + "]" '("shift left" . python-indent-shift-left) + "[" '("shift right" . python-indent-shift-right) + + ;; actions + "a" '(:ignore t :which-key "code actions") + "aa" '("code actions" . lsp-execute-code-action) + "ah" '("highlight symbol" . lsp-document-highlight) + "al" '("lens" . lsp-avy-lens) + + ;; formatting + "=" '(:ignore t :which-key "formatting") + "==" '("format buffer" . lsp-format-buffer) + "=r" '("format region" . lsp-format-region) + + "e" '(:ignore t :which-key "eval") + "e P" '("run python" . run-python) + "e e" '("send statement" . python-shell-send-statement) + "e b" '("send buffer" . python-shell-send-buffer) + "e f" '("send defun" . python-shell-send-defun) + "e F" '("send file" . python-shell-send-file) + "e r" '("send region" . python-shell-send-region) + "e ;" '("expression" . python-shell-send-string) + "e p" '("switch-to-shell" . python-shell-switch-to-shell) + + ;; folders + "F" '(:ignore t :which-key "folders") + "Fa" '("add folder" . lsp-workspace-folders-add) + "Fb" '("un-blacklist folder" . lsp-workspace-blacklist-remove) + "Fr" '("remove folder" . lsp-workspace-folders-remove) + + ;; goto + "g" '(:ignore t :which-key "goto") + "ga" '("find symbol in workspace" . xref-find-apropos) + "gd" '("find declarations" . lsp-find-declaration) + "ge" '("show errors" . lsp-treemacs-errors-list) + "gg" '("find definitions" . lsp-find-definition) + "gh" '("call hierarchy" . lsp-treemacs-call-hierarchy) + "gi" '("find implementations" . lsp-find-implementation) + "gm" '("imenu" . lsp-ui-imenu) + "gr" '("find references" . lsp-find-references) + "gt" '("find type definition" . lsp-find-type-definition) + + ;; peeks + "G" '(:ignore t :which-key "peek") + "Gg" '("peek definitions" . lsp-ui-peek-find-definitions) + "Gi" '("peek implementations" . lsp-ui-peek-find-implementation) + "Gr" '("peek references" . lsp-ui-peek-find-references) + "Gs" '("peek workspace symbol" . lsp-ui-peek-find-workspace-symbol) + + ;; help + "h" '(:ignore t :which-key "help") + "he" '("eldoc" . python-eldoc-at-point) + "hg" '("glance symbol" . lsp-ui-doc-glance) + "hh" '("describe symbol at point" . lsp-describe-thing-at-point) + "gH" '("describe python symbol" . python-describe-at-point) + "hs" '("signature help" . lsp-signature-activate) + + "i" 'imenu + + ;; refactoring + "r" '(:ignore t :which-key "refactor") + "ro" '("organize imports" . lsp-organize-imports) + "rr" '("rename" . lsp-rename) + + ;; toggles + "t" '(:ignore t :which-key "toggle") + "tD" '("toggle modeline diagnostics" . lsp-modeline-diagnostics-mode) + "tL" '("toggle log io" . lsp-toggle-trace-io) + "tS" '("toggle sideline" . lsp-ui-sideline-mode) + "tT" '("toggle treemacs integration" . lsp-treemacs-sync-mode) + "ta" '("toggle modeline code actions" . lsp-modeline-code-actions-mode) + "tb" '("toggle breadcrumb" . lsp-headerline-breadcrumb-mode) + "td" '("toggle documentation popup" . lsp-ui-doc-mode) + "tf" '("toggle on type formatting" . lsp-toggle-on-type-formatting) + "th" '("toggle highlighting" . lsp-toggle-symbol-highlight) + "tl" '("toggle lenses" . lsp-lens-mode) + "ts" '("toggle signature" . lsp-toggle-signature-auto-activate) + + ;; workspaces + "w" '(:ignore t :which-key "workspaces") + "wD" '("disconnect" . lsp-disconnect) + "wd" '("describe session" . lsp-describe-session) + "wq" '("shutdown server" . lsp-workspace-shutdown) + "wr" '("restart server" . lsp-workspace-restart) + "ws" '("start server" . lsp)) +#+END_SRC +* Project Configuration +I work with a lot of projects with my team where I need to /configure/ the project such that LSP and my Emacs setup works. Let's suppose I could point a function at a project directory, and have it /set it up/: + +#+BEGIN_SRC emacs-lisp + (defun ha-python-configure-project (proj-directory) + "Configure PROJ-DIRECTORY for LSP and Python." + (interactive "DPython Project: ") + + (let ((default-directory proj-directory)) + (unless (f-exists? ".envrc") + (message "Configuring direnv") + (with-temp-file ".envrc" + ;; (insert "use_python 3.7.4\n") + (insert "layout_python3\n")) + (direnv-allow)) + + (unless (f-exists? ".pip.conf") + (message "Configuring pip") + (with-temp-file ".pip.conf" + (insert "[global]\n") + (insert "index-url = https://pypi.python.org/simple\n")) + (shell-command "pipconf --local") + (shell-command "pip install --upgrade pip")) + + (message "Configuring pip for LSP") + (with-temp-file "requirements-dev.txt" + (insert "python-lsp-server[all]\n") + + ;; Let's install these extra packages individually ... + (insert "pyls-flake8\n") + ;; (insert "pylsp-mypy") + ;; (insert "pyls-isort") + ;; (insert "python-lsp-black") + ;; (insert "pyls-memestra") + (insert "pylsp-rope\n")) + (shell-command "pip install -r requirements-dev.txt") + + (unless (f-exists? ".projectile") + (with-temp-file ".projectile")) + + (unless (f-exists? ".dir-locals.el") + (with-temp-file ".dir-locals.el" + (insert "((nil . ((projectile-enable-caching . t))))"))))) +#+END_SRC * Technical Artifacts :noexport: Let's =provide= a name so we can =require= this file: