dev-config.el Wed, Sep 2, 2026

;;; dev-config.el --- Development tools and programming modes -*- lexical-binding: t -*-

;;; Commentary:
;; Configuration for programming languages, completion, and development tools

;;; Code:

;; Tree-sitter major mode remapping (built-in in Emacs 29+)
(setq major-mode-remap-alist
      '((go-mode . go-ts-mode)
        (python-mode . python-ts-mode)
        (yaml-mode . yaml-ts-mode)))

;; Built-in inline completion preview (Emacs 30+)
(use-package completion-preview
  :ensure nil
  :hook (prog-mode . completion-preview-mode)
  :bind (:map completion-preview-active-mode-map
              ("M-n" . completion-preview-next-candidate)
              ("M-p" . completion-preview-prev-candidate)
              ("TAB" . completion-preview-complete)
              ("<tab>" . completion-preview-complete)))

(use-package eglot
  :ensure nil  ; Built into Emacs 29+
  :hook (((go-mode go-ts-mode) . eglot-ensure)
         ((python-mode python-ts-mode) . eglot-ensure))
  :bind (:map eglot-mode-map
              ("M-g n" . flymake-goto-next-error)
              ("M-g p" . flymake-goto-prev-error))
  :custom
  (eglot-autoshutdown t)
  (eldoc-echo-area-use-multiline-p 4)
  (eglot-confirm-server-initiated-edits nil)
  :config
  (setq eglot-report-progress nil)
  (setq eglot-workspace-configuration
      '((:gopls . ((env . (("GOFLAGS" . "-buildvcs=false")))))))
  (add-to-list 'eglot-server-programs
               '((python-mode python-ts-mode) . ("ruff" "server")))
  (add-to-list 'eglot-server-programs
               '((go-mode go-ts-mode) . ("gopls"))))

;; venv madness - this is kind of a mess, but it works somehow
(defun dws/set-python-path (directory)
  "Set up PYTHONPATH to use the given DIRECTORY.
If eglot is running, reconnects it to pick up the new path."
  (interactive "DPython path directory: ")
  (let* ((expanded (expand-file-name directory))
         (old-pythonpath (getenv "PYTHONPATH"))
         (new-pythonpath (if old-pythonpath
                             (concat expanded path-separator old-pythonpath)
                           expanded)))
    (setenv "PYTHONPATH" new-pythonpath)
    (when (boundp 'python-shell-extra-pythonpaths)
      (setq python-shell-extra-pythonpaths (list expanded)))
    (when (and (bound-and-true-p eglot--managed-mode)
               (not (string= old-pythonpath (getenv "PYTHONPATH"))))
      (message "PYTHONPATH changed, reconnecting eglot...")
      (eglot-reconnect (eglot-current-server)))))

;; Syntax checking with Flycheck (for non-Eglot modes)
(use-package flycheck
  :ensure t
  :defer 3
  :hook (after-init . global-flycheck-mode)
  :custom
  (flycheck-check-syntax-automatically '(save idle-changes mode-enabled))
  (flycheck-idle-change-delay 1.25)
  (flycheck-disabled-checkers '(go-gofmt go-build go-test rst-sphinx html-tidy python-flake8 python-pylint python-pycompile python-ruff)))

;; Go Mode Configuration (built-in go-ts-mode in Emacs 29+)
(use-package go-ts-mode
  :ensure nil
  :mode ("\\.go\\'" . go-ts-mode)
  :hook (((go-mode go-ts-mode) . (lambda ()
                                   (setq-local indent-tabs-mode t)
                                   (add-hook 'before-save-hook #'dws/go-save-hook nil t))))
  :config
  (defun dws/go-save-hook ()
    "Run formatting before saving."
    (when (bound-and-true-p eglot--managed-mode)
      (eglot-format-buffer)
      (call-interactively #'eglot-code-action-organize-imports))))

;; Python development
(use-package python
  :ensure nil  ; Built into Emacs
  :hook (((python-mode python-ts-mode) . dws-python-mode-hook)
         ((python-mode python-ts-mode) . (lambda ()
                                           (add-hook 'before-save-hook #'dws/python-save-hook nil t))))
  :custom
  (python-shell-interpreter (concat home-dir "/python/bin/python"))
  (python-indent-offset 4)            ;; Emacs default indentation
  (python-indent-guess-indent-offset nil)
  :config
  ;; Custom python-mode hook
  (defun dws-python-mode-hook ()
    "Simple Python setup."
    (setq-local indent-tabs-mode nil)   ;; Don't use tabs
    (setq-local tab-width 4)
    (add-hook 'before-save-hook 'delete-trailing-whitespace nil t))

  (defun dws/python-save-hook ()
    "Run formatting before saving."
    (when (and (bound-and-true-p eglot--managed-mode)
               (derived-mode-p 'python-mode 'python-ts-mode))
      (eglot-format-buffer))))


;; Web Development
(use-package web-mode
  :ensure t
  :defer 10
  :mode ("\\.html\\'" . web-mode)
  :hook dws/setdjangoengine
  :config
  (setq web-mode-engines-alist
        '(("django" . "\\.dhtml\\'")
          ("go" . "\\.gohtml\\'"))))

;; Built-in Project Management (project.el in Emacs 29+)
(use-package project
  :ensure nil
  :demand t
  :bind-keymap
  ("C-c p" . project-prefix-map)
  :custom
  (project-vc-extra-root-markers '(".fslckout" "requirements.txt" "manage.py"))
  :config
  (with-eval-after-load 'consult
    (define-key project-prefix-map "s" #'consult-ripgrep)))

;; Web engine detection using built-in project.el
(defun dws/setdjangoengine ()
  "Set django as web engine when in django dir."
  (when-let* ((proj (project-current))
              (root (project-root proj)))
    (when (file-exists-p (expand-file-name "manage.py" root))
      (web-mode-set-engine "django"))))

;; Magit Configuration
(use-package magit
  :ensure t
  :config
  (defun git-commit-finish-query-functions (force) t)
  (setq git-commit-summary-max-length 500))

;; YASnippet Configuration
(use-package yasnippet
  :ensure t
  :diminish yas-minor-mode
  :custom
  (yas-snippet-dirs '("~/.emacs.d/snippets" yas-installed-snippets-dir))
  :config
  (yas-global-mode 1))

;; Utility Packages
(use-package avy
  :ensure t
  :demand
  :bind (("M-m" . avy-goto-word-1)
         ("M-g f" . avy-goto-line)))

(use-package expand-region
  :ensure t
  :bind ("M-SPC" . er/expand-region))

;; Programming mode common configuration (using built-in comment-line)
(use-package prog-mode
  :bind ("C-c c" . comment-line))

;; Electric pair mode configuration
(setq electric-pair-inhibit-predicate 'electric-pair-conservative-inhibit)
(electric-pair-mode t)

;; Uniquify buffer names
(use-package uniquify
  :demand
  :custom
  (uniquify-buffer-name-style 'forward))

;; YAML mode (built-in via Tree-sitter in Emacs 30+)
(use-package yaml-ts-mode
  :ensure nil
  :mode ("\\.ya?ml\\'" "/ansible/.*/[^.]+\\'")
  :hook (yaml-ts-mode . (lambda ()
                          (setq indent-tabs-mode nil)
                          (setq-local indent-tabs-mode nil)
                          (setq yaml-indent-offset 2))))

;; Markdown mode
(use-package markdown-mode
  :ensure t
  :mode ("\\.md\\'" "\\.markdown\\'"))

;; Nginx mode
(use-package nginx-mode
  :ensure t)

(provide 'dev-config)
;;; dev-config.el ends here