;;; ui-config.el --- UI configuration -*- lexical-binding: t -*-
;;; Commentary:
;; Visual and interface settings
;;; Code:
;; Custom cleanup function instead of midnight mode
;; run manually like (dws/cleanup-buffers 4)
(defcustom dws/cleanup-buffer-excludes
'("\\*scratch\\*"
"\\*Messages\\*"
"\\.org$"
"TAGS$"
"\\*compilation\\*"
"\\*grep\\*"
"\\*Backtrace\\*")
"Regexps of buffer names to exclude from cleanup."
:type '(repeat regexp)
:group 'convenience)
(defun dws/cleanup-buffers (hours)
"Kill stale buffers not visible and unmodified, older than HOURS."
(interactive "Kill buffers not seen in the last N hours: ")
(let* ((now (float-time))
(age-threshold (* hours 3600))
(killed '()))
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (and buffer-display-time ; only process if buffer was ever displayed
(not (get-buffer-window buf t)) ; not visible
(not (buffer-modified-p buf)) ; not dirty
(buffer-file-name buf) ; file buffer
(not (string-prefix-p " " (buffer-name buf))) ; not internal
(> (- now (float-time buffer-display-time)) age-threshold)
(not (seq-some (lambda (re) (string-match-p re (buffer-name buf)))
dws/cleanup-buffer-excludes)))
(push (buffer-name buf) killed)
(kill-buffer buf))))
(if killed
(message "Killed %d stale buffer%s (>%dh): %s"
(length killed)
(if (= 1 (length killed)) "" "s")
hours
(string-join (reverse killed) ", "))
(message "No stale buffers older than %d hours." hours))))
;; run every couple of hours
(run-with-idle-timer (* 120 60) t (lambda () (dws/cleanup-buffers 5)))
;; Modeline cleanup - simplify while keeping line/column numbers
(setq-default mode-line-mule-info nil) ; Hides "U" (UTF-8)
(setq-default mode-line-client nil) ; Hides "@" for emacsclient
(setq-default mode-line-remote nil) ; Hides "-" for remote
(setq-default mode-line-frame-identification nil) ; Hides " " or "F1"
(setq-default mode-line-percent-position nil) ; Hides "Bot", "Top", "45%"
(setq vc-handled-backends nil) ; Disable VC (hides "Git:..." and speeds up startup)
;; Window system specific configuration
(when window-system
(server-start)
(menu-bar-mode 1)
(bind-key "C-x C-c" 'kill-buffer)
(bind-key "H-n" 'make-frame)
(bind-key "H-w" 'delete-frame)
(bind-key "H-o" 'other-frame)
(bind-key "H-m" 'lower-frame)
(bind-key "H-h" 'lower-frame))
;; ;; Font configuration
;; (when (member "Source Code Variable" (font-family-list))
;; (set-face-attribute 'default nil
;; :font "Source Code Pro:pixelsize=14:weight=regular:antialias=1")))
;; Old way to load the theme - doesn't like starting as a daemon
;; (use-package nord-theme
;; :ensure t
;; :config
;; (load-theme 'nord t))
;; new way to load the theme.
(use-package nord-theme
:ensure t
:demand t ; Ensure the package code is available in the daemon
:init
(defun my-nord-theme-setup (frame)
"Load Nord theme only when a frame is created."
;; The with-selected-frame ensures the load-theme command
;; is executed in the context of the new client/frame.
(with-selected-frame frame
;; This check prevents the theme from being loaded multiple times
;; if you open several windows, but still loads it if it's the first time.
(unless (member 'nord custom-enabled-themes)
(load-theme 'nord t)
;; Also make sure true-color is set for this frame
(setq x-term-true-color t))))
;; Add the function to the hook that runs when a frame is created
(add-hook 'after-make-frame-functions #'my-nord-theme-setup))
;; (use-package solarized-theme
;; :ensure t
;; :config
;; ;; Optional: tweak options *before* loading the theme
;; (setq solarized-scale-org-headlines nil ;; e.g. disable headline scaling
;; solarized-use-less-bold t
;; solarized-distinct-fringe-background t)
;; ;; Load Zenburn-inspired variant
;; (load-theme 'solarized-zenburn t))
;; for the function/class hints
(which-function-mode 1)
(use-package vertico
:ensure t
:init
(vertico-mode)
:config
(setq vertico-cycle t
vertico-count 12))
(use-package orderless
:ensure t
:defer 10
:custom
(completion-styles '(orderless basic))
(completion-category-overrides '((file (styles basic partial-completion)))))
(use-package marginalia
:ensure t
:defer 10
:init
(marginalia-mode))
(use-package consult
:ensure t
:defer 1
:bind (;; Modified from your previous ivy bindings
("C-s" . consult-line) ; replaces swiper
("M-y" . consult-yank-pop)
("M-b" . consult-buffer)))
;; Mac-specific configuration
(when (eq system-type 'darwin)
(setq mac-option-key-is-meta t
mac-command-modifier 'hyper
mac-option-modifier 'meta)
;; Clipboard integration
(defun isolate-kill-ring()
"Isolate Emacs kill ring from OS X system pasteboard."
(interactive)
(setq interprogram-cut-function nil)
(setq interprogram-paste-function nil))
(defun pasteboard-copy()
"Copy region to OS X system pasteboard."
(interactive)
(shell-command-on-region
(region-beginning) (region-end) "pbcopy"))
(defun pasteboard-paste()
"Paste from OS X system pasteboard via `pbpaste' to point."
(interactive)
(shell-command-on-region
(point) (if mark-active (mark) (point)) "pbpaste" nil t))
(defun pasteboard-cut()
"Cut region and put on OS X system pasteboard."
(interactive)
(pasteboard-copy)
(delete-region (region-beginning) (region-end)))
(isolate-kill-ring)
(bind-key "H-c" 'pasteboard-copy)
(bind-key "H-v" 'pasteboard-paste)
(bind-key "H-x" 'pasteboard-cut))
;; Linux-specific configuration
(when (eq system-type 'linux)
(setq x-alt-keysym 'meta))
;; Which-key configuration
(use-package which-key
:ensure t
:diminish which-key-mode
:defer 10
:config
(which-key-mode))
;; Global diminishes for built-in or already-loaded modes
(with-eval-after-load 'eldoc
(diminish 'eldoc-mode))
(with-eval-after-load 'autorevert
(diminish 'auto-revert-mode))
(use-package tramp
:config
(add-to-list 'tramp-remote-path "~/bin")
(add-to-list 'tramp-remote-path "~/python/bin")
(add-to-list 'tramp-remote-path "/usr/local/go/bin/")
(add-to-list 'tramp-remote-path 'tramp-own-remote-path))
;; Window management functions
(defun dws/rotate-windows ()
"Rotate your windows."
(interactive)
(cond ((not (> (count-windows)1))
(message "You can't rotate a single window!"))
(t
(setq i 1)
(let ((numWindows (count-windows)))
(while (< i numWindows)
(let* ((w1 (elt (window-list) i))
(w2 (elt (window-list) (+ (% i numWindows) 1)))
(b1 (window-buffer w1))
(b2 (window-buffer w2))
(s1 (window-start w1))
(s2 (window-start w2)))
(set-window-buffer w1 b2)
(set-window-buffer w2 b1)
(set-window-start w1 s2)
(set-window-start w2 s1)
(setq i (1+ i))))))))
(provide 'ui-config)
;;; ui-config.el ends here
;;; shell-config.el --- Shell and terminal configuration -*- lexical-binding: t -*-
;;; Commentary:
;; Configuration for shells, terminals, and related tools
;;; Code:
;; Eshell configuration
(use-package eshell
:bind ("M-/" . eshell)
:custom
(eshell-buffer-maximum-lines 10240)
(eshell-scroll-show-maximum-output t)
(eshell-scroll-to-bottom-on-input 'all)
(eshell-scroll-to-bottom-on-output 'all)
(eshell-where-to-jump 'begin)
(eshell-review-quick-commands nil)
(eshell-smart-space-goes-to-end t)
(eshell-hist-ignoredups t)
(eshell-cmpl-cycle-completions t)
(eshell-history-size 10000)
(eshell-status-in-mode-line nil)
:config
(setenv "EDITOR" "emacsclient")
(setenv "PAGER" "")
;; Set modules
(setq eshell-modules-list
'(eshell-alias
eshell-basic
eshell-cmpl
eshell-dirs
eshell-glob
eshell-hist
eshell-ls
eshell-pred
eshell-prompt
eshell-script
eshell-unix))
;; Set path - now uses exec-path
;; Define custom command to handle beginning of line
(defun eshell-maybe-bol ()
"Go to beginning of command line prompt, then to beginning of line."
(interactive)
(let ((p (point)))
(eshell-bol)
(when (= p (point))
(beginning-of-line))))
;; Eshell hook for customization
(defun dws/eshell-hook ()
"My eshell hook."
(define-key eshell-mode-map (kbd "C-a") 'eshell-maybe-bol)
(rename-buffer "*eshell*" t))
;; Add aliases
(defun eshell-add-aliases ()
"Add custom aliases to eshell."
(dolist (aliasvar '(("ff" "for i in ${eshell-flatten-list $*} {find-file $i}")
("fo" "find-file-other-window $1")
("fsl" "fossil $*")
("ll" "ls -l $*")
("ltr" "ls -ltr $*")))
(add-to-list 'eshell-command-aliases-list aliasvar)))
:hook
((eshell-mode . dws/eshell-hook)
(eshell-post-command . eshell-add-aliases)))
;; frequently used dirs with z
;; (use-package eshell-z
;; :after eshell
;; :ensure t
;; :hook (eshell-mode . eshell-z))
;; eat for visual commands
;; You'll need to 'tic -x ~/.emacs.d/elpa/eat-0.9.4/eat.ti
;; Version will obviously change over time
(use-package eat
:ensure t
:hook (eshell-mode . eat-eshell-mode)
:config
;; This ensures the variable exists before we try to add 'jj' to it
(with-eval-after-load 'eat-eshell
(add-to-list 'eat-eshell-visual-commands "jj")))
;;; org-config.el --- Org mode configuration -*- lexical-binding: t -*-
;;; Commentary:
;; Configuration for Org mode and related features
;;; Code:
(use-package org
:commands (org-agenda org-switchb org-capture)
:bind (("C-c a" . org-agenda)
("C-c b" . org-switchb)
("M-c" . org-capture)
("M-t". org-set-tags-command))
:mode ("\\.org\\'" . org-mode)
:custom
(org-stuck-projects
'("+LEVEL=2/-DONE"
("TODO" "WAITING" "DONE")
("FLAGGED")
""))
(org-adapt-indentation nil)
(org-fontify-whole-heading-line t)
(org-log-done 'time)
(org-log-into-drawer t)
(org-agenda-files '("~/Sync/v/kf/todo.org"
"~/Sync/org/gtd.org"
"~/Sync/org/blog.org"
"~/Sync/org/guitar.org"))
(org-return-follows-link t)
(org-refile-use-outline-path 'file)
(org-outline-path-complete-in-steps nil)
(org-agenda-include-diary t)
:config
(setq org-refile-targets
'((org-agenda-files . (:maxlevel . 4))))
(defun dws/org-mode-hook ()
"Setup for org mode."
(visual-line-mode 1)
(flyspell-mode 1)
(auto-fill-mode 1))
(add-hook 'org-mode-hook 'dws/org-mode-hook)
(defun dws/journal-here ()
"Build the Year/Month/Day hierarchy in the current buffer and jump to it."
(interactive)
;; This "unlocks" the functions you couldn't find
(require 'org-datetree)
;; This builds the tree for today
(org-datetree-find-date-create (calendar-current-date))
;; This moves you to the bottom of today's entry
(org-end-of-subtree t))
(setq org-capture-templates
'(("j" "Journal" entry
(file+olp+datetree "~/Sync/org/journal.org" "Journal")
"***** %^{Description}\n%?\n")
("n" "Notes - in journal" entry
(file+olp+datetree "~/Sync/org/journal.org" "Notes")
"***** %^{Description}\n%?\n")
("g" "gtd" entry
(file+olp "~/Sync/org/gtd.org" "Inbox")
"** %^{Description}\n%?\n\n")
("t" "kf todo" entry
(file+olp "~/Sync/v/kf/todo.org" "Current")
"** %^{Description}\n%?\n\n")
("b" "Blog" entry
(file+olp "~/Sync/org/blog.org" "Ideas")
"** %?\n*** concept\n***draft\n\n"))))
(provide 'org-config)
;;; org-config.el ends here
;;; keybindings.el --- Custom keybindings and functions -*- lexical-binding: t -*-
;;; Commentary:
;; Custom key bindings and utility functions with conflict resolution
;;; Code:
;; Simple aliases
(defalias 'qrr 'query-replace-regexp)
(defalias 'tmm 'tmm-menubar)
(defalias 'qrs 'replace-string)
(defalias 'qfs 'flymake-show-buffer-diagnostics)
;; Utility functions
(defun dws/rename-current-buffer-file ()
"Renames current buffer and file it is visiting."
(interactive)
(let ((name (buffer-name))
(filename (buffer-file-name)))
(if (not (and filename (file-exists-p filename)))
(error "Buffer '%s' is not visiting a file!" name)
(let ((new-name (read-file-name "New name: " filename)))
(if (get-buffer new-name)
(error "A buffer named '%s' already exists!" new-name)
(rename-file filename new-name 1)
(rename-buffer new-name)
(set-visited-file-name new-name)
(set-buffer-modified-p nil)
(message "File '%s' successfully renamed to '%s'"
name (file-name-nondirectory new-name)))))))
(defun dws/kill-other-buffers ()
"Kill all other buffers."
(interactive)
(mapc 'kill-buffer (delq (current-buffer) (buffer-list))))
(defun dws/revert-all-buffers ()
"Refreshes all open buffers from their respective files."
(interactive)
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (and (buffer-file-name)
(file-exists-p (buffer-file-name))
(not (buffer-modified-p)))
(revert-buffer t t t))))
(message "Refreshed open files."))
(defun dws/join-line()
"Join lines at point."
(interactive)
(join-line -1))
(defun dws/set-path ()
"Set up Emacs exec path and PATH environment variable."
(interactive)
(let ((path-from-shell
(replace-regexp-in-string
"[ \t\n]*$" ""
(shell-command-to-string
"$SHELL --login -i -c 'echo $PATH'"))))
(setenv "PATH" path-from-shell)
(setq exec-path (split-string path-from-shell path-separator))))
;; Terminal management
(defun dws/switch-terminal ()
"Switch between eshell and multi-term based on context."
(interactive)
(if (or (eq major-mode 'shell-mode)
(eq major-mode 'term-mode))
;; If we're in a terminal, make a new one
(multi-term)
;; Otherwise switch to most recent terminal or make new
(if (get-buffer "*shell*")
(switch-to-buffer "*shell*")
(shell))))
;; Global keybindings
(bind-keys*
;; Basic editing
("C--" . undo)
("M-j" . dws/join-line)
("DEL" . delete-backward-char) ;; Instead of C-h to preserve help
("C-w" . backward-kill-word)
("C-x ," . dabbrev-expand)
("C-x C-k" . kill-region)
;; Window management (preserved as they're unique)
("M--" . enlarge-window-horizontally)
("M-0" . delete-window)
("M-1" . delete-other-windows)
("M-2" . split-window-below)
("M-3" . split-window-right)
("M-5" . balance-windows)
("M-o" . other-window) ;; Single definition
;; Buffer operations with safer bindings
("M-k" . kill-buffer)
("C-x C-s" . save-buffer) ;; Standard Emacs binding
("M-G" . goto-line) ;; Changed from M-g to preserve goto prefix
;; Terminal management (unified)
;; ("M-/" . dws/switch-terminal) ;; Single smart terminal command
;; setting back to eshell for now
("M-/" . eshell)
;; Custom functions
("C-c r" . dws/revert-all-buffers) ;; Simplified from C-c C-M-r
("C-c d" . delete-frame) ;; Simplified from C-c C-d
("C-c v" . dws/rotate-windows) ;; Simplified from C-c C-v
;; Mac-specific (preserved)
("H-8" . toggle-frame-fullscreen))
;; Mode-specific keybindings
(with-eval-after-load 'term
(bind-keys :map term-mode-map
("M-p" . term-send-up)
("M-n" . term-send-down)
("M-b" . consult-buffer)))
(provide 'keybindings)
;;; keybindings.el ends here
;;; init.el --- Modern Emacs configuration -*- lexical-binding: t -*-
;;; Commentary:
;; Core initialization and package management
;;; Code:
;; Fix home dir when it is a symlink (and other directories)
;; Always resolve symlinks in file buffers
(add-hook 'find-file-hook
(lambda ()
(when buffer-file-name
(setq default-directory
(file-name-directory (file-truename buffer-file-name))))))
;; Canonicalize home directory on startup
(setq default-directory (file-truename default-directory))
;; Performance and startup optimizations
(setq gc-cons-threshold 100000000) ; 100mb
(setq gc-cons-percentage 0.6)
(add-hook 'focus-out-hook #'garbage-collect)
(setq read-process-output-max (* 1024 1024)) ; 1mb
(setq process-adaptive-read-buffering nil) ; Disable adaptive buffering
;; File handling
;; You already have create-lockfiles nil and backup settings, so skip those
;; Display optimizations
(setq bidi-inhibit-bpa t) ; Disable bidirectional parenthesis
(setq-default bidi-paragraph-direction 'left) ; Disable bidirectional rendering
(setq bidi-display-reordering nil) ; Disable reordering of bidirectional text
(setq inhibit-compacting-font-caches t) ; Don't compact font caches during GC
;; Terminal responsiveness and scrolling behavior
(setq redisplay-skip-fontification-on-input t)
(setq fast-but-imprecise-scrolling t)
(setq echo-keystrokes 0.01)
(setq scroll-conservatively 101)
(setq scroll-margin 0)
(setq scroll-preserve-screen-position nil)
(setq auto-window-vscroll nil)
;; Minibuffer optimizations (add to your existing completion settings)
(setq enable-recursive-minibuffers t)
(setq read-buffer-completion-ignore-case t)
(setq read-file-name-completion-ignore-case t)
(setq completion-cycle-threshold 3)
(setq completions-detailed t)
;; Basic UI settings without use-package
(menu-bar-mode -1)
(when (fboundp 'tool-bar-mode)
(tool-bar-mode -1))
(when (fboundp 'scroll-bar-mode)
(scroll-bar-mode -1))
(global-eldoc-mode -1)
(delete-selection-mode t)
(save-place-mode t)
;; Basic settings that don't need use-package
(setq inhibit-startup-screen t
use-dialog-box nil
frame-title-format '("Emacs: %f")
icon-title-format "Emacs - %b"
case-fold-search t
column-number-mode t
create-lockfiles nil
current-language-environment "utf-8"
default-input-method "utf-8"
indent-tabs-mode nil
standard-indent 4
large-file-warning-threshold nil)
;; Package setup
(require 'package)
(setq package-archives
'(("gnu" . "https://elpa.gnu.org/packages/")
("melpa" . "https://melpa.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
(setq package-enable-at-startup nil)
(package-initialize)
;; Bootstrap use-package
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(eval-when-compile
(require 'use-package))
(require 'bind-key)
;; Package list (preserved from original config)
(defvar dws/package-list
'(async
avy
company
company-jedi
diminish
emmet-mode
expand-region
epl
flycheck
go-mode
markdown-mode
magit
multi-term
nginx-mode
org
popup
projectile
python-environment
python-mode
solarized-theme
use-package
web-mode
which-key
vertico
orderless
yaml-mode
yasnippet))
;; Package installation function
(defun dws/install-packages ()
"Install my list of packages."
(interactive)
(unless package-archive-contents
(package-refresh-contents))
(dolist (pkg dws/package-list)
(unless (package-installed-p pkg)
(package-install pkg))))
;; Helper macro for conditional library loading
(defmacro with-library (symbol &rest body)
`(when (require ,symbol nil t)
,@body))
(put 'with-library 'lisp-indent-function 1)
;; UTF-8 configuration
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(prefer-coding-system 'utf-8)
(setenv "LANG" "en_US.UTF-8")
;; Path setup
(defvar home-dir (expand-file-name "~"))
(let ((my-paths (list
"/usr/local/go/bin"
"/opt/homebrew/bin"
(concat home-dir "/bin")
(concat home-dir "/python/bin"))))
(dolist (path my-paths)
(add-to-list 'exec-path path)
(setenv "PATH" (concat path path-separator (getenv "PATH")))))
;; Backup settings
(use-package files
:defer 5
:custom
(backup-by-copying nil)
(backup-directory-alist '(("." . "~/.emacs.d/autosaves/")))
(delete-old-versions t)
(kept-new-versions 6)
(kept-old-versions 3))
(use-package exec-path-from-shell
:ensure t
:config
(when (memq window-system '(mac ns x))
(exec-path-from-shell-initialize)))
;; Default simple settings
(setq-default tab-width 4)
(fset 'yes-or-no-p 'y-or-n-p)
;; Load the rest of the configuration
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
(load "ui-config")
(load "dev-config")
(load "org-config")
(load "shell-config")
(load "keybindings")
;; sometimes yaml still goes back to tabs
(add-hook 'yaml-mode-hook
(lambda ()
(setq indent-tabs-mode nil) ; belt
(setq-local indent-tabs-mode nil) ; and suspenders
(setq yaml-indent-offset 2)))
;; trying to catch when this changes
(add-variable-watcher 'indent-tabs-mode
(lambda (symbol newval operation where)
(message "indent-tabs-mode changed to %s in buffer %s by %s"
newval
(buffer-name where)
operation)))
(provide 'init)
;;; init.el ends here
(custom-set-variables
;; custom-set-variables was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(column-number-mode t)
'(custom-safe-themes
'("5a4cdc4365122d1a17a7ad93b6e3370ffe95db87ed17a38a94713f6ffe0d8ceb"
"51ec7bfa54adf5fff5d466248ea6431097f5a18224788d0bd7eb1257a4f7b773"
default))
'(tool-bar-mode nil))
;;; dev-config.el --- Development tools and programming modes -*- lexical-binding: t -*-
;;; Commentary:
;; Configuration for programming languages, completion, and development tools
;;; Code:
;; Company for auto-completion (but prevent RET from completing)
(use-package company
:ensure t
:diminish
:defer 1
:bind (:map company-active-map
("M-n" . company-select-next)
("M-p" . company-select-previous)
("<return>" . nil) ; Prevent RET from completing
("RET" . nil) ; Prevent RET from completing
("TAB" . company-complete-selection)
("<tab>" . company-complete-selection))
:custom
(company-idle-delay 0.2)
(company-minimum-prefix-length 2)
(company-show-quick-access 'left)
(company-tooltip-flip-when-above t)
(company-require-match nil)
:hook
(prog-mode . company-mode))
(use-package eglot
:ensure nil ; Built into Emacs 29+
:hook ((python-mode . eglot-ensure)
(go-mode . eglot-ensure))
:bind (:map eglot-mode-map
("C-c n" . flymake-goto-next-error))
:custom
(eglot-autoshutdown t)
(eldoc-echo-area-use-multiline-p 4)
(eglot-confirm-server-initiated-edits nil)
:config
;; (setenv "GOFLAGS" "-buildvcs=false")
(setq eglot-stay-out-of nil)
(setq eglot-report-progress nil)
(setq eglot-workspace-configuration
'((:gopls . ((env . (("GOFLAGS" . "-buildvcs=false")))))))
(add-to-list 'eglot-server-programs
'(python-mode . ("ruff" "server")))
(add-to-list 'eglot-server-programs
'(go-mode . ("gopls" "-remote=auto"))))
;; 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
(use-package flycheck
:ensure t
:diminish
: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-python-pylint-executable "~/python/bin/pylint")
(flycheck-flake8-maximum-line-length 120)
(flycheck-disabled-checkers '(go-gofmt go-build go-test rst-sphinx html-tidy python-flake8 python-pylint python-pycompile)))
;; Go Mode Configuration
(use-package go-mode
:mode ("\\.go\\'" . go-mode)
:hook (before-save . dws/go-save-hook)
:config
(defun dws/go-save-hook ()
"Run formatting before saving."
(when (bound-and-true-p eglot--managed-mode)
(eglot-format-buffer)
(eglot-code-action-organize-imports nil)))
(setq indent-tabs-mode 1))
;; Python development
(use-package python
:ensure nil ; Built into Emacs
:hook ((python-mode . dws-python-mode-hook)
(python-mode . eglot-ensure)
(before-save . dws/python-save-hook))
: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 and organize imports before saving."
(when (and (bound-and-true-p eglot--managed-mode)
(derived-mode-p 'python-mode))
(eglot-format-buffer)
(eglot-code-action-organize-imports nil))))
;; 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\\'")))
(setq web-mode-ac-sources-alist
'(("html" . (ac-source-emmet-html-aliases ac-source-emmet-html-snippets))
("css" . (ac-source-css-property ac-source-emmet-css-snippets)))))
;; Projectile configuration
(use-package projectile
:ensure t
:diminish
:defer 1
:bind-keymap
("C-c p" . projectile-command-map)
:custom
(projectile-completion-system 'default) ; Works with vertico/consult
(projectile-indexing-method 'native)
(projectile-enable-caching t)
(projectile-ignored-projects '("~/" "/tmp"))
(projectile-globally-ignored-files
'(".DS_Store" ".git" ".hg" "g/src/kfnm.us/vendor/" "build" ".stversions"
"sites/thesergents/public" "sites/leapfrog/public" "sites/twlk9/public"
"blog/public" "g/src/pkg" "g/bin"))
:config
;; Project type detection
(projectile-register-project-type 'fossil '(".fslckout")
:project-file ".fslckout")
(projectile-register-project-type 'python '("requirements.txt")
:project-file "requirements.txt")
(defvar my-projectile-ignored-patterns
'("\\.DS_Store$" "\\.log$" "\\.pyc$") ; Add more patterns here
"List of regex patterns to ignore in Projectile.")
(defun filter-out-ignored-files (files)
"Filter out files matching `my-projectile-ignored-patterns` from FILES."
(seq-remove (lambda (file)
(seq-some (lambda (pattern)
(string-match-p pattern file))
my-projectile-ignored-patterns))
files))
(advice-add 'projectile-project-files :filter-return #'filter-out-ignored-files)
(projectile-mode +1))
;; New version (projectile)
(defun dws/setdjangoengine ()
"Set django as web engine when in django dir."
(when (projectile-project-p)
(when (file-exists-p (expand-file-name "manage.py" (projectile-project-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
:bind ("M-SPC" . er/expand-region))
;; Programming mode common configuration
(use-package prog-mode
:config
(defun toggle-comments ()
"Comments or uncomments region or current line if no active region."
(interactive)
(let (start end)
(if (region-active-p)
(setq start (region-beginning) end (region-end))
(setq start (line-beginning-position) end (line-end-position)))
(comment-or-uncomment-region start end)
(forward-line 1)))
:bind ("C-c c" . toggle-comments))
;; 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))
;; TRAMP configuration
(use-package tramp
:custom
(tramp-default-method "ssh"))
;; Ansible YAML configuration
(defun dws/in-ansible-directory-p (file)
"Check if FILE is in an ansible directory or its subdirectories."
(and file
(string-match-p "/ansible/" (file-truename file))))
(defun dws/set-yaml-mode-for-ansible-files ()
"Set yaml-mode for files without extensions in ansible directories."
(when (and (not buffer-file-name)
(dws/in-ansible-directory-p default-directory))
(yaml-mode)))
;; Add to auto-mode-alist for files without extensions in ansible directories
(add-to-list 'auto-mode-alist '("/ansible/.*/[^.]+\\'" . yaml-mode)) ; matches files without extensions
(add-hook 'find-file-hook #'dws/set-yaml-mode-for-ansible-files)
(provide 'dev-config)
;;; dev-config.el ends here