dotconf/.emacs.d/readme.org

23 KiB

Emacs Configuration

Literate emacs configuration

I can never rembember what various configs do. Jumping on this literate emacs config to see if it helps!

Set some ENV vars

These are needed to make emacs aware of various things (like Go binaries, etc). I have them here because sometimes when emacs is launched from rofi or similar it doesn't have the same ENV as it does when launching from the shell.

  (defun expand-if-exists (d)
    ;; expand-if-exists(d) expands d only if d exists
    (if (file-exists-p d)
        (expand-file-name d)))

  (let ((paths
         '(
           "/usr/local/bin"
           "/usr/local/jdk-11/bin"
           "/usr/ports/infrastructure/bin"
           "~/bin"
           "~/go/bin"
           "~/opt/bin"
           "/usr/local/plan9/bin"
           )
         )
        )

    (setenv "PATH" (concat (getenv "PATH") (mapconcat 'expand-if-exists (remove nil paths) ":")))
    (setq exec-path (remove "" (split-string (getenv "PATH") ":"))))

Start the emacs server

Starting as a server lets me connect externally to do things like change themes, save buffers via cron and other such dumbary!

(load "server")
(unless (server-running-p) (server-start))

OpenBSD Lockups

For some reason OpenBSD hangs, these seem to help a bit

(setq x-select-enable-clipboard-manager nil)
(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
(setf x-selection-timeout 500)

Interface and Behavior

Interface

Global font

  ;;(set-default-font "Go Regular")

Use 80 columns, this helps keep things readable when windows are split

(setq whitespace-style '(trailing lines space-before-tab)
      whitespace-line-column 80)
(setq-default fill-column 80)

I know I am in emacs, don't need to see the startup screen.

(setq inhibit-startup-screen t)

If we are on OpenBSD, fill the scratch buffer with fortune \o/.

(if (file-executable-p "/usr/games/fortune")
    (setq initial-scratch-message
	  (concat
	   (shell-command-to-string "fortune | sed -e 's/^/;; /g'")
	   "\n\n")))
Use UTF8 where ever possible
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
Change various UI bits
(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
(column-number-mode +1)
(global-font-lock-mode 1)

Behavior

Switch various defaults to be more comfortable for myself.

(fset 'yes-or-no-p 'y-or-n-p)
(show-paren-mode t)
(desktop-save-mode)
(setq backup-directory-alist '(("." . "~/.emacs-saves")))
(setq auto-mode-alist
      (append
       (list
	'("\\.gpg$" . sensitive-minor-mode)
	)
       auto-mode-alist))
(setq auth-sources
      '((:source "~/.netrc")))

Use spelling and auto-fill when we are in text mode.

(add-hook 'text-mode-hook (lambda ()
			    (auto-fill-mode 1)
			    (turn-on-flyspell)))

This fixes some tramp "waiting for prompt" errors.

  ;;(setq trarmp-shell-prompt-pattern "\\(?:^\\|\r\\)[^]#$%>λ\n]*#?[]#$%>λ].* *\\(^[\\[[0-9;]*[a-zA-Z] *\\)*")
  ;;(require 'tramp-sh nil t)
  ;;(setf tramp-ssh-controlmaster-options
  ;;      (concat
  ;;       "-o ControlPath=/tmp/ssh-%%r@%%h:%%p "
  ;;       "-o ControlMaster=auto -o ControlPersist=yes"))

If things aren't working the way we want:

(setq tramp-verbose 6)

Include ports site-lisp

On OpenBSD various packages (mu, git.. etc) install elisp things into a global directory, this makes sure we include it.

(if (file-directory-p "/usr/local/share/emacs/site-lisp")
    (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/"))

Unset custom-file

The customization file mostly just causes churn in the SCM so we disable it here.

(setq custom-file (make-temp-file ""))

Ensure packages are pinned and installed

This makes sure use-package installs things (and makes it so we don't need :ensure t set for every package.

(setq use-package-always-ensure t)
;;(setq use-package-always-pin "melpa-stable")

Packages

parchment-theme

This is a nice theme that resembles acme in plan9. Minimal.

(use-package parchment-theme
  :config (load-theme 'parchment t))

keychain-environment

I make heavy use of ssh-agent this lets emacs pickup / use the existing agents I have running.

(use-package keychain-environment
  ;;:pin "melpa"
  :init
  (keychain-refresh-environment))

ivy

ivy is fantastic. It gives me nice visual search for buffers, code.. etc. Combined with smex for sorting (shows last used things first) and counsel (extends ivy into various areas like the help stuff).

  (use-package counsel)
  (use-package smex)
  (use-package ivy
    :hook (after-init . ivy-mode)
    :bind
    ("C-s"     . swiper)
    ("M-x"     . counsel-M-x)
    ("C-x C-f" . counsel-find-file)
    ("C-x b"   . ivy-switch-buffer)
    ("C-c n"   . counsel-fzf))

magit

Magit is a awesome. Not sure what else to say about it. :P

(use-package magit
  :bind ("C-c m" . magit-status)
  :init
  (setq magit-completing-read-function 'ivy-completing-read))

flycheck

flycheck does automatic syntax checking for most things

(use-package flycheck
  :init (global-flycheck-mode))
  • [2020-05-29 Fri] Unfortunately it clobbers the "C-c !" prefix, so we need to add this to get it back:
(define-key flycheck-mode-map (kbd "C-c !") 'org-time-stamp-inactive)

Go configuration

go-add-tags

This lets one select a struct or similar and auto add the `json:"NAME"` bits.

(use-package go-add-tags
  :bind
  ("C-c t" . go-add-tags))

go-mode

This allows for things like gofmt and auto adding / removing of imports.

(use-package go-mode
  :after (go-add-tags lsp-mode)
  :bind
  ("C-c t" . go-add-tags))
(defun lsp-go-install-save-hooks ()
  (add-hook 'before-save-hook #'lsp-format-buffer t t)
  (add-hook 'before-save-hook #'lsp-organize-imports t t))
(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)

go-eldoc

This extends eldoc to be able to speak Go - quite handy for quickly looking up what things do.

(use-package go-eldoc
  :after (go-mode lsp-mode)
  :hook
  (go-mode . go-eldoc-setup))

yasnippet

Some go tools use this.

(use-package yasnippet
  :commands yas-minor-mode
  :hook (go-mode . yas-minor-mode))

lsp-mode

lsp-mode supports language servers for various things. I pretty much only care about Go and Ruby.

  (use-package lsp-mode
    :hook ((go-mode    . lsp-deferred)
           (ruby-mode  . lsp))
    :commands (lsp lsp-deferred))

company and friends

company allows for auto-completion of various things. It can interface with lsp-mode to complete things like Go.

(use-package company
  :config
  (setq company-tooltip-limit 20
	company-minimum-prefix-length 1
	company-idle-delay .3
	company-echo-delay 0)
  :hook (prog-mode . company-mode))

(use-package company-lsp
  :commands company-lsp)

gitgutter

This gives me a nice in-ui way to see modifications and what not.

(use-package git-gutter
  :hook
  (after-init . global-git-gutter-mode))

nix

Add support for nix files. I don't use nix much atm, but it was recently ported to OpenBSD, so I am hopeful I can start using it there more!

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

shell

I don't often use the shell from emacs, but when I do these bits make it easier for me to treat it like a regular shell.

  ;; Kill terminal buffers on exit so I din't have to kill the buffer after I exit.
  (defadvice term-handle-exit
      (after term-kill-buffer-on-exit activate)
    (kill-buffer))

pinboard

A pinboard.in client

(use-package pinboard)

restclient

(use-package restclient
  ;;:pin "melpa"
  :mode (("\\.http$" . restclient-mode)))

treemacs

This gives me a decent "sidebar" that is project oriented - the workspaces are handy too.

  • C-s C-w s to switch workspaces.
  • C-s C-w e to edit workspaces.
(use-package treemacs
  :config
  (progn
    (setq treemacs-collapse-dirs                 (if treemacs-python-executable 3 0)
	  treemacs-deferred-git-apply-delay      0.5
	  treemacs-directory-name-transformer    #'identity
	  treemacs-display-in-side-window        t
	  treemacs-eldoc-display                 t
	  treemacs-file-event-delay              5000
	  treemacs-file-extension-regex          treemacs-last-period-regex-value
	  treemacs-file-follow-delay             0.2
	  treemacs-file-name-transformer         #'identity
	  treemacs-follow-after-init             t
	  treemacs-git-command-pipe              ""
	  treemacs-goto-tag-strategy             'refetch-index
	  treemacs-indentation                   2
	  treemacs-indentation-string            " "
	  treemacs-is-never-other-window         nil
	  treemacs-max-git-entries               5000
	  treemacs-missing-project-action        'ask
	  treemacs-move-forward-on-expand        nil
	  treemacs-no-png-images                 nil
	  treemacs-no-delete-other-windows       t
	  treemacs-project-follow-cleanup        nil
	  treemacs-persist-file                  (expand-file-name ".cache/treemacs-persist" user-emacs-directory)
	  treemacs-position                      'left
	  treemacs-recenter-distance             0.1
	  treemacs-recenter-after-file-follow    nil
	  treemacs-recenter-after-tag-follow     nil
	  treemacs-recenter-after-project-jump   'always
	  treemacs-recenter-after-project-expand 'on-distance
	  treemacs-show-cursor                   nil
	  treemacs-show-hidden-files             t
	  treemacs-silent-filewatch              nil
	  treemacs-silent-refresh                nil
	  treemacs-sorting                       'alphabetic-asc
	  treemacs-space-between-root-nodes      t
	  treemacs-tag-follow-cleanup            t
	  treemacs-tag-follow-delay              1.5
	  treemacs-user-mode-line-format         nil
	  treemacs-user-header-line-format       nil
	  treemacs-width                         35)

    (treemacs-follow-mode t)
    (treemacs-filewatch-mode t)
    (treemacs-fringe-indicator-mode t)
    (pcase (cons (not (null (executable-find "git")))
		 (not (null treemacs-python-executable)))
      (`(t . t)
       (treemacs-git-mode 'deferred))
      (`(t . _)
       (treemacs-git-mode 'simple))))
  :bind
  (:map global-map
	("M-0"       . treemacs-select-window)
	("C-x t 1"   . treemacs-delete-other-windows)
	("C-x t t"   . treemacs)
	("C-x t B"   . treemacs-bookmark)
	("C-x t C-t" . treemacs-find-file)
	("C-x t M-t" . treemacs-find-tag)))

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

plantuml

plantuml is a pretty easy way to make decent looking flow chart sorta things.

(use-package plantuml-mode
  :config
  (progn
    (setq org-plantuml-jar-path (expand-file-name "~/Docs/plantuml.jar"))
    (add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
    (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t)))))

Mail

mu has been the best mail client for me on emacs.

General mail configuration

(require 'smtpmail)
(setq user-mail-address              "aaron@bolddaemon.com"
      user-full-name                 "Aaron Bieber"
      message-send-mail-function     'smtpmail-send-it
      message-kill-buffer-on-exit    t
      smtpmail-smtp-user             "qbit@fastmail.com"
      smtpmail-smtp-server           "smtp.fastmail.com"
      smtpmail-smtp-service          465
      smtpmail-default-smtp-server   "smtp.fastmail.com"
      smtpmail-stream-type           'ssl)

mu4e specific configs

  (if (file-exists-p "/usr/local/share/emacs/site-lisp/mu4e/mu4e.el")
      (progn
        (load "/usr/local/share/emacs/site-lisp/mu4e/mu4e.el")
        (require 'mu4e)

        (require 'org-mu4e)
        (setq mail-user-agent 'mu4e-user-agent
              mu4e-get-mail-command "mbsync fastmail"
              mu4e-update-interval 420
              mu4e-compose-context-policy nil
              mu4e-context-policy 'pick-first
              mu4e-drafts-folder "/Drafts"
              mu4e-sent-folder   "/Sent Items"
              mu4e-trash-folder  "/Trash"
              mu4e-maildir-shortcuts
              '( ("/INBOX"        . ?i)
                 ("/Archive"      . ?a)
                 ("/Sent Items"   . ?s))
              org-mu4e-link-query-in-headers-mode nil
              mu4e-attachment-dir
              (lambda (fname mtype)
                (cond
                 ((and fname (string-match "\\.diff$" fname))  "~/patches")
                 ((and fname (string-match "\\.patch$" fname))  "~/patches")
                 ((and fname (string-match "\\.diff.gz$" fname))  "~/patches")
                 (t "~/Downloads")))
              mu4e-bookmarks
              `(,(make-mu4e-bookmark
                  :name "Inbox"
                  :query "maildir:/Inbox AND NOT flag:trashed"
                  :key ?i)
                ,(make-mu4e-bookmark
                  :name  "Unread messages"
                  :query "flag:unread AND NOT flag:trashed AND NOT list:ports-changes.openbsd.org AND NOT list:source-changes.openbsd.org"
                  :key ?u)
                ,(make-mu4e-bookmark
                  :name  "Today's messages"
                  :query (concat
                          "date:today..now"
                          " AND NOT flag:trashed"
                          " AND NOT list:ports-changes.openbsd.org"
                          " AND NOT list:source-changes.openbsd.org")
                  :key ?d)
                ,(make-mu4e-bookmark
                  :name  "Last 7 days"
                  :query "date:7d..now AND NOT flag:trashed"
                  :key ?w)
                ,(make-mu4e-bookmark
                  :name  "Hackers"
                  :query "list:hackers.openbsd.org AND NOT flag:trashed"
                  :key ?h)
                ,(make-mu4e-bookmark
                  :name   "Bugs"
                  :query  "list:bugs.openbsd.org AND NOT flag:trashed"
                  :key ?b)
                ,(make-mu4e-bookmark
                  :name  "Tech"
                  :query "list:tech.openbsd.org AND NOT flag:trashed"
                  :key ?t)
                ,(make-mu4e-bookmark
                  :name  "Ports"
                  :query "list:ports.openbsd.org AND NOT flag:trashed"
                  :key ?p)
                ,(make-mu4e-bookmark
                  :name "Misc"
                  :query "list:misc.openbsd.org AND NOT flag:trashed"
                  :key ?m)
                ,(make-mu4e-bookmark
                  :name "9front"
                  :query "list:9front.9front.org AND NOT flag:trashed"
                  :key ?9)
                ,(make-mu4e-bookmark
                  :name "GOT"
                  :query "list:gameoftrees.openbsd.org AND NOT flag:trashed"
                  :key ?g)))))

org-mode

Oh org-mode. It's the reason I started using emacs.. and it's the reason I can't quit!

Publish bits

I publish some of my notes on suah.dev/p. Also some recipes.

  (setq my-org-publish-alist
        '(("notes" :components ("org-notes" "notes-static"))
          ("deftly" :components ("deftly-blog" "deftly-static"))
          ("org-notes"
           :auto-preamble t
           :auto-sitemap t
           :headline-levels 4
           :publishing-directory "/ssh:suah.dev:/var/www/htdocs/p/"
           :publishing-function org-html-publish-to-html
           :recursive t
           :section-numbers nil
           :html-head "<link rel=\"stylesheet\" href=\"https://suah.dev/p/css/stylesheet.css\" type=\"text/css\" />"
           :html-link-home "http://suah.dev/p/"
           :html-link-up "../"
           :style-include-default nil
           :sitemap-filename "index.org"
           :sitemap-title "Notes"
           :with-title t
           :author-info nil
           :creator-info nil
           :base-directory "~/org/notes")
          ("deftly-blog"
           :auto-preamble t
           :auto-sitemap t
           :headline-levels 1
           :publishing-directory "/ssh:suah.dev:/var/www/deftly/new/"
           :publishing-function org-html-publish-to-html
           :recursive t
           :section-numbers nil
           :html-head "<link rel=\"stylesheet\" href=\"https://deftly.net/new/css/stylesheet.css\" type=\"text/css\" />"
           :html-link-home "http://deftly.net/new"
           :html-link-up "../"
           :style-include-default nil
           :sitemap-title "Deftly.net"
           :with-title t
           :author-info nil
           :creator-info nil
           :base-directory "~/org/deftly")
          ("notes-static"
           :base-directory "~/org/notes"
           :publishing-directory "/ssh:suah.dev:/var/www/htdocs/p/"
           :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg"
           :recursive t
           :publishing-function org-publish-attachment)
          ("deftly-static"
           :base-directory "~/org/deftly"
           :publishing-directory "/ssh:suah.dev:/var/www/deftly/new/"
           :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg"
           :recursive t
           :publishing-function org-publish-attachment)
          ("recipes"
           :auto-preamble t
           :auto-sitemap t
           :headline-levels 4
           :publishing-directory "/ssh:suah.dev:/var/www/htdocs/recipes/"
           :publishing-function org-html-publish-to-html
           :recursive t
           :section-numbers nil
           :html-head "<link rel=\"stylesheet\" href=\"https://suah.dev/p/css/stylesheet.css\" type=\"text/css\" />"
           :html-link-home "http://suah.dev/recipes/"
           :html-link-up "../"
           :style-include-default nil
           :sitemap-filename "index.org"
           :sitemap-title "Recipes"
           :with-title t
           :author-info nil
           :creator-info nil
           :base-directory "~/org/recipes")
          ))

Capture templates

(setq my-org-capture-templates
      `(("t" "TODO"
	 entry (file+headline "~/org/todo.org" "TODOs")
	 ,(concat
	   "* TODO %?\n"
	   ":PROPERTIES:\n"
	   ":LOGGING: TODO(!) WAIT(!) DONE(!) CANCELED(!)\n"
	   ":END:\n") :prepend t)
	("f" "TODO with File"
	 entry (file+headline "~/org/todo.org" "TODOs")
	 ,(concat
	   "* TODO %?\n"
	   ":PROPERTIES:\n"
	   ":LOGGING: TODO(!) WAIT(!) DONE(!) CANCELED(!)\n"
	   ":END:\n"
	   "%i\n  %a") :prepend t)
	("b" "Bug"
	 entry (file+olp+datetree "~/org/bugs.org" "Bugs")
	 "* BUG %?\nEntered on %U\n  :PROPERTIES:\n  :FILE: %a\n  :END:\n" :prepend t)
	("p" "Protocol"
	 entry (file+headline "~/org/links.org" "Links")
	 "* %^{Title}\nSource: %u, %c\n #+BEGIN_QUOTE\n%i\n#+END_QUOTE\n\n\n%?")
	("L" "Protocol Link" entry (file+headline "~/org/links.org" "Links")
	 "* %? %:link\n%:description\n")
	("j" "Journal"
	 entry (file+olp+datetree "~/org/journal.org")
	 "* %?\nEntered on %U\n  %i\n")))

org

  (use-package org
    ;;:pin "org"
    :ensure org-plus-contrib
    :hook
    (org-mode . (lambda ()
                  (turn-on-flyspell)
                  (auto-revert-mode)
                  (auto-fill-mode 1)))
    :bind
    ("C-c c" . org-capture)
    ("C-c p" . org-publish)
    ("C-c l" . org-store-link)
    ("C-c a" . org-agenda)
    ("C-c b" . org-iswitchb)
    :config
    (load-library "find-lisp")
    (setq org-directory "~/org"
          org-agenda-files (find-lisp-find-files "~/org" "\.org$")
          org-startup-indented t
          org-log-done 'time
          org-log-into-drawer t
          org-src-tab-acts-natively t
          org-agenda-skip-scheduled-if-deadline-is-shown t
          org-todo-keywords '((sequence "TODO(t)" "|" "DONE(d)")
                              (sequence "REPORT(r)" "BUG(b)" "KNOWNCAUSE(k)" "|" "FIXED(f)")
                              (sequence "|" "CANCELED(c)")))
    (setq org-publish-project-alist my-org-publish-alist)
    (setq org-capture-templates my-org-capture-templates))

org-roam

  ;; (use-package org-roam
  ;;   :hook
  ;;   (after-init . org-roam-mode)
  ;;   :custom
  ;;   (org-roam-directory "~/org-roam")
  ;;   :bind (:map org-roam-mode-map
  ;;               (("C-c n l" . org-roam)
  ;;                ("C-c n f" . org-roam-find-file)
  ;;                ("C-c n g" . org-roam-graph-show))
  ;;               :map org-mode-map
  ;;               (("C-c n i" . org-roam-insert))
  ;;               (("C-c n I" . org-roam-insert-immediate))))

org-brain

  ;; (use-package org-brain
  ;;   :init
  ;;   (setq org-brain-path "~/org/brain")
  ;;   :config
  ;;   (bind-key "C-c b" 'org-brain-prefix-map org-mode-map)
  ;;   (setq org-id-track-globally t)
  ;;   (setq org-id-locations-file "~/org/.org-id-locations")
  ;;   (add-hook 'before-save-hook #'org-brain-ensure-ids-in-buffer)
  ;;   (push '("b" "Brain" plain (function org-brain-goto-end)
  ;;           "* %i%?" :empty-lines 1)
  ;;         org-capture-templates)
  ;;   (setq org-brain-visualize-default-choices 'all)
  ;;   (setq org-brain-title-max-length 12)
  ;;   (setq org-brain-include-file-entries nil
  ;;         org-brain-file-entries-use-title nil))

Extra bits

(use-package org-journal
  :defer t
  :config
  (setq org-journal-dir "~/org/journal/"
	org-journal-file-format "%Y/%m-%d"
	org-journal-date-format "%A, %d %B %Y"))

Add in some org-mode helpers:

  • org-habit lets me keep track of TODOs and other things.
  • org-checklist lets me reset checklists for reoccurring tasks.

    • This requires one to pkg_add a2ps.
    • RESET_CHECK_BOXES property to be set to t on a task headline. (properties can be set via C-c C-x d
(require 'org-habit)
(require 'org-checklist)

Found this bad boy to integrate pinboard with org-mode:

(defun org-pinboard-store-link ()
  "Store a link taken from a pinboard buffer."
  (when (eq major-mode 'pinboard-mode)
    (pinboard-with-current-pin pin
      (org-store-link-props
       :type "pinboard"
       :link (alist-get 'href pin)
       :description (alist-get 'description pin)))))

(org-link-set-parameters "pinboard"
			 :follow #'browse-url
			 :store #'org-pinboard-store-link)

Custom agenda commands for various things.

  • Daily habits shows how well I am keeping track of daily things.
(setq org-agenda-custom-commands
      '(("h" "Daily habits"
	 ((agenda ""))
	 ((org-agenda-show-log t)
	  (org-agenda-ndays 7)
	  (org-agenda-log-mode-items '(state))))))