diff --git a/README.org b/README.org index de343a4..d392575 100644 --- a/README.org +++ b/README.org @@ -2899,11 +2899,55 @@ Listing [[lst:configure-writegood-mode]] configures [[https://github.com/bnbeckw (global-set-key (kbd "C-c g") #'writegood-mode)) #+end_src -* Programming +* Programming Tools :PROPERTIES: -:CUSTOM_ID: sec:programming +:CUSTOM_ID: sec:programming-tools :END: +*** [[https://github.com/joaotavora/eglot][Eglot]] +:PROPERTIES: +:CUSTOM_ID: sec:eglot +:END: + +[[https://github.com/joaotavora/eglot#readme][Emacs polyGLOT (eglot)]] is an Emacs language-server-protocol client that stays +out of the way. Listing [[lst:ensure-eglot-installation]] ensures installation of +[[https://github.com/joaotavora/eglot][eglot]] with minimal configuration and listing [[lst:eglot-maybe-ensure]] starts [[https://github.com/joaotavora/eglot][eglot]] +in case of proper programming modes and proper directory local variables +(meaning in presence of a proper [[info:emacs#Directory Variables][.dir-locals.el]] file in the root directory of +any project using proper programming modes). + +#+caption[Ensure =eglot= installation]: +#+caption: Ensure =eglot= installation. +#+name: lst:ensure-eglot-installation +#+begin_src emacs-lisp + (when (ensure-package-installation 'eglot) + ;; (defvar eglot-server-programs + ;; `((python-mode . ("pylsp" "-vvv"))) + ;; "Shadow the definition of `eglot-server-programs' in `eglot'.") + (with-eval-after-load 'eglot + (define-key eglot-mode-map (kbd "C-c n") #'flymake-goto-next-error) + (define-key eglot-mode-map (kbd "C-c p") #'flymake-goto-prev-error) + (define-key eglot-mode-map (kbd "C-c r") 'eglot-rename))) +#+end_src + +#+caption[Start =eglot= in case of a proper =dir-local-variables-alist=]: +#+caption: Start =eglot= in case of a proper =dir-local-variables-alist=. +#+name: lst:eglot-maybe-ensure +#+begin_src emacs-lisp + (when (fboundp 'eglot-ensure) + (defcustom eglot-maybe-derived-modes '(python-mode) + "Modes to call `eglot-ensure' in case of proper directory local variables.") + ;; The two hooks `after-change-major-mode-hook' and + ;; `hack-local-variables-hook' are OK, but language mode hooks like + ;; `python-mode-hook' are not. + (add-hook 'after-change-major-mode-hook + (defun eglot-maybe-ensure () + (when (and (apply #'derived-mode-p eglot-maybe-derived-modes) + (assoc 'eglot-workspace-configuration + dir-local-variables-alist)) + (eglot-ensure))))) +#+end_src + ** [[https://github.com/lassik/emacs-format-all-the-code#readme][Format-all]] :PROPERTIES: :CUSTOM_ID: sec:format-all @@ -2937,6 +2981,19 @@ Listing [[lst:configure-format-all]]: (message "Saved reformatted tangled buffer `%s'" (buffer-file-name))))))) #+end_src +** [[info:flymake#Top][Flymake (info)]] +:PROPERTIES: +:CUSTOM_ID: sec:flymake +:END: +Flymake is an universal on-the-fly syntax checker for Emacs. It is a requirement +of [[https://github.com/joaotavora/eglot][eglot]], but you can use it without [[https://github.com/joaotavora/eglot][eglot]] for instance in [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][python-mode]] buffers +that do not visit a file. + +* Programming Modes +:PROPERTIES: +:CUSTOM_ID: sec:programming-languages +:END: + ** [[https://dept-info.labri.fr/~strandh/Teaching/PFS/Common/Strandh-Tutorial/Dir-symbolic.html][Common Lisp programming]] :PROPERTIES: :CUSTOM_ID: sec:common-lisp-programming @@ -3145,8 +3202,8 @@ server, before plunging into the configuring steps: :PROPERTIES: :CUSTOM_ID: sec:python-mode :END: -Listing [[lst:configure-python]] tells the Python shell to disregard its environment -variables (in particular =PYTHONSTARTUPFILE=). The [[https://github.com/pythonic-emacs/pythonic#readme][pythonic]] and [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]] packages +Listing [[lst:configure-python]] configures [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][python-mode]] to use [[https://flake8.pycqa.org/en/latest/][flake8]] as style +checker and [[https://ipython.org/][IPython]] as shell interpreter. The [[https://github.com/pythonic-emacs/pythonic#readme][pythonic]] and [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]] packages provide support to handle Python virtual environments within Emacs. The [[https://github.com/pyenv/pyenv][pyenv]] package provides support to work with [[https://github.com/pyenv/pyenv#readme][pyenv]] (eventually with [[https://github.com/pyenv/pyenv-virtualenv#readme][pyenv-virtualenv]]) to select between different python versions (eventually each with different @@ -3155,8 +3212,7 @@ environments). In the end, all those packages do is to set environment variables and restart the relevant Python child processes (in case of [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]]). Therefore, this setup replaces those packages with listing [[lst:manage-pyenv]] to manage [[https://github.com/pyenv/pyenv#readme][pyenv]] from within Emacs and listing -[[lst:setting-python-shell-virtualenv-root]] to set -=python-shell-virtualenv-root=. +[[lst:setting-python-shell-virtualenv-root]] to set =python-shell-virtualenv-root=. #+caption[Configure =python=]: #+caption: Configure =python=. @@ -3165,6 +3221,7 @@ of [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]]). Therefore, thi (with-eval-after-load 'python (custom-set-variables `(python-check-command ,(executable-find "flake8")) + `(python-flymake-command '(,(executable-find "flake8") "-")) '(python-indent-guess-indent-offset nil) '(python-shell-completion-native-disabled-interpreters '("ipython3" "pypy")) '(python-shell-interpreter "ipython3") @@ -3257,85 +3314,51 @@ of [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]]). Therefore, thi python-shell-virtualenv-root))))) #+end_src -*** [[https://github.com/joaotavora/eglot][Eglot]] +*** [[https://github.com/joaotavora/eglot][Eglot]] for [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][python-mode]] :PROPERTIES: -:CUSTOM_ID: sec:eglot +:CUSTOM_ID: sec:eglot-python :END: -Listing [[lst:configure-eglot+python-lsp-server-for-python]] tangles to -=user-init-file= and configures [[https://github.com/joaotavora/eglot][eglot]] for [[https://www.python.org][Python]] using the [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]]. -In order to enable all builtin [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]] capabilities, ensure -installation of the Python packages [[https://github.com/hhatto/autopep8#readme][autopep8]], [[https://github.com/PyCQA/flake8][flake8]], [[https://github.com/PyCQA/pydocstyle#readme][pydocstyle]], [[https://github.com/PyCQA/pylint#readme][pylint]], [[https://github.com/python-rope/rope#readme][rope]], -and [[https://github.com/google/yapf#readme][yapf]]. In addition, install the [[https://github.com/emanspeaks/pyls-flake8#readme][pyls-flake8]] plugin to let [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]] -use [[https://github.com/PyCQA/flake8][flake8]]. The latest [[https://github.com/python-lsp/python-lsp-server#readme][python-lsp-server]] documentation tells to let it use -[[https://github.com/PyCQA/flake8][flake8]] as in [[lst:broken-configure-eglot+python-lsp-server-for-python]], but it -does not work. +Listing [[lst:configure-eglot+python-lsp-server-for-python]] configures [[https://github.com/joaotavora/eglot][eglot]] for +[[https://www.python.org][Python]] using the [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]]. In order to enable all builtin +[[https://github.com/python-lsp/python-lsp-server][python-lsp-server]] capabilities, ensure installation of the Python packages +[[https://github.com/hhatto/autopep8#readme][autopep8]], [[https://github.com/PyCQA/flake8][flake8]], [[https://github.com/PyCQA/pydocstyle#readme][pydocstyle]], [[https://github.com/PyCQA/pylint#readme][pylint]], [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]], [[https://github.com/python-rope/rope#readme][rope]], and [[https://github.com/google/yapf#readme][yapf]]. In +addition, install the [[https://github.com/emanspeaks/pyls-flake8#readme][pyls-flake8]] plugin to let [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]] use [[https://github.com/PyCQA/flake8][flake8]]. +The latest [[https://github.com/python-lsp/python-lsp-server#readme][python-lsp-server]] documentation tells to let it use [[https://github.com/PyCQA/flake8][flake8]] as in +[[lst:broken-configure-eglot+python-lsp-server-for-python]], but it does not work. -Listing [[lst:on-hack-local-variables-hook-eglot-maybe]] defines a hook function to -launch [[https://github.com/joaotavora/eglot][eglot]] in presence of a proper [[info:emacs#Directory Variables][.dir-locals.el]] file in the root directory -of any [[https://www.python.org][Python]] project. Listing [[lst:eglot-directory-variables-for-python]] shows -such a proper [[info:emacs#Directory Variables][.dir-locals.el]] file. +Listing [[lst:eglot-directory-variables-for-python]] shows a proper [[info:emacs#Directory Variables][.dir-locals.el]] +file in the root directory of any [[https://www.python.org][Python]] project to start [[https://github.com/joaotavora/eglot][eglot]] automatically +according to the configuration in listing [[lst:eglot-maybe-ensure]]. #+caption[Configure =eglot= with =python-lsp-server= for Python]: #+caption: Configure =eglot= with =python-lsp-server= for Python. #+name: lst:configure-eglot+python-lsp-server-for-python #+begin_src emacs-lisp - (when (ensure-package-installation 'eglot) - (defvar eglot-server-programs - `((python-mode . ("pylsp"))) - "Shadow the definition of `eglot-server-programs' in `eglot'.") - (with-eval-after-load 'eglot - (setq-default - eglot-workspace-configuration - '(;; Disable the `:pyls_flake8' plugin to fall back to pycodestyle. - (:pylsp . (:plugins (:pyls_flake8 (:enabled t)))) - (:pylsp . (:plugins (:jedi (:auto_import_modules ["numpy"])))) - (:pylsp . (:plugins (:jedi_completion (:cache_for ["astropy"])))))) - - (define-key eglot-mode-map (kbd "C-c n") #'flymake-goto-next-error) - (define-key eglot-mode-map (kbd "C-c p") #'flymake-goto-prev-error) - (define-key eglot-mode-map (kbd "C-c r") 'eglot-rename))) + (with-eval-after-load 'eglot + (setq-default + eglot-workspace-configuration + '(;; Disable the `:pyls_flake8' plugin to fall back to pycodestyle. + (:pylsp . (:plugins (:pyls_flake8 (:enabled t)))) + (:pylsp . (:plugins (:jedi (:auto_import_modules ["numpy"])))) + (:pylsp . (:plugins (:jedi_completion (:cache_for ["astropy"]))))))) #+end_src #+caption[Broken configure =eglot= with =python-lsp-server= for Python]: #+caption: Broken configure =eglot= with =python-lsp-server= for Python. #+name: lst:broken-configure-eglot+python-lsp-server-for-python #+begin_src emacs-lisp :tangle no - (when (ensure-package-installation 'eglot) - (defvar eglot-server-programs - `((python-mode . ("pylsp" "-vvv"))) - "Shadow the definition of `eglot-server-programs' in `eglot'.") - (with-eval-after-load 'eglot - (setq-default - eglot-workspace-configuration - '(;; To use flake8 instead of pycodestyle, see: - ;; https://github.com/python-lsp/python-lsp-server#configuration - (:pylsp . (:configurationSources ["flake8"])) - (:pylsp . (:plugins (:pycodestyle (:enabled :json-false)))) - (:pylsp . (:plugins (:mccabe (:enabled :json-false)))) - (:pylsp . (:plugins (:pyflakes (:enabled :json-false)))) - (:pylsp . (:plugins (:flake8 (:enabled t)))) - (:pylsp . (:plugins (:jedi (:auto_import_modules ["numpy"])))) - (:pylsp . (:plugins (:jedi_completion (:cache_for ["astropy"])))))) - - (define-key eglot-mode-map (kbd "C-c n") #'flymake-goto-next-error) - (define-key eglot-mode-map (kbd "C-c p") #'flymake-goto-prev-error) - (define-key eglot-mode-map (kbd "C-c r") 'eglot-rename))) -#+end_src - -#+caption[Start =eglot= in case of a proper =dir-local-variables-alist=]: -#+caption: Start =eglot= in case of a proper =dir-local-variables-alist=. -#+name: lst:on-hack-local-variables-hook-eglot-maybe -#+begin_src emacs-lisp - (when (fboundp 'eglot-ensure) - ;; The two hooks `after-change-major-mode-hook' and - ;; `hack-local-variables-hook' are OK, but language mode hooks like - ;; `python-mode-hook' are not. - (add-hook 'hack-local-variables-hook - (defun on-hack-local-variables-hook-eglot-maybe () - (when (and (derived-mode-p 'python-mode) - (assoc 'eglot-workspace-configuration - dir-local-variables-alist)) - (eglot-ensure))))) + (with-eval-after-load 'eglot + (setq-default + eglot-workspace-configuration + '(;; To use flake8 instead of pycodestyle, see: + ;; https://github.com/python-lsp/python-lsp-server#configuration + (:pylsp . (:configurationSources ["flake8"])) + (:pylsp . (:plugins (:pycodestyle (:enabled :json-false)))) + (:pylsp . (:plugins (:mccabe (:enabled :json-false)))) + (:pylsp . (:plugins (:pyflakes (:enabled :json-false)))) + (:pylsp . (:plugins (:flake8 (:enabled t)))) + (:pylsp . (:plugins (:jedi (:auto_import_modules ["numpy"])))) + (:pylsp . (:plugins (:jedi_completion (:cache_for ["astropy"]))))))) #+end_src #+caption[Propose =directory-variables= to launch =eglot=]: @@ -3397,9 +3420,9 @@ agree with [[https://black.readthedocs.io/en/stable/index.html][black's uncompro #+end_src [[https://jedi.readthedocs.io/en/latest/][Jedi]] provides grammar checking and completion candidates to [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]]. -Only [[https://jedi.readthedocs.io/en/latest/docs/changelog.html][jedi-0.18.1]] works with for instance [[https://numpy.org/][numpy-1.22.0]] in the sense that it does +Only [[https://jedi.readthedocs.io/en/latest/docs/changelog.html][jedi-0.18.1]] works with for instance [[https://numpy.org/][numpy-1.23.0]] in the sense that it does not choke on universal functions provided that [[https://jedi.readthedocs.io/en/latest/][jedi]] does not parse but imports -[[https://numpy.org/][numpy-1.22.0]] (see [[https://github.com/davidhalter/jedi/issues/1744][jedi issue #1744]], [[https://github.com/davidhalter/jedi/issues/1745][#1745]], and [[https://github.com/davidhalter/jedi/issues/1746][#1746]]). Since the universal +[[https://numpy.org/][numpy-1.23.1]] (see [[https://github.com/davidhalter/jedi/issues/1744][jedi issue #1744]], [[https://github.com/davidhalter/jedi/issues/1745][#1745]], and [[https://github.com/davidhalter/jedi/issues/1746][#1746]]). Since the universal functions are neither builtin methods nor data instances but a kind of "callable instances", the [[https://docs.python.org/3/library/inspect.html][Python inspect]] module also fails to handle the universal functions properly.