EmacsConfig/config.org

45 KiB

Introduction

Welcome!

The configuration is sectioned out to aid readability and chunk similar code together

Bootstrapping

Global variables

These two variables are used throughout the bootstrap and general configuration, so we define them as early as possible (immediately).

*config-root* is determined by running file-truename on our ~/.emacs file which, if the setup instructions have been followed, will be a symlink pointing back to bootstrap.el in our git repo.

  (defvar focks/*init-dir* (file-name-concat user-emacs-directory "init")
    "directory where we store our tangled config")

  (defvar focks/*config-root* (file-name-directory (file-truename "~/.emacs"))
    "our configuration directory")

straight.el

To start with, we ensure that we download the straight.el installer, if it doesn't already exist on our system. Once it's downloaded we load it up and use it to install org-mode and use-package.

This is necessary since, later on, we load org-roam using straight.el and that throws errors, as the version of org-mode clashes with the one built-in to Emacs.

  ;; download and setup straight.el...
  (let ((bootstrap-file
         (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
        (bootstrap-version 6))
    (unless (file-exists-p bootstrap-file)
      (with-current-buffer
          (url-retrieve-synchronously
           "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
           'silent 'inhibit-cookies)
        (goto-char (point-max))
        (eval-print-last-sexp)))
    (load bootstrap-file nil 'nomessage))

  ;; ...and then use that to download use-package & org-mode
  (straight-use-package 'org)
  (straight-use-package 'use-package)
  (require 'org)
  (require 'use-package)

Tangle and Load

The standard function to tangle and load a file (org-babel-load-file) created clutter in my git repo that I did not like (among other things). To avoid this, I opted to manually tangle the file and load it after I was certain it had been created.

By manually tangling this config file, we generate two elisp files that get dropped into our *init-dir*. The bootstrap code from this section gets tangled into one file, strapped.el, and the rest gets tangled into config.el.

  (let* ((config-file (file-name-concat focks/*config-root* "config.org"))
         (tangled-file (file-name-concat focks/*init-dir* "config.el")))
    ;; this only tangles the file if there's a change
    (org-babel-tangle-file config-file)
    
    (when (file-exists-p tangled-file)
      (load tangled-file nil 'no-message)))

Custom

When I first started getting into Emacs, I obviously tried to use the built-in customize screens to build and setup my configuration.

Between other configuration's I saw online and trying to provide my own functionality I quickly moved past using it in favor of configuring packages directly. This became even more trivial once I stumbled across use-package (see Package Loading for examples on usage).

TODO

Move all custom settings from here into Miscellaneous

Variables

  (custom-set-variables
   ;; custom-set-variables was added by Custom.
   ;; If you edit it by hand, you could mess it up, so be careful.
   ;; Your init file should contain only one such instance.
   ;; If there is more than one, they won't work right.
   '(backup-directory-alist '((".*" concat (getenv "HOME") "/.emacs.d/backups")))
   '(column-number-mode t)
   '(company-quickhelp-use-propertized-text t)
   '(dired-use-ls-dired nil)
   '(flycheck-color-mode-line-face-to-color 'mode-line-buffer-id)
   '(global-emojify-mode t)
   '(inhibit-startup-screen t)
   '(make-backup-files nil)
   '(save-place t nil (saveplace))
   '(show-paren-mode t)
   '(size-indication-mode t)
   '(tool-bar-mode nil)
   '(tool-bar-position 'left)
   '(vc-annotate-background nil)
   '(vc-annotate-very-old-color nil)
   '(window-divider-mode nil))

Macros

Operating System macros

For a long time I was hopping back and forth between different operating systems but continued using the same Emacs configuration for each machine. I soon ran into the problem where a single config was not ideal for each environment. Instead of breaking chunks out into various os-specific files I opted to write some macros that would make it easier to perform various checks about what system the editor was currently running on.

OS-p

Although it is fairly trivial to write an operating system check ((eq system-type 'darwin)), it becomes a bit of a hassle, and is not very readable (in my opinion).

os-p makes this a bit of an easier process, by defining predicate functions to test for operating systems while also being more legible.

  (defmacro focks/os-p (os &rest os-name)
    "defines a predicate that checks the current system's OS"
    (let* ((os-list (flatten-list os-name))
           (type-length (length os-list)))
      `(defun ,(intern (format "%s-p" os)) ()
         ,(concat "a predicate to check if SYSTEM-TYPE is "
                  (mapconcat #'prin1-to-string (butlast os-list) ", ")
                  (when (> type-length 1) ", or ")
                  (mapcan #'prin1-to-string (last os-list)))
         (or ,@(mapcar (lambda (name) `(eq system-type ',name))
                       os-list)))))

When/Unless-on

These macros define macros that allow for evaluating blocks of code only when you are running Emacs on certain operating systems. They also allow you to combine system-type-s so that you can specify code to run on a group of OS's but not on all of them (see Bundling them all together for an example of this).

  (defmacro focks/when-on (os &rest type-names)
    "define a macro (named when-on-OS) to run code when SYSTEM-TYPE matches any symbol in TYPE-NAMES

  OS is a symbol (or string) to be placed in the macro name
  TYPE-NAMES is a list of symbols that correspond to values returned by system-type"
    `(defmacro ,(intern (format "when-on-%s" os)) (&rest body)
       `(when (or ,@(mapcar (lambda (name) `(eq system-type ',name))
  			  (flatten-list ',type-names)))
  	,@body)))


  (defmacro focks/unless-on (os &rest type-names)
    "define a macro (named unless-on-OS) to run code when SYSTEM-TYPE matches any symbol in TYPE-NAMES

  OS is a symbol (or string) to be placed in the macro name
  TYPE-NAMES is a list of symbols that correspond to values returned by system-type"
    `(defmacro ,(intern (format "unless-on-%s" os)) (&rest body)
       `(unless (or ,@(mapcar (lambda (name) `(eq system-type ',name))
                              (flatten-list ',type-names)))
  	,@body)))

OS-cond

While the prior macros are nice for quick when/unless blocks, I found myself in dire need of being able to eval one block of code and have it return different values for different operating systems.

os-cond acts like a regular cond structure, but takes forms in the style of (SYSTEM-TYPE FORMS), evaluating the first one that matches the current system-type.

  (defmacro focks/os-cond (&rest forms)
    ""
    `(cond
      ,@(cl-loop for f in forms
                 if (eq (car f) t)
                   collect `(t ,@(cdr f))
                 else
                 collect `((or ,@(mapcar #'(lambda (name)
                                             (ensure-list (intern (format "%s-p" name))))
                                         (ensure-list (car f))))
                           ,@(cdr f)))))

Bundling them all together

After a while, I figured it might be easier to just… run all of the prior macros at once for a given system-type.

So, I fixed that :3c

focks/create-standard-os-macros takes a full list of the default system-type values and runs os-p, when-on, and unless-on for all of them.

  (defmacro focks/create-standard-os-macros ()
  "runs prior OS detection macros for standard values of SYSTEM-TYPE"
  `(progn
     ,@(cl-loop for os in '((gnu . hurd) (gnu/linux . linux)
                            (darwin . macos) (ms-dos . dos)
                            (windows-nt . windows) (gnu/kfreebsd . bsd)
                            ((gnu/linux aix berkeley-unix hpux usg-unix-v) . unix)
                            ((darwin gnu/kfreebsd berkeley-unix) . bsdish)
                            haiku cygwin)
                for os-name = (if (listp os) (cdr os) os)
                for os-type = (if (listp os) (car os) os)

                collect
                `(progn
                   (focks/os-p ,os-name ,os-type)
                   (focks/when-on ,os-name ,os-type)
                   (focks/unless-on ,os-name ,os-type)))))

Hostname-specific code

After consolidating back to (mostly) a single OS, I found that I still needed some ability to selectively run code on one machine and not the other.

Hence, the focks/when-machine macro. It's fairly straight forward and doesn't do anything fun like the prior macros, but it is still handy when a need arises.

  (defmacro focks/when-machine (hostname &rest body)
    "a macro to only execute BODY when HOSTNAME matches the value returned by SYSTEM-NAME

  applies UPCASE to HOSTNAME parameter, and to the value returned by SYSTEM-NAME
  if using a system that returns SYSTEM-NAME as System.local, we drop the .local"
    `(when (string-equal (upcase ,hostname)
                         (upcase (car (split-string (system-name) "\\."))))
       ,@body))

  (defmacro focks/machine-cond (&rest forms)
    `(cond
      ,@(cl-loop for form in (car forms)
                 if (eq (car form) t)
                   collect `(t ,@(cdr form))
                 else
                   collect `((string= ,(upcase (car form))
                                      (upcase (car (split-string (system-name) "\\."))))
                             ,@(cdr form)))))

Cheeky bit of execution

Since the rest of the configuration relies on these macros to be defined, we make sure and run them exceedingly early.

  ;; runs os-p/when-on/unless-on for all system-types
  (focks/create-standard-os-macros)

Variables

Configuration Variables

Custom variables used throughout my custom functions/macros.

  (defvar focks/local-file (file-name-concat focks/*config-root* "local.el"))

  (defvar focks/face-height 120
    "default face height. override in local.el")

  (defvar asdf-build-op-template
    "\n  :build-operation \"program-op\"
    :build-pathname \"bin/%s\"
    :entry-point \"%s::main\")"
    "build operation template for ASDF systems")

  (defvar asdf-compression-op
    "\n\n#+sb-core-compression
  (defmethod asdf:perform ((o asdf:image-op) (c asdf:system))
    (uiop:dump-image (asdf:output-file o c) :executable t :compression t))"
    "compression operation for ASDF systems")

  (defvar asdf-makefile-template
    "define LISP_CMDS
  \"(handler-case                    \\
      (progn (ql:quickload :%s) \\
             (asdf:make :%s))   \\
    (error (e)                      \\
      (format t \\\"~A~%%\\\" e)         \\
      (uiop:quit 1)))\"
  endef\n
  .PHONY: clean all\n
  all:\n\tros --eval $(LISP_CMDS)\n
  clean:\n\trm -ri bin/"
    "ASDF makefile template")

Functions

  ;; custom projectile lisp project detection/compile command
  (defun focks/parse-asdf-system-name (asd-file)
    (let ((regxp (rx "defsystem" (? eol) (*? space)
                     (*? punct) (group (+ (any "-" letter))))))
      (with-temp-buffer
        (insert-file-contents asd-file)
        (string-match regxp (buffer-string))
        (string-trim
         (substring (buffer-string) (match-beginning 1) (match-end 1))))))

  (defun focks/asdf-project-dir-p (&optional path)
    (directory-files (or path (file-name-directory (buffer-file-name (current-buffer)))) 'full ".*asd"))

  (defun focks/has-makefile-p (path)
    (let ((dir (if (file-directory-p path)
                   path
                 (file-name-directory path))))
      (directory-files dir 'full "Make*")))

  (defun focks/asdf-compile-cmd ()
    ;; get project root (asd file)
    ;; parse it for system name (immediately after defsystem)
    ;; build quicklisp/asdf build command
    (let* ((asd-file (car (ensure-list (focks/asdf-project-dir-p))))
           (asdf-system (focks/parse-asdf-system-name asd-file))
           (makefile (focks/has-makefile-p asd-file)))
      (if makefile
          (concat "make -f " (car makefile))
        (concat "ros run --eval \""
                "(handler-case "
                "  (progn "
                "    (ql:quickload :" asdf-system ") "
                "    (asdf:make :" asdf-system ") "
                "    (uiop:quit 0))"
                "  (error (e) "
                "    (format t \\\"~A~%%\\\" e) "
                "    (uiop:quit 1)))"
                "\""))))

  (defun focks/get-system-arch ()
    "gets the system architecture based off of the results of uname -a"
    (focks/os-cond
     ((unix macos) (string-trim (shell-command-to-string "uname -m")))
     (windows (let ((type (string-trim
                           (shell-command-to-string "powershell.exe -Command \"& {(Get-WMIObject -Class Win32_Processor).Architecture}\""))))
                (cond
                 ((string= type "0") "x86")
                 ((string= type "1") "MIPS")
                 ((string= type "2") "Alpha")
                 ((string= type "3") "PowerPC")
                 ((string= type "6") "ia64")
                 ((string= type "9") "x64"))))))

  (defun focks/buffer-existsp (buf-name)
    "checks if buffer with BUF-NAME exists in (buffer-list)"
    (member buf-name (mapcar #'buffer-name (buffer-list))))

  (defun focks/get-file-info ()
    "returns an alist with path and extension under :PATH and :EXTENSION"
    `((:path . ,(butlast
                 (cl-remove-if #'string-blank-p
                               (file-name-split buffer-file-name))))
      (:extension . ,(file-name-extension buffer-file-name))))

  (defun focks/stringify (&rest args)
    "converts every value in ARGS into a string and merges them together"
    (mapconcat (lambda (x) (format "%s" x))  args ""))

  (defun focks/posframe-fallback (buffer-or-name arg-name value)
    (let ((info (list :internal-border-width 3
                      :background-color "dark violet")))
      (or (plist-get info arg-name) value)))

  (defun focks/font-available-p (font-family)
    "predicate to check for the existance of the specified font family"
    (find-font (font-spec :name font-family)))

  (defun focks/compare-lists (l1 l2)
    "compare lists L1 and L2.

  returns a list containing elements in L1 but not L2 and vice versa"
    (cl-flet ((not-in-list (elt lst)
  	      (unless (member elt lst)
  		elt)))
      (list (cl-remove-if-not #'identity
  			    (mapcar #'(lambda (e)
  					(not-in-list e l2))
  				    l1))
  	  (cl-remove-if-not #'identity
  			    (mapcar #'(lambda (e)
  					(not-in-list e l1))
  				    l2)))))

Interactive Functions

Purposeful Functions

  (defun horz-flip-buffers ()
    "Flips the open buffers between two windows"
    (interactive)
    (let ((c-buf (buffer-name))
        (o-buf (buffer-name (window-buffer (next-window)))))
      (switch-to-buffer o-buf nil t)
      (other-window 1)
      (switch-to-buffer c-buf)
      (other-window (- (length (window-list)) 1))))

  (defun init-cpp-file (includes)
    "Quickly and easily initializes a c++ file with main
  INCLUDES is a space seperated list of headers to include"
    (interactive "Mincludes: ")
    (let ((path (directory-file-name buffer-file-truename))
          (inc-list (split-string includes " "))
          point)
      (dolist (inc inc-list)
        (insert "#include ")
        (if (file-exists-p (concat path inc ".h"))
          (insert (concat "\"" inc ".h\"\n"))
        (insert (concat "<" inc ">\n"))))
      (insert "using namespace std;\n\n")
      (insert "int main(int argc, char *argv[]) {\n")
      (insert "  ")
      (setq point (point))
      (insert "\n  return 0;\n")
      (insert "}\n")

      (goto-char point)))

  (defun sly-qlot (directory)
    (interactive (list (read-directory-name "Project directory: ")))
    (require 'sly)
    (sly-start :program "qlot"
               :program-args '("exec" "ros" "-S" "." "run")
               :directory directory
               :name 'qlot
               :env (list (concat "PATH=" (mapconcat 'identity exec-path ":")))))

  (defun make-buffer (name)
    "creates and switches to a new buffer with name NAME"
    (interactive "Bname: ")
    (let ((buff (generate-new-buffer name)))
      (switch-to-buffer buff)
      (text-mode)))

  (defun scratch ()
    "switches to the scratch buffer, creating it if needed"
    (interactive)
    (switch-to-buffer (get-buffer-create "*scratch*"))
    (when (string-blank-p (buffer-string))
      (insert initial-scratch-message)
      (goto-char (point-max)))
    (lisp-interaction-mode))

  (defun asdf-generate-makefile (&optional dir)
    "generates a makefile for a common lisp project"
    (interactive)
    (let* ((cwd (or dir
                    (projectile-project-root)
                    (file-name-directory buffer-file-truename)))
           (files (focks/asdf-project-dir-p cwd))
           (asd-file (if (< 1 (length files))
                         (ivy-read "Select ASDF file: " files)
                       (car files))))
      (if asd-file
          (let ((system (focks/parse-asdf-system-name asd-file)))
            (with-temp-file (file-name-concat cwd "Makefile")
              (insert (format asdf-makefile-template system system))))
        (message "no ASDF system definition in directory"))))

  (defun asdf-add-build-instructions (&optional dir)
    "adds general build instructions into ASDF system definition"
    (interactive)
    (let* ((cwd (or dir
                    (projectile-project-root)
                    (file-name-directory buffer-file-truename)))
           (files (focks/asdf-project-dir-p cwd))
           (asd-file (if (< 1 (length files))
                         (ivy-read "Select ASDF file: " files)
                       (car files))))
      (if asd-file
          (let ((system (focks/parse-asdf-system-name asd-file)))
            (with-temp-file asd-file
              (insert-file-contents asd-file)
              (string-match (rx (group (syntax close-parenthesis)
                                       (syntax whitespace)
                                       string-end))
                            (buffer-string))
              (goto-char (match-end 1))
              (delete-backward-char 1)
              (insert (format asdf-build-op-template
                              system system)
                      asdf-compression-op)))
        (message "no ASDF system definition in directory"))))

  (cl-defun asdf-add-build-and-makefile ()
    "generates a makefile and adds build instructions into the system definition for the lisp project thats currently being edited"
    (interactive)
    (cl-flet ((ivy-find-dir (prompt)
                (let ((counsel--find-file-predicate #'file-directory-p))
                  (ivy-read prompt #'read-file-name-internal :matcher #'counsel--find-file-matcher))))
      (let ((asdf-dir (and current-prefix-arg (ivy-find-dir "ASDF Project Dir: "))))
        (when current-prefix-arg
          (unless (or asdf-dir (focks/asdf-project-dir-p asdf-dir))
            (message "No ASDF directory selected.")
            (cl-return)))
        
        (when (yes-or-no-p "Generate a Makefile and add build instructions for lisp system?")
          (asdf-generate-makefile asdf-dir)
          (asdf-add-build-instructions asdf-dir)))))

  (defun compare-selected-lists (start end)
    "pulls two lists from region.

  lists should be separated by 2+ newlines
  differences are returned in a message to the user.

  if a prefix arg is passed with C-u the results are displayed in a temp buffer"
    (interactive (if (use-region-p)
  		   (list (region-beginning) (region-end))
  		 '(nil nil)))
    (if (and start end)
    
        (let* ((separator
                (let ((input (ivy-read "Element separator? " '("newline" "space") :preselect 0)))
                  (cond
                   ((equal "newline" input) "\n")
                   ((equal "space" input) " ")
                   (t input))))
  	     (region (if rectangle-mark-mode
  			 (string-join (mapcar #'string-trim (extract-rectangle start end)) separator)
  		       (buffer-substring start end)))
  	     (list1 (cl-first (string-split region (rx (>= 2 (seq "\n" (0+ whitespace)))) 'omit-nulls)))
  	     (list2 (cl-second (string-split region (rx (>= 2 (seq "\n" (0+ whitespace)))) 'omit-nulls)))
  	     (diffs (focks/compare-lists
                       (mapcar #'string-trim
                               (string-split list1 separator 'omit-nulls))
  		     (mapcar #'string-trim 
                               (string-split list2 separator 'omit-nulls))))
               (diff-message (format (if (cl-some #'identity diffs)
  			               "In first list only: %s\nIn second list only: %s"
  			             "No differences found...")
  			           (string-join (cl-first diffs) ", ")
  			           (string-join (cl-second diffs) ", "))))
  	(if current-prefix-arg
              ;; this keeps the temp buffer smol and baby
              (let ((temp-buffer-show-hook
                     #'(lambda ()
                         (shrink-window-if-larger-than-buffer))))
                (with-output-to-temp-buffer "<List Differences>"
                  (princ diff-message)))
            (message diff-message)))
      (message "No region selected...")))

For Funsies

  (defun emojofy ()
    "turns a string into a formatted string for shitposting

  prompts for PREFIX and WORD
  copies the resulting formatted string into the kill-ring and displays a message
   showing what was put there

  ex: 
  PREFIX=wide
  WORD=test

  RESULT: :wide_t::wide_e::wide_s::wide_t:"
    (interactive)
    (cl-flet ((emojify (letter)
                (if (memq (get-char-code-property letter 'general-category)
                          '(Ll Lu))
                    (format ":%s_%c:" prefix letter)
                  (format "%c" letter))))
      (let* ((prefix (read-string "prefix: "))
  	   (word (read-string "word: "))
             (result (mapconcat #'emojify word "\u200d")))
      (kill-new result)
      (message result))))

  (defun fox-me-up (&optional message)
    "FOX ME UP INSIDE"
    (interactive "Mmessage: ")
    (let ((skeleton 
  "  _,-=._              /|_/|
    `-.}   `=._,.-=-._.,  @ @._,   <(%s)
       `._ _,-.   )      _,.-'
          `    G.m-\"^m`m'"))
      (message
       (format skeleton (if (or (not message)
                                (focks/blankp message))
                            "reet"
                          message)))))

OS Specific Configuration

Windows

  (when-on-windows
   (prefer-coding-system 'utf-8)
   (set-default-coding-systems 'unix)
   (set-language-environment "UTF-8")
   (set-selection-coding-system 'unix))

macOS

  (when-on-macos
   ;; this disables special character input in emacs when using the option key
   ;; and ensures that the command key sends meta keypresses
   (setq mac-option-modifier 'meta
         mac-command-modifier 'meta)

   ;; this used to be in a check for arm64 system arch
   ;; however, the only macos systems i use *are* arm64...
   (electric-pair-mode 1))

*nix

  (when-on-unix
   (display-battery-mode)
   (setq ispell-local-dictionary "en_US"))

BSD

  (when-on-bsd
 
   ;; the built-in battery-bsd-apm function doesnt seem to work on freebsd
   ;;  it has an extra command line argument, and doesnt properly parse the
   ;;  command output. here's my updated version
   (defun focks/battery-freebsd-apm ()
     "Get APM status information from BSD apm binary.
  The following %-sequences are provided:
  %L AC line status (verbose)
  %B Battery status (verbose)
  %b Battery status, empty means high, `-' means low,
   `!' means critical, and `+' means charging
  %p Battery charge percentage
  %s Remaining battery charge time in seconds
  %m Remaining battery charge time in minutes
  %h Remaining battery charge time in hours
  %t Remaining battery charge time in the form `h:min'"
     (let* ((apm-cmd "/usr/sbin/apm -blta")
          (apm-output (split-string (shell-command-to-string apm-cmd)))
          ;; Battery status
          (battery-status
           (let ((stat (string-to-number (nth 1 apm-output))))
             (cond ((eq stat 0) '("high" . ""))
                   ((eq stat 1) '("low" . "-"))
                   ((eq stat 2) '("critical" . "!"))
                   ((eq stat 3) '("charging" . "+"))
                   ((eq stat 4) '("absent" . nil)))))
          ;; Battery percentage
          (battery-percentage (nth 2 apm-output))
          ;; Battery life
          (battery-life (nth 3 apm-output))
          ;; AC status
          (line-status
           (let ((ac (string-to-number (nth 0 apm-output))))
             (cond ((eq ac 0) "disconnected")
                   ((eq ac 1) "connected")
                   ((eq ac 2) "backup power"))))
          seconds minutes hours remaining-time)
       (unless (member battery-life '("unknown" "-1"))
         (setq seconds (string-to-number battery-life)
               minutes (truncate (/ seconds 60)))
         (setq hours (truncate (/ minutes 60))
             remaining-time (format "%d:%02d" hours
                                    (- minutes (* 60 hours)))))
       (list (cons ?L (or line-status "N/A"))
           (cons ?B (or (car battery-status) "N/A"))
           (cons ?b (or (cdr battery-status) "N/A"))
           (cons ?p (if (string= battery-percentage "255")
                        "N/A"
                      battery-percentage))
           (cons ?s (or (and seconds (number-to-string seconds)) "N/A"))
           (cons ?m (or (and minutes (number-to-string minutes)) "N/A"))
           (cons ?h (or (and hours (number-to-string hours)) "N/A"))
           (cons ?t (or remaining-time "N/A")))))

   (setq ispell-dictionary "en_US"
         ispell-aspell-dict-dir "/usr/local/share/aspell/"
         ispell-aspell-data-dir "/usr/local/lib/aspell-0.60/"
         ispell-dictionary-keyword "american"
         battery-status-function #'focks/battery-freebsd-apm))

Unless

  (unless-on-windows
   (setq ispell-program-name "aspell"))

Theme Loading

General

  ;; theme selection
  (defvar focks/*current-theme* 'dark
    "our current theme
  values can either be 'DARK or 'LIGHT")

  (defvar focks/auto-update-macos-theme t
    "flag for whether we should automatically change theme on macOS when system theme changes")

  (defvar focks/*light-theme* '(solo-jazz)
    "list of themes to load when *current-theme* is 'light")
  (defvar focks/*dark-theme* '(challenger-deep)
    "list of themes to load when *current-theme* is 'dark")

  (defvar focks/*fonts* '("Cartograph CF" "Chalkboard" "Comic Sans MS")
    "list of fonts, in order of preference")

  ;; dark theme 
  (use-package challenger-deep-theme
    :ensure t)

  ;; light theme
  (use-package solo-jazz-theme
    :ensure t)

  (defun focks/load-emacs-theme (&optional mode)
      "loads custom themes based on focks/*current-theme*

  ensures disabling all prior loaded themes before changing"
      (cl-flet ((load-themes (x)
                  (load-theme x t)))
        (unless (member (face-attribute 'default :family)
                        focks/*fonts*)
          (let* ((font-family (prin1-to-string
          		     (font-get (car (cl-remove-if-not #'identity
                                                                (mapcar #'focks/font-available-p focks/*fonts*)))
          		               :family)))
                 (sanitized (string-replace "\\" "" font-family)))
            
            (set-face-attribute 'default nil
          	              :height focks/face-height
          	              :slant 'normal
          	              :width 'normal
          	              :weight 'normal
          	              :family sanitized)
            
            (setq font-lock-comment-face `(:slant italic :family ,sanitized))))
        
        (mapc #'disable-theme custom-enabled-themes)
        (if (eq 'dark (or mode focks/*current-theme*))
            (mapc #'load-themes focks/*dark-theme*)
          (mapc #'load-themes focks/*light-theme*))))

  (add-hook 'window-setup-hook 'focks/load-emacs-theme)

macOS Specific

  (when-on-macos
   (when focks/auto-update-macos-theme
     ;; if we're using a version of emacs with a certain patch
     ;; we dont need to do all the homegrown stuff, and can just
     ;; hook into ns-system-appearance-change-functions
     (if (boundp 'ns-system-appearance)
         (progn
           (setq focks/*current-theme* ns-system-appearance)
           (add-hook 'ns-system-appearance-change-functions
                     #'focks/load-emacs-theme))
       (progn
         ;; define a function that runs a custom applescript script that
         ;; checks our theme and returns 'dark or 'light
         (defun focks/macos-theme ()
           "gets the current macOS window theme by running a supplied helper applescript program

  returns either 'dark or 'light"
           (let ((theme (shell-command-to-string (concat "osascript " focks/*config-root* "CheckSystemTheme.scpt"))))
             (if (string= theme (concat "true" (char-to-string ?\n)))
                 'dark
               'light)))
       
         ;; defines a function that checks the system theme
         ;; and changes our emacs theme to match it
         (defun focks/match-theme-to-system ()
           "checks the system theme and changes the emacs theme to match"
           (let ((current (focks/macos-theme)))
             (unless (equal focks/*current-theme* current)
               (setq focks/*current-theme* current)
               (focks/load-emacs-theme focks/*current-theme*))))
       
         ;; sets up a hook that will run every 5 seconds to
         ;; match the themes
         (when focks/auto-update-macos-theme
           (add-hook 'window-setup-hook
                     #'(lambda ()
                         (run-with-timer 0 5 #'focks/match-theme-to-system))))))))

Miscellaneous

Code that may or may not belong elsewhere. Mostly setting various editor values or settings some keys globally.

  ;; so i dont have to type 'yes' out each time smh smh
  (defalias 'yes-or-no-p 'y-or-n-p)

  ;; when we have ros installed go and include the path in the exec-path list
  (when (executable-find "ros")
    (let* ((ros-info (split-string (shell-command-to-string "ros version")
                                   "\n" t))
           (info-alist (mapcar #'(lambda (s)
                                   (split-string s "=" t "'"))
                               ros-info))
           (path (file-name-concat (cadr (assoc "homedir" info-alist #'string=))
                                   "bin")))
      (setq exec-path (append exec-path (list path)))))

  ;; run these options only when we're running in daemon mode
  (when (daemonp)
    (global-set-key (kbd "C-x M-C-c") 'kill-emacs))

  ;; sets up my custom key bindings
  (global-set-key (kbd "C-x M-f") 'horz-flip-buffers)

  ;; puts the line number in the left fringe
  (global-display-line-numbers-mode)

  ;; ensures that we NEVER have tabs in our code. ANYWHERE
  (setq-default indent-tabs-mode nil)

  ;; disable the scroll bar
  (scroll-bar-mode 0)

  ;; set the time format to 24hr and enable time display
  ;; only if we're running from a console
  (unless window-system
    (setq display-time-24hr-format t)
    (display-time-mode))


  ;;;
  ;; ENABLE DISABLED FUNCTIONS

  ;; 'list-timers 
  (put 'list-timers 'disabled nil)

  ;; downcase-region (C-x C-l)
  (put 'downcase-region 'disabled nil)


  ;; enable desktop saving
  (desktop-save-mode 1)

  ;; also ensure we can save/load desktop files from project directories
  (setq desktop-path
        (list "." (expand-file-name user-emacs-directory) (expand-file-name "~")))

  ;; ensures we keep our init file clean of Customize mess
  (setq custom-file (file-name-concat user-emacs-directory "custom.el"))
  (load custom-file)

Package Loading

One of the first fun things I did when refactoring my configuration was ensure that it was totally independent of needing anything installed to get it up and running. My initial attempt at this was to package use-package as a git submodule in this repo. However, this was a bit tedious and prone to being out of date (it's hard to keep a config, much less a SUBMODULE up to date when you're as lazy as me lmao).

My second attempt involved manually downloading, unzipping, and require-ing use-package manually, if its directory didn't already exist. This worked, however around the same time I was trying to get into using org-roam to write notes and journal. The only way that org-roam is offered for installation is through straight.el.

And since straight is a package manager in its own right, I figured it would be just as simple (if not simpler) to use it to install use-package, and so here we are.

Configure package options

Here we begin by loading the package library and ensuring we have it configured to check the MELPA elisp repository.

  (require 'package)
  (add-to-list 'package-archives
  	     '("melpa" . "http://melpa.org/packages/"))
  (package-initialize)
  (unless package-archive-contents
    (package-refresh-contents))

Use-Package

use-package is built and loaded using straight.el at the beginning of the bootstrap code. What follows from here is loading and configuring the rest of my packages utilizing use-package.

TODO

Reorganize this section to have similar packages grouped together
  (use-package siege-mode
    :ensure straight
    :straight (:host github :repo "tslilc/siege-mode" :branch "master")
    :hook ((programming-mode . siege-mode)))

  (use-package lsp-sourcekit
    :after lsp-mode
    :ensure t
    :when (macos-p)
    :config
    (setq lsp-sourcekit-executable (string-trim (shell-command-to-string "xcrun --find sourcekit-lsp"))))

  (use-package swift-mode
    :ensure t
    :hook (swift-mode . (lambda ()
                          (when-on-macos (lsp)))))

  (use-package json-reformat
    :ensure t)

  (use-package json-mode
    :ensure t
    :pin melpa)

  (use-package org-roam
    :ensure t
    :init
    (setq org-roam-v2-ack t)
    (let ((notes-dir (focks/os-cond
                      (windows (concat (getenv "USERPROFILE") "\\Syncthing\\Notes"))
                      (t "~/Syncthing/Notes"))))
      (unless (file-directory-p notes-dir)
        (make-directory notes-dir)))

    :bind
    (("C-c n l" . org-roam-buffer-toggle)
     ("C-c n f" . org-roam-node-find)
     ("C-c n i" . org-roam-node-insert)
     ("C-c n c" . org-roam-capture)
     ("C-c n d" . org-roam-dailies-goto-today)
     ("C-c n t" . org-roam-dailies-goto-tomorrow))

    :custom
    (org-roam-directory
     (focks/os-cond
      (windows (concat (getenv "USERPROFILE") "\\Syncthing\\Notes"))
      (t "~/Syncthing/Notes")))

    :config
    (when-on-windows
     (unless (version<= "29.0.0" emacs-version)
       (message "SQLite support is built into Emacs v29+ and is recommended for org-roam...")
       (sleep-for 2.5)))

    (org-roam-setup))

  (use-package org-roam-ui
    :straight
    (:host github :repo "org-roam/org-roam-ui" :branch "main" :files ("*.el" "out"))
    :after org-roam
    :custom
    ((org-roam-ui-sync-theme t)
     (org-roam-ui-follow t)   
     (org-roam-ui-update-on-save t)
     (org-roam-ui-open-on-start t)))

  ;; show function docstrings in the minibuffer
  (use-package marginalia
    :ensure t
    :bind (("M-A" . marginalia-cycle)
           :map minibuffer-local-map
           ("M-A" . marginalia-cycle))
    :init (marginalia-mode))

  (use-package parinfer-rust-mode
    :ensure t
    :unless (string= "arm64" (focks/get-system-arch))
    :hook (lisp-mode . parinfer-rust-mode)

    :custom
    (parinfer-rust-library
     (focks/os-cond
      (windows-nt "~/.emacs.d/parinfer-rust/parinfer_rust.dll")
      (t "~/.emacs.d/parinfer-rust/libparinfer_rust.so")))

    :init
    (unless-on-windows
     (setq parinfer-rust-auto-download t)))

  (use-package lua-mode
    :ensure t
    :after company
    :init
    (setq-local company-backends '((company-lua company-etags company-dabbrev-code))))

  (use-package company-lua
    :ensure t
    :after company)

  (use-package cc-mode
    :ensure t
    :bind (("C-c i" . init-cpp-file)))

  (use-package popwin
    :ensure t
    :init (popwin-mode t))

  (use-package posframe
    :ensure t
    :custom
    (posframe-arghandler #'focks/posframe-fallback))

  (use-package frog-jump-buffer
    :ensure t
    :bind ("C-;" . frog-jump-buffer)
    :config
    (dolist (regexp '("TAGS" "^\\*Compile-log" "-debug\\*$" "^\\:" "errors\\*$" "^\\*Backtrace" "-ls\\*$"
                      "stderr\\*$" "^\\*Flymake" "^\\*vc" "^\\*Warnings" "^\\*eldoc" "\\^*Shell Command"))
      (push regexp frog-jump-buffer-ignore-buffers)))

  (use-package eros
    :ensure t
    :init (eros-mode t))

  (use-package css-eldoc
    :ensure t
    :hook ((css-mode . turn-on-css-eldoc)))

  (use-package request
    :ensure t)

  (use-package markdown-mode
    :ensure t)

  (use-package eshell
    :bind ("C-x M-e" . eshell)
    :custom
    ((eshell-prompt-function
      (lambda()
        (concat 
         (funcall #'(lambda (p-lst)
                      (if (> (length p-lst) 1)
                          (concat
                           (mapconcat (lambda (elm)
                                        (unless (equal elm "") (substring elm 0 1)))
                                      (butlast p-lst (- (length p-lst)
                                                        (1- (length p-lst))))
                                      "/")
                           "/"
                           (mapconcat #'identity
                                      (last p-lst (- (length p-lst)
                                                     (1- (length p-lst))))
                                      "/"))
                        (mapconcat #'identity p-lst "/")))
                  (split-string (string-replace (expand-file-name "~") "~" (eshell/pwd))
                                "/"))
         (if (= (user-uid) 0) " # " " $ "))))))

  (use-package info-look
    :ensure t)

  (use-package minions
    :ensure t
    :config (minions-mode 1))

  (use-package doom-modeline
    :ensure t
    :init (doom-modeline-mode 1)
    :custom
    ((doom-modeline-buffer-encoding nil)
     (doom-modeline-minor-modes t)
     (doom-modeline-gnus-timer nil)
     (doom-modeline-bar-width 3)
     (doom-modeline-icon (unless (daemonp) t))
     (inhibit-compacting-font-caches (when-on-windows t))))

  (use-package projectile
    :ensure t
    :init (projectile-mode +1)
    :bind (:map projectile-mode-map
                ("C-c p" . projectile-command-map))
    :config
    (projectile-register-project-type 'asdf 'focks/asdf-project-dir-p
                                      :project-file ".*asd"
                                      :compile 'focks/asdf-compile-cmd))

  ;; (use-package treemacs
  ;;   :ensure t
  ;;   :bind ([f8] . treemacs))

  ;; (use-package treemacs-projectile
  ;;   :after treemacs projectile
  ;;   :ensure t)

  ;; (use-package treemacs-magit
  ;;   :after treemacs magit
  ;;   :ensure t)

  (use-package company
    :ensure t
    :init (global-company-mode))

  (use-package company-quickhelp
    :ensure t
    :hook (company-mode . company-quickhelp-mode))

  (use-package company-box
    :ensure t
    :hook (company-mode . company-box-mode))

  (use-package fish-mode
    :ensure t)

  (use-package hydra
    :ensure t)

  (use-package ivy
    :ensure t
    :init (ivy-mode 1)
    :bind (:map ivy-minibuffer-map
                ("RET" . ivy-alt-done))
    :custom
    (ivy-use-virtual-buffers 'recentf))

  (use-package ivy-hydra
    :ensure t
    :after ivy hydra)

  (use-package counsel
    :ensure t
    :init (counsel-mode 1))

  (use-package counsel-projectile
    :ensure t
    :after counsel projectile
    :init (counsel-projectile-mode))

  (use-package swiper
    :ensure t
    :bind
    ("C-s" . swiper)
    ("C-r" . swiper))

  ;; only install elcord when discord is installed
  (use-package elcord
    :ensure t
    :when (executable-find "discord")
    :hook ((lisp-mode . elcord-mode)))

  (use-package prism
    :ensure t
    :hook ((lisp-mode . prism-mode)
           (common-lisp-mode . prism-mode)
           (ruby-mode . prism-mode)
           (emacs-lisp-mode . prism-mode)))

  (use-package emr
    :ensure t
    :bind (("M-RET" . emr-show-refactor-menu)))

  (use-package dimmer
    :ensure t
    :custom
    (dimmer-fraction 0.4)

    :config
    (dimmer-mode 1))

  (use-package rainbow-delimiters
    :ensure t
    :hook ((lisp-mode . rainbow-delimiters-mode)
           (emacs-lisp-mode . rainbow-delimiters-mode)
           (sly-mode . rainbow-delimiters-mode)))

  (use-package ido-completing-read+
    :ensure t
    :init (ido-ubiquitous-mode 1))

  (use-package amx
    :ensure t
    :init (amx-mode))

  ;; make sure we only use magit WHEN WE HAVE GIT :facepalm:
  (use-package magit
    :ensure t
    :when (executable-find "git")
    :bind ("C-x a" . magit-status))

  ;; (use-package go-autocomplete
  ;;   :disabled
  ;;   :init (ac-config-default))

  ;; (use-package go-complete
  ;;   :disabled)

  ;; (use-package go-mode
  ;;   :disabled
  ;;   :init
  ;;   (when-on-unix (setq shell-file-name (executable-find "fish")))
  ;;   (when (memq window-system '(mac ns x))
  ;;     (exec-path-from-shell-initialize)
  ;;     (exec-path-from-shell-copy-env "GOPATH"))
  ;;   (go-eldoc-setup))

  (use-package flyspell
    :ensure t
    :bind ("C-'" . flyspell-auto-correct-previous-pos))

  (use-package org
    :mode ("\\.notes?$" . org-mode)
    :hook (org-mode . (lambda ()
                        (when (or (executable-find "ispell")
                                  (executable-find "aspell"))
                          (flyspell-mode)))))

  (use-package poly-erb
    :ensure t
    :mode "\\.erb")

  (use-package lisp-mode
    :bind (("C-c g" . asdf-add-build-and-makefile))
    :mode "\\.stumpwmrc$")

  (use-package multiple-cursors
    :ensure t
    :bind (("C->" . mc/mark-next-like-this)
           ("C-<" . mc/mark-previous-like-this)
           ("C-c C-<" . mc/mark-all-like-this)))

  (use-package win-switch
    :ensure t
    :bind (("C-x o" . win-switch-dispatch)
           ("C-c o" . win-switch-dispatch-once)))

  (use-package eldoc
    :ensure t
    :hook ((emacs-lisp-mode lisp-interaction-mode ielm-mode org-mode) . eldoc-mode))

  (use-package macrostep
    :ensure t
    :bind (:map emacs-lisp-mode-map
                ("C-c e" . macrostep-expand)))

  (use-package text-mode
    :hook ((text-mode . visual-line-mode)
           (text-mode . turn-on-orgtbl)))

  (use-package sly-macrostep
    :after sly
    :ensure t)

  (use-package sly-named-readtables
    :after sly
    :ensure t)

  (use-package sly-quicklisp
    :after sly
    :ensure t)

  (use-package sly-asdf
    :after sly
    :ensure t)

  (use-package sly
    :ensure t
    :bind (("s-l" . sly)
           :map lisp-mode-map
           ("C-c e" . macrostep-expand))

    :hook ((lisp-mode . sly-editing-mode))

    :config
    (setq sly-words-of-encouragement
          '("Let the hacking commence!"
            "Hacks and glory await!"
            "Hack and be merry!"
            "Your hacking starts... NOW!"
            "May the source be with you!"
            "Take this REPL, may it serve you well."
            "Lemonodor-fame is but a hack away!"
            "Are we consing yet?"))
     (setq sly-lisp-implementations
           '((roswell ("ros" "-Q" "run"))
             (roswell-4gb-dynamic ("ros" "run" "--" "--dynamic-space-size" "4Gb"))))
    (add-hook 'sly-mrepl-mode-hook
              (lambda () (electric-pair-local-mode -1)))
    (add-hook 'sly-mode-hook
              (lambda ()
                (unless (sly-connected-p)
                  (save-excursion (sly)))))

    :custom
    ((sly-contribs '(sly-fancy sly-macrostep sly-quicklisp sly-asdf sly-named-readtables))
     (sly-default-lisp 'roswell-4gb-dynamic)))

  (use-package elpy
    :disabled
    :hook python-mode
    :custom
    (venv-location (focks/stringify (getenv "HOME") "/programming/python/")))

  (use-package emojify
    :ensure t
    :hook (after-init . global-emojify-mode)
    :custom
    (emojify-display-style
     (focks/os-cond
      ((macos unix) 'unicode)
      (t 'image))))

  (use-package nerd-icons
    :ensure t
    :config
    (unless (focks/font-available-p "Symbols Nerd Font Mono")
      (os-cond
       (windows (message "Please install nerd fonts manually thx :-*"))
       (t (nerd-icons-install-fonts))))
    :custom
    (nerd-icons-font-family "Symbols Nerd Font Mono"))

Some final things

Finally, after everything else we check for our local configuration file and load it.

  (load focks/local-file)