Skip to content

Latest commit

 

History

History
2072 lines (1810 loc) · 68 KB

config.org

File metadata and controls

2072 lines (1810 loc) · 68 KB

Elpaca and other prerequisites

;; -*- lexical-binding: t -*-

(setopt load-prefer-newer t)

(defvar elpaca-installer-version 0.7)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                              :ref nil :depth 1
                              :files (:defaults "elpaca-test.el" (:exclude "extensions"))
                              :build (:not elpaca--activate-package)))
(let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
       (build (expand-file-name "elpaca/" elpaca-builds-directory))
       (order (cdr elpaca-order))
       (default-directory repo))
  (add-to-list 'load-path (if (file-exists-p build) build repo))
  (unless (file-exists-p repo)
    (make-directory repo t)
    (when (< emacs-major-version 28) (require 'subr-x))
    (condition-case-unless-debug err
        (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                 ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
                                                 ,@(when-let ((depth (plist-get order :depth)))
                                                     (list (format "--depth=%d" depth) "--no-single-branch"))
                                                 ,(plist-get order :repo) ,repo))))
                 ((zerop (call-process "git" nil buffer t "checkout"
                                       (or (plist-get order :ref) "--"))))
                 (emacs (concat invocation-directory invocation-name))
                 ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                       "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                 ((require 'elpaca))
                 ((elpaca-generate-autoloads "elpaca" repo)))
            (progn (message "%s" (buffer-string)) (kill-buffer buffer))
          (error "%s" (with-current-buffer buffer (buffer-string))))
      ((error) (warn "%s" err) (delete-directory repo 'recursive))))
  (unless (require 'elpaca-autoloads nil t)
    (require 'elpaca)
    (elpaca-generate-autoloads "elpaca" repo)
    (load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))

;; Install use-package support
(elpaca elpaca-use-package
  ;; Enable :elpaca use-package keyword.
  (elpaca-use-package-mode)
  ;; Assume :elpaca t unless otherwise specified.
  (setopt elpaca-use-package-by-default t))

;; Block until current queue processed.
(elpaca-wait)

;; default is 1K
(setopt elpaca-ui-row-limit 10000)

Fundamental third-party packages that don’t go anywhere else.

(setopt read-process-output-max (* 1024 1024 5))  ; 5 MB

;; Load this before any other third-party packages.
(use-package no-littering
  :demand t)

(use-package dash
  :demand t)

;; Here for early loading.
(use-package delight)
(setopt eldoc-minor-mode-string " ED")

;; Set the path properly. Don't have a conditional on using the window system
;; in order to circumvent
;; https://emacs.stackexchange.com/questions/27918/why-is-exec-path-different-in-emacsclient-emacsserver-than-in-emacs.
(use-package exec-path-from-shell
  :ensure (:wait t)
  :init
  (exec-path-from-shell-initialize))
;; Starting the GUI doesn't know anything about the Keychain:
;; https://emacs.stackexchange.com/questions/41343/magit-asks-for-passphrase-for-ssh-key-every-time
;; I'm not sure that all of these are necessary anymore, considering that most
;; things should be in the correct dotfile.
(let ((ejb/desired-envvars '("ANACONDA_HOME"
                             "GIT_AUTHOR_EMAIL"
                             "GIT_COMMITTER_EMAIL"
                             "GPG_TTY"
                             "SSH_AGENT_PID"
                             "SSH_AUTH_SOCK"
                             "WORKON_HOME")))
  (when (display-graphic-p)
    (-each ejb/desired-envvars (lambda (envvar) (exec-path-from-shell-copy-env envvar)))))

Functions

(defun ejb/insert-date ()
  (interactive)
  (insert (format-time-string "%Y-%m-%d")))

(defun ejb/comparator (e1 e2 ordering)
  "Compare two elements based on a partial ordering. Elements
that are not part of the ordering are stably kept at the end."
  (let ((contains-e1 (-contains-p ordering e1))
        (contains-e2 (-contains-p ordering e2)))
    (cond
     ((and (not contains-e1) (not contains-e2)) nil)
     ((and (not contains-e1) contains-e2) nil)
     ((and contains-e1 (not contains-e2)) t)
     ((< (-elem-index e1 ordering) (-elem-index e2 ordering)) t)
     (t nil))))

M-x customize

Only package-selected-packages and custom-safe-themes should go in here.

(setopt custom-file (expand-file-name "custom.el" user-emacs-directory))
;; Intentionally save to a customization file that is not loaded by Emacs and
;; is ignored by Git. All permanent customization should be in this org file,
;; but not sending to /dev/zero allows temporary customization via `customize`
;; then cherry-picking from `custom.el` into here.

General configuration

What’s the difference between setq and setq-default? See here: http://stackoverflow.com/questions/18172728/the-difference-between-setq-and-setq-default-in-emacs-lisp

(menu-bar-mode -1)
(when (fboundp 'tool-bar-mode)
  (tool-bar-mode -1))
(when (fboundp 'scroll-bar-mode)
  (scroll-bar-mode -1))
(tooltip-mode -1)

(setopt inhibit-startup-screen t
        inhibit-startup-echo-area-message (user-login-name)
        initial-scratch-message nil)

(load "~/dotfiles/dotfiles-private/private.el")
(setopt user-full-name "Eric J. Berquist"
        user-mail-address "eric.berquist@gmail.com")

;; took some tricks from http://www.aaronbedra.com/emacs.d/

;; http://stackoverflow.com/questions/3281581/how-to-word-wrap-in-emacs
;; http://stackoverflow.com/questions/12989072/showing-continuation-lines-in-emacs-in-a-text-terminal
(setopt transient-mark-mode t
        visual-line-mode t)
(delight 'visual-line-mode " W" t)
;; Never truncate lines, even in partial width windows.
(setopt truncate-lines nil
        truncate-partial-width-windows nil
        line-number-mode t
        column-number-mode t
        cursor-type 'box)

;; (global-display-line-numbers-mode 1)
(setopt display-line-numbers-grow-only t)

;; Enable maximum syntax highlighting wherever possible.
(setopt global-font-lock-mode t
        font-lock-maximum-decoration t)

;; Bells are annoying, stop ringing!
(setopt visible-bell nil
        audible-bell nil
        ring-bell-function 'ignore)

;; 'y' is two characters less than 'yes'.
;; 'defalias' or 'fset'?
(defalias 'yes-or-no-p 'y-or-n-p)

;; This is really annoying, yes I want to quit
(setopt confirm-kill-processes nil)

;; death to tabs, tabs are evil
(setopt indent-tabs-mode nil
        tab-width 4)

;; Line length when executing `fill-paragraph`. Default is 70. See
;; https://www.emacswiki.org/emacs/FillParagraph and
;; https://www.gnu.org/software/emacs/manual/html_node/emacs/Fill-Commands.html.
(setopt fill-column 78)

;; Automatically reload buffers when their files change, and make sure
;; to check on the version control status every now and then.
(global-auto-revert-mode t)
;; Turns out this is *extremely* irritating and moves the mark every time the
;; buffer refreshes!
;; (setopt auto-revert-check-vc-info t)

;; Allow making whole regions uppercase or lowercase.
(put 'downcase-region 'disabled nil)
(put 'upcase-region 'disabled nil)

;; TODO set condition to result of attempting to download language library
(setq ejb/has-treesit (boundp 'treesit-extra-load-path))

(when ejb/has-treesit
  (use-package treesit-auto
    :custom
    (treesit-auto-install t)
    :config
    ;; Don't use yaml-ts-mode until indentation is fixed.
    (let ((ejb/treesit-modes-to-remove
           '(yaml)))
      (setopt treesit-auto-langs
              (seq-filter
               (lambda (lang)
                 (not (member lang ejb/treesit-modes-to-remove)))
               treesit-auto-langs)))
    (global-treesit-auto-mode)
    :init
    (require 'treesit-auto)))

;; Silence warnings
(setopt warning-suppress-types
        '((comp)
          (direnv)
          (emacs)
          (lsp-mode)
          (ox-pandoc)
          (treesit)))

Backups/autosaving

;; let's live on the edge and disable backup and autosave
(setopt backup-inhibited t
        auto-save-default nil
        create-lockfiles nil
        make-backup-files nil)

Disable popup boxes

(defadvice yes-or-no-p (around prevent-dialog activate)
  "Prevent yes-or-no-p from activating a dialog"
  (let ((use-dialog-box nil))
    ad-do-it))
(defadvice y-or-n-p (around prevent-dialog-yorn activate)
  "Prevent y-or-n-p from activating a dialog"
  (let ((use-dialog-box nil))
    ad-do-it))

;; Even when using the mouse, force the minibuffer.
(setopt use-file-dialog nil)

Key bindings

  • To view all current keybindings, C-h b.
  • To view all personal keybindings, M-x describe-personal-keybindings.
  • Used to have stuff for =’comment-or-uncomment-region=, =’comment-region=, and =’uncomment-region= here, but M-; calls =’comment-dwim=, which is what you really want.
(global-set-key [remap dabbrev-expand] 'hippie-expand)
(bind-key (kbd "C-x C-b") 'switch-to-buffer)
(bind-key (kbd "C-x b") 'ibuffer)
(bind-key (kbd "C-c b") 'switch-to-previous-buffer)
(bind-key (kbd "C-x C-9") 'text-scale-decrease)
(bind-key (kbd "C-x C-0") 'text-scale-increase)
(bind-key (kbd "C-x C-h") 'replace-string)
;; `hs-minor-mode-map' uses `C-c @` as prefix, but `@` is terrible, but the
;; remaining defaults are fine, so reuse those.
(let ((hs-group-key "C-h"))
  (bind-keys
   ((concat "C-c " hs-group-key " C-h")   . hs-hide-block)
   ((concat "C-c " hs-group-key " C-s")   . hs-show-block)
   ((concat "C-c " hs-group-key " C-M-h") . hs-hide-all)
   ((concat "C-c " hs-group-key " C-M-s") . hs-show-all)
   ((concat "C-c " hs-group-key " C-l")   . hs-hide-level)
   ((concat "C-c " hs-group-key " C-c")   . hs-toggle-hiding)
   ((concat "C-c " hs-group-key " C-a")   . hs-show-all)
   ((concat "C-c " hs-group-key " C-t")   . hs-hide-all)
   ((concat "C-c " hs-group-key " C-d")   . hs-hide-block)
   ((concat "C-c " hs-group-key " C-e")   . hs-toggle-hiding)))
;; I don't understand why this doesn't work.
;; (bind-keys
;;  :prefix-map hs-minor-mode-map
;;  :prefix "C-c C-h"
;;  ("C-h"   . hs-hide-block)
;;  ("C-s"   . hs-show-block)
;;  ("C-M-h" . hs-hide-all)
;;  ("C-M-s" . hs-show-all)
;;  ("C-l"   . hs-hide-level)
;;  ("C-c"   . hs-toggle-hiding)
;;  ("C-a"   . hs-show-all)
;;  ("C-t"   . hs-hide-all)
;;  ("C-d"   . hs-hide-block)
;;  ("C-e"   . hs-toggle-hiding))
(use-package which-key
  ;; why doesn't this work
  ;; :delight
  :custom
  (which-key-lighter "")
  :init
  (which-key-mode))

https://github.com/Wilfred/helpful

(use-package helpful
  :config
  (global-set-key [remap describe-function] 'helpful-callable)
  (global-set-key [remap describe-variable] 'helpful-variable)
  (global-set-key [remap describe-key] 'helpful-key)
  :init
  (require 'helpful))

dir-locals

Taken from https://emacs.stackexchange.com/a/13096/10950

(defun ejb/my-reload-dir-locals-for-current-buffer ()
  "reload dir locals for the current buffer"
  (interactive)
  (let ((enable-local-variables :all))
    (hack-dir-local-variables-non-file-buffer)))

(defun ejb/my-reload-dir-locals-for-all-buffer-in-this-directory ()
  "For every buffer with the same `default-directory` as the
current buffer's, reload dir-locals."
  (interactive)
  (let ((dir default-directory))
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
        (when (equal default-directory dir)
          (ejb/my-reload-dir-locals-for-current-buffer))))))

;; (add-hook 'emacs-lisp-mode-hook
;;           (defun enable-autoreload-for-dir-locals ()
;;             (when (and (buffer-file-name)
;;                        (equal dir-locals-file
;;                               (file-name-nondirectory (buffer-file-name))))
;;               (add-hook (make-variable-buffer-local 'after-save-hook)
;;                         'my-reload-dir-locals-for-all-buffer-in-this-directory))))

Snippets

(use-package yasnippet
  :delight
  ;; TODO
  ;; :bind (:map yas-minor-mode-map
  ;;             ("C-M-/" . yas-expand)
  ;;             ("TAB" . nil))
  :init
  (yas-global-mode 1))
(use-package yasnippet-snippets)

Theming and window shaping

Themes

;; These are nice when I'm bored...
(use-package abyss-theme)
(use-package base16-theme)
(use-package base16-eva-theme
  :ensure (:host github
           :repo "kjakapat/eva-theme"
           :files ("emacs/build/*.el"))
  ;; :config
  ;; (load-theme 'base16-eva t)
  )
(use-package colonoscopy-theme)
(use-package dracula-theme)
(use-package ef-themes)
(use-package plan9-theme)
(use-package rebecca-theme)
(use-package spacegray-theme)
(use-package tron-legacy-theme)
(add-to-list 'custom-theme-load-path (expand-file-name "themes/" user-emacs-directory))
;; ...but ultimately this is my daily driver.
(load-theme 'wombat2 t)
(use-package rainbow-mode)

Windows

TODO set fallback fonts, see http://ergoemacs.org/emacs/emacs_list_and_set_font.html

(add-to-list 'default-frame-alist '(font . "Panic Sans-11"))
;; https://emacs.stackexchange.com/q/45895
(set-face-attribute 'fixed-pitch nil :family "Fira Mono")

(setopt indicate-empty-lines t)
(when (not indicate-empty-lines)
  (toggle-indicate-empty-lines))
(setopt indicate-buffer-boundaries 'right)

(use-package default-text-scale
  :hook (after-init . default-text-scale-mode))

Parens/whitespace/indentation

(use-package elec-pair
  :ensure nil
  :init
  (electric-pair-mode 1))

(use-package paren
  :ensure nil
  :custom
  (show-paren-delay 0.0)
  :init
  (show-paren-mode 1))

(use-package whitespace
  :ensure nil
  :bind (("C-c t" . whitespace-mode))
  :mode (("\\.csv\\'" . whitespace-mode)
         ("\\.tab\\'" . whitespace-mode)
         ("\\.tsv\\'" . whitespace-mode))
  :custom
  ;; use the fill-column value
  (whitespace-line-column nil)
  (whitespace-display-mappings
   '((space-mark   ?\    [?\u00B7]     [?.])                    ; 32 SPACE, 183 MIDDLE DOT 「·」, 46 FULL STOP 「.」
     ;; (space-mark ?\s [183] [46])
     ;; (space-mark 32 [32] [46]) ; normal space, display nothing
     (space-mark   ?\xA0 [?\u00A4]     [?_])                    ; hard space: currency sign
     ;; (newline-mark ?\n [9166 10] [36 10])                    ; 10 LINE FEED, 9166 RETURN SYMBOL 「⏎」, 36 DOLLAR SIGN 「$」
     (newline-mark ?\n   [?\u21B5 ?\n] [172 10] [?\u00AF ?\n])  ; eol: downwards arrow with corner leftwards, ..., macron
     (tab-mark     ?\t   [9655 9]      [92 9] ))                ; 9 TAB, 9655 WHITE RIGHT-POINTING TRIANGLE 「▷」, 92 9 CHARACTER TABULATION 「\t」
   whitespace-style
   '(face
     trailing
     tabs
     ;; spaces
     ;; lines
     ;; lines-tail
     newline
     empty
     ;; indentation::tab
     ;; indentation::space
     ;; indentation
     ;; big-indent
     space-after-tab::tab
     ;; space-after-tab::space
     ;; space-after-tab
     space-before-tab::tab
     ;; space-before-tab::space
     space-before-tab
     space-mark
     tab-mark
     newline-mark
     ))
  ;; By default, `space-before-tab` considers zero or more spaces before a
  ;; tab, but `space-after-tab` only considers `tab-width` or more
  ;; spaces. Since my goal is not to look for indentation problems, but find
  ;; _any_ mixing, consider any number of spaces after a tab. Additionally,
  ;; apply font locking to the spaces, not the tab.
  (whitespace-space-after-tab-regexp '("\011+\\(\\( \\{0,\\}\\)+\\)" . "\\(\011+\\) \\{0,\\}"))
  :init
  (setq global-whitespace-mode nil))

(use-package dtrt-indent
  :delight " dtrt"
  :init
  (dtrt-indent-global-mode 1))

(use-package unfill
  :bind (("C-M-q" . unfill-paragraph)))

;; Always place a newline at the end of files, like nano does by
;; default.
(setopt require-final-newline t)

Narrowing, searching, and projects

(when ejb/has-treesit
  (use-package treesit-fold
    :ensure (treesit-fold :type git :host github :repo "emacs-tree-sitter/treesit-fold")))

(use-package vertico
  :custom
  (vertico-cycle t)
  (vertico-sort-function 'vertico-sort-history-alpha)
  :init
  (vertico-mode 1))

(defun ejb/orderless-style (component)
  (orderless--separated-by '(zero-or-more nonl)
    ;; "rev buffer" will give both "revert-buffer" and "ibuffer-do-revert"
    (cl-loop for prefix in (split-string component)
             collect prefix)))

(use-package orderless
  :ensure t
  :custom
  ;; default:
  ;; (completion-styles '(basic partial-completion emacs22))
  ;; recommended by orderless:
  (completion-styles '(orderless basic))
  (completion-category-overrides '((file (styles basic partial-completion))))
  (orderless-matching-styles '(ejb/orderless-style)))

(use-package marginalia
  :init
  (marginalia-mode 1))

(setopt completions-format 'vertical
        completion-ignore-case t
        read-buffer-completion-ignore-case t
        read-file-name-completion-ignore-case t
        hippie-expand-try-functions-list '(try-complete-file-name-partially
                                           try-complete-file-name
                                           try-expand-all-abbrevs
                                           ;; try-expand-list
                                           try-expand-line
                                           try-expand-dabbrev
                                           try-expand-dabbrev-visible
                                           try-expand-dabbrev-all-buffers
                                           try-expand-dabbrev-from-kill
                                           try-complete-lisp-symbol-partially
                                           try-complete-lisp-symbol))
;; https://github.com/Zetagon/literate-dotfiles/blob/4bb980a2fd0d60784939bfc21dc10e7aebc16eb2/config.org#default
(add-hook 'text-mode-hook
          (lambda ()
            (remove #'try-expand-line hippie-expand-try-functions-list)))

CTRLF replaces Isearch for single-buffer text search.

(use-package ctrlf
  ;; prefer fuzzy over literal searching
  :bind (:map ctrlf-mode-map
              ([remap isearch-forward] . ctrlf-forward-fuzzy)
              ([remap isearch-backward] . ctrlf-backward-fuzzy)
              ([remap isearch-forward-regexp] . ctrlf-forward-fuzzy-regexp)
              ([remap isearch-backward-regexp] . ctrlf-backward-fuzzy-regexp))
  :init
  (ctrlf-mode))

Some of my computers have ripgrep installed.

  • For ripgrep, which has the traditional grep interface, use M-x ripgrep-regexp to activate.
;; This package is needed for projectile-ripgrep to work. Don't disable it.
(use-package ripgrep
  :custom
  (ripgrep-arguments '("--hidden")))
;; ...though projectile will try this one if the above isn't available.
(use-package rg
  :custom
  (rg-command-line-flags '("--hidden"))
  (rg-ignore-ripgreprc nil))
(use-package deadgrep
  ;; <f5> is suggested, but that is inconvenient on my keyboards...
  :bind (("C-c g" . deadgrep)))
(defun ejb/conditional-append (list items)
  "Only append the contents of ITEMS to LIST that don't already
appear in LIST."
  (append list (-difference items list)))

(defun ejb/conditional-prepend (list items)
  "Only prepend the contents of ITEMS to LIST that don't already
appear in LIST."
  (append (-difference items list) list))

(defun ejb/vc-svn-url (file-or-dir &optional _remote-name)
  "Get the Subversion URL for FILE-OR-DIR if possible, returning
nil otherwise."
  (require 'vc-svn)
  (let ((default-directory (vc-svn-root file-or-dir)))
    (if default-directory
        (with-temp-buffer
          (vc-svn-command (current-buffer) 0 nil
                          "info" "--show-item" "url")
          (buffer-substring-no-properties (point-min) (1- (point-max)))))))

(defun ejb/projectile-root-qchem (dir &optional list)
  "Find the project root of a Q-Chem Subversion repository.

When inside a directory of an external, such as in a development
package or Q-Chem trunk, the project root should be the
development package or the Q-Chem checkout, not the directory of
the external.

Placing `projectile-root-top-down-recurring' ahead of
`projectile-root-top-down' will technically work, since it finds
the top-level repository rather than the external, but it messes
with the default ordering Projectile uses.
"
  (let ((root-top-down-recurring (projectile-root-top-down-recurring dir '(".svn"))))
    (if root-top-down-recurring
        (let ((vc-svn-url (ejb/vc-svn-url root-top-down-recurring)))
          (if (string-prefix-p "https://jubilee.q-chem.com/" vc-svn-url)
              root-top-down-recurring)))))

(use-package projectile
  :bind ("C-c p" . projectile-command-map)
  :custom
  (projectile-indexing-method 'alien)
  (projectile-enable-caching t)
  (projectile-mode-line-prefix " P")
  (projectile-require-project-root t)
  ;; not used with (projectile-indexing-method 'alien)
  (projectile-sort-order 'access-time)
  (projectile-project-search-path
   '("~/development" "~/repositories"))
  (projectile-project-root-functions
   '(projectile-root-local
     projectile-root-marked
     ejb/projectile-root-qchem
     projectile-root-bottom-up
     projectile-root-top-down
     projectile-root-top-down-recurring))
  :config
  ;; These can't be in :custom because something to do with the existence of
  ;; the variables first.
  ;;
  ;; For combined C++ and Python projects, such as those for pybind11 or using
  ;; scikit-build-base, have the Python part (pyproject.toml) take precedence.
  (setopt projectile-project-root-files-bottom-up
          (ejb/conditional-prepend projectile-project-root-files-bottom-up
                                   '("pyproject.toml"
                                     ".exercism")))
  (setopt projectile-globally-ignored-directories
          (ejb/conditional-prepend projectile-globally-ignored-directories
                                   '("^\\.hypothesis"
                                     "^\\.mypy_cache"
                                     "^\\.pytest_cache"
                                        ; all possible build directories
                                     "build"
                                     "htmlcov"
                                     "^__pycache__")))
  (setopt projectile-globally-ignored-files
          (ejb/conditional-prepend projectile-globally-ignored-files
                                   '("cmake_install.cmake")))
  (setopt projectile-globally-ignored-file-suffixes
          (ejb/conditional-prepend projectile-globally-ignored-file-suffixes
                                   '(".elc"
                                     ".pyc")))
  ;; (projectile-register-project-type 'python-pyproject '("pyproject.toml")
  ;;                                   :project-file "pyproject.toml"
  ;;                                   ;; TODO should be :package instead?
  ;;                                   :compile "python -m pip wheel"
  ;;                                   :install "python -m pip install"
  ;;                                   :test "python -m pytest -v --color=no"
  ;;                                   :test-prefix "test_"
  ;;                                   :test-suffix "_test")
  :init
  (projectile-mode 1))
(use-package direnv
  :when (executable-find "direnv")
  :init
  (direnv-mode 1))

(use-package dotenv-mode
  :mode (("\\.secrets\\'" . dotenv-mode)))

TRAMP

  • Only set tramp-verbose while debugging, otherwise you’ll think TRAMP is slow for the wrong reason.

Links:

;; Setting this to true would be ideal (so that a reformatter can be applied
;; remotely), but it causes saving to hang.
(setopt auto-revert-remote-files nil
        tramp-default-method "ssh"
        ;; tramp-verbose 8
        vc-handled-backends (delq 'Git vc-handled-backends))
(require 'tramp)
(add-to-list 'tramp-remote-path 'tramp-own-remote-path)
(use-package vagrant-tramp)

Spelling

  • ispell and flyspell are intentionally intermingled
  • TODO ensure backend is aspell?
(use-package langtool
  :custom
  (langtool-default-language "en-US")
  (langtool-mother-tongue "en")
  :config
  ;; Arch Linux
  (if (eq system-type 'gnu/linux)
      (setopt ; langtool-language-tool-server-jar "/usr/share/java/languagetool/languagetool-server.jar"
            langtool-language-tool-jar "/usr/share/java/languagetool/languagetool-commandline.jar"
            langtool-java-classpath "/usr/share/languagetool:/usr/share/java/languagetool/*")))

;; built-in
(use-package flyspell
  :ensure nil
  :hook ((text-mode . flyspell-mode)
         (prog-mode . flyspell-prog-mode))
  :bind (("C-'" . ispell-word)
         ("C-M-'" . flyspell-buffer))
  :custom
  (ispell-silently-savep t)
  (flyspell-issue-welcome-flag nil)
  (flyspell-mode-line-string " FlyS"))

Completion and language servers

(use-package company
  :hook (after-init . global-company-mode)
  :bind
  ("M-]" . company-complete)
  (:map company-active-map
         ("M-/" . company-other-backend)
         ("C-n" . company-select-next)
         ("C-p" . company-select-previous))
  :custom
  (company-dabbrev-downcase nil)
  (company-idle-delay 10)
  (company-lighter-base "cmp")
  (company-minimum-prefix-length 0)
  (company-search-regexp-function #'company-search-words-regexp)
  (company-selection-wrap-around t)
  (company-tooltip-align-annotations t)
  (company-transformers '(company-sort-by-backend-importance))
  :config
  ;; These are company backends I know I'll never use, so remove them if
  ;; present.  Some may be obsolete and not even present anymore.
  (let ((ejb/company-backends-to-remove
         '(company-bbdb
           company-eclim
           company-xcode
           company-oddmuse)))
    (setopt company-backends
            (seq-filter
             (lambda (backend)
               (not (member backend ejb/company-backends-to-remove)))
             company-backends)))
  :init
  ;; Partial ordering of (future) backends from most to least important.
  ;;
  ;; - For Python, prefer the language server over `anaconda-mode' if
  ;;   possible.
  ;;
  ;; - For Nim, nimsuggest seems to give much better results than nimlsp. But
  ;;   it times out too much.
  (setq ejb/company-ordering
        '(company-capf
          company-nimsuggest
          company-anaconda))
  (defun ejb/fix-company-ordering ()
    (with-eval-after-load 'company
      (setopt company-backends
              (-sort '(lambda (e1 e2)
                        (funcall (-rpartial 'ejb/comparator ejb/company-ordering) e1 e2))
                     company-backends)))))

(use-package lsp-mode
  :commands lsp
  :hook ((c-mode c++-mode fortran-mode f90-mode js-mode sh-mode) . lsp)
  :custom
  (lsp-keep-workspace-alive nil)
  (lsp-enable-snippet nil)
  (lsp-auto-guess-root t)
  (lsp-enable-on-type-formatting nil)
  (lsp-modeline-code-actions-enable nil)
  (lsp-modeline-diagnostics-enable nil)
  (lsp-headerline-breadcrumb-enable nil)
  (lsp-lens-enable nil)
  (lsp-file-watch-threshold 500000)
  (lsp-enable-suggest-server-download nil)
  (lsp-clients-clangd-args '("--header-insertion-decorators=0" "--header-insertion=never"))  :config
  :config
  ;; https://emacs-lsp.github.io/lsp-mode/page/faq/#how-do-i-force-lsp-mode-to-forget-the-workspace-folders-for-multi-root
  ;; When using no-littering, `lsp-session-file' is under var/lsp/session.el.
  (advice-add 'lsp :before (lambda (&rest _args)
                             (eval '(setf (lsp-session-server-id->folders (lsp-session)) (ht)))))
  (when ejb/has-treesit
    (mapc (lambda (h) (add-hook h #'lsp-deferred))
          '(bash-ts-mode-hook
            ;; c-ts-mode-hook
            ;; c++-ts-mode-hook
            js-ts-mode-hook
            python-ts-mode-hook))))

(use-package lsp-ui
  :disabled t
  :commands lsp-ui-mode
  :custom
  (lsp-ui-doc-include-signature t)
  (lsp-ui-flycheck-enable t)
  (lsp-ui-peek-always-show t))

Debuggers

(use-package realgud)
;; TODO load this when in Python and realgud has been loaded
(use-package realgud-ipdb)

Flycheck

General

(use-package flycheck
  :hook ((lsp-managed-mode . (lambda ()
                               (when (derived-mode-p 'sh-mode)
                                 (setq my/flycheck-local-cache '((lsp . ((next-checkers . (sh-bash)))))))))
         (lsp-managed-mode . (lambda ()
                               (when (derived-mode-p 'tex-mode)
                                 (setq my/flycheck-local-cache '((lsp . ((next-checkers . (tex-chktex)))))))))
         (lsp-managed-mode . (lambda ()
                               (when (derived-mode-p 'python-mode)
                                 (setq my/flycheck-local-cache '((lsp . ((next-checkers . (python-pycompile))))))))))
  :bind (("C-c f" . flycheck-mode))
  :custom
  (flycheck-check-syntax-automatically '(mode-enabled save))
  (flycheck-checker-error-threshold 2000)
  ; (flycheck-clang-pedantic t)
  ; (flycheck-clang-pedantic-errors t)
  (flycheck-gcc-openmp t)
  ; (flycheck-gcc-pedantic t)
  ; (flycheck-gcc-pedantic-errors t)
  ; (flycheck-cppcheck-checks '("all"))
  (flycheck-markdown-mdl-style "~/.mdlrc")
  (flycheck-mode-line-prefix "FC")
  (flycheck-yamllintrc "~/.config/yamllint/config")
  (flycheck-disabled-checkers '(emacs-lisp-checkdoc python-flake8 python-pylint))
  :config
  ;; https://github.com/flycheck/flycheck/issues/1762#issuecomment-750458442
  (defvar-local my/flycheck-local-cache nil)
  (defun my/flycheck-checker-get (fn checker property)
    (or (alist-get property (alist-get checker my/flycheck-local-cache))
        (funcall fn checker property)))
  (advice-add 'flycheck-checker-get :around 'my/flycheck-checker-get)
  ;; json-python-json -> json-jq -> json-jsonlint
  (setq ejb/flycheck-checker-default-json 'json-python-json)
  (flycheck-add-next-checker 'json-python-json 'json-jq t)
  (flycheck-add-next-checker 'json-jq 'json-jsonlint t)
  ;; python-pycompile -> python-ruff -> python-mypy
  (setq ejb/flycheck-checker-default-python 'python-pycompile)
  (flycheck-add-next-checker 'python-pycompile 'python-ruff nil)
  :init
  (global-flycheck-mode))

Prose

Integration with vale.

(use-package flycheck-vale
  :disabled t
  :if (executable-find "vale")
  :hook (flycheck-mode . flycheck-vale-setup))

Diffing (built-in)

(setopt diff-advance-after-apply-hunk nil
        ;; This is the default, but make it explicit that +/- markers should not
        ;; appear in the fringe.
        diff-font-lock-prettify nil)

(use-package vdiff
  :custom
  (vdiff-auto-refine t)
  (vdiff-diff-algorithm 'git-diff-patience)
  (setf (alist-get 'custom vdiff-diff-algorithms) "git --no-pager diff --patience --no-index --no-color --word-diff-regex=.")
  ;; (vdiff-diff-algorithm 'custom)
  )

(use-package difftastic
  :bind (:map magit-blame-read-only-mode-map
              ("D" . difftastic-magit-diff)
              ("S" . difftastic-magit-show))
  :config
  (eval-after-load 'magit-diff
    '(transient-append-suffix 'magit-diff '(-1 -1)
       [("D" "Difftastic diff (dwim)" difftastic-magit-diff)
        ("S" "Difftastic show" difftastic-magit-show)])))

Git/version control

General

;; Even though VC systems (at least git) commit the symbolic link pointer
;; itself, and not the file it's pointing to, I want to edit the file.
(setopt vc-follow-symlinks t)

Git

(use-package git-commit
  :init
  (require 'git-commit))
(use-package git-modes
  :mode (("/\\.containerignore\\'" . gitignore-mode)
         ("/\\.dockerignore\\'" . gitignore-mode)))
;; https://github.com/progfolio/elpaca/issues/324
(use-package transient)
(use-package magit
  :after (transient)
  :bind (("C-x g" . magit-status))
  :init
  (let ((exe-git
         (cond
          ((executable-find "/usr/bin/git") "/usr/bin/git")
          (t "git")))
        (exe-gitk
         (cond
          ((executable-find "/usr/bin/gitk") "/usr/bin/gitk")
          (t "gitk"))))
    (setopt magit-git-executable exe-git
            magit-gitk-executable exe-gitk)))
(use-package magit-delta
  :disabled t
  :hook (magit-mode . magit-delta-mode))
(use-package magit-svn
  :disabled t
  :hook magit-mode)
(use-package git-timemachine)

Pandoc

(use-package pandoc-mode
  :hook (pandoc-mode . pandoc-load-default-settings))

Org

;; http://orgmode.org/manual/Code-evaluation-security.html
;; (defun ejb/my-org-confirm-babel-evaluate (lang body)
;;   (not (equal lang "latex")))

(use-package org
  ;; Give up on trying to use the latest one, which doesn't load properly with
  ;; straight.
  :ensure nil
  :bind (("C-c l" . org-store-link)
         ("C-c a" . org-agenda)
         ("C-c c" . org-capture))
  :custom
  (org-adapt-indentation nil)
  (org-babel-tangle-lang-exts '(("emacs-lisp" . "el")
                                ("elisp" . "el")
                                ("javascript" . "js")
                                ("js" . "js")
                                ("python" . "py")))
  (org-clock-persist t)
  (org-closed-keep-when-no-todo t)
  (org-confirm-babel-evaluate nil)
  (org-descriptive-links nil)
  (org-duration-format 'h:mm)
  (org-edit-src-content-indentation 0)
  (org-export-backends '(ascii html icalendar latex md))
  (org-export-dispatch-use-expert-ui t)
  (org-export-with-smart-quotes t)
  ;; http://stackoverflow.com/questions/17239273/org-mode-buffer-latex-syntax-highlighting
  (org-highlight-latex-and-relatex '(latex script entities))
  (org-html-with-latex '(mathjax))
  (org-image-actual-width nil)
  (org-latex-create-formula-image-program 'imagemagick)
  ;; The differences from the default are that the following packages are added:
  ;; - xcolor
  ;; - booktabs
  ;; - tabulary
  ;; - braket
  ;; - microtype
  ;; - listings
  ;; - siunitx
  ;; where xcolor needs to be loaded early for packages that would otherwise
  ;; automatically load it.  Although we later prefer minted over listings for
  ;; code formatting, listings is still very good for verbatim-like blocks.
  (org-latex-default-packages-alist '(("AUTO" "inputenc" t ("pdflatex"))
                                      ("T1" "fontenc" t ("pdflatex"))
                                      ("" "graphicx" t)
                                      ("" "grffile" t)
                                      ("" "longtable" nil)
                                      ("" "wrapfig" nil)
                                      ("" "rotating" nil)
                                      ("normalem" "ulem" t)
                                      ("" "amsmath" t)
                                      ("" "textcomp" t)
                                      ("" "amssymb" t)
                                      ("" "capt-of" nil)
                                      ("dvipsnames,svgnames,table" "xcolor" nil)
                                      ("" "hyperref" nil)
                                      ("" "booktabs" nil)
                                      ("" "tabulary" nil)
                                      ("" "braket" t)
                                      ("final" "microtype" nil)
                                      ("" "listings" nil)
                                      ("" "siunitx" nil)))
  (org-latex-hyperref-template "\\hypersetup{\n pdfauthor={%a},\n pdftitle={%t},\n pdfkeywords={%k},\n pdfsubject={%d},\n pdfcreator={%c},\n pdflang={%L},\n colorlinks=true,\n linkcolor=MidnightBlue,\n citecolor=MidnightBlue,\n urlcolor=MidnightBlue}\n")
  (org-latex-inline-image-rules '(("file" . "\\.\\(pdf\\|jpeg\\|jpg\\|png\\|ps\\|eps\\|tikz\\|pgf\\|svg\\|gif\\)\\'")))
  (org-html-mathjax-options
   '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js")
     (scale "100")
     (align "center")
     (font "TeX")
     (linebreaks "false")
     (autonumber "AMS")
     (indent "0em")
     (multlinewidth "85%")
     (tagindent ".8em")
     (tagside "right")))
  (org-latex-pdf-process '("latexmk -pdf -xelatex -shell-escape -output-directory=%o %f"))
  (org-latex-tables-booktabs t)
  (org-list-allow-alphabetical t)
  (org-log-done 'time)
  (org-log-done-with-time t)
  (org-src-fontify-natively t)
  (org-src-tab-acts-natively t)
  (org-startup-folded nil)
  ;; http://superuser.com/questions/299886/linewrap-in-org-mode-of-emacs
  (org-startup-truncated nil)
  ;; http://joat-programmer.blogspot.com/2013/07/org-mode-version-8-and-pdf-export-with.html
  :config
  ;; You need to install pygments to use minted.
  (when (executable-find "pygmentize")
    (add-to-list 'org-latex-packages-alist '("" "minted" nil))
    (setopt org-latex-listings 'minted)
    ;; TODO these are applied in square brackets to every block, rather than using a global \mintedsetup.
    (setopt org-latex-minted-options nil))
  (with-eval-after-load "ox-latex"
    (add-to-list 'org-latex-classes '("refsheet" "\\documentclass{refsheet}"
                                      ("\\section{%s}" . "\\section*{%s}")
                                      ("\\subsection{%s}" . "\\subsection*{%s}")
                                      ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                                      ("\\paragraph{%s}" . "\\paragraph*{%s}")
                                      ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
    ;; The difference here is that xcolor options are passed in.
    (add-to-list 'org-latex-classes '("beamer" "\\documentclass[presentation,xcolor={dvipsnames,svgnames,table}]{beamer}"
                                      ("\\section{%s}" . "\\section*{%s}")
                                      ("\\subsection{%s}" . "\\subsection*{%s}")
                                      ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))))
  (load "~/dotfiles/dotfiles-private/org-agenda-files.el")
  (org-clock-persistence-insinuate))
(use-package htmlize)
(use-package ox-gfm
  :after org
  :hook (org-mode . (lambda () (require 'ox-gfm))))
(use-package ox-pandoc
  :after org
  :hook (org-mode . (lambda () (require 'ox-pandoc))))
(use-package ox-trac
  :after org
  :hook (org-mode . (lambda () (require 'ox-trac))))

From https://emacs.stackexchange.com/questions/20577/org-babel-load-all-languages-on-demand.

(defadvice org-babel-execute-src-block (around load-language nil activate)
  "Load language if needed"
  (let ((language (org-element-property :language (org-element-at-point))))
    (unless (cdr (assoc (intern language) org-babel-load-languages))
      (add-to-list 'org-babel-load-languages (cons (intern language) t))
      (org-babel-do-load-languages 'org-babel-load-languages org-babel-load-languages))
    ad-do-it))

Additional things of interest might be found in https://github.com/xiaohanyu/oh-my-emacs/blob/master/core/ome-org.org.

~~Every time an Org buffer is saved, automatically export it to HTML.~~ Taken from https://www.reddit.com/r/emacs/comments/4golh1/how_to_auto_export_html_when_saving_in_orgmode/. This is more annoying than it’s worth.

(defun ejb/org-mode-export-hook ()
  (add-hook 'after-save-hook 'org-html-export-to-html t t))
;; (add-hook 'org-mode-hook #'org-mode-export-hook)

A function to toggle this auto-HTML-export behavior. Does this play nice with the function above?

(defun ejb/toggle-org-html-export-on-save ()
  (interactive)
  (if (memq 'org-html-export-to-html after-save-hook)
      (progn
        (remove-hook 'after-save-hook 'org-html-export-to-html t)
        (message "Disabled org html export on save for current buffer..."))
    (add-hook 'after-save-hook 'org-html-export-to-html nil t)
    (message "Enabled org html export on save for current buffer...")))

Compilation

Taken from https://emacs.stackexchange.com/questions/62/hide-compilation-window#110.

(defun ejb/comint-clear ()
  (interactive)
  (let ((comint-buffer-maximum-size 0))
    (comint-truncate-buffer)))
(bind-key (kbd "C-c l") 'ejb/comint-clear comint-mode-map)
(setopt compilation-scroll-output t)

Evaluation

(use-package eval-in-repl
  :bind
  (:map emacs-lisp-mode-map
        ("C-<return>" . eir-eval-in-ielm)
   :map lisp-interaction-mode-map
        ("C-<return>" . eir-eval-in-ielm)
   :map Info-mode-map
        ("C-<return>" . eir-eval-in-ielm))
  :custom
  (eir-repl-placement 'right))

C/C++

Taken from https://stackoverflow.com/a/3346308

;; function decides whether .h file is C or C++ header, sets C++ by
;; default because there's more chance of there being a .h without a
;; .cc than a .h without a .c (ie. for C++ template files)
(defun ejb/c-c++-header ()
  "Sets either c-mode or c++-mode, whichever is appropriate for
the header, based upon the associated source code file."
  (interactive)
  (let ((c-filename (concat (substring (buffer-file-name) 0 -1) "c")))
    (if (file-exists-p c-filename)
        (c-mode)
      (c++-mode))))
(add-to-list 'auto-mode-alist '("\\.h\\'" . ejb/c-c++-header))

(defun ejb/c-c++-toggle ()
  "Toggles a buffer between c-mode and c++-mode."
  (interactive)
  (cond ((string= major-mode "c-mode")
         (c++-mode))
        ((string= major-mode "c++-mode")
         (c-mode))))

(defconst ejb/cc-style
  '("k&r"
    (c-basic-offset . 4)
    (c-offsets-alist . ((innamespace . [0])))))

(c-add-style "ejb" ejb/cc-style)

(setopt c-default-style
        '((java-mode . "java")
          (awk-mode . "awk")
          (other . "ejb")))

;; TODO
;; (setopt c-ts-mode-indent-offset tab-width)
(use-package ccls
  :disabled t
  :after lsp-mode
  :hook ((c-mode c++-mode) . lsp))
(use-package clang-format+
  :hook (c-mode-common . clang-format+-mode)
  :custom
  (clang-format-style "file")
  (clang-format+-context 'modification))

(use-package astyle
  :when (executable-find "astyle"))

Java

(use-package lsp-java
  :disabled t
  :hook (java-mode . lsp))

FORTRAN (built-in)

(use-package fortran
  :ensure nil
  :custom
  (fortran-comment-region "C"))

LaTeX

Lowercase functions (latex-mode) come from Emacs tex-mode. Mixed-case functions (LaTeX-mode) come from AUCTeX…kind of. From textmodes/tex-mode.el:

The following three autoloaded aliases appear to conflict with AUCTeX. However, even though AUCTeX uses the mixed case variants for all mode relevant variables and hooks, the invocation function and setting of `major-mode’ themselves need to be lowercase for AUCTeX to provide a fully functional user-level replacement. So these aliases should remain as they are, in particular since AUCTeX users are likely to use them.

(use-package auctex
  :hook ((latex-mode LaTeX-mode) . lsp)
  :config
  (add-to-list 'texmathp-tex-commands "dmath" 'env-on)
  (texmathp-compile)
  :custom
  (TeX-master 'shared)
  ;; nil is the default; this remains here as a reminder that setting it to
  ;; true makes Emacs hang on every save when enabled.
  (TeX-auto-save nil)
  (TeX-parse-self t))

(use-package auctex-latexmk
  :disabled t
  :custom
  (auctex-latexmk-inherit-TeX-PDF-mode t)
  :init
  (auctex-latexmk-setup))

Python

;; The package is "python" but the mode is "python-mode":
(use-package python
  :ensure nil
  :mode (("\\.ipy\\'" . python-mode))
  :hook ((python-base-mode . (lambda ()
                               (flycheck-select-checker ejb/flycheck-checker-default-python))))
  :custom
  (python-fill-docstring-style 'pep-257-nn)
  (python-indent-guess-indent-offset nil)
  (python-shell-interpreter (cond ((executable-find "ipython") "ipython")
                                  ((executable-find "python3") "python3")
                                  ((executable-find "python") "python")
                                  (t "python3")))
  (python-shell-interpreter-args (cond ((executable-find "ipython") "-i --simple-prompt")
                                       (t "-i"))))

(use-package python-docstring
  :hook (python-mode . python-docstring-mode)
  :delight)

(use-package numpydoc
  :after python
  :custom
  (numpydoc-insertion-style 'nil)
  (numpydoc-insert-examples-block nil))

(use-package virtualenvwrapper
  :after python)

(use-package conda
  :disabled t
  :after delight
  :hook (after-init . conda-env-initialize-interactive-shells)
  :commands (conda-env-deactivate
             conda-env-activate
             conda-env-activate-path
             conda-env-list
             conda-env-initialize-eshell
             conda-env-activate-for-buffer))

;; TODO store Python version in variable only when conda env changes
;; (defun ejb/conda-mode-lighter ()
;;   "Only display the lighter if a conda environment is active."
;;   (if (equal conda-env-current-name nil)
;;       ""
;;     (progn
;;       (setq current-python-version
;;             (cadr
;;              (split-string
;;               (shell-command-to-string
;;                (format "%s/bin/python --version" (getenv "CONDA_PREFIX"))))))
;;       (format "conda[%s:%s]" current-python-version conda-env-current-name))))
;; (defun ejb/conda-mode-lighter ()
;;   "Only display the lighter if a conda environment is active."
;;   (if (equal conda-env-current-name nil)
;;       ""
;;     (format " conda[%s]" conda-env-current-name)))
;; TODO this delight for conda breaks elcord.
;; (delight 'python-mode '(:eval (format "Python%s" (ejb/conda-mode-lighter))) :major)

(use-package pyenv-mode
  :after python)

(use-package lsp-pyright
  :hook (python-mode . (lambda ()
                         (require 'lsp-pyright)
                         (lsp))))

Reformatters

If :after python isn’t present, the bindings don’t get added properly?

There is also the built-in python-sort-imports via C-c C-i s.

(use-package blacken
  :after python
  :bind
  (:map python-mode-map
        ("C-c C-b" . blacken-buffer)))

(use-package python-isort
  :after python
  :bind
  (:map python-mode-map
        ("C-c C-a" . python-isort-buffer)))

Leftovers

(use-package cython-mode)
(use-package flycheck-cython)
(use-package pip-requirements)

Markdown

Rather than use --mathjax= with a URL argument, --include-in-header allows the insertion of arbitrary HTML into Pandoc’s output. The mathjax.html file contains Chemistry Stack Exchange’s header scripts for first configuring the MathJax extension to load mhchem, then loads MathJax.

See https://stackoverflow.com/questions/25410701/how-do-i-include-meta-tags-in-pandoc-generated-html for an example of how including arbitrary HTML works.

See https://chemistry.meta.stackexchange.com/questions/3540/what-additional-formatting-features-are-available-to-mathjax-possibly-via-requ for more information about what can be done with the MathJax extension.

(use-package markdown-mode
  :hook ((markdown-mode . pandoc-mode)
         (markdown-mode . outline-minor-mode))
  ;; Don't run pandoc on every save, it gets annoying.
  ;; :config
  ;; (add-hook 'markdown-mode-hook
  ;;           (lambda ()
  ;;             (add-hook 'after-save-hook 'pandoc-run-pandoc t :local)))
  :custom
  (markdown-asymmetric-header t)
  (markdown-content-type "application/xhtml+xml")
  ;; This isn't super necessary since I have pandoc run a similar command
  ;; every time I save with these default arguments, but this always produces
  ;; HTML where pandoc-mode might not.
  (markdown-command
   (concat "pandoc --from=markdown --to=html5 --highlight-style=pygments --standalone --include-in-header="
           (expand-file-name "mathjax.html" user-emacs-directory)))
  (markdown-enable-math t)
  (markdown-fontify-code-blocks-natively t)
  (markdown-hide-markup nil)
  (markdown-hide-urls nil)
  (markdown-italic-underscore t)
  (markdown-link-space-sub-char "-"))

deft

Taken conveniently from Jason Blevins’ website and http://pragmaticemacs.com/emacs/make-quick-notes-with-deft/.

(use-package deft
  :bind (("C-c d" . deft))
  :custom
  (deft-auto-save-interval 60.0)
  ;; "${HOME}/Dropbox/Notes" doesn't work, why is that?
  (deft-directory "~/Dropbox/Notes")
  (deft-default-extension "md")
  (deft-extensions '("txt" "text" "utf8" "taskpaper" "md" "markdown" "org" "tex"))
  (deft-recursive t)
  (deft-text-mode 'gfm-mode)
  (deft-time-format " %Y-%m-%d %H:%M:%S")
  (deft-use-filename-as-title t)
  (deft-use-filter-string-as-filename t))

https://stackoverflow.com/a/35450025/3249688

(defun yashi/new-scratch-buffer-in-org-mode ()
  (interactive)
  (switch-to-buffer (generate-new-buffer-name "*temp*"))
  (org-mode))
(bind-key "<f7>" 'yashi/new-scratch-buffer-in-org-mode)

(defun yashi/deft-new-file ()
  (interactive)
  (let ((deft-filter-regexp nil))
    (deft-new-file)))
(bind-key "<f6>" 'yashi/deft-new-file)

scratch buffers

(with-current-buffer "*scratch*"
  (emacs-lock-mode 'kill))

(use-package persistent-scratch
  :init
  (persistent-scratch-setup-default))

CMake

This section needs to come after the Markdown section so that CMake files get recognized properly.

(use-package cmake-mode
  :hook (cmake-mode . lsp)
  :custom
  ;; This isn't working
  (cmake-tab-width tab-width))

(when ejb/has-treesit
  ;; cmake-ts-mode is available
  (progn
    (setopt cmake-ts-mode-indent-offset 4)
    ;; These are the defaults from cmake-mode; the one provided by
    ;; cmake-ts-mode is "\\(?:CMakeLists\\.txt\\|\\.cmake\\)\\'"
    (let ((cmake-modes '("\\.cmake\\'" "CMakeLists\\.txt\\'")))
      (mapc
       (lambda (s) (add-to-list 'auto-mode-alist `(,s . cmake-ts-mode)))
       cmake-modes))
    (add-hook 'cmake-ts-mode-hook #'lsp)))

Shell Scripts

https://www.reddit.com/r/emacs/comments/5tzub2/improving_shellscriptmode_highlight/

(defconst sh-mode--string-interpolated-variable-regexp
  "{\\$[^}\n\\\\]*\\(?:\\\\.[^}\n\\\\]*\\)*}\\|\\${\\sw+}\\|\\$\\sw+")

(defun ejb/sh-mode--string-interpolated-variable-font-lock-find (limit)
  (while (re-search-forward sh-mode--string-interpolated-variable-regexp limit t)
    (let ((quoted-stuff (nth 3 (syntax-ppss))))
      (when (and quoted-stuff (member quoted-stuff '(?\" ?`)))
        (put-text-property (match-beginning 0) (match-end 0)
                           'face 'font-lock-variable-name-face))))
  nil)

;; TODO I'm not sure why this doesn't work.
;; (with-eval-after-load 'sh-mode
;;   (font-lock-add-keywords 'sh-mode
;;                           `(sh-mode--string-interpolated-variable-font-lock-find)
;;                           'append))
(font-lock-add-keywords 'sh-mode
                        `((ejb/sh-mode--string-interpolated-variable-font-lock-find))
                        'append)

;; This doesn't work because it only finds the first instance.
;; (font-lock-add-keywords 'sh-mode '(("\".*?\\(\\${.*?}\\).*?\"" 1 font-lock-variable-name-face prepend)))

EditorConfig

http://editorconfig.org/

TODO How to make this take precedence over dtrt-indent?

(use-package editorconfig
  :when (executable-find "editorconfig")
  :hook ((prog-mode text-mode) . editorconfig-mode)
  :custom (editorconfig-mode-lighter "")
  :config
  (defun ejb/editorconfig-has-editorconfig ()
    "If there is an .editorconfig file associated with the
current buffer, return its path, otherwise nil."
    (if buffer-file-name
        (let* ((directory (file-name-directory buffer-file-name))
               (file (editorconfig-core-get-nearest-editorconfig directory)))
          file)))
  (defun ejb/editorconfig-mode-lighter ()
    "Only display the lighter if an .editorconfig file has been found."
    (if (ejb/editorconfig-has-editorconfig)
        " EC"
      ""))
  ;; TODO This is disabled until it can be integrated with Projectile. Doing
  ;; the naive search with `editorconfig-core-get-nearest-editorconfig` is
  ;; death over TRAMP.
  ;; :delight '(:eval (ejb/editorconfig-mode-lighter))
  ;; There is a defcustom now.
  ;; :delight
  )

Conf (builtin)

(use-package conf-mode
  :ensure nil
  :mode
  ; generic
  ((".nanorc" . conf-space-mode)
   (".coveragerc" . conf-unix-mode)
   ; Python tools
   (".flake8" . conf-unix-mode)
   (".pylintrc" . conf-unix-mode)
   (".style.yapf" . conf-unix-mode)
   ("poetry.lock" . conf-toml-mode)))

(when ejb/has-treesit
    (setopt toml-ts-mode-indent-offset tab-width))

XML (builtin)

(use-package nxml
  :ensure nil
  :mode (("\\.rdf\\'" . nxml-mode)
         ("\\.xmp\\'" . nxml-mode)))

MATLAB/Octave (builtin)

TODO disable .m files from loading as Objective-C

(use-package octave
  :ensure nil
  :custom
  (octave-block-offset 4))

Julia

(if (not ejb/has-treesit)
    (use-package julia-mode)
  ;; not built-in; depends on julia-mode
  (use-package julia-ts-mode
    :delight "Julia"))

(use-package vterm
  :custom
  (vterm-always-compile-module t))

(use-package eat
  :ensure (eat :type git
               :host codeberg
               :repo "akib/emacs-eat"
               :files ("*.el" ("term" "term/*.el") "*.texi"
                       "*.ti" ("terminfo/e" "terminfo/e/*")
                       ("terminfo/65" "terminfo/65/*")
                       ("integration" "integration/*")
                       (:exclude ".dir-locals.el" "*-tests.el"))))

;; Time to have the Julia REPLs duke it out...
(use-package julia-snail
  ;; :disabled t
  :hook (julia-mode . julia-snail-mode)
  :custom
  (julia-snail-terminal-type :eat)
  (julia-snail-repl-buffer "*julia-snail*")
  :config
  (when (executable-find "julialauncher")
    (setopt julia-snail-executable "julialauncher")))

(defun julia-repl-run-tests (arg)
  "From https://github.com/tpapp/julia-repl/issues/142"
  (interactive "P")
  (julia-repl-activate-parent arg)
  (julia-repl--send-string "Pkg.test()"))

(defun julia-repl-include-tests (arg)
  "From https://github.com/tpapp/julia-repl/issues/142"
  (interactive "P")
  (if arg
      (progn
        (message "activating home project")
        (julia-repl--send-string "import Pkg; Pkg.activate()"))
    (cl-flet ((find-projectfile (filename)
                (locate-dominating-file (buffer-file-name) filename)))
      (if-let ((projectfile (or (find-projectfile "Project.toml")
                                (find-projectfile "JuliaProject.toml"))))
          (progn
            (message "activating %s" projectfile)
            (julia-repl--send-string
             (concat "import Pkg; Pkg.activate(\""
                     (expand-file-name (file-name-directory projectfile))
                     "\"); include(\""
                     (expand-file-name
                      (concat (file-name-directory projectfile)
                              (file-name-as-directory "test")
                              "runtests.jl"))
                     "\")")))
        (message "could not find project file")))))

(use-package julia-repl
  :disabled t
  :hook (julia-mode . julia-repl-mode)
  :config
  (when (executable-find "julialauncher")
    (push '(default-juliaup "julialauncher") julia-repl-executable-records))
  (julia-repl-set-terminal-backend 'eat)
  :custom
  (julia-repl-inferior-buffer-name-base "julia-repl"))

(use-package lsp-julia
  :hook (julia-mode . (lambda ()
                        (require 'lsp-julia)
                        (lsp)))
  :config
  (when (executable-find "julialauncher")
    (setopt lsp-julia-command "julialauncher"))
  :custom
  (lsp-julia-default-environment "~/.julia/environments/v1.9")
  (lsp-julia-timeout 300))

(use-package julia-formatter
  ;; Wow, this is really slow.
  :disabled t
  :hook ((julia-mode . julia-formatter-mode)
         (julia-mode . julia-formatter--ensure-server)))

YAML

(use-package yaml-mode
  ;; The first is a decent assumption for ISI-specific pseudo-YAML files.
  :mode
  (("\\.params\\'" . yaml-mode)
   ("\\clang-format\\'" . yaml-mode)
   ("\\.asdf\\'" . yaml-mode)
   ("CITATION.cff" . yaml-mode)))

JSON

(let ((ejb/json-modes '("\\.cjson\\'"
                        "\\.jsonld\\'"
                        "\\.qcjson\\'"
                        "\\.qcschema\\'"
                        "flake.lock")))
  (if (not ejb/has-treesit)
      ;; Don't use jsonian when tree-sitter is available.
      (progn
        (use-package jsonian
          :hook ((jsonian-mode . (lambda ()
                                   (flycheck-select-checker ejb/flycheck-checker-default-json)))
                 (jsonian-mode . hs-minor-mode))
          :init
          (jsonian-enable-flycheck))
        (mapc
         (lambda (s) (add-to-list 'auto-mode-alist `(,s . jsonian-mode)))
         ejb/json-modes))
    ;; json-ts-mode is available
    (progn
      (setopt json-ts-mode-indent-offset tab-width)
      (mapc
       (lambda (s) (add-to-list 'auto-mode-alist `(,s . json-ts-mode)))
       ejb/json-modes))))

(use-package jq-format
  :custom
  (jq-format-extra-args '("--indent" "4")))

Better configuration languages

(use-package cue-mode)
(use-package dhall-mode)
(use-package nickel-mode)

HTML

(use-package web-mode)
(use-package jinja2-mode
  :mode (("\\.j2\\'" . jinja2-mode)))
(use-package web-beautify
  :disabled t
  :config
  (eval-after-load 'js2-mode
    '(define-key js2-mode-map (kbd "C-c b") 'web-beautify-js))
  (eval-after-load 'json-mode
    '(define-key json-mode-map (kbd "C-c b") 'web-beautify-js))
  (eval-after-load 'sgml-mode
    '(define-key html-mode-map (kbd "C-c b") 'web-beautify-html))
  (eval-after-load 'css-mode
    '(define-key css-mode-map (kbd "C-c b") 'web-beautify-css)))

Rust

(use-package rustic
  :bind (:map rustic-mode-map
              ("C-c C-c C-;" . rustic-docstring-around-dwim)
              ("C-c C-c C-d" . rustic-cargo-build-doc))
  :custom
  (rustic-ansi-faces ansi-color-names-vector)
  (rustic-format-trigger 'on-save)
  (rustic-indent-method-chain t)
  :config
  (defun rustic-docstring-around-dwim ()
    "Use `comment-dwim' to make a Rust docstring for the thing surrounding the comment.

Such comments are mostly for module- or crate-level documentation.
See https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html for more information."
    (interactive)
    (let ((comment-start "//! "))
      (call-interactively 'comment-dwim))))

Scheme/Lisp

(use-package paredit
  :hook (((emacs-lisp-mode inferior-emacs-lisp-mode lisp-mode scheme-mode cider-repl-mode clojure-mode hy-mode racket-mode slime-mode) . paredit-mode)
         ;; TODO I dont't think this works?
         ;; (paredit-mode . (lambda () (electric-indent-local-mode -1)))
         )
  :config
  ;; These are the previous defaults before `paredit-RET' was introduced.
  ;;
  ;; The default is `newline', old paredit was `newline', then turned into
  ;; `paredit-RET'.
  (unbind-key (kbd "RET") paredit-mode-map)
  ;; The default is `electric-newline-and-maybe-indent' (at least in
  ;; `fundamental-mode' and many prog modes), old paredit was
  ;; `paredit-newline', then turned into `paredit-C-j'.
  (bind-key (kbd "C-j") #'paredit-newline paredit-mode-map))

Emacs Lisp

(put 'ert-deftest 'lisp-indent-function 'defun)

(use-package cask-mode)
(use-package flycheck-package)
(add-to-list 'auto-mode-alist '("Eask" . lisp-data-mode))

Common Lisp

(use-package slime
  :disabled t
  :custom
  (common-lisp-style-default "modern")
  ;; default is "lisp", which on my Arch Linux machine is CMUCL
  (inferior-lisp-program "sbcl")
  (lisp-indent-function 'common-lisp-indent-function)
  (slime-contribs '(slime-cl-indent slime-fancy))
  :init
  (require 'slime-autoloads))

(use-package sly
  :custom
  (inferior-lisp-program "sbcl"))

Hy

(use-package hy-mode
  :disabled t)

Scheme and Racket

(use-package geiser-chez
  :disabled t)
(use-package geiser-chibi
  :disabled t)
(use-package geiser-guile)
(use-package geiser-mit
  :disabled t)
(use-package scribble-mode
  :disabled t
  :hook (scribble-mode . geiser))
(use-package racket-mode
  :disabled t)

Clojure

(use-package clojure-mode
  :hook (clojure-mode . lsp)
  :config
  (defun cider-interactive-notify-and-eval (code)
    (interactive)
    (message code)
    (cider-interactive-eval
     code
     (cider-interactive-eval-handler nil (point))
     nil
     nil))
  (defun notespace/eval-and-realize-note-at-this-line ()
    (interactive)
    (save-buffer)
    (cider-interactive-notify-and-eval
     (concat "(notespace.api/eval-and-realize-note-at-line "
             (number-to-string (line-number-at-pos))
             ")")))
  (defun notespace/eval-and-realize-notes-from-this-line ()
    (interactive)
    (save-buffer)
    (cider-interactive-notify-and-eval
     (concat "(notespace.api/eval-and-realize-notes-from-line "
             (number-to-string (line-number-at-pos))
             ")")))
  (defun notespace/eval-and-realize-notes-from-change ()
    (interactive)
    (save-buffer)
    (cider-interactive-notify-and-eval
     (concat "(notespace.api/eval-and-realize-notes-from-change)")))
  (defun notespace/init-with-browser ()
    (interactive)
    (save-buffer)
    (cider-interactive-notify-and-eval
     (concat "(notespace.api/init-with-browser)")))
  (defun notespace/init ()
    (interactive)
    (save-buffer)
    (cider-interactive-notify-and-eval
     (concat "(notespace.api/init)")))
  (defun notespace/eval-this-notespace ()
    (interactive)
    (save-buffer)
    (cider-interactive-notify-and-eval
     "(notespace.api/eval-this-notespace)"))
  (defun notespace/eval-and-realize-this-notespace ()
    (interactive)
    (save-buffer)
    (cider-interactive-notify-and-eval
     "(notespace.api/eval-and-realize-this-notespace)"))
  (defun notespace/render-static-html ()
    (interactive)
    (cider-interactive-notify-and-eval
     "(notespace.api/render-static-html)"))
  :bind (:map clojure-mode-map
              ("C-c n e" . notespace/eval-this-notespace)
              ("C-c n r" . notespace/eval-and-realize-this-notespace)
              ("C-c n n" . notespace/eval-and-realize-note-at-this-line)
              ("C-c n f" . notespace/eval-and-realize-notes-from-this-line)
              ("C-c n i b" . notespace/init-with-browser)
              ("C-c n i i" . notespace/init)
              ("C-c n s" . notespace/render-static-html)
              ("C-c n c" . notespace/eval-and-realize-notes-from-change)))
(use-package cider
  :custom
  (cider-default-cljs-repl 'node)
  (cider-repl-pop-to-buffer-on-connect nil)
  :config
  (defun cider-jack-in-bb ()
    "Start a babashka nREPL server for the current project and connect to it."
    (interactive)
    ;; First try and find an open port, starting at the "default" bb nREPL port
    ;; of 1667.
    (let* ((port 1666)
           (lsof-cmd "lsof -P -i TCP:%d")
           (lsof-output 0))
      (while (zerop lsof-output)
        (setq port (1+ port)
              lsof-output (shell-command (format lsof-cmd port))))
      ;; An open port has been found, start 'bb nrepl-server' and pass it to
      ;; CIDER.
      (let* ((hostname "localhost")
             (project-dir default-directory)
             (params (list :host hostname
                           :port port
                           :project-dir project-dir))
             (bb-cmd (format "bb nrepl-server %s:%s" hostname port)))
        (nrepl-start-server-process
         project-dir
         bb-cmd
         (lambda (server-buffer)
           (cider-connect-sibling-clj params server-buffer)))))))

Semantic web: SPARQL/Turtle

(use-package sparql-mode
  :mode (("\\.sparql\\'" . sparql-mode)
         ("\\.rq\\'" . sparql-mode)))
;; TODO company-sparql
(use-package ttl-mode
  :ensure (ttl-mode
           :type git
           :host github
           :repo "jeeger/ttl-mode"
           :files ("*.el"))
  :delight "N3/Turtle"
  :mode (("\\.n3\\'" . ttl-mode)    ; Notation3
         ("\\.nt\\'" . ttl-mode)    ; N-Triples
         ("\\.shacl\\'" . ttl-mode) ; SHACL (not a graph, but constraints; looks similar)
         ("\\.ttl\\'" . ttl-mode)   ; Turtle (Terse RDF Triple Language)
         ("\\.turtle\\'" . ttl-mode))
  :custom
  (ttl-indent-on-idle-timer nil))

Nim

flycheck-nimsuggest, despite being “old”, is required by nimsuggest-mode.

(use-package flycheck-nimsuggest)
(use-package nim-mode
  :bind (:map nim-mode-map ("C-c C-;" . ejb/nim-docstring-dwim))
  ;; We want to be able to "fix" the company backend ordering after
  ;; `nimsuggest-mode' adds `company-nimsuggest' to `company-backends', so the
  ;; hooks need to be in this order.
  :hook ((nim-mode . ejb/fix-company-ordering)
         (nim-mode . nimsuggest-mode)
         (nim-mode . lsp))
  :config
  (defun ejb/nim-docstring-dwim ()
    "Use `comment-dwim' to make a Nim docstring."
    (interactive)
    (let ((comment-start "## "))
      (call-interactively 'comment-dwim))))

HDF5/h5dump

(use-package h5dump-mode
  :hook (h5dump-mode . hs-minor-mode))

Containers

(use-package dockerfile-mode)
(use-package apptainer-mode
  :mode ("\\.def\\'" . apptainer-mode)
  ;; not on MELPA yet
  :ensure (:host github :repo "berquist/apptainer-mode"))

Other languages, modes, and packages

(use-package coconut-mode
  :disabled t
  :ensure (:host github
           ;; "main" is NickSeagull, alternate (not working) is "padawanphysicist"
           :repo "NickSeagull/coconut-mode")
  :mode ("\\.coco\\'" . coconut-mode))
(use-package crontab-mode)
(use-package cwl-mode)
(use-package earthfile-mode)
(use-package elixir-mode)
(use-package ess)
(use-package exercism-modern
  :ensure (:host github
           :repo "elken/exercism-modern"
           :files ("*.el" "icons")))
(use-package go-mode
  :hook (go-mode . lsp))
(use-package graphviz-dot-mode)
(use-package groovy-mode)
(use-package jenkinsfile-mode)
(use-package just-mode)
(use-package lox-mode)
(use-package lua-mode)
(use-package meson-mode)
(use-package nix-mode)
(if ejb/has-treesit
    (use-package nix-ts-mode))
(use-package pacfiles-mode)
(use-package pdf-tools)
(use-package pkgbuild-mode
  :custom
  (pkgbuild-update-sums-on-save nil))
(use-package snakemake-mode)
(use-package systemd)
(use-package tracwiki-mode
  :mode ("\\.trac\\'" . tracwiki-mode))

External services

Discord

(load "~/dotfiles/dotfiles-private/work-hostnames.el")
(use-package elcord
  :disabled t
  ;; No work machines
  :if (not (ejb/is-work-machine))
  :custom
  (elcord-use-major-mode-as-main-icon t)
  :init
  (elcord-mode))

Wakatime

(use-package wakatime-mode
  :if (executable-find "wakatime")
  :delight
  :custom
  (wakatime-cli-path (executable-find "wakatime"))
  :init
  (global-wakatime-mode))
;; Local Variables:
;; no-byte-compile: t
;; no-native-compile: t
;; no-update-autoloads: t
;; End: