dotfiles/emacs/init.org
Jeremy Dormitzer 8ab1620e52 Require evil-magit at the right time
...although it still seems to not be working? TODO
2020-05-26 21:45:38 -04:00

6409 lines
213 KiB
Org Mode
Executable File

-*- eval: (add-hook 'after-save-hook 'org-babel-tangle 0 t) -*-
#+PROPERTY: header-args :results silent
#+PROPERTY: header-args:emacs-lisp :lexical t :tangle ~/.emacs.el
This is a literate init file holding my Emacs configuration. It is
initially loaded by a [[file:.emacs.d/init.el::;;; -*- lexical-binding: t; -*-][bootstrap file]] that lives at ~/.emacs.d/init.el;
after the initial bootstrapping it writes itself to ~/.emacs.el. Since
~/.emacs.el takes priority over ~/.emacs.d/init.el, after the initial
bootstrapping process the tangled ~/.emacs.el file will get loaded
without needing to load the bootstrap file first.
* Prelude
Enables lexical binding for everything in init.el:
#+BEGIN_SRC emacs-lisp
;;; -*- lexical-binding: t; -*-
#+END_SRC
** Garbage collection
Some GC tweaks [[https://github.com/hlissner/doom-emacs/blob/develop/docs/faq.org#how-does-doom-start-up-so-quickly]["borrowed" from Doom emacs]].
Turn off GC during init and restore it afterwards:
#+BEGIN_SRC emacs-lisp
(setq gc-cons-threshold most-positive-fixnum
gc-cons-percentage 0.6)
(add-hook 'emacs-startup-hook
(lambda ()
(setq gc-cons-threshold 100000000
gc-cons-percentage 0.1)))
#+END_SRC
Also suppress GC for 1 second after the minibuffer is active to avoid stuttering autocompletion and other GC hangups:
#+BEGIN_SRC emacs-lisp
(defun defer-garbage-collection ()
(setq gc-cons-threshold most-positive-fixnum))
(defun restore-garbage-collection ()
(run-at-time
1 nil (lambda () (setq gc-cons-threshold 100000000))))
(add-hook 'minibuffer-setup-hook #'defer-garbage-collection)
(add-hook 'minibuffer-exit-hook #'restore-garbage-collection)
#+END_SRC
** Unset file-handler-alist during initialization
Another optimization from [[https://github.com/hlissner/doom-emacs/blob/develop/docs/faq.org#how-does-doom-start-up-so-quickly][Doom Emacs]].
#+BEGIN_SRC emacs-lisp
(defvar file-name-handler-alist-backup file-name-handler-alist)
(setq file-name-handler-alist nil)
(add-hook 'emacs-startup-hook
(lambda ()
(setq file-name-handler-alist file-name-handler-alist-backup)))
#+END_SRC
** Variables
#+BEGIN_SRC emacs-lisp
(setq vc-follow-symlinks t
frame-resize-pixelwise t
tab-always-indent 'complete
enable-recursive-minibuffers t
read-process-output-max (* 1024 1024))
(setq-default indent-tabs-mode nil)
#+END_SRC
* Default directory
#+BEGIN_SRC emacs-lisp
(cd "~")
#+END_SRC
* Packages
Load [[https://github.com/raxod502/straight.el][straight.el]] to manage package installation:
#+BEGIN_SRC emacs-lisp
(defvar bootstrap-version)
(unless (boundp 'bootstrapping-init)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage)))
#+END_SRC
`use-package` is a macro that simplifies installing and loading packages.
#+BEGIN_SRC emacs-lisp
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)
#+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
* Load org mode
Load org-mode early to [[https://github.com/raxod502/straight.el#the-wrong-version-of-my-package-was-loaded][avoid a version clash]].
#+BEGIN_SRC emacs-lisp
(use-package org
:straight org-plus-contrib
:commands (org-element-map)
:mode (("\\.org\\'" . org-mode)))
;; Annoying that this is necessary...
(require 'org)
(require 'org-refile)
(require 'org-protocol)
#+END_SRC
* Doom themes
#+BEGIN_SRC emacs-lisp
(use-package doom-themes)
(use-package doom-modeline)
(doom-modeline-mode 1)
#+END_SRC
* Ewal theme
#+BEGIN_SRC emacs-lisp
(use-package doom-ewal-themes
:straight (doom-ewal-themes
:host github
:repo "jdormit/doom-ewal-themes"
:files ("themes" :defaults)))
#+END_SRC
* 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 "custom.el" user-emacs-directory))
(load custom-file t)
#+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" "LOLA_HOME"
"MODELS_HOME" "LOLA_TRAVEL_SERVICE_HOME" "WORKON_HOME"
"PIPENV_VERBOSITY" "PIPENV_DONT_LOAD_ENV"
"PIPENV_MAX_DEPTH")
exec-path-from-shell-check-startup-files nil)
(exec-path-from-shell-initialize))
#+END_SRC
* General
Better keybinding.
#+BEGIN_SRC emacs-lisp
(use-package general
:init
(setq general-override-states '(insert
emacs
hybrid
normal
visual
motion
operator
replace)))
#+END_SRC
Delete trailing whitespace on save:
#+BEGIN_SRC emacs-lisp
(defvar *should-delete-trailing-whitespace* t)
(defun delete-trailing-whitespace-hook-fn ()
(when *should-delete-trailing-whitespace*
(delete-trailing-whitespace)))
(add-hook 'before-save-hook #'delete-trailing-whitespace-hook-fn)
#+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))
(general-def "C-M-i" #'company-complete)
(general-def "M-<tab>" #'company-complete)
#+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)
(which-key-add-key-based-replacements
(concat "," " " binding)
name))
#+END_SRC
* Evil Mode
Because I like modal editing and dislike RSI.
#+BEGIN_SRC emacs-lisp
(use-package evil
:init
(setq evil-want-keybinding nil)
:config
(evil-mode 1))
#+END_SRC
Make undo not undo paragraphs at a time:
#+BEGIN_SRC emacs-lisp
(setq evil-want-fine-undo t)
#+END_SRC
Add a convenience function for making buffer-local ex-commands:
#+BEGIN_SRC emacs-lisp
(defun evil-ex-define-local-cmd (cmd function)
(set (make-local-variable 'evil-ex-commands) (copy-tree evil-ex-commands))
(evil-ex-define-cmd cmd function))
#+END_SRC
** evil-collection
A collection of evil bindings for various modes
#+BEGIN_SRC emacs-lisp
(use-package evil-collection
:after (evil)
:config
(setq evil-collection-company-use-tng nil)
(evil-collection-init))
#+END_SRC
** leader key
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
:keymaps 'override
:states '(normal visual motion)
"SPC" nil)
(general-create-definer leader-def-key
:keymaps 'override
:states '(normal visual motion)
:prefix leader
:prefix-map 'leader-map)
#+END_SRC
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "?" "help")
(leader-def-key "?" help-map)
#+END_SRC
** evil-snipe
#+BEGIN_SRC emacs-lisp
(use-package evil-snipe
:after (evil)
:config
(evil-snipe-mode 1)
(evil-snipe-override-mode 1)
(with-eval-after-load 'magit
(add-hook 'magit-mode-hook 'turn-off-evil-snipe-override-mode))
(with-eval-after-load 'prodigy
(add-hook 'prodigy-mode-hook 'turn-off-evil-snipe-mode))
(with-eval-after-load 'pass
(add-hook 'pass-mode-hook 'turn-off-evil-snipe-mode)))
#+END_SRC
** evil-commentary
Adds Evil commands to comment out lines of code:
#+BEGIN_SRC emacs-lisp
(use-package evil-commentary
:after (evil)
:hook ((prog-mode . evil-commentary-mode)))
#+END_SRC
** Additional keybindings
#+BEGIN_SRC emacs-lisp
(general-def 'normal "zM" #'hs-hide-level :keymaps 'override)
(general-def 'normal "z=" #'text-scale-increase :keymaps 'override)
(general-def 'normal "z-" #'text-scale-decrease :keymaps 'override)
(general-def 'normal "z0" #'text-scale-adjust :keymaps 'override)
(general-def 'normal view-mode-map "0" nil :keymaps 'override)
(general-def 'normal prodigy-view-mode-map "0" nil :keymaps 'override)
(general-def 'normal messages-buffer-mode-map "SPC" leader-map :keymaps 'override)
#+END_SRC
* Multiple cursors
#+BEGIN_SRC emacs-lisp
(use-package evil-mc
:defer 0
:config
(global-evil-mc-mode)
(general-def '(normal visual)
"gs" evil-mc-cursors-map
"M-n" #'evil-mc-make-and-goto-next-cursor
"M-p" #'evil-mc-make-and-goto-prev-cursor
"C-n" #'evil-mc-make-and-goto-next-match
"C-t" #'evil-mc-skip-and-goto-next-match
"C-p" #'evil-mc-make-and-goto-prev-match))
#+END_SRC
#+BEGIN_SRC emacs-lisp
(use-package evil-multiedit
:defer 0
:config
(evil-multiedit-default-keybinds))
#+END_SRC
* Transient
A framework for creating Magit-style popups:
#+BEGIN_SRC emacs-lisp
(use-package transient
:commands (define-transient-command))
#+END_SRC
* Hydra
[[https://github.com/abo-abo/hydra][Hydra]]s are convenient keybinding menus.
#+BEGIN_SRC emacs-lisp
(use-package hydra
:defer t)
#+END_SRC
* Magit
Magit is objectively the best Git interface.
#+BEGIN_SRC emacs-lisp
(use-package magit
:commands (magit-status
magit-blame
magit-find-file
magit-name-local-branch))
#+END_SRC
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "g" "git")
(leader-def-key "gs" #'magit-status)
(leader-def-key "gg" #'magit-file-dispatch)
(leader-def-key "gf" #'magit-find-file)
#+END_SRC
Use ido-mode for completion within Magit:
#+BEGIN_SRC emacs-lisp
;; (setq magit-completing-read-function 'magit-ido-completing-read)
#+END_SRC
** Forge
[[https://github.com/magit/forge][Forge]] is an extension for Magit that lets it interact with code forges (e.g. GitHub).
#+BEGIN_SRC emacs-lisp
(use-package forge
:after (magit)
:config
(add-to-list 'forge-alist '("git.jeremydormitzer.com"
"git.jeremydormitzer.com/api/v1"
"git.jeremydormitzer.com"
forge-gitea-repository))
:general
((normal motion) magit-mode-map "yu" #'forge-copy-url-at-point-as-kill)
((normal motion visual) forge-topic-list-mode-map
"y" #'forge-copy-url-at-point-as-kill
"q" #'quit-window))
#+END_SRC
** evil-magit
Evil keybindings for magit!
#+BEGIN_SRC emacs-lisp
(use-package evil-magit
:after (evil magit forge)
:config
(with-eval-after-load 'magit
(require 'evil-magit))
:general
('normal magit-mode-map "SPC" leader-map))
#+END_SRC
** Transient
#+BEGIN_SRC emacs-lisp
(setq transient-default-level 7)
#+END_SRC
* git-link
Open files in Git forges (GitHub, GitLab, etc.):
#+BEGIN_SRC emacs-lisp
(use-package git-link
:init
(defun git-link-copy ()
(interactive)
(let ((git-link-open-in-browser nil))
(call-interactively 'git-link)))
(leader-def-key "gl" #'git-link)
(leader-def-key "gy" #'git-link-copy)
:custom
(git-link-open-in-browser t)
(git-link-remote-alist '(("git.sr.ht" git-link-sourcehut)
("github" git-link-github)
("bitbucket" git-link-bitbucket)
("gitorious" git-link-gitorious)
("gitlab" git-link-gitlab)
("visualstudio\\|azure" git-link-azure)
("git.jeremydormitzer.com" git-link-bitbucket))))
#+END_SRC
* with-editor
A utility from the author of Magit to run shell commands using the current Emacs instance as $EDITOR.
#+BEGIN_SRC emacs-lisp
(shell-command-with-editor-mode)
(add-hook 'shell-mode-hook #'with-editor-export-editor)
(add-hook 'term-exec-hook #'with-editor-export-editor)
(add-hook 'eshell-mode-hook #'with-editor-export-editor)
#+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
(defun password-store-synchronize ()
(interactive)
(with-editor-async-shell-command "pass git pull && pass git push"))
(use-package password-store
:defer t
:if (executable-find "pass")
:config
(setq password-store-password-length 20)
:init
(leader-def-key "P" 'password-store-copy)
(epa-file-enable))
(use-package pass
:if (executable-find "pass")
:commands pass
:general
('(normal motion visual) pass-mode-map "S" #'password-store-synchronize))
(leader-def-key "ap" #'pass)
(setq auth-sources '("~/.authinfo" password-store))
#+END_SRC
* Emacs Lisp
** Packages
Some helpful ELisp packages:
- [[https://github.com/kiwanami/emacs-deferred][deferred]] is an async API library
- [[https://github.com/magnars/s.el][s.el]] is a string manipulation library
- [[https://github.com/magnars/dash.el][dash.el]] is a list manipulation library
- [[https://github.com/rejeep/f.el][f.el]] is a file manipulation library
- [[https://github.com/tkf/emacs-request][request]] is an HTTP library
#+BEGIN_SRC emacs-lisp
(use-package deferred
:commands (deferred:next
deferred:nextc
deferred:error
deferred:cancel
deferred:watch
deferred:wait
deferred:$
deferred:loop
deferred:parallel
deferred:earlier
deferred:call
deferred:apply
deferred:process
deferred:process-buffer
deferred:wait-idle
deferred:url-retrieve
deferred:url-get
deferred:url-post
deferred:new
deferred:succeed
deferred:fail
deferred:callback
deferred:callback-post
deferred:errorback
deferred:errorback-post
deferred:try
deferred:timeout
deferred:process))
(use-package s
:defer t
:init
(add-hook 'emacs-startup-hook (lambda () (require 's)))
:config
(defun camel-case (&optional arg)
(interactive "P")
(let* ((bounds (bounds-of-thing-at-point 'symbol))
(word (buffer-substring (car bounds) (cdr bounds)))
(camel (if arg (s-upper-camel-case word)
(s-lower-camel-case word))))
(delete-region (car bounds) (cdr bounds))
(insert camel))))
(use-package dash
:defer t
:init
(add-hook 'emacs-startup-hook (lambda () (require 'dash))))
(use-package dash-functional
:defer t
:init
(add-hook 'emacs-startup-hook (lambda () (require 'dash-functional))))
(use-package f
:defer t
:init
(add-hook 'emacs-startup-hook (lambda () (require 'f))))
(use-package request
:commands (request request-deferred))
#+END_SRC
** Editing Elisp
#+BEGIN_SRC emacs-lisp
(general-def '(normal motion) emacs-lisp-mode-map "C-c C-c" #'eval-defun :keymaps 'override)
(general-def '(normal motion insert) lisp-interaction-mode-map "C-c C-c" #'eval-print-last-sexp :keymaps 'override)
(add-hook 'ielm-mode-hook 'smartparens-strict-mode)
#+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 "%F %r" (/ millis 1000))))
#+END_SRC
The same but for seconds:
#+BEGIN_SRC emacs-lisp
(defun format-epoch-seconds (seconds)
(interactive "nTimestamp: ")
(message (format-time-string "%F %r" seconds)))
#+END_SRC
Checking if a buffer contains a string:
#+BEGIN_SRC emacs-lisp
(defun buffer-contains-substring (string)
(save-excursion
(save-match-data
(goto-char (point-min))
(search-forward string nil t))))
#+END_SRC
Pretty-print JSON:
#+BEGIN_SRC emacs-lisp
(defun pprint-json (raw-json)
(with-temp-buffer
(insert raw-json)
(json-pretty-print (point-min) (point-max))
(buffer-substring (point-min) (point-max))))
#+END_SRC
Load environment variables into Emacs from a shell script:
#+BEGIN_SRC emacs-lisp
(cl-defun extract-vars-from-env-file (file &key dir)
"Extracts an alist of variable name to value from
a bash script that exports environment variables."
(let ((file (expand-file-name file))
(var-re "\\(.+?\\)=\\(.+\\)$")
(env '()))
(with-temp-buffer
(cd (expand-file-name (or dir (file-name-directory file))))
(insert (shell-command-to-string (concat "source "
(shell-quote-argument file)
" > /dev/null && env")))
(goto-char (point-min))
(save-match-data
(while (re-search-forward var-re nil t)
(push (cons (match-string 1) (match-string 2)) env))))
env))
(defun source-env-file (file)
(interactive "fFile: \n")
(let ((env (extract-vars-from-env-file file)))
(dolist (binding env)
(setenv (car binding) (cdr binding)))))
(cl-defmacro with-env-from-file (file &rest body)
(declare (indent 1))
(let ((env-var (make-symbol "the-env"))
(path-var (make-symbol "the-path")))
`(let* ((,env-var (extract-vars-from-env-file ,file))
(,path-var (assoc "PATH" ,env-var))
(exec-path
(if ,path-var
(append (split-string (cdr ,path-var) ":") exec-path)
exec-path))
(process-environment
(append
(mapcar
(lambda (elt) (format "%s=%s" (car elt) (cdr elt)))
,env-var)
process-environment)))
,@body)))
(cl-defun call-with-env-from-file (file callback &key dir)
(let* ((env (extract-vars-from-env-file file :dir dir))
(path (assoc "PATH" env))
(exec-path
(if path
(append (split-string (cdr path) ":") exec-path)
exec-path))
(process-environment
(append (mapcar (lambda (elt) (format "%s=%s" (car elt) (cdr elt))) env)
process-environment)))
(funcall callback)))
(defmacro with-env (env &rest body)
(declare (indent 1))
`(let* ((process-environment
(append
(mapcar
(lambda (elt) (format "%s=%s" (car elt) (cdr elt)))
,env)
process-environment)))
,@body))
#+END_SRC
Convenience macro to run some code in a particular default-directory:
#+BEGIN_SRC emacs-lisp
(defmacro with-default-directory (dir &rest body)
(declare (indent 1))
`(let ((default-directory ,dir))
,@body))
#+END_SRC
#+BEGIN_SRC emacs-lisp
(defun random-alnum (&optional n)
(let* ((n-chars (or n 1))
(alnum "abcdefghijklmnopqrstuvwxyz0123456789")
(result ""))
(dotimes (_ n-chars result)
(let ((i (% (abs (random)) (length alnum))))
(setq result
(concat result (substring alnum i (1+ i))))))))
#+END_SRC
A handy function to colorize a buffer with ANSI escape characters in it:
#+BEGIN_SRC emacs-lisp
(defun ansi-color (&optional begin end)
(interactive)
(let ((begin (or begin (point-min)))
(end (or end (point-max)))
(inhibit-read-only t))
(ansi-color-apply-on-region begin end)))
#+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
** Process handling
Some utilities for calling out to other processes.
#+BEGIN_SRC emacs-lisp
(defun make-process-sentinel (success err)
"Makes a process sentinel that calls `success` on success and `err` on error"
(lambda (proc event)
(cond ((string-match-p "finished" event) (funcall success))
(t (funcall err)))))
(defun make-success-err-msg-sentinel (buf success-msg err-msg &optional kill-on-err)
(make-process-sentinel
(lambda ()
(message success-msg)
(kill-buffer buf))
(lambda ()
(message err-msg)
(when kill-on-err
(kill-buffer buf)))))
#+END_SRC
A function to call a process passing some string as stdin and returning the process output:
#+BEGIN_SRC emacs-lisp
(defun make-process-fn (program &rest args)
"Returns a function that, when called, call `program` with arguments `args`,
passing the function argument as stdin"
(lambda (&optional input)
(with-temp-buffer
(if input
(progn
(insert input)
(apply #'call-process-region (point-min) (point-max) program t t nil args))
(apply #'call-process program nil t nil args))
(buffer-substring-no-properties (point-min) (point-max)))))
#+END_SRC
The same function but for commands that need to run in a shell:
#+BEGIN_SRC emacs-lisp
(defun make-shell-fn (program &rest args)
"Returns a function that, when called, calls `program` in a shell
with arguments `args`, passing the function argument as stdin"
(lambda (&optional input)
(let ((cmd (combine-and-quote-strings `(,program ,@args))))
(with-temp-buffer
(if input
(progn
(insert input)
(call-shell-region (point-min) (point-max) cmd t t))
(call-process-shell-command cmd nil t))
(buffer-substring-no-properties (point-min) (point-max))))))
#+END_SRC
Running a shell command as sudo:
#+BEGIN_SRC emacs-lisp
(defun sudo-shell-command (command)
(with-temp-buffer
(cd "/sudo::/")
(shell-command command)))
#+END_SRC
** Buffer switch hooks
I want to be able to run code whenever I switch to a buffer running certain modes. The code to run is stored in a alist mapping mode names to lists of code to run (stored as a raw data structure to be eval'ed):
#+BEGIN_SRC emacs-lisp
(defvar buffer-mode-hooks '())
#+END_SRC
To add a new hook, push the code to run onto the correct list:
#+BEGIN_SRC emacs-lisp
(defun add-buffer-mode-hook (mode fn)
(if-let ((existing-entry (assoc mode buffer-mode-hooks)))
(push fn (cdr existing-entry))
(let ((new-entry `(,mode . (,fn))))
(push new-entry buffer-mode-hooks))))
#+END_SRC
Whenever the buffer changes, look up the major-mode to see if there is any code to run:
#+BEGIN_SRC emacs-lisp
(defun run-buffer-mode-hooks ()
(when-let ((entry (assoc major-mode buffer-mode-hooks)))
(dolist (fn (cdr entry))
(funcall fn))))
(add-hook 'buffer-list-update-hook #'run-buffer-mode-hooks)
#+END_SRC
** Aliases
#+BEGIN_SRC emacs-lisp
(defalias 'doc 'describe-symbol)
#+END_SRC
** Miscellaneous
#+BEGIN_SRC emacs-lisp
(setq warning-suppress-types
'((undo discard-info)))
#+END_SRC
* 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 (Personal)"))
(expand-file-name "~/Dropbox (Personal)"))
(t (expand-file-name "~/Dropbox"))))
#+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.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
(with-eval-after-load 'evil
(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 'cider-stacktrace-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
** 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 ":" #'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 "fd" 'dired)
(leader-def-key "fs" 'sudo-find-file)
(leader-def-key "ft" 'auto-revert-tail-mode)
(leader-def-key "fp" 'find-file-at-point)
(general-def '(normal motion visual) "gf" 'find-file-at-point)
#+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)
(leader-def-key "ws" 'window-swap-states)
#+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 "bn" #'next-buffer)
(leader-def-key "bp" #'previous-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 "Fo" #'other-frame)
(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)
(leader-def-key "tf" 'auto-fill-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
** Help Buffers
#+BEGIN_SRC emacs-lisp
(general-def 'motion help-mode-map "TAB" #'forward-button)
#+END_SRC
** Code commands
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "c" "code")
(leader-def-key "cd" #'xref-find-definitions)
(leader-def-key "cr" #'xref-find-references)
(leader-def-key "ca" #'xref-find-apropos)
(general-def 'normal "M-." #'xref-find-definitions)
#+END_SRC
** Process list
#+BEGIN_SRC emacs-lisp
(general-def '(normal motion visual) process-menu-mode-map
"d" #'process-menu-delete-process)
#+END_SRC
* Evil-numbers
Handy functions to increment/decrement numbers at point:
#+BEGIN_SRC emacs-lisp
(use-package evil-numbers
:straight (:host github :repo "janpath/evil-numbers")
:general
((normal visual motion) "g+" 'evil-numbers/inc-at-pt)
((normal visual motion) "g-" 'evil-numbers/dec-at-pt)
((normal visual motion) "g C-+" 'evil-numbers/inc-at-pt-incremental)
((normal visual motion) "g C--" 'evil-numbers/dec-at-pt-incremental))
#+END_SRC
* Winner
Winner is a minor mode that keeps an undo/redo history of the window configuration:
#+BEGIN_SRC emacs-lisp
(winner-mode 1)
(leader-def-key "wn" #'winner-redo)
(leader-def-key "wp" #'winner-undo)
#+END_SRC
* Buffer move
A handy package to shift buffers between open windows:
#+BEGIN_SRC emacs-lisp
(use-package buffer-move
:general
("C-S-j" #'buf-move-down)
("C-S-k" #'buf-move-up)
("C-S-h" #'buf-move-left)
("C-S-l" #'buf-move-right))
#+END_SRC
* Info
#+BEGIN_SRC emacs-lisp
(use-package info
:init
(defhydra hydra-info (:color blue
:hint nil)
"
Info-mode:
^^_]_ forward (next logical node) ^^_l_ast (←) _u_p (↑) _f_ollow reference _T_OC
^^_[_ backward (prev logical node) ^^_r_eturn (→) _m_enu (↓) (C-u for new window) _i_ndex _d_irectory
^^_n_ext (same level only) ^^_H_istory _g_oto (C-u for new window) _,_ next index item _c_opy node name
^^_p_rev (same level only) _<_/_t_op _b_eginning of buffer virtual _I_ndex _C_lone buffer
regex _s_earch (_S_ case sensitive) ^^_>_ final _e_nd of buffer ^^ _a_propos
_1_ .. _9_ Pick first .. ninth item in the node's menu.
"
("]" Info-forward-node)
("[" Info-backward-node)
("n" Info-next)
("p" Info-prev)
("s" Info-search)
("S" Info-search-case-sensitively)
("l" Info-history-back)
("r" Info-history-forward)
("H" Info-history)
("t" Info-top-node)
("<" Info-top-node)
(">" Info-final-node)
("u" Info-up)
("^" Info-up)
("m" Info-menu)
("g" Info-goto-node)
("b" beginning-of-buffer)
("e" end-of-buffer)
("f" Info-follow-reference)
("i" Info-index)
("," Info-index-next)
("I" Info-virtual-index)
("T" Info-toc)
("d" Info-directory)
("c" Info-copy-current-node-name)
("C" clone-buffer)
("a" info-apropos)
("1" Info-nth-menu-item)
("2" Info-nth-menu-item)
("3" Info-nth-menu-item)
("4" Info-nth-menu-item)
("5" Info-nth-menu-item)
("6" Info-nth-menu-item)
("7" Info-nth-menu-item)
("8" Info-nth-menu-item)
("9" Info-nth-menu-item)
("?" Info-summary "Info summary")
("h" Info-help "Info help")
("q" Info-exit "Info exit")
("C-g" nil "cancel" :color blue))
:general
(Info-mode-map "C-/" 'hydra-info/body))
#+END_SRC
* xref
After I select an xref reference, I want the xref buffer closed:
#+BEGIN_SRC emacs-lisp
(defun xref-goto-xref-and-quit ()
(interactive)
(xref-goto-xref t))
(general-def 'normal xref--xref-buffer-mode-map "RET"
#'xref-goto-xref-and-quit :keymaps 'override)
#+END_SRC
Don't prompt for an identifier for these xref commands:
#+BEGIN_SRC emacs-lisp
(setq xref-prompt-for-identifier
'(not xref-find-definitions
xref-find-definitions-other-window
xref-find-definitions-other-frame
xref-find-references))
#+END_SRC
Some keybindings:
#+BEGIN_SRC emacs-lisp
(general-def "M-r" #'xref-find-references)
#+END_SRC
* IBuffer
#+BEGIN_SRC emacs-lisp
(use-package ibuffer
:straight (:type built-in)
:init
(defhydra hydra-ibuffer-main (:color pink :hint nil)
"
^Navigation^ | ^Mark^ | ^Actions^ | ^View^
-^----------^-+-^----^--------+-^-------^--------+-^----^-------
_k_: ʌ | _m_: mark | _D_: delete | _g_: refresh
_RET_: visit | _u_: unmark | _S_: save | _s_: sort
_j_: v | _*_: specific | _a_: all actions | _/_: filter
-^----------^-+-^----^--------+-^-------^--------+-^----^-------
"
("j" ibuffer-forward-line)
("RET" ibuffer-visit-buffer :color blue)
("k" ibuffer-backward-line)
("m" ibuffer-mark-forward)
("u" ibuffer-unmark-forward)
("*" hydra-ibuffer-mark/body :color blue)
("D" ibuffer-do-delete)
("S" ibuffer-do-save)
("a" hydra-ibuffer-action/body :color blue)
("g" ibuffer-update)
("s" hydra-ibuffer-sort/body :color blue)
("/" hydra-ibuffer-filter/body :color blue)
("o" ibuffer-visit-buffer-other-window "other window" :color blue)
("q" quit-window "quit ibuffer" :color blue)
("." nil "toggle hydra" :color blue))
(defhydra hydra-ibuffer-mark (:color teal
:columns 5
:after-exit (hydra-ibuffer-main/body))
"Mark"
("*" ibuffer-unmark-all "unmark all")
("M" ibuffer-mark-by-mode "mode")
("m" ibuffer-mark-modified-buffers "modified")
("u" ibuffer-mark-unsaved-buffers "unsaved")
("s" ibuffer-mark-special-buffers "special")
("r" ibuffer-mark-read-only-buffers "read-only")
("/" ibuffer-mark-dired-buffers "dired")
("e" ibuffer-mark-dissociated-buffers "dissociated")
("h" ibuffer-mark-help-buffers "help")
("z" ibuffer-mark-compressed-file-buffers "compressed")
("b" hydra-ibuffer-main/body "back" :color blue))
(defhydra hydra-ibuffer-action (:color teal
:columns 4
:after-exit
(if (eq major-mode 'ibuffer-mode)
(hydra-ibuffer-main/body)))
"Action"
("A" ibuffer-do-view "view")
("E" ibuffer-do-eval "eval")
("F" ibuffer-do-shell-command-file "shell-command-file")
("I" ibuffer-do-query-replace-regexp "query-replace-regexp")
("H" ibuffer-do-view-other-frame "view-other-frame")
("N" ibuffer-do-shell-command-pipe-replace "shell-cmd-pipe-replace")
("M" ibuffer-do-toggle-modified "toggle-modified")
("O" ibuffer-do-occur "occur")
("P" ibuffer-do-print "print")
("Q" ibuffer-do-query-replace "query-replace")
("R" ibuffer-do-rename-uniquely "rename-uniquely")
("T" ibuffer-do-toggle-read-only "toggle-read-only")
("U" ibuffer-do-replace-regexp "replace-regexp")
("V" ibuffer-do-revert "revert")
("W" ibuffer-do-view-and-eval "view-and-eval")
("X" ibuffer-do-shell-command-pipe "shell-command-pipe")
("b" nil "back"))
(defhydra hydra-ibuffer-sort (:color amaranth :columns 3)
"Sort"
("i" ibuffer-invert-sorting "invert")
("a" ibuffer-do-sort-by-alphabetic "alphabetic")
("v" ibuffer-do-sort-by-recency "recently used")
("s" ibuffer-do-sort-by-size "size")
("f" ibuffer-do-sort-by-filename/process "filename")
("m" ibuffer-do-sort-by-major-mode "mode")
("b" hydra-ibuffer-main/body "back" :color blue))
(defhydra hydra-ibuffer-filter (:color amaranth :columns 4)
"Filter"
("m" ibuffer-filter-by-used-mode "mode")
("M" ibuffer-filter-by-derived-mode "derived mode")
("n" ibuffer-filter-by-name "name")
("c" ibuffer-filter-by-content "content")
("e" ibuffer-filter-by-predicate "predicate")
("f" ibuffer-filter-by-filename "filename")
(">" ibuffer-filter-by-size-gt "size")
("<" ibuffer-filter-by-size-lt "size")
("/" ibuffer-filter-disable "disable")
("b" hydra-ibuffer-main/body "back" :color blue))
:general
((normal motion visual insert emacs) ibuffer-mode-map "." 'hydra-ibuffer-main/body))
#+END_SRC
* Speedbar
Speedbar is cool but having it open in a separate frame is annoying. This makes it open in a side window in the same frame:
#+BEGIN_SRC emacs-lisp
(use-package sr-speedbar
:commands (sr-speedbar-toggle
sr-speedbar-open
sr-speedbar-select-window
sr-speedbar-exist-p)
:general
(speedbar-mode-map "q" #'sr-speedbar-close))
(defun switch-to-speedbar ()
(interactive)
(unless (sr-speedbar-exist-p)
(sr-speedbar-open))
(sr-speedbar-select-window))
(leader-def-key "S" #'switch-to-speedbar)
#+END_SRC
* Whitespace Visualation
#+BEGIN_SRC emacs-lisp
(setq whitespace-line-column 80
whitespace-style '(face lines-tail))
(leader-def-key "tw" #'whitespace-mode)
#+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
(defun winum-assign-0-to-neotree ()
(when (string-match-p (buffer-name) ".*\\NeoTree\\*.*") 10))
(use-package winum
:config
(winum-mode)
(add-to-list 'winum-assign-functions #'winum-assign-0-to-neotree)
(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
:straight (neotree :host github :repo "jaypei/emacs-neotree" :branch "dev")
:commands (neotree-project-dir neotree-toggle)
:init
(leader-def-key "d" 'neotree-project-dir)
:general
('normal neotree-mode-map "SPC" leader-map)
:config
(defun neotree-project-dir ()
"Open NeoTree using the git root."
(interactive)
(let ((project-dir (projectile-project-root))
(file-name (buffer-file-name))
(cw (selected-window)))
(neotree-toggle)
(if project-dir
(if (neo-global--window-exists-p)
(progn
(neotree-dir project-dir)
(neotree-find file-name))
(message "Could not find git project root.")))
(select-window cw t)))
(setq neo-smart-open nil
neo-force-change-root t
neo-show-hidden-files nil
neo-toggle-window-keep-p t
neo-theme (if (display-graphic-p) 'icons 'arrow)
neo-autorefresh t
projectile-switch-project-action 'neotree-project-action))
(use-package all-the-icons
:after (neotree))
#+END_SRC
And while we're here let's enable all-the-icons for dired as well:
#+BEGIN_SRC emacs-lisp
(use-package all-the-icons-dired
:after (all-the-icons)
:commands (all-the-icons-dired-mode)
:config
(add-hook 'dired-mode-hook #'all-the-icons-dired-mode))
#+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
* Smartparens/Parinfer
Smartparens enables structured editing of s-expressions and other pairs:
#+BEGIN_SRC emacs-lisp
(use-package smartparens
:hook ((prog-mode . smartparens-strict-mode))
:init
(defhydra hydra-smartparens (:hint nil)
"
Moving^^^^ Slurp & Barf^^ Wrapping^^ Sexp juggling^^^^ Destructive
------------------------------------------------------------------------------------------------------------------------
[_a_] beginning [_n_] down [_h_] bw slurp [_R_] rewrap [_S_] split [_t_] transpose [_c_] change inner [_w_] copy
[_e_] end [_N_] bw down [_H_] bw barf [_u_] unwrap [_s_] splice [_A_] absorb [_C_] change outer
[_f_] forward [_p_] up [_l_] slurp [_U_] bw unwrap [_r_] raise [_E_] emit [_k_] kill [_g_] quit
[_b_] backward [_P_] bw up [_L_] barf [_(__{__[_] wrap (){}[] [_j_] join [_o_] convolute [_K_] bw kill [_q_] quit"
;; Moving
("a" sp-beginning-of-sexp)
("e" sp-end-of-sexp)
("f" sp-forward-sexp)
("b" sp-backward-sexp)
("n" sp-down-sexp)
("N" sp-backward-down-sexp)
("p" sp-up-sexp)
("P" sp-backward-up-sexp)
;; Slurping & barfing
("h" sp-backward-slurp-sexp)
("H" sp-backward-barf-sexp)
("l" sp-forward-slurp-sexp)
("L" sp-forward-barf-sexp)
;; Wrapping
("R" sp-rewrap-sexp)
("u" sp-unwrap-sexp)
("U" sp-backward-unwrap-sexp)
("(" sp-wrap-round)
("{" sp-wrap-curly)
("[" sp-wrap-square)
;; Sexp juggling
("S" sp-split-sexp)
("s" sp-splice-sexp)
("r" sp-raise-sexp)
("j" sp-join-sexp)
("t" sp-transpose-sexp)
("A" sp-absorb-sexp)
("E" sp-emit-sexp)
("o" sp-convolute-sexp)
;; Destructive editing
("c" sp-change-inner :exit t)
("C" sp-change-enclosing :exit t)
("k" sp-kill-sexp)
("K" sp-backward-kill-sexp)
("w" sp-copy-sexp)
("q" nil)
("g" nil))
:config
(require 'smartparens-config)
:general
(prog-mode-map "C-c p" 'hydra-smartparens/body)
((normal motion visual) prog-mode-map "g p" 'hydra-smartparens/body))
(use-package evil-smartparens
:after (evil smartparens)
:hook ((smartparens-enabled . evil-smartparens-mode)))
(jdormit/define-prefix "l" "lisp")
(jdormit/define-prefix "lw" "wrap")
(leader-def-key "lwr" 'sp-wrap-round)
(leader-def-key "lws" 'sp-wrap-square)
(leader-def-key "lwc" 'sp-wrap-curly)
(leader-def-key "ls" 'sp-forward-slurp-sexp)
(leader-def-key "lb" 'sp-forward-barf-sexp)
#+END_SRC
Enable ES6 arrow functions in web-mode ("borrowed" from [[https://github.com/Fuco1/smartparens/issues/823#issuecomment-403019519][this GitHub comment]]):
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'smartparens
(defun sp-after-equals-p (_id action _context)
(when (memq action '(insert navigate))
(sp--looking-back-p "=>" 2)))
(defun sp-after-equals-skip (ms mb _me)
(when (eq ms ">")
(save-excursion
(goto-char mb)
(sp--looking-back-p "=" 1))))
(sp-local-pair '(web-mode) "<" nil
:unless '(:add sp-after-equals-p)
:skip-match 'sp-after-equals-skip-p))
#+END_SRC
Parinfer infers parens from indentation and vice-versa. Currently
disabled since it turned out to be more annoying than good...
#+BEGIN_SRC emacs-lisp
(use-package parinfer
:disabled
:init
(leader-def-key "lt" 'parinfer-toggle-mode)
(setq parinfer-extensions '(defaults
pretty-parens
evil
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
* jq
The JSON multitool.
#+BEGIN_SRC emacs-lisp
(use-package jq-mode
:commands (jq-mode jq-interactively))
#+END_SRC
* Org Mode
Notes, agenda, calendar, blogging, journaling, etc.
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "o" "org")
(leader-def-key "oa" 'org-agenda)
(leader-def-key "oc" 'org-capture)
(setq org-src-fontify-natively t
org-ellipsis ""
org-directory (concat (get-dropbox-directory) "/org")
org-link-elisp-confirm-function 'y-or-n-p
org-startup-with-inline-images t)
(add-hook 'org-mode-hook #'auto-fill-mode)
#+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
Include a timestamp when TODO entries are closed:
#+BEGIN_SRC emacs-lisp
(setq org-log-done 'time)
#+END_SRC
Tell Emacs how to open file links of various types:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'org
(add-to-list 'org-file-apps '(directory . emacs))
(add-to-list 'org-file-apps '("log" . emacs)))
#+END_SRC
A function to add ids to every heading in an Org file:
#+BEGIN_SRC emacs-lisp
(defun org-get-create-ids-all ()
(interactive)
(save-excursion
(goto-char (point-max))
(while (outline-previous-heading)
(org-id-get-create))))
#+END_SRC
** Agenda files
#+BEGIN_SRC emacs-lisp
(defun agenda-files (&optional file)
(let ((agenda-dir (concat (file-name-as-directory (get-dropbox-directory)) "org")))
(if file
(concat (file-name-as-directory agenda-dir) file)
agenda-dir)))
(setq org-agenda-files `(,(agenda-files)))
#+END_SRC
** Capture templates
#+BEGIN_SRC emacs-lisp
(setq org-capture-templates
`(("L" "Lola task" entry
(file+headline ,(agenda-files "todo.org") "Lola")
"* TODO %i%?")
("p" "Personal task" entry
(file+headline ,(agenda-files "todo.org") "Personal")
"* TODO %i%?")
("n" "Note" entry
(file ,(agenda-files "notes.org"))
"* %^{Description}\n%i%?")
("l" "Log" entry
(file ,(agenda-files "log.org"))
"* %<%Y-%m-%d %H:%M:%S>%?")))
#+END_SRC
** Refile targets
#+BEGIN_SRC emacs-lisp
(setq org-refile-use-outline-path 'file
org-refile-targets `((org-agenda-files :level . 0)
(,(agenda-files "notes.org") :level . 1)
(,(agenda-files "todo.org") :level . 1)))
#+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-todo-ignore-scheduled 'future
org-agenda-tags-todo-honor-ignore-options t
org-agenda-span 'day
org-agenda-custom-commands
'(("L" "Lola" ((tags-todo "@lola")))
("t" "TODOs"
((agenda)
(alltodo)))))
#+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 'normal org-mode-map "TAB" #'org-cycle)
(general-def 'normal org-mode-map "SPC" leader-map)
(general-def '(normal motion visual) org-mode-map "gn" #'org-next-link)
(general-def '(normal motion visual) org-mode-map "gp" #'org-previous-link)
(general-def org-mode-map "C-c e" #'org-preview-latex-fragment)
(general-def "C-c l" #'org-store-link)
#+END_SRC
Set up evil keybindings:
#+BEGIN_SRC emacs-lisp
(use-package evil-org
:after (evil org)
:hook (org-mode . evil-org-mode)
:config
(add-hook 'evil-org-mode-hook
(lambda ()
(evil-org-set-key-theme
'(textobjects
insert
navigation
additional
shift
todo))
(general-def 'insert org-mode-map [backspace] 'org-delete-backward-char)))
(require 'evil-org-agenda)
(evil-org-agenda-set-keys)
(general-def 'motion org-agenda-mode-map "SPC" leader-map)
(general-def 'motion org-agenda-mode-map "gs" 'org-save-all-org-buffers)
(general-def '(normal motion) evil-org-mode-map
"C-S-j" nil
"C-S-k" nil
"C-S-h" nil
"C-S-l" nil))
#+END_SRC
And a global keybinding to open an org file:
#+BEGIN_SRC emacs-lisp
(defun find-org-file (file)
(interactive
(list (completing-read
"Find org file: "
(directory-files (agenda-files) t))))
(find-file file))
(leader-def-key "fo" #'find-org-file)
#+END_SRC
Finally, add a helpful agenda-mode hydra:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'org-agenda
(defhydra hydra-org-agenda (:pre (setq which-key-inhibit t)
:post (setq which-key-inhibit nil)
:hint none)
"
Org agenda (_q_uit)
^Clock^ ^Visit entry^ ^Date^ ^Other^
^-----^---- ^-----------^------------ ^----^----------- ^-----^---------
_ci_ in _SPC_ in other window _ds_ schedule _gr_ reload
_co_ out _TAB_ & go to location _dd_ set deadline _._ go to today
_cq_ cancel _RET_ & del other windows _dt_ timestamp _gd_ go to date
_cj_ jump _o_ link _+_ do later ^^
^^ ^^ _-_ do earlier ^^
^^ ^^ ^^ ^^
^View^ ^Filter^ ^Headline^ ^Toggle mode^
^----^-------- ^------^--------------- ^--------^------- ^-----------^----
_vd_ day _ft_ by tag _ht_ set status _tf_ follow
_vw_ week _fr_ refine by tag _hk_ kill _tl_ log
_vt_ fortnight _fc_ by category _hr_ refile _ta_ archive trees
_vm_ month _fh_ by top headline _hA_ archive _tA_ archive files
_vy_ year _fx_ by regexp _h:_ set tags _tr_ clock report
_vn_ next span _fd_ delete all filters _hp_ set priority _td_ diaries
_vp_ prev span ^^ ^^ ^^
_vr_ reset ^^ ^^ ^^
^^ ^^ ^^ ^^
"
;; Entry
("hA" org-agenda-archive-default)
("hk" org-agenda-kill)
("hp" org-agenda-priority)
("hr" org-agenda-refile)
("h:" org-agenda-set-tags)
("ht" org-agenda-todo)
;; Visit entry
("o" link-hint-open-link :exit t)
("<tab>" org-agenda-goto :exit t)
("TAB" org-agenda-goto :exit t)
("SPC" org-agenda-show-and-scroll-up)
("RET" org-agenda-switch-to :exit t)
;; Date
("dt" org-agenda-date-prompt)
("dd" org-agenda-deadline)
("+" org-agenda-do-date-later)
("-" org-agenda-do-date-earlier)
("ds" org-agenda-schedule)
;; View
("vd" org-agenda-day-view)
("vw" org-agenda-week-view)
("vt" org-agenda-fortnight-view)
("vm" org-agenda-month-view)
("vy" org-agenda-year-view)
("vn" org-agenda-later)
("vp" org-agenda-earlier)
("vr" org-agenda-reset-view)
;; Toggle mode
("ta" org-agenda-archives-mode)
("tA" (org-agenda-archives-mode 'files))
("tr" org-agenda-clockreport-mode)
("tf" org-agenda-follow-mode)
("tl" org-agenda-log-mode)
("td" org-agenda-toggle-diary)
;; Filter
("fc" org-agenda-filter-by-category)
("fx" org-agenda-filter-by-regexp)
("ft" org-agenda-filter-by-tag)
("fr" org-agenda-filter-by-tag-refine)
("fh" org-agenda-filter-by-top-headline)
("fd" org-agenda-filter-remove-all)
;; Clock
("cq" org-agenda-clock-cancel)
("cj" org-agenda-clock-goto :exit t)
("ci" org-agenda-clock-in :exit t)
("co" org-agenda-clock-out)
;; Other
("q" nil :exit t)
("gd" org-agenda-goto-date)
("." org-agenda-goto-today)
("gr" org-agenda-redo))
(general-def '(normal visual motion insert emacs) org-agenda-mode-map "." 'hydra-org-agenda/body))
#+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
*** Github-flavored markdown
#+BEGIN_SRC emacs-lisp
(use-package ox-gfm
:defer t
:init
(with-eval-after-load 'org
(require 'ox-gfm)))
#+END_SRC
*** Jira
#+BEGIN_SRC emacs-lisp
(use-package ox-jira
:defer t
:straight (:host github :repo "stig/ox-jira.el")
:init
(with-eval-after-load 'org
(require 'ox-jira)))
#+END_SRC
** org-babel
Literate programming!
#+BEGIN_SRC emacs-lisp
(add-hook 'org-mode-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)
(ledger . t)
(sql . t)
(jq . t)
(restclient . 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
Enable async source block execution. Note: this execute the contents of the source block in a separate Emacs process, so blocks that rely on anything defined in init.org or the current Emacs process won't work as expected.
#+BEGIN_SRC emacs-lisp
(use-package ob-async
:straight (ob-async :host github :repo "astahlman/ob-async")
:defer t
:hook (org-mode . (lambda () (require 'ob-async))))
#+END_SRC
Filter out the "u" from unicode results in org tabels returned from Python source blocks:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'ob-python
(defun org-babel-python-table-or-string (results)
"Convert RESULTS into an appropriate elisp value.
If the results look like a list or tuple, then convert them into an
Emacs-lisp table, otherwise return the results as a string."
(let ((res (org-babel-script-escape results)))
(if (listp res)
(mapcar (lambda (el)
(cond
((eq el 'None) org-babel-python-None-to)
((listp el) (-filter (lambda (m) (not (eq m 'u))) el))
(t el)))
res)
res))))
#+END_SRC
** Images
#+BEGIN_SRC emacs-lisp
(setq org-image-actual-width nil)
#+END_SRC
** org-scratch
It's very useful to open a new org buffer for a quick org-babel exploration.
#+BEGIN_SRC emacs-lisp
(defun org-scratch (&optional make-new)
"Switches to an org-mode buffer not associated with any file.
If called with a prefix argument, always creates a new buffer;
otherwise, switches to the existing *org-scratch* buffer if it exists."
(interactive "P")
(let ((org-scratch-buffer-name
(generate-new-buffer-name
"*org-scratch*"
(unless make-new "*org-scratch*"))))
(switch-to-buffer org-scratch-buffer-name)
(org-mode)))
(leader-def-key "os" #'org-scratch)
#+END_SRC
** org-gcal
Integrate Google calendar with org-mode:
#+BEGIN_SRC emacs-lisp
(defun get-calendar-file (name)
(concat (file-name-as-directory (get-dropbox-directory))
"org/" name))
(use-package org-gcal
:after (org)
:commands (org-gcal-sync
org-gcal-fetch
org-gcal-post-at-point
org-gcal-delete-at-point
org-gcal-refresh-token)
:config
(setq org-gcal-client-id (password-store-get "lola-org-gcal-client-id")
org-gcal-client-secret (password-store-get "lola-org-gcal-client-secret")
org-gcal-file-alist `(("jeremydormitzer@lola.com" . ,(get-calendar-file "lola-gcal.org"))
("jeremy.dormitzer@gmail.com" . ,(get-calendar-file "personal-gcal.org"))
("lut2o2moohg6qkdsto1qfq7th4@group.calendar.google.com" . ,(get-calendar-file "j-n-gcal.org")))
org-gcal-notify-p nil))
(defun org-gcal-fetch-and-save ()
(interactive)
(deferred:$
(org-gcal-fetch)
(deferred:nextc it
(lambda ()
(dolist (entry org-gcal-file-alist)
(with-current-buffer (cdr entry)
(save-buffer)))))))
(add-hook 'emacs-startup-hook #'org-gcal-fetch-and-save)
(defun org-agenda-redo-and-fetch-gcal (&optional all)
(interactive "P")
(let ((cb (if all #'org-agenda-redo-all #'org-agenda-redo)))
(deferred:nextc (org-gcal-fetch-and-save) cb)))
(with-eval-after-load 'org-agenda
(general-def '(normal motion) org-agenda-mode-map "gR" #'org-agenda-redo-and-fetch-gcal))
#+END_SRC
** Utilities
A function to get the TITLE property of the current org buffer:
#+BEGIN_SRC emacs-lisp
(defun org-get-title (&optional contents)
(let ((raw (or contents
(buffer-substring (point-min) (point-max)))))
(with-temp-buffer
(insert raw)
(car
(org-element-map
(org-element-parse-buffer)
'keyword
(lambda (el)
(when (string-match-p "TITLE"
(org-element-property :key el))
(org-element-property :value el))))))))
#+END_SRC
* org-present
An ultra-minimalist presentation mode for Org:
#+BEGIN_SRC emacs-lisp
(use-package org-present
:commands (org-present)
:config
(defun org-present-on ()
(org-present-big)
(org-display-inline-images)
(org-present-hide-cursor)
(display-line-numbers-mode -1)
(org-present-read-only))
(defun org-present-off ()
(org-present-small)
(org-present-show-cursor)
(unless org-startup-with-inline-images
(org-remove-inline-images))
(display-line-numbers-mode)
(org-present-read-write))
(add-hook 'org-present-mode-hook #'org-present-on)
(add-hook 'org-present-mode-quit-hook #'org-present-off)
;; Redefine org-present to call org-present-narrow after running hooks
(defun org-present ()
"init."
(interactive)
(setq org-present-mode t)
(org-present-add-overlays)
(run-hooks 'org-present-mode-hook)
(org-present-narrow)
(org-present-run-after-navigate-functions))
:init
(general-def '(normal visual motion emacs) org-present-mode-keymap
"<left>" #'org-present-prev
"<right>" #'org-present-next
"C-k" #'org-present-prev
"C-j" #'org-present-next
"q" #'org-present-quit))
#+END_SRC
* link-hint
A very helpful package that provides jump-to-link functionality:
#+BEGIN_SRC emacs-lisp
(use-package link-hint
:commands (link-hint-open-link
link-hint-copy-link)
:init
(jdormit/define-prefix "ol" "link-hint")
(leader-def-key "oll" #'link-hint-open-link)
(leader-def-key "olc" #'link-hint-copy-link))
#+END_SRC
* org-cliplink
Intelligently inserts an org-mode link from the clipboard.
#+BEGIN_SRC emacs-lisp
(use-package org-cliplink
:commands (org-cliplink
org-cliplink-clipboard-content)
:general
(org-mode-map "C-c C-S-l" #'org-cliplink))
#+END_SRC
* org-board
[[https://github.com/scallywag/org-board][Org-board]] is a bookmarking/archiving system built on Org mode:
#+BEGIN_SRC emacs-lisp
(use-package org-board
:defer t
:after org
:init
;; Org-capture setup
(defvar org-capture-bookmark-last-url nil)
(defvar org-capture-bookmark-last-title nil)
(defun org-capture-bookmark-get-url ()
(let* ((clip (org-cliplink-clipboard-content))
(parsed (url-generic-parse-url clip)))
(if (url-type parsed)
clip
(read-string "Bookmark URL: "))))
(defun org-capture-bookmark-get-title (url)
(or (org-cliplink-retrieve-title-synchronously url)
(read-string "Bookmark title: ")))
(defun bookmark-file (title)
(concat (get-dropbox-directory)
(format "/org/%s.org" (org-roam--get-new-id title))))
(defun org-capture-bookmark-file ()
(let* ((url (org-capture-bookmark-get-url))
(title (org-capture-bookmark-get-title url)))
(setq org-capture-bookmark-last-url url)
(setq org-capture-bookmark-last-title title)
(bookmark-file title)))
(defun org-capture-bookmark-link ()
(format "[[%s][%s]]"
org-capture-bookmark-last-url
org-capture-bookmark-last-title))
(defun org-capture-bookmark-title ()
org-capture-bookmark-last-title)
(defun save-bookmark (url)
(save-excursion
(goto-char (point-min))
(when (search-forward "* Bookmark" nil t)
(org-board-new url)
(wallabag-add-entry url
(cl-function
(lambda (&key data &allow-other-keys)
(let ((entry-url (format "%s/view/%s"
wallabag-base-url
(alist-get 'id data))))
(message "Added bookmark to Wallabag: %s" entry-url))))))))
(defun org-capture-bookmark-after-finalize ()
"Runs `org-board-new' on the captured entry.
Also saves to Wallabag."
(let ((success (not org-note-abort))
(key (plist-get org-capture-plist :key))
(desc (plist-get org-capture-plist :description)))
(when (and success
(equal key "b")
(equal desc "Bookmark")
org-capture-bookmark-last-url)
(save-bookmark org-capture-bookmark-last-url)
(setq org-capture-bookmark-last-url nil)
(setq org-capture-bookmark-last-title nil))))
(add-hook 'org-capture-prepare-finalize-hook
#'org-capture-bookmark-after-finalize)
(add-to-list 'org-capture-templates
'("b" "Bookmark" plain
(file org-capture-bookmark-file)
"#+TITLE: %(org-capture-bookmark-title)\n\n- tags :: [[file:deft/bookmarks.org][Bookmarks]]\n- source :: %(org-capture-bookmark-link)\n%?\n* Bookmark"))
;; Org-protocol setup
(defun make-org-protocol-bookmark (url title)
(with-temp-buffer
(let ((filename (bookmark-file title)))
(save-excursion
(insert (concat (format "#+TITLE: %s\n\n" title)
"- tags :: [[file:deft/bookmarks.org][Bookmarks]]\n"
(format "- source :: [[%s][%s]]\n\n" url title)
"* Bookmark"))
(write-file filename)
(save-bookmark url)
(save-buffer)))))
(defun bookmark-via-org-protocol (url)
(org-cliplink-retrieve-title (url-unhex-string url) #'make-org-protocol-bookmark))
(add-to-list 'org-protocol-protocol-alist
'("Bookmark"
:protocol "bookmark"
:function bookmark-via-org-protocol
:kill-client t))
:config
(add-to-list 'org-board-wget-switches "--recursive")
(add-to-list 'org-board-wget-switches "--level=1")
(add-to-list 'org-board-wget-switches "--span-hosts")
;; Use w3m instead of eww to open org-board archived links
(advice-add 'org-board-open-with :around
(lambda (oldfn filename-string arg &rest args)
(cond
((not (file-exists-p filename-string)) 1)
((and filename-string
(or (and arg (eq org-board-default-browser 'system))
(and (not arg) (eq org-board-default-browser 'eww))))
(let ((filename (concat "file://"
(s-chop-prefix "file://"
filename-string))))
(w3m filename t)
0))
(:else (apply oldfn filename-string arg args)))))
:general
(org-mode-map "C-c b" org-board-keymap))
#+END_SRC
* org-rifle
Quickly find stuff in Org files:
#+BEGIN_SRC emacs-lisp
(use-package helm-org-rifle
:defer t
:init
(defvar helm-org-rifle-commands-map (make-sparse-keymap))
(general-def helm-org-rifle-commands-map
"r" #'helm-org-rifle
"a" #'helm-org-rifle-agenda-files
"b" #'helm-org-rifle-current-buffer
"d" #'helm-org-rifle-directories
"f" #'helm-org-rifle-files
"o" #'helm-org-rifle-org-directory
"R" #'helm-org-rifle-occur
"A" #'helm-org-rifle-occur-agenda-files
"B" #'helm-org-rifle-occur-current-buffer
"D" #'helm-org-rifle-occur-directories
"F" #'helm-org-rifle-occur-files
"O" #'helm-org-rifle-occur-org-directory)
(leader-def-key "or" helm-org-rifle-commands-map)
(jdormit/define-prefix "or" "org-rifle"))
#+END_SRC
* Org Noter
[[https://github.com/weirdNox/org-noter][Org Noter]] lets me take org-mode notes on PDFs, epubs, and DocView files:
#+BEGIN_SRC emacs-lisp
(use-package org-noter
:commands org-noter
:general
((normal visual motion) org-noter-doc-mode-map "i" #'org-noter-insert-note)
((normal visual motion) org-noter-doc-mode-map "q" #'org-noter-kill-session)
((normal visual motion) org-noter-doc-mode-map "C-M-n" #'org-noter-sync-next-note)
((normal visual motion) org-noter-doc-mode-map "C-M-p" #'org-noter-sync-prev-note)
((normal visual motion) org-noter-doc-mode-map "M-." #'org-noter-sync-current-page-or-chapter)
((normal visual motion) org-noter-doc-mode-map "M-i" #'org-noter-insert-precise-note)
((normal visual motion) org-noter-doc-mode-map "M-n" #'org-noter-sync-next-page-or-chapter)
((normal visual motion) org-noter-doc-mode-map "M-p" #'org-noter-sync-prev-page-or-chapter)
((normal visual motion) org-noter-doc-mode-map "C-M-." #'org-noter-sync-current-note))
#+END_SRC
* Org Roam
[[https://org-roam.readthedocs.io/en/develop/][Org-roam]] is another backlink package for org-mode:
#+BEGIN_SRC emacs-lisp
(use-package org-roam
:after org
:straight (:host github :repo "jethrokuan/org-roam")
:commands
(org-roam
org-roam-today
org-roam-find-file
org-roam-insert
org-roam-show-graph
org-roam--get-new-id)
:hook
(after-init . org-roam-mode)
:custom
(org-roam-directory (concat (get-dropbox-directory) "/org"))
:init
(leader-def-key "fo" #'org-roam-find-file)
(leader-def-key "of" #'org-roam-find-file)
(defvar org-roam-map (make-sparse-keymap))
(leader-def-key "on" org-roam-map)
(jdormit/define-prefix "on" "org-roam")
(which-key-add-key-based-replacements "C-c n" "org-roam")
(which-key-add-major-mode-key-based-replacements
'org-mode "gn" "org-roam")
:config
(add-hook 'org-roam-backlinks-mode-hook #'olivetti-mode)
:general
(org-roam-map "l" #'org-roam)
(org-roam-map "t" #'org-roam-today)
(org-roam-map "f" #'org-roam-find-file)
(org-roam-map "i" #'org-roam-insert)
(org-roam-map "g" #'org-roam-show-graph)
((normal motion visual) org-mode-map "gr" org-roam-map)
((normal motion visual) org-roam-backlinks-mode-map "<return>" #'org-open-at-point)
((normal motion visual emacs) org-roam-backlinks-mode-map "q" #'quit-window)
("C-c n" org-roam-map))
#+END_SRC
* org-journal
[[https://github.com/bastibe/org-journal][org-journal]] is a package that provides some convenience functions
around keeping a daily Org-mode journal. I also set it up so it plays
nice with Org-roam:
#+BEGIN_SRC emacs-lisp
(use-package org-journal
:defer t
:init
(defun org-journal-file-header-func ()
(let ()
(format "#+TITLE: %s"
(format-time-string "%Y-%m-%d"))))
(defun org-journal-today ()
(interactive)
(org-journal-new-entry t))
(defun org-journal-capture-func ()
(org-journal-new-entry t)
(goto-char (point-min))
;; Account for the #+TITLE
(forward-line))
(add-to-list 'org-capture-templates
'("j" "Journal entry" entry (function org-journal-capture-func)
"* %(format-time-string org-journal-time-format)\n%?"))
(jdormit/define-prefix "oj" "org-journal")
(leader-def-key "ojn" #'org-journal-new-entry)
(leader-def-key "ojt" #'org-journal-today)
:custom
(org-journal-file-type 'daily)
(org-journal-dir (concat (get-dropbox-directory) "/org"))
(org-journal-file-format "%Y-%m-%d.org")
(org-journal-file-header 'org-journal-file-header-func)
(org-journal-carryover-items "")
:general
(org-roam-map "t" 'org-journal-today))
#+END_SRC
* org-super-agenda
#+BEGIN_SRC emacs-lisp
(use-package org-super-agenda
:init
(setq org-super-agenda-groups
'((:name "Today"
:tag "today")
(:name "Lola"
:tag "@lola")
(:name "unifyDB"
:tag "@unifydb")
(:name "Personal"
:tag "@personal")))
(org-super-agenda-mode)
:config
(setq org-super-agenda-header-map (make-sparse-keymap)))
#+END_SRC
* Projectile
#+BEGIN_SRC emacs-lisp
(use-package projectile
:commands (projectile-find-file
projectile-grep
projectile-switch-project
projectile-project-root)
:init
(defhydra hydra-projectile (:color teal :hint nil) "
PROJECTILE: %(projectile-project-root)
Find File Search/Tags Buffers Cache
------------------------------------------------------------------------------------------
_s-f_: file _a_: ag _i_: Ibuffer _c_: cache clear _ff_: file dwim
_g_: update gtags _b_: switch to buffer _x_: remove known project
_fd_: file curr dir _o_: multi-occur _s-k_: Kill all buffers _X_:
cleanup non-existing _r_: recent file ^^^^_z_: cache current _d_:
dir
"
("a" projectile-ag)
("b" projectile-switch-to-buffer)
("c" projectile-invalidate-cache)
("d" projectile-find-dir)
("s-f" projectile-find-file)
("ff" projectile-find-file-dwim)
("fd" projectile-find-file-in-directory)
("g" ggtags-update-tags)
("s-g" ggtags-update-tags)
("i" projectile-ibuffer)
("K" projectile-kill-buffers)
("s-k" projectile-kill-buffers)
("m" projectile-multi-occur)
("o" projectile-multi-occur)
("s-p" projectile-switch-project "switch project")
("p" projectile-switch-project)
("s" projectile-switch-project)
("r" projectile-recentf)
("x" projectile-remove-known-project)
("X" projectile-cleanup-known-projects)
("z" projectile-cache-current-file)
("`" hydra-projectile-other-window/body "other window")
("q" nil "cancel" :color blue))
:config
(projectile-mode)
(jdormit/define-prefix "p" "projectile")
(leader-def-key "p" projectile-command-map))
(defmacro with-projectile-root (&rest body)
`(with-temp-buffer
(when (projectile-project-root)
(cd (projectile-project-root)))
,@body))
#+END_SRC
* Mode line
* UI
Get rid of the janky buttons:
#+BEGIN_SRC emacs-lisp
(tool-bar-mode -1)
#+END_SRC
And the menu bar:
#+BEGIN_SRC emacs-lisp
(menu-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
Display the column number in programming modes:
#+BEGIN_SRC emacs-lisp
(add-hook 'prog-mode-hook #'column-number-mode)
#+END_SRC
Disable the bell:
#+BEGIN_SRC emacs-lisp
(setq ring-bell-function 'ignore)
#+END_SRC
Render stuff differently based on whether or not we are graphical:
#+BEGIN_SRC emacs-lisp
(defun graphical-setup ()
(when (display-graphic-p (selected-frame))
(message "Running graphically")))
(defun non-graphical-setup ()
(when (not (display-graphic-p (selected-frame)))
(message "Running in terminal")
(menu-bar-mode -1)))
(defun do-graphical-non-graphical-setup ()
(graphical-setup)
(non-graphical-setup))
(add-hook 'window-setup-hook #'do-graphical-non-graphical-setup)
#+END_SRC
Try to make the background normal colored in the terminal:
#+BEGIN_SRC emacs-lisp
(defvar no-background-in-tty-faces '(default line-number magit-section-highlight))
(defun on-frame-open (frame)
(unless (display-graphic-p frame)
(menu-bar-mode -1)
(dolist (face no-background-in-tty-faces)
(set-face-background face "unspecified" frame))))
(mapc #'on-frame-open (frame-list))
(add-hook 'after-make-frame-functions #'on-frame-open)
(defun on-after-init ()
(unless (display-graphic-p (selected-frame))
(dolist (face no-background-in-tty-faces)
(set-face-background face "unspecified" (selected-frame)))))
(add-hook 'window-setup-hook #'on-after-init)
#+END_SRC
UI-related keybindings:
#+BEGIN_SRC emacs-lisp
(jdormit/define-prefix "u" "UI")
(leader-def-key "ut" #'customize-themes)
(leader-def-key "uf" #'customize-face)
(leader-def-key "uc" #'display-time-mode)
(leader-def-key "ub" #'display-battery-mode)
#+END_SRC
* Centaur tabs
[[https://github.com/ema2159/centaur-tabs][Centaur tabs]] is a package that gives Emacs buffer tabs similar to those in Atom or VS Code:
#+BEGIN_SRC emacs-lisp
(use-package centaur-tabs
:commands (centaur-tabs-mode
centaur-tabs-local-mode
centaur-tabs-mode-on-p)
:init
(setq centaur-tabs-set-icons t
centaur-tabs-gray-out-icons 'buffer
centaur-tabs-height 30
centaur-tabs-set-bar 'under
x-underline-at-descent-line t
centaur-tabs-set-modified-marker t
centaur-tabs-show-navigation-buttons t
centaur-tabs-down-tab-text ""
centaur-tabs-backward-tab-text ""
centaur-tabs-forward-tab-text ""
centaur-tabs-close-button ""
centaur-tabs-modified-marker ""
centaur-tabs-cycle-scope 'tabs
centaur-tabs-label-fixed-length 20)
(leader-def-key "uT" #'centaur-tabs-mode)
(centaur-tabs-mode)
:config
(centaur-tabs-group-by-projectile-project)
;; Custom buffer groups
(defun centaur-tabs-projectile-buffer-groups ()
"Return the list of group names BUFFER belongs to."
(if centaur-tabs-projectile-buffer-group-calc
(symbol-value 'centaur-tabs-projectile-buffer-group-calc)
(set (make-local-variable 'centaur-tabs-projectile-buffer-group-calc)
(cond
((or (get-buffer-process (current-buffer))
(memq major-mode '(comint-mode compilation-mode))) '("Term"))
((string-equal "*" (substring (buffer-name) 0 1)) '("Misc"))
((condition-case _err
(projectile-project-root)
(error nil)) (list (projectile-project-name)))
((memq major-mode '(emacs-lisp-mode python-mode emacs-lisp-mode c-mode
c++-mode javascript-mode js-mode
js2-mode makefile-mode
lua-mode vala-mode)) '("Coding"))
((memq major-mode '(nxhtml-mode html-mode
mhtml-mode css-mode)) '("HTML"))
((memq major-mode '(org-journal-mode)) '("Journal"))
((memq major-mode '(org-mode calendar-mode diary-mode)) '("Org"))
((memq major-mode '(dired-mode)) '("Dir"))
(t '("Other"))))
(symbol-value 'centaur-tabs-projectile-buffer-group-calc)))
;; Don't show tabs for certain types of buffers
(advice-add 'centaur-tabs-hide-tab :around
(lambda (oldfn buf &rest args)
(if (with-current-buffer buf
(eq major-mode 'vuiet-mode))
t
(apply oldfn buf args))))
;; Only show tabs in buffers visiting files
(advice-add 'centaur-tabs-line :around
(lambda (oldfn &rest args)
(if (buffer-file-name)
(apply oldfn args)
(setq header-line-format nil))))
;; Enable prefix argument for tab switching keybindings
(advice-add 'centaur-tabs-forward :around
(lambda (oldfn &rest args)
(if (numberp current-prefix-arg)
(dotimes (_ current-prefix-arg)
(apply oldfn args))
(apply oldfn args))))
(advice-add 'centaur-tabs-backward :around
(lambda (oldfn &rest args)
(if (numberp current-prefix-arg)
(dotimes (_ current-prefix-arg)
(apply oldfn args))
(apply oldfn args))))
;; Use Org-mode titles for tab names when possible
(advice-add 'centaur-tabs-buffer-tab-label :around
(lambda (oldfn tab &rest args)
(if-let ((title (or (car
(org-roam-db--get-titles
(buffer-file-name (car tab))))
(org-get-title
(with-current-buffer (car tab)
(buffer-substring (point-min)
(min (point-max) 200)))))))
(if (> centaur-tabs-label-fixed-length 0)
(centaur-tabs-truncate-string centaur-tabs-label-fixed-length
(format " %s" title))
(format " %s" title))
(apply oldfn tab args))))
;; Add a cache to speed up icon rendering for huge groups
(defvar centaur-tabs-icon-cache (make-hash-table :test 'equal)
"A cache holding icons generated for centaur-tabs mode tabs.")
(advice-add 'centaur-tabs-icon :around
(lambda (oldfn tab face selected &rest args)
(let ((key (list tab face selected)))
(or (gethash key centaur-tabs-icon-cache)
(puthash key
(apply oldfn tab face selected args)
centaur-tabs-icon-cache)))))
:general
((normal motion visual) "g t" #'centaur-tabs-forward)
((normal motion visual) "g T" #'centaur-tabs-backward)
(leader-map "pt" #'centaur-tabs-counsel-switch-group)
:hook
(git-commit-mode . (lambda ()
(when (centaur-tabs-mode-on-p)
(centaur-tabs-local-mode)))))
#+END_SRC
* Frame parameters
Functions to change the frame size:
#+BEGIN_SRC emacs-lisp
(defun jdormit/set-frame-size (width height)
(interactive "nWidth: \nnHeight: ")
(if (display-graphic-p)
(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
* Transpose Frame
A handy utility that reverse the current frame window split (vertical to horizontal or vice-versa):
#+BEGIN_SRC emacs-lisp
(use-package transpose-frame
:defer t
:init
(leader-def-key "wt" 'transpose-frame))
#+END_SRC
* EShell
Easy keybinding to open EShell:
#+BEGIN_SRC emacs-lisp
(defun open-eshell (&optional arg)
(interactive "P")
(if (and (fboundp 'projectile-project-root)
(projectile-project-root))
(projectile-run-eshell arg)
(eshell arg)))
(leader-def-key "'" 'open-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
And a function to run any program visually:
#+BEGIN_SRC emacs-lisp
(defun eshell/v (&rest args)
(apply #'eshell-exec-visual args))
#+END_SRC
Load .dir-locals.el when switching directories:
#+BEGIN_SRC emacs-lisp
(add-hook 'eshell-mode-hook #'hack-dir-local-variables-non-file-buffer)
(add-hook 'eshell-after-prompt-hook #'hack-dir-local-variables-non-file-buffer)
#+END_SRC
A function to properly clear the eshell:
#+BEGIN_SRC emacs-lisp
(defun clear-eshell (&optional prefix)
(interactive)
(let ((input (eshell-get-old-input)))
(eshell/clear-scrollback)
(eshell-emit-prompt)
(insert input)))
(add-hook 'eshell-mode-hook
(lambda () (general-def eshell-mode-map "C-c C-o" #'clear-eshell)))
#+END_SRC
Some aliases:
#+BEGIN_SRC emacs-lisp
(setq eshell-aliases
'(("k" . "kubectl $*")
("kctx" . "kubectx $*")
("root" . "cd (projectile-project-root)")))
(add-hook
'eshell-mode-hook
(lambda ()
(dolist (alias eshell-aliases)
(eshell/alias (car alias) (cdr alias)))))
#+END_SRC
** Prompt
#+BEGIN_SRC emacs-lisp
(defun jdormit-eshell-prompt ()
(let ((branch (magit-name-local-branch "HEAD")))
(format "%s%s"
(if branch (format "(%s) " branch) "")
(concat (abbreviate-file-name (eshell/pwd))
(if (= (user-uid) 0) " # " " $ ")))))
(setq jdormit-eshell-prompt-regex "^[^#$\n]* [#$] ")
(setq eshell-prompt-function 'jdormit-eshell-prompt)
(setq eshell-prompt-regexp jdormit-eshell-prompt-regex)
#+END_SRC
* Flycheck
Syntax checking etc.:
#+BEGIN_SRC emacs-lisp
(use-package flycheck
:init
(defhydra hydra-flycheck
(:pre (flycheck-list-errors)
:post (quit-windows-on "*Flycheck errors*")
:hint nil)
"Errors"
("f" flycheck-error-list-set-filter "Filter")
("j" flycheck-next-error "Next")
("k" flycheck-previous-error "Previous")
("gg" flycheck-first-error "First")
("G" (progn (goto-char (point-max)) (flycheck-previous-error)) "Last")
("q" nil))
:config
(setq-default flycheck-disabled-checkers '(emacs-lisp emacs-lisp-checkdoc))
(global-flycheck-mode)
:general
((normal motion visual) flycheck-mode-map "ze" 'hydra-flycheck/body))
#+END_SRC
* Tabs
#+BEGIN_SRC emacs-lisp
(defun disable-tab-insert ()
(interactive)
(setq indent-tabs-mode nil))
#+END_SRC
* JSON
#+BEGIN_SRC emacs-lisp
(use-package json-mode
:mode (("\\.json\\'" . json-mode)))
(use-package json-navigator
:commands (json-navigator-navigator
json-navigator-navigate-after-point
json-navigator-navigate-region))
(defun json-pprint ()
(interactive)
(let ((begin (if (region-active-p) (region-beginning) (point-min)))
(end (if (region-active-p) (region-end) (point-max))))
(if (executable-find "jq")
(shell-command-on-region begin end "jq ." nil t)
(json-pretty-print begin end))))
(general-def json-mode-map "C-M-\\" 'json-pprint)
#+END_SRC
* JavaScript
Some formatting stuff:
#+BEGIN_SRC emacs-lisp
(setq js-indent-level 4)
#+END_SRC
#+BEGIN_SRC emacs-lisp
(use-package web-mode
:mode (("\\.html\\'" . web-mode)
("\\.js\\'" . web-mode)
("\\.jsx\\'" . web-mode)
("\\.mako\\'" . web-mode)
("\\.jinja2\\'" . web-mode)
("\\.hbs\\'" . web-mode))
:config
(setq web-mode-engines-alist
'(("django" . "\\.jinja2\\'")))
(add-hook 'web-mode-hook
(lambda ()
(when (equal web-mode-content-type "javascript")
(web-mode-set-content-type "jsx"))
(when (or (equal web-mode-content-type "javascript")
(equal web-mode-content-type "jsx"))
(lsp-deferred))))
(add-hook 'web-mode-hook #'disable-tab-insert))
#+END_SRC
Use nvm to manage node versions:
#+BEGIN_SRC emacs-lisp
(use-package nvm
:straight ((nvm :host github :repo "rejeep/nvm.el"))
:commands (nvm-use nvm-use-for nvm-use-for-buffer))
#+END_SRC
A command to format JS via prettier:
#+BEGIN_SRC emacs-lisp
(defun prettier ()
(interactive)
(let ((start (if (use-region-p) (region-beginning) (point-min)))
(end (if (use-region-p) (region-end) (point-max)))
(parser (cond
((eq major-mode 'graphql-mode) "graphql")
(t "babel"))))
(shell-command-on-region start end (concat "prettier --parser " parser) nil t)))
#+END_SRC
** NVM
Manage node version via NVM within Emacs:
#+BEGIN_SRC emacs-lisp
(use-package nvm
:commands (nvm-use
nvm-use-for
nvm-use-for-buffer
nvm--installed-versions)
:init
(defun nvm (version)
(interactive (list
(completing-read "Node version: "
(mapcar #'car
(nvm--installed-versions)))))
(nvm-use version)))
#+END_SRC
* Java
#+BEGIN_SRC emacs-lisp
(use-package lsp-java
:hook ((java-mode . lsp-deferred)))
#+END_SRC
* Kotlin
#+BEGIN_SRC emacs-lisp
(use-package kotlin-mode
:mode (("\\.kt\\'" . kotlin-mode))
:config
(with-eval-after-load 'lsp
(when (executable-find "kotlin-language-server")
(add-hook 'kotlin-mode-hook #'lsp-deferred))))
#+END_SRC
* Groovy
Used for Jenkins configuration scripts and probably other things.
#+BEGIN_SRC emacs-lisp
(use-package groovy-mode
:commands (groovy-mode)
:mode (("\\.groovy\\'" . groovy-mode))
:init
(add-to-list 'interpreter-mode-alist
'("groovy" . groovy-mode))
:custom
(groovy-indent-offset 2))
#+END_SRC
* Typescript
#+BEGIN_SRC emacs-lisp
(use-package typescript-mode
:mode ("\\.ts\\'")
:config
(with-eval-after-load 'lsp
(add-hook 'typescript-mode-hook 'lsp-deferred)))
#+END_SRC
* LSP Mode
Emacs support for the Language Server Protocol
#+BEGIN_SRC emacs-lisp
(use-package lsp-mode
:defer t
:init
(defhydra hydra-lsp (:exit t :hint nil)
"
Buffer^^ Server^^ Symbol
-------------------------------------------------------------------------------------
[_f_] format [_M-r_] restart [_d_] declaration [_i_] implementation [_o_] documentation
[_m_] imenu [_S_] shutdown [_D_] definition [_t_] type [_r_] rename
[_x_] execute action [_M-s_] describe session [_R_] references [_s_] signature"
("d" lsp-find-declaration)
("D" lsp-ui-peek-find-definitions)
("R" lsp-ui-peek-find-references)
("i" lsp-ui-peek-find-implementation)
("t" lsp-find-type-definition)
("s" lsp-signature-help)
("o" lsp-describe-thing-at-point)
("r" lsp-rename)
("f" lsp-format-buffer)
("m" lsp-ui-imenu)
("x" lsp-execute-code-action)
("M-s" lsp-describe-session)
("M-r" lsp-restart-workspace)
("S" lsp-shutdown-workspace))
:general
(lsp-mode-map "C-c h" 'hydra-lsp/body)
((normal visual motion) lsp-mode-map "K" #'lsp-describe-thing-at-point)
:hook
((lsp-mode . (lambda ()
(let ((lsp-keymap-prefix "gl"))
(lsp-enable-which-key-integration)))))
:config
(setq lsp-prefer-flymake nil)
(general-def '(normal visual motion) "gl" lsp-command-map)
:commands lsp-mode lsp lsp-deferred
:custom
(lsp-enable-snippet nil)
(lsp-eldoc-render-all nil))
(use-package lsp-ui
:after (lsp-mode)
:custom
(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)
(lsp-ui-doc-alignment 'window)
(lsp-ui-doc-header t)
(lsp-ui-doc-position 'top)
(lsp-ui-doc-background '((t (:inherit region))))
(lsp-ui-doc-header '((t (:inherit lsp-face-highlight-write))))
(lsp-ui-sideline-current-symbol '((t (:inherit font-lock-constant-face
:weight ultra-bold)))))
(with-eval-after-load 'lsp-clients
(defun lsp-typescript-javascript-tsx-jsx-activate-p (filename major-mode)
"Checks if the javascript-typescript language server should be enabled
based on FILE-NAME and MAJOR-MODE"
(or (member major-mode '(typescript-mode typescript-tsx-mode js-mode js2-mode rjsx-mode))
(and (eq major-mode 'web-mode)
(or (string-suffix-p ".tsx" filename t)
(string-suffix-p ".jsx" filename t)
(string-suffix-p ".js" filename t))))))
#+END_SRC
* Python
** General
#+BEGIN_SRC emacs-lisp
(leader-def-key "sp" #'run-python)
(add-hook 'python-mode-hook #'disable-tab-insert)
#+END_SRC
ISort is a Python utility to sort imports:
#+BEGIN_SRC emacs-lisp
(use-package py-isort
:commands (py-isort-buffer py-isort-region)
:config
(setq py-isort-options '("-m=3"))
:general
(python-mode-map "C-c C-i" #'py-isort-buffer))
#+END_SRC
Run black on the current buffer:
#+BEGIN_SRC emacs-lisp
(general-def 'normal python-mode-map "C-M-\\" #'format-all-buffer)
#+END_SRC
[[https://github.com/naiquevin/sphinx-doc.el][sphinx-doc.el]] automatically generates doc strings for Python functions (and updates existing ones!):
#+BEGIN_SRC emacs-lisp
(use-package sphinx-doc
:hook ((python-mode . sphinx-doc-mode)))
#+END_SRC
** Dev environment/IDE stuff
Support pyvenv within Emacs:
#+BEGIN_SRC emacs-lisp
(use-package pyvenv
:defer 0
:commands (pyvenv-mode
pyvenv-tracking-mode
pyvenv-workon
pyvenv-activate
pyvenv-track-virtualenv)
:config
(pyvenv-mode
pyvenv-tracking-mode))
(defun eshell/workon (name)
(pyvenv-workon name))
(defun eshell/activate (dir)
(pyvenv-activate dir))
(defun eshell/deactivate ()
(pyvenv-deactivate))
#+END_SRC
And support pyenv (NOT pyvenv) to change Python versions:
#+BEGIN_SRC emacs-lisp
(use-package pyenv-mode
:defer t)
#+END_SRC
Use the LSP python client:
#+BEGIN_SRC emacs-lisp
(defun python-lsp ()
(when (derived-mode-p 'python-mode)
(lsp-deferred)))
(add-hook 'hack-local-variables-hook #'python-lsp)
(general-def 'normal python-mode-map "C-c C-d" #'lsp-describe-thing-at-point)
#+END_SRC
** Autoflake
[[https://pypi.org/project/autoflake/][Autoflake]] is a tool that removes unused imports and variables from Python code:
#+BEGIN_SRC emacs-lisp
(defvar autoflake-args '()
"Arguments to pass to the autoflake command.
See URL `https://pypi.org/project/autoflake' for options.")
(defun autoflake (arg)
(interactive "P")
(let ((autoflake-cmd (or (executable-find "autoflake")
(error "Autoflake executable not found")))
(file (cond
((or arg (not buffer-file-name))
(read-file-name "Run autoflake on file: "))
(buffer-file-name buffer-file-name)
(:else (error "Invalid file for autoflake")))))
(apply #'call-process autoflake-cmd nil nil nil
(append autoflake-args (list "--in-place" file)))))
#+END_SRC
** Testing
[[https://github.com/wbolster/emacs-python-pytest][python-pytest.el]] integrates Pytest with Emacs:
#+BEGIN_SRC emacs-lisp
(use-package python-pytest
:commands (python-pytest-popup
python-pytest--current-defun)
:general
(python-mode-map "C-c t p" #'python-pytest-popup)
((normal motion visual) python-pytest-mode-map "g r" #'python-pytest-repeat)
((normal motion visual) python-pytest-mode-map "q" #'quit-window))
#+END_SRC
And borrowing some functions from python-pytest, we can get some nosetests support as well:
#+BEGIN_SRC emacs-lisp
(defvar nosetests-args ""
"Additional args to pass to nosetests")
(defun run-nose (args &optional debug)
"Runs nosetests with `args'
If `debug' is non-nil, run in a GUD PDB session."
(let* ((nosetests-cmd (executable-find "nosetests"))
(cmdline (format "%s %s" nosetests-cmd args)))
(when (not nosetests-cmd)
(user-error "Nosetests command not found"))
(if debug
(realgud:pdb cmdline)
(compile cmdline))))
(defun run-nose-reading-args (arg nose-args &optional debug)
"Runs nosetests with default args or prompts for args with prefix
If `debug' is non-nil, run in a GUD PDB session."
(let ((args (if arg
(read-string "Nosetests arguments: "
nil nil nosetests-args)
nosetests-args)))
(run-nose (format "%s %s" args nose-args) debug)))
(defun run-nose-in-project (arg nose-args &optional debug)
"Runs nosetests from the project root
If `debug' is non-nil, run in a GUD PDB session."
(let ((dir (or (projectile-project-root)
default-directory)))
(with-temp-buffer
(cd dir)
(run-nose-reading-args arg nose-args debug))))
(defun nosetests-all (arg)
"Runs nosetests on all project tests, prompting for the tests directory"
(interactive "P")
(let ((test-dir (read-file-name "Test directory: "
(or (projectile-project-root)
default-directory)
nil t "tests" #'directory-name-p)))
(run-nose-in-project arg (directory-file-name test-dir))))
(defun nosetests-debug-all (arg)
"Runs nosetests in a GUD session on all project tests"
(interactive "P")
(let ((test-dir (read-file-name "Test directory: "
(or (projectile-project-root)
default-directory)
nil t "tests" #'directory-name-p)))
(run-nose-in-project arg (directory-file-name test-dir) t)))
(defun nosetests-module (arg module)
"Runs nosetests in the module of the current file"
(interactive (list current-prefix-arg
(read-file-name "Run nosetests on module: "
nil nil t
(file-name-directory
(buffer-file-name))
#'directory-name-p)))
(run-nose-in-project arg (directory-file-name module)))
(defun nosetests-debug-module (arg module)
"Runs nosetests in a GUD session in the module of the current file"
(interactive (list current-prefix-arg
(read-file-name "Run nosetests on module: "
nil nil t
(file-name-directory
(buffer-file-name))
#'directory-name-p)))
(run-nose-in-project arg (directory-file-name module) t))
(defun nosetests-file (arg file)
"Runs nosetests on the current file"
(interactive (list current-prefix-arg
(read-file-name "Run nosetests on file: "
nil nil t (buffer-file-name))))
(run-nose-in-project arg file))
(defun nosetests-debug-file (arg file)
"Runs nosetests in a GUD session on the current file"
(interactive (list current-prefix-arg
(read-file-name "Run nosetests on file: "
nil nil t (buffer-file-name))))
(run-nose-in-project arg file t))
(defun nosetests-def (arg def)
"Runs nosetests on the enclosing function at point"
(interactive (list current-prefix-arg
(python-pytest--current-defun)))
(run-nose-in-project arg (format "%s:%s"
(buffer-file-name)
def)))
(defun nosetests-debug-def (arg def)
"Runs nosetests in a GUD session on the enclosing function at point"
(interactive (list current-prefix-arg
(python-pytest--current-defun)))
(run-nose-in-project arg
(format "%s:%s"
(buffer-file-name)
def)
t))
(defvar nosetests-map (make-sparse-keymap)
"Keymap for nosetests")
(defvar nosetests-debug-map (make-sparse-keymap)
"Keymap for debugging nosetests")
(general-def python-mode-map "C-c t n" nosetests-map)
(general-def nosetests-map "a" #'nosetests-all)
(general-def nosetests-map "m" #'nosetests-module)
(general-def nosetests-map "f" #'nosetests-file)
(general-def nosetests-map "d" #'nosetests-def)
(general-def nosetests-map "b" nosetests-debug-map)
(general-def nosetests-debug-map "a" #'nosetests-debug-all)
(general-def nosetests-debug-map "m" #'nosetests-debug-module)
(general-def nosetests-debug-map "f" #'nosetests-debug-file)
(general-def nosetests-debug-map "d" #'nosetests-debug-def)
(which-key-add-major-mode-key-based-replacements
'python-mode "C-c t n" "nosetests")
(which-key-add-major-mode-key-based-replacements
'python-mode "C-c t n b" "debug")
#+END_SRC
* Hy
Python but Lispy!
#+BEGIN_SRC emacs-lisp
(defun run-hy ()
(interactive)
(run-lisp (expand-file-name "~/.virtualenvs/hy/bin/hy")))
#+END_SRC
* Go
Basic support:
#+BEGIN_SRC emacs-lisp
(use-package go-mode
:mode (("\\.go\\'" . go-mode)))
#+END_SRC
LSP support - requires [[https://github.com/sourcegraph/go-langserver][go-langserver]].
#+BEGIN_SRC emacs-lisp
(add-hook 'go-mode-hook #'lsp-deferred)
#+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
Add flycheck support:
#+BEGIN_SRC emacs-lisp
(use-package flycheck-clojure
:commands (flycheck-clojure-setup))
#+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"))
cider-prompt-for-symbol nil)
(general-def cider-mode-map "C-c t" cider-test-commands-map)
(flycheck-clojure-setup)
(add-hook 'cider-repl-mode-hook 'smartparens-strict-mode)
:hook ((clojure-mode . cider-mode)
(clojurescript-mode . cider-mode)
(clojurec-mode . cider-mode))
:general
(cider-stacktrace-mode-map "SPC" leader-map)
('normal cider-mode-map "M-." #'cider-find-var))
(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
Add some handy hydras:
#+BEGIN_SRC emacs-lisp
(use-package cider-hydra
:after (cider)
:hook ((cider-mode . cider-hydra-mode)))
#+END_SRC
Clj-refactor adds magical refactoring abilities:
#+BEGIN_SRC emacs-lisp
(use-package clj-refactor
:init
(defun clj-refactor-setup ()
(interactive)
(clj-refactor-mode 1)
(cljr-add-keybindings-with-prefix "C-c r"))
:config
(setq cljr-auto-sort-ns t)
:hook ((clojure-mode . clj-refactor-setup)))
#+END_SRC
Integrate with cljfmt, the Clojure code formatter:
#+BEGIN_SRC emacs-lisp
(defun cljfmt ()
(interactive)
(let* ((start (if (use-region-p) (region-beginning) (point-min)))
(end (if (use-region-p) (region-end) (point-max)))
(text (buffer-substring start end))
(file (make-temp-file "cljfmt"))
(fmted
(with-temp-buffer
(insert text)
(write-file file)
(shell-command
(concat
"clojure "
"-Sdeps "
"'{:aliases {:fmt {:extra-deps {cljfmt {:mvn/version \"0.6.4\"}} :main-opts [\"-m\" \"cljfmt.main\"]}}}' "
"-A:fmt "
"fix "
file))
(revert-buffer nil t)
(buffer-substring (point-min) (point-max)))))
(delete-region start end)
(goto-char start)
(insert fmted)))
(general-def clojure-mode-map "C-M-\\" #'cljfmt)
#+END_SRC
Add support for running Org-mode Clojure source blocks with [[https://github.com/borkdude/babashka][Babashka]]:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'ob-clojure
(defcustom org-babel-clojure-backend nil
"Backend used to evaluate Clojure code blocks."
:group 'org-babel
:type '(choice
(const :tag "inf-clojure" inf-clojure)
(const :tag "cider" cider)
(const :tag "slime" slime)
(const :tag "bb" bb)
(const :tag "Not configured yet" nil)))
(defun elisp->clj (in)
(cond
((listp in) (concat "[" (s-join " " (mapcar #'elisp->clj in)) "]"))
(t (format "%s" in))))
(defun ob-clojure-eval-with-bb (expanded params)
"Evaluate EXPANDED code block with PARAMS using babashka."
(unless (executable-find "bb")
(user-error "Babashka not installed"))
(let* ((stdin (let ((stdin (cdr (assq :stdin params))))
(when stdin
(elisp->clj
(org-babel-ref-resolve stdin)))))
(input (cdr (assq :input params)))
(file (make-temp-file "ob-clojure-bb" nil nil expanded))
(command (concat (when stdin (format "echo %s | " (shell-quote-argument stdin)))
(format "bb %s -f %s"
(cond
((equal input "edn") "")
((equal input "text") "-i")
(t ""))
(shell-quote-argument file))))
(result (shell-command-to-string command)))
(s-trim result)))
(defun org-babel-execute:clojure (body params)
"Execute a block of Clojure code with Babel."
(unless org-babel-clojure-backend
(user-error "You need to customize org-babel-clojure-backend"))
(let* ((expanded (org-babel-expand-body:clojure body params))
(result-params (cdr (assq :result-params params)))
result)
(setq result
(cond
((eq org-babel-clojure-backend 'inf-clojure)
(ob-clojure-eval-with-inf-clojure expanded params))
((eq org-babel-clojure-backend 'cider)
(ob-clojure-eval-with-cider expanded params))
((eq org-babel-clojure-backend 'slime)
(ob-clojure-eval-with-slime expanded params))
((eq org-babel-clojure-backend 'bb)
(ob-clojure-eval-with-bb expanded params))))
(org-babel-result-cond result-params
result
(condition-case nil (org-babel-script-escape result)
(error result)))))
(customize-set-variable 'org-babel-clojure-backend 'bb))
(add-hook 'org-mode-hook (lambda () (require 'ob-clojure)))
#+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
:commands (run-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
:after (slime))
(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-hook 'slime-repl-mode-hook 'smartparens-strict-mode)
:general
((normal motion visual insert emacs) slime-mode-map "M-." #'slime-edit-definition))
(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
:after php-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
LSP for PHP requires [[https://github.com/felixfbecker/php-language-server][php-language-server]] to be installed in ~/.composer:
#+BEGIN_SRC emacs-lisp
(add-hook 'php-mode-hook #'lsp-deferred)
#+END_SRC
* YAML
#+BEGIN_SRC emacs-lisp
(use-package yaml-mode
:mode ("//.yml//'"))
#+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
* Bash
Use LSP if [[https://github.com/mads-hartmann/bash-language-server][bash-language-server]] is installed.
#+BEGIN_SRC emacs-lisp
(when (executable-find "bash-language-server")
(add-hook 'sh-mode-hook #'lsp-deferred))
#+END_SRC
* Ruby
#+BEGIN_SRC emacs-lisp
(add-hook 'ruby-mode-hook #'lsp-deferred)
#+END_SRC
* Rust
#+BEGIN_SRC emacs-lisp
(use-package rust-mode
:mode "\\.rs\\'"
:general
(rust-mode-map "C-c <tab>" #'rust-format-buffer)
:config
(add-hook 'rust-mode-hook #'lsp-deferred))
(use-package cargo
:after (rust-mode)
:config
(add-hook 'rust-mode-hook #'cargo-minor-mode))
#+END_SRC
* XML
Set up hideshow for nXML mode:
#+BEGIN_SRC emacs-lisp
(add-hook 'nxml-mode-hook #'hs-minor-mode)
(add-to-list 'hs-special-modes-alist
'(nxml-mode
"<!--\\|<[^/>]*[^/]>" ;; regexp for start block
"-->\\|</[^/>]*[^/]>" ;; regexp for end block
"<!--"
nxml-forward-element
nil))
#+END_SRC
A function to format XML using tidy or xmllint if available, falling back to sgml-pretty-print:
#+BEGIN_SRC emacs-lisp
(defun xml-pretty-print ()
(interactive)
(let ((start (if (region-active-p) (region-beginning) (point-min)))
(end (if (region-active-p) (region-end) (point-max))))
(cond
((executable-find "tidy")
(shell-command-on-region start end "tidy -wrap 88 -q -i -xml" nil t))
((executable-find "xmllint")
(shell-command-on-region start end "xmllint --format -" nil t))
(t (sgml-pretty-print start end)))))
(general-def nxml-mode-map "C-M-\\" #'xml-pretty-print)
#+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
Edit-indirect allows markdown-mode to edit source blocks in separate buffers:
#+BEGIN_SRC emacs-lisp
(use-package edit-indirect
:defer t)
#+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
* 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
** Importing
#+BEGIN_SRC emacs-lisp
(defvar ledger-file (expand-file-name "~/journal.ledger"))
(defvar bank-alist
'(("DCU Checking" . ((acct . "Assets:Checking")
(fid . "9999")))
("DCU Savings" . ((acct . "Assets:Savings")
(fid . "9999")))
("DCU Visa" . ((acct . "Liabilities:DCU Visa")
(fid . "9999")))
("Chase Visa" . ((acct . "Liabilities:Chase Visa")))))
(defun ledger-import-ofx (bank file)
(interactive
(list
(completing-read "Bank: "
(mapcar #'car bank-alist))
(read-file-name "OFX file: ")))
(if-let ((ledger-autosync (executable-find "ledger-autosync")))
(let* ((bank-def (alist-get bank bank-alist))
(acct (alist-get 'acct bank-def))
(fid (alist-get 'fid bank-def))
(cmd (concat
ledger-autosync
(if fid (concat " --fid " fid) "")
" --account '" acct "'"
" '" file "'"))
(output (shell-command-to-string cmd)))
(find-file ledger-file)
(goto-char (point-max))
(insert "\n")
(insert output)
(ledger-sort-region (point-min) (point-max))
(ledger-post-align-postings (point-min) (point-max)))
(error "Unable to find ledger-autosync")))
#+END_SRC
* PDFs
#+BEGIN_SRC emacs-lisp
(use-package pdf-tools
:mode ("\\.pdf\\'" . pdf-view-mode)
:init
(defhydra hydra-pdftools (:color blue :hint nil)
"
╭───────────┐
Move History Scale/Fit Annotations Search/Link Do │ PDF Tools │
╭──────────────────────────────────────────────────────────────────┴───────────╯
^^_g_^^ _B_ ^↧^ _+_ ^ ^ [_al_] list [_s_] search [_u_] revert buffer
^^^↑^^^ ^↑^ _H_ ^↑^ ↦ _W_ ↤ [_am_] markup [_o_] outline [_i_] info
^^_p_^^ ^ ^ ^↥^ _0_ ^ ^ [_at_] text [_F_] link [_d_] dark mode
^^^↑^^^ ^↓^ ╭─^─^─┐ ^↓^ ╭─^ ^─┐ [_ad_] delete [_f_] search link
_h_ ←pag_e_→ _l_ _N_ │ _P_ │ _-_ _b_ [_aa_] dired
^^^↓^^^ ^ ^ ╰─^─^─╯ ^ ^ ╰─^ ^─╯ [_y_] yank
^^_n_^^ ^ ^ _r_eset slice box
^^^↓^^^
^^_G_^^
--------------------------------------------------------------------------------
"
("\\" hydra-master/body "back")
("<ESC>" nil "quit")
("al" pdf-annot-list-annotations)
("ad" pdf-annot-delete)
("aa" pdf-annot-attachment-dired)
("am" pdf-annot-add-markup-annotation)
("at" pdf-annot-add-text-annotation)
("y" pdf-view-kill-ring-save)
("+" pdf-view-enlarge :color red)
("-" pdf-view-shrink :color red)
("0" pdf-view-scale-reset)
("H" pdf-view-fit-height-to-window)
("W" pdf-view-fit-width-to-window)
("P" pdf-view-fit-page-to-window)
("n" pdf-view-next-page-command :color red)
("p" pdf-view-previous-page-command :color red)
("d" pdf-view-dark-minor-mode)
("b" pdf-view-set-slice-from-bounding-box)
("r" pdf-view-reset-slice)
("g" pdf-view-first-page)
("G" pdf-view-last-page)
("e" pdf-view-goto-page)
("o" pdf-outline)
("s" pdf-occur)
("i" pdf-misc-display-metadata)
("u" pdf-view-revert-buffer)
("F" pdf-links-action-perform)
("f" pdf-links-isearch-link)
("B" pdf-history-backward :color red)
("N" pdf-history-forward :color red)
("l" image-forward-hscroll :color red)
("h" image-backward-hscroll :color red))
:config
(pdf-tools-install)
:general
(pdf-view-mode-map "SPC" leader-map)
((normal motion visual) pdf-view-mode-map "." #'hydra-pdftools/body)
((normal motion visual) pdf-view-mode-map "F" #'pdf-links-action-perform))
#+END_SRC
* EPubs
#+BEGIN_SRC emacs-lisp
(defun jdormit/nov-config ()
(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
:general
(dashboard-mode-map "SPC" leader-map)
:config
(with-eval-after-load 'evil
(add-to-list 'evil-emacs-state-modes 'dashboard-mode))
(setq dashboard-items '((recents . 5)
(projects . 5))
dashboard-startup-banner 'official
dashboard-set-heading-icons t
dashboard-set-file-icons t
dashboard-set-navigator t))
(dashboard-setup-startup-hook)
#+END_SRC
For some reason Emacs is starting up with the dashboard and the *scratch* buffer open in a split configuration. Not sure why, but let's put a stop to that...
#+BEGIN_SRC emacs-lisp
(add-hook
'after-init-hook
(lambda ()
(switch-to-buffer "*dashboard*")
(delete-other-windows)))
#+END_SRC
* Email
I use [[https://www.djcbsoftware.nl/code/mu/mu4e.html][mu/mu4e]] as my email client.
First, add it to the load path:
#+BEGIN_SRC emacs-lisp
(defvar mu4e-load-path)
(setq mu4e-load-path
(cond
((file-exists-p "/usr/local/share/emacs/site-lisp/mu/mu4e")
"/usr/local/share/emacs/site-lisp/mu/mu4e")
((file-exists-p "/usr/share/emacs/site-lisp/mu4e")
"/usr/share/emacs/site-lisp/mu4e")))
(add-to-list 'load-path mu4e-load-path)
#+END_SRC
Then set up autoloads on the entry functions:
#+BEGIN_SRC emacs-lisp
(autoload 'mu4e (concat mu4e-load-path "/mu4e.el") nil t)
(autoload 'mu4e-update-index (concat mu4e-load-path "/mu4e.el") nil t)
#+END_SRC
Then configure it:
#+begin_src emacs-lisp
(with-eval-after-load 'mu4e
(setq
;; General
mu4e-maildir (expand-file-name "~/.mail")
mu4e-completing-read-function 'completing-read
mu4e-attachment-dir (expand-file-name "~/Downloads")
mu4e-change-filenames-when-moving t
user-mail-address "jeremy.dormitzer@gmail.com"
mu4e-view-show-images t
mu4e-headers-skip-duplicates t
mail-user-agent 'mu4e-user-agent
;; Custom actions
mu4e-view-actions '(("capture message" . mu4e-action-capture-message)
("view as pdf" . mu4e-action-view-as-pdf)
("show this thread" . mu4e-action-show-thread)
("View in browser" . mu4e-action-view-in-browser))
;; Bookmarked searches
mu4e-bookmarks '((:name "Inbox"
:query (concat "maildir:/jeremy-dormitzer-gmail-com/Inbox"
" OR maildir:/jeremydormitzer-lola-com/Inbox")
:key ?i)
(:name "Unread messages"
:query "flag:unread AND NOT flag:trashed"
:key ?u)
(:name "Today's messages"
:query "date:today..now"
:key ?t)
(:name "Last 7 days"
:query "date:7d..now"
:key ?p))
;; Getting mail
mu4e-get-mail-command "mbsync -a"
;; Sending mail
send-mail-function #'sendmail-send-it
message-send-mail-function #'sendmail-send-it
sendmail-program (executable-find "msmtp")
;; Let Gmail handle putting sent messages in the sent folder
mu4e-sent-messages-behavior 'delete
;; Move to trash folder instead of adding trash flag for Gmail mailboxes
mu4e-move-to-trash-patterns '("jeremy-dormitzer-gmail-com" "jeremydormitzer-lola-com")
;; HTML email rendering
shr-use-colors nil
;; Make sure mu4e knows about my different accounts
mu4e-context-policy 'ask
mu4e-compose-context-policy 'ask
mu4e-contexts
`(,(make-mu4e-context
:name "Personal 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")
(mu4e-sent-folder . "/jeremy-dormitzer-gmail-com/Sent")
(mu4e-drafts-folder . "/jeremy-dormitzer-gmail-com/Drafts")
(mu4e-refile-folder . "/jeremy-dormitzer-gmail-com/Archive")
(mu4e-trash-folder . "/jeremy-dormitzer-gmail-com/Trash")
(message-sendmail-extra-arguments
. ("-a" "jeremy-dormitzer-gmail-com"))))
,(make-mu4e-context
:name "Lola Gmail"
:match-func (lambda (msg)
(when msg
(string-match-p
"jeremydormitzer-lola-com"
(mu4e-message-field msg :path))))
:vars '((user-mail-address . "jeremydormitzer@lola.com")
(mu4e-sent-folder . "/jeremydormitzer-lola-com/Sent")
(mu4e-drafts-folder . "/jeremydormitzer-lola-com/Drafts")
(mu4e-refile-folder . "/jeremydormitzer-lola-com/Archive")
(mu4e-trash-folder . "/jeremydormitzer-lola-com/Trash")
(message-sendmail-extra-arguments
. ("-a" "jeremydormitzer-lola-com"))))))
;; Custom mark function to mark messages matching the current message
(defun mu4e-mark-matching-pred (msg from)
(mu4e-message-contact-field-matches msg :from from))
(defun mu4e-mark-matching-input ()
(let* ((msg (mu4e-message-at-point t)))
(if (not msg)
(error "No message at point")
(cdr (mu4e-message-field msg :from)))))
(setq mu4e-headers-custom-markers
'(("Older than"
(lambda
(msg date)
(time-less-p
(mu4e-msg-field msg :date)
date))
(lambda nil
(mu4e-get-time-date "Match messages before: ")))
("Newer than"
(lambda
(msg date)
(time-less-p date
(mu4e-msg-field msg :date)))
(lambda nil
(mu4e-get-time-date "Match messages after: ")))
("Bigger than"
(lambda
(msg bytes)
(>
(mu4e-msg-field msg :size)
(* 1024 bytes)))
(lambda nil
(read-number "Match messages bigger than (Kbytes): ")))
("Matching current message from: field"
(lambda (msg from)
(mu4e-message-contact-field-matches msg :from from))
(lambda ()
(let* ((msg (mu4e-message-at-point t)))
(if (not msg)
(error "No message at point")
(cdar (mu4e-message-field msg :from))))))))
(add-hook 'mu4e-compose-pre-hook
(lambda ()
(set
(make-local-variable '*should-delete-trailing-whitespace*)
nil))))
#+end_src
Support sending attachments from Dired:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'dired
(require 'gnus-dired)
;; make the `gnus-dired-mail-buffers' function also work on
;; message-mode derived modes, such as mu4e-compose-mode
(defun gnus-dired-mail-buffers ()
"Return a list of active message buffers."
(let (buffers)
(save-current-buffer
(dolist (buffer (buffer-list t))
(set-buffer buffer)
(when (and (derived-mode-p 'message-mode)
(null message-sent-message-via))
(push (buffer-name buffer) buffers))))
(nreverse buffers)))
(setq gnus-dired-mail-mode 'mu4e-user-agent)
(add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode))
#+END_SRC
Support sending rich-text emails via Markdown:
#+BEGIN_SRC emacs-lisp
(defvar *message-md-pandoc-html-template*
"<html>
<head>
<meta charset=\"utf-8\" />
</head>
<body>
$body$
</body>
</html>"
"The default template used when converting markdown to HTML via pandoc.")
(defun gfm->html (gfm &optional template)
"Converts GitHub-flavored markdown to HTML via pandoc.
By default, the template `*message-md-pandoc-html-template*' is used,
but this can be overridden with the TEMPLATE argument."
(unless (executable-find "pandoc")
(error "Pandoc not found, unable to convert"))
(let ((template-file (make-temp-file "gfm->html-template"
nil ".html"
(or template *message-md-pandoc-html-template*))))
(with-temp-buffer
(insert gfm)
(unless
(= 0
(call-process-region (point-min) (point-max) "pandoc" t t nil
"--template" template-file "--quiet"
"-f" "gfm" "-t" "html"))
(error "Markdown to HTML conversion failed: %s"
(buffer-substring (point-min) (point-max))))
(buffer-substring (point-min) (point-max)))))
(defun mml-node->str (node)
"Converts a parsed MML node back to an MML string."
(let ((node-name (car node))
(node-alist (cdr node)))
(format "<#%s%s>\n%s"
node-name
(cl-reduce (lambda (acc pair)
(if (equal (car pair) 'contents)
acc
(concat acc (format " %s=%S"
(car pair)
(cdr pair)))))
node-alist
:initial-value "")
(cdr (assoc 'contents node-alist))
node-name)))
(defun multipart-message-as-string (plain html inline attachments)
"Given MML nodes PLAIN, and HTML and MML node lists INLINE and
ATTACHMENTS, constructs a multipart MML email and returns it as a
string."
(let* ((alternative (concat "<#multipart type=alternative>\n"
(mml-node->str plain) "\n"
(mml-node->str html) "\n"
"<#/multipart>"))
(related (when inline
(concat "<#multipart type=related>\n"
alternative "\n"
(cl-reduce
(lambda (acc node)
(concat acc (mml-node->str node) "\n"))
inline
:initial-value "")
"<#/multipart>")))
(mixed (when attachments
(concat "<#multipart type=mixed>\n"
(or related alternative) "\n"
(cl-reduce
(lambda (acc node)
(concat acc (mml-node->str node) "\n"))
attachments
:initial-value "")
"<#/multipart>"))))
(or mixed related alternative)))
(defun assoc-mml-node (key node)
(cdr (assoc key (cdr node))))
(defun multipart-html-message (raw)
"Creates a multipart HTML email with a text part and an html part."
(with-temp-buffer
(insert raw)
(let* ((parsed (mml-parse))
(plain (cl-reduce
(lambda (acc node)
(if (not (equal (assoc-mml-node 'type node) "text/plain"))
acc
`(part (type . "text/plain")
(contents . ,(concat (cdaddr acc)
(cdaddr node))))))
parsed
:initial-value '(part (type . "text/plain")
(contents . ""))))
(html `(part (type . "text/html")
(contents . ,(gfm->html (cdaddr plain)))))
(inline (nreverse
(cl-reduce
(lambda (acc node)
(if (not (equal (assoc-mml-node 'disposition node)
"inline"))
acc
(cons node acc)))
parsed
:initial-value nil)))
(attachments (nreverse
(cl-reduce
(lambda (acc node)
(if (not (equal (assoc-mml-node 'disposition node)
"attachment"))
acc
(cons node acc)))
parsed
:initial-value nil))))
(multipart-message-as-string plain
html
inline
attachments))))
(defun convert-message-to-markdown ()
"Convert the message in the current buffer to a multipart HTML email.
The HTML is rendered by treating the message content as Markdown."
(interactive)
(let* ((begin
(save-excursion
(goto-char (point-min))
(search-forward mail-header-separator)))
(end (point-max))
(raw-body (buffer-substring begin end)))
(undo-boundary)
(delete-region begin end)
(save-excursion
(goto-char begin)
(newline)
(insert (multipart-html-message raw-body)))))
(defun message-md-send (&optional arg)
"Convert the current buffer and send it.
If given prefix arg ARG, skips markdown conversion."
(interactive "P")
(unless arg
(convert-message-to-markdown))
(message-send))
(defun message-md-send-and-exit (&optional arg)
"Convert the current buffer and send it, then exit from mail buffer.
If given prefix arg ARG, skips markdown conversion."
(interactive "P")
(unless arg
(convert-message-to-markdown))
(message-send-and-exit))
(with-eval-after-load 'message
(define-key message-mode-map (kbd "C-c C-s") #'message-md-send)
(define-key message-mode-map (kbd "C-c C-c") #'message-md-send-and-exit))
;; Handle replies to HTML emails as well
(defun html->gfm (html)
(unless (executable-find "pandoc")
(error "Pandoc not found, unable to convert"))
(with-temp-buffer
(insert html)
(unless
(= 0
(call-process-region (point-min) (point-max) "pandoc" t t nil
"--quiet"
"-f" "html-native_divs-native_spans"
"-t" "gfm"))
(error "HTML to mardkwon conversion failed: %s"
(buffer-substring (point-min) (point-max))))
(buffer-substring (point-min) (point-max))))
(with-eval-after-load 'mu4e
(defvar message-md-rich-text-reply t)
(defvar message-md--inhibit-rich-text-reply nil)
(defun html-to-md-command (msg)
"Returns the text of MSG as Markdown."
(if (mu4e-msg-field msg :body-html)
(html->gfm (mu4e-msg-field msg :body-html))
(mu4e-msg-field msg :body-txt)))
(defun mu4e-draft-cite-original-advice (oldfn &rest args)
(let ((res (if (and message-md-rich-text-reply
(not message-md--inhibit-rich-text-reply))
(let ((mu4e-view-prefer-html t)
(mu4e-html2text-command #'html-to-md-command))
(apply oldfn args))
(apply oldfn args))))
(setq message-md--inhibit-rich-text-reply nil)
res))
(defun mu4e-compose-reply-advice (oldfn &rest args)
(when current-prefix-arg
(setq message-md--inhibit-rich-text-reply t))
(apply oldfn args))
(advice-add 'mu4e~draft-cite-original :around #'mu4e-draft-cite-original-advice)
(advice-add 'mu4e-compose-reply :around #'mu4e-compose-reply-advice))
;; Add an "X-Attachment-Id" header to MIME stuff as well as a Content-Id:
(with-eval-after-load 'mml
(defun mml-insert-mime-headers (cont type charset encoding flowed)
(let (parameters id disposition description)
(setq parameters
(mml-parameter-string
cont mml-content-type-parameters))
(when (or charset
parameters
flowed
(not (equal type mml-generate-default-type))
mml-insert-mime-headers-always)
(when (consp charset)
(error
"Can't encode a part with several charsets"))
(insert "Content-Type: " type)
(when charset
(mml-insert-parameter
(mail-header-encode-parameter "charset" (symbol-name charset))))
(when flowed
(mml-insert-parameter "format=flowed"))
(when parameters
(mml-insert-parameter-string
cont mml-content-type-parameters))
(insert "\n"))
(when (setq id (cdr (assq 'id cont)))
(insert "Content-ID: " id "\n"))
(when (setq x-attachment-id (cdr (assq 'x-attachment-id cont)))
(insert "X-Attachment-Id: " x-attachment-id "\n"))
(setq parameters
(mml-parameter-string
cont mml-content-disposition-parameters))
(when (or (setq disposition (cdr (assq 'disposition cont)))
parameters)
(insert "Content-Disposition: "
(or disposition
(mml-content-disposition type (cdr (assq 'filename cont)))))
(when parameters
(mml-insert-parameter-string
cont mml-content-disposition-parameters))
(insert "\n"))
(unless (eq encoding '7bit)
(insert (format "Content-Transfer-Encoding: %s\n" encoding)))
(when (setq description (cdr (assq 'description cont)))
(insert "Content-Description: ")
(setq description (prog1
(point)
(insert description "\n")))
(mail-encode-encoded-word-region description (point))))))
(defun message-md-insert-inline-image (image type description)
(interactive
(let* ((file (mml-minibuffer-read-file "Insert image: "))
(type (if current-prefix-arg
(or (mm-default-file-encoding file)
"application/octet-stream")
(mml-minibuffer-read-type file)))
(description (if current-prefix-arg
nil
(mml-minibuffer-read-description))))
(list file type description)))
(let ((id (format "%s%s" (s-snake-case description) (random))))
(insert (format "![%s](cid:%s)" description id))
(save-excursion
(goto-char (point-max))
(newline)
(insert
(format (concat "<#part id=\"<%s>\" x-attachment-id=%S "
"type=%S filename=%S disposition=inline description=%s>")
id id type image description)))))
#+END_SRC
Global keybindings:
#+begin_src emacs-lisp
(leader-def-key "am" #'mu4e)
#+end_src
Keybindings within mu4e:
#+begin_src emacs-lisp
(with-eval-after-load 'mu4e
(general-def '(normal motion insert emacs) mu4e-headers-mode-map "t" #'mu4e-headers-mark-thread)
(general-def '(normal motion insert emacs) mu4e-view-mode-map "t" #'mu4e-view-mark-thread))
#+end_src
Mu4e uses shr to render HTML emails. Unfortunately the shr function
that sets faces in the rendered document has a bug: it appends the shr
faces to the existing face-list, rather than prepending the shr
face. This means that e.g. links don't actually get rendered correctly
if there is some non-link face already on the text. The fix:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'shr
(defun shr-add-font (start end type)
(save-excursion
(goto-char start)
(while (< (point) end)
(when (bolp)
(skip-chars-forward " "))
;; Remove the APPEND argument to add-face-text-property
;; so the face ends up at the head of the face list
(add-face-text-property (point) (min (line-end-position) end) type)
(if (< (line-end-position) end)
(forward-line 1)
(goto-char end))))))
#+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)
:init
(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")
w3m-use-tab-line nil
browse-url-browser-function '(("nytimes.com" . w3m-browse-url)
("wsj.com" . w3m-browse-url)
("." . browse-url-default-browser)))
:general
('normal w3m-mode-map "SPC" leader-map)
('(normal visual motion) w3m-mode-map "C-f" #'w3m-scroll-up-or-next-url)
('(normal visual motion) w3m-mode-map "C-b" #'w3m-scroll-down-or-previous-url)
('normal w3m-mode-map "J" #'w3m-previous-buffer)
('normal w3m-mode-map "K" #'w3m-next-buffer)
('normal w3m-mode-map "gs" #'w3m-search)
('normal w3m-mode-map "gS" #'w3m-search-new-session)
('normal w3m-mode-map "gl" #'link-hint-open-link)
('normal w3m-mode-map "gc" #'link-hint-copy-link)
('normal w3m-mode-map "zc" #'w3m-print-current-url))
(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
(use-package wakatime-mode
:if (executable-find "wakatime")
:init
(setq wakatime-api-key (password-store-get "wakatime-api-key")
wakatime-cli-path (executable-find "wakatime"))
:config
(global-wakatime-mode)
;; global-wakatime-mode breaks recovering autosaves for some reason
(advice-add 'recover-this-file :around
(lambda (oldfn &rest args)
(let ((wakatime-was-enabled global-wakatime-mode))
(when wakatime-was-enabled
(global-wakatime-mode -1))
(apply oldfn args)
(when wakatime-was-enabled
(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)
("https://metaredux.com/feed.xml" clojure)
("https://emacsredux.com/atom.xml" emacs))))
#+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
:commands (emojify-mode
emojify-apropos-emoji
emojify-insert-emoji)
:hook
((emacs-startup . emojify-mode))
:init
(leader-def-key "te" 'emojify-mode))
#+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
:general
(deadgrep-mode-map "SPC" leader-map))
(leader-def-key "fg" 'deadgrep)
#+END_SRC
* RCIRC
IRC in Emacs, just in case anyone actually still uses it...
Channels:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'rcirc
(setq rcirc-server-alist
`(("znc.jeremydormitzer.com"
:port 3000
:nick "jdormit"
:user-name "jdormit"
: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
:commands (dumb-jump-go dumb-jump-prompt)
:config (dumb-jump-mode))
(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
Variables:
#+BEGIN_SRC emacs-lisp
(setq dired-dwim-target t)
#+END_SRC
Set up a hydra for dired:
#+BEGIN_SRC emacs-lisp
(use-package dired
:straight (:type built-in)
:init
(defhydra hydra-dired (:hint nil :color pink)
"
_+_ mkdir _v_iew _m_ark _(_ details _i_nsert-subdir wdired
_C_opy _O_ view other _U_nmark all _)_ omit-mode _$_ hide-subdir C-x C-q : edit
_D_elete _o_pen other _u_nmark _l_ redisplay _w_ kill-subdir C-c C-c : commit
_R_ename _M_ chmod _t_oggle _g_ revert buf _e_ ediff C-c ESC : abort
_Y_ rel symlink _G_ chgrp _E_xtension mark _s_ort _=_ pdiff
_S_ymlink ^ ^ _F_ind marked _._ toggle hydra \\ flyspell
_r_sync ^ ^ ^ ^ ^ ^ _?_ summary
_z_ compress-file _A_ find regexp
_Z_ compress _Q_ repl regexp
T - tag prefix
"
("\\" dired-do-ispell)
("(" dired-hide-details-mode)
(")" dired-omit-mode)
("+" dired-create-directory)
("=" diredp-ediff) ;; smart diff
("?" dired-summary)
("$" diredp-hide-subdir-nomove)
("A" dired-do-find-regexp)
("C" dired-do-copy) ;; Copy all marked files
("D" dired-do-delete)
("E" dired-mark-extension)
("e" dired-ediff-files)
("F" dired-do-find-marked-files)
("G" dired-do-chgrp)
("g" revert-buffer) ;; read all directories again (refresh)
("i" dired-maybe-insert-subdir)
("l" dired-do-redisplay) ;; relist the marked or single directory
("M" dired-do-chmod)
("m" dired-mark)
("O" dired-display-file)
("o" dired-find-file-other-window)
("Q" dired-do-find-regexp-and-replace)
("R" dired-do-rename)
("r" dired-do-rsynch)
("S" dired-do-symlink)
("s" dired-sort-toggle-or-edit)
("t" dired-toggle-marks)
("U" dired-unmark-all-marks)
("u" dired-unmark)
("v" dired-view-file) ;; q to exit, s to search, = gets line #
("w" dired-kill-subdir)
("Y" dired-do-relsymlink)
("z" diredp-compress-this-file)
("Z" dired-do-compress)
("q" nil)
("." nil :color blue))
:general
((normal visual motion insert emacs) dired-mode-map "." 'hydra-dired/body)
(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 edit-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
(leader-def-key "ad" #'mpc)
(with-eval-after-load 'evil
(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
* wgrep
#+BEGIN_SRC emacs-lisp
(use-package wgrep
:defer t)
#+END_SRC
* Ivy
An alternative minibuffer completion framework:
#+BEGIN_SRC emacs-lisp
(use-package counsel
:defer 0
:config
(ivy-mode 1)
(counsel-mode 1)
(setq ivy-height 20
ivy-wrap t
ivy-use-virtual-buffers nil
ivy-count-format "%d/%d ")
(with-eval-after-load 'projectile
(setq projectile-completion-system 'ivy))
(leader-def-key "SPC" #'counsel-M-x)
(jdormit/define-prefix "i" "ivy")
(jdormit/define-prefix "iU" "ui")
(leader-def-key "ir" #'ivy-resume)
(leader-def-key "ip" #'swiper-thing-at-point)
(leader-def-key "iP" #'counsel-yank-pop)
(leader-def-key "iu" #'counsel-unicode-char)
(leader-def-key "iUt" #'counsel-load-theme)
(leader-def-key "is" #'swiper)
(leader-def-key "ia" #'swiper-all)
(leader-def-key "ff" #'counsel-find-file)
(leader-def-key "oc" #'counsel-org-capture)
(leader-def-key "bb" #'counsel-ibuffer)
(if (executable-find "rg")
(leader-def-key "ig" #'counsel-rg)
(leader-def-key "ig" #'counsel-grep))
(defvar counsel-set-frame-font-history nil)
(defun counsel-set-frame-font (font)
(interactive (list (ivy-read "Font: " (delete-dups (font-family-list))
:require-match t
:history 'counsel-set-frame-font-history
:caller 'counsel-set-frame-font)))
(set-frame-font font))
(leader-def-key "iUf" #'counsel-set-frame-font)
(defun counsel-ibuffer-kill-buffer (x)
(kill-buffer (cdr x)))
(ivy-set-actions
'counsel-ibuffer
'(("k" counsel-ibuffer-kill-buffer "kill buffer")))
;; Function to open files without Ivy to avoid lag in really huge directories
(defun find-file-default (filename &optional wildcards)
(interactive
(let ((completing-read-function 'completing-read-default))
(find-file-read-args "Find file: "
(confirm-nonexistent-file-or-buffer))))
(funcall-interactively 'find-file filename wildcards))
:general
("C-c C-r" #'ivy-resume)
("M-x" #'counsel-M-x)
("C-x C-f" #'counsel-find-file)
("C-M-u" #'counsel-unicode-char)
("C-c P" #'counsel-yank-pop)
("C-s" #'swiper-isearch)
(help-map "f" #'counsel-describe-function)
(help-map "v" #'counsel-describe-variable)
((normal motion visual) "g/" #'swiper-thing-at-point))
(use-package ivy-hydra
:after counsel)
(use-package counsel-projectile
:after (counsel projectile)
:commands (counsel-projectile
counsel-projectile-switch-project
counsel-projectile-find-file
counsel-projectile-grep)
:init
(counsel-projectile-mode)
(leader-def-key "pp" #'counsel-projectile-switch-project)
(leader-def-key "pf" #'counsel-projectile)
(if (executable-find "rg")
(leader-def-key "pg" #'counsel-projectile-rg)
(leader-def-key "pg" #'counsel-projectile-grep)))
(use-package lsp-ivy
:after (ivy lsp)
:commands (lsp-ivy-workspace-symbol
lsp-ivy-global-workspace-symbol))
(leader-def-key "cs" #'lsp-ivy-workspace-symbol)
#+END_SRC
* graphviz
#+BEGIN_SRC emacs-lisp
(use-package graphviz-dot-mode
:mode (("\\.dot\\'" . graphviz-dot))
:init
(with-eval-after-load 'org
(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 t)
(setq slack-prefer-current-team t)
:config
(slack-register-team
:name "lolatravel"
:default t
;; :client-id "2400384563.846406584294"
;; :client-secret "31d7bb557aebc773ef26fb53b0f3caf5"
:token "xoxs-2400384563-531653313251-753670261511-dd9fd44b4755430caf78045af40361d9a2bf46d8a5392f09ea1b6f3c6db0b545"
;; :token "xoxp-2400384563-531653313251-844227895840-6bd114a9d3d8642116f0a1d50675fa43"
:subscribed-channels '(1-1-2020
backend
booking-team-backend
critical-bugs
dev
devops
frontend
search-and-book
smash
southwest
work
not-work)
: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 (if (eq system-type 'darwin)
'osx-notifier
'libnotify)))
#+END_SRC
* Matrix
#+BEGIN_SRC emacs-lisp
(use-package matrix-client
:commands matrix-client-connect
:straight ((matrix-client :host github :repo "alphapapa/matrix-client.el"
:files (:defaults "logo.png" "matrix-client-standalone.el.sh"))))
#+END_SRC
* EMMS
The Emacs Multi-Media System. For libtag to work, libtag must be installed on the system via the system package manager. Then Emms should be installed from source via:
#+BEGIN_SRC shell :tangle no
git clone git://git.sv.gnu.org/emms.git
cd emms
make emms-print-metadata
make
make install
#+END_SRC
#+BEGIN_SRC emacs-lisp
(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/emms")
(autoload 'emms-smart-browse "emms")
(autoload 'emms-start "emms")
(autoload 'emms-stop "emms")
(autoload 'emms-pause "emms")
(autoload 'emms-next "emms")
(autoload 'emms-previous "emms")
(autoload 'emms "emms")
(autoload 'emms-play-directory-tree "emms")
(autoload 'emms-all "emms-setup")
(with-eval-after-load 'emms
(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")
(with-eval-after-load 'evil
(add-to-list 'evil-emacs-state-modes 'emms-browser-mode))
(general-def emms-browser-mode-map "," leader-map)
(general-def emms-playlist-mode-map "SPC" leader-map)
(general-def emms-playlist-mode-map "," leader-map)
(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)
(when (eq system-type 'darwin)
(define-emms-simple-player afplay '(file)
(regexp-opt '(".mp3" ".m4a" ".aac" ".m4p"))
"afplay")
(setq emms-player-list `(,emms-player-afplay))))
#+END_SRC
* Direnv
[[https://direnv.net/][Direnv]] automatically runs bash scripts when you enter certain directories. This sets it up to work with Emacs:
#+BEGIN_SRC emacs-lisp
(defun update-cider-env ()
(direnv-update-directory-environment nrepl-project-dir))
(use-package direnv
:if (executable-find "direnv")
:config
(direnv-mode)
(add-hook 'eshell-mode-hook #'direnv-update-directory-environment)
(add-hook 'eshell-directory-change-hook
(lambda ()
(unless (file-remote-p default-directory)
(direnv-update-directory-environment)))))
#+END_SRC
* SQL
Emacs has excellent built-in SQL support.
#+BEGIN_SRC emacs-lisp
(leader-def-key "sP" #'sql-postgres)
#+END_SRC
* GraphQL
GraphQL mode for editing GraphQL queries:
#+BEGIN_SRC emacs-lisp
(use-package graphql-mode
:mode "\\.gql\\'"
:commands (graphql-mode)
:config
(defvar graphql-env-alist '()
"An alist defining available GraphQL servers
The key is any symbol and the value is a cons pair of
(graphql-url . graphql-auth-token)")
(setq graphql-variables-file "~/variables.graphql")
(defun graphql-set-env (env)
(interactive
(list
(intern
(completing-read
"GraphQL env: "
(mapcar #'car graphql-env-alist)))))
(let* ((gql-params (alist-get env graphql-env-alist))
(url (car gql-params))
(auth-token (cdr gql-params)))
(setq graphql-url url
graphql-extra-headers `(("Authorization" . ,auth-token)))))
(defun graphql (env)
"Opens a graphql-mode buffer in the specified environment"
(interactive
(list
(intern
(completing-read
"GraphQL env: "
(mapcar #'car graphql-env-alist)))))
(graphql-set-env env)
(let ((graphql-buf (get-buffer-create "*graphql-query*")))
(set-buffer graphql-buf)
(when (string= "" (buffer-substring (point-min) (point-max)))
(insert "{\n \n}")
(goto-char 5))
(graphql-mode)
(switch-to-buffer graphql-buf)))
(defun find-graphql-variables-file ()
(interactive)
(find-file graphql-variables-file))
(jdormit/define-prefix "q" "graphql")
(leader-def-key "qe" #'graphql-set-env)
(leader-def-key "qf" #'find-graphql-variables-file)
(leader-def-key "aq" #'graphql)
(general-def graphql-mode-map "C-c C-e" #'graphql-set-env)
(general-def graphql-mode-map "C-c C-v" #'find-graphql-variables-file))
#+END_SRC
GraphQL environments:
#+BEGIN_SRC emacs-lisp
(setq graphql-env-alist
`((dev . ("https://api-dev.lola.co/api/graphql" .
,(password-store-get "lola-graphql-dev-token")))
(local . ("http://localhost:7200/api/graphql" .
,(password-store-get "lola-graphql-local-token")))))
#+END_SRC
And ob-graphql for evaluating GraphQL source blocks in org-mode:
#+BEGIN_SRC emacs-lisp
(use-package ob-graphql
:defer t)
#+END_SRC
* Docker
Syntax highlighting for Dockerfiles:
#+BEGIN_SRC emacs-lisp
(use-package dockerfile-mode
:mode ("\\Dockerfile\\'"))
#+END_SRC
* Kubernetes
#+BEGIN_SRC emacs-lisp
(use-package kubernetes
:ensure t
:commands (kubernetes-overview)
:init (leader-def-key "ak" #'kubernetes-overview)
:config
(add-to-list 'evil-emacs-state-modes 'kubernetes-overview-mode)
(general-def kubernetes-overview-mode-map "SPC" leader-map))
#+END_SRC
* AWS
** S3
#+BEGIN_SRC emacs-lisp
(use-package s3ed
:commands (s3ed-mode
s3ed-find-file
s3ed-save-file)
:init
(jdormit/define-prefix "fS" "s3")
(leader-def-key "fSf" #'s3ed-find-file)
(leader-def-key "fSs" #'s3ed-save-file)
:config
(defun browse-blob-in-emacs (url bufname)
(let* ((token (password-store-get "blob-logs-token"))
(buf (generate-new-buffer bufname))
(blob (shell-command-to-string (concat "curl "
"-s "
"--cookie 'TOKEN=" token "' "
url)))
(status
(with-current-buffer buf
(insert blob)
(json-mode)
(format-all-buffer))))
(if (not (equal status "Formatting error"))
(switch-to-buffer-other-window buf)
(with-current-buffer buf
(set-buffer-modified-p nil)
(kill-buffer))
(switch-to-buffer-other-window "*format-all-errors*"))))
(defun s3ed-open-blob-log (arg)
"Opens the blob log at point via the cloudfront proxy.
If given prefix arg ARG, opens in browser, otherwise opens in Emacs."
(interactive "P")
(let ((fname-at-point (dired-file-name-at-point)))
(if (string-match
"\\(bloblogs.ops.lola.co[m]*\\)\/blobs\/\\(\\(production\\|dev\\|local\\|smoke\\|staging\\)\/[0-9]+/.*\\)"
fname-at-point)
(let* ((proxy-url (format "https://%s" (match-string 0 fname-at-point))))
(if arg
(browse-url proxy-url)
(browse-blob-in-emacs proxy-url fname-at-point)))
(error "Not in a blob logs directory"))))
:general
((normal) s3ed-mode-map "B" #'s3ed-open-blob-log))
#+END_SRC
* Prodigy
[[https://github.com/rejeep/prodigy.el][Prodigy]] gives Emacs a nice way to run services (web servers, etc.).
#+BEGIN_SRC emacs-lisp
(use-package prodigy
:commands (prodigy)
:general
('normal 'prodigy-mode-map "SPC" leader-map)
('normal 'prodigy-view-mode-map "SPC" leader-map)
:config
(setq prodigy-completion-system 'default)
(add-hook 'prodigy-view-mode-hook (lambda () (toggle-truncate-lines 1))))
(leader-def-key "aP" #'prodigy)
#+END_SRC
Add the ability to associate a file with a service instead of a buffer:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'prodigy
(defun prodigy-service-file (service)
"Return SERVICE file.
If SERVICE file exists, use that. If not, find the first SERVICE
tag that has a file and return that."
(let ((file (prodigy-service-or-first-tag-with service :file)))
(if (functionp file)
(prodigy-callback-with-plist file service)
file)))
(defun prodigy-display-process-file-or-buffer ()
(interactive)
(when-let (service (prodigy-service-at-pos))
(if-let (file (prodigy-service-file service))
(progn
(find-file-literally file)
(prodigy-view-mode)
(auto-revert-tail-mode)
(general-define-key
:states 'normal
:keymaps 'local
"Q" #'kill-this-buffer))
(prodigy-switch-to-process-buffer service))))
(general-def 'normal prodigy-mode-map "`" #'prodigy-display-process-file-or-buffer))
#+END_SRC
And add the ability to inhibit all service output processing:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'prodigy
(defun prodigy-start-service (service &optional callback)
"Start process associated with SERVICE unless already started.
When CALLBACK function is specified, that is called when the
process has been started.
When the process is started, a timer starts and checks every
second for `prodigy-start-tryouts' times if the process is live.
If the process is not live after `prodigy-start-tryouts' seconds,
the process is put in failed status."
(declare (indent 1))
(unless (prodigy-service-started-p service)
(let* ((default-directory
(-if-let (cwd (prodigy-service-cwd service))
(f-full cwd)
default-directory))
(name (plist-get service :name))
(sudo (plist-get service :sudo))
(command (prodigy-service-command service))
(args (prodigy-service-args service))
(exec-path (append (prodigy-service-path service) exec-path))
(env (--map (s-join "=" it) (prodigy-service-env service)))
(process-environment (append env process-environment))
(process nil)
(create-process
(lambda ()
(unless process
(setq process (apply (if sudo 'prodigy-start-sudo-process 'start-process)
(append (list name nil command) args)))))))
(-when-let (init (prodigy-service-init service))
(funcall init))
(-when-let (init-async (prodigy-service-init-async service))
(let (callbacked)
(funcall
init-async
(lambda ()
(setq callbacked t)
(funcall create-process)))
(with-timeout
(prodigy-init-async-timeout
(error "Did not callback async callback within %s seconds"
prodigy-init-async-timeout))
(while (not callbacked) (accept-process-output nil 0.005)))))
(funcall create-process)
(let ((tryout 0))
(prodigy-every 1
(lambda (next)
(setq tryout (1+ tryout))
(if (process-live-p process)
(when callback (funcall callback))
(if (= tryout prodigy-start-tryouts)
(prodigy-set-status service 'failed)
(funcall next))))))
(plist-put service :process process)
(when (not (plist-get service :inhibit-process-filter))
(set-process-filter
process
(lambda (_ output)
(run-hook-with-args 'prodigy-process-on-output-hook service output))))
(set-process-query-on-exit-flag process nil)))))
#+END_SRC
* Lola
Some functions to make my day job easier.
** Services (Prodigy)
#+BEGIN_SRC emacs-lisp
(defun call-with-venv (venv callback)
(let ((venv-dir (cond
((file-exists-p venv) venv)
((file-exists-p
(substitute-in-file-name
(format "$WORKON_HOME/%s" venv)))
(substitute-in-file-name
(format "$WORKON_HOME/%s" venv)))
((t (error "virtual environment %s does not exist" venv))))))
(call-with-env-from-file (format "%s/bin/activate" venv-dir) callback)))
(defun get-latest-lola-log (prefix)
(lambda ()
(concat
"~/lola/logs/"
(car (last (directory-files "~/lola/logs"
nil
(concat prefix "[[:digit:]-]+T[[:digit:]:]+\\.log")))))))
(defun lola-log-file (prefix)
(format "~/lola/logs/%s%s.log"
prefix
(format-time-string "%Y-%m-%dT%H:%M:%S")))
(defun kill-log-buffers ()
(interactive)
(kill-matching-buffers "\\.log$" nil t)
(message "Killed log buffers"))
(cl-defun python-service-setup (venv &optional env-file &key env-dir)
(lambda (done)
(call-with-venv
venv
(if env-file
(lambda ()
(call-with-env-from-file env-file done :dir env-dir))
done))))
(defun call-with-lola-env (callback)
(let ((process-environment
(cons (format "LOLA_ENV=%s"
(completing-read
"Environment: "
'("local" "development" "staging")))
process-environment)))
(funcall callback)))
(prodigy-define-tag
:name 'lola)
(prodigy-define-service
:name "lola-server (gunicorn)"
:tags '(lola backend)
:command "bash"
:args (lambda ()
(list
"-c"
(format
"gunicorn -c server/web/gunicorn.conf.py \
-b 127.0.0.1:7200 bin.start_web:init_and_create_flask_app\\(\\) \
&> %s"
(lola-log-file "lola-server-"))))
:file (get-latest-lola-log "lola-server-")
:inhibit-process-filter t
:cwd "~/lola/lola-server"
:stop-signal 'int
:truncate-output t
:init-async (python-service-setup "lola-server"
"~/lola/lola-server/.env"))
(prodigy-define-service
:name "lola-server celery worker"
:tags '(lola backend)
:command "python"
:args '("bin/start_celery_worker.py" "-P" "gevent" "-n" "lola-server")
:cwd "~/lola/lola-server"
:stop-signal 'int
:truncate-output t
:init-async (python-service-setup "lola-server"
"~/lola/lola-server/.env"))
(prodigy-define-service
:name "travel-service"
:tags '(lola backend)
:command "bash"
:args (lambda ()
(list
"-c"
(format "python bin/start_web.py &> %s"
(lola-log-file "travel-svc-"))))
:cwd "~/lola/lola-travel-service"
:file (get-latest-lola-log "travel-svc-")
:inhibit-process-filter t
:stop-signal 'int
:truncate-output t
:init-async (python-service-setup "travel-service"
"~/lola/lola-travel-service/.env"))
(prodigy-define-service
:name "travel-service celery worker"
:tags '(lola backend)
:command "bash"
:args (lambda ()
(list
"-c"
(concat "python "
"bin/start_celery_workers.py "
"-n " "travel-service "
"-Q "
"default,io_pool,cpu_pool,priority_io_pool,priority_cpu_pool "
(format "&> %s" (lola-log-file "travel-svc-celery-")))))
:file (get-latest-lola-log "travel-svc-celery-")
:inhibit-process-filter t
:cwd "~/lola/lola-travel-service"
:stop-signal 'int
:truncate-output t
:init-async (python-service-setup "travel-service"
"~/lola/lola-travel-service/.env"))
(prodigy-define-service
:name "secrets"
:tags '(lola backend)
:command "python"
:args '("bin/cmdline.py" "www")
:cwd "~/lola/secrets"
:truncate-output t
:stop-signal 'int
:init-async (python-service-setup "secrets"
"~/lola/secrets/.env"))
(prodigy-define-service
:name "lola-desktop"
:tags '(lola frontend)
:command "npm"
:args '("start")
:cwd "~/lola/lola-desktop"
:port 3001
:env '(("PORT" "3001"))
:stop-signal 'int
:init-async #'call-with-lola-env)
(prodigy-define-service
:name "wallet"
:tags '(lola frontend)
:command "npm"
:args '("start")
:cwd "~/lola/wallet"
:stop-signal 'int
:env '(("PORT" "3000"))
:init-async #'call-with-lola-env)
(prodigy-define-service
:name "agent-console"
:tags '(lola frontend)
:command "npm"
:args '("start")
:cwd "~/lola/agent-console"
:stop-signal 'int
:env '(("PORT" "3002"))
:init-async (lambda (done)
(call-with-lola-env
(lambda ()
(nvm-use "v10.15.1" done)))))
(prodigy-define-service
:name "luigid"
:command "luigid"
:cwd "~/lola/data-pipeline"
:port 8082
:stop-signal 'int
:init-async (python-service-setup "data-pipeline"
"~/lola/data-pipeline/.env"))
(prodigy-define-service
:name "prometheus"
:command "prometheus"
:args '("--config.file=prometheus.yml")
:port 9090
:stop-signal 'int
:cwd "~/prometheus")
(prodigy-define-service
:name "priceline-service"
:tags '(lola backend)
:command "~/lola/python-services/priceline/bin/start.sh"
:args '("web")
:cwd "~/lola/python-services"
:stop-signal 'int
:init-async (python-service-setup "~/lola/python-services/.venv"
"~/lola/python-services/priceline/.env"
:env-dir "~/lola/python-services"))
(prodigy-define-service
:name "threev-service"
:tags '(lola backend)
:command "~/lola/python-services/threev/bin/start.sh"
:args '("web")
:cwd "~/lola/python-services"
:stop-signal 'int
:init-async (python-service-setup "~/lola/python-services/.venv"
"~/lola/python-services/threev/.env"
:env-dir "~/lola/python-services"))
(prodigy-define-service
:name "ean-hotels-service"
:tags '(lola backend)
:command "~/lola/python-services/ean_hotels/bin/start.sh"
:args '("web")
:cwd "~/lola/python-services"
:stop-signal 'kill
:init-async (python-service-setup "~/lola/python-services/.venv"
"~/lola/python-services/ean_hotels/.env"
:env-dir "~/lola/python-services"))
(prodigy-define-service
:name "email-template-service"
:tags '(lola backend)
:command "npm"
:args '("start")
:cwd "~/lola/email-template-service"
:env '(("PORT" "7300"))
:stop-signal 'int
:init-async (lambda (done)
(nvm-use "10.15.1" done)))
(prodigy-define-service
:name "mabl-link-agent"
:command "link-agent"
:args (lambda ()
(list "-a" (password-store-get "mabl-link-agent")
"-n" "jdormit-macbook")))
(prodigy-define-service
:name "xray-daemon"
:command "xray_mac"
:args '("-o" "-n" "us-east-1"))
#+END_SRC
** Services (eShell)
#+BEGIN_SRC emacs-lisp :lexical yes
(defun run-service-in-eshell (name dir cmd &optional setup)
(if-let ((buf (get-buffer name)))
(progn (when (eq major-mode 'eshell-mode)
(eshell-interrupt-process)
(while eshell-process-list))
(kill-buffer buf)))
(let ((buf (get-buffer-create name)))
(switch-to-buffer buf)
(cd dir)
(eshell-mode)
(when setup (funcall setup))
(insert cmd)
(eshell-send-input)))
(defun release-manager ()
(interactive)
(run-service-in-eshell "*release-manager*"
"~/lola/release-manager"
"pipenv run python release-manager"))
(jdormit/define-prefix "L" "lola")
(leader-def-key "Lr" #'release-manager)
#+END_SRC
** Python stuff
Run pip install in the current project:
#+BEGIN_SRC emacs-lisp
(defun pip-install (reqs)
(interactive
(list
(read-file-name "Requirements file: "
(or (projectile-project-root)
default-directory)
nil t "requirements"
(lambda (name)
(string-match-p "requirements.*"
name)))))
(with-temp-buffer
(cd (or (projectile-project-root)
default-directory))
(compile (format "pip install -r %s" reqs))))
#+END_SRC
** AWS-MFA
The aws-mfa command:
#+BEGIN_SRC emacs-lisp
(defun aws-mfa (mfa-token)
(interactive "MMFA code: ")
(let ((proc (start-process "aws-mfa"
"*aws-mfa*"
"aws-mfa"
"--force")))
(set-process-sentinel
proc
(make-success-err-msg-sentinel "*aws-mfa*"
"AWS MFA succeeded"
"AWS MFA failed, check *aws-mfa* buffer for details"))
(process-send-string proc (concat mfa-token "\n"))))
(with-eval-after-load 'kubernetes
(general-def kubernetes-overview-mode-map "m" #'aws-mfa))
#+END_SRC
** 1Password
#+BEGIN_SRC emacs-lisp
(defvar op-token nil
"The 1Password session token")
(defun 1pass-signin ()
(interactive)
(cl-letf* ((signin-address "team-lolatravel.1password.com")
(signin-email "jeremydormitzer@lola.com")
(secret-key (password-store-get "1pass-lola-secret-key"))
((symbol-function 'op-signin)
(make-shell-fn "pass" "team-lolatravel.1password.com" "|" "op" "signin" signin-address signin-email secret-key "--output=raw"))
(token (op-signin)))
(if (string-match-p "ERROR" token)
(error "Unable to sign in to 1Password: %s" token)
(setf op-token token)
(message (format "Signed in to 1Password with session token %s" op-token))
op-token)))
(defun op-fn (&rest args)
(lambda (&optional input)
(cl-letf* (((symbol-function 'op-function)
(apply #'make-shell-fn "op" `(,@args ,(format "--session=%s" op-token))))
(output (op-function input)))
(if (or (string-match-p "Authentication required" output)
(string-match-p "You are not currently signed in" output))
(cl-letf* ((new-token (1pass-signin))
((symbol-function 'op-function)
(apply #'make-shell-fn "op" `(,@args ,(format "--session=%s" new-token)))))
(op-function input))
output))))
(defun op-list-items ()
(cl-flet ((op-list-items-fn (op-fn "list" "items")))
(mapcar #'cdr
(mapcar (apply-partially #'assoc 'title)
(mapcar (apply-partially #'assoc 'overview)
(json-read-from-string (op-list-items-fn)))))))
(defun op-get-item (item)
(cl-flet ((op-get-item (op-fn "get" "item" item)))
(json-read-from-string (op-get-item))))
(defun op-get-item-field (item-json field-designation)
(let* ((fields (assoc-recursive item-json 'details 'fields))
(pw-field (car (seq-filter
(lambda (field)
(string= field-designation (cdr (assoc 'designation field))))
fields))))
(when pw-field (cdr (assoc 'value pw-field)))))
(defun op-copy-password (item)
(interactive
(list
(completing-read "1Password item: " (op-list-items))))
(if-let ((password (op-get-item-field (op-get-item item) "password")))
(with-temp-buffer
(insert password)
(copy-region-as-kill (point-min) (point-max))
(message "Copied password for \"%s\" to kill ring." item))
;; TODO if no password found, prompt for alternate field in record to return
(error "No password found in 1Password for \"%s\"." item)))
(leader-def-key "ao" #'op-copy-password)
#+END_SRC
** Resetting DNSResponder
#+BEGIN_SRC emacs-lisp
(defun reset-dnsresponsder ()
(interactive)
(sudo-shell-command "killall -HUP mDNSResponder"))
#+END_SRC
** Devpi
#+BEGIN_SRC emacs-lisp
(defvar devpi-indices nil
"Login profiles for devpi. An alist
where the keys are index names and
the values are alists with the keys
devpi-index-url, devpi-index-username,
and devpi-index-password-fn.")
(setq devpi-indices
`((pip .
((devpi-index-url . "https://pip.aws.lolatravel.com/pip/dev")
(devpi-index-username . "pip")
(devpi-index-password-fn . ,(lambda ()
(op-get-item-field
(op-get-item "PIP Devpi Login")
"password")))))
(jdormit .
((devpi-index-url . "https://pip.aws.lolatravel.com/jdormit/dev")
(devpi-index-username . "jdormit")
(devpi-index-password-fn . ,(lambda ()
(password-store-get "devpi-jdormit")))))))
(defun devpi-use-index (index-name)
(interactive
(list
(intern
(completing-read
"Devpi index to use: "
(mapcar #'car devpi-indices)))))
(cl-letf* ((index-alist (alist-get index-name devpi-indices))
(index-url (alist-get 'devpi-index-url index-alist))
(index-username (alist-get 'devpi-index-username index-alist))
((symbol-function 'index-password-fn) (alist-get 'devpi-index-password-fn index-alist))
(index-password (index-password-fn))
((symbol-function 'devpi-use) (make-process-fn "devpi" "use" index-url))
((symbol-function 'devpi-login)
(make-process-fn "devpi" "login" index-username "--password" index-password))
(login-result (devpi-login))
(use-result (devpi-use)))
(save-match-data
(if (and (string-match "credentials valid" login-result)
(string-match "current devpi index" use-result))
(message "Switched to Devpi index %s" index-name)
(with-current-buffer (get-buffer-create "*devpi*")
(erase-buffer)
(goto-char (point-min))
(insert login-result)
(insert use-result))
(message "Failed to switch to Devpi index %s - check the *devpi* buffer for details" index-name)))))
(defun devpi-upload ()
(interactive)
(cl-letf (((symbol-function 'devpi-upload) (make-process-fn "devpi" "upload")))
(message (devpi-upload))))
#+END_SRC
** Release notes
#+BEGIN_SRC emacs-lisp
(defvar release-notes-repo (expand-file-name
"~/lola/release-manager/release_notes")
"The directory containing the release notes repository.")
(defun lola-release-notes (staged &optional repos)
"Prints release notes for the latest release.
If called with a prefix argument, prints release notes
for the upcoming release instead."
(interactive "P")
(let* ((all-repos '("lola-server"
"lola-travel-service"
"lola-desktop"))
(repos
(or
repos
(if (y-or-n-p "Query all repos?")
all-repos
(completing-read-multiple "Repos: "
all-repos)))))
(with-env-from-file (expand-file-name
(concat release-notes-repo "/.env"))
(async-shell-command
(format
(concat release-notes-repo "/venv/bin/python "
release-notes-repo "/release_notes/query_release_notes.py "
(when staged "--staged ")
" %s")
(combine-and-quote-strings repos))
"*release-notes*"))))
#+END_SRC
* StumpWM
A handy keybinding to connect to the StumpWM SBCL process via SLIME:
#+BEGIN_SRC emacs-lisp
(defun connect-stumpwm ()
(interactive)
(slime-connect "127.0.0.1" 4004))
#+END_SRC
* Emacs Network Client
Emacs frontend for networkmanager.
#+BEGIN_SRC emacs-lisp
(use-package enwc
:config
(setq enwc-default-backend 'nm)
(add-to-list 'evil-emacs-state-modes 'enwc-mode)
(general-def enwc-mode-map "SPC" leader-map)
:init
(leader-def-key "an" #'enwc)
:commands (enwc))
#+END_SRC
* Deft
A fuzzy-finder for notes.
#+BEGIN_SRC emacs-lisp
(use-package deft
:commands (deft)
:init
(setq deft-extensions '("org" "txt" "md" "markdown" "text")
deft-recursive t
deft-directory (concat
(file-name-as-directory (get-dropbox-directory))
"org"))
;; Still lots of notes in the old Deft directory
(add-to-list 'org-agenda-files
(concat (file-name-as-directory (get-dropbox-directory))
"/org/deft"))
(leader-def-key "D" #'deft)
(leader-def-key "od" #'deft)
:config
(setq deft-use-filter-string-for-filename t
deft-file-naming-rules '((noslash . "-")
(nospace . "-")
(case-fn . downcase))
deft-auto-save-interval 0)
(add-to-list 'evil-emacs-state-modes 'deft-mode))
#+END_SRC
Tell Deft to use the TITLE property for entry titles, if it exists:
#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'deft
(advice-add
'deft-parse-title :around
(lambda (old-fn file contents &rest args)
(let ((title (org-get-title contents)))
(if title
title
(apply old-fn file contents args))))))
#+END_SRC
* Pollen
#+BEGIN_SRC emacs-lisp
(use-package pollen-mode
:mode ("\\.p\\'" "\\.pp\\'" "\\.pm\\'"))
#+END_SRC
* Ngrok
#+BEGIN_SRC emacs-lisp
(defun ngrok (port &optional subdomain)
(interactive "nPort: \nsSubdomain: ")
(let ((buf (get-buffer-create
(concat "*ngrok-"
(number-to-string port)
"*")))
(cmd (if (and subdomain (not (string-empty-p subdomain)))
(concat "ngrok http "
"--subdomain=" subdomain " "
(number-to-string port))
(concat "ngrok http " (number-to-string port)))))
(async-shell-command cmd buf)))
#+END_SRC
* Make
#+BEGIN_SRC emacs-lisp
(defun make ()
(interactive)
(let ((project-root (projectile-project-root)))
(if project-root
(with-temp-buffer
(cd project-root)
(async-shell-command "make"))
(error "Not in a project"))))
#+END_SRC
* Redis
#+BEGIN_SRC emacs-lisp
(defun redis-cli (&optional host port)
(interactive (list (read-string "host (default localhost): " nil nil "localhost")
(read-number "port: " 6379)))
(let ((cli-path (executable-find "redis-cli"))
(host (or host "localhost"))
(port (or port 6379)))
(if cli-path
(progn
(make-comint-in-buffer
"redis-cli"
nil
"redis-cli"
nil
"-h" host
"-p" (number-to-string port)
"--no-raw")
(switch-to-buffer "*redis-cli*"))
(error "Can't find redis-cli"))))
#+END_SRC
* Restclient
Explore APIs from within Emacs!
#+BEGIN_SRC emacs-lisp
(use-package restclient
:commands (restclient-mode))
(use-package company-restclient
:after (restclient company)
:config
(add-to-list 'company-backends 'company-restclient))
(use-package ob-restclient
:defer t
:hook (org-mode . (lambda () (require 'ob-restclient))))
#+END_SRC
* IMenu
Get a nice IMenu sidebar:
#+BEGIN_SRC emacs-lisp
(use-package imenu-list
:config
(setq imenu-list-focus-after-activation t)
:general
(imenu-list-major-mode-map "SPC" leader-map)
(imenu-list-major-mode-map "." #'imenu-list-display-entry))
(defun open-imenu-list ()
(interactive)
(if (and (fboundp 'lsp-ui-imenu)
(boundp 'lsp-mode)
lsp-mode)
(lsp-ui-imenu)
(imenu-list-smart-toggle)))
(leader-def-key "\\" #'open-imenu-list)
(leader-def-key "m" #'imenu)
#+END_SRC
* calfw-org
A fancy calendar view:
#+BEGIN_SRC emacs-lisp
(use-package calfw
:commands (cfw:open-calendar-buffer))
(use-package calfw-org
:config (require 'calfw)
:commands (cfw:open-org-calendar))
(leader-def-key "oC" #'cfw:open-org-calendar)
#+END_SRC
* hackernews
#+BEGIN_SRC emacs-lisp
(use-package hackernews
:commands (hackernews)
:config
(setq hackernews-internal-browser-function 'w3m-browse-url)
:general
('normal hackernews-mode-map "SPC" leader-map))
(leader-def-key "ah" 'hackernews)
#+END_SRC
* counsel-spotify
Spotify in Emacs!
#+BEGIN_SRC emacs-lisp
(use-package counsel-spotify
:commands (counsel-spotify-search-track
counsel-spotify-search-album
counsel-spotify-search-artist
counsel-spotify-seach-tracks-by-album
counsel-spotify-search-tracks-by-artist
counsel-spotify-play
counsel-spotify-toggle-play-pause
counsel-spotify-next
counsel-spotify-previous)
:config
(setq counsel-spotify-client-id "825add4224704126adf3912b847c86df"
counsel-spotify-client-secret "f71fb236c06b4af886658667056ef7bd"))
(jdormit/define-prefix "iS" "counsel-spotify")
(leader-def-key "iSt" #'counsel-spotify-search-track)
(leader-def-key "iSa" #'counsel-spotify-search-album)
(leader-def-key "iSA" #'counsel-spotify-search-artist)
(leader-def-key "iS SPC" #'counsel-spotify-toggle-play-pause)
(leader-def-key "iSn" #'counsel-spotify-next)
(leader-def-key "iSp" #'counsel-spotify-previous)
#+END_SRC
* ISpell
#+BEGIN_SRC emacs-lisp
(when (executable-find "hunspell")
(setq ispell-program-name "hunspell"
ispell-really-hunspell t))
#+END_SRC
* VTerm
A better terminal emulator for Emacs. Replaces ansi-term, not EShell.
#+BEGIN_SRC emacs-lisp
(defun eshell-exec-in-vterm (&rest args)
(let* ((program (car args))
(buf (generate-new-buffer
(concat "*" (file-name-nondirectory program) "*"))))
(with-current-buffer buf
(vterm-mode)
(vterm-send-string (concat (s-join " " args) "\n")))
(switch-to-buffer buf)))
(use-package vterm
:if module-file-suffix
:init
(with-eval-after-load 'em-term
(defun eshell-exec-visual (&rest args)
(apply #'eshell-exec-in-vterm args)))
:commands (vterm vterm-other-window vterm-mode))
(defun run-vterm (&optional new-buffer)
(interactive "P")
(let ((buffer-name (when (not new-buffer) "vterm")))
(if (and buffer-name (get-buffer buffer-name))
(switch-to-buffer buffer-name)
(vterm buffer-name))))
(defun open-vterm (&optional arg)
(interactive "P")
(if (and (fboundp 'projectile-project-root)
(projectile-project-root))
(projectile-run-vterm arg)
(run-vterm arg)))
(leader-def-key "sv" 'open-vterm)
#+END_SRC
* Hide mode line
Does what it says on the box. I use it to hide the mode line in Neotree buffers.
#+BEGIN_SRC emacs-lisp
(use-package hide-mode-line
:hook ((neotree-mode imenu-list-major-mode) . hide-mode-line-mode))
#+END_SRC
* show-paren-mode
#+BEGIN_SRC emacs-lisp
(show-paren-mode 1)
#+END_SRC
* Dash
[[https://kapeli.com/dash][Dash]] is a code browser app for MacOS. [[https://github.com/stanaka/dash-at-point][dash-at-point]] is an Emacs interface to it.
#+BEGIN_SRC emacs-lisp
(use-package dash-at-point
:if (file-exists-p "/Applications/Dash.app")
:general
((normal visual motion insert emacs) "C-c d" 'dash-at-point)
((normal visual motion insert emacs) "C-c e" 'dash-at-point-with-docset))
#+END_SRC
* Alfred
Add a function to power the [[https://orgmode.org/worg/org-contrib/alfred-org-capture.html][alfred-org-capture]] Alfred workflow:
#+BEGIN_SRC emacs-lisp
(defun make-orgcapture-frame ()
"Create a new frame and run org-capture."
(interactive)
(make-frame '((name . "Org Capture") (width . 100) (height . 24)
(top . 400) (left . 300)))
(select-frame-by-name "Org Capture")
(add-hook 'org-capture-after-finalize-hook #'delete-frame t t)
(counsel-org-capture))
#+END_SRC
* gist.el
[[https://github.com/defunkt/gist.el][Integrate with GitHub gists]] from Emacs!
#+BEGIN_SRC emacs-lisp
(use-package gist
:defer t
:config
(add-hook 'gist-mode-hook
(lambda ()
(evil-ex-define-local-cmd "w[rite]" 'gist-mode-save-buffer))))
#+END_SRC
* ffap
Add the ability to jump to a particular line number in find-file-at-point. "Borrowed" from the [[https://www.emacswiki.org/emacs/FindFileAtPoint#toc6][EmacsWiki]].
#+BEGIN_SRC emacs-lisp
(defvar ffap-file-at-point-line-number nil
"Variable to hold line number from the last `ffap-file-at-point' call.")
(defadvice ffap-file-at-point (after ffap-store-line-number activate)
"Search `ffap-string-at-point' for a line number pattern and
save it in `ffap-file-at-point-line-number' variable."
(let* ((string (ffap-string-at-point)) ;; string/name definition copied from `ffap-string-at-point'
(name
(or (condition-case nil
(and (not (string-match "//" string)) ; foo.com://bar
(substitute-in-file-name string))
(error nil))
string))
(line-number-string
(and (string-match ":[0-9]+" name)
(substring name (1+ (match-beginning 0)) (match-end 0))))
(line-number
(and line-number-string
(string-to-number line-number-string))))
(if (and line-number (> line-number 0))
(setq ffap-file-at-point-line-number line-number)
(setq ffap-file-at-point-line-number nil))))
(defadvice find-file-at-point (after ffap-goto-line-number activate)
"If `ffap-file-at-point-line-number' is non-nil goto this line."
(when ffap-file-at-point-line-number
(goto-line ffap-file-at-point-line-number)
(setq ffap-file-at-point-line-number nil)))
#+END_SRC
* Format-all-the-code
A package that bundles together common code beautifying tools for many languages (the actual formatting tools still need to be installed separately):
#+BEGIN_SRC emacs-lisp
(use-package format-all
:commands (format-all-buffer)
:init
(leader-def-key "cf" 'format-all-buffer))
#+END_SRC
* Compiling
Enable ANSI colors in compile buffers:
#+BEGIN_SRC emacs-lisp
(autoload 'ansi-color-apply-on-region "ansi-color")
(defun colorize-compilation-buffer ()
(ansi-color-apply-on-region compilation-filter-start (point-max)))
(add-hook 'compilation-filter-hook #'colorize-compilation-buffer)
#+END_SRC
Set up some keybindings for Comint-mode compilation buffers:
#+BEGIN_SRC emacs-lisp
(general-def (normal visual motion) compilation-shell-minor-mode-map "q" #'quit-window)
(general-def (normal visual motion) compilation-shell-minor-mode-map "gr" #'recompile)
#+END_SRC
* Wallabag
[[https://github.com/jdormit/emacs-wallabag-client][My Wallabag client]] is still a WIP, but it is useful enough to pull in right now:
#+BEGIN_SRC emacs-lisp
(use-package wallabag
:defer t
:straight (:host github :repo "jdormit/emacs-wallabag-client")
:custom
(wallabag-base-url "https://wallabag.jeremydormitzer.com")
(wallabag-client-id (password-store-get "wallabag-client-id"))
(wallabag-client-secret (password-store-get "wallabag-client-secret"))
(wallabag-username "jdormit")
(wallabag-password (password-store-get "wallabag.jeremydormitzer.com")))
#+END_SRC
* Clipmon
Syncs up the X Windows clipboard with the Emacs kill ring. Only
necessary on Linux.
#+BEGIN_SRC emacs-lisp
(use-package clipmon
:if (eq window-system 'x)
:hook (after-init . clipmon-mode-start))
#+END_SRC
* GUD
Emacs ships with a built-in debugger interface that I primarily use
for PDB. The default keybindings are RSI-inducing, so here's a hydra
to use instead:
#+BEGIN_SRC emacs-lisp
(defhydra hydra-gud (:hint nil)
"
_n_: step over _b_: set breakpoint _e_: execute statement _l_: refresh buffer
_s_: step into _d_: remove breakpoint _p_: print statement _q_: quit hyra
_>_: up stack frame _f_: finish function _w_: watch expression
_<_: down stack frame _c_: continue
"
(">" gud-up)
("<" gud-down)
("b" gud-break)
("d" gud-remove)
("e" gud-statement)
("f" gud-finish)
("l" gud-refresh)
("n" gud-next)
("p" gud-print)
("c" gud-cont :color blue)
("s" gud-step)
("w" gud-watch)
("q" nil))
(leader-def-key "cg" #'hydra-gud/body)
#+END_SRC
* realgud
Like [[info:emacs#Debuggers][GUD]], but better!
#+BEGIN_SRC emacs-lisp
(use-package realgud
:init
(defun projectile-pdb ()
(interactive)
(with-projectile-root
(funcall-interactively #'realgud:pdb))))
#+END_SRC
* EDiff
#+BEGIN_SRC emacs-lisp
(setq ediff-window-setup-function #'ediff-setup-windows-plain)
#+END_SRC
* Apache Drill
#+BEGIN_SRC emacs-lisp
(use-package sql-drill
:straight (:host github :repo "jdormit/sql-drill.el")
:commands (sql-drill)
:init
(when (file-exists-p "~/drill/apache-drill-1.17.0/bin/drill-embedded")
(setq sql-drill-program
(expand-file-name "~/drill/apache-drill-1.17.0/bin/drill-embedded"))))
#+END_SRC
* Helpful
A much-improved help buffer:
#+BEGIN_SRC emacs-lisp
(use-package helpful
:defer t
:init
(setq counsel-describe-function-function #'helpful-callable
counsel-describe-variable-function #'helpful-variable)
(add-hook 'emacs-lisp-mode-hook
(lambda ()
(set (make-local-variable 'evil-lookup-func)
#'helpful-at-point)))
:general
(help-map "k" #'helpful-key))
#+END_SRC
* Inform
Provides links to the help documentation for ELisp symbols from Info
buffers:
#+BEGIN_SRC emacs-lisp
(use-package inform
:straight (:host github :repo "dieter-wilhelm/inform")
:defer 0
:config
;; If Helpful is installed, link to Helpful buffers instead of vanilla help
(with-eval-after-load 'helpful
(define-button-type 'inform-function
:supertype 'inform-xref
'inform-function 'helpful-function
'inform-echo (purecopy "mouse-2, RET: describe this function"))
(define-button-type 'inform-variable
:supertype 'inform-xref
'inform-function 'helpful-variable
'inform-echo (purecopy "mouse-2, RET: describe this variable"))
(define-button-type 'inform-symbol
:supertype 'inform-xref
'inform-function #'helpful-symbol
'inform-echo (purecopy "mouse-2, RET: describe this symbol"))))
#+END_SRC
* Vuiet
A music browser and player:
#+BEGIN_SRC emacs-lisp
(use-package versuri
:straight (versuri :host github :repo "mihaiolteanu/versuri")
:defer t)
(use-package vuiet
:straight (vuiet :host github :repo "mihaiolteanu/vuiet")
:commands (vuiet-track-artist
vuiet-track-name
vuiet-track-duration
vuiet--playing-track)
:defer t
:config
(setq vuiet-update-mode-line-automatically t
vuiet-update-mode-line-interval 1)
(defun vuiet-mode-setup ()
(set (make-local-variable 'org-link-elisp-confirm-function)
nil))
(add-hook 'vuiet-mode-hook #'vuiet-mode-setup)
:general
((normal motion visual) vuiet-mode-map "q" #'quit-window)
((normal motion visual) vuiet-mode-map "C-m" #'org-open-at-point))
#+END_SRC
And a handy hydra for it:
#+BEGIN_SRC emacs-lisp
(defun now-playing (length)
(let* ((str (s-truncate length
(if-let ((track (vuiet--playing-track)))
(format "%s - %s"
(vuiet-track-artist track)
(vuiet-track-name track))
"----")))
(remainder (- length (length str))))
(if (> remainder 0)
(let* ((left-pad (/ remainder 2))
(right-pad (- remainder left-pad)))
(s-prepend (s-repeat left-pad " ")
(s-append (s-repeat right-pad " ") str)))
str)))
(defun track-pos ()
(if-let ((track (vuiet--playing-track)))
(format "[%s/%s]"
(format-time-string "%M:%S" (condition-case nil
(mpv-get-playback-position)
(error 0)))
(s-pad-left 5 "0" (vuiet-track-duration track)))
"[00:00/00:00]"))
(defvar hydra-vuiet-body-timer)
(defun refresh-hydra-vuiet ()
(interactive)
(hydra-show-hint hydra-vuiet/hint 'refresh-hydra-vuiet))
(defhydra hydra-vuiet (:hint nil
:color blue
:body-pre (setq hydra-vuiet-body-timer
(run-at-time 1 1 #'refresh-hydra-vuiet))
:post (cancel-function-timers 'refresh-hydra-vuiet))
"
--- ^^ ^^ ^^ ---
+------------------/ / ^^ ^^ ^^ +------------------/ /
| - ------- / / ^^ ^^ ^^ | - ------- / /
|(-) .d########b. //)|+------------^^----------------^^------------^^-----+|(-) .d########b. //)| Play [_t_]ag(s)
| .d############// ||+-----------^^----------------^^------------^^----+|| .d############// | Play [_a_]rtist
| .d######''####//b. |||%s(now-playing 43)^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^||| .d######''####//b. | Play a[_l_]bum
| 9######( )#-//##P ||+-----------^^--+-------------^^+-----------^^----+|| 9######( )#-//##P |
| 'b######++#/-/##d' || ^^ |%s(track-pos)^^| ^^ || 'b######++#/-/##d' | [_L_]ove current track
| '9############P' || ^^ +-------------^^+ ^^ || '9############P' | [_U_]nlove current track
| -'9a#######aP' || +---------^^+---------------^^--+---------^^--+ || -'9a#######aP' |
| |-| `'''''' || | _s_top | _p_lay/pause | _n_ext | || |-| `'''''' | [_I_]nfo menu
| ---..----------- || +---------^^+---------------^^--+---------^^--+ || ---..----------- | [_S_]imilar menu
| |---||-----------| |+------------^^----------------^^------------^^-----+| |---||-----------| |
| | ^^ ^^ ^^ | | [_q_]uit
+--------------------+ ^^ ^^ ^^ +--------------------+
"
("s" vuiet-stop)
("p" vuiet-play-pause :color red)
("n" vuiet-next :color red)
("t" vuiet-play-tag-similar)
("a" vuiet-play-artist)
("l" vuiet-play-album)
("L" vuiet-love-track)
("U" vuiet-unlove-track)
("I" hydra-vuiet-info/body)
("S" hydra-vuiet-similar/body)
("q" nil))
(general-def "C-c v" #'hydra-vuiet/body)
(defvar hydra-vuiet-info-body-timer)
(defun refresh-hydra-vuiet-info ()
(interactive)
(hydra-show-hint hydra-vuiet-info/hint 'refresh-hydra-vuiet-info))
(defhydra hydra-vuiet-info (:hint nil
:color blue
:body-pre (setq hydra-vuiet-info-body-timer
(run-at-time 1 1 #'refresh-hydra-vuiet-info))
:post (cancel-function-timers 'refresh-hydra-vuiet-info))
"
--- ^^ ^^ ^^ ---
+------------------/ / ^^ ^^ ^^ +------------------/ /
| - ------- / / ^^ ^^ ^^ | - ------- / /
|(-) .d########b. //)|+------------^^----------------^^------------^^-----+|(-) .d########b. //)| Info for [_t_]ag
| .d############// ||+-----------^^----------------^^------------^^----+|| .d############// | Info for [_a_]rtist
| .d######''####//b. |||%s(now-playing 43)^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^||| .d######''####//b. | Info for a[_l_]bum
| 9######( )#-//##P ||+-----------^^--+-------------^^+-----------^^----+|| 9######( )#-//##P | Info for [_c_]urrent artist
| 'b######++#/-/##d' || ^^ |%s(track-pos)^^| ^^ || 'b######++#/-/##d' | L[_y_]rics for current track
| '9############P' || ^^ +-------------^^+ ^^ || '9############P' | [_L_]oved tracks
| -'9a#######aP' || +---------^^+---------------^^--+---------^^--+ || -'9a#######aP' |
| |-| `'''''' || | _s_top | _p_lay/pause | _n_ext | || |-| `'''''' | [_P_]layer menu
| ---..----------- || +---------^^+---------------^^--+---------^^--+ || ---..----------- | [_S_]imilar menu
| |---||-----------| |+------------^^----------------^^------------^^-----+| |---||-----------| |
| | ^^ ^^ ^^ | | [_q_]uit
+--------------------+ ^^ ^^ ^^ +--------------------+
"
("s" vuiet-stop)
("p" vuiet-play-pause :color red)
("n" vuiet-next :color red)
("t" vuiet-tag-info)
("a" vuiet-artist-info-search)
("l" vuiet-album-info-search)
("c" vuiet-playing-artist-info)
("y" vuiet-playing-track-lyrics)
("L" vuiet-loved-tracks-info)
("P" hydra-vuiet/body)
("S" hydra-vuiet-similar/body)
("q" nil))
(defvar hydra-vuiet-similar-body-timer)
(defun refresh-hydra-vuiet-similar ()
(interactive)
(hydra-show-hint hydra-vuiet-similar/hint 'refresh-hydra-vuiet-similar))
(defhydra hydra-vuiet-similar (:hint nil
:color blue
:body-pre (setq hydra-vuiet-similar-body-timer
(run-at-time 1 1 #'refresh-hydra-vuiet-similar))
:post (cancel-function-timers 'refresh-hydra-vuiet-similar))
"
--- ^^ ^^ ^^ ---
+------------------/ / ^^ ^^ ^^ +------------------/ /
| - ------- / / ^^ ^^ ^^ | - ------- / /
|(-) .d########b. //)|+------------^^----------------^^------------^^-----+|(-) .d########b. //)| Play [_t_]ag similar
| .d############// ||+-----------^^----------------^^------------^^----+|| .d############// | Play [_a_]rtist similar
| .d######''####//b. |||%s(now-playing 43)^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^||| .d######''####//b. | Play [_l_]oved tracks similar
| 9######( )#-//##P ||+-----------^^--+-------------^^+-----------^^----+|| 9######( )#-//##P |
| 'b######++#/-/##d' || ^^ |%s(track-pos)^^| ^^ || 'b######++#/-/##d' | Play [_c_]urrent artist similar
| '9############P' || ^^ +-------------^^+ ^^ || '9############P' | Play current [_T_]ag similar
| -'9a#######aP' || +---------^^+---------------^^--+---------^^--+ || -'9a#######aP' |
| |-| `'''''' || | _s_top | _p_lay/pause | _n_ext | || |-| `'''''' | [_P_]layer menu
| ---..----------- || +---------^^+---------------^^--+---------^^--+ || ---..----------- | [_I_]nfo menu
| |---||-----------| |+------------^^----------------^^------------^^-----+| |---||-----------| |
| | ^^ ^^ ^^ | | [_q_]uit
+--------------------+ ^^ ^^ ^^ +--------------------+
"
("s" vuiet-stop)
("p" vuiet-play-pause :color red)
("n" vuiet-next :color red)
("t" vuiet-play-tag-similar)
("a" vuiet-play-artist-similar)
("l" vuiet-play-loved-tracks-similar)
("c" vuiet-play-playing-artist-similar)
("T" vuiet-play-playing-tag-similar)
("P" hydra-vuiet/body)
("I" hydra-vuiet-info/body)
("q" nil))
#+END_SRC
* Jira
Jira in Emacs:
#+BEGIN_SRC emacs-lisp
(use-package org-jira
:init
(setq jiralib-url "https://lola.atlassian.net"
org-jira-working-dir (concat (get-dropbox-directory) "/org/jira")
org-jira-jira-status-to-org-keyword-alist '(("Won't Fix" . "CANCELLED")))
(add-to-list 'org-agenda-files org-jira-working-dir)
(add-to-list 'org-super-agenda-groups
`(:name "Jira" :file-path ,org-jira-working-dir)
t))
#+END_SRC
* Twitter
Socially networking from Emacs:
#+BEGIN_SRC emacs-lisp
(use-package twittering-mode
:commands (twit)
:init
(leader-def-key "at" #'twit)
:config
(setq twittering-use-master-password t)
(defun twittering-browse-uri (uri)
(interactive (list (or (get-text-property (point) 'uri)
(if (get-text-property (point) 'field)
(let* ((id (get-text-property (point) 'id))
(status (twittering-find-status id)))
(twittering-get-status-url-from-alist status))
nil))))
(when uri (browse-url uri)))
:general
(twittering-mode-map "SPC" leader-map)
(twittering-mode-map "W" #'twittering-browse-uri))
#+END_SRC