Mode-specific keybindings for Python
Now the LSP seems to be working better with pyright.
This commit is contained in:
parent
2c806791b3
commit
9d14742138
1 changed files with 199 additions and 11 deletions
|
@ -24,6 +24,34 @@ A literate programming file for configuring Python.
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
* Introduction
|
* 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.
|
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 "<f17>"
|
||||||
|
: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
|
** Virtual Environment
|
||||||
For a local virtual machine, simply put the following in your =.envrc= file:
|
For a local virtual machine, simply put the following in your =.envrc= file:
|
||||||
#+begin_src conf
|
#+begin_src conf
|
||||||
|
@ -69,9 +97,26 @@ CONTAINER_EXTRA_ARGS="--env SOME_ENV_VAR=${SOME_ENV_VAR}"
|
||||||
|
|
||||||
container_layout
|
container_layout
|
||||||
#+end_src
|
#+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
|
** 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=):
|
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
|
|
||||||
|
#+begin_src conf :tangle no
|
||||||
python-lsp-server[all]
|
python-lsp-server[all]
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
@ -91,14 +136,19 @@ python-lsp-server[all]
|
||||||
stestr slowest
|
stestr slowest
|
||||||
# ...
|
# ...
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** Using Jedi Instead
|
*** Pyright
|
||||||
Do we want to use the Jedi version of LSP? Not sure what it buys us.
|
I’m using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead. Adding this to my =requirements.txt= files:
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
#+begin_src conf :tangle no
|
||||||
(use-package lsp-jedi
|
pyright
|
||||||
:config
|
#+end_src
|
||||||
(with-eval-after-load "lsp-mode"
|
|
||||||
(add-to-list 'lsp-disabled-clients 'pyls)
|
The [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP.
|
||||||
(add-to-list 'lsp-enabled-clients 'jedi)))
|
|
||||||
|
#+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
|
#+END_SRC
|
||||||
* LSP Integration of Python
|
* 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:
|
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
|
(use-package lsp-mode
|
||||||
:hook ((python-mode . lsp)))
|
:hook ((python-mode . lsp)))
|
||||||
#+END_SRC
|
#+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:
|
* Technical Artifacts :noexport:
|
||||||
Let's =provide= a name so we can =require= this file:
|
Let's =provide= a name so we can =require= this file:
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue