;;; 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