dotfiles/dotfiles/emacs.d
2024-03-09 20:44:01 -07:00
..
snippets [Emacs] Add ipdb snippet 2023-07-23 19:09:42 -06:00
.gitignore [Emacs] Add ignores for org-gcal stuff 2023-08-30 14:20:20 -06:00
.mc-lists.el [Emacs] Never do handle mouse movement for all cursors 2023-11-24 20:03:53 -07:00
init.el [Emacs] Add comment explaining early demand of magit 2023-09-12 11:47:54 -06:00
kat-mode.org [Emacs] Reindent kat specific aphelia configuration 2023-09-29 22:40:59 -06:00
org-config.org [Emacs] Rename org-agenda-log filter for person function 2023-09-30 12:06:22 -06:00
README.org [Emacs] Add graphql-mode 2024-03-09 20:44:01 -07:00

file:https://travis-ci.org/IvanMalison/dotfiles.svg?branch=master

This document is best read at http://ivanmalison.github.io/dotfiles/ or, of course, in emacs, as the internal links that follow are unlikely to work anywhere else (including, for example, at https://github.com/IvanMalison/dotfiles).

About

This is my emacs configuration in literate form. It aspires to be like the incredibly well commented literate configurations of Sacha Chua and Ryan Rix, but I haven't quite gotten around to polishing it to the point that those two have. Still, there are definitely a few sections of which I am quite proud, and that others may find to be useful.

Highlights

These sections are the ones that have the most potential to be interesting to others:

How I generate http://ivanmalison.github.io/dotfiles/

org-export

travis-ci

This directory contains a collection of scripts that export this org document to html. This approach is taken (with only a few modifications) from here.

githook (The old way)

NOTE: I've left this here in case other people have an interest in this method, but I no longer use it.

I wrote a githook and an installer script that automatically update index.html at the root of the repository by running org-html-export-to-html on my README.org (essentially this document) . These should work no matter where they are placed in a repository. The org-mode file that they target is set here and should be relatively easy to change.

Read The Org

I use fniessen's ReadTheOrg theme which can be found at https://github.com/fniessen/org-html-themes.

Predictable and Human Readable Heading Links

To make it so that internal heading links have names that correspond to the heading text, and don't change when new headings are added to the document, I wrote a custom version of org-export-get-reference.

See how this link (which is just a normal internal link in the original document) takes you to http://ivanmalison.github.io/dotfiles/#usemyowndefaultnamingschemefororgheadings.

In case you haven't noticed, that is where you should go grab the code that does this.

I'm considering turning this snippet in to a package, so please let me know if that is something you are interested in.

Add link icons in headings that lead to themselves

This is another pretty nasty hack. This is useful when you are browsing the document and you want to grab a link to the current heading.

Set Background Color Source Blocks

For some reason, org-mode uses all of your currently active fontification when exporting EXCEPT for background color. This modification fixes this.

Custom frame control

My custom frame-mode stuff is built to integrate really nicely with xmonad. I think its pretty awesome!

Programming Language Configurations

My programming language major mode configurations can all be found here.

org-mode

My org-mode configuration is pretty comprehensive, but not super well commented.

HTML Headers

Early

The configurations in this section need to occur early in emacs startup for some reason or another.

Lexical Binding

This makes it so that the file that is produced from tangling this file uses lexical scoping.

;;; -*- lexical-binding: t -*-
(setq-default lexical-binding t)

Security

(defvar imalison:secure t)

(defun imalison:use-https-and-tls ()
  (setq tls-checktrust t)
  (let ((trustfile
         (replace-regexp-in-string
          "\\\\" "/"
          (replace-regexp-in-string
           "\n" ""
           (shell-command-to-string "python -m certifi")))))
    (setq tls-program
          (list
           (format "gnutls-cli%s --x509cafile %s -p %%p %%h"
                   (if (eq window-system 'w32) ".exe" "") trustfile)))))

(defun imalison:test-security ()
  (interactive)
  (let ((bad-hosts
         (loop for bad
               in `("https://wrong.host.badssl.com/"
                    "https://self-signed.badssl.com/")
               if (condition-case _e
                      (url-retrieve
                       bad (lambda (_retrieved) t))
                    (error nil))
               collect bad)))
    (if bad-hosts
        (error (format "tls misconfigured; retrieved %s ok"
                       bad-hosts))
      (url-retrieve "https://badssl.com"
                    (lambda (_retrieved) t)))))

(when imalison:secure (imalison:use-https-and-tls))

Setup auto-compile

(use-package auto-compile
  :demand t
  :config
  (progn
    (auto-compile-on-load-mode)
    (auto-compile-on-save-mode)))

Prefer Newer Versions

To reduce the risk of loading outdated byte code files, we set load-prefer-newer and enable auto-compile-on-load-mode as early as possible.

(setq load-prefer-newer t)

Custom Files

The default value of custom-file is just the current user's .emacs.d/init.el file. Emacs will add content to custom-file whenever a variable is customized or marked as safe. When init.el is version controlled, it is quite annoying to have random machine-generated variable settings added to it because those changes are often not worth keeping permanently, so we set a different custom file here to avoid this situation.

custom-before.el is loaded before the rest of init.el, while custom-after.el is loaded afterwards. this-machine.el has customizations that should only apply to the current machine. custom-before and custom-after are not version controlled in the dotfiles repo but they are shared across machines elsewhere.

(defvar machine-custom "~/.emacs.d/this-machine.el")
(defvar custom-after-file "~/.emacs.d/custom-after.el")
(setq custom-file "~/.emacs.d/custom-before.el")
(when (file-exists-p custom-file) (load custom-file))

Benchmarking

This appears here so that it can accurately benchmark as much of startup as possible.

(defvar imalison:do-benchmark)

(let ((bench-file (concat (file-name-directory user-init-file) "benchmark.el")))
  (when (file-exists-p bench-file) (load bench-file)))

(use-package benchmark-init
  :if imalison:do-benchmark
  :demand t
  :config
  (setq max-specpdl-size 99999999))

GUI Disables

Death to any gui elements in emacs! Do this EARLY so that emacs doesn't redisplay in a way that is visually unpleasant on startup a bunch of times.

(when (fboundp 'menu-bar-mode) (menu-bar-mode -1))
(when (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(when (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))

Tooltips are annoying:

(if (fboundp 'tooltip-mode) (tooltip-mode -1) (setq tooltip-use-echo-area t))'

Byte-Compiler

These definitions silence the byte-compiler.

(defvar grep-find-ignored-directories nil)
(defvar grep-find-ignored-files nil)
(defvar ido-context-switch-command nil)
(defvar ido-cur-item nil)
(defvar ido-cur-list nil)
(defvar ido-default-item nil)
(defvar inherit-input-method nil)
(defvar oauth--token-data nil)
(defvar tls-checktrust nil)
(defvar tls-program nil)
(defvar url-callback-arguments nil)
(defvar url-callback-function nil)
(defvar url-http-extra-headers nil)
;; This variable doesn't exist in old versions of org-mode
(defvar org-show-context-detail)

exec-path-from-shell

Sets environment variables by starting a shell.

(use-package exec-path-from-shell
  :disabled (not (equal system-type 'darwin))
  :config
  (progn
    ;; For debugging
    (when nil
      (message "path: %s, setup: %s" (getenv "PATH")
               (getenv "ENVIRONMENT_SETUP_DONE"))
      (setq exec-path-from-shell-debug t))
    (setq exec-path-from-shell-arguments (list "-l"))
    (setq exec-path-from-shell-check-startup-files nil)
    (add-to-list 'exec-path-from-shell-variables "SHELL")
    (add-to-list 'exec-path-from-shell-variables "GOPATH")
    (add-to-list 'exec-path-from-shell-variables "ENVIRONMENT_SETUP_DONE")
    (add-to-list 'exec-path-from-shell-variables "PYTHONPATH")
    (exec-path-from-shell-initialize)))

noflet

(use-package noflet
  :demand t)

Non-Forking Shell Command To String

Emacs' built in shell-command-to-string function has the downside that it forks a new shell process every time it is executed. This means that any shell startup cost is incurred when this function is called.

The following implementation uses eshell's executable-find to find the binary (which is the only reason shell-comand-to-string is typically used anyway), but it avoids incurring any shell-startup cost.

This was originally inspired by this issue.

(defun imalison:call-process-to-string (program &rest args)
  (with-temp-buffer
    (apply 'call-process program nil (current-buffer) nil args)
    (buffer-string)))

(defun imalison:get-call-process-args-from-shell-command (command)
  (cl-destructuring-bind
      (the-command . args) (split-string command " ")
    (let ((binary-path (executable-find the-command)))
      (when binary-path
        (cons binary-path args)))))

(defun imalison:shell-command-to-string (command)
  (let ((call-process-args
         (imalison:get-call-process-args-from-shell-command command)))
    (if call-process-args
        (apply 'imalison:call-process-to-string call-process-args)
      (shell-command-to-string command))))

This makes it so that we always try to call-process instead of shell-command-to-sting. It may cause undesireable behavior.

(defun imalison:try-call-process (command)
  (let ((call-process-args
         (imalison:get-call-process-args-from-shell-command command)))
    (if call-process-args
        (apply 'imalison:call-process-to-string call-process-args))))

This had to be disabled because it was causing a bunch of issues with projectile.

(advice-add 'shell-command-to-string :before-until 'imalison:try-call-process)

This solution only applies it to projectile-find-file

(defun imalison:call-with-quick-shell-command (fn &rest args)
  (noflet
    ((shell-command-to-string (&rest args)
                              (condition-case _e
                                  (or (apply 'imalison:try-call-process args) (apply this-fn args))
                                (error (apply this-fn args)))))
    (apply fn args)))

;; I've had to disable this becuase newer versions of projectile use a ton of shell commands
;; (advice-add 'projectile-files-via-ext-command :around 'imalison:call-with-quick-shell-command)

Set EMACS environment variable

Emacs cask seems to depend on the EMACS environment variable being set to the binary path of emacs. I found the method for getting the path to the emacs executable here.

(setenv "EMACS"
        (file-truename (expand-file-name invocation-name invocation-directory)))

Update: It turns out that it is term-exec-1 that is causing this environment variable to be set to something strange. When I tried to disable it, it seemed to cause issues. Oh well…

Don't use system font

I want to control my own font for emacs

(setq font-use-system-font nil)

Set default browser

(when (equal system-type 'gnu/linux)
    (setq browse-url-browser-function 'browse-url-generic
          browse-url-generic-program "xdg-open"))

Functions

Join Paths

Works in the same way as os.path.join in python

(defun imalison:join-paths (root &rest dirs)
  (let ((result root))
    (cl-loop for dir in dirs do
             (setq result (concat (file-name-as-directory result) dir)))
    result))

Variables

(defvar imalison:projects-directory
  (imalison:join-paths (substitute-in-file-name "$HOME") "Projects"))

(defvar imalison:gpg-key)

Required Packages

The packages in this section provide no functionality on their own, but provide support for writing custom elisp.

s

(use-package s :demand t)

dash

(use-package dash
  :demand t
  :config
  (progn
    (dash-enable-font-lock)))

gh

(use-package gh
  :defer t
  :straight (gh :type git :host github :repo "IvanMalison/gh.el"))

shut-up

(use-package shut-up
  :config
  (defun imalison:shut-up-around (function &rest args)
    (shut-up (apply function args))))

parse-csv

(use-package parse-csv
  :demand t)

emit

(use-package emit
  :straight (emit :type git :host github :repo "colonelpanic8/emit"))

request

(use-package request
  :defer t)

Named Build

<<namedbuild>> imalison:named-build provides a way to invoke a macro in such a way that the lambda that it produces is given a name.

(defmacro imalison:named-build (name builder &rest args)
  `(defalias (quote ,name) (,builder ,@args)))
(put 'imalison:named-build 'lisp-indent-function 1)

imalison:named-builder-builder builds a macro from another macro that builds lambda functions. The arguments to the macro that results are exactly the same as those of the original macro, except that the first argument of the new macro is used to name the lambda produced by the original macro (which is passed as the second argument to imalison:named-builder-builder).

(defmacro imalison:named-builder-builder (named-builder-name builder-name)
  `(progn
       (defmacro ,named-builder-name (function-name &rest args)
         (cons 'imalison:named-build
               (cons function-name
                     (cons (quote ,builder-name) args))))
       (put (quote ,named-builder-name) 'lisp-indent-function 1)))

imalison:named-builder runs imalison:named-builder-builder with the convention that original macro to modify is the concatenation of the new macro name and the -fn suffix.

(defmacro imalison:named-builder (name)
  `(imalison:named-builder-builder
    ,name ,(intern (concat (symbol-name name) "-fn"))))

Emacs Version Predicate

(defmacro imalison:emacs-version-predicate-fn (major-version minor-version)
  `(lambda ()
     (or (> emacs-major-version ,major-version)
         (and (>= emacs-major-version ,major-version)
              (>= emacs-minor-version ,minor-version)))))

(defun imalison:check-emacs-version (major-version minor-version)
  (funcall (imalison:emacs-version-predicate-fn major-version minor-version)))

(imalison:named-builder imalison:emacs-version-predicate)

Compose Functions

A version supporting macros

<<composemacros>>

(defun imalison:help-function-arglist (fn)
  (let ((result (help-function-arglist fn)))
    (if (eq result t) '(&rest args) result)))

(defmacro imalison:compose-fn (&rest funcs)
  (let* ((last-function (car (last funcs)))
         (arguments (imalison:help-function-arglist last-function))
         (call-arguments (delq '&optional arguments)))
    ;; When we have an &rest arguments there is no point in taking any
    ;; of the arguments by name, so we simply pass them all as an
    ;; argument list. See the comment below to understand how this
    ;; impacts the evaluation of the last function.
    (when (memq '&rest arguments)
      (setq arguments '(&rest args))
      (setq call-arguments '(args)))
    `(imalison:compose-argspec ,arguments ,call-arguments ,@funcs)))

(defmacro imalison:compose-argspec (arguments call-arguments &rest funcs)
  "Build a new function with NAME that is the composition of FUNCS."
  `(lambda ,arguments
     (imalison:compose-helper ,funcs ,call-arguments)))

(defmacro imalison:compose-helper (funcs arguments)
  "Builds funcalls of FUNCS applied to the arg."
  (if (equal (length funcs) 1)
      (let ((last-function (car funcs)))
        ;; This hideous clause is here because it is the only way to
        ;; handle functions that take &rest args.
        (when (memq '&rest (imalison:help-function-arglist last-function))
          (setq last-function (apply-partially 'apply last-function)))
        `(,last-function ,@arguments))
    `(,(car funcs)
      (imalison:compose-helper ,(cdr funcs) ,arguments))))

(defmacro imalison:compose-macro-fn (&rest args)
  `(cons 'macro (imalison:compose-fn ,@args)))

(imalison:named-builder imalison:compose)
(imalison:named-builder imalison:compose-macro)

Arbitrary arguments at every step

(defun imalison:make-list (thing)
  (if (listp thing)
      thing
    (list thing)))

(defmacro imalison:compose-with-apply (&rest funcs)
  "Build a new function with NAME that is the composition of FUNCS."
  `(lambda (&rest args)
     (imalison:compose-with-apply-helper ,funcs)))

(defmacro imalison:compose-with-apply-helper (funcs)
  "Builds funcalls of FUNCS applied to the arg."
  (if (equal (length funcs) 0)
      (quote args)
    `(apply ,(car funcs)
            (imalison:make-list (imalison:compose-with-apply-helper ,(cdr funcs))))))

Simpler unary version

(defmacro imalison:compose-unary (&rest funcs)
  "Build a new function with NAME that is the composition of FUNCS."
  `(lambda (arg)
     (imalison:compose-helper-unary ,funcs)))

(defmacro imalison:compose-helper-unary (funcs)
  "Builds funcalls of FUNCS applied to the arg."
  (if (equal (length funcs) 0)
      'arg
    `(funcall ,(car funcs) (imalison:compose-helper-unary ,(cdr funcs)))))

With Advice

Taken from here.

(defmacro imalison:with-advice (args &rest body)
  (declare (indent 1))
  (let ((fun-name (car args))
        (advice   (cadr args))
        (orig-sym (make-symbol "orig")))
    `(cl-letf* ((,orig-sym  (symbol-function ',fun-name))
                ((symbol-function ',fun-name)
                 (lambda (&rest args)
                   (apply ,advice ,orig-sym args))))
       ,@body)))

Make Interactive

(defmacro imalison:make-interactive-fn (function)
  `(lambda (&rest args)
     (interactive)
     (apply ,function args)))

(imalison:named-builder imalison:make-interactive)

Advice Add Around Builder

For composing functions with an apply so that they can be used with the :around keyword of advice-add.

(defmacro imalison:advice-add-around-builder-fn (&rest functions)
  `(imalison:compose-argspec
    (function &rest args) (function args) ,@functions apply))

(imalison:named-builder imalison:advice-add-around-builder)

Kill New

(imalison:advice-add-around-builder imalison:kill-new-around kill-new)

Let Around

(defmacro imalison:let-around-fn (orig-func &rest forms)
  (let* ((orig-interactive-form (interactive-form orig-func))
         (docstring-form (format "Call `%s' with bindings: %s." orig-func forms))
         (additional-forms (list docstring-form)))
    (when orig-interactive-form
      (nconc additional-forms (list orig-interactive-form)))
    `(lambda (&rest args)
       ,@additional-forms
       (let ,forms
         (apply (quote ,orig-func) args)))))

(imalison:named-builder imalison:let-around)

Let Around Advice

(defmacro imalison:let-advise-around-fn (&rest forms)
  `(lambda (orig-func &rest args)
     (let ,forms
       (apply orig-func args))))

(imalison:named-builder imalison:let-advise-around)

Let Advise

(defmacro imalison:let-advise (advised-function &rest forms)
  (let ((advice-fn-name (imalison:concat-symbols
                         "around-advice-" advised-function)))
    `(progn
       (imalison:let-advise-around ,advice-fn-name ,@forms)
       (advice-add (quote ,advised-function) :around (quote ,advice-fn-name)))))
(put 'imalison:let-advise 'lisp-indent-function 1)

Compose Around Builder

For composing functions with an apply so that they can be used with the :around keyword of advice-add.

;; TODO/XXX: Isn't this just apply? why doesn't apply work here
(defun imalison:around-identity (fn &rest args)
  (apply fn args))

(defmacro imalison:compose-around-builder-fn (&rest functions)
  `(imalison:compose-fn ,@functions imalison:around-identity))

(imalison:named-builder imalison:compose-around-builder)

Measure Time

(defmacro imalison:measure-time (&rest body)
  "Measure and return the running time of the code block."
  (declare (indent defun))
  (let ((start (make-symbol "start")))
    `(let ((,start (float-time)))
       ,@body
       (- (float-time) ,start))))

Get String From File

(defun imalison:get-string-from-file (file-path)
  "Return file-path's file content."
  (with-temp-buffer
    (insert-file-contents file-path)
    (buffer-string)))

Get Current Location

(defun imalison:get-lat-long ()
  (condition-case _ex
      (mapcar 'string-to-number (s-split "," (s-trim (shell-command-to-string
                                                      "whereami"))))
    (error (list 37.7879312624533 -122.402388853402))))

Haversine distance

(defun imalison:sin2 (p)
  (let ((sin-p (sin p)))
    (* sin-p sin-p) ))

(defun imalison:haversine-distance
    (left-lat-long right-lat-long &optional radius)
  ;; Default to earth radius in km
  (unless radius (setq radius 6378.1))
  (interactive)
  (cl-destructuring-bind (left-lat left-long) left-lat-long
    (cl-destructuring-bind (right-lat right-long) right-lat-long
      (let ((l1 (degrees-to-radians left-lat))
            (f1 (degrees-to-radians left-long))
            (l2 (degrees-to-radians right-lat))
            (f2 (degrees-to-radians right-long)) )
        (* 2 radius
           (asin
            (sqrt
             (+ (imalison:sin2 (/ (- f2 f1) 2))
                (* (cos f2) (cos f1) (imalison:sin2 (/ (- l2 l1) 2))) ))))))))

Font Size

This was taken from here but it has diverged significantly from the original.

(defvar imalison:default-font-size-pt nil)

(defun imalison:acceptable-default-font-size (value)
  (and (numberp value) (< value 150) (> value 50)))

;; XXX: hack to get proper default value when default is set to something crazy
(defun imalison:set-default-font-size (&rest args)
  (interactive)
  (when (and (imalison:acceptable-default-font-size
              (face-attribute 'default :height))
             (not (imalison:acceptable-default-font-size
                   imalison:default-font-size-pt)))
    (setq imalison:default-font-size-pt (face-attribute 'default :height))))

(advice-add 'set-face-attribute :after 'imalison:set-default-font-size)

(defvar imalison:huge-font-size 280)

(defun imalison:current-font-size ()
  (plist-get (custom-face-attributes-get 'default nil) :height))

(defun imalison:set-font-size (size)
  (interactive (list (string-to-number (read-string "Enter a font size: "))))
  (set-face-attribute 'default nil :height size))

(defun imalison:set-huge-font-size ()
  (interactive)
  (imalison:set-font-size imalison:huge-font-size))

(cl-defun imalison:modify-font-size (&optional (arg 10))
  (interactive "p")
  (unless imalison:default-font-size-pt
    (imalison:set-default-font-size))
  (imalison:set-font-size (+ (imalison:current-font-size) arg)))

(defun imalison:font-size-incr ()
  (interactive)
  (imalison:modify-font-size +10))

(defun imalison:font-size-decr ()
  (interactive)
  (imalison:modify-font-size -10))

(defun imalison:font-size-reset ()
  (interactive)
  (imalison:set-font-size imalison:default-font-size-pt))

(defun imalison:font-size-80chars ()
  (interactive)
  (imalison:set-font-size 120))

Message Result Builder

This macro is useful when writing emacs-lisp. It creates a new interactive command that shows you the result of evaluating a function, with optionally provided arguments.

(defmacro imalison:message-result-builder (new-function-name function-to-call &rest args)
  `(defun ,new-function-name ()
     (interactive)
     (message "%s" (apply (quote ,function-to-call) (list ,@args)))))

This interactive functions allows the user the select a function to invoke using a freshly minted imalison:message-result-builder

(defun imalison:message-result-builder-runtime (function &rest args)
  (lambda ()
    (interactive)
    (message "%s" (apply function-to-call args))))

(defun imalison:message-function-result (function)
  (interactive (find-function-read))
  (message "%s" (funcall function)))

Custom shell-command-on-region

(defun imalison:copy-shell-command-on-region (start end command)
  (interactive (list (region-beginning) (region-end)
                     (read-shell-command "Shell command on region: ")))
  (let ((original-buffer (current-buffer)))
    (with-temp-buffer
      (let ((temp-buffer (current-buffer)))
        (with-current-buffer original-buffer
          (shell-command-on-region start end command temp-buffer))
        (let ((min (point-min))
              (max (point-max)))
          (kill-ring-save min max)
          (buffer-substring min max))))))

(defun region-if-active-otherwise-buffer ()
  (if (region-active-p)
      (list (region-beginning) (region-end))
    (list (buffer-end -1) (buffer-end 1))))

(region-if-active-otherwise-buffer)

(defun imalison:shell-command-on-region-replace (start end command)
  (interactive (list (region-beginning) (region-end)
                     (read-shell-command "Shell command on region: ")))
  (shell-command-on-region start end command nil t))

(emit-prefix-selector imalison:shell-command-on-region
  imalison:copy-shell-command-on-region
  imalison:shell-command-on-region-replace
  imalison:jq-replace)

(defun imalison:jq-replace (start end)
  (interactive (region-if-active-otherwise-buffer))
  (imalison:shell-command-on-region-replace start end "jq ."))

Copy/Yank String Functions

A macro for composing functions together to build an interactive command to copy a string to the kill ring.

(defmacro imalison:compose-copy-builder-fn (&rest funcs)
  `(imalison:make-interactive-fn
    (imalison:compose-fn kill-new ,@funcs)))

(imalison:named-builder imalison:compose-copy-builder)

Copy portions of the buffer file name

(defmacro imalison:copy-buffer-file-path-builder (&rest args)
  `(imalison:compose-copy-builder ,@args buffer-file-name))

(imalison:copy-buffer-file-path-builder imalison:copy-buffer-file-path-full)
(imalison:copy-buffer-file-path-builder imalison:copy-buffer-file-name
                                        file-name-nondirectory)
(imalison:copy-buffer-file-path-builder imalison:copy-buffer-file-path
                                        car
                                        projectile-make-relative-to-root
                                        list)

Copy the current branch using magit

(imalison:compose-copy-builder imalison:copy-current-git-branch
                               magit-get-current-branch)

Copy the current buffer name

(imalison:compose-copy-builder imalison:copy-current-buffer-name
  buffer-name)

Copy the last message

(defun imalison:last-message (&optional num)
  (or num (setq num 1))
  (if (= num 0)
      (current-message)
    (save-excursion
      (set-buffer "*Messages*")
      (save-excursion
    (forward-line (- 1 num))
    (backward-char)
    (let ((end (point)))
      (forward-line 0)
      (buffer-substring-no-properties (point) end))))))

(imalison:compose-copy-builder imalison:copy-last-message imalison:last-message)

Named Compile

(defun imalison:named-compile (command)
  (interactive
   (list
    (let ((command (eval compile-command)))
      (if (or compilation-read-command current-prefix-arg)
          (compilation-read-command command)
        command))))
  (compilation-start command nil (lambda (&rest args)
                                   (format "*compilation %s*" command))))

Replace Escape Sequences

(defun imalison:replace-escape-sequences ()
  (interactive)
  (shut-up
    (let* ((delimited (and transient-mark-mode mark-active))
           (beg (when delimited (region-beginning)))
           (end (when delimited (region-end))))
      (save-excursion
        (perform-replace "\\t" "    " nil nil delimited nil nil beg end nil))
      (save-excursion
        (perform-replace "\\n" "\n" nil nil delimited nil nil beg end nil)))))

Download a File Into a Buffer

<<downloadfile>>

(defun imalison:download-to-buffer (uri)
  (interactive (list (read-string "Enter uri: ")))
  (require 'request)
  (request uri
           :parser 'buffer-string
           :success (cl-function
                     (lambda (&key data &allow-other-keys)
                       (let ((created-buffer (get-buffer-create uri)))
                         (with-current-buffer created-buffer
                           (insert data))
                         (switch-to-buffer created-buffer))))))

Concat With Symbols

(defun imalison:maybe-symbol-name (arg)
  (if (symbolp arg)
      (symbol-name arg)
    arg))

(defun imalison:concat-symbols (&rest args)
  (intern (mapconcat 'imalison:maybe-symbol-name args "")))

Edit a script on PATH

<<editscript>> Note that you'll need to make sure that emacs properly inherits the path variable for this work. Check out my exec-path-from-shell config for details.

(defun imalison:get-executables-at-path (filepath)
  (when (and (file-exists-p filepath) (f-directory? filepath))
    (--filter (let ((fullpath (imalison:join-paths filepath it)))
                (and (file-executable-p fullpath)
                     (not (f-directory? fullpath))))
              (directory-files filepath))))

(use-package eshell
  :commands (eshell-parse-colon-path imalison:get-executables-on-path)
  :config
  (defun imalison:get-executables-on-path ()
    (mapcan 'imalison:get-executables-at-path
            (eshell-parse-colon-path (getenv "PATH")))))

(defun imalison:edit-script ()
  (interactive)
  (find-file
   (executable-find
    (completing-read "Select a script to edit: "
                     (imalison:get-executables-on-path)))))

Toggle lexical binding in the current buffer

(defun imalison:toggle-lexical-binding ()
  (interactive)
  (let ((new-binding (not lexical-binding)))
    (message "Setting lexical-binding to: %s" new-binding)
    (setq lexical-binding new-binding)))

Sync kill ring with copyq

(defun imalison:copyq-get (i)
  (imalison:shell-command-to-string (format "copyq eval read(%s)" i)))

(defun imalison:copyq-sync ()
  (interactive)
  (let ((missing-items (cl-loop for i from 0 to (string-to-number
                         (imalison:shell-command-to-string "copyq eval size()"))
         for item = (imalison:copyq-get i)
         when (not (member item kill-ring))
         collect item)))
    (setq kill-ring (nconc kill-ring missing-items))))

(when (executable-find "copyq")
 (run-with-idle-timer 10 nil 'imalison:copyq-sync))

Disable hooks

(cl-defmacro imalison:disable-mode-hook (mode-name &optional (disable-value -1))
  `(defun ,(imalison:concat-symbols 'imalison:disable- mode-name) ()
     (when (fboundp ',mode-name)
       (,mode-name ,disable-value))))

(imalison:disable-mode-hook linum-mode)
(imalison:disable-mode-hook nlinum-mode)
(imalison:disable-mode-hook yas-minor-mode)

Add a blacklist to a major mode

Sometimes a major mode's syntax highlighting can take a really long time to load in certain buffers. Usually you can just set a header to tell emacs not to run the major mode, but for cases where you can't always edit the file ahead of time this macro allows you to define a blacklist for your major mode that will prevent the major mode from being enabled on that file.

(defmacro imalison:add-blacklist-to-major (major-mode-fn-symbol)
  (let ((blacklist-var-symbol
         (imalison:concat-symbols major-mode-fn-symbol "-blacklist"))
        (check-blacklist-symbol
         (imalison:concat-symbols major-mode-fn-symbol "-check-blacklist")))
    `(progn
       (defvar ,blacklist-var-symbol nil)
       (defun ,check-blacklist-symbol (mode-fn &rest args)
         (unless (and (not (equal major-mode (quote ,major-mode-fn-symbol)))
                      (equal nil args)
                      (cl-loop for blacklist-regex in ,blacklist-var-symbol
                               thereis (string-match blacklist-regex
                                                     (buffer-name))))
           (apply mode-fn args)))
       (advice-add (quote ,major-mode-fn-symbol)
                   :around (quote ,check-blacklist-symbol)))))

Other

The stuff in this section is pretty crusty. I don't think its used anywhere, but I keep it around just in case I need it.

(defun random-choice (choices)
  (nth (random (length choices)) choices))

(defun display-prefix (arg)
  "Display the value of the raw prefix arg."
  (interactive "p")
  (message "%s" arg))

(defun imalison:uuid ()
  (interactive)
  (s-replace "\n" "" (shell-command-to-string "uuid")))

(defun imalison:disable-smartparens-mode ()
  (smartparens-mode 0))

(defun imalison:insert-uuid ()
  (interactive)
  (insert (imalison:uuid)))

(defun imalison:compare-int-list (a b)
  (when (and a b)
    (cond ((> (car a) (car b)) 1)
          ((< (car a) (car b)) -1)
          (t (imalison:compare-int-list (cdr a) (cdr b))))))

(defun get-date-created-from-agenda-entry (agenda-entry)
  (org-time-string-to-time
   (org-entry-get (get-text-property 1 'org-marker agenda-entry) "CREATED")))

(defmacro defvar-setq (name value)
  `(if (boundp (quote ,name))
       (setq ,name ,value)
     (defvar ,name ,value)))

(defun eval-region-or-last-sexp ()
  (interactive)
  (if (region-active-p) (call-interactively 'eval-region)
    (call-interactively 'eval-last-sexp)))

(defun undo-redo (&optional arg)
  (interactive "P")
  (if arg (undo-tree-redo) (undo-tree-undo)))

(defun up-list-region ()
  (interactive)
  (up-list) (set-mark-command nil) (backward-sexp))

(defun up-list-back ()
  (interactive)
  (up-list) (backward-sexp))

(defun frame-exists ()
  (cl-find-if
   (lambda (frame)
     (assoc 'display (frame-parameters frame))) (frame-list)))

(defun make-frame-if-none-exists ()
  (let* ((existing-frame (frame-exists)))
    (if existing-frame
        existing-frame
      (make-frame-on-display (getenv "DISPLAY")))))

(defun make-frame-if-none-exists-and-focus ()
  (make-frame-visible (select-frame (make-frame-if-none-exists))))

(defun notification-center (title message)
  (cl-flet ((encfn (s) (encode-coding-string s (keyboard-coding-system))))
    (shell-command
     (format "osascript -e 'display notification \"%s\" with title \"%s\"'"
             (encfn message) (encfn title)))))

(defun growl-notify (title message)
  (shell-command (format "grownotify -t %s -m %s" title message)))

(defun notify-send (title message)
  (shell-command (format "notify-send -u critical %s %s" title message)))

(defvar notify-function
  (cond ((eq system-type 'darwin) 'notification-center)
        ((eq system-type 'gnu/linux) 'notify-send)))
;; TODO: fix
(emit-prefix-selector imalison:mark-ring
  mark-ring)

Keyboard Macros

For editing literate config

extract-current-sexp-to-src-block

This keyboard macro extracts the current sexp to an emacs-lisp source block of its own

(fset 'extract-current-sexp-to-src-block
     [?\C-a return ?\C-p ?# ?+ ?E ?N ?D ?_ ?S ?R ?C return ?# ?+ ?B ?E ?G ?I ?N ?_ ?S ?R ?C ?  ?e ?m ?a ?c ?s ?- ?l ?i ?s ?p ?\C-a ?\C-p ?\C-  ?\C-n ?\C-e ?\M-w ?\C-n ?\C-a ?\C-\M-f return ?\C-y])
name-source-block-for-use-package-name
(fset 'name-source-block-for-use-package-name
     [?\C-c ?\' ?\M-< ?\C-s ?u ?s ?e ?- ?p ?a ?c ?k return ?\C-\M-f ?\C-f ?\C-  ?\C-\M-f ?\M-w ?\C-c ?\' ?\C-r ?B ?E ?G ?I ?N return ?\C-a ?\C-p ?\C-e return ?* ?  ?\C-y])
extract-and-name-use-package-block
(fset 'extract-and-name-use-package-block
     [?\C-a return ?\C-p ?# ?+ ?E ?N ?D ?_ ?S ?R ?C return ?# ?+ ?B ?E ?G ?I ?N ?_ ?S ?R ?C ?  ?e ?m ?a ?c ?s ?- ?l ?i ?s ?p ?\C-a ?\C-p ?\C-  ?\C-n ?\C-e ?\M-w ?\C-n ?\C-a ?\C-\M-f return ?\C-y ?\C-p ?\C-p ?\C-c ?\' ?\M-< ?\C-s ?u ?s ?e ?- ?p ?a ?c ?k return ?\C-\M-f ?\C-f ?\C-  ?\C-\M-f ?\M-w ?\C-c ?\' ?\C-r ?B ?E ?G ?I ?N return ?\C-a ?\C-p ?\C-e return ?* ?  ?\C-y])

General

User Info

(setq user-full-name
      (replace-regexp-in-string "\n$" "" (shell-command-to-string
                                          "git config --get user.name")))
(setq user-mail-address
      (replace-regexp-in-string "\n$" "" (shell-command-to-string
                                          "git config --get user.email")))

Sane Defaults

(global-auto-revert-mode +1)
(auto-save-visited-mode +1)
(show-paren-mode 1)
(setq reb-re-syntax 'string)
(setq ad-redefinition-action 'accept)              (ref:ad-redefinition-action)
(setq-default find-file-visit-truename t)
(setq large-file-warning-threshold (* 25 1024 1024))
(setq line-move-visual t)
(setq require-final-newline t)
(when (fboundp 'pixel-scroll-mode)
  (pixel-scroll-mode 1))

This is set because this alias causes annoying messaging at startup.

System Clipboard

(setq save-interprogram-paste-before-kill t)

Line Numbers

(line-number-mode t)
(column-number-mode t)
(defun imalison:enable-display-line-numbers-mode ()
  (display-line-numbers-mode +1))
(add-hook 'prog-mode-hook 'imalison:enable-display-line-numbers-mode)

Backups

Put them all in one directory

(defconst emacs-tmp-dir
  (format "%s/%s%s/" temporary-file-directory "emacs" (user-uid)))
(setq backup-directory-alist `((".*" . ,emacs-tmp-dir)))
(setq auto-save-file-name-transforms `((".*" ,emacs-tmp-dir t)))
(setq auto-save-list-file-prefix emacs-tmp-dir)

Completely disable backups

(setq backup-inhibited t)
(setq make-backup-files nil)
(setq auto-save-default nil)

Prompts

No popup frames

(setq ns-pop-up-frames nil)
(setq pop-up-frames nil)

boolean (yes-or-no)

(defadvice yes-or-no-p (around prevent-dialog activate)
  "Prevent yes-or-no-p from activating a dialog"
  (let ((use-dialog-box nil))
    ad-do-it))

(defadvice y-or-n-p (around prevent-dialog-yorn activate)
  "Prevent y-or-n-p from activating a dialog"
  (let ((use-dialog-box nil))
    ad-do-it))

(defalias 'yes-or-no-p 'y-or-n-p)                           (ref:y-or-n-p-only)

No dialog boxes

(setq use-dialog-box nil)

Splitting

(defun split-horizontally-for-temp-buffers () (split-window-horizontally))
(add-hook 'temp-buffer-setup-hook 'split-horizontally-for-temp-buffers)
(setq split-height-threshold nil)
(setq split-width-threshold 160)

Time in Mode Line

(setq display-time-default-load-average nil)
(setq display-time-interval 1)
(setq display-time-format "%a|%m-%d|%r")
(display-time-mode +1)

Buffer Display

ewmctrl

(use-package ewmctrl
  :defer t)

frame-mode

(defvar imalison:use-frame-mode
  (s-contains? "xmonad" (shell-command-to-string "wmctrl -m")))

(use-package frame-mode
  :if imalison:use-frame-mode
  :demand t
  :config
  (progn
    (add-hook 'frame-mode-hook (lambda () (display-time-mode -1)))
    (frame-mode +1)
    (frame-keys-mode +1)))

Handle xrefs annoying dedicated window garbage

(use-package xref)

Fill Setup

Get rid of nags about requiring setences to end with two spaces.

(setq sentence-end-double-space nil)

Set the default fill-column

(setq-default fill-column 80)

Show Trailing Whitespace

Trailing whitespace is really messy and annoying, which makes this a must-have in my opinion. It's kind of crazy how often you will encounter serious codebases with random whitespace ALL over the place.

(setq-default show-trailing-whitespace nil)

(defun imalison:show-trailing-whitespace ()
  (interactive)
  (setq show-trailing-whitespace t))

(add-hook 'text-mode-hook 'imalison:show-trailing-whitespace)
(add-hook 'prog-mode-hook 'imalison:show-trailing-whitespace)

Disable

Unfortunately, this setting can get annoying in a lot of modes, which is why I use this hook to disable it in those modes

(defun imalison:disable-show-trailing-whitespace ()
  (setq show-trailing-whitespace nil))

Encoding

UTF-8 everywhere

(defun imalison:set-coding-systems ()
  (interactive)
  (set-language-environment "Latin-1")
  (set-default-coding-systems 'utf-8)
  (unless (eq system-type 'windows-nt)
    (set-selection-coding-system 'utf-8))
  (set-terminal-coding-system 'utf-8)
  (setq locale-coding-system 'utf-8)
  (prefer-coding-system 'utf-8))
(imalison:set-coding-systems)

Disable CJK coding/encoding (Chinese/Japanese/Korean characters)

(setq utf-translate-cjk-mode nil)

Visible Bell

This is set to true to disable the annoying audible bell that plays whenever there is an error.

(setq visible-bell t)

Configure vc

(setq vc-follow-symlinks t)

Kill Ring

(setq kill-ring-max 1000)

Subword

This makes forward-word and backward-word understand snake and camel case.

(setq c-subword-mode t)
(global-subword-mode)

Scratch Buffer

(setq initial-scratch-message "")

Don't prompt about local variables

(defun risky-local-variable-p (&rest args)
  nil)

proced

proced is an top like utility that runs inside of emacs. The following sets auto updating automatically and makes the update interval faster.

(use-package proced
  :defer t
  :config
  (progn
    (setq proced-auto-update-interval 1)
    (add-hook 'proced-mode-hook (lambda () (proced-toggle-auto-update +1)))))

Set epa program

(setq epg-gpg-program "gpg")

Make files executable

(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)

Misc

(defvar iedit-toggle-key-default nil)
(put 'set-goal-column 'disabled nil)
(auto-fill-mode -1)
(setq indent-tabs-mode nil)

(setq confirm-nonexistent-file-or-buffer nil)

;; No prompt for killing a buffer with processes attached.
(setq kill-buffer-query-functions
      (remq 'process-kill-buffer-query-function
            kill-buffer-query-functions))

(setq inhibit-startup-message t
      inhibit-startup-echo-area-message t)

;; Make buffer names unique.
(setq uniquify-buffer-name-style 'forward)

;; Don't disable commands...
(setq disabled-command-function nil)

;; Make forward word understand camel and snake case.

;; Preserve pastes from OS when saving a new item to the kill
;; ring. Why wouldn't this be enabled by default?

(setq-default cursor-type 'box)
(setq-default cursor-in-non-selected-windows 'bar)

(when nil ;; Causing too many annoying issues
  (add-hook 'after-init-hook '(lambda () (setq debug-on-error t))))

;; Make mouse scrolling less jumpy.
(setq mouse-wheel-scroll-amount '(1 ((shift) . 1)))

(setq ediff-split-window-function 'split-window-horizontally)
(setq ediff-window-setup-function 'ediff-setup-windows-plain)

;; Disable this per major mode or maybe using file size if it causes
;; performance issues?
(setq imenu-auto-rescan t)
(setq imenu-max-item-length 300)

(put 'narrow-to-region 'disabled nil)
(put 'narrow-to-page 'disabled nil)

(setq echo-keystrokes 0.25)


;; text mode stuff:
(remove-hook 'text-mode-hook #'turn-on-auto-fill)
(add-hook 'text-mode-hook 'turn-on-visual-line-mode)
(setq sentence-end-double-space nil)

;; y and n instead of yes and no
(setq-default c-basic-offset 4
              tab-width 4
              indent-tabs-mode t)

(add-hook 'prog-mode-hook (lambda () (auto-fill-mode -1)))
;; (add-hook 'prog-mode-hook 'flyspell-prog-mode)

;; (add-hook 'prog-mode-hook (lambda () (highlight-lines-matching-regexp
;;                                  ".\\{81\\}" 'hi-blue)))

paradox

Paradox is a package.el extension. I have no use for it now that I use straight.el.

(use-package paradox
  :disabled t
  :commands (paradox-upgrade-packages paradox-list-packages)
  :config
  (progn
    (require 'gh)
    (setq paradox-execute-asynchronously t
          paradox-github-token (gh-auth-get-oauth-token))))

gcmh

(use-package gcmh
  :config (gcmh-mode 1))

diminish

(use-package diminish
  :preface
  (defvar imalison:packages-to-diminish
    '(auto-revert-mode smartparens-mode eldoc-mode tern-mode js2-refactor-mode))
  :config
  (progn
    (cl-loop for package in imalison:packages-to-diminish
             do (diminish package))
    (eval-after-load 'subword '(diminish 'subword-mode))
    (eval-after-load 'simple '(diminish 'visual-line-mode))))

emacs-everywhere

(use-package emacs-everywhere
  :commands emacs-everywhere)

atomic-chrome

(use-package atomic-chrome
  :defer 1
  :config
  (progn
    (atomic-chrome-start-server)
    (setq atomic-chrome-buffer-open-style 'frame)))

load-dir

(use-package load-dir
  :demand t
  :config
  (progn
    (setq load-dir-debug nil)
    (add-to-list 'load-dirs "~/.emacs.d/load.d")
    (defvar site-lisp "/usr/share/emacs24/site-lisp/")
    (when (file-exists-p site-lisp) (add-to-list 'load-dirs site-lisp))
    (load-dirs-reload)))

server

(use-package server
  :demand t
  :config
  (progn
  (setq server-use-tcp nil)
    (defvar imalison:default-server-file
      (imalison:join-paths user-emacs-directory "server" "server"))
    (defun imalison:main-emacs-server-name ()
      (file-name-nondirectory
       (file-truename imalison:default-server-file)))
    (defun imalison:make-main-emacs-server ()
      (interactive)
      (unless (string-equal server-name (imalison:main-emacs-server-name))
        (if (string-equal server-name "server")
              (error "Unable to set main server name 'server'.
The file server file for this emacs instance no longer exists.")
          (progn
            (delete-file imalison:default-server-file)
            (make-symbolic-link (imalison:join-paths user-emacs-directory "server" server-name)
                                imalison:default-server-file)))))
    (defun imalison:get-this-server-filepath ()
      (let ((server-dir (if server-use-tcp server-auth-dir server-socket-dir)))
        (expand-file-name server-name server-dir)))
    (when (equal nil (server-running-p)) (server-start)
          (imalison:make-main-emacs-server))))

list-environment

(use-package list-environment)

bug-hunter

(use-package bug-hunter)

shackle

(use-package shackle
  :disabled t
  :config
  (progn
    (diminish 'shackle-mode)
    (when nil                           ; disabled for now
      (shackle-mode))
    (setq shackle-inhibit-window-quit-on-same-windows t)
    (setq shackle-default-rule '(:same t))))

beacon

(use-package beacon
  :disabled t
  :demand t
  :diminish beacon-mode
  :bind ("C-c b" . beacon-blink)
  :config
  (beacon-mode 1))

iregister

(use-package iregister)

discover-my-major

(use-package discover-my-major)

refine

(use-package refine
  :disabled t)

winner

(use-package winner
  :commands (winner-undo winner-redo)
  :bind ("C-c q" . imalison:winner-hydra/body)
  :config
  (progn
    (defhydra imalison:winner-hydra ()
      "Winner"
      ("p" winner-undo "back")
      ("n" winner-redo "forward" :exit t))
    (winner-mode 1)))

eyebrowse

I don't have any use for this now that I use frames mode, but its an interesting idea.

(use-package eyebrowse
  :disabled t
  :defer 1
  :config
  (progn (eyebrowse-mode +1)))

stream

(use-package stream :defer t)

tile

(use-package tile
  :bind ("C-c t" . imalison:hydra-tile/body)
  :config
  (progn
    (defvar imalison:tall-tile-strategy (tile-split-n-tall 3))
    (defvar imalison:wide-tile-strategy tile-wide)
    (defvar imalison:master-tile-strategy (tile-argument-buffer-fetcher
                                           :layout tile-master-left))
    (require 'hydra)
    (defhydra imalison:hydra-tile
      nil
      "tile"
      ("t" (tile :strategy imalison:tall-tile-strategy))
      ("w" (tile :strategy imalison:wide-tile-strategy))
      ("m" (tile :strategy imalison:master-tile-strategy))
      ("s" tile-select)
      ("0" (tile :strategy tile-one))
      ("n" tile)
      ("l" winner-undo))
    (setq tile-cycler
          (tile-strategies :strategies
                           (list imalison:tall-tile-strategy
                                 imalison:master-tile-strategy
                                 imalison:wide-tile-strategy
                                 tile-one)))))

fill-column-indicator

This interferes with too many other packages. See https://github.com/alpaker/Fill-Column-Indicator/issues/21 for more details

(use-package fill-column-indicator
  :disabled t
  :config
  (progn
    (defun fci-on-off-fci-before-company (command)
      (when (string= "show" command)
        (turn-off-fci-mode))
      (when (string= "hide" command)
        (turn-on-fci-mode)))
    (advice-add 'company-call-frontends :before #'fci-on-off-fci-before-company)
    (add-hook 'prog-mode-hook 'fci-mode)))

highlight-indent-guides

If the load-theme hook from this package starts causing trouble check for custom-set-faces in your custom file.

(use-package highlight-indent-guides
  :commands highlight-indent-guides-mode
  :diminish highlight-indent-guides-mode
  :preface
  (progn
    (add-hook 'prog-mode-hook 'highlight-indent-guides-mode))
  :config
  (progn
    (setq highlight-indent-guides-method 'fill)))

man-mode

Man page escape sequences aren't properly handled by emacs pager. This function fixes that, but for now, it needs to be run manually, since I haven't figured out how to detect that a buffer is a man mode buffer.

(use-package man
  :config
  (progn
    (defun imalison:fontify-man-page-buffer ()
      (interactive)
      (read-only-mode -1)
      (Man-fontify-manpage)
      (read-only-mode +1))))

treesit

treesit-auto

(use-package treesit-auto
  :demand t
  :config
  (progn
	(setq treesit-auto-install t)
	(global-treesit-auto-mode +1)

	(defun treesit-auto-for-each (fn)
	  (cl-loop for recipe in treesit-auto-recipe-list
			   do
			   (let ((from (treesit-auto-recipe-remap recipe))
					 (to (treesit-auto-recipe-ts-mode recipe)))
				 (funcall fn from to))))

	(defun treesit-auto-get-mode-hook-symbol (mode)
	  (intern (concat (symbol-name mode) "-hook")))

	(defvar treesit-auto-run-original-hooks t)
	(defvar treesit-auto-hook-copy-blacklist '((rust-mode . rust-ts-mode)))

	(treesit-auto-for-each
	 (lambda (from to)
	   (let ((targets (if (listp from) from (list from))))
		 (cl-loop for from in targets
				  do
				  (letrec ((to-hook (treesit-auto-get-mode-hook-symbol to))
						   (from-hook (treesit-auto-get-mode-hook-symbol from))
						   (treesit-auto-hook-name
							(intern
							 (concat "treesit-auto-run-"
									 (symbol-name from-hook)
									 "-for-" (symbol-name to)))))
					(defalias treesit-auto-hook-name
					  `(lambda ()
						 (when (and treesit-auto-run-original-hooks
									(boundp ',from-hook)
									(not (memq '(,from . ,to) treesit-auto-hook-copy-blacklist)))
						   (message "Running hooks from %s for %s" ',from-hook ',to)
						   (run-mode-hooks ',from-hook))))
					(add-hook to-hook treesit-auto-hook-name))))))))

helpful

(use-package helpful
  :bind (("C-h f" . helpful-callable)
         ("C-h v" . helpful-variable)
         ("C-h k" . helpful-key)
         ("C-h a" . describe-symbol)))

Keybindings

god-mode

(use-package god-mode
  :disabled t
  :demand t
  :config
  (progn
    (global-set-key (kbd "<escape>") 'god-local-mode)))

bind-key

(use-package bind-key)

which-key

(use-package which-key
  :config
  (progn
    (setq which-key-idle-delay .50)
    (diminish 'which-key-mode)
    (which-key-mode)))

hydra

(use-package hydra
  :demand t
  :bind (("C-c f" . imalison:hydra-font/body)
         ("C-c y" . imalison:hydra-yank/body)
         ("C-c 6" . imalison:compile/body))
  :config
  (progn

Font Settings

<<fontsizehydra>>

(defhydra imalison:hydra-font
 nil
 "Font Settings"
 ("-" imalison:font-size-decr "Decrease")
 ("d" imalison:font-size-decr "Decrease")
 ("=" imalison:font-size-incr "Increase")
 ("+" imalison:font-size-incr "Increase")
 ("i" imalison:font-size-incr "Increase")
 ("h" imalison:set-huge-font-size "Huge")
 ("a" imalison:appearance "Set Default Appearance")
 ("f" set-frame-font "Set Frame Font")
 ("t" load-theme "Choose Emacs Theme")
 ("0" imalison:font-size-reset "Reset to default size")
 ("8" imalison:font-size-80chars "80 chars 3 columns font size"))

Copy/Yanking

(defhydra imalison:hydra-yank
  nil
  "Yank text"
  ("p" imalison:copy-buffer-file-path "Projectile path")
  ("b" imalison:copy-current-buffer-name "Buffer Name")
  ("f" imalison:copy-buffer-file-path-full "Full path")
  ("n" imalison:copy-buffer-file-name "File name")
  ("g" imalison:copy-current-git-branch "Git Branch")
  ("m" imalison:copy-last-message "Last Message"))

Compile

(defun imalison:make-test ()
  (interactive)
  (let ((default-directory (projectile-project-root)))
    (imalison:named-compile "make test")))

(defun imalison:glide-up ()
  (interactive)
  (imalison:named-compile "glide up"))

(defun imalison:stack-build ()
  (interactive)
  (let ((frame-mode-use-new-frame-or-window t))
    (imalison:named-compile "stack build")))

(defun imalison:nix-build ()
  (interactive)
  (let ((frame-mode-use-new-frame-or-window t))
    (imalison:named-compile "nix-build default.nix")))

(defhydra imalison:compile nil "Compile"
  ("c" imalison:named-compile "Enter Custom Command")
  ("s" imalison:stack-build "Stack build")
  ("n" imalison:nix-build "Nix build")
  ("t" imalison:make-test "Test")
  ("u" imalison:glide-up "Update Dependencies"))
;; The following parens close the use-package/progn created several blocks above
))

kill-emacs

This ensures that C-x C-c will always kill emacs, even if we are running in server mode.

(bind-key "C-x C-c" 'kill-emacs)

imenu

imenu is the best. This should be a default binding.

(bind-key "C-x C-i" 'imenu)

undo

I can't shake the habit of using this keybinding for undo. I should really use the default of C-/.

(bind-key "C--" 'undo)

other-window

Go the other way when you use capital O.

(bind-key "C-x O" (lambda () (interactive) (other-window -1)))

Mark ring

(bind-key "C-c SPC" 'imalison:mark-ring)

Other bindings

(bind-key "C-x p" 'pop-to-mark-command)
(setq set-mark-command-repeat-pop t)
(bind-key "C-x C-b" 'buffer-menu)
(bind-key "C-x C-r" (lambda () (interactive) (revert-buffer t t)))
(bind-key "C-x w" 'whitespace-mode)
(bind-key "M-n" 'forward-paragraph)
(bind-key "M-p" 'backward-paragraph)
(bind-key "C-M-<backspace>" 'backward-kill-sexp)
(bind-key "s-<return>" 'toggle-frame-fullscreen)
(bind-key "M-|" 'imalison:shell-command-on-region)
(bind-key "C-x 9" 'previous-buffer)
(bind-key "s-v" 'clipboard-yank)

global-set-key-to-use-package

This might be useless, but I believe that it is a macro that converts between bind-key and global-set-key forms.

(fset 'global-set-key-to-use-package
      (lambda (&optional arg) "Keyboard macro." (interactive "p")
        (kmacro-exec-ring-item
         (quote ([1 67108896 19 100 6 23 40 19 41 return
                    backspace 32 46 6 4] 0 "%d")) arg)))

OSX

(when (equal system-type 'darwin)
  (setq mac-option-modifier 'meta)
  (setq mac-command-modifier 'super))

xref

(use-package xref
  :bind ("M-," . xref-go-back))

Navigation

zop-to-char

(use-package zop-to-char
  :bind ("M-z" . zop-to-char)
  :init
  (progn
    (setq zop-to-char-kill-keys '(?\C-k ?\C-w))
    (setq zop-to-char-quit-at-point-keys '(?\r))))

projectile

(use-package projectile
  :demand t
  :bind (:map projectile-mode-map
              ("C-c p f" . imalison:projectile-find-file)
              ("C-c p" . projectile-command-map)
              ("C-c p s" . imalison:do-rg)
              ("C-c p f" . imalison:projectile-find-file))
  :custom
  ((projectile-require-project-root nil)
   (projectile-enable-caching nil)
   (projectile-git-submodule-command nil)
   (project-vc-merge-submodules nil)
   (projectile-current-project-on-switch 'keep))
  :preface
  (progn
    (defmacro imalison:projectile-do-in-project (project-dir &rest forms)
      `(imalison:with-default-directory ,project-dir
                                        (noflet ((projectile-project-root (&rest args) ,project-dir))
                                          ,@forms)))

    (defmacro imalison:with-default-directory (directory &rest forms)
      `(let ((default-directory ,directory))
         ,@forms))

    (defun imalison:do-rg-default-directory (&rest args)
      (interactive)
      (apply 'consult-ripgrep default-directory args))

    (emit-prefix-selector imalison:do-rg
      consult-ripgrep
      imalison:do-rg-default-directory)

    (emit-prefix-selector imalison:projectile-find-file
      projectile-find-file
      projectile-find-file-other-window)

    (imalison:let-around imalison:set-options-do-rg
      imalison:do-rg)

    (defun imalison:projectile-make-all-subdirs-projects (directory)
      (cl-loop for file-info in (directory-files-and-attributes directory)
               do (when (nth 1 file-info)
                    (write-region "" nil
                                  (expand-file-name
                                   (concat directory "/"
                                           (nth 0 file-info) "/.projectile")))))))
  :config
  (progn
    (projectile-global-mode)
    (diminish 'projectile-mode)))

ido

(use-package ido
  :disabled t
  :demand t
  :commands ido-mode
  :config
  (progn
    (ido-mode 1)
    (setq ido-auto-merge-work-directories-length -1
          ido-default-buffer-method 'selected-window
          ido-use-virtual-buffers t
          ido-use-filename-at-point nil
          ido-create-new-buffer 'always)
    (ido-everywhere +1)
    (setq ido-enable-flex-matching t)
    (use-package flx-ido
      :defer 5
      :config
      (progn
        ;; disable ido faces to see flx highlights.
        ;; This makes flx-ido much faster.
        (setq gc-cons-threshold 20000000)
        (flx-ido-mode 1)
        (setq ido-use-faces nil)))
    (use-package ido-vertical-mode
      :config
      (progn
        (ido-vertical-mode 1)
        (setq ido-vertical-define-keys 'C-n-C-p-up-and-down)))
    (use-package flx-ido)))

icomplete

(use-package icomplete
  :disabled t
  :config
  (progn
    (fido-mode +1)
    (fido-vertical-mode)))

avy

(use-package avy
  :preface
  (progn
    (emit-prefix-selector imalison:avy
      avy-goto-word-1
      avy-goto-char))
  :bind (("C-j" . imalison:avy)
         ("M-g l" . avy-goto-line)
         ("C-'" . avy-goto-char-2)))

ace-window

(use-package ace-window
  :preface
  (emit-prefix-selector imalison:ace-window
    ace-select-window
    ace-swap-window)
  :config (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  :bind ("C-c w" . imalison:ace-window))

neotree

Neotree is useless with frame mode for now, so I've disabled it.

(use-package neotree
  :disabled t)

flimenu

(use-package flimenu
  :disabled t
  :config
  (progn
    (flimenu-global-mode)))

ctrlf

(use-package ctrlf
  :disabled t
  :config (ctrlf-mode +1))

Completion

vertico

(use-package vertico
  :demand t
  :config
  (progn
    (defmacro imalison:use-vertico-extension (extension-name &rest forms)
      (let ((extension-symbol (intern (concat "vertico-" (symbol-name extension-name)))))
        `(use-package ,extension-symbol
           :after vertico
           :straight (,extension-symbol
                      :repo "emacs-straight/vertico"
                      :host github
                      :files ,(list (concat "extensions/" (symbol-name extension-symbol) ".el")))
           ,@forms)))

    (put 'imalison:use-vertico-extension 'lisp-indent-function 'defun)

    (imalison:use-vertico-extension directory
      :bind (:map vertico-map
                  ("RET" . vertico-directory-enter)
                  ("DEL" . vertico-directory-delete-char)
                  ("M-DEL" . vertico-directory-delete-word)
                  ("TAB" . embark-act)))

    (imalison:use-vertico-extension grid)
    (imalison:use-vertico-extension buffer)
    (imalison:use-vertico-extension flat)
    (imalison:use-vertico-extension multiform
      :config
      (progn
        (vertico-multiform-mode +1)
        (setq vertico-multiform-commands
              '((consult-imenu grid)
                (consult-ripgrep buffer)))))

    (vertico-mode +1)))

orderless

(use-package orderless
  :demand t
  :config
  (defun basic-remote-try-completion (string table pred point)
    (and (vertico--remote-p string)
         (completion-basic-try-completion string table pred point)))

  (defun basic-remote-all-completions (string table pred point)
    (and (vertico--remote-p string)
         (completion-basic-all-completions string table pred point)))

   (setq completion-styles '(orderless basic)
         completion-category-overrides '((file (styles basic partial-completion)))))

marginalia

(use-package marginalia
  :demand t
  :config (marginalia-mode +1))

embark

(use-package embark
  :demand t
  :bind
  (("C-." . embark-act)         ;; pick some comfortable binding
   ("C-;" . embark-dwim)
   ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
  :init
  ;; Optionally replace the key help with a completing-read interface
  (setq prefix-help-command #'embark-prefix-help-command)
  (setq embark-prompter #'embark-completing-read-prompter)

  ;; Show the Embark target at point via Eldoc.  You may adjust the Eldoc
  ;; strategy, if you want to see the documentation from multiple providers.
  (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
  ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)

  :config
  (progn
    (setq embark-mixed-indicator-delay 1.0)
    (defmacro imalison:embark-projectile-act-for-file (file &rest forms)
      `(let ((default-directory (projectile-project-root ,file)))
        (imalison:projectile-do-in-project default-directory ,@forms)))

    (defmacro imalison:build-embark-projectile-for-file (command)
      `(defun ,(intern (concat "imalison:embark-projectile-file-" (symbol-name command))) (filepath)
         (imalison:embark-projectile-act-for-file filepath (,command))))

    (imalison:build-embark-projectile-for-file term-projectile-switch)
    (imalison:build-embark-projectile-for-file magit-status)
    (imalison:build-embark-projectile-for-file consult-ripgrep)

    (setq embark-prompter #'embark-keymap-prompter)

    (define-key embark-general-map (kbd "t")
                #'imalison:embark-projectile-file-term-projectile-switch)

    (define-key embark-general-map (kbd "m")
                #'imalison:embark-projectile-file-magit-status)

    (define-key embark-general-map (kbd "g")
                #'imalison:embark-projectile-file-magit-status)

    (define-key embark-general-map (kbd "s")
      #'imalison:embark-projectile-file-consult-ripgrep)

    (defvar-keymap imalison:projectile-embark-map
      :doc "Keymap for actions on projectile projects"
      "m" #'imalison:embark-projectile-file-magit-status
      "t" #'imalison:embark-projectile-file-term-projectile-switch
      "s" #'imalison:embark-projectile-file-consult-ripgrep)))

(use-package embark-consult
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

consult

(use-package consult
  :commands consult-ripgrep
  :bind (("C-x b" . consult-buffer)
         ("C-x C-i" . consult-imenu))
  :config
  (progn
    (setq consult-project-function 'projectile-project-root)))

company

(use-package company
  :commands company-mode imalison:company
  :bind (("C-\\" . imalison:company))
  :config
  (progn
    (emit-prefix-selector imalison:company
      company-complete
      company-yasnippet)

    (setq company-idle-delay .25)
    (use-package company-prescient
      :demand t
      :config (company-prescient-mode +1))
    (global-company-mode)
    (diminish 'company-mode))
  :init
  (add-hook 'prog-mode-hook (lambda () (company-mode t))))

company-flx

(use-package company-flx
  :disabled t
  :after company
  :config
  (company-flx-mode +1))

auto-complete

I don't use auto-complete at all, so I have set up a hook to automatically disable it whenever it is enabled to avoid creating conflicting popups when company is activated.

(use-package auto-complete
  :defer t
  :preface
  (progn
    (defun imalison:auto-complete-hook ()
      (debug)
      (warn "auto-complete-mode was activated, but is being automatically disabled.")
      (let ((auto-complete-mode-hook nil))
        (auto-complete-mode -1))))
  :config
  (progn
    (add-hook 'auto-complete-mode-hook 'imalison:auto-complete-hook)))

Text Manipulation

smartparens

(use-package smartparens
  :demand t
  :diminish smartparens-mode
  :bind (:map smartparens-mode-map
              ("H-z" . sp-kill-symbol)
              ("C-)" . sp-forward-slurp-sexp)
              ("C-}" . sp-forward-barf-sexp)
              ("C-(" . sp-backward-slurp-sexp)
              ("C-{" . sp-backward-barf-sexp))
  :config
  (progn
    (require 'smartparens-config)
    (smartparens-global-mode 1)
    (sp-use-smartparens-bindings)
    (sp-local-pair 'org-mode "~" "~")
    (unbind-key "C-M-<backspace>" smartparens-mode-map)
    (unbind-key "C-<backspace>" smartparens-mode-map)
    (unbind-key "M-<backspace>" smartparens-mode-map)
    (unbind-key "M-<delete>" smartparens-mode-map)))

multiple-cursors

(use-package multiple-cursors
  :config
  (progn
    (use-package phi-search-mc
      :demand t
      :config
      (phi-search-mc/setup-keys))
    (use-package mc-extras
      :demand t
      :config
      (define-key mc/keymap (kbd "C-. =") 'mc/compare-chars))
    (defhydra imalison:multiple-cursors-hydra (:hint nil)
      "multiple-cursors"
      ("l" mc/edit-lines "Edit lines" :exit t)
      ("a" mc/mark-all-like-this "Mark all" :exit t)
      ("n" mc/mark-next-like-this "Mark next")
      ("N" mc/skip-to-next-like-this "Skip to next")
      ("M-n" mc/unmark-next-like-this "Unmark next")
      ("p" mc/mark-previous-like-this "Mark previous")
      ("P" mc/skip-to-previous-like-this "Skip to previous")
      ("M-p" mc/unmark-previous-like-this "Unmark previous")
      ("r" mc/mark-all-in-region-regexp "Mark by regex in region":exit t)
      ("q" nil "Quit")))
  :bind
  (("C-c m" . imalison:multiple-cursors-hydra/body)
   :map mc/keymap
   ("C-s" . phi-search)))

expand-region

(use-package expand-region
  :commands er/expand-region
  :config (setq expand-region-contract-fast-key "j")
  :bind (("C-c k" . er/expand-region)))

multi-line

(use-package multi-line
  ;; Demand multi-line to avoid failure to load mode specific strategies
  :demand t
  :bind ("C-c d" . multi-line)
  :config
  (progn
    (multi-line-defhook java
      (make-instance
       'multi-line-strategy
       :respace (multi-line-respacers-with-single-line
                 (list (multi-line-clearing-reindenting-respacer
                        multi-line-skip-first-and-last-respacer)))))
    ;; This only works for array respacing
    (multi-line-defhook sh
      (make-instance
       'multi-line-strategy
       :find multi-line-lisp-find-strategy
       :respace (multi-line-default-respacers
                 (multi-line-clearing-reindenting-respacer
                  multi-line-always-newline-respacer)
                 (multi-line-clearing-reindenting-respacer
                  multi-line-force-first-and-last-respacer))))))

comment-dwim-2

(use-package comment-dwim-2
  :bind ("M-;" . comment-dwim-2))

unfill

(use-package unfill
  :bind ("M-q" . unfill-toggle))

format-all

(use-package format-all
  :disabled t
  :commands (format-all-buffer format-all-mode))

apheleia

(use-package apheleia
  :config
  (progn
    (setq-default apheleia-inhibit t)
    (apheleia-global-mode +1)))

cliphist

(use-package cliphist
  :disabled t
  :config (setq cliphist-use-ivy t))

electric-operator-mode

(use-package electric-operator
  :commands electric-operator-mode)

string-inflection

(use-package string-inflection
  :commands string-inflection-all-cycle
  :bind ("C-c l" . string-inflection-all-cycle))

yasnippet

(use-package yasnippet
  :defer 5
  :commands (yas-global-mode)
  :config
  (progn
    (yas-global-mode)
    (diminish 'yas-minor-mode)
    (add-hook 'term-mode-hook (lambda() (yas-minor-mode -1)))
    (setq yas-prompt-functions
          (cons 'yas-ido-prompt
                (cl-delete 'yas-ido-prompt yas-prompt-functions)))))

(use-package yasnippet-snippets
  :after yasnippet
  :demand t)

align

(use-package align
  :bind ("C-c C-a" . imalison:align-regexp-hydra/body)
  :config
  (progn
    (require 'hydra)
    (defun imalison:complex-align-regexp ()
      (interactive)
      (let ((current-prefix-arg t))
        (call-interactively 'align-regexp)))
    (defun imalison:align-whitespace ()
      (interactive)
      (align-regexp (region-beginning) (region-end)
                    "\\(\\s-*\\)\\s-" 1 1 t))
    (defun imalison:align-to-character (character)
      (interactive
       (list (read-string "Character to align to " "=")))
      (align-regexp
       (region-beginning) (region-end)
       (format "\\(\\s-*\\)%s" character) 1 1 nil))
    (defhydra imalison:align-regexp-hydra ()
      "align-regexp"
      ("r" imalison:complex-align-regexp)
      ("SPC" imalison:align-whitespace)
      ("c" imalison:align-to-character))))

flycheck

  (use-package flycheck
	:commands flycheck-mode
	:init (add-hook 'prog-mode-hook 'flycheck-mode)
	:config
	(progn
	  (use-package flycheck-package
		:disabled t
		:config (flycheck-package-setup))

	  (use-package flycheck-cask
		:after flycheck
		:config
		(add-hook 'flycheck-mode-hook #'flycheck-cask-setup))

	  (add-to-list 'flycheck-emacs-lisp-checkdoc-variables
				   'sentence-end-double-space)
	  (setq-default flycheck-disabled-checkers '(rust-cargo rust rust-clippy))
	  (setq flycheck-display-errors-delay 10000)
	  (global-flycheck-mode))
	:diminish flycheck-mode)

straight

(use-package straight
  :config
  (setq straight-vc-git-auto-fast-forward t))

auth-source

(use-package auth-source
  :straight nil
  :config
  (progn
    (setq auth-sources '("~/.authinfo.gpg"))))

Major Modes

Programming

<<programminglanguages>>

python

(defvar imalison:use-lsp-python t)

(use-package python
  :commands python-mode
  :mode ("\\.py\\'" . python-mode)
  :preface
  (defun imalison:python-mode ()
    (add-hook 'hack-local-variables-hook
              (lambda ()
                (when imalison:use-lsp-python
                  (lsp-deferred))) nil t))
  :config
  (progn
    (use-package apheleia
      :config
      (progn
        (setf (alist-get 'isort apheleia-formatters)
              '("isort" "--stdout" "-"))
        (setf (alist-get 'autoflake apheleia-formatters)
              '("autoflake" "--stdout" "--remove-all-unused-imports"
                "--ignore-init-module-imports" "-"))
        (setf (alist-get 'python-mode apheleia-mode-alist)
              '(autoflake isort black))
        (setf (alist-get 'python-ts-mode apheleia-mode-alist)
              (alist-get 'python-mode apheleia-mode-alist))))
    (use-package lsp-mode
      :config
      (progn
        (require 'lsp-pylsp)
        (setq lsp-pylsp-plugins-pydocstyle-enabled nil)))

	(use-package sphinx-doc)
	(unbind-key "C-j" python-mode-map)
	(add-hook 'python-mode-hook #'imalison:python-mode)))

go

(use-package go-mode
  :mode (("\\.go\\'" . go-mode))
  :preface
  (progn
    (defun imalison:glide-novendor ()
      (projectile-with-default-dir (projectile-project-root)
        (shell-command-to-string "glide novendor")))

    (defun imalison:go-mode-create-imenu-index ()
      "Create and return an imenu index alist. Unlike the default
  alist created by go-mode, this method creates an alist where
  items follow a style that is consistent with other prog-modes."
      (let* ((patterns '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)))
             (type-index (imenu--generic-function patterns))
             (func-index))
        (save-excursion
          (goto-char (point-min))
          (while (re-search-forward go-func-meth-regexp (point-max) t)
            (let* ((var (match-string-no-properties 1))
                   (func (match-string-no-properties 2))
                   (name (if var
                             (concat (substring var 0 -1) "." func)
                           func))
                   (beg (match-beginning 0))
                   (marker (copy-marker beg))
                   (item (cons name marker)))
              (setq func-index (cons item func-index)))))
        (nconc type-index (list (cons "func" func-index)))))

    (defun imalison:go-workspace-path ()
      (file-relative-name (projectile-project-root)
                          (concat (file-name-as-directory
                                   (imalison:get-go-path)) "src")))

    (defun imalison:install-current-go-project ()
      (interactive)
      (start-process
       "go install" "go install log" "go" "install"
       (concat (file-name-as-directory (imalison:go-workspace-path)) "...")))

    (defun imalison:get-go-path ()
      (let ((environment-go-path (getenv "GOPATH")))
        (if environment-go-path
            (file-name-as-directory (car (s-split ":" environment-go-path)))
          "~/go")))

    (defmacro imalison:get-go-src (&rest paths)
      `(imalison:join-paths (imalison:get-go-path) "src" ,@paths))

    (imalison:let-advise-around imalison:advise-normal-go-command
      (go-command "go"))

    (defun imalison:go-mode-hook ()
      (go-eldoc-setup)
      (set (make-local-variable 'company-backends) '(company-go))
      (make-local-variable 'projectile-globally-ignored-files)
      (add-hook 'after-save-hook 'imalison:install-current-go-project nil
                'yes-do-local)
      (add-to-list 'projectile-globally-ignored-files
                   "vendor")))
  :config
  (progn
    (use-package gotest
      :demand t
      :bind (:map go-mode-map
                  ("C-c t" . imalison:gotest))
      :preface
      (progn
        (emit-prefix-selector imalison:gotest
          go-test-current-test
          go-test-current-file)

        (defun imalison:add-expected-test-name-for-suite (suite-name test-name)
          (if (> (length suite-name) 0)
              (concat " -run Test" suite-name)
            "")))
      :config
      (progn
        (setq go-test-verbose t
              go-test-additional-arguments-function
              'imalison:add-expected-test-name-for-suite)))
    (use-package company-go
      :config (setq company-go-show-annotation t))

    (advice-add 'go-import-add :around 'imalison:advise-normal-go-command)

    (setq gofmt-command "goimports")

    (add-hook 'go-mode-hook 'imalison:go-mode-hook)
    (add-hook 'before-save-hook 'gofmt-before-save t)))
Show diffs of testify output
(defvar imalison:testify-ediff-buffers nil)
(defun imalison:purge-ediff-buffers (&rest args)
  (cl-loop for buffer in imalison:testify-ediff-buffers
           do (kill-buffer buffer))
  (setq imalison:testify-ediff-buffers nil))

(add-hook 'ediff-cleanup-hook 'imalison:purge-ediff-buffers)

(defun imalison:go-testify-show-ediff ()
  (interactive)
  (let ((buffer (get-buffer-create "*Testify JSON*"))
        json-result)
    (shell-command-on-region (point-min) (point-max) "parse_go_testify_for_emacs.py" buffer)
    (with-current-buffer buffer
      (goto-char (point-min))
      (setq json-result (json-read)))
    (let ((actual-buffer (generate-new-buffer "*Testify Actual*"))
          (expected-buffer (generate-new-buffer "*Testify Expected*")))
      (add-to-list 'imalison:testify-ediff-buffers actual-buffer)
      (add-to-list 'imalison:testify-ediff-buffers expected-buffer)
      (with-current-buffer actual-buffer
        (insert (cdr (assoc 'actual json-result)))
        (with-current-buffer expected-buffer
          (insert (cdr (assoc 'expected json-result)))
          (ediff-buffers actual-buffer expected-buffer))))))

(defun imalison:go-testify-show-icdiff ()
  (interactive)
  (let ((buffer (get-buffer-create "*Testify Comparison*")))
    (shell-command-on-region (point-min) (point-max) "parse_go_testify_not_equal.py" buffer)
    (with-current-buffer buffer
      (fundamental-ansi-mode))
    (switch-to-buffer buffer)))

emacs-lisp

elisp-slime-nav
(use-package elisp-slime-nav
  :commands elisp-slime-nav-mode
  :config
  (diminish 'elisp-slime-nav-mode)
  :preface
  (emit-prefix-selector imalison:elisp-slime-nav
    elisp-slime-nav-find-elisp-thing-at-point
    elisp-slime-nav-describe-elisp-thing-at-point)
  :bind (:map elisp-slime-nav-mode-map
              ("M-." . imalison:elisp-slime-nav)))
Make find-function use display-buffer

For some reason find-function doesn't allow to display-buffer to do what it's supposed to do, but instead uses its own collection of functions to control where the definition is popped up. This fixes that.

(use-package find-func
  :preface
  (progn
    (defun imalison:find-function-display-buffer (function)
      (interactive (find-function-read))
      (find-function-do-it function nil 'display-buffer)))
  :config
  (advice-add 'find-function :override 'imalison:find-function-display-buffer))

(defun imalison:find-function-display-buffer (function)
  (interactive (find-function-read))
  (find-function-do-it function nil 'pop-to-buffer))
macrostep

Macrostep is an indespensible tool for writing emacs lisp macros. It lets you see pretty printed versions of the result of macro evaluation as the macro is evaluated

(use-package macrostep
  :bind (:map lisp-mode-shared-map
              ("C-c e" . macrostep-expand)))
emr
(use-package emr
  :bind ("M-RET" . emr-show-refactor-menu)
  :config
  (progn
    (add-hook 'prog-mode-hook 'emr-initialize)))
Editing configuration

Reduce indentation for some functions

(put 'use-package 'lisp-indent-function 1)
Checkdoc
(setq checkdoc-force-docstrings-flag nil
      checkdoc-arguments-in-order-flag nil)
edebug
(use-package edebug
  :defer t
  :config
  (progn (setq edebug-trace t)))
overseer
(use-package overseer
  :defer t)
Misc
(defun imenu-elisp-sections ()
  (setq imenu-prev-index-position-function nil)
  (setq imenu-space-replacement nil)
  (add-to-list 'imenu-generic-expression
               `("Package"
                 ,"(use-package \\(.+\\)$" 1))
  (add-to-list 'imenu-generic-expression
               `("Section"
                 ,(concat ";\\{1,4\\} =\\{10,80\\}\n;\\{1,4\\} \\{10,80\\}"
                          "\\(.+\\)$") 1) t))

(defun imalison:maybe-remove-flycheck-checkdoc-checker ()
  (when (s-starts-with? "*" (buffer-name))
    (flycheck-disable-checker 'emacs-lisp-checkdoc)))

(add-hook 'emacs-lisp-mode-hook 'imenu-elisp-sections)
(add-hook 'emacs-lisp-mode-hook (lambda ()
                                  (setq indent-tabs-mode nil)
                                  (setq show-trailing-whitespace t)))
(add-hook 'flycheck-mode-hook 'imalison:maybe-remove-flycheck-checkdoc-checker)
eros
(use-package eros
  :commands (eros-mode)
  :config
  (progn
    (advice-add 'eval-defun :around 'eros-around-eval-defun)
    (advice-add 'eval-last-sexp :around 'eros-around-eval-last-sexp))
  :preface
  (progn
    (defvar eros-mode nil)

    (defun eros-around-eval-last-sexp (fn &rest args)
      (let ((result (apply fn args)))
        (when eros-mode
          (eros--eval-overlay result (point)))
        result))

    (defun eros-around-eval-defun (fn &rest args)
      (let ((result (apply fn args)))
        (when eros-mode
          (eros--eval-overlay
           result
           (save-excursion
             (end-of-defun)
             (point))))
        result))

    (add-hook 'emacs-lisp-mode-hook 'eros-mode)))
Reevalute defvars when running eval-last-sexp

We noflet elisppreceding-sexp to munge defvars into sexps only for eval-last-sexp.

(defun imalison:maybe-setq-instead (fn &rest args)
  (noflet ((elisp--preceding-sexp (&rest preceding-args)
                                  (let* ((preceding-sexp (apply this-fn preceding-args)))
                                    (if (and (listp preceding-sexp) (equal (car preceding-sexp) 'defvar))
                                        `(setq ,@(cdr preceding-sexp))
                                      preceding-sexp))))
    (apply fn args)))

(advice-add 'eval-last-sexp :around 'imalison:maybe-setq-instead)
Init hook
(defvar imalison:check-parens nil)

(defun imalison:maybe-check-parens ()
  (if imalison:check-parens
      (check-parens)))

(defun imalison:emacs-lisp-hook ()
  (elisp-slime-nav-mode t)
  (add-hook 'write-file-functions 'imalison:maybe-check-parens nil t))

(add-hook 'emacs-lisp-mode-hook 'imalison:emacs-lisp-hook)
Keybinds
(emit-compose
 imalison:copy-eval-last-sexp kill-new prin1-to-string eval-last-sexp)

(emit-prefix-selector imalison:eval-last-sexp
  eval-region-or-last-sexp
  imalison:copy-eval-last-sexp)

(define-key lisp-mode-shared-map (kbd "C-c C-c") 'eval-defun)
(define-key lisp-mode-shared-map (kbd "C-x C-e") 'imalison:eval-last-sexp)
(unbind-key "C-j" lisp-interaction-mode-map)

nix

(use-package nix-mode
  :hook (nix-mode . lsp-deferred)
  :preface
  (progn
    (imalison:add-blacklist-to-major nix-mode)
    (setq nix-mode-blacklist '("all-packages.nix" "hackage-packages.nix")))
  (defun imalison:goto-nixpkgs ()
    (interactive)
    (find-file (s-trim
     (shell-command-to-string "nix eval -I \"$NIX_PATH\" --expr '<nixpkgs>' --impure"))))
  :config
  (progn
    (use-package apheleia
      :demand t
      :config
      (progn
        (add-to-list 'apheleia-formatters '(alejandra . ("alejandra")))
        (setf (alist-get 'nix-mode apheleia-mode-alist)
              '(alejandra))))
    (setq nix-indent-function 'nix-indent-line)))

(use-package lsp-nix
  :straight nil
  :after (lsp-mode)
  :config
  (progn
    (setq lsp-nix-nil-formatter ["nixpkgs-fmt"])))
(use-package nix-update
  :after nix)

clojure

The following is taken from spacemacs. It adds fancification to a clojure mode.

(defun imalison:clojure-fancify-symbols (mode)
  "Pretty symbols for Clojure's anonymous functions and sets,
   like (λ [a] (+ a 5)), ƒ(+ % 5), and ∈{2 4 6}."
  (font-lock-add-keywords mode
    `(("(\\(fn\\)[\n\[[:space:]]"
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) "λ"))))
      ("(\\(partial\\)[\[[:space:]]"
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) "Ƥ"))))
      ("(\\(comp\\)[\n\[[:space:]]"
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) "∘"))))
      ("\\(#\\)("
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) "ƒ"))))
      ("\\(#\\){"
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) "∈")))))))
  (use-package clojure-mode
	:commands clojure-mode
	:preface
	(progn
	  (add-to-list 'magic-mode-alist '("#!.*boot\\s-*$" . clojure-mode))
	  (add-to-list 'auto-mode-alist '("\\.boot\\'" . clojure-mode))

	  (defun imalison:clojure-mode-hook ()
		;; (cljr-add-keybindings-with-prefix "C-c C-m")
		;; This is disabled because seq-25 can't be loaded
		;; (clj-refactor-mode 1)
		;;for adding require/use/import statements
		(yas-minor-mode 1))

	  (defvar imalison:clojure-level-1-symobls
		'(describe it)))
	:config
	(progn
	  (cl-loop for symbol in imalison:clojure-level-1-symobls
			   do (put-clojure-indent symbol 1))
	  (add-hook 'clojure-mode-hook 'imalison:clojure-mode-hook)
	  (dolist (m '(clojure-mode clojurescript-mode clojurec-mode clojurex-mode))
		(imalison:clojure-fancify-symbols m))))
cider
(use-package cider
  :commands (cider-jack-in)
  :config
  (progn
    (setq cider-stacktrace-default-filters '(tooling dup)
          cider-repl-pop-to-buffer-on-connect nil
          cider-prompt-save-file-on-load nil
          cider-repl-use-clojure-font-lock t
          cider-prompt-for-symbol nil
          cider-preferred-build-tool "boot")
    (add-hook 'clojure-mode-hook 'cider-mode)))
clj-refactor
(use-package clj-refactor
  :commands clj-refactor-mode)

scala

(use-package scala-mode
  :mode (("\\.scala\\'" . scala-mode)
         ("\\.sc\\'" . scala-mode))
  :config
  (progn
    (use-package ensime
      :demand t
      :bind (:map ensime-mode-map
                  ("M-," . ensime-pop-find-definition-stack))
      :commands ensime-mode
      :config
      (progn
        (setq ensime-startup-snapshot-notification nil
              ensime-startup-notification nil)))
    (add-hook 'scala-mode-hook 'ensime-scala-mode-hook)
    (setq scala-indent:align-parameters t)))

javascript

(defun tape-onlyify ()
  (interactive)
  (save-excursion
    (move-end-of-line nil)
    (re-search-backward "^test")
    (forward-sexp)
    (if (looking-at ".only") (progn (zap-to-char 1 (string-to-char "(")) (insert "("))
      (insert ".only"))))

(use-package js2-mode
  :commands (js2-mode)
  :mode "\\.js\\'"
  :preface
  (progn
    (defvar-setq imalison:identifier-count 0)
    (defun imalison:console-log-unique ()
      (interactive)
      (let* ((identifier-string (int-to-string imalison:identifier-count))
             (uuid (imalison:uuid)))
        (insert (format "console.log('%s//////////%s//////////');" identifier-string uuid))
        (setq imalison:identifier-count (+ imalison:identifier-count 1))))
    (defun imalison:js2-mode-hook ()
      ;; Sensible defaults
      (setq js2-bounce-indent-p nil
            js2-indent-level 4
            js2-basic-offset 4
            js2-highlight-level 3
            js2-include-node-externs t
            js2-mode-show-parse-errors nil
            js2-mode-show-strict-warnings nil
            indent-tabs-mode nil
            js2-indent-switch-body t)
      (setq imenu-create-index-function
            (lambda ()
              (imalison:flatten-imenu-index
               (js2-mode-create-imenu-index))))))
  :init
  (progn
    (add-hook 'js2-mode-hook 'imalison:js2-mode-hook)
    (add-hook 'js2-mode-hook 'js2-imenu-extras-mode)))

(use-package js2-refactor
  :after js2-mode
  :config
  (progn
    (js2r-add-keybindings-with-prefix "C-c C-m")
    (add-hook 'js2-mode-hook #'js2-refactor-mode)))

typescript

(use-package typescript-mode
  :mode ("\\.ts\\'" "\\.tsx\\'")
  :config
  (progn
    (add-hook 'typescript-mode-hook 'lsp-deferred)))

rust

(use-package rust-mode
  :mode (("\\.rs\\'" . rust-mode))
  :preface
  (progn
    (defvar imalison:use-lsp-rust t)
    (setq lsp-rust-analyzer-diagnostics-disabled ["unresolved-proc-macro"])
    (defun imalison:rust-mode-hook ()
      (when imalison:use-lsp-rust
        (lsp-deferred))
      (add-to-list 'flycheck-disabled-checkers 'rust-cargo)
      (add-to-list 'flycheck-disabled-checkers 'rust)
      (add-to-list 'flycheck-disabled-checkers 'rust-clippy)
      (setq rust-format-on-save t)))
  :config
  (progn
    (add-hook 'rust-mode-hook 'imalison:rust-mode-hook)))

haskell

(defvar imalison:use-lsp-haskell nil)
(defvar imalison:dfinity nil)
(use-package haskell-mode
  :commands haskell-mode
  :bind
  (:map haskell-mode-map
        ("C-c h" . haskell-hoogle))
  :preface
  (progn
    (defun imalison:haskell-mode ()
      (turn-on-haskell-indent)
      (when imalison:dfinity
        (setq
         haskell-mode-stylish-haskell-path "brittany"
         haskell-mode-stylish-haskell-args '("-")))
      (message "imalison:use-lsp-haskell: %s" imalison:use-lsp-haskell)
      (when t
        (require 'lsp)
        (use-package lsp-haskell
          :demand t)
        (lsp-deferred)))
    (add-hook 'haskell-mode-hook 'imalison:haskell-mode))
  :config
  (progn
    (setq
     haskell-hoogle-command "hoogle"
     haskell-interactive-popup-errors nil
     haskell-process-auto-import-loaded-modules nil
     haskell-process-show-overlays nil
     haskell-stylish-on-save nil
     haskell-tags-on-save nil
     haskell-indent-offset 2)

	(require 'flycheck)
    (cl-loop for checker in '(haskell-stack-ghc haskell-ghc haskell-hlint)
             do (delq checker flycheck-checkers))))
hindent
(use-package hindent
  :disabled t
  :after haskell-mode
  :bind (:map hindent-mode-map
              ("C-c d" . hindent-reformat-decl))
  :config
  (progn
    (setq hindent-style nil)
    (add-hook 'haskell-mode-hook 'hindent-mode)))
company-ghc
(use-package company-ghc
  :disabled t
  :demand t
  :config
  (add-to-list 'company-backends '(company-ghc :with company-dabbrev-code)))

purescript

(use-package purescript-mode
  :defer t)

(use-package purty
  :commands purty-on-save-mode
  :straight (purty :repo "joneshf/purty"
                   :type git
                   :host gitlab
                   :files ("purty.el")))

(use-package psc-ide
  :after purescript-mode
  :config
  (progn
    (defun imalison:purescript-mode-hook ()
      (psc-ide-mode +1)
	  (purty-on-save-mode +1)
      (turn-on-purescript-indentation))
    (add-hook 'purescript-mode-hook 'imalison:purescript-mode-hook)))

kotlin

(use-package kotlin-mode
  :defer t
  :mode ("\\.kt\\'"))

groovy

(use-package groovy-mode)

vala

(use-package vala-mode
  :disabled t)

lua

(use-package lua-mode
  :defer t)

C/C++

(use-package cc-mode)

C

(use-package cmm-mode
  :defer t)

C#

(use-package csharp-mode
  :mode "\\.cs\\'")

racket

(use-package racket-mode
  :mode "\\.rkt\\'")

ein

(use-package ein
  :mode "\\.ipynb\\'")

Data/Config/Protocol

thrift

(use-package thrift
  :commands thrift-mode
  :mode (("\\.thrift\\'" . thrift-mode)))

protobuf

(use-package protobuf-mode
  :defer t)

graphql

(use-package graphql-mode)

json-mode

(use-package json-mode
  :mode "\\.json\\'"
  :init
  (add-hook 'json-mode-hook
            (lambda ()
              (setq indent-tabs-mode nil)
              (setq js-indent-level 4))))

yaml-mode

(use-package yaml-mode
  :mode (("\\.yaml\\'" . yaml-mode)
         ("\\.yml\\'" . yaml-mode)))

just-mode

(use-package just-mode)

es-mode

(use-package es-mode
  :defer t)

docker

(use-package dockerfile-mode
  :mode ("Dockerfile\\'" . dockerfile-mode))

dhall

(use-package dhall-mode
  :mode "\\.dhall\\'")

toml

(use-package toml-mode
  :mode (("\\.toml\\'" . toml-mode)))

terraform

(use-package terraform-mode
  :mode (("\\.tf\\'" . terraform-mode)))

Document

org

My org-mode configuration now lives in its own file org-config.org.

(org-babel-load-file
 (concat (file-name-directory load-file-name) "org-config.org"))

TeX

(use-package tex
  :straight (auctex :host github :repo "raxod502/auctex"
                    :branch "fork/1"
                    :files (:defaults (:exclude "doc/*.texi")))
  :commands TeX-mode
  :defer t
  :preface
  (progn
    (defun imalison:TeX-mode-hook ()
      (turn-on-reftex)
      (TeX-source-correlate-mode +1)
      (TeX-PDF-mode +1)))
  :config
  (progn
    (unbind-key "C-j" TeX-mode-map)
    (TeX-global-PDF-mode)
    (setq TeX-auto-save t
          TeX-parse-self t
          TeX-save-query nil
          TeX-PDF-mode t)
    (TeX-global-PDF-mode t)
    (add-hook 'TeX-mode-hook 'imalison:TeX-mode-hook)))
latex
(use-package latex
 :straight auctex
 :after tex
 :config
 (progn
   (unbind-key "C-j" LaTeX-mode-map)))
auctex-latexmk
(use-package auctex-latexmk
  :disabled t
  :config
  (progn
    (setq auctex-latexmk-inherit-TeX-PDF-mode t)
    (auctex-latexmk-setup)))
company-auctex
(use-package company-auctex
  :after tex
  :defer t
  :config
  (company-auctex-init))

markdown-mode

(use-package markdown-mode
  :defer t
  :mode ("\\.md\\'")
  :init
  (progn
    (add-hook 'markdown-mode-hook 'imalison:disable-linum-mode)))

mermaid-mode

(use-package mermaid-mode
  :mode (("\\.mermaid\\'" . mermaid-mode)
         ("\\.mmd\\'" . mermaid-mode)))

plantuml-mode

(use-package plantuml-mode
  :commands plantuml-mode
  :mode ("\\.puml\\'" "\\.plantuml\\'")
  :preface
  (progn
    (add-to-list
     'org-src-lang-modes '("plantuml" . plantuml))))

wsd-mode

(use-package wsd-mode
  :commands (wsd-mode))

adoc-mode

(use-package adoc-mode
  :mode "\\.adoc\\'")

Utility

restclient

(use-package restclient
  :mode (("\\.restclient\\'" . restclient-mode))
  :config
  (progn
    (use-package company-restclient)))

jq-mode

(use-package jq-mode
  :mode "\\.jq\\'")

systemd

(use-package systemd
  :commands systemd-mode
  :preface
  (ignore
   (add-to-list 'auto-mode-alist
                '("\\.service\\'" . systemd-mode))
   (add-to-list 'auto-mode-alist
                '("\\.socket\\'" . systemd-mode))))

Source Control

magit

(use-package magit
  :commands magit-status
  :bind (("C-x g" . imalison:magit-status))
  :preface
  (progn
    (emit-let-around imalison:magit-status-traditional
                     magit-status
                     (magit-display-buffer-function
                      'magit-display-buffer-traditional))
    (emit-prefix-selector imalison:magit-status
      magit-status
      imalison:magit-status-traditional)
    (defun imalison:after-magit-visit-file (&rest args)
      (when (derived-mode-p 'org-mode)
        (org-show-context 'magit-goto))))
  :config
  (progn
    (when frame-mode
      (setq magit-commit-show-diff t))
    (unbind-key "C-j" magit-status-mode-map)
    (unbind-key "C-j" magit-hunk-section-map)
    (unbind-key "C-j" magit-file-section-map)
    (unbind-key "C-j" magit-diff-section-base-map)
    (setq magit-last-seen-setup-instructions "1.4.0"
          magit-display-buffer-function
          'magit-display-buffer-same-window-except-diff-v1)
    (magit-auto-revert-mode)

    (add-to-list 'org-show-context-detail '(magit-goto . lineage))
    (advice-add 'magit-diff-visit-file :after 'imalison:after-magit-visit-file)
    (add-hook 'magit-popup-mode-hook 'imalison:disable-show-trailing-whitespace)))

forge

(use-package forge
  :disabled t
  :after magit)

vc

(use-package vc
  :config
  (progn
    (defun vc-find-revision-of-file (revision)
      (interactive
       (list (vc-read-revision
              "Revision to visit: "
              (list (imalison:join-paths (magit-toplevel) (car (magit-list-files)))))))
      (let* ((file (completing-read "Select file to visit: " (magit-revision-files revision)))
             (full-file (imalison:join-paths (magit-toplevel) file)))
        (switch-to-buffer (vc-find-revision full-file revision))))))

git-link

(use-package git-link
  :defer t
  :config
  (progn
    (setq git-link-use-commit t)
    (defun git-link-commit-gitea (hostname dirname commit)
      (format "https://%s/%s/commit/%s"
	          hostname
	          dirname
	          commit))
    (defun git-link-gitea (hostname dirname filename branch commit start end)
      (format "https://%s/%s/src/commit/%s/%s#L%s"
              hostname
              dirname
              commit
              filename
              start))
    (add-to-list 'git-link-remote-alist '("dev.railbird.ai" git-link-gitea))
    (add-to-list 'git-link-commit-remote-alist '("dev.railbird.ai" git-link-gitea))))

magit-gitflow

(use-package magit-gitflow
  :diminish magit-gitflow-mode
  :after magit
  :init
  (progn
    (setq magit-gitflow-popup-key "C-k"))
  :config
  (progn
    (add-hook 'magit-mode-hook 'turn-on-magit-gitflow)))

git-gutter

(use-package git-gutter
  :disabled t
  :config
  (progn
    (global-git-gutter-mode +1)))

gitolite-clone

(use-package gitolite-clone
  :defer 4
  :preface
  (progn
    (defun gitolite-clone-force-refresh ()
      (interactive)
      (gitolite-clone-get-projects nil nil t))))

#+END_SRC

github

github-search

(use-package github-search
  :commands (github-search-clone-repo github-search-user-clone-repo)
  :preface
  (progn
    (defun imalison:get-appropriate-path-from-gh-repo-for-go (repo)
      (require 'go-mode)
      (imalison:get-go-src "github.com" (oref (oref repo :owner) :login)
                           (oref repo :name)))

    (defun imalison:get-projects-directory-target-from-repo (repo)
      (let ((prospective-path
             (if (equal (oref repo language) "Go")
                 (imalison:get-appropriate-path-from-gh-repo-for-go repo)
               (imalison:join-paths imalison:projects-directory (oref repo :name)))))
        (if (file-exists-p prospective-path)
            (funcall 'github-search-prompt-for-target-directory repo)
          prospective-path))))
  :config
  (progn
    (setq github-search-get-target-directory-for-repo-function
          'imalison:get-projects-directory-target-from-repo
          ;; See https://github.com/sigma/gh.el/issues/102
          gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")))

github-clone

(use-package github-clone
  :commands (github-clone-add-parent-remote
             github-clone-add-source-remote
             github-clone-fork-remote
             github-clone-add-existing-remote
             github-clone))

github-notifier

This is disabled because it was causing too many issues with my modeline and with excessive http requests to github.

(use-package github-notifier
  :disabled t
  :config
  (progn
    (advice-add 'github-notifier-update :around 'imalison:shut-up-around)
    (github-notifier-mode)))

github-browse-file

(use-package github-browse-file
  :commands github-browse-file)

magit-gh-pulls

(use-package magit-gh-pulls
  :disabled t
  :diminish magit-gh-pulls-mode
  :after magit
  :config
  (progn
    (add-hook 'magit-mode-hook 'turn-on-magit-gh-pulls)))

gist

(use-package gist
  :commands (gist-region gist-region-private gist-buffer gist-buffer-private
                         gist-region-or-buffer gist-region-or-buffer-private
                         gist-list-user gist-list gist-fetch gist-star
                         gist-unstar gist-list-starred gist-fork))

Programming

emr

emr (emacs refactor) provides support for refactoring in many programming languages

(use-package emr
  :bind (:map prog-mode-map
              ("M-RET" . emr-show-refactor-menu))
  :config (emr-initialize))

semantic

(use-package semantic
  :commands semantic-mode
  :disabled t
  :preface
  (progn
    (add-hook 'prog-mode-hook 'semantic-mode)))

language-server-protocol (lsp)

(defvar imalison:use-lsp nil)
(use-package lsp-mode
  :commands lsp
  :preface
  (defun imalison:lsp-deferred-when-enabled ()
    (add-hook 'hack-local-variables-hook
              (lambda ()
                (when imalison:use-lsp
                  (lsp-deferred))) nil t))
  (defun imalison:nix-develop-lsp-wrapper-function (argv)
    (append
     (append (list "nix" "develop" "-I" "." "--impure" "--command")
             argv)))
  :config
  (progn
    (setq lsp-prefer-flymake nil)
    (use-package lsp-ui
      :config
      (progn
        (add-hook 'prog-mode-hook 'imalison:lsp-deferred-when-enabled)
        (add-hook 'lsp-mode-hook 'lsp-ui-mode)
        (setq lsp-ui-doc-position 'bottom)))))

Utility

term

The main thing I do here is restore a bunch of keybindings that are eliminated in term-mode. This makes term-mode 1000% more useful (especially having M-x and C-y available).

(use-package term
  :demand t
  :preface
  (progn
    (defun imalison:avy-term (arg)
      (interactive "P")
      (term-line-mode)
      (imalison:avy arg))

    (defun imalison:term-yank (&optional string)
      (interactive)
      (process-send-string
       (get-buffer-process (current-buffer))
       (if string string (current-kill 0))))
    (defun imalison:term-yank-pop ()
      (interactive)
      (imalison:term-yank (read-from-kill-ring "Yank from kill-ring: "))))
  :bind
  (:map term-mode-map
        ("C-c C-k" . imalison:term-char-mode)
        :map term-raw-escape-map
        ("M-x" . execute-extended-command)
        ("C-j" . imalison:avy-term)
        :map term-raw-map
        ("M-x" . execute-extended-command)
        ("C-j" . imalison:avy-term)
        ("M-:" . eval-expression)
        ("C-y" . imalison:term-yank)
        ("M-y" . imalison:term-yank-pop))
  :config
  (progn

    (defun imalison:term-char-mode ()
      (interactive)
      (term-char-mode)
      (goto-char (point-max)))

    (define-key term-raw-map (kbd "C-h") help-map)
    (add-hook 'term-mode-hook 'imalison:disable-linum-mode)
    (add-hook 'term-mode-hook 'imalison:disable-yas-minor-mode)
    (add-hook 'term-mode-hook 'imalison:disable-show-trailing-whitespace)
    (setq term-buffer-maximum-size 0
          term-suppress-hard-newline t)))

vterm

;; (use-package vterm)

term-manager

(use-package term-manager
  :defer t
  :preface
  (progn
    (defun imalison:set-escape-char (&rest _args)
      (let (term-escape-char)
        (term-set-escape-char ?\C-x))))
  :config
  (progn
    (advice-add
     'term-manager-default-build-term :after 'imalison:set-escape-char)))

term-projectile

(use-package term-projectile
  :bind ("C-c 7" . imalison:term-hydra-global/body)
  :commands
  (term-projectile-backward
   term-projectile-create-new
   term-projectile-create-new-default-directory
   term-projectile-default-directory-backward
   term-projectile-default-directory-forward
   term-projectile-forward
   term-projectile-switch)
  :config
  (progn
    (emit-prefix-selector imalison:term
      term-projectile-forward
      term-projectile-create-new)

    (defvar imalison:term-hydra-original-default-directory)

    (defhydra imalison:term-hydra-default-directory
      (:body-pre
       (term-projectile-default-directory-forward-restored))
      "term - default-directory"
      ("s" term-projectile-switch-to "Switch to existing")
      ("f" term-projectile-default-directory-forward-restored "Forward for current directory terminals")
      ("b" term-projectile-default-directory-backward-restored "Backward for current directory terminals")
      ("c" term-projectile-default-directory-create-new-restored "Create new current directory terminal")
      ("d" term-projectile-default-directory-forward-restored "Switch/Create default directory terminal")
      ("g" imalison:term-hydra-global/body-restored "Switch/Create global terminal" :exit t)
      ("p" imalison:term-hydra-projectile/body-restored  "Switch/Create project terminal" :exit t))

    (defhydra imalison:term-hydra-projectile
      (:body-pre
       (progn
         (term-projectile-forward-restored)))
      "term - projectile"
      ("s" term-projectile-switch-to "Switch to existing")
      ("f" term-projectile-forward-restored "Forward for project terminals")
      ("b" term-projectile-backward-restored "Backward for project terminals")
      ("c" term-projectile-create-new-restored "Create new project terminal")
      ("d" imalison:term-hydra-default-directory/body-restored "Switch/Create default directory terminal" :exit t)
      ("g" imalison:term-hydra-global/body-restored "Switch/Create global terminal" :exit t)
      ("p" term-projectile-forward-restored "Switch/Create project terminal"))


    (defhydra imalison:term-hydra-global
      (:body-pre
       (progn (setq imalison:term-hydra-original-default-directory
                    default-directory)))
      "term - global"
      ("s" term-projectile-switch-to "Switch to existing")
      ("f" term-projectile-global-forward-restored "Forward for project terminals")
      ("b" term-projectile-global-backward-restored "Backward for project terminals")
      ("c" term-projectile-global-create-new-restored "Create new project terminal")
      ("d" imalison:term-hydra-default-directory/body-restored "Switch/Create default directory terminal" :exit t)
      ("g" term-projectile-global-forward-restored "Switch/Create global terminal")
      ("p" imalison:term-hydra-projectile/body-restored "Switch/Create project terminal" :exit t))

    (mapcar (lambda (term-projectile-function)
              (defalias (imalison:concat-symbols term-projectile-function '-restored)
                (lambda (&rest args)
                  (interactive)
                  (let ((default-directory imalison:term-hydra-original-default-directory))
                    (apply term-projectile-function args)))))
            '(term-projectile-default-directory-forward
              term-projectile-default-directory-backward
              term-projectile-default-directory-create-new
              term-projectile-forward
              term-projectile-backward
              term-projectile-create-new
              term-projectile-global-forward
              term-projectile-global-backward
              term-projectile-global-create-new
              imalison:term-hydra-global/body
              imalison:term-hydra-projectile/body
              imalison:term-hydra-default-directory/body))))

crux

crux-reopen-as-root-mode makes it so that any file owned by root will automatically be opened as the root user.

(use-package crux
  :defer 10
  :bind (("C-c C-s" . crux-sudo-edit)
         ("C-c C-r" . crux-eval-and-replace))
  :config
  (progn
    (crux-reopen-as-root-mode)))

kde-connect

(use-package kdeconnect)

aurel

(use-package aurel
  :defer t)

gptel

(use-package gptel
  :bind (("C-c g" . gptel-menu))
  :config
  (progn
    (add-to-list 'gptel-directives '(finish-code . "You are a large language model and a careful programmer. Provide code that completes what is provided and only code as output without any additional text, prompt or note."))
    (setq gptel-model "gpt-4")
    ))

Cooperation

togetherly

(use-package togetherly
  :defer t)

floobits

(use-package floobits
  :defer t)

rudel

(use-package rudel
  :disabled t)

Other

anzu

(use-package anzu
  :defer 10
  :config
  (progn
    (global-anzu-mode +1)

    (custom-set-variables
     '(anzu-mode-lighter "")
     '(anzu-deactivate-region t)
     '(anzu-search-threshold 1000)
     '(anzu-replace-threshold 50)
     '(anzu-replace-to-string-separator " => "))

    (define-key isearch-mode-map [remap isearch-query-replace]
      #'anzu-isearch-query-replace)
    (define-key isearch-mode-map [remap isearch-query-replace-regexp]
      #'anzu-isearch-query-replace-regexp)))

shell-history

I think that shell-history is causing projectile to be very slow so I have disabled it.

(use-package shell-history
  :demand t
  :disabled t)

iedit

I don't use iedit directly, but it is used by emr and I need to disable iedit-toggle-key-default or else a buffer pops up complaing that the key has been bound to something else

(use-package iedit
  :defer t)

tramp

(use-package tramp
  :straight nil
  :commands tramp
  :config
  (setq tramp-default-method "scp"))

narrow-indirect

(use-package narrow-indirect
  :init
  (progn
    (define-key ctl-x-4-map "nd" 'ni-narrow-to-defun-indirect-other-window)
    (define-key ctl-x-4-map "nn" 'ni-narrow-to-region-indirect-other-window)
    (define-key ctl-x-4-map "np" 'ni-narrow-to-page-indirect-other-window)))

editorconfig

I had to disable this mode because something that it does messes with coding settings and makes it so that I have to select the appropriate encoding every time I save gpg encrypted files.

(use-package editorconfig
  :config
  (progn
    (add-to-list 'editorconfig-exclude-modes '(org-mode))
    (editorconfig-mode 1)))

direnv

(use-package direnv
  :demand t
  :config
  (direnv-mode +1)
  :custom
  (direnv-always-show-summary nil))

dtrt-indent

(use-package dtrt-indent
  :diminish 'dtrt-indent-mode
  :commands 'dtrt-indent-mode
  :preface
  (progn
    (defun dtrt-indent-force-adapt ()
      (interactive)
      (setq dtrt-indent-original-indent nil)
      (dtrt-indent-adapt)))
  :init (add-hook 'prog-mode-hook 'dtrt-indent-mode)
  :config
  (progn
    (setq dtrt-indent-active-mode-line-info " [⟼]")))

indent-guide

(use-package indent-guide
  :disabled t
  :config
  (progn
    (indent-guide-global-mode -1)
    (setq indent-guide-delay 0.1)))

rainbow-delimiters

(use-package rainbow-delimiters
  :commands rainbow-delimiters-mode
  :init
  (progn
    (add-hook 'prog-mode-hook (lambda () (rainbow-delimiters-mode t)))))

undo-tree

Disabled because it makes it hard to redo things

(use-package undo-tree
  :disabled t
  :bind (("C--" . undo-redo)
         ("C-c u" . undo-tree-visualize)
         ("C-c r" . undo-tree-redo))
  :config
  (diminish 'undo-tree-mode)
  :init
  (progn
    ;;(setq undo-tree-visualizer-diff t) ;; This causes performance problems
    (setq undo-limit (expt 2 25)
          undo-strong-limit (expt 2 25))
    (global-undo-tree-mode)
    (setq undo-tree-visualizer-timestamps t)))

recentf

(use-package recentf
  :demand t
  :config
  (progn
    (setq recentf-max-saved-items 1000
          recentf-max-menu-items 1000)
    (advice-add 'recentf-cleanup :around 'imalison:shut-up-around)
    (recentf-mode +1)))

key-chord

I have currently disabled key-chord because it may cause typing lag.

(use-package key-chord
  :disabled t
  :preface
  (defun imalison:disable-keychord-around (function &rest args)
    (let ((key-chord-mode-was-enabled (bound-and-true-p key-chord-mode)))
      (when key-chord-mode-was-enabled (key-chord-mode -1))
      (condition-case err (progn (apply function args)) (error))
      (when key-chord-mode-was-enabled (key-chord-mode 1))))
  :config
  (progn
    (advice-add 'key-chord-mode :around 'imalison:shut-up-around)
    (key-chord-mode 1)
    (advice-add 'imalison:avy :around 'imalison:disable-keychord-around)
    (key-chord-define-global "tg" 'imalison:term-hydra/body)
    (key-chord-define-global "pj" 'imalison:projectile-find-file)
    (key-chord-define-global "p[" 'projectile-switch-project)
    (key-chord-define-global "fj" 'imalison:do-ag)
    (key-chord-define-global "jh" 'imalison:avy)))

nodejs-repl

(use-package nodejs-repl
  :commands nodejs-repl)

calc-mode

(use-package calc-mode
  :straight nil
  :commands calc
  :config
  (progn
    (setq calc-context-sensitive-enter t)))

jabber

Disabled because it has its own version of hexrgb which might cause conflicts

(use-package jabber
  :commands jabber-connect
  :disabled t
  :config
  (progn
    (setq jabber-alert-presence-hooks nil)
    (defun jabber-message-content-message (from buffer text)
      (when (or jabber-message-alert-same-buffer
                (not (memq (selected-window) (get-buffer-window-list buffer))))
        (if (jabber-muc-sender-p from)
            (format "%s: %s" (jabber-jid-resource from) text)
          (format "%s: %s" (jabber-jid-displayname from) text))))
    (setq jabber-alert-message-function 'jabber-message-content-message)))

htmlize

This package is needed to export org to html.

(use-package htmlize)

calfw

(use-package calfw-org
  :disabled t
  :demand t)

clocker

Not really sure what this is

(use-package clocker
  :disabled t)

deft

(use-package deft
  :commands deft
  :config
  (progn
    (setq deft-default-extension "org")
    (setq deft-extensions '("org"))
    (setq deft-use-filter-string-for-filename t)
    (setq deft-file-naming-rules '((noslash . "_")
                                   (nospace . "_")
                                   (case-fn . downcase)))
    (setq deft-directory (imalison:join-paths imalison:org-dir "notes"))))

epg

(use-package epg
  :after shut-up
  :config
  (shut-up
    (epa-file-enable)))

pinentry

(use-package pinentry
  :defer 5
  :config
  (pinentry-start))

twittering-mode

(use-package twittering-mode
  :commands twittering-mode)

matrix-client

(use-package matrix-client
  :disabled t ;; fails to load eieio on startup
  )

mu4e

;; (eval-when-compile
;;   (require 's)
;;   (defvar mu4e-elisp-directory
;;     (s-trim (shell-command-to-string "mu4e_directory"))))
(use-package mu4e
  :disabled t
  :load-path mu4e-elisp-directory
  :straight nil
  :commands (mu4e mu4e-view-message-with-msgid mu4e-update-index email)
  :bind ("C-c 0" . email)
  :config
  (progn
    (defun email (&optional arg)
      (interactive "P")
      (if (string-equal (persp-name persp-curr) "email")
          (progn (delete-other-windows) (mu4e))
        (progn
          (persp-switch "email")
          (when (or (not (mu4e-running-p)) arg)
            (delete-other-windows) (mu4e)))))
    ;; enable inline images
    (setq mu4e-view-show-images t)
    ;; show images
    (setq mu4e-show-images t)
    ;; Try to display html as text
    (setq mu4e-view-prefer-html nil)

    (setq mu4e-html2text-command "html2text -width 80 -nobs -utf8")

    ;; use imagemagick, if available
    (when (fboundp 'imagemagick-register-types)
      (imagemagick-register-types))
    (setq mail-user-agent 'mu4e-user-agent)
    (require 'org-mu4e)
    (setq mu4e-compose-complete-only-after nil)
    (setq mu4e-maildir "~/Mail")

    (setq mu4e-drafts-folder "/[Gmail].Drafts")
    (setq mu4e-sent-folder   "/[Gmail].Sent Mail")
    (setq mu4e-trash-folder  "/[Gmail].Trash")

    (setq mu4e-sent-messages-behavior 'delete)
    (setq mu4e-headers-skip-duplicates t)
    (setq mu4e-update-interval (* 60 20))
    (setq message-kill-buffer-on-exit t)
    (setq mail-user-agent 'mu4e-user-agent) ;; make mu4e the default mail client

    ;; don't save message to Sent Messages, Gmail/IMAP takes care of this
    (setq mu4e-sent-messages-behavior 'delete)

    ;; allow for updating mail using 'U' in the main view:
    (setq mu4e-get-mail-command "timeout 60 offlineimap")

    (add-hook 'mu4e-compose-mode-hook
              (defun my-do-compose-stuff () (flyspell-mode)))

    (add-to-list 'mu4e-headers-actions '("view in browser" . mu4e-action-view-in-browser))
    (add-to-list 'mu4e-view-actions '("view in browser" . mu4e-action-view-in-browser))

    (defun mu4e-view (msg headersbuf)
      "Display the message MSG in a new buffer, and keep in sync with HDRSBUF.
  'In sync' here means that moving to the next/previous message in
  the the message view affects HDRSBUF, as does marking etc.

  As a side-effect, a message that is being viewed loses its 'unread'
  marking if it still had that."
      (let* ((embedded ;; is it as an embedded msg (ie. message/rfc822 att)?
              (when (gethash (mu4e-message-field msg :path)
                             mu4e~path-parent-docid-map) t))
             (buf
              (if embedded
                  (mu4e~view-embedded-winbuf)
                (get-buffer-create mu4e~view-buffer-name))))
        ;; note: mu4e~view-mark-as-read will pseudo-recursively call mu4e-view again
        ;; by triggering mu4e~view again as it marks the message as read
        (with-current-buffer buf
          (switch-to-buffer buf)
          (setq mu4e~view-msg msg)
          ;;(or embedded (not (mu4e~view-mark-as-read msg)))
          (when (or (mu4e~view-mark-as-read msg) t)
            (let ((inhibit-read-only t))
              (erase-buffer)
              (mu4e~delete-all-overlays)
              (insert (mu4e-view-message-text msg))
              (goto-char (point-min))
              (mu4e~fontify-cited)
              (mu4e~fontify-signature)
              (mu4e~view-make-urls-clickable)
              (mu4e~view-show-images-maybe msg)
              (setq
               mu4e~view-buffer buf
               mu4e~view-headers-buffer headersbuf)
              (when embedded (local-set-key "q" 'kill-buffer-and-window))
              (mu4e-view-mode))))))

    (require 'smtpmail)

    ;; alternatively, for emacs-24 you can use:
    (setq message-send-mail-function 'smtpmail-send-it
          smtpmail-stream-type 'starttls
          smtpmail-default-smtp-server "smtp.gmail.com"
          smtpmail-smtp-server "smtp.gmail.com"
          smtpmail-smtp-service 587)))

gmail-message-mode

This is useful with server mode when editing gmail messages. I think that it is not currently working, or it may need to be manually enabled.

(use-package gmail-message-mode
  :mode ("\\.gmm\\'" . gmail-message-mode))

ham-mode

(use-package ham-mode
  :commands ham-mode
  :config
  (progn
    (setq ham-mode-html-to-markdown-command
          '("pandoc" "--from" "html" "--to" "markdown" file))))

alert

(use-package alert
  :defer t
  :preface
  (defun imalison:windows-toast-notify (info)
    (let ((message (plist-get info :message))
          (title (plist-get info :title)))
      (shell-command (format "windows_toast '%s' '%s'" (or title "No title") (or message "No message")))))
  :config
  (progn
    (setq alert-default-style 'libnotify)
    (when (not (string-empty-p (shell-command-to-string "grep -i microsoft /proc/version")))
      (alert-define-style
       'windows-toast
       :title "Windows Toast"
       :notifier 'imalison:windows-toast-notify)
      (setq alert-default-style 'windows-toast))))

sauron

(use-package sauron
  :disabled t
  :defer 5
  :commands (sauron-start sauron-start-hidden)
  :init
  (progn
    (when (eq system-type 'darwin)
      (setq sauron-modules '(sauron-erc sauron-org sauron-notifications
                                        sauron-twittering sauron-jabber sauron-identica))
      (defun sauron-dbus-start ()
        nil)
      (makunbound 'dbus-path-emacs)))
  :config
  (progn
    (sauron-start-hidden)
    ;; This should really check (featurep 'dbus) but for some reason
    ;; this is always true even if support is not there.
    (setq sauron-prio-sauron-started 2)
    (setq sauron-min-priority 3)
    ;; (setq sauron-dbus-cookie t) ;; linux only?
    (setq sauron-separate-frame nil)
    (setq sauron-nick-insensitivity 1)
    (defun sauron:jabber-notify (origin priority message &optional properties)
      (funcall notify-function "gtalk" message))
    (defun sauron:erc-notify (origin priority message &optional properties)
      (let ((event (plist-get properties :event)))
        (funcall notify-function "IRC" message)))
    (defun sauron:mu4e-notify (origin priority message &optional properties)
      nil)
    (defun sauron:dbus-notify (origin priority message &optional properties)
      (funcall notify-function "GMail" message))
    (defun sauron:dispatch-notify (origin priority message &optional properties)
      (let ((handler (cond ((string= origin "erc") 'sauron:erc-notify)
                           ((string= origin "jabber") 'sauron:jabber-notify)
                           ((string= origin "mu4e") 'sauron:mu4e-notify)
                           ((string= origin "dbus") 'sauron:dbus-notify)
                           (t (lambda (&rest r) nil)))))
        (funcall handler origin priority message properties)))
    ;; Prefering alert.el for now ;; (add-hook 'sauron-event-added-functions 'sauron:dispatch-notify)
    (sauron-start-hidden)
    (add-hook 'sauron-event-added-functions 'sauron-alert-el-adapter)))

screenshot

(use-package screenshot
  :commands screenshot)

libmpdee

(use-package libmpdee
  :defer t)

flyspell

(use-package flyspell
  :disabled t                           ; kind of annoying
  :preface (setq flyspell-issue-welcome-flag nil)
  :config
  (progn
    (diminish 'flyspell-mode)
    (bind-key "M-s" 'flyspell-correct-word-before-point flyspell-mode-map)
    (unbind-key "C-;" flyspell-mode-map)
    (defun flyspell-emacs-popup-textual (event poss word)
      "A textual flyspell popup menu."
      (let* ((corrects (if flyspell-sort-corrections
                           (sort (car (cdr (cdr poss))) 'string<)
                         (car (cdr (cdr poss)))))
             (cor-menu (if (consp corrects)
                           (mapcar (lambda (correct)
                                     (list correct correct))
                                   corrects)
                         '()))
             (affix (car (cdr (cdr (cdr poss)))))
             show-affix-info
             (base-menu  (let ((save (if (and (consp affix) show-affix-info)
                                         (list
                                          (list (concat "Save affix: "
                                                        (car affix))
                                                'save)
                                          '("Accept (session)" session)
                                          '("Accept (buffer)" buffer))
                                       '(("Save word" save)
                                         ("Accept (session)" session)
                                         ("Accept (buffer)" buffer)))))
                           (if (consp cor-menu)
                               (append cor-menu (cons "" save))
                             save)))
             (menu (mapcar
                    (lambda (arg) (if (consp arg) (car arg) arg))
                    base-menu)))
        (cadr (assoc (popup-menu* menu :scroll-bar t) base-menu))))
    (fset 'flyspell-emacs-popup 'flyspell-emacs-popup-textual)))

web-mode

(use-package web-mode
  :mode (("\\.tmpl\\'" . web-mode)
         ("\\.cql\\'" . web-mode))
  :config
  (progn
    (defvar-setq web-mode-content-types-alist
      '(("gtl" . "\\.tmpl\\'")
        ("gtl" . "\\.cql\\'")))))

perspective

I've disabled perspective because I just don't use it much.

(use-package perspective
  :disabled t
  :demand t
  :config
  (progn
    (persp-mode)
    (defun persp-get-perspectives-for-buffer (buffer)
      "Get the names of all of the perspectives of which `buffer` is a member."
      (cl-loop for perspective being the hash-value of perspectives-hash
               if (member buffer (persp-buffers perspective))
               collect (persp-name perspective)))

    (defun persp-pick-perspective-by-buffer (buffer)
      "Select a buffer and go to the perspective to which that buffer
    belongs. If the buffer belongs to more than one perspective
    completion will be used to pick the perspective to switch to.
    Switch the focus to the window in which said buffer is displayed
    if such a window exists. Otherwise display the buffer in whatever
    window is active in the perspective."
      (interactive (list (funcall persp-interactive-completion-function
                                  "Buffer: " (mapcar 'buffer-name (buffer-list)))))
      (let* ((perspectives (persp-get-perspectives-for-buffer (get-buffer buffer)))
             (perspective (if (> (length perspectives) 1)
                              (funcall persp-interactive-completion-function
                                       (format "Select the perspective in which you would like to visit %s."
                                               buffer)
                                       perspectives)
                            (car perspectives))))
        (if (string= (persp-name persp-curr) perspective)
            ;; This allows the opening of a single buffer in more than one window
            ;; in a single perspective.
            (switch-to-buffer buffer)
          (progn
            (persp-switch perspective)
            (if (get-buffer-window buffer)
                (set-frame-selected-window nil (get-buffer-window buffer))
              (switch-to-buffer buffer))))))

    (defun persp-mode-switch-buffers (arg)
      (interactive "P")
      (if arg (call-interactively 'ido-switch-buffer)
        (call-interactively 'persp-pick-perspective-by-buffer)))

    (define-key persp-mode-map (kbd "C-x b") 'persp-mode-switch-buffers))
  :bind ("C-c 9" . persp-switch))

android-mode

(use-package android-mode
  :defer t
  :config
  (progn
    (require 's)
    (setq android-mode-sdk-dir
          (s-trim (shell-command-to-string "android_sdk_directory")))))

gradle-mode

(use-package gradle-mode)

groovy-mode

This also adds syntax highlighting for gradle

(use-package groovy-mode
  :disabled t)

jsx-mode

(use-package jsx-mode
  :defer t)

css

(use-package css-mode
  :mode (("\\.css\\'" . css-mode)
         ("\\.rasi\\'" . css-mode)))

sgml-mode

(use-package sgml-mode
  ;; :bind ("C-c b" . web-beautify-html) TODO: mode specific, change binding
  :commands sgml-mode)

evil

(use-package evil :commands (evil-mode))

hackernews

(use-package hackernews :commands hackernews)

Appearance

Basic Config

(setq inhibit-startup-screen t)
(blink-cursor-mode -1)

Themes

Ensure all themes that I use are installed:

(use-package solarized-theme
  :defer t
  :init
  (progn
    (setq solarized-high-contrast-mode-line t)))

(defvar-setq packages-appearance
  '(monokai-theme zenburn-theme base16-theme molokai-theme moe-theme
                  tango-2-theme gotham-theme sublime-themes rainbow-delimiters
                  waher-theme ample-theme material-theme zerodark-theme
                  color-theme-modern leuven-theme spacemacs-theme gruvbox-theme
                  forest-blue-theme flatland-theme afternoon-theme
                  cyberpunk-theme dracula-theme))

(mapcar 'straight-use-package packages-appearance)

(use-package doom-themes
  :defer t)

(use-package badwolf-theme)

all-the-icons

(use-package all-the-icons
  :demand t)

doom-modeline

(use-package doom-modeline
  :commands doom-modeline-mode
  :custom
  (doom-modeline-height 40))

page-break-lines

(use-package page-break-lines
  :demand t
  :diminish (page-break-lines-mode)
  :commands page-break-lines-mode
  :init
  :config
  (progn
    (add-to-list 'page-break-lines-modes 'prog-mode)
    (global-page-break-lines-mode +1)))

window-number

(use-package window-number
  :defer t)

Whitespace Setup

Make whitespace-mode use just basic coloring:

(setq whitespace-style
      '(spaces tabs newline space-mark tab-mark newline-mark))

Set the character used to represent spaces to ·, and the character used for tabs to be ▷.

(setq whitespace-display-mappings
      '((space-mark 32 [183] [46])
        (tab-mark 9 [9655 9] [92 9])))

Colorize Compliation Buffers

This automatically applies ansi-color interpretation of terminal escape sequences to compilation buffers.

(defun imalison:colorize-compilation-buffer ()
  (let ((was-read-only buffer-read-only))
    (unwind-protect
        (progn
          (when was-read-only
            (read-only-mode -1))
          (ansi-color-apply-on-region (point-min) (point-max)))
      (when was-read-only
        (read-only-mode +1)))))

(add-hook 'compilation-filter-hook 'imalison:colorize-compilation-buffer)

Automatic Theme Changer

Disabled for now

(use-package theme-changer
  :disabled t
  :config
  (progn
    (destructuring-bind (latitude longitude)
        (imalison:get-lat-long)
      (setq calendar-latitude latitude)
      (setq calendar-longitude longitude))))

Fix ansi-term Colors

For some reason, loading certain themes can cause colors in ansi-term-color-vector to be undefined. The following code handles restoring the original ansi-term-color-vector state. The code is exectued in a load-theme hook (See the heading below).

(defvar imalison:ansi-term-color-vector ansi-term-color-vector)

(defun imalison:ansi-term-color-vector-broken? ()
  (--some (or (eq it 'unspecified) (not (symbolp it)))
          (append ansi-term-color-vector nil)))

(defun imalison:restore-ansi-term-color-vector (&optional force)
  (when (or force (imalison:ansi-term-color-vector-broken?))
    (setq ansi-term-color-vector imalison:ansi-term-color-vector)
    (when (imalison:ansi-term-color-vector-broken?)
      (message
       "Oh no! ansi-term-color vector is super busted (check in custom)."))))

After load-theme hook

(defvar imalison:light-theme 'solarized-light)
(defvar imalison:dark-theme 'gotham)

(defun imalison:after-load-theme (&rest _args)
  (when (fboundp 'powerline-reset)
    (powerline-reset))
  (set-face-background 'fringe (face-background 'default))
  (imalison:restore-ansi-term-color-vector))

(advice-add 'load-theme :after #'imalison:after-load-theme)

Set Font

(add-to-list 'default-frame-alist
             '(font . "JetBrainsMono Nerd Font-10:weight=medium"))

imalison:appearance

(defvar imalison:linum-format)

(defun imalison:format-linum (line-text)
  (propertize (format imalison:linum-format line-text) 'face 'linum))

(make-variable-buffer-local 'imalison:linum-format)
(defun imalison:linum-before-numbering-hook ()
  (setq imalison:linum-format
        (concat "%" (number-to-string
                     (max (length
                           (number-to-string
                            (count-lines (point-min) (point-max)))) 3)) "d")))

(defun imalison:remove-fringe-and-hl-line-mode (&rest _stuff)
  (interactive)
  (if (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
  (if (fboundp 'tool-bar-mode) (tool-bar-mode -1))
  (if (fboundp 'menu-bar-mode) (menu-bar-mode -1))
  (defvar-setq linum-format 'imalison:format-linum)
  (add-hook 'linum-before-numbering-hook 'imalison:linum-before-numbering-hook)
  (setq left-margin-width 0)
  (defvar-setq hl-line-mode nil))


(defun imalison:appearance (&optional frame)
  (setq font-use-system-font nil)
  (interactive (list nil))
  (imalison:remove-fringe-and-hl-line-mode)
  (setq powerline-default-separator (random-choice '(butt slant wave))))

Hooks to set everything up

(defvar imalison:appearance-setup-done nil)

(defun imalison:appearance-setup-hook (&rest args)
  (unless imalison:appearance-setup-done
    (unless (member imalison:dark-theme custom-enabled-themes)
      (load-theme imalison:dark-theme t))
    (apply 'imalison:appearance args)
    (message "running appearance")
    (doom-modeline-mode +1)
    (setq imalison:default-font-size-pt (face-attribute 'default :height))
    (setq imalison:appearance-setup-done t)))

(add-hook 'after-make-frame-functions 'imalison:appearance-setup-hook)
(add-hook 'after-init-hook 'imalison:appearance-setup-hook)

Post Init Custom

(when (file-exists-p custom-after-file) (load custom-after-file))
(when (file-exists-p machine-custom) (load machine-custom))