;;; init.el --- Amin Bandali's Emacs config -*- lexical-binding: t -*- ;; Copyright (C) 2018 Amin Bandali ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Emacs configuration of Amin Bandali, computer scientist and functional ;; programmer. ;; THIS FILE IS AUTO-GENERATED FROM `init.org'. ;; Naming conventions: ;; ;; amin-... public variables or non-interactive functions ;; amin--... private anything (non-interactive), not safe for direct use ;; amin/... an interactive function; safe for M-x or keybinding ;; amin|... a hook function ;; amin*... an advising function ;; amin@... a hydra command ;; ...! a macro ;;; Code: ;; * Initial setup (defvar amin--before-user-init-time (current-time) "Value of `current-time' when Emacs begins loading `user-init-file'.") (message "Loading Emacs...done (%.3fs)" (float-time (time-subtract amin--before-user-init-time before-init-time))) (defvar amin--gc-cons-threshold gc-cons-threshold) (defvar amin--gc-cons-percentage gc-cons-percentage) (defvar amin--file-name-handler-alist file-name-handler-alist) (setq gc-cons-threshold (* 400 1024 1024) ; 400 MiB gc-cons-percentage 0.6 file-name-handler-alist nil ;; sidesteps a bug when profiling with esup esup-child-profile-require-level 0) (add-hook 'after-init-hook (lambda () (setq gc-cons-threshold amin--gc-cons-threshold gc-cons-percentage amin--gc-cons-percentage file-name-handler-alist amin--file-name-handler-alist))) (setq message-log-max 20000) ;; (setq byte-compile-warnings ;; '(not free-vars unresolved noruntime lexical make-local)) (setq user-full-name "Amin Bandali" user-mail-address "amin@aminb.org") (setq package-enable-at-startup nil) ;; (package-initialize) (setq user-init-file (or load-file-name buffer-file-name) user-emacs-directory (file-name-directory user-init-file)) (add-to-list 'load-path (expand-file-name "lib/borg" user-emacs-directory)) (require 'borg) (borg-initialize) ;; (require 'borg-nix-shell) ;; (setq borg-build-shell-command 'borg-nix-shell-build-command) (with-eval-after-load 'bind-key (bind-keys :package borg ("C-c b A" . borg-activate) ("C-c b a" . borg-assimilate) ("C-c b b" . borg-build) ("C-c b c" . borg-clone) ("C-c b r" . borg-remove))) (require 'use-package) (if nil ; set to t when need to debug init (setq use-package-verbose t use-package-expand-minimally nil use-package-compute-statistics t debug-on-error t) (setq use-package-verbose nil use-package-expand-minimally t)) (use-package epkg :defer t :bind (("C-c b d" . epkg-describe-package) ("C-c b p" . epkg-list-packages) ("C-c b u" . epkg-update))) (use-package no-littering :demand t :config (savehist-mode 1) (add-to-list 'savehist-additional-variables 'kill-ring) (save-place-mode 1) (setq auto-save-file-name-transforms `((".*" ,(no-littering-expand-var-file-name "auto-save/") t)))) (use-package custom :no-require t :config (setq custom-file (no-littering-expand-etc-file-name "custom.el")) (when (file-exists-p custom-file) (load custom-file)) (setf custom-safe-themes t)) (with-demoted-errors (load (no-littering-expand-etc-file-name "secrets"))) (use-package exec-path-from-shell :defer 1 :init (setq exec-path-from-shell-check-startup-files nil) :config (exec-path-from-shell-initialize) ;; while we're at it, let's fix access to our running ssh-agent (exec-path-from-shell-copy-env "SSH_AGENT_PID") (exec-path-from-shell-copy-env "SSH_AUTH_SOCK")) ;; only one custom theme at a time ;; ;; (defadvice load-theme (before clear-previous-themes activate) ;; "Clear existing theme settings instead of layering them" ;; (mapc #'disable-theme custom-enabled-themes)) (use-package server :defer 1 :config (or (server-running-p) (server-mode))) ;; unicode support ;; ;; (dolist (ft (fontset-list)) ;; (set-fontset-font ;; ft ;; 'unicode ;; (font-spec :name "Source Code Pro" :size 14)) ;; (set-fontset-font ;; ft ;; 'unicode ;; (font-spec :name "DejaVu Sans Mono") ;; nil ;; 'append) ;; ;; (set-fontset-font ;; ;; ft ;; ;; 'unicode ;; ;; (font-spec ;; ;; :name "Symbola monospacified for DejaVu Sans Mono") ;; ;; nil ;; ;; 'append) ;; ;; (set-fontset-font ;; ;; ft ;; ;; #x2115 ; ℕ ;; ;; (font-spec :name "DejaVu Sans Mono") ;; ;; nil ;; ;; 'append) ;; (set-fontset-font ;; ft ;; (cons ?Α ?ω) ;; (font-spec :name "DejaVu Sans Mono" :size 14) ;; nil ;; 'prepend)) (setq text-scale-mode-step 1.05) (setq mouse-autoselect-window t) (defun amin--no-mouse-autoselect-window () (make-local-variable 'mouse-autoselect-window) (setq mouse-autoselect-window nil)) (require 'cl-lib) (require 'subr-x) (defun amin-enlist (exp) "Return EXP wrapped in a list, or as-is if already a list." (if (listp exp) exp (list exp))) ; from https://github.com/hlissner/doom-emacs/commit/589108fdb270f24a98ba6209f6955fe41530b3ef (defmacro after! (features &rest body) "A smart wrapper around `with-eval-after-load'. Supresses warnings during compilation." (declare (indent defun) (debug t)) (list (if (or (not (bound-and-true-p byte-compile-current-file)) (dolist (next (amin-enlist features)) (if (symbolp next) (require next nil :no-error) (load next :no-message :no-error)))) #'progn #'with-no-warnings) (cond ((symbolp features) `(eval-after-load ',features '(progn ,@body))) ((and (consp features) (memq (car features) '(:or :any))) `(progn ,@(cl-loop for next in (cdr features) collect `(after! ,next ,@body)))) ((and (consp features) (memq (car features) '(:and :all))) (dolist (next (cdr features)) (setq body `(after! ,next ,@body))) body) ((listp features) `(after! (:all ,@features) ,@body))))) (defmacro setq-every! (value &rest vars) "Set all the variables from VARS to value VALUE." (declare (indent defun) (debug t)) `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars))) ;; * Core ;; (fringe-mode '(3 . 1)) (fringe-mode nil) (setq disabled-command-function nil) (setq save-interprogram-paste-before-kill t) (setq enable-recursive-minibuffers t resize-mini-windows t) (defalias 'yes-or-no-p #'y-or-n-p) (setq initial-buffer-choice t) (setq initial-scratch-message nil) (setq initial-major-mode 'text-mode) (setq inhibit-startup-buffer-menu t) (advice-add #'display-startup-echo-area-message :override #'ignore) (setq inhibit-startup-screen t inhibit-startup-echo-area-message user-login-name) (setq frame-title-format '("" invocation-name " - " (:eval (if (buffer-file-name) (abbreviate-file-name (buffer-file-name)) "%b")))) (setq backup-by-copying t version-control t delete-old-versions t) (global-auto-revert-mode 1) (setq auto-revert-verbose nil global-auto-revert-non-file-buffers nil) (setq-default indent-tabs-mode nil require-final-newline t tab-width 4) (winner-mode 1) (setq compilation-exit-message-function (lambda (status code msg) "Close the compilation window if successful." ;; if M-x compile exits with 0 (when (and (eq status 'exit) (zerop code)) (bury-buffer) (delete-window (get-buffer-window (get-buffer "*compilation*")))) ;; return the result of compilation-exit-message-function (cons msg code))) (setq search-default-mode #'char-fold-to-regexp) ;; uncomment to extend this behaviour to query-replace ;; (setq replace-char-fold t) (bind-keys ("C-c a i" . ielm) ("C-c e b" . eval-buffer) ("C-c e r" . eval-region) ("C-c F m" . make-frame-command) ("C-c F d" . delete-frame) ("C-c F D" . delete-other-frames) ("C-c o" . other-window) ("C-c Q" . save-buffers-kill-terminal) ("C-S-h C" . describe-char) ("C-S-h F" . describe-face) ("C-x K" . kill-this-buffer) ("s-p" . beginning-of-buffer) ("s-n" . end-of-buffer)) (use-package auto-compile :demand t :config (auto-compile-on-load-mode) (auto-compile-on-save-mode) (setq auto-compile-display-buffer nil auto-compile-mode-line-counter t auto-compile-source-recreate-deletes-dest t auto-compile-toggle-deletes-nonlib-dest t auto-compile-update-autoloads t) (add-hook 'auto-compile-inhibit-compile-hook 'auto-compile-inhibit-compile-detached-git-head)) (use-package org :defer 1 :config (setq org-src-tab-acts-natively t org-src-preserve-indentation nil org-edit-src-content-indentation 0 org-email-link-description-format "Email %c: %s" ; %.30s org-highlight-latex-and-related '(entities) org-log-done 'time) (add-to-list 'org-structure-template-alist '("L" . "src emacs-lisp") t) (font-lock-add-keywords 'org-mode '(("[ \t]*\\(#\\+\\(BEGIN\\|END\\|begin\\|end\\)_\\(\\S-+\\)\\)[ \t]*\\([^\n:]*\\)" (1 '(:foreground "#5a5b5a" :background "#292b2b") t) ; directive (3 '(:foreground "#81a2be" :background "#292b2b") t) ; kind (4 '(:foreground "#c5c8c6") t))) ; title t) :bind (:map org-mode-map ("M-L" . org-insert-last-stored-link)) :hook ((org-mode . org-indent-mode) (org-mode . auto-fill-mode) (org-mode . flyspell-mode)) :custom (org-latex-packages-alist '(("" "listings") ("" "color"))) :custom-face '(org-block-begin-line ((t (:foreground "#5a5b5a" :background "#1d1f21")))) '(org-block ((t (:background "#1d1f21")))) '(org-latex-and-related ((t (:foreground "#b294bb"))))) (use-package ox-latex :after ox :config (setq org-latex-listings 'listings ;; org-latex-prefer-user-labels t ) (add-to-list 'org-latex-packages-alist '("" "listings")) (add-to-list 'org-latex-packages-alist '("" "color")) (add-to-list 'org-latex-classes '("IEEEtran" "\\documentclass[11pt]{IEEEtran}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) t)) (use-package ox-beamer :after ox) (use-package orgalist :after message :hook (message-mode . orgalist-mode)) (after! org (defvar amin-show-async-tangle-results nil "Keep *emacs* async buffers around for later inspection.") (defvar amin-show-async-tangle-time nil "Show the time spent tangling the file.") (defvar amin-async-tangle-post-compile "make ti" "If non-nil, pass to `compile' after successful tangle.") (defun amin/async-babel-tangle () "Tangle org file asynchronously." (interactive) (let* ((file-tangle-start-time (current-time)) (file (buffer-file-name)) (file-nodir (file-name-nondirectory file)) (async-quiet-switch "-q")) (async-start `(lambda () (require 'org) (org-babel-tangle-file ,file)) (unless amin-show-async-tangle-results `(lambda (result) (if result (progn (message "Tangled %s%s" ,file-nodir (if amin-show-async-tangle-time (format " (%.3fs)" (float-time (time-subtract (current-time) ',file-tangle-start-time))) "")) (when amin-async-tangle-post-compile (compile amin-async-tangle-post-compile))) (message "Tangling %s failed" ,file-nodir)))))))) (add-to-list 'safe-local-variable-values '(eval add-hook 'after-save-hook #'amin/async-babel-tangle 'append 'local)) (use-package magit :defer 1 :bind (("C-x g" . magit-status) ("s-g s" . magit-status) ("s-g l" . magit-log-buffer-file)) :config (magit-add-section-hook 'magit-status-sections-hook 'magit-insert-modules 'magit-insert-stashes 'append) (setq magit-repository-directories '(("~/.emacs.d/" . 0) ("~/src/git/" . 1))) (nconc magit-section-initial-visibility-alist '(([unpulled status] . show) ([unpushed status] . show))) :custom-face (magit-diff-file-heading ((t (:weight normal))))) (use-package ivy :defer 1 :bind (:map ivy-minibuffer-map ([escape] . keyboard-escape-quit) ([S-up] . ivy-previous-history-element) ([S-down] . ivy-next-history-element) ("DEL" . ivy-backward-delete-char)) :config (setq ivy-wrap t) (ivy-mode 1) ;; :custom-face ;; (ivy-minibuffer-match-face-2 ((t (:background "#e99ce8" :weight semi-bold)))) ;; (ivy-minibuffer-match-face-3 ((t (:background "#bbbbff" :weight semi-bold)))) ;; (ivy-minibuffer-match-face-4 ((t (:background "#ffbbff" :weight semi-bold)))) ) (use-package swiper :bind (("C-s" . swiper) ("C-r" . swiper))) (use-package counsel :defer 1 :bind (([remap execute-extended-command] . counsel-M-x) ([remap find-file] . counsel-find-file) ("s-r" . counsel-recentf) ("C-c x" . counsel-M-x) ("C-c f ." . counsel-find-file) :map minibuffer-local-map ("C-r" . counsel-minibuffer-history)) :config (counsel-mode 1) (defalias 'locate #'counsel-locate)) (use-package eshell :defer 1 :commands eshell :config (eval-when-compile (defvar eshell-prompt-regexp)) (defun amin/eshell-quit-or-delete-char (arg) (interactive "p") (if (and (eolp) (looking-back eshell-prompt-regexp nil)) (eshell-life-is-too-much) (delete-char arg))) (defun amin/eshell-clear () (interactive) (let ((inhibit-read-only t)) (erase-buffer)) (eshell-send-input)) (defun amin|eshell-setup () (make-local-variable 'company-idle-delay) (setq company-idle-delay nil) (bind-keys :map eshell-mode-map ("C-d" . amin/eshell-quit-or-delete-char) ("C-S-l" . amin/eshell-clear) ("M-r" . counsel-esh-history) ([tab] . company-complete))) :hook (eshell-mode . amin|eshell-setup) :custom (eshell-hist-ignoredups t) (eshell-input-filter 'eshell-input-filter-initial-space)) (use-package ibuffer :defer t :bind (("C-x C-b" . ibuffer-other-window) :map ibuffer-mode-map ("P" . ibuffer-backward-filter-group) ("N" . ibuffer-forward-filter-group) ("M-p" . ibuffer-do-print) ("M-n" . ibuffer-do-shell-command-pipe-replace)) :config ;; Use human readable Size column instead of original one (define-ibuffer-column size-h (:name "Size" :inline t) (cond ((> (buffer-size) 1000000) (format "%7.1fM" (/ (buffer-size) 1000000.0))) ((> (buffer-size) 100000) (format "%7.0fk" (/ (buffer-size) 1000.0))) ((> (buffer-size) 1000) (format "%7.1fk" (/ (buffer-size) 1000.0))) (t (format "%8d" (buffer-size))))) :custom (ibuffer-saved-filter-groups '(("default" ("dired" (mode . dired-mode)) ("org" (mode . org-mode)) ("web" (or (mode . web-mode) (mode . css-mode) (mode . scss-mode) (mode . js2-mode))) ("shell" (or (mode . eshell-mode) (mode . shell-mode))) ("notmuch" (name . "\*notmuch\*")) ("programming" (or (mode . python-mode) (mode . c++-mode) (mode . emacs-lisp-mode))) ("emacs" (or (name . "^\\*scratch\\*$") (name . "^\\*Messages\\*$"))) ("slack" (or (name . "^\\*Slack*")))))) (ibuffer-formats '((mark modified read-only locked " " (name 18 18 :left :elide) " " (size-h 9 -1 :right) " " (mode 16 16 :left :elide) " " filename-and-process) (mark " " (name 16 -1) " " filename))) :hook (ibuffer . (lambda () (ibuffer-switch-to-saved-filter-groups "default")))) (use-package outline :defer t :hook (prog-mode . outline-minor-mode) :bind (:map outline-minor-mode-map ("" . outline-toggle-children) ("M-p" . outline-previous-visible-heading) ("M-n" . outline-next-visible-heading) :prefix-map amin--outline-prefix-map :prefix "s-o" ("TAB" . outline-toggle-children) ("a" . outline-hide-body) ("H" . outline-hide-body) ("S" . outline-show-all) ("h" . outline-hide-subtree) ("s" . outline-show-subtree))) ;; * Borg's `layer/essentials' (use-package dash :config (dash-enable-font-lock)) (use-package diff-hl :config (setq diff-hl-draw-borders nil) (global-diff-hl-mode) (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh t)) (use-package dired :defer t :config (setq dired-listing-switches "-alh")) (use-package eldoc :when (version< "25" emacs-version) :config (global-eldoc-mode)) (use-package help :defer t :config (temp-buffer-resize-mode) (setq help-window-select t)) (progn ; `isearch' (setq isearch-allow-scroll t)) (use-package lisp-mode :config (add-hook 'emacs-lisp-mode-hook 'outline-minor-mode) (add-hook 'emacs-lisp-mode-hook 'reveal-mode) (defun indent-spaces-mode () (setq indent-tabs-mode nil)) (add-hook 'lisp-interaction-mode-hook #'indent-spaces-mode)) (use-package man :defer t :config (setq Man-width 80)) (use-package paren :config (show-paren-mode)) (use-package prog-mode :config (global-prettify-symbols-mode) (defun indicate-buffer-boundaries-left () (setq indicate-buffer-boundaries 'left)) (add-hook 'prog-mode-hook #'indicate-buffer-boundaries-left)) (use-package recentf :defer 0.5 :config (add-to-list 'recentf-exclude "^/\\(?:ssh\\|su\\|sudo\\)?:") (setq recentf-max-saved-items 40)) (use-package savehist :config (savehist-mode)) (use-package saveplace :when (version< "25" emacs-version) :config (save-place-mode)) (use-package simple :config (column-number-mode)) (progn ; `text-mode' (add-hook 'text-mode-hook #'indicate-buffer-boundaries-left) (add-hook 'text-mode-hook #'abbrev-mode)) (use-package tramp :defer t :config (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:")) (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil)) (add-to-list 'tramp-default-proxies-alist (list (regexp-quote (system-name)) nil nil))) (use-package undo-tree :config (global-undo-tree-mode -1)) ;; :bind (("C-?" . undo-tree-undo) ;; ("M-_" . undo-tree-redo)) ;; :config ;; (global-undo-tree-mode) ;; (setq undo-tree-mode-lighter "" ;; undo-tree-auto-save-history t)) ;; * Editing (use-package company :defer 1 :bind (:map company-active-map ([tab] . company-complete-common-or-cycle) ([escape] . company-abort)) :custom (company-minimum-prefix-length 1) (company-selection-wrap-around t) (company-dabbrev-char-regexp "\\sw\\|\\s_\\|[-_]") (company-dabbrev-downcase nil) (company-dabbrev-ignore-case nil) :config (global-company-mode t)) ;; * Syntax and spell checking (use-package flycheck :defer 3 :hook (prog-mode . flycheck-mode) :bind (:map flycheck-mode-map ("M-P" . flycheck-previous-error) ("M-N" . flycheck-next-error)) :config ;; Use the load-path from running Emacs when checking elisp files (setq flycheck-emacs-lisp-load-path 'inherit) ;; Only flycheck when I actually save the buffer (setq flycheck-check-syntax-automatically '(mode-enabled save))) ;; http://endlessparentheses.com/ispell-and-apostrophes.html (use-package ispell :defer 3 :config ;; ’ can be part of a word (setq ispell-local-dictionary-alist `((nil "[[:alpha:]]" "[^[:alpha:]]" "['\x2019]" nil ("-B") nil utf-8))) ;; don't send ’ to the subprocess (defun endless/replace-apostrophe (args) (cons (replace-regexp-in-string "’" "'" (car args)) (cdr args))) (advice-add #'ispell-send-string :filter-args #'endless/replace-apostrophe) ;; convert ' back to ’ from the subprocess (defun endless/replace-quote (args) (if (not (derived-mode-p 'org-mode)) args (cons (replace-regexp-in-string "'" "’" (car args)) (cdr args)))) (advice-add #'ispell-parse-output :filter-args #'endless/replace-quote)) ;; * Programming modes (use-package alloy-mode :defer t :config (setq alloy-basic-offset 2)) (use-package proof-site ; Proof General :defer t :load-path "lib/proof-site/generic/") (eval-when-compile (defvar lean-mode-map)) (use-package lean-mode :defer 1 :bind (:map lean-mode-map ("S-SPC" . company-complete)) :config (require 'lean-input) (setq default-input-method "Lean" lean-input-tweak-all '(lean-input-compose (lean-input-prepend "/") (lean-input-nonempty)) lean-input-user-translations '(("/" "/"))) (lean-input-setup)) (use-package haskell-mode :defer t :config (setq haskell-indentation-layout-offset 4 haskell-indentation-left-offset 4 flycheck-checker 'haskell-hlint flycheck-disabled-checkers '(haskell-stack-ghc haskell-ghc))) (use-package dante :after haskell-mode :commands dante-mode :hook (haskell-mode . dante-mode)) (use-package hlint-refactor :after haskell-mode :bind (:map hlint-refactor-mode-map ("C-c l b" . hlint-refactor-refactor-buffer) ("C-c l r" . hlint-refactor-refactor-at-point)) :hook (haskell-mode . hlint-refactor-mode)) (use-package flycheck-haskell :after haskell-mode) (use-package sgml-mode :defer t :config (setq sgml-basic-offset 2)) (use-package css-mode :defer t :config (setq css-indent-offset 2)) (use-package web-mode :defer t :mode "\\.html\\'" :config (setq-every! 2 web-mode-code-indent-offset web-mode-css-indent-offset web-mode-markup-indent-offset)) (use-package emmet-mode :after (:any web-mode css-mode sgml-mode) :bind* (("C-)" . emmet-next-edit-point) ("C-(" . emmet-prev-edit-point)) :config (unbind-key "C-j" emmet-mode-keymap) (setq emmet-move-cursor-between-quotes t) :hook (web-mode css-mode html-mode sgml-mode)) (use-package nix-mode :defer t :mode "\\.nix\\'") ;; * Emacs Enhancements (use-package which-key :defer 1 :config (which-key-mode)) (add-to-list 'custom-theme-load-path "~/.emacs.d/lisp") (load-theme 'tangomod t) (use-package doom-modeline :demand t :config (setq doom-modeline-height 32) :hook (after-init . doom-modeline-init)) (use-package doom-themes) (defun amin/lights-on () "Enable my favourite light theme." (interactive) (progn (mapc #'disable-theme custom-enabled-themes) (load-theme 'tangomod t))) (defun amin/lights-off () "Go dark." (interactive) (progn (mapc #'disable-theme custom-enabled-themes) (load-theme 'doom-tomorrow-night t))) (bind-keys ("s-t d" . amin/lights-off) ("s-t l" . amin/lights-on)) (use-package crux :defer 1 :bind (("C-c b k" . crux-kill-other-buffers) ("C-c d" . crux-duplicate-current-line-or-region) ("C-c D" . crux-duplicate-and-comment-current-line-or-region) ("C-c f c" . crux-copy-file-preserve-attributes) ("C-c f d" . crux-delete-file-and-buffer) ("C-c f r" . crux-rename-file-and-buffer) ("C-c j" . crux-top-join-line) ("C-S-j" . crux-top-join-line))) (use-package mwim :bind (("C-a" . mwim-beginning-of-code-or-line) ("C-e" . mwim-end-of-code-or-line) ("" . mwim-beginning-of-line-or-code) ("" . mwim-end-of-line-or-code))) (use-package projectile :defer t :bind-keymap ("C-c p" . projectile-command-map) :config (projectile-mode) (defun my-projectile-invalidate-cache (&rest _args) ;; ignore the args to `magit-checkout' (projectile-invalidate-cache nil)) (eval-after-load 'magit-branch '(progn (advice-add 'magit-checkout :after #'my-projectile-invalidate-cache) (advice-add 'magit-branch-and-checkout :after #'my-projectile-invalidate-cache)))) (use-package helpful :defer 1 :bind (("C-S-h c" . helpful-command) ("C-S-h f" . helpful-callable) ; helpful-function ("C-S-h v" . helpful-variable) ("C-S-h k" . helpful-key) ("C-S-h p" . helpful-at-point))) (use-package shell-toggle :after eshell :bind ("C-c a s e" . amin/shell-toggle) :config (defun amin/shell-toggle (make-cd) "Toggle between the shell buffer and whatever buffer you are editing. With a prefix argument MAKE-CD also insert a \"cd DIR\" command into the shell, where DIR is the directory of the current buffer. When called in the shell buffer returns you to the buffer you were editing before calling this the first time. Options: `shell-toggle-goto-eob'" (interactive "P") ;; Try to decide on one of three possibilities: ;; If not in shell-buffer, switch to it. ;; If in shell-buffer, return to state before going to the shell-buffer (if (eq (current-buffer) shell-toggle-shell-buffer) (shell-toggle-buffer-return-from-shell) (progn (shell-toggle-buffer-goto-shell make-cd) (if shell-toggle-full-screen-window-only (delete-other-windows))))) ;; override to split horizontally instead (defun shell-toggle-buffer-switch-to-other-window () "Switch to other window. If the current window is the only window in the current frame, create a new window and switch to it. \(This is less intrusive to the current window configuration than `switch-buffer-other-window')" (let ((this-window (selected-window))) (other-window 1) ;; If we did not switch window then we only have one window and need to ;; create a new one. (if (eq this-window (selected-window)) (progn (split-window-horizontally) (other-window 1))))) :custom (shell-toggle-launch-shell 'shell-toggle-eshell)) (use-package unkillable-scratch :defer 3 :config (unkillable-scratch 1) :custom (unkillable-scratch-behavior 'do-nothing) (unkillable-buffers '("^\\*scratch\\*$" "^\\*Messages\\*$"))) (use-package boxquote :defer 3 :bind (:prefix-map amin--boxquote-prefix-map :prefix "C-c q" ("b" . boxquote-buffer) ("B" . boxquote-insert-buffer) ("d" . boxquote-defun) ("F" . boxquote-insert-file) ("hf" . boxquote-describe-function) ("hk" . boxquote-describe-key) ("hv" . boxquote-describe-variable) ("hw" . boxquote-where-is) ("k" . boxquote-kill) ("p" . boxquote-paragraph) ("q" . boxquote-boxquote) ("r" . boxquote-region) ("s" . boxquote-shell-command) ("t" . boxquote-text) ("T" . boxquote-title) ("u" . boxquote-unbox) ("U" . boxquote-unbox-region) ("y" . boxquote-yank) ("M-q" . boxquote-fill-paragraph) ("M-w" . boxquote-kill-ring-save))) (use-package highlight-indent-guides :disabled t :defer 3 :hook ((prog-mode . highlight-indent-guides-mode) ;; (org-mode . highlight-indent-guides-mode) ) :config (setq highlight-indent-guides-character ?\|) (setq highlight-indent-guides-auto-enabled nil) (setq highlight-indent-guides-method 'character) (setq highlight-indent-guides-responsive 'top) (set-face-foreground 'highlight-indent-guides-character-face "gainsboro") (set-face-foreground 'highlight-indent-guides-top-character-face "grey40")) ; grey13 is nice too (use-package pdf-tools :defer t :magic ("%PDF" . pdf-view-mode) :config (setq pdf-view-resize-factor 1.05) (pdf-tools-install) :bind (:map pdf-view-mode-map ("C-s" . isearch-forward) ("C-r" . isearch-backward) ("j" . pdf-view-next-line-or-next-page) ("k" . pdf-view-previous-line-or-previous-page) ("h" . image-backward-hscroll) ("l" . image-forward-hscroll))) (use-package anzu) (use-package typo :defer 2 :config (typo-global-mode 1) :hook (text-mode . typo-mode)) (use-package hl-todo :defer 4 :config (global-hl-todo-mode)) (use-package shrink-path :after eshell :config (setq eshell-prompt-regexp "\\(.*\n\\)*λ " eshell-prompt-function #'+eshell/prompt) (defun +eshell/prompt () (let ((base/dir (shrink-path-prompt default-directory))) (concat (propertize (car base/dir) 'face 'font-lock-comment-face) (propertize (cdr base/dir) 'face 'font-lock-constant-face) (propertize (+eshell--current-git-branch) 'face 'font-lock-function-name-face) "\n" (propertize "λ" 'face 'eshell-prompt-face) ;; needed for the input text to not have prompt face (propertize " " 'face 'default)))) (defun +eshell--current-git-branch () (let ((branch (car (loop for match in (split-string (shell-command-to-string "git branch") "\n") when (string-match "^\*" match) collect match)))) (if (not (eq branch nil)) (concat " " (substring branch 2)) "")))) (use-package slack :disabled t :commands (slack-start) :init (eval-when-compile ; silence the byte-compiler (defvar url-http-data nil) (defvar url-http-extra-headers nil) (defvar url-http-method nil) (defvar url-callback-function nil) (defvar url-callback-arguments nil) (defvar oauth--token-data nil)) (setq slack-buffer-emojify t slack-prefer-current-team t) :config (slack-register-team :name "uw-apv" :default t :client-id uw-apv-client-id :client-secret uw-apv-client-secret :token uw-apv-token :subscribed-channels '(general) :full-and-display-names t) (slack-register-team :name "watform" :default nil :client-id watform-client-id :client-secret watform-client-secret :token watform-token :subscribed-channels '(general) :full-and-display-names t) (add-to-list 'swiper-font-lock-exclude 'slack-message-buffer-mode t) (setq lui-time-stamp-format "[%Y-%m-%d %H:%M:%S]" lui-time-stamp-only-when-changed-p t lui-time-stamp-position 'right) :bind (("C-c s s" . slack-start) ("C-c s u" . slack-select-unread-rooms) ("C-c s b" . slack-select-rooms) ("C-c s t" . slack-change-current-team) ("C-c s c" . slack-ws-close) :map slack-mode-map ("M-p" . slack-buffer-goto-prev-message) ("M-n" . slack-buffer-goto-next-message) ("C-c e" . slack-message-edit) ("C-c k" . slack-message-delete) ("C-c C-k" . slack-channel-leave) ("C-c r a" . slack-message-add-reaction) ("C-c r r" . slack-message-remove-reaction) ("C-c r s" . slack-message-show-reaction-users) ("C-c p l" . slack-room-pins-list) ("C-c p a" . slack-message-pins-add) ("C-c p r" . slack-message-pins-remove) ("@" . slack-message-embed-mention) ("#" . slack-message-embed-channel))) (use-package alert :commands (alert) :init (setq alert-default-style 'notifier)) (use-package eshell-up :after eshell) (use-package multi-term :defer 1 :bind (("C-c C-j" . term-line-mode) ("C-c a s m m" . multi-term) ("C-c a s m p" . multi-term-dedicated-toggle)) :config (setq multi-term-program "/bin/screen" ;; TODO: add separate bindings for connecting to existing ;; session vs. always creating a new one multi-term-dedicated-select-after-open-p t multi-term-dedicated-window-height 20 multi-term-dedicated-max-window-height 30 term-bind-key-alist '(("C-c C-c" . term-interrupt-subjob) ("C-c C-e" . term-send-esc) ("C-k" . kill-line) ("C-y" . term-paste) ("M-f" . term-send-forward-word) ("M-b" . term-send-backward-word) ("M-p" . term-send-up) ("M-n" . term-send-down) ("" . term-send-backward-kill-word) ("" . term-send-backward-kill-word) ("M-d" . term-send-delete-word) ("M-," . term-send-raw) ("M-." . comint-dynamic-complete)) term-unbind-key-alist '("C-z" "C-x" "C-c" "C-h" "C-y" ""))) (use-package page-break-lines :config (global-page-break-lines-mode)) ;; * Email (defvar amin-maildir (expand-file-name "~/mail/")) (after! recentf (add-to-list 'recentf-exclude amin-maildir)) (setq amin-gnus-init-file (no-littering-expand-etc-file-name "gnus") mail-user-agent 'gnus-user-agent read-mail-command 'gnus) (use-package gnus :bind (("s-m" . gnus) ("s-M" . gnus-unplugged)) :init (setq gnus-select-method '(nnnil "") gnus-secondary-select-methods '((nnimap "amin" (nnimap-stream plain) (nnimap-address "127.0.0.1") (nnimap-server-port 143) (nnimap-authenticator plain) (nnimap-user "amin@aminb.org")) (nnimap "uwaterloo" (nnimap-stream plain) (nnimap-address "127.0.0.1") (nnimap-server-port 143) (nnimap-authenticator plain) (nnimap-user "abandali@uwaterloo.ca"))) gnus-message-archive-group "nnimap+amin:Sent" gnus-parameters '(("gnu.*" (gcc-self . t))) gnus-large-newsgroup 50 gnus-home-directory (no-littering-expand-var-file-name "gnus/") gnus-directory (concat gnus-home-directory "news/") message-directory (concat gnus-home-directory "mail/") nndraft-directory (concat gnus-home-directory "drafts/") gnus-save-newsrc-file nil gnus-read-newsrc-file nil gnus-interactive-exit nil gnus-gcc-mark-as-read t)) (use-package gnus-art :config (setq gnus-visible-headers (concat gnus-visible-headers "\\|^List-Id:\\|^X-RT-Originator:\\|^User-Agent:") gnus-sorted-header-list '("^From:" "^Subject:" "^Summary:" "^Keywords:" "^Followup-To:" "^To:" "^Cc:" "X-RT-Originator" "^Newsgroups:" "List-Id:" "^Organization:" "^User-Agent:" "^Date:") ;; local-lapsed article dates ;; from https://www.emacswiki.org/emacs/GnusFormatting#toc11 gnus-article-date-headers '(user-defined) gnus-article-time-format (lambda (time) (let* ((date (format-time-string "%a, %d %b %Y %T %z" time)) (local (article-make-date-line date 'local)) (combined-lapsed (article-make-date-line date 'combined-lapsed)) (lapsed (progn (string-match " (.+" combined-lapsed) (match-string 0 combined-lapsed)))) (concat local lapsed)))) (bind-keys :map gnus-article-mode-map ("r" . gnus-article-reply-with-original) ("R" . gnus-article-wide-reply-with-original) ("M-L" . org-store-link))) (use-package gnus-sum :bind (:map gnus-summary-mode-map :prefix-map amin--gnus-summary-prefix-map :prefix "v" ("r" . gnus-summary-reply) ("w" . gnus-summary-wide-reply) ("v" . gnus-summary-show-raw-article)) :config (bind-keys :map gnus-summary-mode-map ("r" . gnus-summary-reply-with-original) ("R" . gnus-summary-wide-reply-with-original) ("M-L" . org-store-link)) :hook (gnus-summary-mode . amin--no-mouse-autoselect-window)) (use-package gnus-msg :config (setq gnus-posting-styles '((".*" (address "amin@aminb.org") (body "\nBest,\namin\n") (eval (setq amin--message-cite-say-hi t))) ("gnu.*" (address "bandali@gnu.org")) ((header "subject" "ThankCRM") (to "webmasters-comment@gnu.org") (body "\nAdded to 2018supporters.html.\n\nMoving to campaigns.\n\n-amin\n") (eval (setq amin--message-cite-say-hi nil))) ("nnimap\\+uwaterloo:.*" (address "abandali@uwaterloo.ca") (gcc "\"nnimap+uwaterloo:Sent Items\""))))) (use-package gnus-topic :hook (gnus-group-mode . gnus-topic-mode)) (use-package gnus-agent :config (setq gnus-agent-synchronize-flags 'ask) :hook (gnus-group-mode . gnus-agent-mode)) (use-package gnus-group :config (setq gnus-permanently-visible-groups "\\((INBOX\\|gnu$\\)")) (use-package mm-decode :config (setq mm-discouraged-alternatives '("text/html" "text/richtext"))) (use-package sendmail :config (setq sendmail-program "/usr/bin/msmtp" ;; message-sendmail-extra-arguments '("-v" "-d") mail-specify-envelope-from t mail-envelope-from 'header)) (use-package message :config (defconst amin--message-cite-style-format "On %Y-%m-%d %l:%M %p, %N wrote:") (defconst message-cite-style-bandali '((message-cite-function 'message-cite-original) (message-citation-line-function 'message-insert-formatted-citation-line) (message-cite-reply-position 'traditional) (message-yank-prefix "> ") (message-yank-cited-prefix ">") (message-yank-empty-prefix ">") (message-citation-line-format (if amin--message-cite-say-hi (concat "Hi %F,\n\n" amin--message-cite-style-format) amin--message-cite-style-format))) "Citation style based on Mozilla Thunderbird's. Use with message-cite-style.") (setq message-cite-style 'message-cite-style-bandali message-kill-buffer-on-exit t message-send-mail-function 'message-send-mail-with-sendmail message-sendmail-envelope-from 'header message-dont-reply-to-names "\\(\\(.*@aminb\\.org\\)\\|\\(amin@bandali\\.me\\)\\|\\(\\(aminb?\\|mab\\|bandali\\)@gnu\\.org\\)\\|\\(\\(m\\|a\\(min\\.\\)?\\)bandali@uwaterloo\\.ca\\)\\)" message-user-fqdn "aminb.org") :hook (;; (message-setup . mml-secure-message-sign-pgpmime) (message-mode . flyspell-mode) (message-mode . (lambda () ;; (setq fill-column 65 ;; message-fill-column 65) (make-local-variable 'company-idle-delay) (setq company-idle-delay 0.2)))) ;; :custom-face ;; (message-header-subject ((t (:foreground "#111" :weight semi-bold)))) ;; (message-header-to ((t (:foreground "#111" :weight normal)))) ;; (message-header-cc ((t (:foreground "#333" :weight normal)))) ) (after! mml-sec (setq mml-secure-openpgp-encrypt-to-self t mml-secure-openpgp-sign-with-sender t)) (use-package footnote :after message :bind (:map message-mode-map :prefix-map amin--footnote-prefix-map :prefix "C-c f" ("a" . footnote-add-footnote) ("b" . footnote-back-to-message) ("c" . footnote-cycle-style) ("d" . footnote-delete-footnote) ("g" . footnote-goto-footnote) ("r" . footnote-renumber-footnotes) ("s" . footnote-set-style)) :config (setq footnote-start-tag "" footnote-end-tag "" footnote-style 'unicode)) (use-package bbdb :load-path "lisp/bbdb" :init (load (expand-file-name "lisp/bbdb/bbdb-autoloads.el" user-emacs-directory)) ;; (bbdb-mua-auto-update-init 'message) (setq bbdb-mua-auto-update-p 'query bbdb-complete-mail nil) (bbdb-initialize 'gnus 'message)) (use-package message-x :disabled t :custom (message-x-completion-alist (quote (("\\([rR]esent-\\|[rR]eply-\\)?[tT]o:\\|[bB]?[cC][cC]:" . gnus-harvest-find-address) ((if (boundp (quote message-newgroups-header-regexp)) message-newgroups-header-regexp message-newsgroups-header-regexp) . message-expand-group))))) (use-package gnus-harvest :disabled t :commands gnus-harvest-install :demand t :config (if (featurep 'message-x) (gnus-harvest-install 'message-x) (gnus-harvest-install))) ;; * Blogging (use-package ox-hugo :after ox) (use-package ox-hugo-auto-export :load-path "lib/ox-hugo") ;; * Post initialization (message "Loading %s...done (%.3fs)" user-init-file (float-time (time-subtract (current-time) amin--before-user-init-time))) ;;; init.el ends here