Add lang/python module with uv/ruff/pylsp, ANSI colors, and LSP auto-install

- Add lang/python.el: project detection, ruff format-on-save, C-c C-v -> ruff check,
  optional pylsp with auto-install via uv (asks once per session)
- Fix pylsp resolution to check .venv, uv tool path, and PATH
- Disable legacy pyls client to avoid spurious warnings
- Add ANSI color support in compilation buffers (editor.el)
- Remove python-mode/python-ts-mode from global lsp-deferred hooks (lsp.el)
- Add ruff, ty, uv packages to desktop group_vars
This commit is contained in:
Fabio Scotto di Santolo
2026-04-17 00:07:12 +02:00
parent 5065c02930
commit 10e79a4bae
5 changed files with 168 additions and 27 deletions

View File

@@ -115,8 +115,11 @@ profile_packages:
- ristretto
- rsync
- shotwell
- ruff
- terminus-font
- texlive
- ty
- uv
- Thunar
- thunar-archive-plugin
- thunar-volman

View File

@@ -6,13 +6,13 @@
;; Load modules
;;=====================================================================================
(fscotto/load-modules
;; Core
'core/packages
'core/ui
'core/performance
'core/editor
'core/keybindings
'core/buffer
;; Core
'core/packages
'core/ui
'core/performance
'core/editor
'core/keybindings
'core/buffer
;; Tools
'tools/completion
@@ -24,28 +24,29 @@
'tools/dap
'tools/treesitter
;; Languages
'lang/c
'lang/docker
'lang/golang
'lang/json
'lang/markdown
'lang/org
'lang/shell
'lang/yaml
;; Languages
'lang/c
'lang/docker
'lang/golang
'lang/json
'lang/markdown
'lang/org
'lang/python
'lang/shell
'lang/yaml
;; Misc
'misc/dashboard
'misc/custom-functions
'misc/doom-modeline
'misc/which-key
'misc/gptel
'misc/email
'misc/rss
;; Misc
'misc/dashboard
'misc/custom-functions
'misc/doom-modeline
'misc/which-key
'misc/gptel
'misc/email
'misc/rss
'misc/terminal
'misc/vcs
'misc/pdf
'misc/epub
'misc/i3-config)
'misc/i3-config)
(message "...user configuration loaded")

View File

@@ -1,5 +1,7 @@
;;; core-editor
(require 'ansi-color)
(setq standard-indent 4)
(setq tab-stop-list nil)
(setq indent-tabs-mode nil)
@@ -9,6 +11,8 @@
(prefer-coding-system 'utf-8-unix)
(setq custom-file (null-device))
(add-hook 'compilation-filter-hook #'ansi-color-compilation-filter)
(provide 'editor)
;;; editor.el ends here

View File

@@ -0,0 +1,135 @@
;;; python.el -*- lexical-binding: t; -*-
(require 'reformatter)
(with-eval-after-load 'project
(add-to-list 'project-vc-extra-root-markers "pyproject.toml")
(add-to-list 'project-vc-extra-root-markers "uv.lock")
(add-to-list 'project-vc-extra-root-markers ".venv"))
(defun fscotto/python-project-root ()
"Return project root for Python projects, nil otherwise."
(when (and (featurep 'project)
(project-current))
(let ((root (project-root (project-current))))
(when (or (file-exists-p (expand-file-name "pyproject.toml" root))
(file-exists-p (expand-file-name "uv.lock" root)))
root))))
(defun fscotto/python-project-has-uv-p ()
"Return t if current project uses uv (pyproject.toml or uv.lock)."
(when-let ((root (fscotto/python-project-root)))
(or (file-exists-p (expand-file-name "uv.lock" root))
(file-exists-p (expand-file-name "pyproject.toml" root)))))
(defun fscotto/python-project-bin (tool)
"Return path to TOOL in .venv/bin if it exists, else nil."
(when-let ((root (fscotto/python-project-root)))
(let ((venv-bin (expand-file-name (concat ".venv/bin/" tool) root)))
(when (and (file-exists-p venv-bin)
(file-executable-p venv-bin))
venv-bin))))
(defun fscotto/python-activate-project-tools ()
"Prefer tools from the project virtualenv when available."
(when-let* ((root (fscotto/python-project-root))
(venv-bin (expand-file-name ".venv/bin" root))
((file-directory-p venv-bin)))
(make-local-variable 'exec-path)
(add-to-list 'exec-path venv-bin)
(setq-local process-environment (copy-sequence process-environment))
(setenv "PATH" (concat venv-bin path-separator (getenv "PATH")))))
(defvar fscotto/python-lsp-install-asked nil
"Non-nil if user was already asked about installing pylsp this session.")
(defun fscotto/resolve-pylsp ()
"Return the pylsp binary path if found, else nil.
Checks: project .venv, uv tool install path, and PATH."
(or (fscotto/python-project-bin "pylsp")
(let ((uv-pylsp (expand-file-name "pylsp"
(concat (getenv "HOME")
"/.local/share/uv/tools/python-lsp-server/bin"))))
(when (and (file-exists-p uv-pylsp)
(file-executable-p uv-pylsp))
uv-pylsp))
(executable-find "pylsp")))
(defun fscotto/python-lsp-server-available-p ()
"Return non-nil when a supported Python LSP server is available."
(fscotto/resolve-pylsp))
(defun fscotto/python-ensure-lsp-server ()
"Ensure a Python LSP server is installed, prompting the user if needed.
If pylsp is already available, start LSP immediately. Otherwise, ask once
whether to install it via uv. On confirmation, install python-lsp-server
as a uv tool and then start LSP."
(unless (fboundp 'lsp-deferred)
(message "python: lsp-mode not available")
(return-from fscotto/python-ensure-lsp-server))
(if (fscotto/python-lsp-server-available-p)
(progn
(setq lsp-pylsp-server-command (fscotto/resolve-pylsp))
(lsp-deferred))
(when (or fscotto/python-lsp-install-asked
(y-or-n-p "python-lsp-server not found. Install it via uv? "))
(setq fscotto/python-lsp-install-asked t)
(let ((buf (current-buffer)))
(message "Installing python-lsp-server via uv in background...")
(make-process
:name "uv-pylsp-install"
:buffer (get-buffer-create " *uv-pylsp-install*")
:command '("uv" "tool" "install" "--upgrade" "python-lsp-server")
:sentinel
(lambda (proc event)
(cond
((string-match-p "finished" event)
(message "python-lsp-server installed.")
(setq lsp-pylsp-server-command (fscotto/resolve-pylsp))
(with-current-buffer buf
(lsp-deferred)))
((string-match-p "exited\\|failed" event)
(with-current-buffer buf
(message "python-lsp-server installation failed. Check *uv-pylsp-install* buffer."))))))))))
(defun fscotto/python-maybe-start-lsp ()
"Start Python LSP, installing pylsp via uv if needed."
(fscotto/python-ensure-lsp-server))
(defun fscotto/python-setup-check-command ()
"Use Ruff for the built-in Python check command."
(setq-local python-check-command "ruff check"))
(reformatter-define fscotto/ruff-format
:program (or (fscotto/python-project-bin "ruff") (executable-find "ruff"))
:args '("format" "-")
:lighter " ruff")
(with-eval-after-load 'python
(add-hook 'python-mode-hook #'fscotto/python-activate-project-tools)
(add-hook 'python-mode-hook #'fscotto/python-setup-check-command)
(add-hook 'python-mode-hook #'fscotto/ruff-format-on-save-mode)
(add-hook 'python-mode-hook #'fscotto/python-maybe-start-lsp))
(with-eval-after-load 'python-ts-mode
(add-hook 'python-ts-mode-hook #'fscotto/python-activate-project-tools)
(add-hook 'python-ts-mode-hook #'fscotto/python-setup-check-command)
(add-hook 'python-ts-mode-hook #'fscotto/ruff-format-on-save-mode)
(add-hook 'python-ts-mode-hook #'fscotto/python-maybe-start-lsp))
(with-eval-after-load 'lsp-mode
(add-to-list 'lsp-language-id-configuration '(python-mode . "python"))
(add-to-list 'lsp-language-id-configuration '(python-ts-mode . "python"))
(setq lsp-pylsp-server-command (fscotto/resolve-pylsp))
(setq lsp-pylsp-plugins
'((pycodestyle nil)
(pyflakes nil)
(mccabe nil)
(autopep8 nil)
(yapf nil)))
(add-to-list 'lsp-disabled-clients 'pyls))
(provide 'lang/python)
;;; python.el ends here

View File

@@ -8,8 +8,6 @@
c-ts-mode
c++-mode
c++-ts-mode
python-mode
python-ts-mode
sh-mode
bash-ts-mode) . lsp-deferred)
:config