2019-01-28 02:46:29 +00:00
#+PROPERTY : header-args :results silent
This init file is based on [[https://medium.com/@CBowdon/pinching-the-best-bits-from-spacemacs-869b8c793ad3 ][this blog post ]].
It's meant to be loaded from init.el like so:
#+BEGIN_SRC emacs-lisp :tangle no
(require 'org)
(org-babel-load-file (expand-file-name "path/to/init.org"))
#+END_SRC
* Packages
Set up package.el to load from ELPA and MELPA
#+BEGIN_SRC emacs-lisp
(require 'package)
(setq package-archives
'(("gnu" . "https://elpa.gnu.org/packages/ ")
("melpa" . "https://melpa.org/packages/ ")))
(package-initialize)
#+END_SRC
`use-package` is a macro that simplifies installing and loading packages. `use-package-always-ensure` makes sure that all packages will be installed before loading is attempted.
#+BEGIN_SRC emacs-lisp
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(require 'use-package)
(setq use-package-always-ensure t)
#+END_SRC
[[https://framagit.org/steckerhalter/quelpa ][Quelpa ]] extends package.el to build packages from source from a bunch of targets (git, hg, etc.).
#+BEGIN_SRC emacs-lisp
(use-package quelpa)
(use-package quelpa-use-package)
#+END_SRC
* Benchmarking
`benchmark-init` does what it says on the box. This sets it up to benchmark my init time and then disable benchmarking after init completes.
#+BEGIN_SRC emacs-lisp
(use-package benchmark-init
:config
(add-hook 'after-init-hook 'benchmark-init/deactivate))
#+END_SRC
* Path
`exec-path-from-shell` uses Bash to set MANPATH, PATH, and exec-path from those defined in the user's shell config. This won't work on Windows.
#+BEGIN_SRC emacs-lisp
(use-package exec-path-from-shell
:if (memq window-system '(mac ns x))
:config
(setq exec-path-from-shell-variables '("PATH" "MANPATH" "LEDGER_FILE"))
(exec-path-from-shell-initialize))
#+END_SRC
* Autocompletion
There seems to be [[https://github.com/company-mode/company-mode/issues/68 ][some contention ]] about whether autocomplete or company are better autocomplete packages. I'm going with company for now because the maintainer seems nicer...
#+BEGIN_SRC emacs-lisp
(use-package company
:config
(setq company-idle-delay 0.3)
(add-hook 'after-init-hook #'global-company-mode))
#+END_SRC
* General
Better keybinding.
#+BEGIN_SRC emacs-lisp
(use-package general)
#+END_SRC
* Which-key
`which-key` makes keybindings discoverable.
#+BEGIN_SRC emacs-lisp
(use-package which-key
:config
(which-key-mode))
#+END_SRC
This function defines a prefix group for `which-key` so that it doesn't display `prefix`.
#+BEGIN_SRC emacs-lisp
(defun jdormit/define-prefix (binding name)
(which-key-add-key-based-replacements
(concat leader " " binding)
name))
#+END_SRC
* Evil Mode
Because I like modal editing and dislike RSI.
#+BEGIN_SRC emacs-lisp
(use-package evil
:config
(evil-mode 1))
#+END_SRC
Use the spacebar as a leader key in evil-mode's normal state and in various other modes:
#+BEGIN_SRC emacs-lisp
(defconst leader "SPC")
(general-define-key
:states 'motion
"SPC" nil)
(general-create-definer leader-def-key
:states 'motion
:prefix leader
:prefix-map 'leader-map)
#+END_SRC
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "?" "help")
(leader-def-key "?" help-map)
#+END_SRC
Make undo not undo paragraphs at a time:
#+BEGIN_SRC emacs-lisp
(setq evil-want-fine-undo t)
#+END_SRC
* Password Store
Interfacing with Pass, the "standard Unix password manager". This should also be loaded before `exec-path-from-shell`.
#+BEGIN_SRC emacs-lisp
(use-package password-store
:commands (password-store-list
password-store-get
password-store-copy)
:config
(setq password-store-password-length 20))
(leader-def-key "p" 'password-store-copy)
(use-package pass
:commands pass
:general
(pass-mode-map "SPC" leader-map))
(leader-def-key "ap" 'pass)
(defun password-store-synchronize ()
(interactive)
(async-shell-command "pass git pull && pass git push"))
#+END_SRC
* Emacs Lisp
Requires:
#+BEGIN_SRC emacs-lisp
(eval-when-compile (require 'subr-x))
#+END_SRC
Default to lexical binding:
#+BEGIN_SRC emacs-lisp
(setq lexical-binding t)
#+END_SRC
** Load path
For machine or user specific libraries:
#+BEGIN_SRC emacs-lisp
(add-to-list 'load-path (expand-file-name "~/site-lisp"))
#+END_SRC
And for global ones:
#+BEGIN_SRC emacs-lisp
(add-to-list 'load-path "/usr/local/share/emacs/site-lisp")
#+END_SRC
** Utilities
Reading a file as a string:
#+BEGIN_SRC emacs-lisp
(defun read-file (path)
"Returns the contents of the file as a string"
(with-temp-buffer
(insert-file-contents path)
(buffer-string)))
#+END_SRC
Opening a file as sudo:
#+BEGIN_SRC emacs-lisp
(defun sudo-find-file (file-name)
"Like find file, but opens the file as root."
(interactive "F")
(let ((tramp-file-name (concat "/sudo::" (expand-file-name file-name))))
(find-file tramp-file-name)))
#+END_SRC
Recursive =assoc= for nested alists:
#+BEGIN_SRC emacs-lisp
(defun assoc-recursive (alist &rest keys)
"Recursively find KEYs in ALIST."
(while keys
(setq alist (cdr (assoc (pop keys) alist))))
alist)
#+END_SRC
Format a millis timestamp into human-readable form:
#+BEGIN_SRC emacs-lisp
(defun format-epoch-millis (millis)
(interactive "nTimestamp: ")
(message (format-time-string "%x %X" (/ millis 1000))))
#+END_SRC
The same but for seconds:
#+BEGIN_SRC emacs-lisp
(defun format-epoch-seconds (seconds)
(interactive "nTimestamp: ")
(message (format-time-string "%x %X" seconds)))
#+END_SRC
** Persisting variables between session
The idea behind this is pretty simple - variables get persisted in ~/.emacs.d/ <persisted-vars-file > as a plist of (variable-name variable-value).
#+BEGIN_SRC emacs-lisp
(defvar persisted-vars-file "~/.emacs.d/persisted-vars")
#+END_SRC
This function retrieves the plist of persisted variables or nil if it doesn't exist:
#+BEGIN_SRC emacs-lisp
(defun get-persisted-plist ()
(let ((file (expand-file-name persisted-vars-file)))
(when (file-exists-p file)
(let ((vars-plist-str (read-file file)))
(unless (string= vars-plist-str "")
(car (read-from-string vars-plist-str)))))))
#+END_SRC
This function retrieves a persisted variable:
#+BEGIN_SRC emacs-lisp
(defun get-persisted-var (var-name)
"Retrieves the value of persisted variable `var-name`, or nil if not found"
(let ((vars-plist (get-persisted-plist)))
(when vars-plist
(plist-get vars-plist var-name))))
#+END_SRC
And this function persists a variable:
#+BEGIN_SRC emacs-lisp
(defun persist-variable (var-name value)
(let ((file (expand-file-name persisted-vars-file))
(vars-plist (get-persisted-plist)))
(if vars-plist
(progn
(plist-put vars-plist var-name value)
(write-region
(prin1-to-string vars-plist) nil file))
(let ((vars-plist `(,var-name ,value)))
(write-region
(prin1-to-string vars-plist) nil file)))))
#+END_SRC
2019-01-28 03:22:00 +00:00
* Customization File
I don't want anything to write to my init.el, so save customizations in a separate file:
#+BEGIN_SRC emacs-lisp
(setq custom-file (expand-file-name "~/.emacs.d/custom.el"))
2019-01-28 13:29:39 +00:00
(load custom-file t)
2019-01-28 03:22:00 +00:00
#+END_SRC
2019-01-28 02:46:29 +00:00
* Dropbox
I put lots of stuff in Dropbox, but the actual folder location differs on my different computers. This function resolves to the Dropbox directory:
#+BEGIN_SRC emacs-lisp
(defun get-dropbox-directory ()
(cond
((file-exists-p (expand-file-name "~/Dropbox"))
(expand-file-name "~/Dropbox"))
((file-exists-p (expand-file-name "~/Dropbox (Personal)"))
(expand-file-name "~/Dropbox (Personal)"))))
#+END_SRC
Load up libraries from Dropbox, if there are any:
#+BEGIN_SRC emacs-lisp
(add-to-list 'load-path
(concat
(file-name-as-directory (get-dropbox-directory))
"site-lisp"))
#+END_SRC
* Init File
A function to reload my init file. It reloads the major mode after the init file is loaded to rebind keymappings.
#+BEGIN_SRC emacs-lisp
(defun reload-init-file ()
(interactive)
(load-file "~/.emacs.d/init.el")
(funcall major-mode))
#+END_SRC
And another one to edit it:
#+BEGIN_SRC emacs-lisp
(defun find-init-file ()
(interactive)
(find-file "~/init.org"))
#+END_SRC
* Keybindings
These are general keybindings; those specific to certain packages are in the sections for those packages.
In some modes I want vanilla Emacs bindings:
#+BEGIN_SRC emacs-lisp
(add-to-list 'evil-emacs-state-modes 'dired-mode)
(add-to-list 'evil-emacs-state-modes 'benchmark-init/tabulated-mode)
(add-to-list 'evil-emacs-state-modes 'benchmark-init/tree-mode)
(add-to-list 'evil-emacs-state-modes 'neotree-mode)
(add-to-list 'evil-emacs-state-modes 'cider-stacktrace-mode)
(add-to-list 'evil-emacs-state-modes 'pass-mode)
(add-to-list 'evil-emacs-state-modes 'picture-mode)
(add-to-list 'evil-emacs-state-modes 'geiser-debug-mode)
(add-to-list 'evil-emacs-state-modes 'undo-tree-visualizer-mode)
(add-to-list 'evil-emacs-state-modes 'makey-key-mode)
(add-to-list 'evil-emacs-state-modes 'term-mode)
#+END_SRC
And in some modes I want to preserve the spacebar as a leader key:
#+BEGIN_SRC emacs-lisp
(general-def picture-mode-map "SPC" leader-map)
(general-def 'motion Info-mode-map "SPC" leader-map)
#+END_SRC
** Visual line navigation
#+BEGIN_SRC emacs-lisp
(general-def 'motion "j" #'evil-next-visual-line)
(general-def 'motion "k" #'evil-previous-visual-line)
(general-def 'motion "j" #'evil-next-visual-line)
(general-def 'motion "k" #'evil-previous-visual-line)
#+END_SRC
** M-x
#+BEGIN_SRC emacs-lisp
(leader-def-key "SPC" 'execute-extended-command)
#+END_SRC
** Eval-ing
#+BEGIN_SRC emacs-lisp
(leader-def-key "e" #'eval-expression)
#+END_SRC
** Init file commands
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "." "dotfile")
(leader-def-key ".r" 'reload-init-file)
(leader-def-key ".f" 'find-init-file)
#+END_SRC
** Commands about files
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "f" "files")
(leader-def-key "ff" 'find-file)
(leader-def-key "fs" 'sudo-find-file)
(leader-def-key "ft" 'auto-revert-tail-mode)
#+END_SRC
** Window commands
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "w" "window")
(leader-def-key "w/" 'split-window-right)
(leader-def-key "w-" 'split-window-below)
(leader-def-key "wm" 'delete-other-windows)
(leader-def-key "wd" 'delete-window)
#+END_SRC
** Buffer commands
A function to switch to previous buffer from [[http://emacsredux.com/blog/2013/04/28/switch-to-previous-buffer/ ][this blog post ]]:
#+BEGIN_SRC emacs-lisp
(defun switch-to-previous-buffer ()
"Switch to previously open buffer.
Repeated invocations toggle between the two most recently open buffers."
(interactive)
(switch-to-buffer (other-buffer (current-buffer) 1)))
(leader-def-key "TAB" 'switch-to-previous-buffer)
#+END_SRC
A function to kill all buffers except the current one from [[https://www.emacswiki.org/emacs/KillingBuffers#toc2 ][EmacsWiki ]]:
#+BEGIN_SRC emacs-lisp
(defun kill-other-buffers ()
"Kill all other buffers."
(interactive)
(mapc 'kill-buffer (delq (current-buffer) (buffer-list))))
#+END_SRC
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "b" "buffer")
(leader-def-key "bb" 'switch-to-buffer)
(leader-def-key "bd" 'kill-buffer)
(leader-def-key "bm" 'kill-other-buffers)
(leader-def-key "br" 'rename-buffer)
#+END_SRC
** Frame commands
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "F" "frame")
(leader-def-key "Fn" 'make-frame-command)
(leader-def-key "Fm" 'delete-other-frames)
(leader-def-key "Fd" 'delete-frame)
#+END_SRC
** Running shell commands
#+BEGIN_SRC emacs-lisp
(leader-def-key "!" 'shell-command)
(leader-def-key "|" 'shell-command-on-region)
#+END_SRC
** Toggles
Like in Spacemacs, put all toggle commands behind a prefix:
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "t" "toggle")
#+END_SRC
Toggles about line truncation:
#+BEGIN_SRC emacs-lisp
(leader-def-key "tt" 'toggle-truncate-lines)
(leader-def-key "tT" 'visual-line-mode)
#+END_SRC
Toggle lisp debugging:
#+BEGIN_SRC emacs-lisp
(leader-def-key "td" 'toggle-debug-on-error)
#+END_SRC
** Shells/REPLs
Emacs has a shell for every mood!
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "s" "shells/REPLs")
(leader-def-key "ss" 'shell)
(leader-def-key "si" 'ielm)
(leader-def-key "se" 'eshell)
(leader-def-key "sa" 'ansi-term)
#+END_SRC
** Applications
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "a" "applications")
#+END_SRC
* Line Numbers
Toggle line numbers:
#+BEGIN_SRC emacs-lisp
(setq display-line-numbers-type 'visual)
(leader-def-key "tn" 'display-line-numbers-mode)
#+END_SRC
Toggle line numbering mode (normal or relative):
#+BEGIN_SRC emacs-lisp
(defun toggle-line-number-mode ()
(interactive)
(when display-line-numbers
(if (eq display-line-numbers 'visual)
(progn
(setq display-line-numbers t)
(setq display-line-numbers-type t))
(progn
(setq display-line-numbers 'visual)
(setq display-line-numbers-type 'visual)))))
(leader-def-key "tr" #'toggle-line-number-mode)
#+END_SRC
Display line numbers by default in code and org-mode buffers:
#+BEGIN_SRC emacs-lisp
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
(add-hook 'org-mode-hook #'display-line-numbers-mode)
#+END_SRC
* Amx
A better M-x.
#+BEGIN_SRC emacs-lisp
(use-package amx
:config
(amx-mode))
#+END_SRC
* Olivetti Mode
Olivetti is a minor mode for a nice writing environment.
#+BEGIN_SRC emacs-lisp
(use-package olivetti
:config
(setq-default olivetti-body-width 100)
(setq olivetti-body-width 100)
:commands olivetti-mode)
(leader-def-key "to" 'olivetti-mode)
#+END_SRC
* Winum
This package includes functions to switch windows by number.
#+BEGIN_SRC emacs-lisp
(use-package winum
:config
(winum-mode)
(leader-def-key "0" 'winum-select-window-0-or-10)
(leader-def-key "1" 'winum-select-window-1)
(leader-def-key "2" 'winum-select-window-2)
(leader-def-key "3" 'winum-select-window-3)
(leader-def-key "4" 'winum-select-window-4)
(leader-def-key "5" 'winum-select-window-5)
(leader-def-key "6" 'winum-select-window-6)
(leader-def-key "7" 'winum-select-window-7)
(leader-def-key "8" 'winum-select-window-8)
(leader-def-key "9" 'winum-select-window-9))
#+END_SRC
I don't want which-key display "lambda" for the descriptions of these, so set a custom display function. This is [[https://github.com/syl20bnr/spacemacs/blob/master/layers/+distributions/spacemacs-bootstrap/packages.el#L312 ][stolen from Spacemacs ]].
#+BEGIN_SRC emacs-lisp
(push '(("\\(.*\\) 0" . "select-window-0") . ("\\1 0..9" . "window 0..9"))
which-key-replacement-alist)
(push '((nil . "select-window-[1-9]") . t) which-key-replacement-alist)
#+END_SRC
* NeoTree
A package to browse files in a tree view
#+BEGIN_SRC emacs-lisp
(use-package neotree
:commands neotree-toggle
:init
(leader-def-key "d" 'neotree-toggle)
:general
(neotree-mode-map "SPC" leader-map))
#+END_SRC
* Backups and Autosaves
Store backups and autosaves in a centralized place. This should really be the default...
#+BEGIN_SRC emacs-lisp
(make-directory (expand-file-name "~/.emacs.d/autosaves") t)
(setq auto-save-file-name-transforms '((".*" "~/.emacs.d/autosaves" t)))
(setq backup-directory-alist '(("." . "~/.emacs.d/backups")))
#+END_SRC
* Paredit/Parinfer
Paredit enables structured editing of s-expressions
#+BEGIN_SRC emacs-lisp
(use-package paredit
:hook ((emacs-lisp-mode . enable-paredit-mode)
(lisp-mode . enable-paredit-mode)
(clojure-mode . enable-paredit-mode)
(cider-repl-mode . enable-paredit-mode)
(ielm-mode . enable-paredit-mode)
(scheme-mode . enable-paredit-mode)
(geiser-repl-mode . enable-paredit-mode)
(slime-repl-mode . enable-paredit-mode)))
(jdormit/define-prefix "l" "lisp")
(jdormit/define-prefix "lw" "wrap")
(leader-def-key "lwr" 'paredit-wrap-round)
(leader-def-key "lws" 'paredit-wrap-square)
(leader-def-key "lwc" 'paredit-wrap-curly)
(leader-def-key "ls" 'paredit-forward-slurp-sexp)
(leader-def-key "lb" 'paredit-forward-barf-sexp)
#+END_SRC
Parinfer infers parens from indentation and vice-versa:
#+BEGIN_SRC emacs-lisp
(use-package parinfer
:init
(leader-def-key "lt" 'parinfer-toggle-mode)
(setq parinfer-extensions '(defaults
pretty-parens
evil
paredit
smart-tab
smart-yank))
:hook ((clojure-mode . parinfer-mode)
(emacs-lisp-mode . parinfer-mode)
(common-lisp-mode . parinfer-mode)
(scheme-mode . parinfer-mode)
(lisp-mode . parinfer-mode)))
#+END_SRC
* Org Mode
Notes, agenda, calendar, blogging, journaling, etc.
#+BEGIN_SRC emacs-lisp
(use-package org
:mode ("\\.org\\'" . org-mode)
:commands (org-agenda org-capture))
(jdormit/define-prefix "o" "org")
(leader-def-key "oa" 'org-agenda)
(leader-def-key "oc" 'org-capture)
(setq org-src-fontify-natively t)
#+END_SRC
Use RET to follow links:
#+BEGIN_SRC emacs-lisp
(setq org-return-follows-link t)
#+END_SRC
Always show inline images:
#+BEGIN_SRC emacs-lisp
(add-hook 'org-mode-hook
(lambda ()
(org-display-inline-images nil t)
(org-redisplay-inline-images)))
#+END_SRC
** Agenda files
#+BEGIN_SRC emacs-lisp
(setq org-agenda-files '("~/org"))
#+END_SRC
** Capture templates
#+BEGIN_SRC emacs-lisp
(setq org-capture-templates
'(("t" "Task" entry
(file "~/org/todo.org")
"* TODO %i%?")
("b" "Backlog task" entry
(file "~/org/backlog.org")
"* TODO %i%?")
("n" "Note" entry
(file "~/org/notes.org")
"* %^{Description}\n%i%?")
("j" "Journal entry" entry
(file "~/org/journal.org")
"* %<%Y-%m-%d %H:%M:%S >%?")
("p" "Project" entry
(file "~/org/notes.org")
"* %^{Project name}\n\n** What's it supposed to do?\n%?\n** How can I test that it works?\n\n** How can I test that it didn't break anything?\n\n** What alternative approaches are there - what trade-offs can be made?\n\n** What assumptions have I made?\n\n** What deployables will I need to deploy?\n")
("b" "Brain" plain (function org-brain-goto-end)
"* %i%?" :empty-lines 1)))
#+END_SRC
** Refile targets
#+BEGIN_SRC emacs-lisp
(setq org-refile-use-outline-path 'file
org-refile-targets '(("~/org/todo.org" :level . 0)
("~/org/backlog.org" :level . 0)))
#+END_SRC
** Todo keywords
#+BEGIN_SRC emacs-lisp
(setq org-todo-keywords
'((sequence "TODO(t)" "WAITING(w)" "|" "DONE(d)" "CANCELLED(c)")))
#+END_SRC
** Agenda views
#+BEGIN_SRC emacs-lisp
(setq org-agenda-custom-commands
'(("t" "Todo list"
((agenda)
(todo "TODO" ((org-agenda-files '("~/org/todo.org"
"~/org/scheduled.org"))))))
("b" "backlog"
((agenda)
(todo "TODO" ((org-agenda-files '("~/org/todo.org"
"~/org/backlog.org"
"~/org/scheduled.org"))))))
("a" "all"
((agenda)
(todo "TODO" ((org-agenda-files '("~/org"))))))))
#+END_SRC
** Keybindings
#+BEGIN_SRC emacs-lisp
(general-def 'normal org-mode-map "T" #'org-insert-todo-heading)
(general-def 'normal org-mode-map "K" #'org-move-subtree-up)
(general-def 'normal org-mode-map "J" #'org-move-subtree-down)
(general-def 'normal org-mode-map "<return >" #'org-return)
(general-def org-mode-map "C-c e" #'org-preview-latex-fragment)
(general-def "C-c l" #'org-store-link)
#+END_SRC
Enable using the leader key in the agenda view:
#+BEGIN_SRC emacs-lisp
(general-def org-agenda-mode-map "SPC" leader-map)
#+END_SRC
** Exporting
*** HTML
Export to HTML:
#+BEGIN_SRC emacs-lisp
(use-package htmlize
:commands htmlize-buffer)
#+END_SRC
Don't put section numbers in front of headers:
#+BEGIN_SRC emacs-lisp
(setq org-export-with-section-numbers nil)
#+END_SRC
Disable the preamble and postamble:
#+BEGIN_SRC emacs-lisp
(setq org-html-preamble nil
org-html-postamble nil)
#+END_SRC
Redefine org-html-src-block to wrap code blocks in <pre ><code > and language class for use by highlight.js:
#+BEGIN_SRC emacs-lisp
(defun org-html-src-block (src-block _contents info)
"Transcode a SRC-BLOCK element from Org to HTML.
CONTENTS holds the contents of the item. INFO is a plist holding
contextual information."
(if (org-export-read-attribute :attr_html src-block :textarea)
(org-html--textarea-block src-block)
(let* ((lang (org-element-property :language src-block))
(code (org-html-format-code src-block info))
(label (let ((lbl (and (org-element-property :name src-block)
(org-export-get-reference src-block info))))
(if lbl (format " id=\"%s\"" lbl) "")))
(klipsify (and (plist-get info :html-klipsify-src)
(member lang '("javascript" "js"
"ruby" "scheme" "clojure" "php" "html")))))
(if (not lang) (format "<pre ><code class=\"example\"%s >\n%s</code ></pre >" label code)
(format "<div class=\"org-src-container\" >\n%s%s\n</div >"
;; Build caption.
(let ((caption (org-export-get-caption src-block)))
(if (not caption) ""
(let ((listing-number
(format
"<span class=\"listing-number\" >%s </span >"
(format
(org-html--translate "Listing %d:" info)
(org-export-get-ordinal
src-block info nil #'org-html--has-caption-p)))))
(format "<label class=\"org-src-name\" >%s%s</label >"
listing-number
(org-trim (org-export-data caption info))))))
;; Contents.
(if klipsify
(format "<pre ><code class=\"src src-%s\"%s%s >%s</code ></pre >"
lang
label
(if (string= lang "html")
" data-editor-type=\"html\""
"")
code)
(format "<pre ><code class=\"src %s src-%s\"%s >%s</code ></pre >"
lang lang label code)))))))
#+END_SRC
*** Markdown
#+BEGIN_SRC emacs-lisp
(eval-after-load "org"
'(require 'ox-md nil t))
#+END_SRC
** org-babel
Literate programming!
#+BEGIN_SRC emacs-lisp
(add-hook 'after-init-hook
(lambda ()
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(python . t)
(shell . t)
(clojure . t)
(lisp . t)
(scheme . t)
(java . t)
(js . t)
(dot . t)
(ditaa . t)))))
#+END_SRC
Get rid of the confirmation prompt:
#+BEGIN_SRC emacs-lisp
(setq org-confirm-babel-evaluate nil)
#+END_SRC
Display inline images after executing a source block:
#+BEGIN_SRC emacs-lisp
(add-hook 'org-babel-after-execute-hook
(lambda ()
(org-display-inline-images nil t)
(org-redisplay-inline-images)))
#+END_SRC
* Projectile
#+BEGIN_SRC emacs-lisp
(use-package projectile
:config
(projectile-mode)
(jdormit/define-prefix "fp" "projectile")
(leader-def-key "fpf" 'projectile-find-file)
(leader-def-key "fpg" 'projectile-grep))
#+END_SRC
* Mode line
A sexy mode line for maximum geek cred:
#+BEGIN_SRC emacs-lisp
(use-package smart-mode-line
:init
(sml/setup))
#+END_SRC
* UI
Get rid of the janky buttons:
#+BEGIN_SRC emacs-lisp
(tool-bar-mode -1)
#+END_SRC
And the ugly scroll bars:
#+BEGIN_SRC emacs-lisp
(set-scroll-bar-mode nil)
#+END_SRC
Use =variable-pitch-mode= in text modes:
#+BEGIN_SRC emacs-lisp
(add-hook 'text-mode-hook (lambda () (variable-pitch-mode)))
(add-hook 'w3m-mode-hook (lambda () (variable-pitch-mode)))
#+END_SRC
Always use =buffer-face-mode= in code and text buffers:
#+BEGIN_SRC emacs-lisp
(add-hook 'prog-mode-hook #'buffer-face-mode)
(add-hook 'text-mode-hook #'buffer-face-mode)
#+END_SRC
Load up some tasty solarized themes:
#+BEGIN_SRC emacs-lisp
(use-package solarized-theme)
#+END_SRC
And also [[https://github.com/kunalb/poet ][poet-theme ]]:
#+BEGIN_SRC emacs-lisp
(use-package poet-theme)
#+END_SRC
A function to load a font, including overriding any theme settings for org-mode headers:
#+BEGIN_SRC emacs-lisp
(defun jdormit/get-font-alist ()
(let ((font-alist (get-persisted-var 'jdormit/font)))
(if font-alist
font-alist
(list))))
(defun jdormit/load-font (face font height)
(interactive
(list
(intern (completing-read "Change face: " (face-list)))
(completing-read "Load font: " (font-family-list))
(read-minibuffer "Font height: " "120")))
(if (not (number-or-marker-p height))
(error "Height must be a number.")
(if (member font (font-family-list))
(if (member face (face-list))
(let ((font-alist
(assq-delete-all face (jdormit/get-font-alist))))
(progn
(set-face-attribute face nil :family font :height height)
(persist-variable
'jdormit/font
(cons `(,face (,font ,height)) font-alist))))
(error "Face %s not found." (symbol-name face)))
(error "Font %s not found." font))))
(defun jdormit/load-persisted-fonts ()
(let ((fonts (jdormit/get-font-alist)))
(dolist (font fonts)
(jdormit/load-font (car font) (car (cadr font)) (cadr (cadr font))))))
#+END_SRC
A function to load a theme then override font settings:
#+BEGIN_SRC emacs-lisp
(defun jdormit/load-theme (theme)
(interactive
(list (intern (completing-read "Load custom theme: "
(mapcar 'symbol-name
(custom-available-themes))))))
(load-theme theme t)
(jdormit/load-persisted-fonts)
(persist-variable 'jdormit/theme theme))
#+END_SRC
UI-related keybindings:
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "u" "UI")
(leader-def-key "ut" 'jdormit/load-theme)
(leader-def-key "uf" 'jdormit/load-font)
#+END_SRC
Some defaults:
#+BEGIN_SRC emacs-lisp
(let ((default (assoc 'default (jdormit/get-font-alist)))
(fixed-pitch (assoc 'fixed-pitch (jdormit/get-font-alist)))
(variable-pitch (assoc 'variable-pitch (jdormit/get-font-alist)))
(theme (get-persisted-var 'jdormit/theme)))
(unless default (jdormit/load-font 'default "Courier" 120))
(unless fixed-pitch (jdormit/load-font 'fixed-pitch "Courier" 115))
(unless variable-pitch (jdormit/load-font 'variable-pitch "Palatino" 120))
(unless theme (jdormit/load-theme 'poet)))
#+END_SRC
Load up previously saved theme and font:
#+BEGIN_SRC emacs-lisp
(when-let ((theme (get-persisted-var 'jdormit/theme)))
(jdormit/load-theme theme))
(jdormit/load-persisted-fonts)
#+END_SRC
* Frame parameters
Remember the previous frame size if previously set:
#+BEGIN_SRC emacs-lisp
(when-let ((frame-width (get-persisted-var 'frame-width)))
(set-frame-width (selected-frame) frame-width))
(when-let ((frame-height (get-persisted-var 'frame-height)))
(set-frame-height (selected-frame) frame-height))
#+END_SRC
Default to reasonable value if not:
#+BEGIN_SRC emacs-lisp
(let ((frame-width (get-persisted-var 'frame-width))
(frame-height (get-persisted-var 'frame-height)))
(unless frame-width (set-frame-width (selected-frame) 150))
(unless frame-height (set-frame-height (selected-frame) 60)))
#+END_SRC
Functions to change the frame size:
#+BEGIN_SRC emacs-lisp
(defun jdormit/set-frame-size (width height)
(interactive "nWidth: \nnHeight: ")
(if (display-graphic-p)
(progn
(persist-variable 'frame-width width)
(persist-variable 'frame-height height)
(set-frame-size (selected-frame) width height))
(message "Not running graphically")))
(defun jdormit/set-frame-width (width)
(interactive "nWidth: ")
(jdormit/set-frame-size width (frame-height)))
(defun jdormit/set-frame-height (height)
(interactive "nHeight: ")
(jdormit/set-frame-size (frame-width) height))
#+END_SRC
Keybindings:
#+BEGIN_SRC emacs-lisp
(leader-def-key "Fw" 'jdormit/set-frame-width)
(leader-def-key "Fh" 'jdormit/set-frame-height)
(leader-def-key "Fs" 'jdormit/set-frame-size)
#+END_SRC
* EShell
Easy keybinding to open EShell:
#+BEGIN_SRC emacs-lisp
(leader-def-key "'" 'eshell)
#+END_SRC
Make EShell's tab completion work like Bash's:
#+BEGIN_SRC emacs-lisp
(setq eshell-cmpl-cycle-completions nil)
#+END_SRC
Destroy shell buffers created by eshell when the process dies::
#+BEGIN_SRC emacs-lisp
(setq eshell-destroy-buffer-when-process-dies t)
#+END_SRC
Visual programs:
#+BEGIN_SRC emacs-lisp
(defun eshell-setup ()
(add-to-list 'eshell-visual-commands "crawl")
(add-to-list 'eshell-visual-commands "ssh"))
(add-hook 'eshell-mode-hook #'eshell-setup)
#+END_SRC
Some aliases:
#+BEGIN_SRC emacs-lisp
(add-hook 'eshell-mode-hook
(lambda ()
(eshell/alias "php-debug"
"php -d xdebug.remote_enable=on -d xdebug.remote_host=127.0.0.1 -d xdebug.remote_port=9000 -d xdebug.remote_handler=dbgp -d xdebug.idekey=geben -d xdebug.remote_autostart=On $*")
(eshell/alias "sortpom"
"mvn com.github.ekryd.sortpom:sortpom-maven-plugin:sort -Dsort.keepBlankLines -Dsort.sortDependencies=scope,groupId,artifactId -Dsort.createBackupFile=false $*")))
#+END_SRC
* JavaScript
#+BEGIN_SRC emacs-lisp
(use-package json-mode
:mode (("\\.json\\'" . json-mode)))
#+END_SRC
* Java
LSP Java uses the Eclipse JDT Language Server as a backend to enable Java IDE features.
#+BEGIN_SRC emacs-lisp
(use-package lsp-mode)
(use-package company-lsp
:after (company)
:config
(setq company-lsp-cache-candidates t))
(use-package lsp-ui
:config (setq lsp-ui-sideline-enable t
lsp-ui-sideline-show-symbol t
lsp-ui-sideline-show-hover t
lsp-ui-sideline-show-code-actions t
lsp-ui-sideline-update-mode 'point))
(defun jdormit/set-up-java ()
(lsp-java-enable)
(flycheck-mode t)
(push 'company-lsp company-backends)
(company-mode t)
(lsp-ui-flycheck-enable t)
(set-variable 'c-basic-offset 2)
(lsp-ui-sideline-mode))
(use-package lsp-java
:requires (lsp-ui-flycheck lsp-ui-sideline)
:config
(add-hook 'java-mode-hook 'jdormit/set-up-java)
(setq lsp-inhibit-message t
lsp-java-import-maven-enabled t))
#+END_SRC
Configure Java project sources:
#+BEGIN_SRC emacs-lisp
(setq lsp-java--workspace-folders
(list (expand-file-name "~/src/Automation")
(expand-file-name "~/src/AutomationSharedExecution")
(expand-file-name "~/src/AutomationPlatform")))
#+END_SRC
* Python
Elpy is a python IDE package:
#+BEGIN_SRC emacs-lisp
(use-package elpy
:config (elpy-enable))
#+END_SRC
Pipenv is the Python standard dependency management/virtual environment tool. pipenv.el teaches Emacs its ways:
#+BEGIN_SRC emacs-lisp
(use-package pipenv
:hook (python-mode . pipenv-mode)
:commands (pipenv-mode
pipenv-activate
pipenv-run))
#+END_SRC
A function to run a pipenv-aware python repl:
#+BEGIN_SRC emacs-lisp
(defun run-pipenv ()
"Runs a pipenv-aware Python shell"
(interactive)
(pipenv-activate)
(run-python nil nil t))
#+END_SRC
* Go
Basic support:
#+BEGIN_SRC emacs-lisp
(use-package go-mode
:mode (("\\.go\\'" . go-mode)))
#+END_SRC
Add in autocompletion. This requires [[https://github.com/mdempsky/gocode ][gocode ]] to be installed:
#+BEGIN_SRC emacs-lisp
(use-package company-go
:after go-mode
:config
(add-hook 'go-mode-hook
(lambda ()
(set (make-local-variable 'company-backends) '(company-go))
(company-mode-on))))
#+END_SRC
* Clojure
Start with clojure-mode:
#+BEGIN_SRC emacs-lisp
(use-package clojure-mode
:mode (("\\.clj\\'" . clojure-mode)
("\\.cljs\\'" . clojurescript-mode)
("\\.cljc\\'" . clojurec-mode)
("\\.edn\\'" . clojure-mode))
:config
(define-clojure-indent
(defroutes 'defun)
(GET 2)
(POST 2)
(PUT 2)
(DELETE 2)
(HEAD 2)
(ANY 2)
(OPTIONS 2)
(PATCH 2)
(rfn 2)
(let-routes 1)
(context 2)
(:= 3)
(:+ 3)))
#+END_SRC
Sprinkle in some CIDER:
#+BEGIN_SRC emacs-lisp
(use-package cider
:commands (cider-mode cider-jack-in cider-jack-in-clojurescript)
:config (setq cider-known-endpoints
'(("local" "localhost" "4005")))
:hook ((clojure-mode . cider-mode)
(clojurescript-mode . cider-mode)
(clojurec-mode . cider-mode))
:general
(cider-stacktrace-mode-map "SPC" leader-map))
(defun jdormit/cider-setup ()
(local-set-key (kbd "C-c M-b") 'cider-debug-defun-at-point))
(add-hook 'cider-mode-hook 'jdormit/cider-setup)
#+END_SRC
* Scheme
Tell emacs about file extensions which should activate scheme-mode:
#+BEGIN_SRC emacs-lisp
(add-to-list 'auto-mode-alist '("\\.guile\\'" . scheme-mode))
(add-to-list 'auto-mode-alist '("\\.rkt\\'" . scheme-mode))
#+END_SRC
[[http://www.nongnu.org/geiser/geiser_1.html ][Geiser ]] is a Scheme IDE for Emacs that supports a bunch of common Scheme implementations.
#+BEGIN_SRC emacs-lisp
(use-package geiser
:general
(geiser-debug-mode-map "SPC" leader-map))
#+END_SRC
And a handy shortcut to hop into a Geiser REPL:
#+BEGIN_SRC emacs-lisp
(leader-def-key "sg" 'run-geiser)
#+END_SRC
* Common Lisp
[[https://common-lisp.net/project/slime/ ][SLIME ]] is a set of modes and utilities for writing Common Lisp in Emacs. [[https://www.quicklisp.org/beta/ ][Quicklisp ]] is the de-facto Common Lisp package manager. It comes with some Emacs bindings.
#+BEGIN_SRC emacs-lisp
(use-package slime-company)
(use-package slime
:commands slime
:config
(setq inferior-lisp-program
(executable-find "sbcl")
slime-contribs '(slime-repl
slime-fancy
slime-company))
(when (file-exists-p
(expand-file-name "~/quicklisp/slime-helper.el"))
(load (expand-file-name "~/quicklisp/slime-helper.el"))))
(add-to-list 'auto-mode-alist '("\\.cl\\'" . lisp-mode))
#+END_SRC
Keyboard shortcut to start a SLIME REPL:
#+BEGIN_SRC emacs-lisp
(leader-def-key "sc" 'slime)
#+END_SRC
* Haskell
#+BEGIN_SRC emacs-lisp
(defun jdormit/haskell-setup ()
(local-set-key (kbd "C-c M-j") 'interactive-haskell-mode))
(use-package haskell-mode
:mode (("\\.hs\\'" . haskell-mode)))
(add-hook 'haskell-mode-hook 'jdormit/haskell-setup)
#+END_SRC
* PHP
#+BEGIN_SRC emacs-lisp
(use-package php-mode
:mode "\\.php\\'")
(use-package mmm-mode)
#+END_SRC
Geben is an interface to XDebug allowing debugging PHP in Emacs:
#+BEGIN_SRC emacs-lisp
(use-package geben
:commands (geben))
#+END_SRC
Some keybindings to start and end Geben:
#+BEGIN_SRC emacs-lisp
(general-def php-mode-map "C-c C-d" #'geben)
(general-def php-mode-map "C-c C-q" #'geben-end)
#+END_SRC
An Eshell alias to start PHP using XDebug:
#+BEGIN_SRC emacs-lisp
(add-hook 'eshell-mode-hook
(lambda ()
(eshell/alias "php-debug"
"php -d xdebug.remote_enable=on -d xdebug.remote_host=127.0.0.1 -d xdebug.remote_port=9000 -d xdebug.remote_handler=dbgp -d xdebug.idekey=geben -d xdebug.remote_autostart=On $*")))
#+END_SRC
Company-mode autocompletion for PHP:
#+BEGIN_SRC emacs-lisp
(use-package company-php
:config
(add-hook 'php-mode-hook
(lambda ()
(ac-php-core-eldoc-setup)
(make-local-variable 'company-backends)
(add-to-list 'company-backends 'company-ac-php-backend))))
#+END_SRC
* Pharen
[[https://pharen.org ][Pharen ]] is a Lisp that compiles to PHP. It looks a lot like Clojure.
#+BEGIN_SRC emacs-lisp
(add-to-list 'auto-mode-alist '("\\.phn\\'" . clojure-mode))
#+END_SRC
* CSVs
#+BEGIN_SRC emacs-lisp
(use-package csv-mode
:mode "\\.csv\\'")
#+END_SRC
* Markdown
#+BEGIN_SRC emacs-lisp
(use-package markdown-mode
:commands (markdown-mode gfm-mode)
:mode (("README\\.md\\'" . gfm-mode)
("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode))
:init (setq markdown-command "pandoc"))
#+END_SRC
* IELM
Enable lexical scoping in IELM:
#+BEGIN_SRC emacs-lisp
(add-hook 'ielm-mode-hook
#'(lambda ()
(interactive)
(setq lexical-binding t)))
#+END_SRC
* Magit
Magit is objectively the best Git interface.
#+BEGIN_SRC emacs-lisp
(use-package magit
:commands magit-status
:general
(magit-mode-map "SPC" leader-map))
#+END_SRC
Enable evil keybindings:
#+BEGIN_SRC emacs-lisp
(use-package evil-magit
:hook ((magit-status-mode . evil-magit-init))
:config
(require 'evil-magit))
#+END_SRC
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "g" "git")
(leader-def-key "gs" 'magit-status)
#+END_SRC
Use ido-mode for completion within Magit:
#+BEGIN_SRC emacs-lisp
(setq magit-completing-read-function 'magit-ido-completing-read)
#+END_SRC
* Ledger Mode
This mode requires that [[https://github.com/ledger/ledger ][ledger ]] be installed on the system.
#+BEGIN_SRC emacs-lisp
(use-package ledger-mode
:mode "\\.ledger\\'"
:config
(add-to-list 'evil-emacs-state-modes 'ledger-report-mode)
(general-def ledger-report-mode-map "SPC" leader-map)
(add-hook 'ledger-mode-hook
(lambda ()
(variable-pitch-mode 0)))
(add-hook 'ledger-report-mode-hook
(lambda ()
(variable-pitch-mode 0))))
#+END_SRC
* PDFs
#+BEGIN_SRC emacs-lisp
(use-package pdf-tools
:mode ("\\.pdf\\'" . pdf-view-mode)
:config
(pdf-tools-install)
:general
(pdf-view-mode-map "SPC" leader-map))
#+END_SRC
* EPubs
#+BEGIN_SRC emacs-lisp
(defun jdormit/nov-config ()
(when (member "Input Serif" (font-family-list))
(face-remap-add-relative 'variable-pitch :family "Input Serif"))
(olivetti-mode)
(nov-render-document))
(use-package nov
:mode ("\\.epub\\'" . nov-mode)
:config
(setq nov-text-width 80)
(add-hook 'nov-mode-hook 'jdormit/nov-config)
:general
('normal nov-mode-map "r" 'nov-render-document)
('normal nov-mode-map "=" 'nov-view-source)
('normal nov-mode-map "+" 'nov-view-content-source)
('normal nov-mode-map "m" 'nov-display-metadata)
('normal nov-mode-map "n" 'nov-next-document)
('normal nov-mode-map "]" 'nov-next-document)
('normal nov-mode-map "p" 'nov-previous-document)
('normal nov-mode-map "[" 'nov-previous-document)
('normal nov-mode-map "t" 'nov-goto-toc)
('normal nov-mode-map "RET" 'nov-browse-url)
('normal nov-mode-map "<follow-link >" 'mouse-face)
('normal nov-mode-map "<mouse-2 >" 'nov-browse-url)
('normal nov-mode-map "TAB" 'shr-next-link)
('normal nov-mode-map "M-TAB" 'shr-previous-link)
('normal nov-mode-map "<backtab >" 'shr-previous-link)
('normal nov-mode-map "SPC" 'nov-scroll-up)
('normal nov-mode-map "S-SPC" 'nov-scroll-down)
('normal nov-mode-map "DEL" 'nov-scroll-down)
('normal nov-mode-map "<home >" 'beginning-of-buffer)
('normal nov-mode-map "<end >" 'end-of-buffer)
('normal nov-mode-map "SPC" leader-map))
#+END_SRC
** Org mode links
First, keep a reference to filename of the .epub before nov.el blows it away:
#+BEGIN_SRC emacs-lisp
(defvar nov-epub-file)
(add-hook 'nov-mode-hook
#'(lambda ()
(message "epub file: " buffer-file-name)
(setq nov-epub-file buffer-file-name)))
#+END_SRC
That reference lets us construct a link back to the .epub:
#+BEGIN_SRC emacs-lisp
(defun org-epub-store-link ()
(when (eq major-mode 'nov-mode)
(let ((epub-name (alist-get 'title nov-metadata)))
(org-store-link-props
:type "file"
:link (concat "file://" nov-epub-file)))))
(add-hook 'org-store-link-functions 'org-epub-store-link)
#+END_SRC
* Dashboard
Instead of the *GNU Emacs* buffer on startup, display a cool dashboard:
#+BEGIN_SRC emacs-lisp
(use-package dashboard
:config
(setq dashboard-items '((recents . 5)
(projects . 5))
dashboard-startup-banner 'official)
(dashboard-setup-startup-hook))
#+END_SRC
* Mu4e
Because email in Emacs is badass. My mail set up is based on [[http://stevelosh.com/blog/2012/10/the-homely-mutt/ ][this mutt setup ]] and [[https://notanumber.io/2016-10-03/better-email-with-mu4e/ ][this mu4e setup ]].
#+BEGIN_SRC emacs-lisp
(defvar jdormit/mu4e-load-path
(if (file-exists-p "/usr/local/share/emacs/site-lisp/mu/mu4e")
"/usr/local/share/emacs/site-lisp/mu/mu4e"
(if (file-exists-p "/usr/share/emacs/site-lisp/mu4e")
"/usr/share/emacs/site-lisp/mu4e")))
(add-to-list 'load-path jdormit/mu4e-load-path)
(autoload 'mu4e (concat jdormit/mu4e-load-path "/mu4e.el"))
(autoload 'mu4e-update-index (concat jdormit/mu4e-load-path "/mu4e.el"))
(with-eval-after-load 'mu4e
(require 'org-mu4e)
(setq
mu4e-maildir (expand-file-name "~/.mail")
message-send-mail-function 'message-send-mail-with-sendmail
mu4e-get-mail-command "mbsync -a"
sendmail-program (executable-find "msmtp")
mu4e-attachment-dir "~/Downloads"
mu4e-compose-format-flowed t
mu4e-html2text-command "w3m -dump -T text/html -o display_link_number=1"
mu4e-change-filenames-when-moving t
org-mu4e-link-query-in-headers-mode nil
mu4e-maildirs-extension-custom-list
'("/jeremy-dormitzer-gmail-com/Inbox"
"/jeremy-dormitzer-net/Inbox"
"/jeremy-getpterotype-com/Inbox")
mu4e-contexts
(let ((per-dir "/jeremy-dormitzer-gmail-com")
(dormit-dir "/jeremy-dormitzer-net")
(pterotype-dir "/jeremy-getpterotype-com"))
`(,(make-mu4e-context
:name "Pterotype"
:match-func (lambda (msg)
(when msg ())
(when msg (string-match-p
"jeremy-getpterotype-com"
(mu4e-message-field msg :path))))
:vars `((user-mail-address . "jeremy@getpterotype.com")
(user-full-name . "Jeremy Dormitzer")
(mu4e-sent-folder . ,(concat pterotype-dir "/Sent"))
(mu4e-drafts-folder . ,(concat pterotype-dir "/Drafts"))
(mu4e-refile-folder . ,(concat pterotype-dir "/Archive"))
(mu4e-trash-folder . ,(concat pterotype-dir "/Trash"))
(mu4e-sent-messages-behavior . delete)
(mu4e-get-mail-command . "mbsync jeremy-getpterotype-com")
(message-sendmail-extra-arguments
. ("-a" "jeremy-getpterotype.com"))))
,(make-mu4e-context
:name "GMail"
:match-func (lambda (msg)
(when msg (string-match-p
"jeremy-dormitzer-gmail-com"
(mu4e-message-field msg :path))))
:vars `((user-mail-address . "jeremy.dormitzer@gmail.com")
(user-full-name . "Jeremy Dormitzer")
(mu4e-sent-folder . ,(concat per-dir "/Sent"))
(mu4e-drafts-folder . ,(concat per-dir "/Drafts"))
(mu4e-refile-folder . ,(concat per-dir "/Archive"))
(mu4e-trash-folder . ,(concat per-dir "/Trash"))
(mu4e-sent-messages-behavior . delete)
(mu4e-get-mail-command . "mbsync jeremy-dormitzer-gmail-com")
(message-sendmail-extra-arguments
. ("-a" "jeremy.dormitzer-gmail.com"))))
,(make-mu4e-context
:name "Dormitzer"
:match-func (lambda (msg)
(when msg (string-match-p
"jeremy-dormitzer-net"
(mu4e-message-field msg :path))))
:vars `((user-mail-address . "jeremy@dormitzer.net")
(user-full-name . "Jeremy Dormitzer")
(mu4e-sent-folder . ,(concat dormit-dir "/Sent"))
(mu4e-drafts-folder . ,(concat dormit-dir "/Drafts"))
(mu4e-refile-folder . ,(concat dormit-dir "/Archive"))
(mu4e-trash-folder . ,(concat dormit-dir "/Trash"))
(mu4e-get-mail-command . "mbsync jeremy-dormitzer-net")
(message-sendmail-extra-arguments
. ("-a" "jeremy-dormitzer.net"))))))
mu4e-context-policy 'ask
mu4e-compose-context-policy 'ask-if-none))
(leader-def-key "am" 'mu4e)
#+END_SRC
Custom actions:
#+BEGIN_SRC emacs-lisp
(defun mu4e-view-go-to-url-w3m (&optional MULTI)
(let ((browse-url-browser-function 'w3m-browse-url))
(mu4e-view-go-to-url MULTI)))
(defun mu4e-action-view-in-browser-w3m (msg)
(let ((browse-url-browser-function 'w3m-browse-url))
(mu4e-action-view-in-browser msg)))
(with-eval-after-load 'mu4e
(add-to-list 'mu4e-view-actions
'("View in browser" . mu4e-action-view-in-browser) t)
(add-to-list 'mu4e-view-actions
'("WView in w3m" . mu4e-action-view-in-browser-w3m) t)
(add-to-list 'mu4e-view-actions
'("wGo to URL with w3m" . mu4e-view-go-to-url-w3m) t))
#+END_SRC
Make mu4e the default sendmail program:
#+BEGIN_SRC emacs-lisp
(setq mail-user-agent 'mu4e-user-agent)
#+END_SRC
Use the spacebar as a leader key in mu4e modes:
#+BEGIN_SRC emacs-lisp
(general-def mu4e-main-mode-map "SPC" leader-map)
(general-def mu4e-headers-mode-map "SPC" leader-map)
(general-def mu4e-view-mode-map "SPC" leader-map)
#+END_SRC
** HTML email
*** Redefinitions
Redefine =org-mime-insert-html-content= to export the plain part of HTML emails as ascii instead of org:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'org-mime
(defun org-mime-insert-html-content (body file s opts)
(let* ((files (org-mime-extract-non-image-files))
;; dvipng for inline latex because MathJax doesn't work in mail
;; Also @see https://github.com/org-mime/org-mime/issues/16
;; (setq org-html-with-latex nil) sometimes useful
(org-html-with-latex org-mime-org-html-with-latex-default)
;; we don't want to convert org file links to html
(org-html-link-org-files-as-html nil)
(org-link-file-path-type 'absolute)
;; makes the replies with ">"s look nicer
(org-export-preserve-breaks org-mime-preserve-breaks)
(plain (org-mime--export-string body 'ascii))
;; org 9
(org-html-htmlize-output-type 'inline-css)
;; org 8
(org-export-htmlize-output-type 'inline-css)
(html-and-images (org-mime-replace-images (org-mime--export-string s 'html opts)
file))
(images (cdr html-and-images))
(html (org-mime-apply-html-hook (car html-and-images))))
;; If there are files that were attached, we should remove the links,
;; and mark them as attachments. The links don't work in the html file.
(when files
(mapc (lambda (f)
(setq html (replace-regexp-in-string
(format "<a href=\"%s\" >%s</a >"
(regexp-quote f) (regexp-quote f))
(format "%s (attached)" (file-name-nondirectory f))
html)))
files))
(insert (org-mime-multipart plain
html
(mapconcat 'identity images "\n")))
;; Attach any residual files
(when files
(mapc (lambda (f)
(when org-mime-debug (message "attaching: %s" f))
(mml-attach-file f))
files)))))
#+END_SRC
And redefine =mu4e~compose-handler= to add a new hook:
#+BEGIN_SRC emacs-lisp
(defcustom jdormit-mu4e-compose-hook nil
"Hook run after the message composition buffer is set up"
:type 'hook
:group 'mu4e-compose)
(with-eval-after-load 'mu4e
(defun* mu4e~compose-handler (compose-type &optional original-msg includes)
"Create a new draft message, or open an existing one.
COMPOSE-TYPE determines the kind of message to compose and is a
symbol, either `reply', `forward', `edit', `resend' `new'. `edit'
is for editing existing (draft) messages. When COMPOSE-TYPE is
`reply' or `forward', MSG should be a message plist. If
COMPOSE-TYPE is `new', ORIGINAL-MSG should be nil.
Optionally (when forwarding, replying) ORIGINAL-MSG is the original
message we will forward / reply to.
Optionally (when forwarding) INCLUDES contains a list of
(:file-name <filename > :mime-type <mime-type > :disposition <disposition >)
for the attachements to include; file-name refers to
a file which our backend has conveniently saved for us (as a
tempfile)."
;; Run the hooks defined for `mu4e-compose-pre-hook'. If compose-type is
;; `reply', `forward' or `edit', `mu4e-compose-parent-message' points to the
;; message being forwarded or replied to, otherwise it is nil.
(set (make-local-variable 'mu4e-compose-parent-message) original-msg)
(put 'mu4e-compose-parent-message 'permanent-local t)
;; remember the compose-type
(set (make-local-variable 'mu4e-compose-type) compose-type)
(put 'mu4e-compose-type 'permanent-local t)
;; maybe switch the context
(mu4e~context-autoswitch mu4e-compose-parent-message
mu4e-compose-context-policy)
(run-hooks 'mu4e-compose-pre-hook)
;; this opens (or re-opens) a messages with all the basic headers set.
(let ((winconf (current-window-configuration)))
(condition-case nil
(mu4e-draft-open compose-type original-msg)
(quit (set-window-configuration winconf)
(mu4e-message "Operation aborted")
(return-from mu4e~compose-handler))))
;; insert mail-header-separator, which is needed by message mode to separate
;; headers and body. will be removed before saving to disk
(mu4e~draft-insert-mail-header-separator)
;; maybe encrypt/sign replies
(mu4e~compose-crypto-reply original-msg compose-type)
;; include files -- e.g. when forwarding a message with attachments,
;; we take those from the original.
(save-excursion
(goto-char (point-max)) ;; put attachments at the end
(dolist (att includes)
(mml-attach-file
(plist-get att :file-name) (plist-get att :mime-type))))
;; buffer is not user-modified yet
(mu4e~compose-set-friendly-buffer-name compose-type)
(set-buffer-modified-p nil)
;; now jump to some useful positions, and start writing that mail!
(if (member compose-type '(new forward))
(message-goto-to)
(message-goto-body))
;; bind to `mu4e-compose-parent-message' of compose buffer
(set (make-local-variable 'mu4e-compose-parent-message) original-msg)
(put 'mu4e-compose-parent-message 'permanent-local t)
;; hide some headers
(mu4e~compose-hide-headers)
;; switch on the mode
(mu4e-compose-mode)
(run-hooks 'jdormit-mu4e-compose-hook)
;; set mu4e-compose-type once more for this buffer,
;; we loose it after the mode-change, it seems
(set (make-local-variable 'mu4e-compose-type) compose-type)
(put 'mu4e-compose-type 'permanent-local t)
(when mu4e-compose-in-new-frame
;; make sure to close the frame when we're done with the message these are
;; all buffer-local;
(push 'delete-frame message-exit-actions)
(push 'delete-frame message-postpone-actions))))
#+END_SRC
*** Actual HTML mail logic
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'mu4e (require 'org-mu4e))
(use-package org-mime
:config
(setq org-mime-export-options '(:section-numbers nil
:with-author nil
:with-toc nil)))
(defun htmlize-and-send ()
(interactive)
(when (member 'org~mu4e-mime-switch-headers-or-body post-command-hook)
(org-mime-htmlize)
(message-send-and-exit)))
(add-hook 'org-ctrl-c-ctrl-c-hook #'htmlize-and-send t)
(defun setup-compose-buffer ()
(org-mu4e-compose-org-mode))
(add-hook 'jdormit-mu4e-compose-hook #'setup-compose-buffer)
#+END_SRC
When citing (quoting) messages in a reply, wrap them in org quote blocks instead of prefixing each line with '> ':
#+BEGIN_SRC emacs-lisp
(defun jdormit-citation-line-function ()
(message-insert-citation-line)
(insert "#+BEGIN_QUOTE\n"))
(defun jdormit-cite-function ()
(let ((message-yank-prefix "")
(message-yank-cited-prefix "")
(message-yank-empty-prefix ""))
(save-excursion
(message-cite-original)
(goto-char (point-max))
(insert "\n#+END_QUOTE"))))
(with-eval-after-load 'mu4e
(setq message-citation-line-function #'jdormit-citation-line-function
mu4e-compose-cite-function #'jdormit-cite-function))
#+END_SRC
Some keybindings to send the current org buffer or subtree as an email:
#+BEGIN_SRC emacs-lisp
(general-def org-mode-map "C-c m" #'org-mime-org-buffer-htmlize)
(general-def org-mode-map "C-c s" #'org-mime-org-subtree-htmlize)
#+END_SRC
* Mu4e-alert
Desktop notifications for mu4e emails.
#+BEGIN_SRC emacs-lisp
(defun jdormit-get-mu4e-alert-style ()
(if (memq window-system '(mac ns))
'notifier
'libnotify))
(use-package mu4e-alert
:after (mu4e)
:config
(setq mu4e-alert-interesting-mail-query
(concat
"flag:unread maildir:/jeremy-dormitzer-gmail-com/Inbox"
" OR flag:unread maildir:/jeremy-dormitzer.net/Inbox"
" OR flag:unread maildir:/jeremy-getpterotype-com/Inbox"))
(mu4e-alert-set-default-style (jdormit-get-mu4e-alert-style))
(mu4e-alert-enable-notifications)
(mu4e-alert-enable-mode-line-display))
#+END_SRC
* w3m
Browsing the web from Emacs. Relies on having [[http://w3m.sourceforge.net/ ][w3m ]] installed.
#+BEGIN_SRC emacs-lisp
(use-package w3m
:commands (w3m
w3m-browse-url
w3m-search-new-session)
:config
(setq w3m-home-page "https://start.duckduckgo.com"
w3m-search-default-engine "duckduckgo"
w3m-cookie-reject-domains '("www.wsj.com"
"www.bbc.com"
"www.nytimes.com"
"www.washingtonpost.com"))
(add-to-list 'evil-normal-state-modes 'w3m-mode)
:general
('normal w3m-mode-map "R" 'w3m-reload-this-page)
('normal w3m-mode-map "r" 'w3m-redisplay-this-page)
('normal w3m-mode-map "<tab >" 'w3m-next-anchor)
('normal w3m-mode-map "<backtab >" 'w3m-previous-anchor)
('normal w3m-mode-map "]" 'w3m-next-form)
('normal w3m-mode-map "[" 'w3m-previous-form)
('normal w3m-mode-map "}" 'w3m-next-image)
('normal w3m-mode-map "{" 'w3m-previous-image)
('normal w3m-mode-map "RET" 'w3m-view-this-url)
('normal w3m-mode-map "B" 'w3m-view-previous-page)
('normal w3m-mode-map "N" 'w3m-view-next-page)
('normal w3m-mode-map "^" 'w3m-view-parent-page)
('normal w3m-mode-map "C-f" 'w3m-scroll-up-or-next-url)
('normal w3m-mode-map "C-b" 'w3m-scroll-down-or-previous-url)
('normal w3m-mode-map "u" 'w3m-goto-url)
('normal w3m-mode-map "U" 'w3m-goto-url-new-session)
('normal w3m-mode-map "H" 'w3m-gohome)
('normal w3m-mode-map "M" 'w3m-view-url-with-browse-url)
('normal w3m-mode-map "M-d" 'w3m-download)
('normal w3m-mode-map "d" 'w3m-download-this-url)
('normal w3m-mode-map "I" 'w3m-view-image)
('normal w3m-mode-map "M-i" 'w3m-save-image)
('normal w3m-mode-map "t" 'w3m-toggle-inline-image)
('normal w3m-mode-map "T" 'w3m-toggle-inline-images)
('normal w3m-mode-map "C" 'w3m-print-this-url)
('normal w3m-mode-map "c" 'w3m-print-current-url)
('normal w3m-mode-map "\\" 'w3m-view-source)
('normal w3m-mode-map "=" 'w3m-view-header)
('normal w3m-mode-map "M-k" 'w3m-cookie)
('normal w3m-mode-map "s" 'w3m-search)
('normal w3m-mode-map "S" 'w3m-search-new-session)
('normal w3m-mode-map "|" 'w3m-pipe-source)
('normal w3m-mode-map "M-h" 'w3m-history)
('normal w3m-mode-map "q" 'w3m-close-window)
('normal w3m-mode-map "Q" 'w3m-quit))
(jdormit/define-prefix "aw" "w3m")
(leader-def-key "aww" 'w3m)
(leader-def-key "aws" 'w3m-search-new-session)
(leader-def-key "awb" 'w3m-browse-url)
#+END_SRC
I mostly want `browse-url-at-point` to open stuff in Firefox, but in some cases I want it within Emacs:
#+BEGIN_SRC emacs-lisp
(defun browse-url-at-point-w3m ()
"Opens the URL at point in w3m"
(interactive)
(let ((browse-url-browser-function 'w3m-browse-url))
(if (eq major-mode 'org-mode)
(org-open-at-point)
(browse-url-at-point))))
(leader-def-key "awB" 'browse-url-at-point-w3m)
#+END_SRC
I want to be able to set a custom "Referer" header by setting the variable `jdormit/w3m-referers`. I also want to be able to set websites that cookies will never get sent to:
#+BEGIN_SRC emacs-lisp
(defvar jdormit/w3m-referers nil)
(defvar jdormit/w3m-no-cookie-sites nil)
(defun get-referer (url referer-list)
"Retrieve the referer specified by url in referer-list"
(when (not (eq nil referer-list))
(let ((first (car referer-list))
(rest (cdr referer-list)))
(if (string-match-p (car first) url)
(cdr first)
(get-referer url rest)))))
(defun should-not-set-cookie-p (url no-cookie-sites)
"Non-nil if cookies should not be sent to url"
(when (not (eq nil no-cookie-sites))
(if (string-match-p (car no-cookie-sites) url)
t
(should-not-set-cookie-p url (cdr no-cookie-sites)))))
(advice-add 'w3m-header-arguments :around
(lambda (w3m-header-arguments &rest r)
(cl-destructuring-bind
(method url temp-file body referer content-type) r
(let ((w3m-use-cookies
(if (should-not-set-cookie-p url jdormit/w3m-no-cookie-sites)
nil
w3m-use-cookies)))
(if-let ((referer (get-referer url jdormit/w3m-referers)))
(funcall w3m-header-arguments
method
url
temp-file
body
referer
content-type)
(apply w3m-header-arguments r))))))
#+END_SRC
And here are the websites where I want custom referers and/or no cookies:
#+BEGIN_SRC emacs-lisp
(setq jdormit/w3m-referers '(("www.wsj.com" . "https:/ /google.com")
("www.nytimes.com" . "https://google.com")
("www.newyorker.com" . "https://google.com")
("www.economist.com" . "https://google.com")))
(setq jdormit/w3m-no-cookie-sites '("www.wsj.com"
"www.nytimes.com"
"www.economist.com"
"www.newyorker.com"))
#+END_SRC
Render =<code>= and =<pre>= blocks in a different face. Which face can be set by setting or customizing the variable =w3m-code-block-face= . It defaults to ='fixed-pitch= :
#+BEGIN_SRC emacs-lisp
(setq w3m-code-open-delimiter "{{{W3M_CODE_BLOCK_BEGIN}}} ")
(setq w3m-code-close-delimiter "{{{W3M_CODE_BLOCK_END}}} ")
(defun w3m-filter-code-blocks (url)
(w3m-tag-code-block-filter "code")
(w3m-tag-code-block-filter "pre"))
(defun w3m-tag-code-block-filter (tag)
(goto-char (point-min))
(let ((open-tag-re (format "<%s[ \t\r\f\n]*[^ >]*>" tag))
(close-tag-re (format "</%s[ \t\r\f\n]* >" tag)))
(while (re-search-forward open-tag-re nil t)
(let ((start (match-beginning 0)))
(when (re-search-forward close-tag-re nil t)
(goto-char start)
(insert w3m-code-open-delimiter)
(goto-char (+ (string-width w3m-code-open-delimiter) (match-end 0)))
(insert w3m-code-close-delimiter))))))
(defcustom w3m-code-block-face 'fixed-pitch
"Face for <code > and <pre > blocks in w3m")
(defun w3m-fontify-code-block ()
(goto-char (point-min))
(while (search-forward w3m-code-open-delimiter nil t)
(let ((begin-block-start (match-beginning 0))
(begin-block-end (match-end 0))
(code-start (match-end 0)))
(when (search-forward w3m-code-close-delimiter nil t)
(w3m-add-face-property code-start (match-beginning 0) w3m-code-block-face)
(delete-region (match-beginning 0) (match-end 0)))
(delete-region begin-block-start begin-block-end)
(goto-char (point-min)))))
;(add-hook 'w3m-fontify-before-hook 'w3m-fontify-code-block)
#+END_SRC
Add a w3m filter to handle the code block delimiters:
#+BEGIN_SRC emacs-lisp
;; (with-eval-after-load 'w3m-filter
;; (add-to-list 'w3m-filter-configuration
;; '(t "Render code blocks with a different face" ".*" w3m-filter-code-blocks)))
#+END_SRC
* Wakatime
[[https://wakatime.com/emacs ][Wakatime ]] is a tool that tracks how much time you spend coding and various metrics about your development activity.
It needs a helper script to work properly.
#+BEGIN_SRC emacs-lisp
(defvar wakatime-path
(if (file-exists-p "/usr/local/bin/wakatime")
"/usr/local/bin/wakatime"
(when (file-exists-p "/usr/bin/wakatime")
"/usr/bin/wakatime")))
(when wakatime-path
(let ((wakatime-key (password-store-get "wakatime-api-key")))
(use-package wakatime-mode
:init
(setq wakatime-api-key wakatime-key
wakatime-cli-path wakatime-path)
:config
(global-wakatime-mode))))
#+END_SRC
* Elfeed
Elfeed is a feed reader for Emacs.
#+BEGIN_SRC emacs-lisp
(defun elfeed-setup-hook ()
(set (make-local-variable 'browse-url-browser-function)
'w3m-browse-url)
(set (make-local-variable 'jdormit/w3m-referer) "https:/ /www.google.com")
(general-def 'normal elfeed-search-mode-map "q" 'elfeed-search-quit-window)
(general-def 'normal elfeed-search-mode-map "C-r" 'elfeed-search-update--force)
(general-def 'normal elfeed-search-mode-map "R" 'elfeed-search-fetch)
(general-def 'normal elfeed-search-mode-map "RET" 'elfeed-search-show-entry)
(general-def 'normal elfeed-search-mode-map "s" 'elfeed-search-live-filter)
(general-def 'normal elfeed-search-mode-map "S" 'elfeed-search-set-filter)
(general-def 'normal elfeed-search-mode-map "B" 'elfeed-search-browse-url)
(general-def 'normal elfeed-search-mode-map "y" 'elfeed-search-yank)
(general-def 'normal elfeed-search-mode-map "u" 'elfeed-search-tag-all-unread)
(general-def 'normal elfeed-search-mode-map "r" 'elfeed-search-untag-all-unread)
(general-def 'normal elfeed-search-mode-map "n" 'next-line)
(general-def 'normal elfeed-search-mode-map "p" 'previous-line)
(general-def 'normal elfeed-search-mode-map "+" 'elfeed-search-tag-all)
(general-def 'normal elfeed-search-mode-map "-" 'elfeed-search-untag-all)
(general-def 'normal elfeed-show-mode-map "d" 'elfeed-show-save-enclosure)
(general-def 'normal elfeed-show-mode-map "q" 'elfeed-kill-buffer)
(general-def 'normal elfeed-show-mode-map "r" 'elfeed-show-refresh)
(general-def 'normal elfeed-show-mode-map "n" 'elfeed-show-next)
(general-def 'normal elfeed-show-mode-map "p" 'elfeed-show-prev)
(general-def 'normal elfeed-show-mode-map "s" 'elfeed-show-new-live-search)
(general-def 'normal elfeed-show-mode-map "b" 'elfeed-show-visit)
(general-def 'normal elfeed-show-mode-map "y" 'elfeed-show-yank)
(general-def 'normal elfeed-show-mode-map "u" (elfeed-expose #'elfeed-show-tag 'unread))
(general-def 'normal elfeed-show-mode-map "+" 'elfeed-show-tag)
(general-def 'normal elfeed-show-mode-map "-" 'elfeed-show-untag)
(general-def 'normal elfeed-show-mode-map "SPC" 'scroll-up-command)
(general-def 'normal elfeed-show-mode-map "DEL" 'scroll-down-command)
(general-def 'normal elfeed-show-mode-map "\t" 'shr-next-link)
(general-def 'normal elfeed-show-mode-map [tab] 'shr-next-link)
(general-def 'normal elfeed-show-mode-map "\e\t" 'shr-previous-link)
(general-def 'normal elfeed-show-mode-map [backtab] 'shr-previous-link)
(general-def 'normal elfeed-show-mode-map [mouse-2] 'shr-browse-url)
(general-def 'normal elfeed-show-mode-map "A" 'elfeed-show-add-enclosure-to-playlist)
(general-def 'normal elfeed-show-mode-map "P" 'elfeed-show-play-enclosure))
(use-package elfeed
:commands elfeed
:config
(add-hook 'elfeed-show-mode-hook 'elfeed-setup-hook)
(setq-default elfeed-search-filter "@1-week-ago +unread +news ")
(add-to-list 'evil-normal-state-modes 'elfeed-search-mode)
(add-to-list 'evil-normal-state-modes 'elfeed-show-mode)
(setq elfeed-feeds
'(("http://www.wsj.com/xml/rss/3_7085.xml" news)
("https://www.wsj.com/xml/rss/3_7014.xml" news)
("http://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml" news)
("https://www.newyorker.com/feed/everything" news)
("https://www.economist.com/sections/business-finance/rss.xml" news)
("https://www.economist.com/sections/economics/rss.xml" news))))
#+END_SRC
Keybinding for opening Elfeed:
#+BEGIN_SRC emacs-lisp
(leader-def-key "al" 'elfeed)
#+END_SRC
* Undo Tree
#+BEGIN_SRC emacs-lisp
(use-package undo-tree
:init
(global-undo-tree-mode)
(leader-def-key "bu" 'undo-tree-visualize)
:general
('normal "u" #'undo-tree-undo)
('normal "C-r" #'undo-tree-redo)
(undo-tree-visualizer-mode-map "SPC" leader-map))
#+END_SRC
* Emojify
Because emojis make everything better.
#+BEGIN_SRC emacs-lisp
(use-package emojify)
(leader-def-key "te" 'emojify-mode)
#+END_SRC
* Mastodon
[[https://joinmastodon.org/ ][Mastodon ]] is a federated FOSS social network similar to Twitter. Let's put it in Emacs!
First, install a dependency:
#+BEGIN_SRC emacs-lisp
(use-package discover)
#+END_SRC
#+BEGIN_SRC emacs-lisp
(defun jdormit/mastodon-setup ()
(general-def 'normal mastodon-mode-map "j" #'mastodon-tl--goto-next-toot)
(general-def 'normal mastodon-mode-map "k" #'mastodon-tl--goto-prev-toot)
(general-def 'normal mastodon-mode-map "h" #'mastodon-tl--next-tab-item)
(general-def 'normal mastodon-mode-map "l" #'mastodon-tl--previous-tab-item)
(general-def 'normal mastodon-mode-map [?\t] #'mastodon-tl--next-tab-item)
(general-def 'normal mastodon-mode-map [backtab] #'mastodon-tl--previous-tab-item)
(general-def 'normal mastodon-mode-map [?\S-\t] #'mastodon-tl--previous-tab-item)
(general-def 'normal mastodon-mode-map [?\M-\t] #'mastodon-tl--previous-tab-item)
;; Navigating to other buffers:
(general-def 'normal mastodon-mode-map "N" #'mastodon-notifications--get)
(general-def 'normal mastodon-mode-map "A" #'mastodon-profile--get-toot-author)
(general-def 'normal mastodon-mode-map "U" #'mastodon-profile--show-user)
(general-def 'normal mastodon-mode-map "F" #'mastodon-tl--get-federated-timeline)
(general-def 'normal mastodon-mode-map "H" #'mastodon-tl--get-home-timeline)
(general-def 'normal mastodon-mode-map "L" #'mastodon-tl--get-local-timeline)
(general-def 'normal mastodon-mode-map "t" #'mastodon-tl--thread)
(general-def 'normal mastodon-mode-map "T" #'mastodon-tl--get-tag-timeline)
(general-def 'normal mastodon-mode-map "q" #'kill-this-buffer)
(general-def 'normal mastodon-mode-map "Q" #'kill-buffer-and-window)
;; Actions
(general-def 'normal mastodon-mode-map "c" #'mastodon-tl--toggle-spoiler-text-in-toot)
(general-def 'normal mastodon-mode-map "g" #'undefined) ;; override special mode binding
(general-def 'normal mastodon-mode-map "n" #'mastodon-toot)
(general-def 'normal mastodon-mode-map "r" #'mastodon-toot--reply)
(general-def 'normal mastodon-mode-map "u" #'mastodon-tl--update)
(general-def 'normal mastodon-mode-map "b" #'mastodon-toot--toggle-boost)
(general-def 'normal mastodon-mode-map "f" #'mastodon-toot--toggle-favourite)
(general-def 'normal mastodon-mode-map "?" #'makey-key-mode-popup-mastodon)
(general-def 'normal mastodon-profile-mode-map "F" #'mastodon-profile--open-followers)
(general-def 'normal mastodon-profile-mode-map "f" #'mastodon-profile--open-following))
(eval-and-compile
(defun mastodon-load-path ()
(expand-file-name "~/mastodon.el/lisp")))
(if (file-exists-p (mastodon-load-path))
(use-package mastodon
:load-path (lambda () (mastodon-load-path))
:init (setq mastodon-instance-url "https://mastodon.technology")
:config
(jdormit/mastodon-setup)
:commands
(mastodon
mastodon-toot))
(use-package mastodon
:init (setq mastodon-instance-url "https://mastodon.technology")
:config
(jdormit/mastodon-setup)
:commands
(mastodon
mastodon-toot)))
(jdormit/define-prefix "aM" "mastodon")
(leader-def-key "aMm" 'mastodon)
(leader-def-key "aMt" 'mastodon-toot)
#+END_SRC
* Calc
#+BEGIN_SRC emacs-lisp
(leader-def-key "ac" 'calc)
#+END_SRC
* Deadgrep
A nice Emacs UI over [[https://github.com/BurntSushi/ripgrep#installation ][ripgrep ]].
#+BEGIN_SRC emacs-lisp
(use-package deadgrep
:commands deadgrep)
(leader-def-key "fg" 'deadgrep)
(add-to-list 'evil-emacs-state-modes 'deadgrep-mode)
#+END_SRC
* RCIRC
IRC in Emacs, just in case anyone actually still uses it...
Channels:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'rcirc
(add-to-list 'rcirc-server-alist
`("znc.jeremydormitzer.com"
:port 3000
:nick "jdormit"
:user-name "jdormit/freenode"
:password ,(password-store-get "znc.jeremydormitzer.com")))
(add-to-list 'rcirc-server-alist
`("znc.jeremydormitzer.com"
:channels ("#social")
:nick "jdormit"
:user-name "jdormit/w3"
:port 3000
:password ,(password-store-get "znc.jeremydormitzer.com"))))
#+END_SRC
Key bindings:
#+BEGIN_SRC emacs-lisp
(leader-def-key "ai" 'irc)
#+END_SRC
Use evil keybindings by default:
#+BEGIN_SRC emacs-lisp
(add-to-list 'evil-normal-state-modes 'rcirc-mode)
#+END_SRC
* dumb-jump
[[https://github.com/jacktasia/dumb-jump ][Dumb-jump ]] uses ripgrep and some algorithms based on the major-mode to implement jump-to-definition.
#+BEGIN_SRC emacs-lisp
(use-package dumb-jump
:config (dumb-jump-mode))
(jdormit/define-prefix "c" "code")
(leader-def-key "cj" 'dumb-jump-go)
(leader-def-key "cp" 'dumb-jump-go-prompt)
#+END_SRC
* Dictionary
This package looks up word definitions online.
#+BEGIN_SRC emacs-lisp
(use-package define-word
:commands (define-word define-word-at-point))
(jdormit/define-prefix "r" "research")
(leader-def-key "rd" 'define-word-at-point)
(leader-def-key "rD" 'define-word)
#+END_SRC
* Gnus
An ancient newsreader.
#+BEGIN_SRC emacs-lisp
(leader-def-key "ag" 'gnus)
(general-def gnus-mode-map "SPC" leader-map)
#+END_SRC
It can read email:
#+BEGIN_SRC emacs-lisp
(setq gnus-select-method '(nnnil "")
gnus-secondary-select-methods
'((nnmaildir "jeremy@dormitzer.net"
(directory "~/.mail/jeremy-dormitzer-net"))
(nnmaildir "jeremy@getpterotype.com"
(directory "~/.mail/jeremy-getpterotype-com"))
(nnmaildir "jeremy.dormitzer@gmail.com"
(directory "~/.mail/jeremy-dormitzer-gmail-com"))
(nnmaildir "jdormitzer@hubspot.com"
(directory "~/.mail/jdormitzer-hubspot-com")))
mm-text-html-renderer 'gnus-w3m)
#+END_SRC
Or Gnus can read RSS feeds directly:
#+BEGIN_SRC emacs-lisp
;; (add-to-list 'gnus-secondary-select-methods
;; '(nnrss "http://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml"))
#+END_SRC
* Dired
Enable [[info:dired-x#Top ][Dired-X ]]:
#+BEGIN_SRC emacs-lisp
(require 'dired-x)
#+END_SRC
Preserve the leader key:
#+BEGIN_SRC emacs-lisp
(general-def dired-mode-map "SPC" leader-map)
#+END_SRC
* Crontab
Magit ships with a cool utility called =with-editor= that lets you run a shell command using the current Emacs instance as $EDITOR. This means we can define a command to edit the crontab with the current Emacs instance:
#+BEGIN_SRC emacs-lisp
(defun crontab ()
(interactive)
(with-editor-async-shell-command "crontab -e"))
#+END_SRC
* Emacs Server
In case I need an =emacsclient= for some reason.
#+BEGIN_SRC emacs-lisp
(add-hook 'after-init-hook #'server-start)
#+END_SRC
* YASnippet
YASnippet is Yet Another Snippet template system.
#+BEGIN_SRC emacs-lisp
(use-package yasnippet
:config
(unless (file-exists-p (expand-file-name "~/.emacs.d/snippets"))
(mkdir (expand-file-name "~/.emacs.d/snippets") t))
(setq yas-snippet-dirs
`(,(concat (file-name-as-directory (get-dropbox-directory)) "yasnippet")
,(expand-file-name "~/.emacs.d/snippets")))
(yas-global-mode))
(use-package yasnippet-snippets
:after (yasnippet)
:config (yasnippet-snippets-initialize))
#+END_SRC
* mpc
An Emacs interface to MPD, the Music Player Daemon
#+BEGIN_SRC emacs-lisp
(require 'mpc)
(leader-def-key "ad" #'mpc)
(add-to-list 'evil-emacs-state-modes 'mpc-mode)
(general-def mpc-mode-map "SPC" leader-map)
(general-def mpc-mode-map "a" #'mpc-playlist-add)
#+END_SRC
* Ido
Interactively do things! Some more info in [[https://masteringemacs.org/article/introduction-to-ido-mode ][this blog post ]].
#+BEGIN_SRC emacs-lisp
(setq ido-enable-flex-matching t)
(setq ido-everywhere t)
(ido-mode 1)
#+END_SRC
Enable it everywhere:
#+BEGIN_SRC emacs-lisp
(use-package ido-completing-read+
:config
(ido-ubiquitous-mode 1))
(use-package crm-custom
:config
(crm-custom-mode 1))
#+END_SRC
The horizontal completion is ugly. Make it vertical:
#+BEGIN_SRC emacs-lisp
(use-package ido-vertical-mode
:config
(ido-vertical-mode 1)
(setq ido-vertical-define-keys 'C-n-and-C-p-only))
#+END_SRC
The default auto-merge time is too short.
#+BEGIN_SRC emacs-lisp
(setq ido-auto-merge-delay-time 1.5)
#+END_SRC
* graphviz
#+BEGIN_SRC emacs-lisp
(use-package graphviz-dot-mode
:mode (("\\.dot\\'" . graphviz-dot))
:init
(add-to-list 'org-src-lang-modes '("dot" . graphviz-dot)))
#+END_SRC
** Functions
A function that converts a lisp form into Graphviz format, e.g.:
#+BEGIN_EXAMPLE
'(a ((label . "Node A"))
(b ((label . "Node B"))
(d ((label . "Node D"))))
(c ((label . "Node C"))
(e ((label . "Node E")))))
#+END_EXAMPLE
becomes:
#+BEGIN_EXAMPLE
digraph {
a[label="Node A"];
b[label="Node B"];
c[label="Node C"];
d[label="Node D"];
e[label="Node E"];
a -> {b, c};
b -> d;
c -> e;
}
#+END_EXAMPLE
#+BEGIN_SRC emacs-lisp
(defun graphviz-make-node-string (id attrs)
"Makes a Graphviz Dot string representing a node with attributes"
(if attrs
(format
"%s[%s];"
id
(mapconcat 'identity
(mapcar
(lambda (attr)
(format "%s=\"%s\"" (car attr) (cdr attr)))
attrs)
", "))
(format "%s;" id)))
(defun graphviz-make-edge-string (id children)
"Makes a Graphviz Dot string representing the edges between id and children"
(when children
(format "%s -> {%s}"
id
(mapconcat
'identity
(mapcar
(lambda (child)
(format "%s" (car child)))
children)
"; "))))
(defun graphviz-parse-graph (graph)
"Parses a graph into nodes and edges represented in the dot language.
Returns an alist ((nodes (<node strings >)) (edges (<edge strings >)))"
(when graph
(let* ((id (car graph))
(attrs (cadr graph))
(children (cddr graph))
(child-graphs (mapcar #'graphviz-parse-graph children)))
`((nodes ,(cons (graphviz-make-node-string id attrs)
(apply #'append
(mapcar (lambda (child)
(cadr (assoc 'nodes child)))
child-graphs))))
(edges ,(let ((edge-string (graphviz-make-edge-string id children))
(child-edges (apply #'append
(mapcar (lambda (child)
(cadr (assoc 'edges child)))
child-graphs))))
(if edge-string
(cons edge-string child-edges)
child-edges)))))))
(defun graphviz-compile-graph (graph)
"Transpiles a graph defined as a lisp form to the Graphviz Dot language"
(let* ((graph (graphviz-parse-graph graph))
(nodes (cadr (assoc 'nodes graph)))
(edges (cadr (assoc 'edges graph))))
(message "%s" graph)
(format "digraph {\n %s\n %s\n}"
(mapconcat 'identity nodes "\n ")
(mapconcat 'identity edges "\n "))))
#+END_SRC
* HideShow
[[help:hs-minor-mode ][hs-minor-mode ]] enables comment and code-folding. It's useful almost everywhere, so just enable it:
#+BEGIN_SRC emacs-lisp
(add-hook 'prog-mode-hook (lambda () (hs-minor-mode 1)))
#+END_SRC
* Slack
#+BEGIN_SRC emacs-lisp
(use-package slack
:commands (slack-start)
:init
(setq slack-buffer-emojify nil)
(setq slack-prefer-current-team t)
:config
(slack-register-team
:name "hubspot"
:default t
:client-id "2152023175.242841693617"
:client-secret "5a753800968d3a9727ccb83b470db696"
:token "xoxp-2152023175-199371301809-242880891761-652436eeff14e6e9d083a131ed0eedb4"
:subscribed-channels '(automation-platform
support-workflow
workflow-extensions
workflows
workflows-alerts
workflows-backend
workflows-yt)
:full-and-display-names t)
:general
('normal slack-info-mode-map ",u" #'slack-room-update-messages)
('normal slack-mode-map
",c" 'slack-buffer-kill
",ra" 'slack-message-add-reaction
",rr" 'slack-message-remove-reaction
",rs" 'slack-message-show-reaction-users
",pl" 'slack-room-pins-list
",pa" 'slack-message-pins-add
",pr" 'slack-message-pins-remove
",mm" 'slack-message-write-another-buffer
",me" 'slack-message-edit
",md" 'slack-message-delete
",u" 'slack-room-update-messages
",2" 'slack-message-embed-mention
",3" 'slack-message-embed-channel
"\C-n" 'slack-buffer-goto-next-message
"\C-p" 'slack-buffer-goto-prev-message)
('normal slack-edit-message-mode-map
",k" #'slack-message-cancel-edit
",s" #'slack-message-send-from-buffer
",2" #'slack-message-embed-mention
",3" #'slack-message-embed-channel))
#+END_SRC
#+BEGIN_SRC emacs-lisp
(use-package alert
:commands (alert)
:init
(setq alert-default-style (jdormit-get-mu4e-alert-style)))
#+END_SRC
* Matrix
#+BEGIN_SRC emacs-lisp
(use-package matrix-client
:quelpa ((matrix-client :fetcher github :repo "alphapapa/matrix-client.el"
:files (:defaults "logo.png" "matrix-client-standalone.el.sh"))))
#+END_SRC
* EMMS
The Emacs Multi-Media System
#+BEGIN_SRC emacs-lisp
(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/emms")
(when (require 'emms-setup nil t)
(emms-all)
(emms-default-players)
(require 'emms-info-libtag)
(setq emms-info-functions
'(emms-info-libtag)
emms-source-file-default-directory
(concat (get-dropbox-directory) "/music"))
(jdormit/define-prefix "ae" "emms")
(add-to-list 'evil-emacs-state-modes 'emms-browser-mode)
(leader-def-key "aeb" 'emms-smart-browse)
(leader-def-key "aes" 'emms-start)
(leader-def-key "aeS" 'emms-stop)
(leader-def-key "aeP" 'emms-pause)
(leader-def-key "aen" 'emms-next)
(leader-def-key "aep" 'emms-previous)
(leader-def-key "aee" 'emms)
(leader-def-key "aed" 'emms-play-directory-tree))
#+END_SRC