X-Git-Url: https://git.shemshak.org/gitweb.cgi/~bandali/configs/blobdiff_plain/4ae2cd629b9f16cca4960b9e6257fc841c5f82d0..6f4c133d3c97853e260b2ef6e3d17e95089ea1ed:/init.org diff --git a/init.org b/init.org index e5149a3..c7b2c83 100644 --- a/init.org +++ b/init.org @@ -9,9 +9,21 @@ :END: This org file is my literate configuration for GNU Emacs, and is -tangled to [[./init.el][init.el]]. Packages are installed and managed using [[https://github.com/emacscollective/borg][Borg]]. - -** Installation +tangled to [[./init.el][init.el]]. Packages are installed and managed using +[[https://github.com/emacscollective/borg][Borg]]. Over the years, I've taken inspiration from configurations of +many different people. Some of the configurations that I can remember +off the top of my head are: + +- [[https://github.com/dieggsy/dotfiles][dieggsy/dotfiles]]: literate Emacs and dotfiles configuration, uses + straight.el for managing packages +- [[https://github.com/dakra/dmacs][dakra/dmacs]]: literate Emacs configuration, using Borg for managing + packages +- [[http://pages.sachachua.com/.emacs.d/Sacha.html][Sacha Chua's literate Emacs configuration]] +- [[https://github.com/dakrone/eos][dakrone/eos]] +- Ryan Rix's [[http://doc.rix.si/cce/cce.html][Complete Computing Environment]] ([[http://doc.rix.si/projects/fsem.html][about cce]]) +- [[https://github.com/jwiegley/dot-emacs][jwiegley/dot-emacs]]: nix-based configuration +- [[https://github.com/wasamasa/dotemacs][wasamasa/dotemacs]] +- [[https://github.com/hlissner/doom-emacs][Doom Emacs]] I'd like to have a fully reproducible Emacs setup (part of the reason why I store my configuration in this repository) but unfortunately out @@ -22,15 +34,16 @@ to my init time; which is unacceptable for me: I use Emacs as my window manager (via EXWM) and coming from bspwm, I'm too used to having fast startup times. +** Installation + To use this config for your Emacs, first you need to clone this repo, then bootstrap Borg, tell Borg to retrieve package submodules, and byte-compiled the packages. Something along these lines should work: -#+begin_src sh +#+begin_src sh :tangle no git clone https://github.com/aminb/dotfiles ~/.emacs.d cd ~/.emacs.d make bootstrap-borg -make tangle-init make bootstrap make build #+end_src @@ -92,20 +105,19 @@ file. ** Naming conventions -The conventions below were inspired by [[https://github.com/hlissner/doom-emacs][Doom]]'s conventions, found -[[https://github.com/hlissner/doom-emacs/blob/5dacbb7cb1c6ac246a9ccd15e6c4290def67757c/core/core.el#L3-L17][here]]. Naturally, I use my initials, =ab=, instead of =doom=. +The conventions below were inspired by [[https://github.com/hlissner/doom-emacs][Doom]]'s, found [[https://github.com/hlissner/doom-emacs/blob/5dacbb7cb1c6ac246a9ccd15e6c4290def67757c/core/core.el#L3-L17][here]]. #+begin_src emacs-lisp :comments none ;; Naming conventions: ;; -;; ab-... public variables or non-interactive functions -;; ab--... private anything (non-interactive), not safe for direct use -;; ab/... an interactive function; safe for M-x or keybinding -;; ab:... an evil operator, motion, or command -;; ab|... a hook function -;; ab*... an advising function -;; ab@... a hydra command -;; ...! a macro +;; 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:... an evil operator, motion, or command +;; amin|... a hook function +;; amin*... an advising function +;; amin@... a hydra command +;; ...! a macro #+end_src * Initial setup @@ -124,10 +136,10 @@ let's see how long Emacs takes to start up, before even loading =init.el=, i.e. =user-init-file=: #+begin_src emacs-lisp -(defvar ab--before-user-init-time (current-time) +(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 ab--before-user-init-time + (float-time (time-subtract amin--before-user-init-time before-init-time))) #+end_src @@ -137,9 +149,9 @@ frequency. Clearing the ~file-name-handler-alist~ seems to help reduce startup time as well. #+begin_src emacs-lisp -(defvar ab--gc-cons-threshold gc-cons-threshold) -(defvar ab--gc-cons-percentage gc-cons-percentage) -(defvar ab--file-name-handler-alist file-name-handler-alist) +(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 @@ -154,13 +166,9 @@ done initializing. (add-hook 'after-init-hook (lambda () - (let ((elapsed (float-time (time-subtract (current-time) - ab--before-user-init-time)))) - (message "Loading %s...done (%.3fs) [after-init]" - user-init-file elapsed)) - (setq gc-cons-threshold ab--gc-cons-threshold - gc-cons-percentage ab--gc-cons-percentage - file-name-handler-alist ab--file-name-handler-alist))) + (setq gc-cons-threshold amin--gc-cons-threshold + gc-cons-percentage amin--gc-cons-percentage + file-name-handler-alist amin--file-name-handler-alist))) #+end_src Increase the number of lines kept in message logs (the =*Messages*= @@ -179,6 +187,13 @@ but for now I've decided to keep them enabled. See documentation for ;; '(not free-vars unresolved noruntime lexical make-local)) #+end_src +** whoami + +#+begin_src emacs-lisp +(setq user-full-name "Amin Bandali" + user-mail-address "amin@aminb.org") +#+end_src + ** Package management *** No =package.el= @@ -232,7 +247,7 @@ and without compromising on performance. #+begin_src emacs-lisp (require 'use-package) -(if nil ; set to t when need to debug init +(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 @@ -310,6 +325,14 @@ in my shell. (exec-path-from-shell-copy-env "SSH_AUTH_SOCK")) #+end_src +** Only one custom theme at a time + +#+begin_src emacs-lisp +(defadvice load-theme (before clear-previous-themes activate) + "Clear existing theme settings instead of layering them" + (mapc #'disable-theme custom-enabled-themes)) +#+end_src + ** Server Start server if not already running. Alternatively, can be done by @@ -325,6 +348,86 @@ See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.htm :config (or (server-running-p) (server-mode))) #+end_src +** Unicode support + +Font stack with better unicode support, around =Ubuntu Mono= and +=Hack=. + +#+begin_src emacs-lisp +(dolist (ft (fontset-list)) + (set-fontset-font + ft + 'unicode + (font-spec :name "Ubuntu Mono")) + (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)) +#+end_src + +** Libraries + +#+begin_src emacs-lisp +(require 'cl-lib) +(require 'subr-x) +#+end_src + +** Useful utilities + +#+begin_src emacs-lisp +(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))))) +#+end_src + * Core :PROPERTIES: :CUSTOM_ID: core @@ -332,6 +435,28 @@ See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.htm ** Defaults +*** Time and battery in mode-line + +Enable displaying time and battery in the mode-line, since I'm not +using the Xfce panel anymore. Also, I don't need to see the load +average on a regular basis, so disable that. + +Note: using =i3status= on sway at the moment, so disabling this. + +#+begin_src emacs-lisp :tangle no +(use-package time + :ensure nil + :init + (setq display-time-default-load-average nil) + :config + (display-time-mode)) + +(use-package battery + :ensure nil + :config + (display-battery-mode)) +#+end_src + *** Smaller fringe Might want to set the fringe to a smaller value, especially if using @@ -339,6 +464,7 @@ EXWM. I'm fine with the default for now. #+begin_src emacs-lisp ;; (fringe-mode '(3 . 1)) +(fringe-mode nil) #+end_src *** Disable disabled commands @@ -440,6 +566,25 @@ variable. version-control t) #+end_src +*** Auto revert + +Enable automatic reloading of changed buffers and files. + +#+begin_src emacs-lisp +(global-auto-revert-mode 1) +(setq auto-revert-verbose nil + global-auto-revert-non-file-buffers t) +#+end_src + +*** Always use space for indentation + +#+begin_src emacs-lisp +(setq-default + indent-tabs-mode nil + require-final-newline t + tab-width 4) +#+end_src + ** Packages The packages in this section are absolutely essential to my everyday @@ -456,32 +601,145 @@ customizing it. (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) + 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)) #+end_src -*** TODO [[https://github.com/Kungsgeten/ryo-modal][ryo-modal]] +*** [[https://github.com/noctuid/general.el][general]] -#+begin_quote -Roll your own modal mode -#+end_quote + #+begin_src emacs-lisp +(use-package general + :demand t + :config + (general-evil-setup t) + + (general-override-mode) + + (general-create-definer + amin--mode-leader-keys + :keymaps 'override + :states '(emacs normal visual motion insert) + :non-normal-prefix "C-," + :prefix ",") + + (general-create-definer + amin--leader-keys + :keymaps 'override + :states '(emacs normal visual motion insert) + :non-normal-prefix "M-m" + :prefix "SPC")) +#+end_src -*** [[https://github.com/ch11ng/exwm][EXWM]] (window manager) +*** evil #+begin_src emacs-lisp -(use-package exwm +(use-package evil :demand t + :hook (view-mode . evil-motion-state) + :init + (setq evil-want-integration nil) + :config (evil-mode 1) + (general-swap-key nil '(normal motion) ";" ":") + (setq evil-want-visual-char-semi-exclusive t)) +#+end_src + +#+begin_src emacs-lisp +(use-package evil-collection + :after evil + :config + (evil-collection-init)) +#+end_src + +#+begin_src emacs-lisp +(use-package evil-escape + :demand t + :init + (setq evil-escape-excluded-states '(normal visual multiedit emacs motion) + evil-escape-excluded-major-modes '(neotree-mode) + evil-escape-key-sequence "jk" + evil-escape-delay 0.25) + :general + (:states '(insert replace visual operator) + "C-g" #'evil-escape) + :config + (evil-escape-mode 1) + ;; no `evil-escape' in minibuffer + (push #'minibufferp evil-escape-inhibit-functions)) +#+end_src + +#+begin_src emacs-lisp +(use-package evil-magit + :after magit + :config + (evil-set-initial-state 'git-commit-mode 'insert)) +#+end_src + +#+begin_src emacs-lisp +(use-package evil-org + :hook (org-mode . evil-org-mode) + :general + (nvmap evil-org-mode-map + "TAB" 'org-cycle + "S-TAB" 'org-cycle) :config - (require 'exwm-config) - (exwm-config-default) - (require 'exwm-systemtray) - (exwm-systemtray-enable) - (require 'exwm-randr) - (exwm-randr-enable)) + (evil-org-set-key-theme) + (require 'evil-org-agenda) + (evil-org-agenda-set-keys) + (nmap evil-org-mode-map + [backtab] 'org-shifttab) + (after! org-src + (define-key org-src-mode-map [remap evil-write] 'org-edit-src-save) + (define-key org-src-mode-map [remap evil-save-and-close] + (lambda () (interactive) + (org-edit-src-save) + (org-edit-src-exit))) + (define-key org-src-mode-map [remap evil-save-modified-and-close] + (lambda () (interactive) + (org-edit-src-save) + (org-edit-src-exit))))) +#+end_src + +#+begin_src emacs-lisp +(amin--leader-keys + "/" '(:ignore t :wk "search") + + "B" '(:ignore t :wk "Borg") + "BA" 'borg-activate + "Ba" 'borg-assimilate + "Bb" 'borg-build + "Bc" 'borg-clone + "Bd" 'epkg-describe-package + "Bp" 'epkg-list-packages + "Br" 'borg-remove + "Bu" 'epkg-update + + "b" '(:ignore t :wk "buffers") + "bb" 'ibuffer-list-buffers + "bk" 'kill-this-buffer + "bs" 'save-buffer + + "f" '(:ignore t :wk "files") + + "h" '(:ignore t :wk "help") + "hc" 'describe-char + "hf" 'describe-function + "hF" 'describe-face + "hi" 'info + "hk" 'describe-key + "hl" 'view-lossage + "hv" 'describe-variable + + "o" 'other-window + + "w" '(:ignore t :wk "window") + "wo" 'other-window + + "q" '(:ignore t :wk "quit") + "qq" 'save-buffers-kill-terminal) #+end_src *** [[https://orgmode.org/][Org mode]] @@ -495,9 +753,18 @@ system. In short, my favourite way of life. #+begin_src emacs-lisp -(setq org-src-tab-acts-natively t - org-src-preserve-indentation nil - org-edit-src-content-indentation 0) +(use-package org + :general + (amin--leader-keys "bt" 'org-babel-tangle) + (amin--leader-keys org-mode-map "i" 'org-edit-special) + :config + (setq org-src-tab-acts-natively t + org-src-preserve-indentation nil + org-edit-src-content-indentation 0) + :hook (org-mode . org-indent-mode)) + +(use-package org-notmuch + :after (:any org notmuch)) #+end_src *** [[https://magit.vc/][Magit]] @@ -510,9 +777,24 @@ Not just how I do git, but /the/ way to do git. #+begin_src emacs-lisp (use-package magit + :general + (amin--leader-keys + "g" '(:ignore t :wk "magit") + "g SPC" 'magit-status + "gs" 'magit-status + "gS" 'magit-status-prefix + "gB" 'magit-blame + "gC" 'magit-clone + "gf" 'magit-fetch + "gF" 'magit-pull + "gP" 'magit-push + "gcc" 'magit-commit + "gca" 'magit-commit-amend + "gbb" 'magit-checkout + "gbc" 'magit-branch) :defer t :bind (("s-g" . magit-status) - ("C-x g" . magit-status) + ("C-x g" . magit-status) ("C-x M-g" . magit-dispatch-popup)) :config (magit-add-section-hook 'magit-status-sections-hook @@ -534,15 +816,18 @@ There's no way I could top that, so I won't attempt to. #+begin_src emacs-lisp (use-package ivy + :defer 1 :bind (:map ivy-minibuffer-map ([escape] . keyboard-escape-quit) - ("C-j" . ivy-next-line) - ("C-k" . ivy-previous-line) + ;; ("C-j" . ivy-next-line) + ;; ("C-k" . ivy-previous-line) ([S-up] . ivy-previous-history-element) ([S-down] . ivy-next-history-element) ("DEL" . ivy-backward-delete-char)) + :general (amin--leader-keys "," 'ivy-switch-buffer) :config + (setq ivy-wrap t) (ivy-mode 1)) #+end_src @@ -550,20 +835,27 @@ There's no way I could top that, so I won't attempt to. #+begin_src emacs-lisp (use-package swiper + :general (:states '(normal motion) "/" 'swiper) + (amin--leader-keys "//" 'swiper) :bind (([remap isearch-forward] . swiper) - ([remap isearch-backward] . swiper))) + ([remap isearch-backward] . swiper))) #+end_src **** Counsel #+begin_src emacs-lisp (use-package counsel - :defer 1.5 + :defer 1 + :general + (amin--leader-keys + "fr" 'counsel-recentf + "SPC" 'counsel-M-x + "." 'counsel-find-file) :bind (([remap execute-extended-command] . counsel-M-x) ([remap find-file] . counsel-find-file) ("s-r" . counsel-recentf) :map minibuffer-local-map - ("C-r" . counsel-minibuffer-history)) + ("C-r" . counsel-minibuffer-history)) :config (counsel-mode 1) (defalias 'locate #'counsel-locate)) @@ -645,9 +937,530 @@ TODO: break this giant source block down into individual org sections. (list (regexp-quote (system-name)) nil nil))) (use-package undo-tree + :bind (("C-?" . undo-tree-undo) + ("M-_" . undo-tree-redo)) :config (global-undo-tree-mode) - (setq undo-tree-mode-lighter "")) + (setq undo-tree-mode-lighter "" + undo-tree-auto-save-history t)) +#+end_src + +* Editing + +** Company + +#+begin_src emacs-lisp +(use-package company + :defer 5 + :bind + (:map company-active-map + ([tab] . company-complete-common-or-cycle)) + :custom + (company-idle-delay 0.3) + (company-minimum-prefix-length 1) + (company-selection-wrap-around t) + (company-dabbrev-char-regexp "\\sw\\|\\s_\\|[-_]") + :config + (global-company-mode t)) +#+end_src + +* Syntax and spell checking +#+begin_src emacs-lisp +(use-package flycheck + :hook (prog-mode . flycheck-mode) + :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))) +#+end_src +* Programming modes + +** [[http://alloytools.org][Alloy]] (with [[https://github.com/dwwmmn/alloy-mode][alloy-mode]]) + +#+begin_src emacs-lisp +(use-package alloy-mode + :config (setq alloy-basic-offset 2)) +#+end_src + +** [[https://coq.inria.fr][Coq]] (with [[https://github.com/ProofGeneral/PG][Proof General]]) + +#+begin_src emacs-lisp +(use-package proof-site ; Proof General + :load-path "lib/proof-site/generic/") +#+end_src + +** [[https://leanprover.github.io][Lean]] (with [[https://github.com/leanprover/lean-mode][lean-mode]]) + +#+begin_src emacs-lisp +(use-package lean-mode + :bind (:map lean-mode-map + ("S-SPC" . company-complete))) +#+end_src + +** Haskell + +*** [[https://github.com/haskell/haskell-mode][haskell-mode]] + +#+begin_src emacs-lisp +(use-package haskell-mode + :config + (setq haskell-indentation-layout-offset 4 + haskell-indentation-left-offset 4 + flycheck-checker 'haskell-hlint + flycheck-disabled-checkers '(haskell-stack-ghc haskell-ghc))) +#+end_src + +*** [[https://github.com/jyp/dante][dante]] + +#+begin_src emacs-lisp +(use-package dante + :after haskell-mode + :commands dante-mode + :hook (haskell-mode . dante-mode)) +#+end_src + +*** [[https://github.com/mpickering/hlint-refactor-mode][hlint-refactor]] + +Emacs bindings for [[https://github.com/ndmitchell/hlint][hlint]]'s refactor option. This requires the refact +executable from [[https://github.com/mpickering/apply-refact][apply-refact]]. + +#+begin_src emacs-lisp +(use-package hlint-refactor + :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)) +#+end_src + +*** [[https://github.com/flycheck/flycheck-haskell][flycheck-haskell]] + +#+begin_src emacs-lisp +(use-package flycheck-haskell) +#+end_src + +*** [[https://github.com/ndmitchell/hlint/blob/20e116a043f2073c57b17b24ae6364b5e433ba7e/data/hs-lint.el][hs-lint.el]] +:PROPERTIES: +:header-args+: :tangle lisp/hs-lint.el :mkdirp yes +:END: + +Currently using =flycheck-haskell= with the =haskell-hlint= checker +instead. + +#+begin_src emacs-lisp :tangle no +;;; hs-lint.el --- minor mode for HLint code checking + +;; Copyright 2009 (C) Alex Ott +;; +;; Author: Alex Ott +;; Keywords: haskell, lint, HLint +;; Requirements: +;; Status: distributed under terms of GPL2 or above + +;; Typical message from HLint looks like: +;; +;; /Users/ott/projects/lang-exp/haskell/test.hs:52:1: Eta reduce +;; Found: +;; count1 p l = length (filter p l) +;; Why not: +;; count1 p = length . filter p + + +(require 'compile) + +(defgroup hs-lint nil + "Run HLint as inferior of Emacs, parse error messages." + :group 'tools + :group 'haskell) + +(defcustom hs-lint-command "hlint" + "The default hs-lint command for \\[hlint]." + :type 'string + :group 'hs-lint) + +(defcustom hs-lint-save-files t + "Save modified files when run HLint or no (ask user)" + :type 'boolean + :group 'hs-lint) + +(defcustom hs-lint-replace-with-suggestions nil + "Replace user's code with suggested replacements" + :type 'boolean + :group 'hs-lint) + +(defcustom hs-lint-replace-without-ask nil + "Replace user's code with suggested replacements automatically" + :type 'boolean + :group 'hs-lint) + +(defun hs-lint-process-setup () + "Setup compilation variables and buffer for `hlint'." + (run-hooks 'hs-lint-setup-hook)) + +;; regex for replace suggestions +;; +;; ^\(.*?\):\([0-9]+\):\([0-9]+\): .* +;; Found: +;; \s +\(.*\) +;; Why not: +;; \s +\(.*\) + +(defvar hs-lint-regex + "^\\(.*?\\):\\([0-9]+\\):\\([0-9]+\\): .*[\n\C-m]Found:[\n\C-m]\\s +\\(.*\\)[\n\C-m]Why not:[\n\C-m]\\s +\\(.*\\)[\n\C-m]" + "Regex for HLint messages") + +(defun make-short-string (str maxlen) + (if (< (length str) maxlen) + str + (concat (substring str 0 (- maxlen 3)) "..."))) + +(defun hs-lint-replace-suggestions () + "Perform actual replacement of suggestions" + (goto-char (point-min)) + (while (re-search-forward hs-lint-regex nil t) + (let* ((fname (match-string 1)) + (fline (string-to-number (match-string 2))) + (old-code (match-string 4)) + (new-code (match-string 5)) + (msg (concat "Replace '" (make-short-string old-code 30) + "' with '" (make-short-string new-code 30) "'")) + (bline 0) + (eline 0) + (spos 0) + (new-old-code "")) + (save-excursion + (switch-to-buffer (get-file-buffer fname)) + (goto-char (point-min)) + (forward-line (1- fline)) + (beginning-of-line) + (setf bline (point)) + (when (or hs-lint-replace-without-ask + (yes-or-no-p msg)) + (end-of-line) + (setf eline (point)) + (beginning-of-line) + (setf old-code (regexp-quote old-code)) + (while (string-match "\\\\ " old-code spos) + (setf new-old-code (concat new-old-code + (substring old-code spos (match-beginning 0)) + "\\ *")) + (setf spos (match-end 0))) + (setf new-old-code (concat new-old-code (substring old-code spos))) + (remove-text-properties bline eline '(composition nil)) + (when (re-search-forward new-old-code eline t) + (replace-match new-code nil t))))))) + +(defun hs-lint-finish-hook (buf msg) + "Function, that is executed at the end of HLint execution" + (if hs-lint-replace-with-suggestions + (hs-lint-replace-suggestions) + (next-error 1 t))) + +(define-compilation-mode hs-lint-mode "HLint" + "Mode for check Haskell source code." + (set (make-local-variable 'compilation-process-setup-function) + 'hs-lint-process-setup) + (set (make-local-variable 'compilation-disable-input) t) + (set (make-local-variable 'compilation-scroll-output) nil) + (set (make-local-variable 'compilation-finish-functions) + (list 'hs-lint-finish-hook)) + ) + +(defun hs-lint () + "Run HLint for current buffer with haskell source" + (interactive) + (save-some-buffers hs-lint-save-files) + (compilation-start (concat hs-lint-command " \"" buffer-file-name "\"") + 'hs-lint-mode)) + +(provide 'hs-lint) +;;; hs-lint.el ends here +#+end_src + +#+begin_src emacs-lisp :tangle no +(use-package hs-lint + :load-path "lisp/" + :bind (:map haskell-mode-map + ("C-c l l" . hs-lint))) +#+end_src +* Emacs Enhancements + +** [[https://github.com/justbur/emacs-which-key][which-key]] + +#+begin_quote +Emacs package that displays available keybindings in popup +#+end_quote + +#+begin_src emacs-lisp +(use-package which-key + :defer 1 + :config (which-key-mode)) +#+end_src + +** [[https://github.com/seagle0128/doom-modeline][doom-modeline]] + +#+begin_src emacs-lisp +(use-package doom-modeline + :demand t + :config (setq doom-modeline-height 32) + :hook (after-init . doom-modeline-init)) +#+end_src + +** [[https://github.com/11111000000/tao-theme-emacs][tao-theme]] + +#+begin_src emacs-lisp :tangle no +(use-package tao-theme + :demand t + :config (load-theme 'tao-yang t)) +#+end_src + +** [[https://github.com/maio/eink-emacs][eink-theme]] + +#+begin_src emacs-lisp +(load-theme 'eink t) +#+end_src + +** [[https://github.com/bbatsov/crux][crux]] + +#+begin_src emacs-lisp +(use-package crux + :bind (("C-c d" . crux-duplicate-current-line-or-region) + ("C-c M-d" . crux-duplicate-and-comment-current-line-or-region)) + :general + (amin--leader-keys + "bK" 'crux-kill-other-buffers + "fc" 'crux-copy-file-preserve-attributes + "fD" 'crux-delete-file-and-buffer + "fR" 'crux-rename-file-and-buffer)) +#+end_src + +** [[https://github.com/alezost/mwim.el][mwim]] + +#+begin_src emacs-lisp +(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)) + :general (:states '(normal visual) + "0" 'mwim-beginning-of-code-or-line + "$" 'mwim-end-of-code-or-line)) +#+end_src + +* Email +** [[https://notmuchmail.org][notmuch]] + +See [[notmuch:id:87muuqsvci.fsf@fencepost.gnu.org][bug follow-up]]. + +#+begin_src emacs-lisp +(defvar amin-maildir "~/mail") + +(use-package sendmail + ;; :ensure nil + :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 + ;; :ensure nil + :config + (setq message-kill-buffer-on-exit t + message-send-mail-function 'message-send-mail-with-sendmail + message-sendmail-envelope-from 'header + message-directory "drafts" + message-user-fqdn "aminb.org") + (add-hook 'message-mode-hook + (lambda () (setq fill-column 65 + message-fill-column 65))) + (add-hook 'message-mode-hook + #'flyspell-mode) + ;; (add-hook 'notmuch-message-mode-hook #'+doom-modeline|set-special-modeline) + ;; TODO: is there a way to only run this when replying and not composing? + ;; (add-hook 'notmuch-message-mode-hook + ;; (lambda () (progn + ;; (newline) + ;; (newline) + ;; (forward-line -1) + ;; (forward-line -1)))) + ;; (add-hook 'message-setup-hook + ;; #'mml-secure-message-sign-pgpmime) + ) + +(after! mml-sec + (setq mml-secure-openpgp-encrypt-to-self t + mml-secure-openpgp-sign-with-sender t)) + +(defun amin/notmuch () + "Delete other windows, then launch `notmuch'." + (interactive) + (delete-other-windows) + (notmuch)) + +(use-package notmuch + :general (amin--leader-keys "m" 'amin/notmuch) + :commands notmuch + :config + (setq notmuch-hello-sections + '(notmuch-hello-insert-header + notmuch-hello-insert-saved-searches + ;; notmuch-hello-insert-search + notmuch-hello-insert-alltags) + notmuch-search-oldest-first nil + notmuch-show-all-tags-list t + notmuch-message-headers ; see bug follow-up above + '("Subject" "To" "Cc" "Date" "List-Id" "X-RT-Originator") + notmuch-hello-thousands-separator "," + notmuch-fcc-dirs + '(("amin@aminb.org" . "amin/Sent") + ("amin@gnu.org" . "gnu/Sent") + ("abandali@uwaterloo.ca" . "\"uwaterloo/Sent Items\"") + ("mab@gnu.org" . "gnu/Sent") + ("aminb@gnu.org" . "gnu/Sent") + (".*" . "sent")) + notmuch-search-result-format + '(("date" . "%12s ") + ("count" . "%-7s ") + ("authors" . "%-40s ") + ("subject" . "%s ") + ("tags" . "(%s)"))) + ;; (add-hook 'visual-fill-column-mode-hook + ;; (lambda () + ;; (when (string= major-mode 'notmuch-message-mode) + ;; (setq visual-fill-column-width 70)))) + ;; (set! :evil-state 'notmuch-message-mode 'insert) + ;; (advice-add #'notmuch-bury-or-kill-this-buffer + ;; :override #'kill-this-buffer) + :bind + (:map notmuch-hello-mode-map + ("g" . notmuch-poll-and-refresh-this-buffer) + ("u" . (lambda () + "Search for `unread' tagged messages" + (interactive) + (notmuch-hello-search "tag:unread"))) + ("i" . (lambda () + "Search for `inbox' tagged messages" + (interactive) + (notmuch-hello-search "tag:inbox"))) + ("l" . (lambda () + "Search for `latest' tagged messages" + (interactive) + (notmuch-hello-search "tag:latest"))) + ("e" . (lambda () + "Search for `encrypted' tagged messages" + (interactive) + (notmuch-hello-search "tag:encrypted"))) + ("M" . (lambda () + "Compose new mail and prompt for sender" + (interactive) + (let ((current-prefix-arg t)) + (call-interactively #'notmuch-mua-new-mail))))) + (:map notmuch-search-mode-map + ("g" . notmuch-poll-and-refresh-this-buffer) + ("k" . (lambda () + "Mark message read" + (interactive) + (notmuch-search-tag '("-unread")) + ;; (notmuch-search-archive-thread) + (notmuch-search-next-thread))) + ("u" . (lambda () + "Mark message unread" + (interactive) + (notmuch-search-tag '("+unread")) + (notmuch-search-next-thread))) + ("K" . (lambda () + "Mark message deleted" + (interactive) + (notmuch-search-tag '("-unread" "-inbox" "+deleted")) + (notmuch-search-archive-thread))) + ("S" . (lambda () + "Mark message as spam" + (interactive) + (notmuch-search-tag '("-unread" "-inbox" "-webmasters" "+spam")) + (notmuch-search-archive-thread)))) + (:map notmuch-tree-mode-map ; TODO: additional bindings + ("S" . (lambda () + "Mark message as spam" + (interactive) + (notmuch-tree-tag '("-unread" "-inbox" "-webmasters" "+spam")) + (notmuch-tree-archive-thread)))) +) + +(use-package counsel-notmuch + :general (amin--leader-keys "/m" 'counsel-notmuch)) + +(after! notmuch-crypto + (setq notmuch-crypto-process-mime t)) + +;; (after! evil +;; (mapc (lambda (str) (evil-set-initial-state (car str) (cdr str))) +;; '((notmuch-hello-mode . emacs) +;; (notmuch-search-mode . emacs) +;; (notmuch-tree-mode . emacs)))) + +(after! recentf + (add-to-list 'recentf-exclude (expand-file-name amin-maildir))) +#+end_src + +** supercite + +#+begin_src emacs-lisp :tangle no +(use-package supercite + :commands sc-cite-original + :init + (add-hook 'mail-citation-hook 'sc-cite-original) + + (defun sc-remove-existing-signature () + (save-excursion + (goto-char (region-beginning)) + (when (re-search-forward message-signature-separator (region-end) t) + (delete-region (match-beginning 0) (region-end))))) + + (add-hook 'mail-citation-hook 'sc-remove-existing-signature) + + (defun sc-remove-if-not-mailing-list () + (unless (assoc "list-id" sc-mail-info) + (setq attribution sc-default-attribution + citation (concat sc-citation-delimiter + sc-citation-separator)))) + + (add-hook 'sc-attribs-postselect-hook 'sc-remove-if-not-mailing-list) + + :config + (defun sc-fill-if-different (&optional prefix) + "Fill the region bounded by `sc-fill-begin' and point. +Only fill if optional PREFIX is different than +`sc-fill-line-prefix'. If `sc-auto-fill-region-p' is nil, do not +fill region. If PREFIX is not supplied, initialize fill +variables. This is useful for a regi `begin' frame-entry." + (if (not prefix) + (setq sc-fill-line-prefix "" + sc-fill-begin (line-beginning-position)) + (if (and sc-auto-fill-region-p + (not (string= prefix sc-fill-line-prefix))) + (let ((fill-prefix sc-fill-line-prefix)) + (unless (or (string= fill-prefix "") + (save-excursion + (goto-char sc-fill-begin) + (or (looking-at ">+ +") + (< (length + (buffer-substring (point) + (line-end-position))) + 65)))) + (fill-region sc-fill-begin (line-beginning-position))) + (setq sc-fill-line-prefix prefix + sc-fill-begin (line-beginning-position))))) +nil)) +#+end_src + +* Blogging +** [[https://ox-hugo.scripter.co][ox-hugo]] + +#+begin_src emacs-lisp +(use-package ox-hugo + :after ox) #+end_src * Post initialization @@ -660,7 +1473,7 @@ Display how long it took to load the init file. #+begin_src emacs-lisp (message "Loading %s...done (%.3fs)" user-init-file (float-time (time-subtract (current-time) - ab--before-user-init-time))) + amin--before-user-init-time))) #+end_src * Footer