;;; Emacs Config ;;;; various options (setq read-file-name-completion-ignore-case t inhibit-startup-screen t vc-follow-symlinks t custom-file (expand-file-name "~/.emacs.d/custom.el") gc-cons-threshold 100000000) (load custom-file t) (load-theme 'tango-dark t) (load-theme 'fred t) (column-number-mode 1) (setq standard-indent 2) (setq-default tab-width 4 indent-tabs-mode nil) ;;;; enable disabled functions (put 'upcase-region 'disabled nil) (put 'downcase-region 'disabled nil) (put 'narrow-to-region 'disabled nil) (put 'TeX-narrow-to-group 'disabled nil) (put 'LaTeX-narrow-to-environment 'disabled nil) ;;;; save backups and autosaves in tmp (setq backup-directory-alist `((".*" . ,temporary-file-directory)) auto-save-file-name-transforms `((".*" ,temporary-file-directory t))) ;;; Packages ;;;; packaging related stuff (setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3") (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/")) (add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/") t) (package-initialize) (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) (use-package use-package :config (setq use-package-check-before-init t use-package-always-defer t)) (unless (or (< emacs-major-version 29) (package-installed-p 'vc-use-package)) (package-vc-install "https://github.com/slotThe/vc-use-package")) ;;;; not sure where to put this (defun open-thunar-in-current-directory () (interactive) (call-process "setsid" nil 0 nil "thunar" ".")) (defun open-term-in-current-directory () (interactive) (call-process "setsid" nil 0 nil "alacritty")) (bind-key "" 'open-term-in-current-directory) (bind-key "s-t" 'open-thunar-in-current-directory) ;;;; internal packages (use-package tool-bar :demand :config (when (fboundp 'tool-bar-mode) (tool-bar-mode -1))) (use-package savehist :demand :config (savehist-mode) (defun savehist-file-name-history-cleanup () "Delete all the files in file-name-history that don't exist anymore" (interactive) (setq file-name-history (seq-filter (lambda (file) (or (file-remote-p file) (file-exists-p file))) file-name-history)))) (use-package paren :demand :config (show-paren-mode 1)) (use-package sh-script :config (add-hook 'sh-mode-hook (lambda () (setq tab-width 4)))) (use-package ibuffer :bind ("C-x C-b" . ibuffer) :config (setq ibuffer-saved-filter-groups (quote (("default" ("emacs" (or (name . "^\\*scratch\\*$") (name . "^\\*Messages\\*$"))))))) (add-hook 'ibuffer-mode-hook (lambda () (ibuffer-auto-mode 1) (setq ibuffer-show-empty-filter-groups nil)))) (use-package sgml-mode :hook ((sgml-mode nxml-mode html-mode web-mode) . sgml-electric-tag-pair-mode) :config (setq sgml-basic-offset 4)) (use-package compile :commands (compile recompile) :bind (("C-z" . recompile) ("C-S-z" . compile)) :config (setq compilation-scroll-output 'first-error) (require 'ansi-color) (add-hook 'compilation-filter-hook (lambda () (toggle-read-only 0) (ansi-color-apply-on-region compilation-filter-start (point)) (toggle-read-only 1))) (add-hook 'compilation-start-hook (lambda (x) (setq-local scroll-up-aggressively 0.0)))) (use-package verilog-mode :config (setq verilog-indent-level 4 verilog-indent-level-behavioral 4 verilog-indent-level-declaration 4 verilog-indent-level-module 4 verilog-auto-newline nil verilog-linter "verilator --lint-only") (add-hook 'verilog-mode-hook (lambda () (setq indent-tabs-mode nil))) (add-to-list 'company-keywords-alist (cons 'verilog-mode verilog-keywords))) (use-package python :config (when (executable-find "ipython") (setq python-shell-interpreter "ipython") (setq python-shell-interpreter-args "-i --simple-prompt") (add-to-list 'python-shell-completion-native-disabled-interpreters "ipython"))) (use-package tramp :bind ("C-c C-f" . find-file-as-root) :config (add-to-list 'tramp-remote-path 'tramp-own-remote-path) (setq tramp-default-method "ssh" tramp-show-ad-hoc-proxies t) (defun find-file-as-root () (interactive) (find-alternate-file (concat "/sudo::" (buffer-file-name))))) (use-package comint :config (setq-default comint-scroll-to-bottom-on-input t ; always insert at the bottom comint-scroll-to-bottom-on-output nil ; always add output at the bottom comint-scroll-show-maximum-output t ; scroll to show max possible output ;; comint-completion-autolist t ; show completion list when ambiguous comint-input-ignoredups t ; no duplicates in command history comint-completion-addsuffix t ; insert space/slash after file completion comint-buffer-maximum-size 20000 ; max length of the buffer in lines comint-get-old-input (lambda () "") ; what to run when i press enter on a ; line above the current prompt comint-input-ring-size 500) ; max shell history size (add-hook 'comint-mode-hook (lambda () (setq-local show-trailing-whitespace nil)))) (use-package semantic :hook (c-mode . semantic-mode) :config (setq semantic-default-submodes '(global-semantic-idle-scheduler-mode global-semanticdb-minor-mode global-semantic-decoration-mode global-semantic-highlight-func-mode global-semantic-show-unmatched-syntax-mode global-semantic-idle-summary-mode global-semantic-stickyfunc-mode global-semantic-idle-local-symbol-highlight-mode) semantic-idle-scheduler-idle-time 0.2) ; inhibit semantic outside of specific modes (setq semantic-inhibit-functions (lambda () (not (member major-mode '(c-mode cc-mode java-mode))))) (eval-after-load "cc-mode" '(bind-key "M-." 'semantic-ia-fast-jump c-mode-map))) (use-package term :config (add-hook 'term-mode-hook ; if this is t, it breaks shell-command (lambda () (setq-local comint-prompt-read-only nil)))) (use-package gud :config (add-hook 'gud-mode-hook (lambda () (setq-local comint-prompt-read-only t)))) (use-package org :bind (:map org-mode-map (("C-z" . org-latex-export-to-pdf-no-kr) ("M-[" . org-metaleft) ("M-]" . org-metaright) ("C-{" . org-shiftleft) ("C-}" . org-shiftright) ("C-M-[" . org-shiftmetaleft) ("C-M-]" . org-shiftmetaright) ("M-P" . org-metaup) ("M-N" . org-metadown)) :map org-src-mode-map ("C-z" . org-src-export-to-pdf)) :config (add-to-list 'org-modules 'org-tempo t) ; enable org-tempo templates (use-package ox-beamer :bind (:map org-beamer-mode-map ("C-z" . org-beamer-export-to-pdf))) ;; Make windmove work in org-mode: (add-hook 'org-shiftup-final-hook 'windmove-up) (add-hook 'org-shiftleft-final-hook 'windmove-left) (add-hook 'org-shiftdown-final-hook 'windmove-down) (add-hook 'org-shiftright-final-hook 'windmove-right) (setq org-special-ctrl-a/e t org-special-ctrl-k t org-special-ctrl-o t org-preview-latex-default-process 'dvisvgm org-confirm-babel-evaluate nil) ;; enable line wraping (add-hook 'org-mode-hook (lambda () (setq truncate-lines nil word-wrap t))) (defun org-latex-open-pdf () (interactive) (call-process "evince" nil 0 nil (org-latex-export-to-pdf))) (defun org-latex-export-to-pdf-no-kr () (interactive) (org-latex-export-to-pdf) (pop kill-ring)) ; remove most recent element of kill ring (defun org-src-export-to-pdf () (interactive) (with-current-buffer (org-src-source-buffer) (org-latex-export-to-pdf-no-kr))) (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (dot . t) (python . t))) (add-to-list 'org-src-lang-modes '("dot" . graphviz-dot)) (add-to-list 'org-src-lang-modes '("dot2tex" . graphviz-dot)) (defvar org-babel-default-header-args:dot2tex '((:results . "latex") (:exports . "results")) "Default arguments to use when evaluating a dot2tex source block.") (defun org-babel-execute:dot2tex (body params) "Execute a block of Dot code via dot2tex with org-babel. This function is called by `org-babel-execute-src-block'." (let* ((cmdline (concat (cdr (assoc :cmdline params)) " --figonly")) (cmd (or (cdr (assq :cmd params)) "dot2tex"))) (org-babel-eval (concat cmd " " cmdline) (org-babel-expand-body:dot body params)))) (defun org-babel-prep-session:dot2tex (session params) "Return an error because dot2tex does not support sessions." (error "dot2tex does not support sessions")) (defun org-insert-homework-header () "" (interactive) (yas-minor-mode t) (yas-expand-snippet (yas-lookup-snippet "Homework Header" 'org-mode))) (use-package ob-ipython :config (org-babel-do-load-languages 'org-babel-load-languages '((ipython . t)))) (defun org-link--open-pdf (path) (let ((path-parts (split-string path "::"))) (apply 'call-process "evince" nil 0 nil (car path-parts) (when (cadr path-parts) (list "--page-index" (cadr path-parts)))))) (org-link-set-parameters "pdf" :follow 'org-link--open-pdf)) (use-package outline :bind (:map outline-minor-mode-map ("" . outline-cycle))) (use-package windmove :demand :config (windmove-default-keybindings)) (use-package help-mode :config ; bind "g" to revert help buffers with no prompt (bind-key "g" (lambda () (interactive) (revert-buffer t t)) help-mode-map)) (use-package files :config ; bind a key to revert buffers with no prompt (bind-key "C-x M-v" (lambda () (interactive) (revert-buffer t t t)))) (use-package prog-mode :config (add-hook 'prog-mode-hook (lambda () (setq-local show-trailing-whitespace t)))) (use-package frame :demand :bind ("C-x C-S-c" . delete-invisible-frames) :config (defun suspend-frame () "If running in a secondary tty, call `suspend-tty', else nop" (interactive) (when (eq (framep (selected-frame)) t) (suspend-tty))) (defun delete-invisible-frames () "Delete all non-visible frames, to fix \"Attempt to delete a surrogate minibuffer frame\" issues" (interactive) (mapc (lambda (frame) (unless (frame-visible-p frame) (delete-frame frame))) (frame-list)))) (use-package cc-styles :config (setq c-default-style "linux" c-basic-offset 4) (c-set-offset `inline-open 0)) (use-package antlr-mode :mode "\\.g4\\'") (use-package paragraphs :bind (("M-p" . backward-paragraph) ("M-n" . forward-paragraph))) (use-package ns-win :config (setq ns-command-modifier 'hyper) (server-start) ; disable focus stealing (defun raise-frame (frame) (interactive) nil)) (use-package yaml-mode :config (add-hook 'yaml-mode-hook (lambda () (setq-local show-trailing-whitespace t)))) (use-package auth-source-pass :demand :config (auth-source-pass-enable) (defun auth-source-pass--remap (orig-fun host &optional user port) (let ((new-host (pcase host ("hosting" "hosting.tardisventures.net") (_ host))) (new-user (if (string= port "sudo") (user-login-name) user))) (message "remapped %s@%s:%s -> %s@%s:%s" host user port new-host new-user port) (funcall orig-fun new-host new-user port))) (advice-add 'auth-source-pass--find-match-unambiguous :around #'auth-source-pass--remap)) (use-package calc :bind ("C-x M-e" . calc-eval-in-place) :init (defun calc-eval-in-place () (interactive) (let ((result (calc-eval (buffer-substring-no-properties (point) (mark))))) (if (stringp result) (progn (delete-region (point) (mark)) (insert result)) (apply 'user-error "Error at charater %d: %s" result))))) (use-package xt-mouse :demand :config (xterm-mouse-mode 1)) ;;;; external packages (required) (use-package auto-package-update :ensure :commands auto-package-update-cli :config (setq auto-package-update-delete-old-versions t) ;; wrap apu-now to print to stdout for batch calling (defun auto-package-update-cli () (cl-letf (((symbol-function 'apu--write-results-buffer) 'princ)) (auto-package-update-now)))) (use-package delight :ensure :demand) (use-package company :ensure :demand :delight :bind ("" . company-manual-begin) :config (global-company-mode) (setq company-idle-delay 0.1 company-dabbrev-downcase nil company-dabbrev-ignore-case nil) (use-package company-statistics :config (company-statistics-mode)) (use-package company-c-headers :config (add-to-list 'company-backends 'company-c-headers)) (use-package company-ansible :config (add-to-list 'company-backends 'company-ansible)) (use-package company-auctex :config (company-auctex-init))) (use-package smart-mode-line :ensure :demand :config (setq sml/no-confirm-load-theme t) (sml/setup) (sml/apply-theme 'respectful) (setq sml/replacer-regexp-list '(("^~/\\.dotfiles/" ":dots:") ("^:dots:emacs/\\.emacs\\.d/" ":ED:") ("^~/Documents/" ":Doc:") ("^~/Programs/" ":Prog:") ("^:Doc:WPI/" ":WPI:") ("^/sshx:ccc:" ">ccc:") ("^>ccc:/home/asgoldsmith" ":>ccc:~")))) (use-package avy :ensure :config (eval-after-load "isearch" '(bind-key "C-'" 'avy-isearch isearch-mode-map)) :bind (("C-;" . avy-goto-char) ("C-'" . avy-goto-char-2))) (use-package smex :ensure :bind ("M-X" . smex-major-mode-commands)) (use-package swiper :ensure :bind (("C-s" . swiper) ("C-r" . swiper) ("C-c C-r" . ivy-resume) :map swiper-map (("C-r" . ivy-previous-line-or-history) ("M-," . swiper-mc)) :map ivy-minibuffer-map ("C-'" . ivy-avy)) :init (bind-key "C-S-s" 'isearch-forward) (bind-key "C-S-r" 'isearch-backward) :config (use-package ivy :ensure :delight :config (ivy-mode 1)) (use-package ivy-hydra :ensure) (defun ivy-backward-delete-char () "Forward to `backward-delete-char'. Do less dumb things with directories On error (read-only), call `ivy-on-del-error-function'." (interactive) (if (and ivy--directory (= (minibuffer-prompt-end) (point))) (progn (let ((old-dir (file-name-nondirectory (directory-file-name (expand-file-name ivy--directory))))) (ivy--cd (file-name-directory (directory-file-name (expand-file-name ivy--directory)))) (insert old-dir)) (ivy--exhibit)) (condition-case nil (backward-delete-char 1) (error (when ivy-on-del-error-function (funcall ivy-on-del-error-function))))))) (use-package counsel :ensure :bind (("M-x" . counsel-M-x) ("C-M-y" . counsel-yank-pop) ("C-x C-f" . counsel-find-file) :map counsel-find-file-map (("C-M-i" . counsel-find-file-edit-path) ("C-DEL" . ivy-backward-kill-word) ("C-" . ivy-backward-kill-word) ("M-DEL" . counsel-up-directory) ("M-" . counsel-up-directory)) :map comint-mode-map (("C-s" . counsel-shell-history) ("C-r" . counsel-shell-history) ("M-s" . swiper) ("M-r" . swiper))) :init (bind-key "C-c C-c M-x" 'execute-extended-command) ;;normal M-x. :config (setq ivy-extra-directories nil counsel-preselect-current-file t ;; ignore "Dotfiles and Lockfiles" by default counsel-find-file-ignore-regexp "\\(?:\\`\\|[/\\]\\)\\(?:[#.]\\)") (assq-delete-all 'counsel-M-x ivy-initial-inputs-alist) (defun counsel-find-file-edit-path () "Edit the current path in ivy find-file" (interactive) (let ((old-path (substring (concat (expand-file-name ivy--directory) ivy-text) 1 -1))) (ivy--cd "/") (insert old-path)))) (use-package marginalia :ensure :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle)) :init (marginalia-mode)) (use-package ace-window :ensure :bind ("M-S-p" . ace-window)) (use-package undo-tree :ensure :demand :delight :config (bind-key "M-/" 'undo-tree-visualize) (global-undo-tree-mode) (setq undo-tree-enable-undo-in-region nil undo-tree-history-directory-alist `(("." . ,(concat user-emacs-directory "undo-tree-hist"))))) (use-package hydra :ensure :demand :config ;config is in separate file because it is really big (load-file "~/.emacs.d/hydra-defs.el") :bind (("C-c w" . 'hydra-window/body) ("C-c s" . 'hydra-shortcuts/body) ("C-c a" . 'hydra-avy/body) ("C-c c" . 'hydra-mc-manual/body) ("C-^" . 'hydra-smerge/body)) :bind* ("C-," . 'hydra-mc/body)) ; I like my binding, stop messing with it (use-package multiple-cursors :ensure :demand) (use-package expand-region :ensure :bind ("C-=" . er/expand-region)) (use-package magit :ensure :bind (("C-x g" . magit-status) (:map magit-status-mode-map (("i" . magit-gitignore-in-topdir) ("I" . magit-gitignore-in-gitdir)))) :init (defun magit-status-no-new-window (directory) (interactive "D") (let ((magit-display-buffer-function (lambda (buffer) (display-buffer buffer '(display-buffer-same-window)))) (default-directory directory)) (magit-status))) :config (setq magit-bury-buffer-function 'magit-mode-quit-window magit-diff-refine-hunk t) (use-package magit-todos :demand :if (package-installed-p 'magit-todos) :config (magit-todos-mode))) (use-package popwin :ensure :disabled :config (popwin-mode 1) (delete 'help-mode popwin:special-display-config) (add-to-list 'popwin:special-display-config '(help-mode :stick t))) (use-package dired+ :vc (:fetcher github :repo emacsmirror/dired-plus) :init (setq diredp-hide-details-initially-flag nil) :config (toggle-diredp-find-file-reuse-dir 1)) (use-package column-enforce-mode :ensure :demand :delight :hook prog-mode :config (setq column-enforce-column 100)) (use-package immortal-scratch :ensure :demand :config (setq immortal-scratch-switch-to-respawned-scratch t) (immortal-scratch-mode t)) (use-package frames-only-mode :ensure :demand :config ; seems to need to be before mode init (add-to-list 'frames-only-mode-use-window-functions 'undo-tree-visualize) (setq server-raise-frame nil) (defun x-focus-frame (FRAME &optional NOACTIVATE)) (frames-only-mode t) (menu-bar-mode 0) (defun split-window-below-i3 () "It's like `split-window-below', but uses i3 stuff when available" (interactive) (cond ((not (display-graphic-p)) (split-window-below)) ((executable-find "i3") (call-process "i3" nil nil nil "split v") (make-frame)) (t (make-frame)))) (defun split-window-right-i3 () "It's like `split-window-right', but uses i3 stuff when available" (interactive) (cond ((not (display-graphic-p)) (split-window-right)) ((executable-find "i3") (call-process "i3" nil nil nil "split h") (make-frame)) (t (make-frame)))) (bind-key "C-x 2" 'split-window-below-i3) (bind-key "C-x 3" 'split-window-right-i3)) (use-package crux :ensure :bind (("C-a" . crux-move-beginning-of-line))) (use-package flycheck :ensure) (use-package lsp-mode :hook ((python-mode html-mode web-mode css-mode js2-mode typescript-mode java-mode rust-mode) . lsp) :bind (:map lsp-mode-map ("C-M-z" . lsp-compile-in-root)) :config (setq lsp-prefer-flymake nil lsp-inhibit-message t lsp-eldoc-render-all nil lsp-pylsp-plugins-jedi-use-pyenv-environment t lsp-pylsp-plugins-pycodestyle-enabled nil lsp-pylsp-plugins-pydocstyle-enabled nil lsp-pylsp-plugins-flake8-enabled t) (when (executable-find "rust-analyzer") (setq lsp-rust-server 'rust-analyzer)) (lsp-register-custom-settings '(("pylsp.plugins.pyls_mypy.enabled" t t) ("pylsp.plugins.pyls_mypy.live_mode" nil t) ("pylsp.plugins.ruff.enabled" t t))) (defun lsp-compile-in-root () "Do compilation in the root dir of a project" (interactive) (let ((default-directory (lsp-workspace-root))) (call-interactively 'compile))) (use-package lsp-ui :ensure :bind (:map lsp-ui-mode-map ([remap xref-find-definitions] . lsp-ui-peek-find-definitions) ([remap xref-find-references] . lsp-ui-peek-find-references)) :config (setq lsp-ui-sideline-ignore-duplicate t lsp-ui-doc-max-height 5 lsp-ui-doc-max-width 70 ;; lsp-ui-sideline-update-mode 'point ) (defun adjust-lsp-ui-doc-frame-pos (orig-fun &rest args) (let* ((y-position-in-window (nth 1 (pos-visible-in-window-p (point) nil t))) (edges (window-absolute-body-pixel-edges)) (bottom-edge (- (nth 3 edges) (nth 1 edges))) (lsp-ui-doc-max-height (/ (window-height) 5)) (lsp-ui-doc-position (if (< (/ (float y-position-in-window) bottom-edge) 0.5) 'bottom 'top))) (when (lsp-ui-doc--get-frame) (lsp-ui-doc--move-frame (lsp-ui-doc--get-frame))) (apply orig-fun args))) (advice-add 'lsp-ui-doc--display :around #'adjust-lsp-ui-doc-frame-pos)) (use-package lsp-java :demand :if (package-installed-p 'lsp-java) :config (setq lsp-java-save-action-organize-imports nil))) (use-package indent-bars :vc (:fetcher github :repo jdtsmith/indent-bars) :custom (indent-bars-treesit-support t) (indent-bars-no-descend-string t) (indent-bars-treesit-ignore-blank-lines-types '("module")) (indent-bars-treesit-wrap '((python argument_list parameters ; for python, as an example list list_comprehension dictionary dictionary_comprehension parenthesized_expression subscript))) :hook ((python-base-mode yaml-base yaml-ts-mode) . indent-bars-mode)) (use-package editorconfig :ensure t :demand :delight :config (editorconfig-mode 1)) (use-package exec-path-from-shell :ensure :if (memq window-system '(mac ns)) :config (exec-path-from-shell-initialize)) (use-package helpful :ensure :bind (("C-h f" . helpful-callable) ("C-h v" . helpful-variable) ("C-h k" . helpful-key) ("C-h C-c" . helpful-at-point) ("C-h F" . helpful-function) ("C-h C" . helpful-command))) ;;;; optional external packages (use-package arduino-mode :mode "\\.pde\\'" :mode "\\.ino\\'") (use-package circe :config (setq circe-default-nick "ad1217" circe-reduce-lurker-spam t) (enable-circe-color-nicks) (setq circe-network-options '(("Freenode" :tls t :nick "ad1217" :sasl-username "ad1217" :sasl-password "ablablop" :channels ("#emacs-circe" "#qutebrowser" "#archlinux" "##linux")) ("WPI" :host "irc.wpiirc.net" :port 9999 :use-tls t)) circe-default-part-message "")) (use-package dtrt-indent :delight :config (dtrt-indent-global-mode 1)) (use-package graphviz-dot-mode :config ; don't auto-newline on semicolon (defun electric-graphviz-dot-semi () "Terminate line and indent next line." (interactive) (insert ";") (when (and graphviz-dot-auto-indent-on-semi (not (graphviz-dot-comment-or-string-p))) (save-excursion (beginning-of-line) (skip-chars-forward " \t") (graphviz-dot-indent-line)) (delete-horizontal-space)))) (use-package gnuplot-mode :mode ("\\.gp$" . gnuplot-mode)) (use-package markdown-mode :bind (:map markdown-mode-map ("" . markdown-demote) ("S-" . markdown-promote)) :mode "\\.md\\'" :config (setq markdown-command "markdown_py -x markdown.extensions.wikilinks -x markdown.extensions.smarty -x markdown.extensions.extra -x latex -x markdown.extensions.sane_lists -x markdown.extensions.toc -x markdown_checklist.extension -c ~/.emacs.d/markdownConfig.yml" markdown-enable-math t) (use-package grip-mode :bind (:map markdown-mode-command-map ("g" . grip-mode)))) (use-package projectile :bind (:map projectile-mode-map (("C-S-z" . projectile-compile-project) ("C-c p" . projectile-command-map))) :delight '(:eval (if (file-remote-p default-directory) " P" (format " P[%s]" (projectile-project-name)))) :config (use-package counsel-projectile :ensure)) (use-package scad-mode :mode "\\.scad$" :config (defun scad-compile (ext) "Compile current buffer using 'scad-command' and the extention 'ext'" (interactive (list (completing-read "Extension: " '("stl" "off" "amf" "dxf" "svg" "csg" "png")))) (compile (combine-and-quote-strings `(,scad-command "-o" ,(file-name-with-extension buffer-file-name ext) ,buffer-file-name)))) :bind (:map scad-mode-map ("C-c z" . scad-compile))) (use-package smart-tabs-mode :config (smart-tabs-insinuate 'c 'c++ 'javascript)) (use-package todotxt-mode :commands (todotxt-open-file todotxt-mode) :bind ("C-c t" . todotxt-open-file) :init (setq todotxt-base-path (expand-file-name "~/Documents/todo") todotxt-default-file (concat todotxt-base-path "/todo.txt") todotxt-default-archive-file (concat todotxt-base-path "/done.txt")) (add-to-list 'auto-mode-alist `(,(concat todotxt-base-path "/.*\\.txt$") . todotxt-mode)) :config (setq todotxt-due-tag "due" todotxt-mode-keywords '(("^x .*$" 0 '(:foreground "gray80" :strike-through t)) ("^(A) " 0 '(:foreground "red")) ("^(B) " 0 '(:foreground "orange")) ("^(C) " 0 '(:foreground "teal")) ("^(D) " 0 '(:foreground "light green")) ("^(Y) " 0 '(:foreground "light grey")) ("([A-Z]+)" . font-lock-builtin-face) ("\\([a-zA-Z0-9_-]+\\):\\([a-zA-Z0-9._-]+\\)" . font-lock-variable-name-face) ("+\\w+" . font-lock-function-name-face) ("@\\w+" . font-lock-type-face) ("#important" 0 '(:foreground "orange red")) ; special tag ("#waiting" 0 '(:foreground "dark orange")) ; special tag ("#\\w+" . font-lock-comment-face) ("-\\([a-zA-Z_-]+\\)" . font-lock-variable-name-face) ("^[0-9]+-[0-9]+-[0-9]+" 0 '(:foreground "gray90")))) (defun todotxt-open-sub () "Opens the todotxt sub on current line" (interactive) (let ((line (thing-at-point 'line t))) (string-match "\\([^ ]+\\)/:\\([.A-Za-z0-9_]+\\)" line) (find-file (concat (match-string 1 line) "/" (match-string 2 line)))))) (use-package latex :defer :bind (:map LaTeX-mode-map ("C-z" . latex-save-and-compile) ("C-c e" . tex-close-latex-block)) :config (setq-default TeX-command-extra-options "-shell-escape") (defun latex-save-and-compile () "Pretty much what it says on the tin" (interactive) (save-buffer) (TeX-command "LaTeX" 'TeX-master-file -1)) (add-hook 'LaTeX-mode-hook 'TeX-source-correlate-mode) (defun latex-tsv-to-table () "Converts tab-seperated-values to a LaTeX table." (interactive) (let ((beg (region-beginning)) (end-line (line-number-at-pos (region-end)))) (save-excursion (goto-line end-line) (indent-region beg (point-at-eol)) (replace-regexp "\t" " & " nil beg (point-at-eol)) (replace-regexp "$" " \\\\\\\\" nil beg (point-at-eol)) (align beg (point)))))) (use-package fasd :if (package-installed-p 'fasd) :bind ("C-x C-S-f" . fasd-find-file) :init (global-fasd-mode 1) :config (setq fasd-enable-initial-prompt nil fasd-completing-read-function 'ivy-completing-read)) (use-package yasnippet :commands yas-minor-mode :bind (:map yas-minor-mode-map ("TAB" . nil) ("M-TAB" . yas-expand)) :init (add-hook 'LaTeX-mode-hook (lambda () (yas-minor-mode) (bind-key "" 'company-to-yasnippet company-active-map) (bind-key "" 'company-yasnippet) (current-local-map))) :config (yas-reload-all) (defun company-to-yasnippet () (interactive) (company-abort) (call-interactively 'company-yasnippet))) (use-package counsel-dash :hook (python-mode . (lambda ()(setq-local helm-dash-docsets '("Python 3"))))) (use-package pkgbuild-mode :bind (:map pkgbuild-mode-map ("C-c i" . pkgbuild-printsrcinfo)) :config (setq pkgbuild-user-full-name "Adam Goldsmith" pkgbuild-user-mail-address "contact@adamgoldsmith.name" pkgbuild-makepkg-command "PKGEXT='.pkg.tar' makepkg -mf") (defun pkgbuild-printsrcinfo () (interactive) (shell-command "makepkg --printsrcinfo > .SRCINFO")) (defun pkgbuild-update-sums-line () (interactive) (shell-command "updpkgsums") (pkgbuild-printsrcinfo) (revert-buffer t t t))) (use-package qml-mode :mode "\\.qml\\'" :config (use-package company-qml :config (add-to-list 'company-backends 'company-qml))) (use-package jdee :defer :config (setq jdee-server-dir "~/.emacs.d/jdee-server/target" jdee-mode-line-format mode-line-format)) (use-package web-mode :mode "\\.html?\\'" :mode "\\.vue?\\'" :mode "\\.dtl?\\'" :mode "\\.djhtml?\\'" :bind (:map web-mode-map ("M-" . web-mode-comment-indent-new-line)) :config (add-to-list 'web-mode-indentation-params '("lineup-calls" . nil)) (setq-default web-mode-markup-indent-offset 2 web-mode-css-indent-offset 2 web-mode-code-indent-offset 2 web-mode-script-padding nil web-mode-style-padding nil web-mode-enable-auto-indentation nil web-mode-enable-comment-annotation t) (let ((regexps (cdr (assoc "django" web-mode-engine-file-regexps)))) (unless (string-match-p "dj\\.html" regexps) (push `("django" . ,(replace-regexp-in-string "djhtml" "dj\\\\.html\\\\|djhtml" regexps)) web-mode-engine-file-regexps)))) (use-package vue-mode :config (add-hook 'vue-mode-hook (lambda () (highlight-indent-guides-mode t))) (set-face-background 'mmm-default-submode-face nil)) (use-package js2-mode :mode "\\.js\\'" :config (setq js2-basic-offset 2) (use-package js2-refactor :hook (js2-mode . js2-refactor-mode) :config (js2r-add-keybindings-with-prefix "C-c C-m"))) (use-package nodejs-repl :bind* (:map js2-mode-map ("C-x C-e" . nodejs-repl-send-last-expression) ("C-c C-r" . nodejs-repl-send-region) ("C-c C-l" . nodejs-repl-load-file) ("C-c C-z" . nodejs-repl-switch-to-repl) ("C-c C-c" . nodejs-repl-send-buffer)) :config (unbind-key "C-c C-c" tern-mode-keymap)) (use-package nm :if (executable-find "notmuch") :vc (:fetcher github :repo ad1217/nevermore) :bind (:map nm-mode-map ("n" . nm-read) ("s" . nm-spam) ("G" . nm-update-remote) ("M-g" . nm-reset) ("j" . next-line) ("k" . previous-line) ("N" . nm-sticky-tag-and-next)) :config (defun nm-tag-and-next (tags) "Apply some changes to a message, then move to the next line" (nm-apply-to-result (lambda (q) (notmuch-tag q tags))) (nm-update-tags) (forward-line)) (defun nm-read () "Mark message as read." (interactive) (nm-tag-and-next '("-unread"))) (defun nm-spam () "Mark message as read." (interactive) (nm-tag-and-next '("+spam" "-unread" "-inbox"))) (defun nm-update-remote () "Pull email from remote mailbox" (interactive) (shell-command "mujmap -C ~/mail/adamgoldsmith.name sync") (nm-refresh)) (defvar nm-sticky-tags nil) (defun nm-sticky-tag-and-next (tags) (interactive (if (or current-prefix-arg (null nm-sticky-tags)) (list (notmuch-tag-change-list (split-string (read-string "tags: " (mapconcat 'identity nm-sticky-tags " "))))) (list nm-sticky-tags))) (setq nm-sticky-tags tags) (nm-tag-and-next tags)) (setq message-kill-buffer-on-exit t mail-host-address "adamgoldsmith.name" mail-specify-envelope-from t message-sendmail-envelope-from 'header message-send-mail-function 'message-send-mail-with-sendmail sendmail-program (executable-find "msmtp") notmuch-fcc-dirs "adamgoldsmith.name +sent" message-forward-as-mime nil message-make-forward-subject-function '(message-forward-subject-fwd) )) (use-package fortune-cookie :demand :if (package-installed-p 'fortune-cookie) :config ; change message every time scratch buffer created (setq initial-major-mode (lambda () (setq initial-scratch-message (concat (fortune-cookie-comment (fortune-cookie) fortune-cookie-comment-start) "\n\n")) (lisp-interaction-mode)))) (use-package git-timemachine :bind ("C-x M-g" . git-timemachine-toggle)) (use-package darkroom :config (setq darkroom-text-scale-increase 1 darkroom-margins .1)) (use-package rainbow-identifiers :config (setq rainbow-identifiers-choose-face-function 'rainbow-identifiers-cie-l*a*b*-choose-face rainbow-identifiers-cie-l*a*b*-lightness 90 rainbow-identifiers-cie-l*a*b*-saturation 75)) (use-package atomic-chrome :config (atomic-chrome-start-server) (setq atomic-chrome-url-major-mode-alist '(("github\\.com" . markdown-mode) ("overleaf\\.com" . LaTeX-mode) ;; ("claremontmakerspace\\.org/\\(w\\|wiki\\)/" . mediawiki-mode) ("claremontmakerspace\\.org" . mediawiki-mode) ))) (use-package which-key :demand :if (package-installed-p 'which-key) :delight :config (which-key-mode) (use-package which-key-posframe :ensure :demand :config (which-key-posframe-mode) (set-face-background 'which-key-posframe "#333333"))) (use-package show-marks :config :bind ("C-S-" . backward-mark) :bind ("C-S-" . forward-mark) :bind ("C-S-" . show-marks)) (use-package org-lookup-dnd :if (file-exists-p "~/Documents/DnD/dnd-phb-5e-index/indexes.org") :vc (:fetcher gitlab :repo ad1217/org-lookup-dnd :rev no-pdfview) :bind (:map org-mode-map ("C-c d" . org-lookup-dnd-at-point)) :config (setq org-lookup-dnd-chose 'org-lookup-dnd-chose-ivy org-lookup-dnd-link-format "[[pdf:%s::%d][%s]]") (setq org-lookup-dnd-sources t) ;; '(("~/Documents/DnD/Books/D&D 5E - Player's Handbook.pdf" 1 4 4) ;; ("~/Documents/DnD/Books/D&D 5E - Dungeon Master's Guide.pdf" 0 317 320) ;; ("~/Documents/DnD/Books/D&D 5E - Monster Manual.pdf" 1 352 353))) (setq org-lookup-dnd-extra-index "~/Documents/DnD/dnd-phb-5e-index/indexes.org") ;; roughly org-lookup-dnd-parse, but without the pdf parsing (setq org-lookup-dnd-db (make-hash-table :test #'equal :size 256 :rehash-size 2.0 :rehash-threshold .97)) (org-lookup-dnd-parse-extras) (org-lookup-dnd-dump-vars-to-file '(org-lookup-dnd-db) org-lookup-dnd-db-file)) (use-package typescript-mode :bind (:map typescript-mode-map ("" . c-indent-new-comment-line))) (use-package rustic :config (setq rustic-format-on-save t rustic-format-display-method 'ignore)) (use-package git-gutter :demand :if (package-installed-p 'git-gutter) :delight :config (global-git-gutter-mode t) (setq git-gutter:update-interval 0.2) ;; Disable git-gutter on remote files (defun git-gutter--turn-on () (when (and (buffer-file-name) (not (memq major-mode git-gutter:disabled-modes)) (not (file-remote-p (buffer-file-name)))) (git-gutter-mode +1))) (use-package git-gutter-fringe :ensure :demand :config (define-fringe-bitmap 'git-gutter-fr:added [224] nil nil '(center repeated)) (define-fringe-bitmap 'git-gutter-fr:modified [224] nil nil '(center repeated)) (define-fringe-bitmap 'git-gutter-fr:deleted [224] nil nil '(center repeated)))) (use-package apheleia :demand :if (package-installed-p 'apheleia) :hook (python-base-mode . apheleia--determine-python-formatter) :config (setf (alist-get 'djlint apheleia-formatters) '("djlint" "--reformat" "-")) (setf (alist-get "\\.dj\\.html$" apheleia-mode-alist) '(djlint)) (defun apheleia--pyproject-contains-section (section) (when-let (parent (locate-dominating-file default-directory "pyproject.toml")) (with-temp-buffer (insert-file-contents (concat parent "pyproject.toml")) (re-search-forward (concat "^\\[" section "\\]$") nil t 1)))) (defun apheleia--check-formatter-configured () "Disable apheleia when a formatter's configuration does not exist" (not (let ((formatters (apheleia--get-formatters))) (cond ((member 'ruff formatters) (apheleia--pyproject-contains-section "tool.ruff")) ((member 'black formatters) (apheleia--pyproject-contains-section "tool.black")) ((member 'djlint formatters) (apheleia--pyproject-contains-section "tool.djlint")) ((seq-some (lambda (f) (string-prefix-p "prettier" (symbol-name f))) formatters) (when (= 0 (call-process "prettier" nil nil nil "--find-config-path" (buffer-file-name))) (let* ((config-file (shell-command-to-string (concat "prettier --find-config-path " (buffer-file-name)))) (default-directory (file-name-directory (expand-file-name config-file))) (file-info (shell-command-to-string (concat "prettier --file-info " (buffer-file-name)))) (file-ignored (gethash "ignored" (json-parse-string file-info)))) (eq file-ignored :false)))))))) (add-to-list 'apheleia-inhibit-functions 'apheleia--check-formatter-configured) (defun apheleia--determine-python-formatter () "Determine which formatter to use for a python buffer based on existence of pyproject.toml sections" (cond ((apheleia--pyproject-contains-section "tool.ruff") (setq-local apheleia-formatter '(ruff-isort ruff))) ((apheleia--pyproject-contains-section "tool.black") (if (apheleia--pyproject-contains-section "tool.isort") (setq-local apheleia-formatter '(black isort)) (setq-local apheleia-formatter '(black)))))) (apheleia-global-mode t)) (use-package smali-mode :vc (:fetcher github :repo strazzere/Emacs-Smali) :if (package-installed-p 'smali-mode) :autoload smali-mode :mode "\\.smali\\'") (use-package pet :demand :if (package-installed-p 'pet) :delight :config (defun pet--python-shell-setup () (setq-local python-shell-interpreter (pet-executable-find "python") python-shell-virtualenv-root (pet-virtualenv-root))) (setq pet-toml-to-json-program "yq" pet-toml-to-json-program-arguments '("-p" "toml" "-o" "json") pet-yaml-to-json-program "yq" pet-yaml-to-json-program-arguments '("-p" "yaml" "-o" "json")) (add-hook 'python-base-mode-hook 'pet-mode -10) (add-hook 'python-base-mode-hook 'pet--python-shell-setup) (add-hook 'python-base-mode-hook 'pet-flycheck-setup) (when (boundp 'apheleia-formatters) (setf (car (alist-get 'black apheleia-formatters)) '(pet-executable-find "black")) (setf (car (alist-get 'djlint apheleia-formatters)) '(pet-executable-find "djlint")) (setf (car (alist-get 'isort apheleia-formatters)) '(pet-executable-find "isort")))) ;;; Local Variables (add-to-list 'safe-local-eval-forms '(outline-hide-body)) ;; Local Variables: ;; indent-tabs-mode: nil ;; eval: (outline-minor-mode) ;; eval: (outline-hide-body) ;; End: