;; -*- lexical-binding: t -*- ;; Super handy macro for loading packages but not stopping the init ;; process if they aren't found. (require 'use-package) ;; This convenient package removes many lines of custom config I used ;; to have here. (use-package better-defaults) (set-fringe-mode 10) (setq inhibit-startup-message t) ; no splash screen (setq visible-bell t) ;; Maximize frames (add-to-list 'default-frame-alist '(fullscreen . maximized)) ;; Stop customize from writing to my init file. (setq custom-file "~/.emacs.d/custom.el") ;;; ;;; Look and Feel ;;; ;; Use Inconsolata font. (add-to-list 'default-frame-alist '(font . "Inconsolata-14")) (use-package doom-themes :config (load-theme 'doom-vibrant t) ;; Enable flashing mode-line on errors (doom-themes-visual-bell-config) ;; Enable custom neotree theme (all-the-icons must be installed!) (doom-themes-neotree-config) ;; Corrects (and improves) org-mode's native fontification. (doom-themes-org-config)) (use-package doom-modeline :init (doom-modeline-mode 1) :custom ((doom-modeline-irc t) (doom-modeline-irc-buffers t))) (column-number-mode t) (which-function-mode t) (add-hook 'prog-mode-hook (lambda () (display-line-numbers-mode t))) (use-package which-key :init (which-key-mode) :diminish which-key-mode :custom (which-key-idle-delay 0.3)) ;;; ;;; Guix ;;; (use-package guix :config (add-hook 'scheme-mode-hook 'guix-devel-mode) :custom ;; Load modules from the guix obtained via 'guix pull'. (guix-load-path '("~/.guix/current/share/guile/site/3.0")) (guix-load-compiled-path '("~/.guix/current/lib/guile/3.0/site-ccache"))) ;; This config based on: https://amodernist.com/texts/emacs-guix.html (use-package buffer-env :config (add-hook 'hack-local-variables-hook #'buffer-env-update) (add-hook 'comint-mode-hook #'hack-dir-local-variables-non-file-buffer) :custom (buffer-env-script-name '("guix.scm" "manifest.scm"))) ;;; ;;; Dired ;;; ;; Don't create buffer litter when moving through directories with ;; dired. (setq dired-kill-when-opening-new-dired-buffer t) ;; Revert dired buffer after a copy, rename, etc. (setq dired-do-revert-buffer t) ;;; ;;; TRAMP ;;; (use-package tramp-sh ;; Include the current path for the system to TRAMP's remote path. ;; This is necessary for GuixSD, where the usual /usr/bin, ;; /usr/local/bin, etc. do not exist. :config (push 'tramp-own-remote-path tramp-remote-path)) ;;; ;;; Battery ;;; (use-package battery :init ;; Display battery level in modeline only if a battery is present, ;; otherwise display-battery-mode will throw an error. (when (and (boundp 'battery-status-function) (not (null battery-status-function)) (not (string-match-p "N/A" (battery-format "%B" (funcall battery-status-function))))) (display-battery-mode t))) ;;; ;;; Bookmarks ;;; (setq bookmark-save-flag 1) ;;; ;;; Searching ;;; ;; Highlight the current "error" (or rgrep search result, etc. it's a ;; bad name.) (setq next-error-message-highlight t) ;;; ;;; Minibuffer and completion ;;; (use-package smex) ;; Present more information when looking at completions via the ;; describe-* family of commands. (setq completions-detailed t) ;; Prompt for y/n instead of yes/no. (setq user-short-answers t) ;;; ;;; Buffers ;;; ;; Kill buffers that haven't been modified in awhile. (use-package midnight) ;; Save point position between sessions (use-package saveplace :custom ((save-place t) (save-place-file (expand-file-name ".places" user-emacs-directory)))) (defun cleanup-buffer-safe () "Perform a bunch of safe operations on the whitespace content of a buffer. Does not indent buffer, because it is used for a before-save-hook, and that might be bad." (interactive) (delete-trailing-whitespace) (set-buffer-file-coding-system 'utf-8)) ;; Various superfluous white-space. Just say no. (add-hook 'before-save-hook 'cleanup-buffer-safe) (use-package ibuffer :init (add-hook 'ibuffer-mode-hook (lambda () (ibuffer-switch-to-saved-filter-groups "default"))) :custom ((ibuffer-saved-filter-groups '(("default" ("Dired" (mode . dired-mode)) ("Org" (mode . org-mode)) ("ERC" (mode . erc-mode)) ("Magit" (or (mode . magit-status-mode) (mode . magit-diff-mode) (mode . magit-process-mode))) ("Shell" (mode . shell-mode)) ("Guix" (name . "Guix")) ("Emacs Lisp" (mode . emacs-lisp-mode)) ("Scheme" (or (mode . scheme-mode) (name . "Geiser") (mode . geiser-repl-mode) (mode . geiser-messages-mode))) ("Ruby" (mode . ruby-mode)) ("JavaScript" (mode . js2-mode))))) (ibuffer-show-empty-filter-groups nil))) (setq view-read-only t) ;; Attempt to make buffers with long lines not slow Emacs to a crawl. (global-so-long-mode) ;;; ;;; Tabs and Newlines ;;; (setq-default indent-tabs-mode nil) (setq indent-tabs-mode nil) (setq tab-width 2) (setq require-final-newline t) (setq electric-indent-mode t) ;;; ;;; Version Control ;;; (use-package magit :bind (("C-c g" . magit-status)) :init ;; Make magit-status project shortcut available immediately. ;; Without this, it only becomes available after running ;; magit-status once manually. (use-package magit-extras) :config (use-package magit-blame) :custom ;; I don't like magit's default local tracking branch naming ;; strategy. ((magit-default-tracking-name-function 'magit-default-tracking-name-branch-only) (magit-last-seen-setup-instructions "1.4.0") (magit-completing-read-function #'magit-ido-completing-read))) ;; Follow symlinks automatically instead of asking each time. (setq vc-follow-symlinks t) ;;; ;;; Compilation ;;; (use-package ansi-color ;; Process ANSI color codes in compilation buffers. :config (add-hook 'compilation-filter-hook #'ansi-color-compilation-filter)) ;;; ;;; Ediff ;;; ;; Don't break out a separate frame for ediff. (setq ediff-window-setup-function 'ediff-setup-windows-plain) (setq ediff-split-window-function 'split-window-horizontally) ;;; ;;; C ;;; (setq c-default-style "k&r") (setq-default c-basic-offset 2) (setq-default c-basic-indent 2) ;;; ;;; Javascript ;;; (setq js-indent-level 2) (use-package js2-mode :init (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode)) :custom ((js2-basic-offset 2))) ;;; ;;; Web ;;; (use-package web-mode :init (add-to-list 'auto-mode-alist '("\\.html$" . web-mode)) (add-to-list 'auto-mode-alist '("\\.tpl$" . web-mode)) (add-to-list 'auto-mode-alist '("\\.erb$" . web-mode)) (add-hook 'web-mode-hook (lambda () (setq web-mode-markup-indent-offset 2)))) ;;; ;;; Lisp ;;; (use-package rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode)) (use-package scheme) (use-package paredit :config (add-hook 'emacs-lisp-mode-hook #'paredit-mode) (add-hook 'lisp-mode-hook #'paredit-mode) (add-hook 'lisp-data-mode-hook #'paredit-mode) (add-hook 'lisp-interaction-mode-hook #'paredit-mode) (add-hook 'scheme-mode-hook #'paredit-mode)) (use-package geiser :config (use-package geiser-guile :config (define-key project-prefix-map "l" #'geiser-guile) (add-to-list 'project-switch-commands '(geiser-guile "Guile REPL") t)) ;; Flycheck-guile only works if Geiser is around. (use-package flycheck-guile :config (add-hook 'scheme-mode-hook #'flycheck-mode)) :custom ((geiser-active-implementations '(guile)) ;; One REPL per project, please! (geiser-repl-per-project-p t))) ;; Hacked to properly indent keywords. Thanks to mark_weaver. (defun scheme-indent-function (indent-point state) "Scheme mode function for the value of the variable `lisp-indent-function'. This behaves like the function `lisp-indent-function', except that: i) it checks for a non-nil value of the property `scheme-indent-function' \(or the deprecated `scheme-indent-hook'), rather than `lisp-indent-function'. ii) if that property specifies a function, it is called with three arguments (not two), the third argument being the default (i.e., current) indentation." (let ((normal-indent (current-column))) (goto-char (1+ (elt state 1))) (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) (if (and (elt state 2) (not (looking-at "\\sw\\|\\s_"))) ;; car of form doesn't seem to be a symbol (progn (if (not (> (save-excursion (forward-line 1) (point)) calculate-lisp-indent-last-sexp)) (progn (goto-char calculate-lisp-indent-last-sexp) (beginning-of-line) (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t))) ;; Indent under the list or under the first sexp on the same ;; line as calculate-lisp-indent-last-sexp. Note that first ;; thing on that line has to be complete sexp since we are ;; inside the innermost containing sexp. (backward-prefix-chars) (current-column)) (let ((function (buffer-substring (point) (progn (forward-sexp 1) (point)))) method) (setq method (or (get (intern-soft function) 'scheme-indent-function) (get (intern-soft function) 'scheme-indent-hook))) (cond ((or (eq method 'defun) (and (null method) (> (length function) 3) (string-match "\\`def" function))) (lisp-indent-defform state indent-point)) ;; This next cond clause is the only change -mhw ((and (null method) (> (length function) 1) ; The '#' in '#:' seems to get lost, not sure why (string-match "\\`:" function)) (let ((lisp-body-indent 1)) (lisp-indent-defform state indent-point))) ((integerp method) (lisp-indent-specform method state indent-point normal-indent)) (method (funcall method state indent-point normal-indent))))))) ;;; ;;; Ruby ;;; ;; Rake files are ruby, too, as are gemspecs, rackup files, etc. (add-to-list 'auto-mode-alist '("\\.rake$" . ruby-mode)) (add-to-list 'auto-mode-alist '("\\.gemspec$" . ruby-mode)) (add-to-list 'auto-mode-alist '("\\.ru$" . ruby-mode)) (add-to-list 'auto-mode-alist '("Rakefile$" . ruby-mode)) (add-to-list 'auto-mode-alist '("Gemfile$" . ruby-mode)) ;;; ;;; SQL ;;; ;; Don't wrap lines so that table listings with a lot of columns ;; remain readable. (add-hook 'sql-interactive-mode-hook (lambda () (setq truncate-lines t))) ;;; ;;; Org ;;; (use-package ox-beamer) (use-package cl-lib) (defun org-sort-by-priority-and-todo () "Sort org entries first by priority, and then by TODO status." (interactive) (push-mark) (push-mark (point-max) nil t) (let ((p (point))) (goto-char (point-min)) (org-sort-entries nil ?p) (org-sort-entries nil ?o) (goto-char p)) (pop-mark)) (add-hook 'org-mode-hook (lambda () (local-set-key (kbd "C-c o") 'org-sort-by-priority-and-todo))) ;;; ;;; GPG ;;; (setq epg-gpg-program "gpg") (setq epa-pinentry-mode 'loopback) ;;; ;;; IRC ;;; (use-package erc :init (require 'erc-join) (erc-autojoin-mode t) (erc-track-mode t) :custom ((erc-interpret-mirc-color t) (erc-kill-buffer-on-part t) (erc-kill-queries-on-quit t) (erc-kill-server-buffer-on-quit t) (erc-server-coding-system '(utf-8 . utf-8)) (erc-autojoin-channels-alist '(("libera.chat" "#gnuassembly" "#guile" "#guile-steel" "#guix" "#lispgames" "#chickadee" "#spritely"))) ;; Don't open channel buffers in place of the current ;; buffer because that drives me fucking crazy. (erc-join-buffer 'bury) (erc-track-exclude-types '("JOIN" "NICK" "PART" "QUIT" "MODE" "324" "329" "332" "333" "353" "477")) (erc-save-buffer-on-part t))) (use-package erc-log :custom ((erc-log-channels-directory "~/.erc/logs/"))) (if (not (file-exists-p erc-log-channels-directory)) (mkdir erc-log-channels-directory t)) (use-package erc-notify) (use-package erc-autoaway) (use-package erc-services) (use-package gnutls) ;; Start and stop erc (defun start-irc () "Connect to IRC." (interactive) (erc-tls :server "irc.libera.chat" :port 6697 :nick "dthompson" :password (read-passwd "Password: "))) (defun stop-irc () "Disconnects from all irc servers" (interactive) (dolist (buffer (delq nil (mapcar (lambda (x) (and (erc-server-buffer-p x) x)) (buffer-list)))) (message "Server buffer: %s" (buffer-name buffer)) (with-current-buffer buffer (erc-quit-server "Later")))) (global-set-key (kbd "C-c i") 'start-irc) ;;; ;;; Keybindings ;;; (global-set-key (kbd "RET") 'newline-and-indent) (global-set-key (kbd "C-c C-f") 'ff-find-other-file) (global-set-key (kbd "M-%") 'query-replace-regexp) ;; No more minimizing Emacs by accident. (global-unset-key (kbd "C-z")) ;; No more closing Emacs by accident. (global-unset-key (kbd "C-x C-c")) ;; Enable some disabled-by-default functions. (put 'upcase-region 'disabled nil) (put 'downcase-region 'disabled nil)