Pre-Doom Emacs

was my default, now aiming for “just comfortable enough to get work done”

Leans heavily on use-package, perhaps to excess. Anywhere I can configure something via the use-package macro, I do.

Over in Windows, Emacs doesn’t assume I want UTF-8. So first things first I’m going to spell that out.

  (prefer-coding-system 'utf-8)
    (prefer-coding-system 'utf-8)
    ;;
    ;; package manager setup
    ;;
    ;; Package not installing?
    ;;  Try 'M-x package-refresh-contents'

    (require 'package)

    ;; via https://mastodon.social/@genehack/103737652356761968
    (defvar bmw/packages-refreshed nil)

    (defadvice package-install (before refresh activate)
      "Call package-refresh-contents` before the first package-install."
      (unless (eq bmw/packages-refreshed t)
        (progn
          (package-refresh-contents)
          (setq bmw/packages-refreshed nil))))

    (add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/"))
    (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

    (setq package-enable-at-startup nil)
    (package-initialize)

    (unless (package-installed-p 'use-package)
      (package-install 'use-package))

    (eval-when-compile
      (require 'use-package))
    ;; Arbitrarily chosen, but assigned to a variable for consistency.
    (setq bmw/line-length 100)

    ;; Remove the distractions I don't care for
    (setq-default visible-bell t)
    (setq-default fill-column bmw/line-length)
    (setq-default left-margin-width 2)
    (setq-default right-margin-width 2)
    (setq-default indent-tabs-mode nil)  ; I prefer spaces
    (setq-default tab-width 2)
    (setq-default vc-follow-symlinks nil)
    (setq x-underline-at-descent-line t)
    (setq inhibit-startup-message t
          inhibit-startup-echo-area-message t)

      (if (eq system-type 'windows-nt)
          (setq inhibit-compacting-font-caches t))
      ;; Hugo gets confused by Emacs lockfiles
      (setq-default create-lockfiles nil)
      (setq bmw/org-dir
            (expand-file-name "~/Dropbox/org/"))

      (setq bmw/org-agenda-dir (file-name-as-directory (concat bmw/org-dir "agendas")))
      (setq bmw/org-journal-dir (file-name-as-directory (concat bmw/org-dir "journals")))
      (setq bmw/org-templates-dir (file-name-as-directory (concat bmw/org-dir "templates")))
      (setq bmw/org-config-dir (file-name-as-directory (concat bmw/org-dir "config")))
      (setq bmw/org-site-dir (file-name-as-directory "~/Sites/rgb-hugo/org"))

      (setq
       bmw/org-main (concat bmw/org-agenda-dir "main.org")
       bmw/org-beorg-inbox (concat bmw/org-dir "inbox.org")
       bmw/org-journal (concat bmw/org-journal-dir "journal.org")
       bmw/org-money (concat bmw/org-dir "money.org")
       bmw/org-config-main (concat bmw/org-config-dir "emacs.org")
       bmw/org-ox-site (concat bmw/org-site-dir "site.org"))

      (setq bmw/org-agenda-files
            (list bmw/org-main
                  bmw/org-journal
                  bmw/org-beorg-inbox
                  bmw/org-money
                  bmw/org-config-main
                  bmw/org-ox-site))
    (global-set-key (kbd "<f5>")
                    (lambda ()
                      (interactive)
                      (find-file bmw/org-config-main)
                      (message "Opened: %s" (buffer-name))))

  (add-to-list 'load-path
               (file-name-as-directory
                (concat bmw/org-config-dir "local-lisp")))

  ;;
  ;; My own special mode hooks
  ;;

  ;; base mode for coding
  (add-hook 'prog-mode-hook (lambda ()
                              (setq-local display-line-numbers t)))
  (add-hook 'cperl-mode-hook (lambda ()
                              (setq-local display-line-numbers t)))
  (use-package generic-x)

    (use-package color)

    (defun bmw/color-dim (steps)
      (apply 'color-rgb-to-hex
             (car (color-gradient
                   (color-name-to-rgb (face-attribute 'default :background))
                   (color-name-to-rgb (face-attribute 'default :foreground))
                   steps))))
    (set-face-attribute 'default nil
                        :family "IBM Plex Mono"
                        :height 130
                        :weight 'normal
                        :width 'normal)

    (set-face-attribute 'fixed-pitch nil
                        :family "IBM Plex Mono"
                        :height 130
                        :weight 'normal
                        :width 'normal)

    (set-face-attribute 'variable-pitch nil
                        :family "IBM Plex Serif"
                        :height 160
                        :weight 'normal
                        :width 'normal)

    (use-package face-remap
      :custom-face
      (font-lock-keyword-face ((t (:inherit fixed-pitch :background nil))))
      (line-number ((t (:inherit fixed-pitch))))

      (whitespace-indentation ((t (:inherit error))))
      (whitespace-space ((t (:inherit shadow))))
      (whitespace-tab ((t (:inherit font-lock-comment-face)))))
    ;; locks and autosaves all over the place just annoy me
    ;; TODO: set up proper autosave
    (use-package files
      :custom
      (auto-save-default nil)
      (auto-save-file-name-transforms `((".*" ,(concat user-emacs-directory "autosaves"))))
      (backup-directory-alist `(("." . ,(concat user-emacs-directory "backups")))))
    (use-package goto-addr
      :ensure t
      :hook
      ((text-mode prog-mode) . goto-address-mode))

  (use-package scroll-bar
    :init
    (scroll-bar-mode -1))

  ;; Fine-tune basic editing commands
  (use-package simple
    :init
    ;; Invoke M-x without Alt
    (global-set-key "\C-x\C-m" 'execute-extended-command)
    (column-number-mode)
    :hook
    ((prog-mode) . turn-on-auto-fill))

  (use-package tool-bar
    :init
    (tool-bar-mode -1))

    ;; ensure environment in Emacs is environment in shell
    (if (not (eq system-type 'windows-nt))
        (use-package exec-path-from-shell
          :ensure t
          :config
          (setq exec-path-from-shell-check-startup-files nil
                exec-path-from-shell-variables
                '("PLENV_ROOT"
                  "PATH" "MAN_PATH"
                  "RAKUBREW_HOME"))
          (exec-path-from-shell-initialize)))
      (defun bmw/theme-whitespace ()
        "Apply my own face-attribute changes after loading a custom theme"
        (set-face-attribute 'whitespace-space nil
                            :background (face-attribute 'font-lock-comment-face :background)
                            :foreground (bmw/color-dim 3)))

    (use-package whitespace
      :ensure t
      :preface
      (defun bmw/whitespace-mode ()
        (unless (eq major-mode 'org-mode)
          (progn
            (whitespace-mode)
            (bmw/theme-whitespace))))
      :custom
      (whitespace-action '(auto-cleanup))
      (whitespace-line-column bmw/line-length)
      (whitespace-style
       '(face lines trailing empty tabs spaces indentation space-mark tab-mark))
      :hook
      ((prog-mode text-mode) . bmw/whitespace-mode))
    (use-package adoc-mode
      :ensure t
      :mode "\\.adoc")
    (use-package deft
      :ensure t
      :init
      (setq deft-extensions '("md" "org" "txt"))
      :config
      (setq deft-markdown-mode-title-level 1)
      (setq deft-use-filename-as-title t)
      (setq deft-file-naming-rules
            '((noslash . "-")
              (nospace . "-")
              (case-fn . downcase)))
      (setq deft-directory "~/Dropbox/org/notes"))
    ;; elscreen for sessions
    (use-package elscreen
      :ensure t
      :init
      (elscreen-start))
    (use-package gnuplot
      :ensure t)
    (use-package lua-mode
      :ensure t
      :mode ("\\.lua\\'" "\\.luac\\'")
      :config (setq lua-indent-level 2))
    (use-package org-bullets
      :ensure t
      :custom
      (org-bullets-bullet-list '("◉" "○"))
      :hook (org-mode . org-bullets-mode))
      (use-package olivetti
        :ensure t
        :custom
        (olivetti-body-width bmw/line-length)
        :hook (text-mode . olivetti-mode))

    (use-package org
      :ensure org-plus-contrib
      :defer t
      :custom
      (org-agenda-files bmw/org-agenda-files)
      (org-babel-python-command "python3")
      (org-catch-invisible-edits 'show-and-error)
      (org-clock-persist t)
      (org-confirm-babel-evaluate nil)
      (org-display-inline-images t)
      (org-edit-src-content-indentation 2)
      (org-directory bmw/org-dir)
      (org-ellipsis "⬎")
      (org-enforce-todo-dependencies t)
      (org-fontify-quote-and-verse-blocks t)
      (org-log-into-drawer t)
      (org-log-done 'time)
      (org-log-redeadline 'time)
      (org-outline-path-complete-in-steps nil)
      (org-redisplay-inline-images t)
      (org-refile-targets '((org-agenda-files :maxlevel . 6)))
      (org-refile-use-outline-path 'file)
      (org-log-reschedule 'time)
      (org-src-fontify-natively t)
      (org-src-tab-acts-natively t)
      (org-startup-indented t)
      (org-startup-with-inline-images "inlineimages")

        (org-capture-templates
         (quote (("t" "Task")
                 ("ts" "Site" entry
                  (file+olp bmw/org-main "Tasks" "Site")
                  (file "templates/generic-task.txt") :clock-in t :clock-resume t)
                 ("tg" "General" entry
                  (file+olp bmw/org-main "Tasks" "General")
                  (file "templates/generic-task.txt") :clock-in t :clock-resume t)
                 ("tn" "Now" entry
                  (file+olp bmw/org-main "Tasks" "General")
                  "* NOW %? \nDEADLINE: %t")
                 ("ti" "Idea" entry
                  (file+olp bmw/org-main "Tasks" "Ideas")
                  (file "templates/idea.txt"))

                 ("j" "Journal Entry" entry
                  (file+datetree bmw/org-journal "Journal")
                  (file "templates/journal.txt"))

                 ("m" "Money")
                 ("me" "Money Expense" entry
                  (file+datetree bmw/org-money "Transactions")
                  (file "templates/money-expense.txt"))
                 ("mi" "Money Income" entry
                  (file+datetree bmw/org-money "Transactions")
                  (file "templates/money-income.txt")))))

      ;; just in case it's not set in the org file
      (org-todo-keywords
       '((type "TASK(t)" "NOW(n!)" "|" "DONE(d!)")      ; the main flow
         (type "MAYBE(m) WAIT(w)" "|" "CANCEL(c@)")))  ; common branches

      :init
      (global-set-key (kbd "C-c l") 'org-store-link)
      (global-set-key (kbd "C-c c") 'org-capture)
      (global-set-key (kbd "C-c a") 'org-agenda)
      (advice-add 'org-archive-default-command :after #'org-save-all-org-buffers)

      :config
      (turn-off-auto-fill)
      (setq-local display-line-numbers nil)
      (setq-local org-fontify-whole-heading-line t)
      (setq-local org-fontify-done-headline t)

        (org-babel-do-load-languages
         'org-babel-load-languages
         '((dot , t)
           (gnuplot . t)
           (shell . t)
           (ruby . t)
           (R . t)
           (python . t)
           (sql . t)
           (sqlite . t)
           (perl . t)))
      (org-clock-persistence-insinuate))
    ;; markdown-mode
    (use-package markdown-mode
      :ensure t
      :mode (("README\\.md\\'" . gfm-mode)
             ("\\.md\\'" . markdown-mode)
             ("\\.markdown\\'" . markdown-mode))
      :init (setq markdown-command "mmark"))
    ;; writegood-mode
    (use-package writegood-mode
      :ensure t
      :config
      (global-set-key (kbd "C-c g") 'writegood-mode)
      (add-hook 'text-mode-hook 'writegood-mode))
    (use-package yaml-mode
      :ensure t)
    ;; Development with the Crystal language
    (use-package crystal-mode
      :ensure t
      :mode ("\\.cr$" . crystal-mode)
      :interpreter ("crystal" . crystal-mode)
      :config
      (autoload 'crystal-mode "crystal-mode" "Major mode for Crystal files" t))

    (use-package ob-crystal
      :ensure t)
      (require 'tramp)
      (require 'perltidy)

      (if (eq system-type 'windows-nt)
          (setq-default perltidy-program "perltidy.bat"))

      (add-hook 'cperl-mode-hook (lambda ()
                                   (local-set-key (kbd "C-c t") 'perltidy-dwim)))
      (use-package cperl-mode
        :ensure t
        :defer t
        :custom
          ;; automatic indent after semicolon insertion
          (cperl-autoindent-on-semi nil)

          ;; automatically newline before braces, and after braces, colons, and semi-colons
          (cperl-auto-newline nil)

          ;; "not overwrite C-h f"?
          (cperl-clobber-lisp-bindings t)

          ;; extra indent for substatements that start with close parens
          (cperl-close-paren-offset -2)

          ;; extra indent for lines not starting new statements
          (cperl-continued-statement-offset 2)

          ;; keywords are electric in cperl-mode
          (cperl-electric-keywords nil)

          ;; { after $ should be preceded by a space(?)
          (cperl-electric-lbrace-space nil)

          ;; linefeed should be hairy in cperl (otherwise `C-c <return>')
          (cperl-electric-linefeed t)

          ;; parens should be electric in cperl-mode
          (cperl-electric-parens nil)

          ;; use font-lock-mode in cperl buffers
          (cperl-font-lock t)

          ;; perform additional highlighting on variables (currently just scalars)
          (cperl-highlight-variables-indiscriminately t)

          ;; indentation of cperl statements relative to containing block (default 2)
          (cperl-indent-level 2)

          ;; non-block (), {}, and [] (without trailing `,') are indented as blocks
          (cperl-indent-parens-as-block t)

          ;; amount of space to insert between `}' and `else' or `elsif'
          (cperl-indent-region-fix-constructs 1)

          ;; skip prompts on C-h f
          (cperl-info-on-command-no-prompt t)

          ;; "result of evaluation of this expression highlights trailing whitespace"
          (cperl-invalid-face nil)

          ;; show lazy help after given idle time
          (cperl-lazy-help-time 5)

          ;; always reindent the current line on <tab>
          (cperl-tab-always-indent t)
        :interpreter "perl"
        :mode "\\.\\(cgi\\|psgi\\|t\\)\\'")
      (defalias 'perl-mode 'cperl-mode)

    (require 'digraphs)
    (defadvice load-theme (before clear-previous-themes activate)
      "Clear existing theme settings instead of layering them"
      (mapc #'disable-theme custom-enabled-themes))

      (use-package mixed-pitch
        :ensure t
        :custom
        (mixed-pitch-set-height t)

        :hook
        (text-mode . mixed-pitch-mode))
      (use-package color-theme-modern
        :ensure t
        :config
        (load-theme 'classic t t)
        (enable-theme 'classic))
  (setq custom-file "~/.emacs-bmw/.emacs-custom.el")
  (load custom-file)

Set up package repositories and make sure use-package is in force.

Components

Preface

  ;;
  ;; package manager setup
  ;;
  ;; Package not installing?
  ;;  Try 'M-x package-refresh-contents'

  (require 'package)

  ;; via https://mastodon.social/@genehack/103737652356761968
  (defvar bmw/packages-refreshed nil)

  (defadvice package-install (before refresh activate)
    "Call package-refresh-contents` before the first package-install."
    (unless (eq bmw/packages-refreshed t)
      (progn
        (package-refresh-contents)
        (setq bmw/packages-refreshed nil))))

  (add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/"))
  (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

  (setq package-enable-at-startup nil)
  (package-initialize)

  (unless (package-installed-p 'use-package)
    (package-install 'use-package))

  (eval-when-compile
    (require 'use-package))

Variables

Personal custom variables and settings I haven’t assigned to a package yet.

  ;; Arbitrarily chosen, but assigned to a variable for consistency.
  (setq bmw/line-length 100)

  ;; Remove the distractions I don't care for
  (setq-default visible-bell t)
  (setq-default fill-column bmw/line-length)
  (setq-default left-margin-width 2)
  (setq-default right-margin-width 2)
  (setq-default indent-tabs-mode nil)  ; I prefer spaces
  (setq-default tab-width 2)
  (setq-default vc-follow-symlinks nil)
  (setq x-underline-at-descent-line t)
  (setq inhibit-startup-message t
        inhibit-startup-echo-area-message t)

    (if (eq system-type 'windows-nt)
        (setq inhibit-compacting-font-caches t))
    ;; Hugo gets confused by Emacs lockfiles
    (setq-default create-lockfiles nil)
    (setq bmw/org-dir
          (expand-file-name "~/Dropbox/org/"))

    (setq bmw/org-agenda-dir (file-name-as-directory (concat bmw/org-dir "agendas")))
    (setq bmw/org-journal-dir (file-name-as-directory (concat bmw/org-dir "journals")))
    (setq bmw/org-templates-dir (file-name-as-directory (concat bmw/org-dir "templates")))
    (setq bmw/org-config-dir (file-name-as-directory (concat bmw/org-dir "config")))
    (setq bmw/org-site-dir (file-name-as-directory "~/Sites/rgb-hugo/org"))

    (setq
     bmw/org-main (concat bmw/org-agenda-dir "main.org")
     bmw/org-beorg-inbox (concat bmw/org-dir "inbox.org")
     bmw/org-journal (concat bmw/org-journal-dir "journal.org")
     bmw/org-money (concat bmw/org-dir "money.org")
     bmw/org-config-main (concat bmw/org-config-dir "emacs.org")
     bmw/org-ox-site (concat bmw/org-site-dir "site.org"))

    (setq bmw/org-agenda-files
          (list bmw/org-main
                bmw/org-journal
                bmw/org-beorg-inbox
                bmw/org-money
                bmw/org-config-main
                bmw/org-ox-site))

Hugo file lock workaround

Keep Hugo from complaining about the presence of Emacs file locks.

  ;; Hugo gets confused by Emacs lockfiles
  (setq-default create-lockfiles nil)

This isn’t ideal — file locks are a good thing. OTOH I’m pretty much the only person on my system, so I have nobody but myself to blame if a missing file lock breaks something.

But without it?

  $ hugo
  Building sites … Built in 48 ms
  Error: Error building site: process: readAndProcessContent: walk: Readdir: decorate: lstat /home/random/Sites/rgb-hugo/content/post/2020/04/from-dotfiles-to-org-file/random@geekery.3137437:1588601341: no such file or directory

Still an open issue as of 2020-05-09, using Hugo version:

  Hugo Static Site Generator v0.69.2/extended linux/amd64 BuildDate: unknown

Inhibit compacting font caches on windows

Had a bad problem with performance on a Windows laptop — but one that didn’t show up on my Windows desktop PC, oddly. Eventually narrowed it down to org-bullets, and then further to a known font cache issue that comes up when using org-bullets on Windows.

  (if (eq system-type 'windows-nt)
      (setq inhibit-compacting-font-caches t))

Fonts

Using VcXsrv as an X server on Windows, and it seems to be working okay. I’m still working on the finer details of variable-pitch-mode and Emacs fonts in general.

Current font choices:

Sitting in front of Pop!_OS at this split second, so here’s the apt instructions.

sudo apt install fonts-ibm-plex

fc-list is the font-listing command I keep forgetting, not xls-fonts

Maybe someday automate stuff like font installation with Ansible or even just a shell script? Maybe, but that kinda depends on me knowing what distro or operating system I’m sitting in front of from one week to the next.

  (set-face-attribute 'default nil
                      :family "IBM Plex Mono"
                      :height 130
                      :weight 'normal
                      :width 'normal)

  (set-face-attribute 'fixed-pitch nil
                      :family "IBM Plex Mono"
                      :height 130
                      :weight 'normal
                      :width 'normal)

  (set-face-attribute 'variable-pitch nil
                      :family "IBM Plex Serif"
                      :height 160
                      :weight 'normal
                      :width 'normal)

  (use-package face-remap
    :custom-face
    (font-lock-keyword-face ((t (:inherit fixed-pitch :background nil))))
    (line-number ((t (:inherit fixed-pitch))))

    (whitespace-indentation ((t (:inherit error))))
    (whitespace-space ((t (:inherit shadow))))
    (whitespace-tab ((t (:inherit font-lock-comment-face)))))

Resources

Org mode

Org mode is why I use Emacs. So yeah this section’s gonna be a little big.

Org-specific packages

Org bullets

  (use-package org-bullets
    :ensure t
    :custom
    (org-bullets-bullet-list '("◉" "○"))
    :hook (org-mode . org-bullets-mode))

Org variables for file organization

Because I move stuff around a lot, it’s less work in the long run to define my org files by building up from this week’s folder layout. In the end I explicitly specify which individual files belong in the agenda. This keeps me from “organizing” my tasks into a thousand different subtopic files. Only six.

So far.

Filename expansion works without fuss on Linux and Windows, so no need to be clever.

Well, you might want to define HOME for yourself in Windows, or Emacs guesses based on what Windows version you’re running.

  (setq bmw/org-dir
        (expand-file-name "~/Dropbox/org/"))

  (setq bmw/org-agenda-dir (file-name-as-directory (concat bmw/org-dir "agendas")))
  (setq bmw/org-journal-dir (file-name-as-directory (concat bmw/org-dir "journals")))
  (setq bmw/org-templates-dir (file-name-as-directory (concat bmw/org-dir "templates")))
  (setq bmw/org-config-dir (file-name-as-directory (concat bmw/org-dir "config")))
  (setq bmw/org-site-dir (file-name-as-directory "~/Sites/rgb-hugo/org"))

  (setq
   bmw/org-main (concat bmw/org-agenda-dir "main.org")
   bmw/org-beorg-inbox (concat bmw/org-dir "inbox.org")
   bmw/org-journal (concat bmw/org-journal-dir "journal.org")
   bmw/org-money (concat bmw/org-dir "money.org")
   bmw/org-config-main (concat bmw/org-config-dir "emacs.org")
   bmw/org-ox-site (concat bmw/org-site-dir "site.org"))

  (setq bmw/org-agenda-files
        (list bmw/org-main
              bmw/org-journal
              bmw/org-beorg-inbox
              bmw/org-money
              bmw/org-config-main
              bmw/org-ox-site))

Capture templates

  (org-capture-templates
   (quote (("t" "Task")
           ("ts" "Site" entry
            (file+olp bmw/org-main "Tasks" "Site")
            (file "templates/generic-task.txt") :clock-in t :clock-resume t)
           ("tg" "General" entry
            (file+olp bmw/org-main "Tasks" "General")
            (file "templates/generic-task.txt") :clock-in t :clock-resume t)
           ("tn" "Now" entry
            (file+olp bmw/org-main "Tasks" "General")
            "* NOW %? \nDEADLINE: %t")
           ("ti" "Idea" entry
            (file+olp bmw/org-main "Tasks" "Ideas")
            (file "templates/idea.txt"))

           ("j" "Journal Entry" entry
            (file+datetree bmw/org-journal "Journal")
            (file "templates/journal.txt"))

           ("m" "Money")
           ("me" "Money Expense" entry
            (file+datetree bmw/org-money "Transactions")
            (file "templates/money-expense.txt"))
           ("mi" "Money Income" entry
            (file+datetree bmw/org-money "Transactions")
            (file "templates/money-income.txt")))))

I use org for config, note-taking, and to a lesser extent site management. That currently means I have org files scattered throughout my system. Some go to the cloud, some to version control. I need org-agenda to be grabby.

apply solution via Reddit. I tried putting it in :custom instead — without setq — but Emacs complained about org-agenda-file-regexp being undefined.

Main org section

    (use-package olivetti
      :ensure t
      :custom
      (olivetti-body-width bmw/line-length)
      :hook (text-mode . olivetti-mode))

  (use-package org
    :ensure org-plus-contrib
    :defer t
    :custom
    (org-agenda-files bmw/org-agenda-files)
    (org-babel-python-command "python3")
    (org-catch-invisible-edits 'show-and-error)
    (org-clock-persist t)
    (org-confirm-babel-evaluate nil)
    (org-display-inline-images t)
    (org-edit-src-content-indentation 2)
    (org-directory bmw/org-dir)
    (org-ellipsis "⬎")
    (org-enforce-todo-dependencies t)
    (org-fontify-quote-and-verse-blocks t)
    (org-log-into-drawer t)
    (org-log-done 'time)
    (org-log-redeadline 'time)
    (org-outline-path-complete-in-steps nil)
    (org-redisplay-inline-images t)
    (org-refile-targets '((org-agenda-files :maxlevel . 6)))
    (org-refile-use-outline-path 'file)
    (org-log-reschedule 'time)
    (org-src-fontify-natively t)
    (org-src-tab-acts-natively t)
    (org-startup-indented t)
    (org-startup-with-inline-images "inlineimages")

      (org-capture-templates
       (quote (("t" "Task")
               ("ts" "Site" entry
                (file+olp bmw/org-main "Tasks" "Site")
                (file "templates/generic-task.txt") :clock-in t :clock-resume t)
               ("tg" "General" entry
                (file+olp bmw/org-main "Tasks" "General")
                (file "templates/generic-task.txt") :clock-in t :clock-resume t)
               ("tn" "Now" entry
                (file+olp bmw/org-main "Tasks" "General")
                "* NOW %? \nDEADLINE: %t")
               ("ti" "Idea" entry
                (file+olp bmw/org-main "Tasks" "Ideas")
                (file "templates/idea.txt"))

               ("j" "Journal Entry" entry
                (file+datetree bmw/org-journal "Journal")
                (file "templates/journal.txt"))

               ("m" "Money")
               ("me" "Money Expense" entry
                (file+datetree bmw/org-money "Transactions")
                (file "templates/money-expense.txt"))
               ("mi" "Money Income" entry
                (file+datetree bmw/org-money "Transactions")
                (file "templates/money-income.txt")))))

    ;; just in case it's not set in the org file
    (org-todo-keywords
     '((type "TASK(t)" "NOW(n!)" "|" "DONE(d!)")      ; the main flow
       (type "MAYBE(m) WAIT(w)" "|" "CANCEL(c@)")))  ; common branches

    :init
    (global-set-key (kbd "C-c l") 'org-store-link)
    (global-set-key (kbd "C-c c") 'org-capture)
    (global-set-key (kbd "C-c a") 'org-agenda)
    (advice-add 'org-archive-default-command :after #'org-save-all-org-buffers)

    :config
    (turn-off-auto-fill)
    (setq-local display-line-numbers nil)
    (setq-local org-fontify-whole-heading-line t)
    (setq-local org-fontify-done-headline t)

      (org-babel-do-load-languages
       'org-babel-load-languages
       '((dot , t)
         (gnuplot . t)
         (shell . t)
         (ruby . t)
         (R . t)
         (python . t)
         (sql . t)
         (sqlite . t)
         (perl . t)))
    (org-clock-persistence-insinuate))

Custom faces

  (org-table ((t (:inherit fixed-pitch))))
  (org-block ((t (:inherit fixed-pitch))))
  (org-meta-line ((t (:inherit (font-lock-comment-face fixed-pitch)))))
  (org-level-1 ((t (:background nil :weight bold :family "IBM Plex Serif" :height 1.5))))
  (org-level-2 ((t (:background nil :weight bold :family "IBM Plex Serif" :height 1.4))))
  (org-level-3 ((t (:background nil :weight bold :family "IBM Plex Serif" :height 1.3))))
  (org-level-4 ((t (:background nil :weight bold :family "IBM Plex Serif" :height 1.2))))
  (org-level-5 ((t (:background nil :weight bold :family "IBM Plex Serif" :height 1.1))))
  (org-level-6 ((t (:background nil :weight bold :family "IBM Plex Serif" :height 160))))

Babel language support

  (org-babel-do-load-languages
   'org-babel-load-languages
   '((dot , t)
     (gnuplot . t)
     (shell . t)
     (ruby . t)
     (R . t)
     (python . t)
     (sql . t)
     (sqlite . t)
     (perl . t)))

Perl

Initially just extracting interesting bits from genehack’s perl.el.

    (require 'tramp)
    (require 'perltidy)

    (if (eq system-type 'windows-nt)
        (setq-default perltidy-program "perltidy.bat"))

    (add-hook 'cperl-mode-hook (lambda ()
                                 (local-set-key (kbd "C-c t") 'perltidy-dwim)))
    (use-package cperl-mode
      :ensure t
      :defer t
      :custom
        ;; automatic indent after semicolon insertion
        (cperl-autoindent-on-semi nil)

        ;; automatically newline before braces, and after braces, colons, and semi-colons
        (cperl-auto-newline nil)

        ;; "not overwrite C-h f"?
        (cperl-clobber-lisp-bindings t)

        ;; extra indent for substatements that start with close parens
        (cperl-close-paren-offset -2)

        ;; extra indent for lines not starting new statements
        (cperl-continued-statement-offset 2)

        ;; keywords are electric in cperl-mode
        (cperl-electric-keywords nil)

        ;; { after $ should be preceded by a space(?)
        (cperl-electric-lbrace-space nil)

        ;; linefeed should be hairy in cperl (otherwise `C-c <return>')
        (cperl-electric-linefeed t)

        ;; parens should be electric in cperl-mode
        (cperl-electric-parens nil)

        ;; use font-lock-mode in cperl buffers
        (cperl-font-lock t)

        ;; perform additional highlighting on variables (currently just scalars)
        (cperl-highlight-variables-indiscriminately t)

        ;; indentation of cperl statements relative to containing block (default 2)
        (cperl-indent-level 2)

        ;; non-block (), {}, and [] (without trailing `,') are indented as blocks
        (cperl-indent-parens-as-block t)

        ;; amount of space to insert between `}' and `else' or `elsif'
        (cperl-indent-region-fix-constructs 1)

        ;; skip prompts on C-h f
        (cperl-info-on-command-no-prompt t)

        ;; "result of evaluation of this expression highlights trailing whitespace"
        (cperl-invalid-face nil)

        ;; show lazy help after given idle time
        (cperl-lazy-help-time 5)

        ;; always reindent the current line on <tab>
        (cperl-tab-always-indent t)
      :interpreter "perl"
      :mode "\\.\\(cgi\\|psgi\\|t\\)\\'")
    (defalias 'perl-mode 'cperl-mode)

  (use-package cperl-mode
    :ensure t
    :defer t
    :custom
      ;; automatic indent after semicolon insertion
      (cperl-autoindent-on-semi nil)

      ;; automatically newline before braces, and after braces, colons, and semi-colons
      (cperl-auto-newline nil)

      ;; "not overwrite C-h f"?
      (cperl-clobber-lisp-bindings t)

      ;; extra indent for substatements that start with close parens
      (cperl-close-paren-offset -2)

      ;; extra indent for lines not starting new statements
      (cperl-continued-statement-offset 2)

      ;; keywords are electric in cperl-mode
      (cperl-electric-keywords nil)

      ;; { after $ should be preceded by a space(?)
      (cperl-electric-lbrace-space nil)

      ;; linefeed should be hairy in cperl (otherwise `C-c <return>')
      (cperl-electric-linefeed t)

      ;; parens should be electric in cperl-mode
      (cperl-electric-parens nil)

      ;; use font-lock-mode in cperl buffers
      (cperl-font-lock t)

      ;; perform additional highlighting on variables (currently just scalars)
      (cperl-highlight-variables-indiscriminately t)

      ;; indentation of cperl statements relative to containing block (default 2)
      (cperl-indent-level 2)

      ;; non-block (), {}, and [] (without trailing `,') are indented as blocks
      (cperl-indent-parens-as-block t)

      ;; amount of space to insert between `}' and `else' or `elsif'
      (cperl-indent-region-fix-constructs 1)

      ;; skip prompts on C-h f
      (cperl-info-on-command-no-prompt t)

      ;; "result of evaluation of this expression highlights trailing whitespace"
      (cperl-invalid-face nil)

      ;; show lazy help after given idle time
      (cperl-lazy-help-time 5)

      ;; always reindent the current line on <tab>
      (cperl-tab-always-indent t)
    :interpreter "perl"
    :mode "\\.\\(cgi\\|psgi\\|t\\)\\'")
  (defalias 'perl-mode 'cperl-mode)

Customized variables

Using :custom for anything that I could have set via Emacs Customization.

  ;; automatic indent after semicolon insertion
  (cperl-autoindent-on-semi nil)

  ;; automatically newline before braces, and after braces, colons, and semi-colons
  (cperl-auto-newline nil)

  ;; "not overwrite C-h f"?
  (cperl-clobber-lisp-bindings t)

  ;; extra indent for substatements that start with close parens
  (cperl-close-paren-offset -2)

  ;; extra indent for lines not starting new statements
  (cperl-continued-statement-offset 2)

  ;; keywords are electric in cperl-mode
  (cperl-electric-keywords nil)

  ;; { after $ should be preceded by a space(?)
  (cperl-electric-lbrace-space nil)

  ;; linefeed should be hairy in cperl (otherwise `C-c <return>')
  (cperl-electric-linefeed t)

  ;; parens should be electric in cperl-mode
  (cperl-electric-parens nil)

  ;; use font-lock-mode in cperl buffers
  (cperl-font-lock t)

  ;; perform additional highlighting on variables (currently just scalars)
  (cperl-highlight-variables-indiscriminately t)

  ;; indentation of cperl statements relative to containing block (default 2)
  (cperl-indent-level 2)

  ;; non-block (), {}, and [] (without trailing `,') are indented as blocks
  (cperl-indent-parens-as-block t)

  ;; amount of space to insert between `}' and `else' or `elsif'
  (cperl-indent-region-fix-constructs 1)

  ;; skip prompts on C-h f
  (cperl-info-on-command-no-prompt t)

  ;; "result of evaluation of this expression highlights trailing whitespace"
  (cperl-invalid-face nil)

  ;; show lazy help after given idle time
  (cperl-lazy-help-time 5)

  ;; always reindent the current line on <tab>
  (cperl-tab-always-indent t)

Formatting

Couldn’t find a “perltidy” package. I downloaded my own copy of perltidy.el. Might make more sense to check it out from the wiki git repo.

  (require 'tramp)
  (require 'perltidy)

  (if (eq system-type 'windows-nt)
      (setq-default perltidy-program "perltidy.bat"))

  (add-hook 'cperl-mode-hook (lambda ()
                               (local-set-key (kbd "C-c t") 'perltidy-dwim)))

Packages

Color handling

  (use-package color)

  (defun bmw/color-dim (steps)
    (apply 'color-rgb-to-hex
           (car (color-gradient
                 (color-name-to-rgb (face-attribute 'default :background))
                 (color-name-to-rgb (face-attribute 'default :foreground))
                 steps))))

Exec path

Need to get Emacs starting happy under windows-nt before I get back to borrowing from the system environment.

  ;; ensure environment in Emacs is environment in shell
  (if (not (eq system-type 'windows-nt))
      (use-package exec-path-from-shell
        :ensure t
        :config
        (setq exec-path-from-shell-check-startup-files nil
              exec-path-from-shell-variables
              '("PLENV_ROOT"
                "PATH" "MAN_PATH"
                "RAKUBREW_HOME"))
        (exec-path-from-shell-initialize)))

Asciidoctor

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

Crystal programming

  ;; Development with the Crystal language
  (use-package crystal-mode
    :ensure t
    :mode ("\\.cr$" . crystal-mode)
    :interpreter ("crystal" . crystal-mode)
    :config
    (autoload 'crystal-mode "crystal-mode" "Major mode for Crystal files" t))

  (use-package ob-crystal
    :ensure t)

Deft for notes

https://jblevins.org/projects/deft/

  (use-package deft
    :ensure t
    :init
    (setq deft-extensions '("md" "org" "txt"))
    :config
    (setq deft-markdown-mode-title-level 1)
    (setq deft-use-filename-as-title t)
    (setq deft-file-naming-rules
          '((noslash . "-")
            (nospace . "-")
            (case-fn . downcase)))
    (setq deft-directory "~/Dropbox/org/notes"))

Elscreen for multiplexing (tabs)

  ;; elscreen for sessions
  (use-package elscreen
    :ensure t
    :init
    (elscreen-start))

Files

Keeping backups somewhere besides the current folder to reduce Hugo’s confusion. It helps, but see also Hugo file lock workaround.

  ;; locks and autosaves all over the place just annoy me
  ;; TODO: set up proper autosave
  (use-package files
    :custom
    (auto-save-default nil)
    (auto-save-file-name-transforms `((".*" ,(concat user-emacs-directory "autosaves"))))
    (backup-directory-alist `(("." . ,(concat user-emacs-directory "backups")))))

gnuplot

  (use-package gnuplot
    :ensure t)

Goto address

  (use-package goto-addr
    :ensure t
    :hook
    ((text-mode prog-mode) . goto-address-mode))

Lua

  (use-package lua-mode
    :ensure t
    :mode ("\\.lua\\'" "\\.luac\\'")
    :config (setq lua-indent-level 2))

Markdown

  ;; markdown-mode
  (use-package markdown-mode
    :ensure t
    :mode (("README\\.md\\'" . gfm-mode)
           ("\\.md\\'" . markdown-mode)
           ("\\.markdown\\'" . markdown-mode))
    :init (setq markdown-command "mmark"))

Mixed Pitch Mode

Really curious to see how mixed-pitch-mode compares to poet, and if I can mix it with solarized.

  (use-package mixed-pitch
    :ensure t
    :custom
    (mixed-pitch-set-height t)

    :hook
    (text-mode . mixed-pitch-mode))

Olivetti

for bigger margins

  (use-package olivetti
    :ensure t
    :custom
    (olivetti-body-width bmw/line-length)
    :hook (text-mode . olivetti-mode))

Pyenv

Why oh why did I pick a version manager for every language I use? Seemed like such a great idea when everything was tmux. Oh well. Most of this pyenv stuff I grabbed from Setup Emacs for Python development using Pyenv by Rakan Al-Hneiti.

  (defun pyenv-activate-current-project ()
    "Automatically activates pyenv version if .python-version file exists."
    (interactive)
    (let ((python-version-directory (locate-dominating-file (buffer-file-name) ".python-version")))
      (if python-version-directory
          (let* ((pyenv-version-path (f-expand ".python-version" python-version-directory))
                 (pyenv-current-version (s-trim (f-read-text pyenv-version-path 'utf-8))))
            (pyenv-mode-set pyenv-current-version)
            (message (concat "Setting virtualenv to " pyenv-current-version))))))

  (defvar pyenv-current-version nil nil)

  (defun pyenv-init()
    "Initialize pyenv's current version to the global one."
    (let ((global-pyenv (replace-regexp-in-string "\n" "" (shell-command-to-string "pyenv global"))))
      (message (concat "Setting pyenv version to " global-pyenv))
      (pyenv-mode-set global-pyenv)
      (setq pyenv-current-version global-pyenv)))

  (add-hook 'after-init-hook 'pyenv-init)

  (use-package pyenv-mode
    :ensure t
    :init
    (add-to-list 'exec-path "~/.pyenv/shims")
    (setenv "WORKON_HOME" "~/.pyenv/versions")
    :config
    (pyenv-mode)
    :bind
    ("C-x p e" . pyenv-activate-current-project))

MAYBE Pyenv and windows?

I’m getting a bit uncertain about pyenv in the first place, and with me trying to using Emacs in windows-nt I’m really getting uncertain. Maybe just explore Elpy for a bit — see if there’s anything in there about venv.

Markup faces (for adoc)

I fiddled with these so adoc-mode would be more aesthetically appealing to me.

  (use-package markup-faces
    :config
    (set-face-attribute 'markup-title-0-face nil :height 1.0)
    (set-face-attribute 'markup-title-1-face nil :height 1.0)
    (set-face-attribute 'markup-title-2-face nil :height 1.0)
    (set-face-attribute 'markup-title-3-face nil :height 1.0)
    (set-face-attribute 'markup-title-4-face nil :height 1.0)
    (set-face-attribute 'markup-title-5-face nil :height 1.0)
    (set-face-attribute 'markup-secondary-text-face nil :height 1.0)
    (set-face-attribute 'markup-meta-face nil
                        :height 1.0
                        :foreground (face-attribute 'font-lock-comment-face :foreground))
    (set-face-attribute 'markup-meta-hide-face nil
                        :height 1.0
                        :foreground (face-attribute 'font-lock-comment-face :foreground))
    (set-face-attribute 'markup-list-face nil
                        :background (face-attribute 'font-lock-builtin-face :background)
                        :foreground (face-attribute 'font-lock-builtin-face :foreground))
    (set-face-attribute 'markup-table-face nil
                        :background (face-attribute 'font-lock-builtin-face :background)
                        :foreground (face-attribute 'font-lock-builtin-face :foreground))
    (set-face-attribute 'markup-verbatim-face nil
                        :background (face-attribute 'font-lock-string-face :background)
                        :foreground (face-attribute 'font-lock-string-face :foreground)))

I’ve experimented with my own settings, but so far poet is better than what I came up with.

Whitespace

  (defun bmw/theme-whitespace ()
    "Apply my own face-attribute changes after loading a custom theme"
    (set-face-attribute 'whitespace-space nil
                        :background (face-attribute 'font-lock-comment-face :background)
                        :foreground (bmw/color-dim 3)))

    (defun bmw/theme-whitespace ()
      "Apply my own face-attribute changes after loading a custom theme"
      (set-face-attribute 'whitespace-space nil
                          :background (face-attribute 'font-lock-comment-face :background)
                          :foreground (bmw/color-dim 3)))

  (use-package whitespace
    :ensure t
    :preface
    (defun bmw/whitespace-mode ()
      (unless (eq major-mode 'org-mode)
        (progn
          (whitespace-mode)
          (bmw/theme-whitespace))))
    :custom
    (whitespace-action '(auto-cleanup))
    (whitespace-line-column bmw/line-length)
    (whitespace-style
     '(face lines trailing empty tabs spaces indentation space-mark tab-mark))
    :hook
    ((prog-mode text-mode) . bmw/whitespace-mode))

Writegood mode

  ;; writegood-mode
  (use-package writegood-mode
    :ensure t
    :config
    (global-set-key (kbd "C-c g") 'writegood-mode)
    (add-hook 'text-mode-hook 'writegood-mode))

Yaml Mode

  (use-package yaml-mode
    :ensure t)

Web Mode

Need more than stock HTML for Hugo templates.

  (use-package web-mode
    :ensure t
    :mode (("\\.html?\\'" . web-mode)))

Themes

I bounce between themes a lot.

Startup theme selector

Make some non-standard themes available, then load whatever today’s favorite theme is.

  (defadvice load-theme (before clear-previous-themes activate)
    "Clear existing theme settings instead of layering them"
    (mapc #'disable-theme custom-enabled-themes))

    (use-package mixed-pitch
      :ensure t
      :custom
      (mixed-pitch-set-height t)

      :hook
      (text-mode . mixed-pitch-mode))
    (use-package color-theme-modern
      :ensure t
      :config
      (load-theme 'classic t t)
      (enable-theme 'classic))

More color themes

With mixed-pitch-mode and friends taking care of font sizing, most theme handling is done with replace-colorthemes, which is in MELPA as “color-theme-modern.”

  (use-package color-theme-modern
    :ensure t
    :config
    (load-theme 'classic t t)
    (enable-theme 'classic))

Random bits of functionality

Quick-edit config

  (global-set-key (kbd "<f5>")
                  (lambda ()
                    (interactive)
                    (find-file bmw/org-config-main)
                    (message "Opened: %s" (buffer-name))))

Digraphs

Like Vim digraphs, but for Emacs. I like those. C-c d calls digraph-map, which gets Emacs listening for the next sequence of characters. Via Github user ibarland.

That’s a big source block. Just go to the gist.

I know for sure I’ll be revisiting this until I get it just so for my own tastes. With that in mind, here are some resources for future me.

Resources

Timestamps

Generate ISO-style timestamps with UTC offset, mainly for usage in front matter for site content.

  (defun bmw/timestamp ()
    (concat
     (format-time-string "%Y-%m-%d %T")
     ((lambda (x) (concat (substring x 0 3) ":" (substring x 3 5)))
      (format-time-string "%z"))))
Got a comment? A question? More of a comment than a question? Talk to me about this config!

Back to the config section