4177 lines
128 KiB
Org Mode
Executable File
4177 lines
128 KiB
Org Mode
Executable File
#+PROPERTY: header-args :results silent
|
|
#+PROPERTY: header-args:emacs-lisp :lexical t
|
|
|
|
This init file is based on [[https://medium.com/@CBowdon/pinching-the-best-bits-from-spacemacs-869b8c793ad3][this blog post]].
|
|
|
|
It's meant to be loaded from init.el like so:
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(require 'org)
|
|
(org-babel-load-file (expand-file-name "path/to/init.org"))
|
|
#+END_SRC
|
|
|
|
* Prelude
|
|
Enables lexical binding for everything in init.el:
|
|
#+BEGIN_SRC emacs-lisp
|
|
;;; -*- lexical-binding: t; -*-
|
|
#+END_SRC
|
|
|
|
Requires:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(require 'json)
|
|
#+END_SRC
|
|
|
|
* Default directory
|
|
#+BEGIN_SRC emacs-lisp
|
|
(cd "~")
|
|
#+END_SRC
|
|
|
|
* Packages
|
|
Use [[https://github.com/raxod502/straight.el][straight.el]] to manage packages:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defvar bootstrap-version)
|
|
(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
|
|
|
|
* Solarized
|
|
Solarized is the best color scheme, objectively speaking:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package solarized-theme)
|
|
(require 'solarized-theme)
|
|
#+END_SRC
|
|
|
|
* Doom themes
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package doom-themes)
|
|
|
|
(use-package doom-modeline
|
|
:init
|
|
(doom-modeline-mode 1))
|
|
#+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"))
|
|
(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
|
|
(add-hook 'before-save-hook #'delete-trailing-whitespace)
|
|
#+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
|
|
|
|
** 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)
|
|
:config (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
|
|
|
|
* Transient
|
|
A framework for creating Magit-style popups:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package transient)
|
|
#+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))
|
|
#+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))
|
|
(add-to-list 'evil-emacs-state-modes 'forge-topic-list-mode)
|
|
:general
|
|
(forge-topic-list-mode-map "SPC" leader-map))
|
|
#+END_SRC
|
|
|
|
** evil-magit
|
|
Evil keybindings for magit!
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package evil-magit
|
|
:after (evil magit forge)
|
|
:general
|
|
('normal magit-mode-map "SPC" leader-map))
|
|
#+END_SRC
|
|
|
|
** Transient
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq transient-default-level 7)
|
|
#+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
|
|
:if (executable-find "pass")
|
|
:commands (password-store-list
|
|
password-store-get
|
|
password-store-copy)
|
|
:config
|
|
(setq password-store-password-length 20)
|
|
(leader-def-key "P" 'password-store-copy))
|
|
|
|
(use-package pass
|
|
:if (executable-find "pass")
|
|
:commands pass
|
|
:general
|
|
('(normal motion visual) pass-mode-map "S" #'password-store-synchronize))
|
|
|
|
(leader-def-key "ap" #'pass)
|
|
#+END_SRC
|
|
|
|
* Emacs Lisp
|
|
Requires:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(eval-when-compile (require 'subr-x))
|
|
#+END_SRC
|
|
|
|
** 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)
|
|
(use-package dash)
|
|
(use-package dash-functional)
|
|
(use-package f)
|
|
(use-package request)
|
|
#+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)
|
|
#+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
|
|
(defun extract-vars-from-env-file (file)
|
|
"Extracts an alist of variable name to value from
|
|
a bash script that exports environment variables."
|
|
(let ((var-re "\\(.+?\\)=\\(.+\\)$")
|
|
(env '()))
|
|
(with-temp-buffer
|
|
(shell-command (concat "source " file " > /dev/null && env")
|
|
(current-buffer))
|
|
(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)))))
|
|
|
|
(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)))
|
|
|
|
(defun call-with-env-from-file (file callback)
|
|
(let* ((env (extract-vars-from-env-file file))
|
|
(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
|
|
|
|
** 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"))
|
|
(expand-file-name "~/Dropbox"))
|
|
((file-exists-p (expand-file-name "~/Dropbox (Personal)"))
|
|
(expand-file-name "~/Dropbox (Personal)"))))
|
|
|
|
#+END_SRC
|
|
|
|
Load up libraries from Dropbox, if there are any:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(add-to-list 'load-path
|
|
(concat
|
|
(file-name-as-directory (get-dropbox-directory))
|
|
"site-lisp"))
|
|
#+END_SRC
|
|
|
|
* Init File
|
|
A function to reload my init file. It reloads the major mode after the init file is loaded to rebind keymappings.
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun reload-init-file ()
|
|
(interactive)
|
|
(load-file "~/.emacs.d/init.el")
|
|
(funcall major-mode))
|
|
#+END_SRC
|
|
|
|
And another one to edit it:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun find-init-file ()
|
|
(interactive)
|
|
(find-file "~/init.org"))
|
|
#+END_SRC
|
|
|
|
* Keybindings
|
|
These are general keybindings; those specific to certain packages are in the sections for those packages.
|
|
|
|
|
|
In some modes I want vanilla Emacs bindings:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(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 "fs" 'sudo-find-file)
|
|
(leader-def-key "ft" 'auto-revert-tail-mode)
|
|
#+END_SRC
|
|
|
|
** Window commands
|
|
#+BEGIN_SRC emacs-lisp
|
|
(jdormit/define-prefix "w" "window")
|
|
(leader-def-key "w/" 'split-window-right)
|
|
(leader-def-key "w-" 'split-window-below)
|
|
(leader-def-key "wm" 'delete-other-windows)
|
|
(leader-def-key "wd" 'delete-window)
|
|
#+END_SRC
|
|
|
|
** Buffer commands
|
|
A function to switch to previous buffer from [[http://emacsredux.com/blog/2013/04/28/switch-to-previous-buffer/][this blog post]]:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun switch-to-previous-buffer ()
|
|
"Switch to previously open buffer.
|
|
Repeated invocations toggle between the two most recently open buffers."
|
|
(interactive)
|
|
(switch-to-buffer (other-buffer (current-buffer) 1)))
|
|
|
|
(leader-def-key "TAB" 'switch-to-previous-buffer)
|
|
#+END_SRC
|
|
|
|
A function to kill all buffers except the current one from [[https://www.emacswiki.org/emacs/KillingBuffers#toc2][EmacsWiki]]:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun kill-other-buffers ()
|
|
"Kill all other buffers."
|
|
(interactive)
|
|
(mapc 'kill-buffer (delq (current-buffer) (buffer-list))))
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(jdormit/define-prefix "b" "buffer")
|
|
(leader-def-key "bb" #'switch-to-buffer)
|
|
(leader-def-key "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)
|
|
#+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
|
|
|
|
* 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
|
|
|
|
* 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
|
|
|
|
* 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
|
|
|
|
* 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)
|
|
#+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")
|
|
: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)
|
|
(add-hook 'neotree-mode-hook 'hide-mode-line-mode))
|
|
|
|
(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)
|
|
: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
|
|
|
|
* Paredit/Parinfer
|
|
Paredit enables structured editing of s-expressions
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package paredit
|
|
:hook ((emacs-lisp-mode . enable-paredit-mode)
|
|
(lisp-mode . enable-paredit-mode)
|
|
(clojure-mode . enable-paredit-mode)
|
|
(cider-repl-mode . enable-paredit-mode)
|
|
(ielm-mode . enable-paredit-mode)
|
|
(scheme-mode . enable-paredit-mode)
|
|
(geiser-repl-mode . enable-paredit-mode)
|
|
(slime-repl-mode . enable-paredit-mode)))
|
|
|
|
|
|
(jdormit/define-prefix "l" "lisp")
|
|
(jdormit/define-prefix "lw" "wrap")
|
|
(leader-def-key "lwr" 'paredit-wrap-round)
|
|
(leader-def-key "lws" 'paredit-wrap-square)
|
|
(leader-def-key "lwc" 'paredit-wrap-curly)
|
|
(leader-def-key "ls" 'paredit-forward-slurp-sexp)
|
|
(leader-def-key "lb" 'paredit-forward-barf-sexp)
|
|
#+END_SRC
|
|
|
|
Parinfer infers parens from indentation and vice-versa:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package parinfer
|
|
:init
|
|
(leader-def-key "lt" 'parinfer-toggle-mode)
|
|
(setq parinfer-extensions '(defaults
|
|
pretty-parens
|
|
evil
|
|
paredit
|
|
smart-tab
|
|
smart-yank))
|
|
:hook ((clojure-mode . parinfer-mode)
|
|
(emacs-lisp-mode . parinfer-mode)
|
|
(common-lisp-mode . parinfer-mode)
|
|
(scheme-mode . parinfer-mode)
|
|
(lisp-mode . parinfer-mode)))
|
|
#+END_SRC
|
|
|
|
* 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
|
|
(use-package org
|
|
:mode ("\\.org\\'" . org-mode)
|
|
:commands (org-agenda org-capture))
|
|
|
|
(jdormit/define-prefix "o" "org")
|
|
(leader-def-key "oa" 'org-agenda)
|
|
(leader-def-key "oc" 'org-capture)
|
|
(setq org-src-fontify-natively t)
|
|
#+END_SRC
|
|
|
|
Use RET to follow links:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq org-return-follows-link t)
|
|
#+END_SRC
|
|
|
|
Always show inline images:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(add-hook 'org-mode-hook
|
|
(lambda ()
|
|
(org-display-inline-images nil t)
|
|
(org-redisplay-inline-images)))
|
|
#+END_SRC
|
|
|
|
** Agenda files
|
|
#+BEGIN_SRC emacs-lisp
|
|
(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%?")
|
|
("j" "Journal entry" entry
|
|
(file ,(agenda-files "journal.org"))
|
|
"* %<%Y-%m-%d %H:%M:%S>%?")
|
|
("p" "Project" entry
|
|
(file ,(agenda-files "notes.org"))
|
|
"* %^{Project name}\n\n** What's it supposed to do?\n%?\n** How can I test that it works?\n\n** How can I test that it didn't break anything?\n\n** What alternative approaches are there - what trade-offs can be made?\n\n** What assumptions have I made?\n\n** What deployables will I need to deploy?\n")
|
|
("b" "Brain" plain (function org-brain-goto-end)
|
|
"* %i%?" :empty-lines 1)
|
|
("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)
|
|
(setq org-agenda-tags-todo-honor-ignore-options t)
|
|
(setq org-agenda-custom-commands
|
|
'(("L" "Lola" ((tags-todo "@lola")))
|
|
("T" "Today's list"
|
|
((agenda)
|
|
(tags-todo "today")))))
|
|
#+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 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)
|
|
:config
|
|
(add-hook 'org-mode-hook #'evil-org-mode)
|
|
(add-hook 'evil-org-mode-hook
|
|
(lambda ()
|
|
(evil-org-set-key-theme
|
|
'(textobjects
|
|
insert
|
|
navigation
|
|
additional
|
|
shift
|
|
todo
|
|
heading))))
|
|
(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))
|
|
#+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
|
|
|
|
** 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
|
|
|
|
** 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"))
|
|
#+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))
|
|
|
|
(add-hook 'org-agenda-mode-hook #'org-gcal-fetch)
|
|
|
|
(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) cb)))
|
|
|
|
(general-def '(normal motion) org-agenda-mode-map "gr" #'org-agenda-redo-and-fetch-gcal)
|
|
#+END_SRC
|
|
|
|
* Projectile
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package projectile
|
|
:commands (projectile-find-file
|
|
projectile-grep
|
|
projectile-switch-project
|
|
projectile-project-root)
|
|
:config
|
|
(projectile-mode)
|
|
(jdormit/define-prefix "p" "projectile")
|
|
(leader-def-key "pf" #'projectile-find-file)
|
|
(leader-def-key "pg" #'projectile-grep)
|
|
(leader-def-key "pp" #'projectile-switch-project))
|
|
#+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
|
|
|
|
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
|
|
|
|
* 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
|
|
|
|
* EShell
|
|
Easy keybinding to open EShell:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(leader-def-key "'" 'eshell)
|
|
#+END_SRC
|
|
|
|
Make EShell's tab completion work like Bash's:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq eshell-cmpl-cycle-completions nil)
|
|
#+END_SRC
|
|
|
|
Destroy shell buffers created by eshell when the process dies::
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq eshell-destroy-buffer-when-process-dies t)
|
|
#+END_SRC
|
|
|
|
Visual programs:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun eshell-setup ()
|
|
(add-to-list 'eshell-visual-commands "crawl")
|
|
(add-to-list 'eshell-visual-commands "ssh"))
|
|
|
|
(add-hook 'eshell-mode-hook #'eshell-setup)
|
|
#+END_SRC
|
|
|
|
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-directory-change-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)))
|
|
|
|
(with-eval-after-load 'eshell
|
|
(general-def eshell-mode-map "C-c C-o" #'clear-eshell))
|
|
#+END_SRC
|
|
|
|
Some aliases:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defvar eshell-aliases
|
|
'(("k" . "kubectl $*")
|
|
("kctx" . "kubectx $*")))
|
|
(add-hook
|
|
'eshell-mode-hook
|
|
(lambda ()
|
|
(dolist (alias eshell-aliases)
|
|
(eshell/alias (car alias) (cdr alias)))))
|
|
#+END_SRC
|
|
|
|
* Flycheck
|
|
Syntax checking etc.:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package flycheck
|
|
:config
|
|
(setq-default flycheck-disabled-checkers '(emacs-lisp emacs-lisp-checkdoc))
|
|
(global-flycheck-mode))
|
|
#+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)))))
|
|
#+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
|
|
|
|
* Typescript
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package typescript-mode
|
|
:mode ("\\.ts\\'")
|
|
:config
|
|
(with-eval-after-load 'lsp
|
|
(add-hook 'typescript-mode-hook 'lsp)))
|
|
#+END_SRC
|
|
|
|
* LSP Mode
|
|
Emacs support for the Language Server Protocol
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package lsp-mode
|
|
:commands lsp-mode)
|
|
|
|
(use-package company-lsp
|
|
:after (lsp-mode company)
|
|
:config
|
|
(setq company-lsp-cache-candidates t))
|
|
|
|
(use-package lsp-ui
|
|
:after (lsp-mode)
|
|
:config (setq lsp-ui-sideline-enable t
|
|
lsp-ui-sideline-show-symbol t
|
|
lsp-ui-sideline-show-hover t
|
|
lsp-ui-sideline-show-code-actions t
|
|
lsp-ui-sideline-update-mode 'point))
|
|
|
|
(use-package dap-mode
|
|
:init
|
|
(define-transient-command dap-dispatch
|
|
"Dispatcher popup for dap-mode commands"
|
|
["Launching"
|
|
("d" "Start debugger" dap-debug)]
|
|
["Debugging"
|
|
("n" "Next" dap-next)
|
|
("i" "Step in" dap-step-in)
|
|
("o" "Step out" dap-step-out)
|
|
("c" "Continue" dap-continue)
|
|
("r" "Restart frame" dap-restart-frame)
|
|
("l" "View locals" dap-ui-locals)
|
|
("e" "Eval" dap-eval)
|
|
("x" "Inspect" dap-ui-inspect)
|
|
("Q" "Disconnect" dap-disconnect)]
|
|
["Breakpoints"
|
|
("b" "Toggle breakpoint" dap-breakpoint-toggle)
|
|
("B" "View breakpoints" dap-ui-breakpoints)]
|
|
["Misc."
|
|
("T" "Edit template" dap-debug-edit-template)
|
|
("h" "Hydra" dap-hydra)])
|
|
:config
|
|
(require 'dap-python)
|
|
:general
|
|
(leader-map "cm" 'dap-dispatch)
|
|
((normal visual motion) "zd" 'dap-dispatch))
|
|
|
|
(dap-mode 1)
|
|
(dap-ui-mode 1)
|
|
(dap-tooltip-mode 1)
|
|
|
|
(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
|
|
|
|
* Java
|
|
LSP Java uses the Eclipse JDT Language Server as a backend to enable Java IDE features.
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun jdormit/set-up-java ()
|
|
(lsp-java-enable)
|
|
(flycheck-mode t)
|
|
(push 'company-lsp company-backends)
|
|
(company-mode t)
|
|
(lsp-ui-flycheck-enable t)
|
|
(set-variable 'c-basic-offset 2)
|
|
(lsp-ui-sideline-mode))
|
|
|
|
(use-package lsp-java
|
|
:requires (lsp-ui-flycheck lsp-ui-sideline)
|
|
:config
|
|
(add-hook 'java-mode-hook 'jdormit/set-up-java)
|
|
(setq lsp-inhibit-message t
|
|
lsp-java-import-maven-enabled t))
|
|
#+END_SRC
|
|
|
|
Configure Java project sources:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq lsp-java--workspace-folders
|
|
(list (expand-file-name "~/src/Automation")
|
|
(expand-file-name "~/src/AutomationSharedExecution")
|
|
(expand-file-name "~/src/AutomationPlatform")))
|
|
#+END_SRC
|
|
|
|
* Python
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; (leader-def-key "sp" #'elpy-shell-switch-to-shell)
|
|
(leader-def-key "sp" #'run-python)
|
|
#+END_SRC
|
|
|
|
Elpy is a python IDE package:
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; (use-package elpy
|
|
;; :init (elpy-enable)
|
|
;; :config (setq elpy-rpc-python-command "python3"))
|
|
#+END_SRC
|
|
|
|
Alternatively, use the LSP python client:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(add-hook 'python-mode-hook #'lsp)
|
|
(general-def 'normal python-mode-map "C-c C-d" #'lsp-describe-thing-at-point)
|
|
#+END_SRC
|
|
|
|
Support pyvenv within Emacs:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package pyvenv
|
|
:commands (pyvenv-workon pyvenv-activate)
|
|
:config
|
|
(pyvenv-mode))
|
|
|
|
(defun eshell/workon (name)
|
|
(pyvenv-workon name))
|
|
|
|
(defun eshell/activate (dir)
|
|
(pyvenv-activate dir))
|
|
|
|
(defun eshell/deactivate ()
|
|
(pyvenv-deactivate))
|
|
#+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
|
|
|
|
Pipenv is the Python standard dependency management/virtual environment tool. pipenv.el teaches Emacs its ways:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package pipenv
|
|
:hook (python-mode . pipenv-mode)
|
|
:commands (pipenv-mode
|
|
pipenv-activate
|
|
pipenv-run))
|
|
#+END_SRC
|
|
|
|
A function to run a pipenv-aware python repl:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun run-pipenv ()
|
|
"Runs a pipenv-aware Python shell"
|
|
(interactive)
|
|
(pipenv-activate)
|
|
(run-python nil nil t))
|
|
#+END_SRC
|
|
|
|
Run black on the current buffer:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun blacken ()
|
|
(interactive)
|
|
(let ((start (if (use-region-p) (region-beginning) (point-min)))
|
|
(end (if (use-region-p) (region-end) (point-max))))
|
|
(shell-command-on-region start end "black -q -" nil t)))
|
|
|
|
(general-def 'normal python-mode-map "C-M-\\" #'blacken)
|
|
#+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)
|
|
#+END_SRC
|
|
|
|
* Clojure
|
|
Start with clojure-mode:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package clojure-mode
|
|
:mode (("\\.clj\\'" . clojure-mode)
|
|
("\\.cljs\\'" . clojurescript-mode)
|
|
("\\.cljc\\'" . clojurec-mode)
|
|
("\\.edn\\'" . clojure-mode))
|
|
:config
|
|
(define-clojure-indent
|
|
(defroutes 'defun)
|
|
(GET 2)
|
|
(POST 2)
|
|
(PUT 2)
|
|
(DELETE 2)
|
|
(HEAD 2)
|
|
(ANY 2)
|
|
(OPTIONS 2)
|
|
(PATCH 2)
|
|
(rfn 2)
|
|
(let-routes 1)
|
|
(context 2)
|
|
(:= 3)
|
|
(:+ 3)))
|
|
#+END_SRC
|
|
|
|
Sprinkle in some CIDER:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package cider
|
|
:commands (cider-mode cider-jack-in cider-jack-in-clojurescript)
|
|
:config (setq cider-known-endpoints
|
|
'(("local" "localhost" "4005"))
|
|
cider-prompt-for-symbol nil)
|
|
: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
|
|
|
|
Enable Org-mode Clojure evaluation:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(require 'ob-clojure)
|
|
(setq org-babel-clojure-backend 'cider)
|
|
#+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
|
|
|
|
* 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-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)
|
|
#+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))
|
|
#+END_SRC
|
|
|
|
* Ruby
|
|
#+BEGIN_SRC emacs-lisp
|
|
(add-hook 'ruby-mode-hook #'lsp)
|
|
#+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))
|
|
|
|
|
|
(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)
|
|
#+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)
|
|
:config
|
|
(pdf-tools-install)
|
|
:general
|
|
(pdf-view-mode-map "SPC" leader-map))
|
|
#+END_SRC
|
|
|
|
* EPubs
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun jdormit/nov-config ()
|
|
(when (member "Input Serif" (font-family-list))
|
|
(face-remap-add-relative 'variable-pitch :family "Input Serif"))
|
|
(olivetti-mode)
|
|
(nov-render-document))
|
|
|
|
(use-package nov
|
|
:mode ("\\.epub\\'" . nov-mode)
|
|
:config
|
|
(setq nov-text-width 80)
|
|
(add-hook 'nov-mode-hook 'jdormit/nov-config)
|
|
:general
|
|
('normal nov-mode-map "r" 'nov-render-document)
|
|
('normal nov-mode-map "=" 'nov-view-source)
|
|
('normal nov-mode-map "+" 'nov-view-content-source)
|
|
('normal nov-mode-map "m" 'nov-display-metadata)
|
|
('normal nov-mode-map "n" 'nov-next-document)
|
|
('normal nov-mode-map "]" 'nov-next-document)
|
|
('normal nov-mode-map "p" 'nov-previous-document)
|
|
('normal nov-mode-map "[" 'nov-previous-document)
|
|
('normal nov-mode-map "t" 'nov-goto-toc)
|
|
('normal nov-mode-map "RET" 'nov-browse-url)
|
|
('normal nov-mode-map "<follow-link>" 'mouse-face)
|
|
('normal nov-mode-map "<mouse-2>" 'nov-browse-url)
|
|
('normal nov-mode-map "TAB" 'shr-next-link)
|
|
('normal nov-mode-map "M-TAB" 'shr-previous-link)
|
|
('normal nov-mode-map "<backtab>" 'shr-previous-link)
|
|
('normal nov-mode-map "SPC" 'nov-scroll-up)
|
|
('normal nov-mode-map "S-SPC" 'nov-scroll-down)
|
|
('normal nov-mode-map "DEL" 'nov-scroll-down)
|
|
('normal nov-mode-map "<home>" 'beginning-of-buffer)
|
|
('normal nov-mode-map "<end>" 'end-of-buffer)
|
|
('normal nov-mode-map "SPC" leader-map))
|
|
#+END_SRC
|
|
|
|
** Org mode links
|
|
First, keep a reference to filename of the .epub before nov.el blows it away:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defvar nov-epub-file)
|
|
(add-hook 'nov-mode-hook
|
|
#'(lambda ()
|
|
(message "epub file: " buffer-file-name)
|
|
(setq nov-epub-file buffer-file-name)))
|
|
#+END_SRC
|
|
|
|
That reference lets us construct a link back to the .epub:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun org-epub-store-link ()
|
|
(when (eq major-mode 'nov-mode)
|
|
(let ((epub-name (alist-get 'title nov-metadata)))
|
|
(org-store-link-props
|
|
:type "file"
|
|
:link (concat "file://" nov-epub-file)))))
|
|
|
|
(add-hook 'org-store-link-functions 'org-epub-store-link)
|
|
#+END_SRC
|
|
|
|
* Dashboard
|
|
Instead of the *GNU Emacs* buffer on startup, display a cool dashboard:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package dashboard
|
|
: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
|
|
|
|
* Mu4e
|
|
Because email in Emacs is badass. My mail set up is based on [[http://stevelosh.com/blog/2012/10/the-homely-mutt/][this mutt setup]] and [[https://notanumber.io/2016-10-03/better-email-with-mu4e/][this mu4e setup]].
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defvar jdormit/mu4e-load-path
|
|
(if (file-exists-p "/usr/local/share/emacs/site-lisp/mu/mu4e")
|
|
"/usr/local/share/emacs/site-lisp/mu/mu4e"
|
|
(if (file-exists-p "/usr/share/emacs/site-lisp/mu4e")
|
|
"/usr/share/emacs/site-lisp/mu4e")))
|
|
|
|
(add-to-list 'load-path jdormit/mu4e-load-path)
|
|
(autoload 'mu4e (concat jdormit/mu4e-load-path "/mu4e.el"))
|
|
(autoload 'mu4e-update-index (concat jdormit/mu4e-load-path "/mu4e.el"))
|
|
(with-eval-after-load 'mu4e
|
|
(require 'org-mu4e)
|
|
(setq
|
|
mu4e-maildir (expand-file-name "~/.mail")
|
|
message-send-mail-function 'message-send-mail-with-sendmail
|
|
mu4e-get-mail-command "mbsync -a"
|
|
sendmail-program (executable-find "msmtp")
|
|
mu4e-attachment-dir "~/Downloads"
|
|
mu4e-compose-format-flowed t
|
|
mu4e-html2text-command "w3m -dump -T text/html -o display_link_number=1"
|
|
mu4e-change-filenames-when-moving t
|
|
org-mu4e-link-query-in-headers-mode nil
|
|
mu4e-maildirs-extension-custom-list
|
|
'("/jeremy-dormitzer-gmail-com/Inbox"
|
|
"/jeremy-dormitzer-net/Inbox"
|
|
"/jeremy-getpterotype-com/Inbox")
|
|
mu4e-contexts
|
|
(let ((per-dir "/jeremy-dormitzer-gmail-com")
|
|
(dormit-dir "/jeremy-dormitzer-net")
|
|
(pterotype-dir "/jeremy-getpterotype-com")
|
|
(lola-dir "/jeremydormitzer-lola-com"))
|
|
`(,(make-mu4e-context
|
|
:name "Pterotype"
|
|
:match-func (lambda (msg)
|
|
(when msg ())
|
|
(when msg (string-match-p
|
|
"jeremy-getpterotype-com"
|
|
(mu4e-message-field msg :path))))
|
|
:vars `((user-mail-address . "jeremy@getpterotype.com")
|
|
(user-full-name . "Jeremy Dormitzer")
|
|
(mu4e-sent-folder . ,(concat pterotype-dir "/Sent"))
|
|
(mu4e-drafts-folder . ,(concat pterotype-dir "/Drafts"))
|
|
(mu4e-refile-folder . ,(concat pterotype-dir "/Archive"))
|
|
(mu4e-trash-folder . ,(concat pterotype-dir "/Trash"))
|
|
(mu4e-sent-messages-behavior . delete)
|
|
(mu4e-get-mail-command . "mbsync jeremy-getpterotype-com")
|
|
(message-sendmail-extra-arguments
|
|
. ("-a" "jeremy-getpterotype.com"))))
|
|
,(make-mu4e-context
|
|
:name "GMail"
|
|
:match-func (lambda (msg)
|
|
(when msg (string-match-p
|
|
"jeremy-dormitzer-gmail-com"
|
|
(mu4e-message-field msg :path))))
|
|
:vars `((user-mail-address . "jeremy.dormitzer@gmail.com")
|
|
(user-full-name . "Jeremy Dormitzer")
|
|
(mu4e-sent-folder . ,(concat per-dir "/Sent"))
|
|
(mu4e-drafts-folder . ,(concat per-dir "/Drafts"))
|
|
(mu4e-refile-folder . ,(concat per-dir "/Archive"))
|
|
(mu4e-trash-folder . ,(concat per-dir "/Trash"))
|
|
(mu4e-sent-messages-behavior . delete)
|
|
(mu4e-get-mail-command . "mbsync jeremy-dormitzer-gmail-com")
|
|
(message-sendmail-extra-arguments
|
|
. ("-a" "jeremy.dormitzer-gmail.com"))))
|
|
,(make-mu4e-context
|
|
:name "Dormitzer"
|
|
:match-func (lambda (msg)
|
|
(when msg (string-match-p
|
|
"jeremy-dormitzer-net"
|
|
(mu4e-message-field msg :path))))
|
|
:vars `((user-mail-address . "jeremy@dormitzer.net")
|
|
(user-full-name . "Jeremy Dormitzer")
|
|
(mu4e-sent-folder . ,(concat dormit-dir "/Sent"))
|
|
(mu4e-drafts-folder . ,(concat dormit-dir "/Drafts"))
|
|
(mu4e-refile-folder . ,(concat dormit-dir "/Archive"))
|
|
(mu4e-trash-folder . ,(concat dormit-dir "/Trash"))
|
|
(mu4e-get-mail-command . "mbsync jeremy-dormitzer-net")
|
|
(message-sendmail-extra-arguments
|
|
. ("-a" "jeremy-dormitzer.net"))))
|
|
,(make-mu4e-context
|
|
:name "Lola"
|
|
:match-func (lambda (msg)
|
|
(when msg ())
|
|
(when msg (string-match-p
|
|
"jeremydormitzer-lola-com"
|
|
(mu4e-message-field msg :path))))
|
|
:vars `((user-mail-address . "jdormit@lola.com")
|
|
(user-full-name . "Jeremy Dormitzer")
|
|
(mu4e-sent-folder . ,(concat lola-dir "/Sent"))
|
|
(mu4e-drafts-folder . ,(concat lola-dir "/Drafts"))
|
|
(mu4e-refile-folder . ,(concat lola-dir "/Archive"))
|
|
(mu4e-trash-folder . ,(concat lola-dir "/Trash"))
|
|
(mu4e-sent-messages-behavior . delete)
|
|
(mu4e-get-mail-command . "mbsync jeremydormitzer-lola-com")
|
|
(message-sendmail-extra-arguments
|
|
. ("-a" "jeremydormitzer-lola.com"))))))
|
|
mu4e-context-policy 'ask
|
|
mu4e-compose-context-policy 'ask-if-none))
|
|
|
|
(jdormit/define-prefix "am" "mu4e")
|
|
(leader-def-key "amm" 'mu4e)
|
|
#+END_SRC
|
|
|
|
Custom actions:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun mu4e-view-go-to-url-w3m (&optional MULTI)
|
|
(let ((browse-url-browser-function 'w3m-browse-url))
|
|
(mu4e-view-go-to-url MULTI)))
|
|
|
|
(defun mu4e-action-view-in-browser-w3m (msg)
|
|
(let ((browse-url-browser-function 'w3m-browse-url))
|
|
(mu4e-action-view-in-browser msg)))
|
|
|
|
(with-eval-after-load 'mu4e
|
|
(add-to-list 'mu4e-view-actions
|
|
'("View in browser" . mu4e-action-view-in-browser) t)
|
|
(add-to-list 'mu4e-view-actions
|
|
'("WView in w3m" . mu4e-action-view-in-browser-w3m) t)
|
|
(add-to-list 'mu4e-view-actions
|
|
'("wGo to URL with w3m" . mu4e-view-go-to-url-w3m) t))
|
|
#+END_SRC
|
|
|
|
Make mu4e the default sendmail program:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq mail-user-agent 'mu4e-user-agent)
|
|
#+END_SRC
|
|
|
|
Use the spacebar as a leader key in mu4e modes:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(general-def mu4e-main-mode-map "SPC" leader-map)
|
|
(general-def mu4e-headers-mode-map "SPC" leader-map)
|
|
(general-def mu4e-view-mode-map "SPC" leader-map)
|
|
#+END_SRC
|
|
|
|
** HTML email
|
|
*** Redefinitions
|
|
Redefine =org-mime-insert-html-content= to export the plain part of HTML emails as ascii instead of org:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(with-eval-after-load 'org-mime
|
|
(defun org-mime-insert-html-content (body file s opts)
|
|
(let* ((files (org-mime-extract-non-image-files))
|
|
;; dvipng for inline latex because MathJax doesn't work in mail
|
|
;; Also @see https://github.com/org-mime/org-mime/issues/16
|
|
;; (setq org-html-with-latex nil) sometimes useful
|
|
(org-html-with-latex org-mime-org-html-with-latex-default)
|
|
;; we don't want to convert org file links to html
|
|
(org-html-link-org-files-as-html nil)
|
|
(org-link-file-path-type 'absolute)
|
|
;; makes the replies with ">"s look nicer
|
|
(org-export-preserve-breaks org-mime-preserve-breaks)
|
|
(plain (org-mime--export-string body 'ascii))
|
|
;; org 9
|
|
(org-html-htmlize-output-type 'inline-css)
|
|
;; org 8
|
|
(org-export-htmlize-output-type 'inline-css)
|
|
(html-and-images (org-mime-replace-images (org-mime--export-string s 'html opts)
|
|
file))
|
|
(images (cdr html-and-images))
|
|
(html (org-mime-apply-html-hook (car html-and-images))))
|
|
|
|
;; If there are files that were attached, we should remove the links,
|
|
;; and mark them as attachments. The links don't work in the html file.
|
|
(when files
|
|
(mapc (lambda (f)
|
|
(setq html (replace-regexp-in-string
|
|
(format "<a href=\"%s\">%s</a>"
|
|
(regexp-quote f) (regexp-quote f))
|
|
(format "%s (attached)" (file-name-nondirectory f))
|
|
html)))
|
|
files))
|
|
|
|
(insert (org-mime-multipart plain
|
|
html
|
|
(mapconcat 'identity images "\n")))
|
|
|
|
;; Attach any residual files
|
|
(when files
|
|
(mapc (lambda (f)
|
|
(when org-mime-debug (message "attaching: %s" f))
|
|
(mml-attach-file f))
|
|
files)))))
|
|
#+END_SRC
|
|
|
|
And redefine =mu4e~compose-handler= to add a new hook:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defcustom jdormit-mu4e-compose-hook nil
|
|
"Hook run after the message composition buffer is set up"
|
|
:type 'hook
|
|
:group 'mu4e-compose)
|
|
|
|
(with-eval-after-load 'mu4e
|
|
(defun* mu4e~compose-handler (compose-type &optional original-msg includes)
|
|
"Create a new draft message, or open an existing one.
|
|
|
|
COMPOSE-TYPE determines the kind of message to compose and is a
|
|
symbol, either `reply', `forward', `edit', `resend' `new'. `edit'
|
|
is for editing existing (draft) messages. When COMPOSE-TYPE is
|
|
`reply' or `forward', MSG should be a message plist. If
|
|
COMPOSE-TYPE is `new', ORIGINAL-MSG should be nil.
|
|
|
|
Optionally (when forwarding, replying) ORIGINAL-MSG is the original
|
|
message we will forward / reply to.
|
|
|
|
Optionally (when forwarding) INCLUDES contains a list of
|
|
(:file-name <filename> :mime-type <mime-type> :disposition <disposition>)
|
|
for the attachements to include; file-name refers to
|
|
a file which our backend has conveniently saved for us (as a
|
|
tempfile)."
|
|
|
|
;; Run the hooks defined for `mu4e-compose-pre-hook'. If compose-type is
|
|
;; `reply', `forward' or `edit', `mu4e-compose-parent-message' points to the
|
|
;; message being forwarded or replied to, otherwise it is nil.
|
|
(set (make-local-variable 'mu4e-compose-parent-message) original-msg)
|
|
(put 'mu4e-compose-parent-message 'permanent-local t)
|
|
;; remember the compose-type
|
|
(set (make-local-variable 'mu4e-compose-type) compose-type)
|
|
(put 'mu4e-compose-type 'permanent-local t)
|
|
;; maybe switch the context
|
|
(mu4e~context-autoswitch mu4e-compose-parent-message
|
|
mu4e-compose-context-policy)
|
|
(run-hooks 'mu4e-compose-pre-hook)
|
|
|
|
;; this opens (or re-opens) a messages with all the basic headers set.
|
|
(let ((winconf (current-window-configuration)))
|
|
(condition-case nil
|
|
(mu4e-draft-open compose-type original-msg)
|
|
(quit (set-window-configuration winconf)
|
|
(mu4e-message "Operation aborted")
|
|
(return-from mu4e~compose-handler))))
|
|
;; insert mail-header-separator, which is needed by message mode to separate
|
|
;; headers and body. will be removed before saving to disk
|
|
(mu4e~draft-insert-mail-header-separator)
|
|
;; maybe encrypt/sign replies
|
|
(mu4e~compose-crypto-reply original-msg compose-type)
|
|
;; include files -- e.g. when forwarding a message with attachments,
|
|
;; we take those from the original.
|
|
(save-excursion
|
|
(goto-char (point-max)) ;; put attachments at the end
|
|
(dolist (att includes)
|
|
(mml-attach-file
|
|
(plist-get att :file-name) (plist-get att :mime-type))))
|
|
;; buffer is not user-modified yet
|
|
(mu4e~compose-set-friendly-buffer-name compose-type)
|
|
(set-buffer-modified-p nil)
|
|
;; now jump to some useful positions, and start writing that mail!
|
|
|
|
(if (member compose-type '(new forward))
|
|
(message-goto-to)
|
|
(message-goto-body))
|
|
;; bind to `mu4e-compose-parent-message' of compose buffer
|
|
(set (make-local-variable 'mu4e-compose-parent-message) original-msg)
|
|
(put 'mu4e-compose-parent-message 'permanent-local t)
|
|
|
|
;; hide some headers
|
|
(mu4e~compose-hide-headers)
|
|
;; switch on the mode
|
|
(mu4e-compose-mode)
|
|
|
|
(run-hooks 'jdormit-mu4e-compose-hook)
|
|
|
|
;; set mu4e-compose-type once more for this buffer,
|
|
;; we loose it after the mode-change, it seems
|
|
(set (make-local-variable 'mu4e-compose-type) compose-type)
|
|
(put 'mu4e-compose-type 'permanent-local t)
|
|
|
|
(when mu4e-compose-in-new-frame
|
|
;; make sure to close the frame when we're done with the message these are
|
|
;; all buffer-local;
|
|
(push 'delete-frame message-exit-actions)
|
|
(push 'delete-frame message-postpone-actions))))
|
|
#+END_SRC
|
|
|
|
*** Actual HTML mail logic
|
|
#+BEGIN_SRC emacs-lisp
|
|
(with-eval-after-load 'mu4e (require 'org-mu4e))
|
|
(use-package org-mime
|
|
:after (mu4e org)
|
|
:config
|
|
(setq org-mime-export-options '(:section-numbers nil
|
|
:with-author nil
|
|
:with-toc nil)))
|
|
|
|
(defun htmlize-and-send ()
|
|
(interactive)
|
|
(when (member 'org~mu4e-mime-switch-headers-or-body post-command-hook)
|
|
(org-mime-htmlize)
|
|
(message-send-and-exit)))
|
|
|
|
(add-hook 'org-ctrl-c-ctrl-c-hook #'htmlize-and-send t)
|
|
|
|
(defun setup-compose-buffer ()
|
|
(org-mu4e-compose-org-mode))
|
|
|
|
(add-hook 'jdormit-mu4e-compose-hook #'setup-compose-buffer)
|
|
#+END_SRC
|
|
|
|
When citing (quoting) messages in a reply, wrap them in org quote blocks instead of prefixing each line with '> ':
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun jdormit-citation-line-function ()
|
|
(message-insert-citation-line)
|
|
(insert "#+BEGIN_QUOTE\n"))
|
|
|
|
(defun jdormit-cite-function ()
|
|
(let ((message-yank-prefix "")
|
|
(message-yank-cited-prefix "")
|
|
(message-yank-empty-prefix ""))
|
|
(save-excursion
|
|
(message-cite-original)
|
|
(goto-char (point-max))
|
|
(insert "\n#+END_QUOTE"))))
|
|
|
|
(with-eval-after-load 'mu4e
|
|
(setq message-citation-line-function #'jdormit-citation-line-function
|
|
mu4e-compose-cite-function #'jdormit-cite-function))
|
|
#+END_SRC
|
|
|
|
Some keybindings to send the current org buffer or subtree as an email:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(general-def org-mode-map "C-c m" #'org-mime-org-buffer-htmlize)
|
|
(general-def org-mode-map "C-c s" #'org-mime-org-subtree-htmlize)
|
|
#+END_SRC
|
|
|
|
* Mu4e-alert
|
|
Desktop notifications for mu4e emails.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; (defun jdormit-get-mu4e-alert-style ()
|
|
;; (if (memq window-system '(mac ns))
|
|
;; 'notifier
|
|
;; 'libnotify))
|
|
;;
|
|
;; (use-package mu4e-alert
|
|
;; :config
|
|
;; (setq mu4e-alert-interesting-mail-query
|
|
;; (concat
|
|
;; "flag:unread maildir:/jeremy-dormitzer-gmail-com/Inbox"
|
|
;; " OR flag:unread maildir:/jeremy-dormitzer-net/Inbox"
|
|
;; " OR flag:unread maildir:/jeremydormitzer-lola-com/Inbox"
|
|
;; " OR flag:unread maildir:/jeremy-getpterotype-com/Inbox"))
|
|
;; (mu4e-alert-set-default-style (jdormit-get-mu4e-alert-style))
|
|
;; (mu4e-alert-enable-notifications)
|
|
;; (mu4e-alert-enable-mode-line-display)
|
|
;; (leader-def-key "amu" #'mu4e-alert-view-unread-mails))
|
|
#+END_SRC
|
|
|
|
* w3m
|
|
Browsing the web from Emacs. Relies on having [[http://w3m.sourceforge.net/][w3m]] installed.
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package w3m
|
|
:commands (w3m
|
|
w3m-browse-url
|
|
w3m-search-new-session)
|
|
:config
|
|
(general-def 'normal w3m-mode-map "SPC" leader-map)
|
|
(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")))
|
|
|
|
(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
|
|
(setq wakatime-path
|
|
(if (file-exists-p "/usr/local/bin/wakatime")
|
|
"/usr/local/bin/wakatime"
|
|
(when (file-exists-p "/usr/bin/wakatime")
|
|
"/usr/bin/wakatime")))
|
|
|
|
(use-package wakatime-mode
|
|
:if wakatime-path
|
|
:init
|
|
(setq wakatime-api-key (password-store-get "wakatime-api-key")
|
|
wakatime-cli-path wakatime-path)
|
|
:config
|
|
(global-wakatime-mode))
|
|
#+END_SRC
|
|
|
|
* Elfeed
|
|
Elfeed is a feed reader for Emacs.
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun elfeed-setup-hook ()
|
|
(set (make-local-variable 'browse-url-browser-function)
|
|
'w3m-browse-url)
|
|
(set (make-local-variable 'jdormit/w3m-referer) "https://www.google.com")
|
|
(general-def 'normal elfeed-search-mode-map "q" 'elfeed-search-quit-window)
|
|
(general-def 'normal elfeed-search-mode-map "C-r" 'elfeed-search-update--force)
|
|
(general-def 'normal elfeed-search-mode-map "R" 'elfeed-search-fetch)
|
|
(general-def 'normal elfeed-search-mode-map "RET" 'elfeed-search-show-entry)
|
|
(general-def 'normal elfeed-search-mode-map "s" 'elfeed-search-live-filter)
|
|
(general-def 'normal elfeed-search-mode-map "S" 'elfeed-search-set-filter)
|
|
(general-def 'normal elfeed-search-mode-map "B" 'elfeed-search-browse-url)
|
|
(general-def 'normal elfeed-search-mode-map "y" 'elfeed-search-yank)
|
|
(general-def 'normal elfeed-search-mode-map "u" 'elfeed-search-tag-all-unread)
|
|
(general-def 'normal elfeed-search-mode-map "r" 'elfeed-search-untag-all-unread)
|
|
(general-def 'normal elfeed-search-mode-map "n" 'next-line)
|
|
(general-def 'normal elfeed-search-mode-map "p" 'previous-line)
|
|
(general-def 'normal elfeed-search-mode-map "+" 'elfeed-search-tag-all)
|
|
(general-def 'normal elfeed-search-mode-map "-" 'elfeed-search-untag-all)
|
|
(general-def 'normal elfeed-show-mode-map "d" 'elfeed-show-save-enclosure)
|
|
(general-def 'normal elfeed-show-mode-map "q" 'elfeed-kill-buffer)
|
|
(general-def 'normal elfeed-show-mode-map "r" 'elfeed-show-refresh)
|
|
(general-def 'normal elfeed-show-mode-map "n" 'elfeed-show-next)
|
|
(general-def 'normal elfeed-show-mode-map "p" 'elfeed-show-prev)
|
|
(general-def 'normal elfeed-show-mode-map "s" 'elfeed-show-new-live-search)
|
|
(general-def 'normal elfeed-show-mode-map "b" 'elfeed-show-visit)
|
|
(general-def 'normal elfeed-show-mode-map "y" 'elfeed-show-yank)
|
|
(general-def 'normal elfeed-show-mode-map "u" (elfeed-expose #'elfeed-show-tag 'unread))
|
|
(general-def 'normal elfeed-show-mode-map "+" 'elfeed-show-tag)
|
|
(general-def 'normal elfeed-show-mode-map "-" 'elfeed-show-untag)
|
|
(general-def 'normal elfeed-show-mode-map "SPC" 'scroll-up-command)
|
|
(general-def 'normal elfeed-show-mode-map "DEL" 'scroll-down-command)
|
|
(general-def 'normal elfeed-show-mode-map "\t" 'shr-next-link)
|
|
(general-def 'normal elfeed-show-mode-map [tab] 'shr-next-link)
|
|
(general-def 'normal elfeed-show-mode-map "\e\t" 'shr-previous-link)
|
|
(general-def 'normal elfeed-show-mode-map [backtab] 'shr-previous-link)
|
|
(general-def 'normal elfeed-show-mode-map [mouse-2] 'shr-browse-url)
|
|
(general-def 'normal elfeed-show-mode-map "A" 'elfeed-show-add-enclosure-to-playlist)
|
|
(general-def 'normal elfeed-show-mode-map "P" 'elfeed-show-play-enclosure))
|
|
|
|
(use-package elfeed
|
|
:commands elfeed
|
|
:config
|
|
(add-hook 'elfeed-show-mode-hook 'elfeed-setup-hook)
|
|
(setq-default elfeed-search-filter "@1-week-ago +unread +news ")
|
|
(add-to-list 'evil-normal-state-modes 'elfeed-search-mode)
|
|
(add-to-list 'evil-normal-state-modes 'elfeed-show-mode)
|
|
(setq elfeed-feeds
|
|
'(("http://www.wsj.com/xml/rss/3_7085.xml" news)
|
|
("https://www.wsj.com/xml/rss/3_7014.xml" news)
|
|
("http://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml" news)
|
|
("https://www.newyorker.com/feed/everything" news)
|
|
("https://www.economist.com/sections/business-finance/rss.xml" news)
|
|
("https://www.economist.com/sections/economics/rss.xml" news))))
|
|
#+END_SRC
|
|
|
|
Keybinding for opening Elfeed:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(leader-def-key "al" 'elfeed)
|
|
#+END_SRC
|
|
|
|
* Undo Tree
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package undo-tree
|
|
:init
|
|
(global-undo-tree-mode)
|
|
(leader-def-key "bu" 'undo-tree-visualize)
|
|
:general
|
|
('normal "u" #'undo-tree-undo)
|
|
('normal "C-r" #'undo-tree-redo)
|
|
(undo-tree-visualizer-mode-map "SPC" leader-map))
|
|
#+END_SRC
|
|
|
|
* Emojify
|
|
Because emojis make everything better.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package emojify
|
|
:commands (emojify-mode
|
|
emojify-apropos-emoji
|
|
emojify-insert-emoji))
|
|
(leader-def-key "te" 'emojify-mode)
|
|
#+END_SRC
|
|
|
|
* Mastodon
|
|
[[https://joinmastodon.org/][Mastodon]] is a federated FOSS social network similar to Twitter. Let's put it in Emacs!
|
|
|
|
First, install a dependency:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package discover)
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun jdormit/mastodon-setup ()
|
|
(general-def 'normal mastodon-mode-map "j" #'mastodon-tl--goto-next-toot)
|
|
(general-def 'normal mastodon-mode-map "k" #'mastodon-tl--goto-prev-toot)
|
|
(general-def 'normal mastodon-mode-map "h" #'mastodon-tl--next-tab-item)
|
|
(general-def 'normal mastodon-mode-map "l" #'mastodon-tl--previous-tab-item)
|
|
(general-def 'normal mastodon-mode-map [?\t] #'mastodon-tl--next-tab-item)
|
|
(general-def 'normal mastodon-mode-map [backtab] #'mastodon-tl--previous-tab-item)
|
|
(general-def 'normal mastodon-mode-map [?\S-\t] #'mastodon-tl--previous-tab-item)
|
|
(general-def 'normal mastodon-mode-map [?\M-\t] #'mastodon-tl--previous-tab-item)
|
|
;; Navigating to other buffers:
|
|
(general-def 'normal mastodon-mode-map "N" #'mastodon-notifications--get)
|
|
(general-def 'normal mastodon-mode-map "A" #'mastodon-profile--get-toot-author)
|
|
(general-def 'normal mastodon-mode-map "U" #'mastodon-profile--show-user)
|
|
(general-def 'normal mastodon-mode-map "F" #'mastodon-tl--get-federated-timeline)
|
|
(general-def 'normal mastodon-mode-map "H" #'mastodon-tl--get-home-timeline)
|
|
(general-def 'normal mastodon-mode-map "L" #'mastodon-tl--get-local-timeline)
|
|
(general-def 'normal mastodon-mode-map "t" #'mastodon-tl--thread)
|
|
(general-def 'normal mastodon-mode-map "T" #'mastodon-tl--get-tag-timeline)
|
|
(general-def 'normal mastodon-mode-map "q" #'kill-this-buffer)
|
|
(general-def 'normal mastodon-mode-map "Q" #'kill-buffer-and-window)
|
|
;; Actions
|
|
(general-def 'normal mastodon-mode-map "c" #'mastodon-tl--toggle-spoiler-text-in-toot)
|
|
(general-def 'normal mastodon-mode-map "g" #'undefined) ;; override special mode binding
|
|
(general-def 'normal mastodon-mode-map "n" #'mastodon-toot)
|
|
(general-def 'normal mastodon-mode-map "r" #'mastodon-toot--reply)
|
|
(general-def 'normal mastodon-mode-map "u" #'mastodon-tl--update)
|
|
(general-def 'normal mastodon-mode-map "b" #'mastodon-toot--toggle-boost)
|
|
(general-def 'normal mastodon-mode-map "f" #'mastodon-toot--toggle-favourite)
|
|
(general-def 'normal mastodon-mode-map "?" #'makey-key-mode-popup-mastodon)
|
|
(general-def 'normal mastodon-profile-mode-map "F" #'mastodon-profile--open-followers)
|
|
(general-def 'normal mastodon-profile-mode-map "f" #'mastodon-profile--open-following))
|
|
|
|
(eval-and-compile
|
|
(defun mastodon-load-path ()
|
|
(expand-file-name "~/mastodon.el/lisp")))
|
|
|
|
(if (file-exists-p (mastodon-load-path))
|
|
(use-package mastodon
|
|
:load-path (lambda () (mastodon-load-path))
|
|
:init (setq mastodon-instance-url "https://mastodon.technology")
|
|
:config
|
|
(jdormit/mastodon-setup)
|
|
:commands
|
|
(mastodon
|
|
mastodon-toot))
|
|
(use-package mastodon
|
|
:init (setq mastodon-instance-url "https://mastodon.technology")
|
|
:config
|
|
(jdormit/mastodon-setup)
|
|
:commands
|
|
(mastodon
|
|
mastodon-toot)))
|
|
|
|
(jdormit/define-prefix "aM" "mastodon")
|
|
(leader-def-key "aMm" 'mastodon)
|
|
(leader-def-key "aMt" 'mastodon-toot)
|
|
#+END_SRC
|
|
|
|
* Calc
|
|
#+BEGIN_SRC emacs-lisp
|
|
(leader-def-key "ac" 'calc)
|
|
#+END_SRC
|
|
|
|
* Deadgrep
|
|
A nice Emacs UI over [[https://github.com/BurntSushi/ripgrep#installation][ripgrep]].
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package deadgrep
|
|
:commands deadgrep
|
|
: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
|
|
Enable [[info:dired-x#Top][Dired-X]]:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(require 'dired-x)
|
|
#+END_SRC
|
|
|
|
Preserve the leader key:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(general-def dired-mode-map "SPC" leader-map)
|
|
#+END_SRC
|
|
|
|
* Crontab
|
|
Magit ships with a cool utility called =with-editor= that lets you run a shell command using the current Emacs instance as $EDITOR. This means we can define a command to edit the crontab with the current Emacs instance:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun 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
|
|
|
|
* Ido
|
|
Interactively do things! Some more info in [[https://masteringemacs.org/article/introduction-to-ido-mode][this blog post]].
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; (setq ido-enable-flex-matching t)
|
|
;; (setq ido-everywhere t)
|
|
;; (ido-mode 1)
|
|
#+END_SRC
|
|
|
|
Enable it everywhere:
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; (use-package completing-read
|
|
;; :config
|
|
;; (ido-ubiquitous-mode 1))
|
|
;;
|
|
;; (use-package crm-custom
|
|
;; :config
|
|
;; (crm-custom-mode 1))
|
|
#+END_SRC
|
|
|
|
The horizontal completion is ugly. Make it vertical:
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; (use-package ido-vertical-mode
|
|
;; :config
|
|
;; (ido-vertical-mode 1)
|
|
;; (setq ido-vertical-define-keys 'C-n-and-C-p-only))
|
|
#+END_SRC
|
|
|
|
The default auto-merge time is too short.
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; (setq ido-auto-merge-delay-time 1.5)
|
|
#+END_SRC
|
|
|
|
* Ivy
|
|
An alternative minibuffer completion framework:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun counsel-ibuffer-kill-buffer (x)
|
|
(kill-buffer (cdr x)))
|
|
|
|
(use-package counsel
|
|
:defer 0
|
|
:config
|
|
(ivy-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 "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 "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))
|
|
(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
|
|
("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))
|
|
|
|
(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
|
|
(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
|
|
(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")
|
|
(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
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package graphql-mode
|
|
:mode "\\.gql\\'"
|
|
:commands (graphql-mode org-babel-execute:graphql)
|
|
:straight ((graphql-mode :host github
|
|
:repo "jdormit/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
|
|
|
|
* 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
|
|
(defun save-to-s3 (bucket public)
|
|
(interactive "MS3 bucket URL (e.g. s3://bucket/filename): \nP")
|
|
(let* ((tmp-file (make-temp-file "s3-upload"))
|
|
(cmd `("aws" "s3" "cp" ,@(if public '("--acl" "public-read")) ,tmp-file ,bucket)))
|
|
(setq last-s3-url bucket)
|
|
(write-region (point-min) (point-max) tmp-file)
|
|
(let ((proc (apply #'start-process "aws-s3" "*aws-s3*" cmd)))
|
|
(set-process-sentinel
|
|
proc
|
|
(make-success-err-msg-sentinel "*aws-s3*"
|
|
(format "Uploaded to %s" last-s3-url)
|
|
"Failed to upload to s3, check *aws-s3* buffer for details")))))
|
|
#+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
|
|
(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)
|
|
(call-with-env-from-file
|
|
(substitute-in-file-name (format "$WORKON_HOME/%s/bin/activate" venv))
|
|
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"))
|
|
|
|
(defun python-service-setup (venv &optional env-file)
|
|
(lambda (done)
|
|
(call-with-venv
|
|
venv
|
|
(if env-file
|
|
(lambda ()
|
|
(call-with-env-from-file env-file done))
|
|
done))))
|
|
|
|
(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")
|
|
: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 "
|
|
"-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"))
|
|
|
|
(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-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 "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/lola-services/priceline/bin/start.sh"
|
|
:args '("web")
|
|
:cwd "~/lola/lola-services"
|
|
:stop-signal 'int
|
|
:init-async (python-service-setup "lola-services-ZdLKpchq"
|
|
"~/lola/lola-services/priceline/.env"))
|
|
|
|
(prodigy-define-service
|
|
:name "mabl-link-agent"
|
|
:command "link-agent"
|
|
:args (lambda ()
|
|
(list "-a" (password-store-get "mabl-link-agent")
|
|
"-n" "jdormit-macbook")))
|
|
#+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
|
|
|
|
** Debug configurations (dap)
|
|
#+BEGIN_SRC emacs-lisp
|
|
(dap-register-debug-template
|
|
"lola-server"
|
|
(list :type "python"
|
|
:args "bin/start_web.py"
|
|
:cwd (expand-file-name "~/lola/lola-server")
|
|
:env (extract-vars-from-env-file (expand-file-name "~/lola/lola-server/.env"))
|
|
:module nil
|
|
:program nil
|
|
:request "launch"
|
|
:name "lola-server"))
|
|
#+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*"
|
|
"/Users/jdormit/.local/share/virtualenvs/k8s-deploy/bin/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
|
|
|
|
* 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
|
|
(setq deft-directory
|
|
(concat (file-name-as-directory (get-dropbox-directory))
|
|
"org/deft"))
|
|
(add-to-list 'org-agenda-files deft-directory)
|
|
|
|
(use-package deft
|
|
:commands (deft)
|
|
:init
|
|
(setq deft-extensions '("org" "txt" "md" "markdown" "text"))
|
|
: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))
|
|
|
|
(leader-def-key "D" #'deft)
|
|
#+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
|
|
:after (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))
|
|
|
|
(leader-def-key "\\" #'imenu-list-smart-toggle)
|
|
(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 open-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))))
|
|
|
|
(leader-def-key "sv" 'open-vterm)
|
|
#+END_SRC
|