Table of Contents

1. Python Development Configuration

Configuration for Python development with modern tooling and best practices.

1.1. Overview

This module provides Python development support including:

  • Language Support: python-mode with tree-sitter
  • Virtual Environments: pyvenv for virtualenv management
  • Code Quality: Black formatting, isort import sorting
  • Testing: pytest integration
  • REPL: Enhanced Python REPL
  • LSP: Python Language Server support

1.2. Key Features

1.2.1. Python Mode & Tree-Sitter

  • Syntax highlighting (enhanced with tree-sitter)
  • Intelligent indentation
  • Code navigation
  • Integration with debugging tools
  • Automatic mode selection based on tree-sitter availability

1.2.2. Virtual Environment Management

  • Automatic venv detection
  • Easy switching between environments
  • Integration with project workflows

1.2.3. Code Formatting & Quality

Tool Purpose
black Opinionated code formatter
isort Import statement organizer
flake8 Style guide enforcement
pylint Code analysis

1.2.4. Development Workflow

  1. Virtual environment activation
  2. LSP server connection
  3. Auto-formatting on save
  4. Test runner integration

1.3. Python

;;; prog-python-conf.el --- Python development configuration -*- lexical-binding: t; -*-
;;; Commentary:
;;; Configuration for Python development including LSP support, formatting,
;;; testing, virtual environments, and debugging.
;;; Code:

;; Python mode settings
;; (use-package python
;;   :ensure nil
;;   :mode ("\\.py\\'" . python-mode)
;;   :interpreter ("python" "python3")
;;   :custom
;;   (python-indent-offset 4)
;;   (python-indent-guess-indent-offset nil)
;;   (python-shell-interpreter "python3")
;;   (python-shell-completion-native-enable nil)  ; Avoid readline issues
;;   :config
;;   ;; Remove trailing whitespace on save
;;   (add-hook 'python-mode-hook
;;             (lambda ()
;;               (add-hook 'before-save-hook #'delete-trailing-whitespace nil t))))

;; Python Tree-Sitter mode configuration
;; Emacs 29+ includes built-in tree-sitter support
(use-package python
  :ensure nil
  :mode ("\\.py\\'" . python-ts-mode)
  :interpreter ("python" . python-ts-mode)
  :custom
  (python-indent-offset 4)
  (python-indent-guess-indent-offset nil)
  (python-shell-interpreter "python3")
  (python-shell-completion-native-enable nil)  ; Avoid readline issues
  :init
  ;; Remap python-mode to python-ts-mode when tree-sitter is available
  (when (and (fboundp 'treesit-available-p)
             (treesit-available-p))
    (add-to-list 'major-mode-remap-alist '(python-mode . python-ts-mode)))

  :config
  ;; Ensure tree-sitter grammar is installed
  (defun fu-python-ensure-treesit-grammar ()
    "Ensure Python tree-sitter grammar is installed."
    (when (and (fboundp 'treesit-available-p)
               (treesit-available-p)
               (not (treesit-language-available-p 'python)))
      (message "Python tree-sitter grammar not found. Install with M-x treesit-install-language-grammar RET python RET")))

  (add-hook 'after-init-hook #'fu-python-ensure-treesit-grammar)

  ;; Common settings for both python-mode and python-ts-mode
  (defun fu-python-common-setup ()
    "Common setup for Python modes."
    (setq-local tab-width 4)
    (setq-local indent-tabs-mode nil)
    ;; Remove trailing whitespace on save
    (add-hook 'before-save-hook #'delete-trailing-whitespace nil t))

  (add-hook 'python-mode-hook #'fu-python-common-setup)
  (add-hook 'python-ts-mode-hook #'fu-python-common-setup))

;; Virtual environment management
(use-package pyvenv
  :defer t
  :config
  (setq pyvenv-mode-line-indicator '(pyvenv-virtual-env-name ("[venv:" pyvenv-virtual-env-name "] ")))

  ;; Automatically activate virtual environment
  (defun fu-python-auto-activate-venv ()
    "Automatically activate Python virtual environment if found.
Looks for .venv, venv, env directories or .python-version file."
    (interactive)
    (let* ((project-root (or (locate-dominating-file default-directory ".venv")
                             (locate-dominating-file default-directory "venv")
                             (locate-dominating-file default-directory "env")
                             (locate-dominating-file default-directory ".python-version")))
           (venv-path (when project-root
                        (or (expand-file-name ".venv" project-root)
                            (expand-file-name "venv" project-root)
                            (expand-file-name "env" project-root)))))
      (when (and venv-path (file-directory-p venv-path))
        (pyvenv-activate venv-path)
        (message "Activated virtual environment: %s" venv-path))))

  (add-hook 'python-mode-hook #'fu-python-auto-activate-venv)
  (add-hook 'python-ts-mode-hook #'fu-python-auto-activate-venv))

;; Python formatting with Black
(use-package python-black
  :defer t
  :commands (python-black-buffer python-black-region python-black-on-save-mode)
  :hook ((python-mode python-ts-mode) . python-black-on-save-mode-enable-dwim)
  :config
  (defun python-black-on-save-mode-enable-dwim ()
    "Enable python-black-on-save-mode if black is available."
    (when (executable-find "black")
      (python-black-on-save-mode 1))))

;; Import sorting with isort
(use-package py-isort
  :defer t
  :commands (py-isort-buffer py-isort-region)
  :hook (before-save . fu-python-isort-before-save)
  :config
  (defun fu-python-isort-before-save ()
    "Run isort before saving if in python-mode or python-ts-mode and isort is available."
    (when (and (derived-mode-p 'python-mode 'python-ts-mode)
               (executable-find "isort"))
      (py-isort-buffer))))

;; Python REPL enhancements
(use-package python
  :ensure nil
  :config
  ;; Send region/buffer to Python REPL
  (defun fu-python-shell-send-region-or-buffer ()
    "Send the current region to Python REPL, or whole buffer if no region is active."
    (interactive)
    (if (use-region-p)
        (python-shell-send-region (region-beginning) (region-end))
      (python-shell-send-buffer)))

  (defun fu-python-shell-send-defun-and-step ()
    "Send the current function to Python REPL and move to next function."
    (interactive)
    (python-shell-send-defun nil)
    (python-nav-forward-defun))

  :bind ((:map python-mode-map
               ("C-c C-c" . fu-python-shell-send-region-or-buffer)
               ("C-c C-n" . fu-python-shell-send-defun-and-step)
               ("C-c C-r" . python-shell-send-region)
               ("C-c C-b" . python-shell-send-buffer)
               ("C-c C-z" . python-shell-switch-to-shell))
         (:map python-ts-mode-map
               ("C-c C-c" . fu-python-shell-send-region-or-buffer)
               ("C-c C-n" . fu-python-shell-send-defun-and-step)
               ("C-c C-r" . python-shell-send-region)
               ("C-c C-b" . python-shell-send-buffer)
               ("C-c C-z" . python-shell-switch-to-shell))))

;; Python debugger (pdb) integration
(use-package realgud
  :defer t
  :commands (realgud:pdb))

;; Optional: IPython integration
;; Uncomment if you prefer IPython over standard Python REPL
;; (use-package python
;;   :config
;;   (when (executable-find "ipython")
;;     (setq python-shell-interpreter "ipython"
;;           python-shell-interpreter-args "-i --simple-prompt --no-color-info"
;;           python-shell-prompt-regexp "In \\[[0-9]+\\]: "
;;           python-shell-prompt-output-regexp "Out\\[[0-9]+\\]: "
;;           python-shell-completion-setup-code
;;           "from IPython.core.completerlib import module_completion"
;;           python-shell-completion-module-string-code
;;           "';'.join(module_completion('''%s'''))\n"
;;           python-shell-completion-string-code
;;           "';'.join(get_ipython().Completer.all_completions('''%s'''))\n")))

;; LSP configuration with Eglot
(with-eval-after-load 'eglot
  ;; Prefer pyright over pylsp if available
  (when (executable-find "pyright-langserver")
    (add-to-list 'eglot-server-programs
                 '(python-mode . ("pyright-langserver" "--stdio"))))

  ;; Enable eglot for Python
  (add-hook 'python-mode-hook #'eglot-ensure)
  (add-hook 'python-ts-mode-hook #'eglot-ensure))

;; Flycheck for additional linting (optional, alongside Flymake)
;; (use-package flycheck
;;   :hook (python-mode . flycheck-mode)
;;   :config
;;   (setq flycheck-python-flake8-executable "python3"
;;         flycheck-python-pycompile-executable "python3"
;;         flycheck-python-pylint-executable "python3"))

;; Jupyter notebook support (optional)
;; (use-package ein
;;   :defer t
;;   :commands (ein:run ein:login))

(provide 'prog-python-conf)
;;; prog-python-conf.el ends here