Skip to content

Latest commit

 

History

History
978 lines (754 loc) · 28.8 KB

configuration.org

File metadata and controls

978 lines (754 loc) · 28.8 KB

Love Affair With an Editor

Welcome to my attempt at literate configuration of the one and only Emacs. My goal is to edit this into a readable state that describes and illustrates one possible way of driving this wonderful system.

Packages

list of packages (and themes) used in this config

(defvar my-packages
      '(
        ;; utilities
        all-the-icons
        all-the-icons-ivy
        company
        company-box
        counsel ;; includes ivy & swiper
        counsel-projectile
        docker
        eglot
        flycheck
        ivy-hydra
        magit
        perspective
        projectile
        smartparens
        smex
        undo-tree
        which-key
        xah-fly-keys

        ;; languages

        ;; Common Lisp
        sly

        ;; Emacs Lisp
        flycheck-package
        package-lint

        ;; Ruby
        enh-ruby-mode
        inf-ruby
        minitest
        ))

(defvar my-themes
      '(
        goose-theme
        kaolin-themes
        lab-themes
        modus-operandi-theme
        modus-vivendi-theme
        sublime-themes
        sunburn-theme
        tao-theme
        zenburn-theme
        ))

fix colorless themes with straight install?

(straight-use-package
  '(colorless-themes :type git
                     :repo "https://git.sr.ht/~lthms/colorless-themes.el"))

The theme files do not copy to the build directory, and the themes do not show up in my list of themes to load.

Installing packages

with straight.el i can just simply map over the list, it handles the logic of what to do if a package is already installed, etc.

(mapc 'straight-use-package (append my-packages my-themes))

research proper way to update packages etc

  • straight-check-all
  • straight-pull-all
  • straight-rebuild-all

native compiling packages

would be nice to have a way to automatically compile packages i’ve selected for it. i don’t really know what the best way to go about deciding which packages should get compiled this way.

  • [ ] ask gccemacs author about this
  • [ ] write a script to compile things that need it (after install, update, etc)

Emacs Editing Configurations

xah-fly-keys

After experiencing some crippling Emacs pinky, i started looking at options for different key bindings. one of the first things i came across was xah-fly-keys, written by Xah Lee, a long time Emacs user who has written extensively about his experiences and findings. fly keys seemed worth a try, and didn’t clobber existing bindings, so i could always fall back on muscle memory while learning.

it took some getting used to, but after a month i was proficient and have not looked back. muscle memory changes, and this one is for the better. i’ve been using it alongside Spacemacs, but it made it so i had several keymaps to learn and doubling of bindings that aren’t necessary. this config will attempt to use xah’s bindings while enhancing it with my own as needed.

Initialization

by default, fly keys will use the control key for certain common bindings (CUA) but i prefer to have it off. i set the variable to nil, require the package (perhaps it could use some autoloads?), use the qwerty layout and turn it on:

(setq xah-fly-use-control-key nil)
(require 'xah-fly-keys)
(xah-fly-keys-set-layout "qwerty")
(xah-fly-keys 1)

Enhancements

I like to configure a few visual cues to let me know if I’m in command or insert mode. Here’s one function each for when each mode turns on, which are called with the appropriate hook. These toggle the line highlight and change the cursor color and shape. Additionally, the way fly keys currently works is that it erases xah-fly-key-map for insert mode (so everything works like vanilla) and then remaps everything when command mode is turned on. this has the effect of resetting any keys i want to bind in xah-fly-key-map. so when command mode is turned on, i also set a few keys to call Ivy commands.

(defun xah-fly-keys-command-mode-on ()
  (global-hl-line-mode 1)
  (set-cursor-color "deep pink")
  (setq cursor-type 'box)
  (xah-fly--define-keys
   xah-fly-key-map
   '(
     ("a" . counsel-M-x)
     ("b" . swiper)
     )))

(defun xah-fly-keys-insert-mode-on ()
  (global-hl-line-mode 0)
  (set-cursor-color "Dark Turquoise")
  (setq cursor-type 'bar))

(add-hook 'xah-fly-command-mode-activate-hook 'xah-fly-keys-command-mode-on)
(add-hook 'xah-fly-insert-mode-activate-hook  'xah-fly-keys-insert-mode-on)

Custom Key Maps and Bindings

Setting up a personal key map to put my own shortcuts in. Currently bound to “SPC SPC” and “M-m” (like spacemacs). Currently, I’m associating keybindings with the section where they apply, but it may make more sense to have a complete map represented somewhere.

(defvar personal-key-map (make-sparse-keymap))
(define-prefix-command 'personal-key-map)
(define-key xah-fly-leader-key-map (kbd "SPC") personal-key-map)
(global-set-key (kbd "M-m") personal-key-map)

y7 Now I can bind commands

File Shortcuts

This config file

(defun my-configuration ()
  (interactive)
  (find-file (string-join `(,user-emacs-directory "configuration.org"))))

(define-key personal-key-map (kbd "c") 'my-configuration)

Major Mode Keymaps

There’s no good way to just bind some other key sequence to “C-c” which is the prefix for most major modes. So I’m trying out some advice functions here to check the mode and then bind a different personal major mode map to a key in my personal key map. Its a bit buggy yet, but appears to work so far. Each major mode i care about will get its own keymap with my most often used keys.

(defvar my-major-mode-maps nil)

(setq-default my-major-mode-maps
              '((org-mode my-org-keymap)
                (enh-ruby-mode my-ruby-keymap)
                (lisp-mode my-sly-keymap)
                (sly-mrepl-mode my-sly-keymap)))

(defun set-my-major-mode-map (&rest args)
  (let ((map (cadr (assoc major-mode my-major-mode-maps))))
    (define-key personal-key-map (kbd ",") map)))

(advice-add 'find-file :after #'set-my-major-mode-map)
(advice-add 'switch-to-buffer :after #'set-my-major-mode-map)
(advice-add 'xah-next-window-or-frame :after #'set-my-major-mode-map)
(add-hook 'persp-switch-hook #'set-my-major-mode-map)

Lakota Input

(straight-use-package 'lakota-input)
(require 'lakota-input)

Faces for marking buffers

If you put a file into enriched-mode it seems like the text properties are saved along with the buffer content. This allows for faces applied to text to be saved. Its possible using the highlight.el package will be good enough for this.

I’m wondering if I should define a special lakota-mode to insert blocks into org files, or if org-mode and enriched-mode can coexist.

Seems like highlight.el has some good ideas, but it will be more useful to me to have a set of quick keybindings for adding faces to make examples of grammar in emacs.

Topic and Comment

(defface topic
  '((t (:background "red" :foreground "white")))
  "Face for marking the topic grammar structures.")

(defface comment
  '((t (:background "blue" :foreground "white")))
  "Face for marking the comment grammar structures.")

(defun mark-topic ()
  (interactive)
  (add-face-text-property (region-beginning) (region-end) 'topic))

(defun mark-comment ()
  (interactive)
  (add-face-text-property (region-beginning) (region-end) 'comment))

Highlight trailing whitespace

This is a feature I enjoyed in Spacemacs, so I borrowed it for my own config. This modifies the face for whitespace in all frames to highlight the background.

(set-face-attribute 'trailing-whitespace nil
                    :background
                    (face-attribute 'font-lock-comment-face
                                    :foreground))

And this adds a hook to programming modes to toggle the show-trailing-whitespace variable on. You can add it to any mode you choose.

(add-hook 'prog-mode-hook (lambda () (setq show-trailing-whitespace t)))

Turn off tabs

been having issues in javascript modes where tabs are being inserted. this may or may not be a solution

(setq-default indent-tabs-mode nil)

Programming Languages

Common Lisp

(setq inferior-lisp-program "/usr/bin/sbcl")

Sly

Trying out sly instead of slime, since i’m all “modern” now with emacs.

Personal Sly keymap

(xah-fly--define-keys
 (define-prefix-command 'my-sly-keymap)
 '(
   ;; ("." . slime-eval-buffer)
   ("a" . sly-apropos-all)
   ;; ("e" . slime-compile-defun)
   ;; ("E" . slime-edit-value)
   ("h" . sly-documentation-lookup)
   ;; ("j" . slime-compile-and-load-file)
   ("m" . sly-compile-defun)
   ;; ("M" . slime-eval-last-expression-display-output)
   ;; ("p" . slime-pprint-eval-last-expression)
   ;; ("r" . slime-interactive-eval)
   ;; ("u" . slime-eval-region)
   ))

PicoLisp

Put your source directory here:

"/home/shoshin/picolisp"
ln -s $installdir/man/man1/picolisp.1 /usr/share/man/man1 &&
ln -s $installdir/man/man1/pil.1 /usr/share/man/man1 &&
ln -s $installdir /usr/share/picolisp

Javascript

(add-hook 'js-mode-hook #'flycheck-mode)

Setup rjsx mode

Ruby

(add-hook 'ruby-mode-hook #'enh-ruby-mode)
;; (add-hook 'enh-ruby-mode-hook 'eglot-ensure)
(add-hook 'enh-ruby-mode-hook 'flycheck-mode)
;; (add-hook 'enh-ruby-mode-hook #'lsp)

Haml

(straight-use-package 'haml-mode)
(add-hook 'haml-mode-hook 'flycheck-mode)

https://gist.github.com/mbreit/229d2528604af2f8db37

This works, but doesn’t respect project directory, which means no local rubocop.yml

(flycheck-def-config-file-var flycheck-haml-lintrc haml-lint “.haml-lint.yml” :safe #’stringp)

(defun flycheck-haml-lint–find-project-root (checker) (expand-file-name (flycheck-ruby–find-project-root checker)))

(flycheck-define-command-checker ‘haml-lint “A haml-lint syntax checker” :command ‘(“haml-lint” (config-file “–config” flycheck-haml-lintrc) source) :error-patterns ‘((warning line-start (file-name) “:” line ” [W] ” (message) line-end)) :modes ‘(haml-mode) :next-checker ‘haml ;; maybe this fixes it? (it does not) :working-directory #’flycheck-haml-lint–find-project-root )

;; this does work, perhaps i could get it to work in the checker code? (setenv “HAML_LINT_RUBOCOP_CONF” ”home/shoshin/unabridged/n2/pub_maintainer.rubocop.yml”)

(add-to-list ‘flycheck-checkers ‘haml-lint)

Personal Ruby Keymap

(defvar my-ruby-keymap (make-sparse-keymap))
(define-prefix-command 'my-ruby-keymap)
(define-key my-ruby-keymap (kbd "t") 'minitest-verify)
(define-key my-ruby-keymap (kbd "s") 'minitest-verify-single)

Docker flycheck

this seems to work, but would be nice to figure out how to “officially” extend flycheck to work with linter binaries that are running inside a container

;; an example. there must be a better way :sob:
;; (setq flycheck-rubocop-docker-app-path "/var/www/print_shop")

;; perhaps just docker would be preferable?
(flycheck-def-executable-var ruby-docker-rubocop "docker-compose")

;; not sure what a good default for this would be. perhaps there's a good
;; way to use docker commands to find out. or parsing the dockerfile
(defcustom flycheck-rubocop-docker-app-path "/var/www/app"
  "Set to project root for app inside the container")

;; this provides the path for the config file /inside/ the container
;; it exists locally too, but doesn't resolve properly :|
(flycheck-def-config-file-var flycheck-docker-rubocoprc
    ruby-docker-rubocop
    (concat (file-name-as-directory flycheck-rubocop-docker-app-path) flycheck-rubocoprc))

(flycheck-define-command-checker 'ruby-docker-rubocop
  "A Ruby syntax and style checker using the RuboCop tool inside a docker container.

You need at least RuboCop 0.34 for this syntax checker.

See URL `http://batsov.com/rubocop/'."
  :command `("docker-compose"
             "exec"
             ;; not a TTY
             "-T"
             ;; this would need to be configured for a different app name
             "app"
             "bin/rubocop"

             ;; copied from basic rubocop config
             "--display-cop-names"
             "--force-exclusion"
             "--format" "emacs"
             ;; Explicitly disable caching to prevent Rubocop 0.35.1 and earlier
             ;; from caching standard input.  Later versions of Rubocop
             ;; automatically disable caching with --stdin, see
             ;; https://github.com/flycheck/flycheck/issues/844 and
             ;; https://github.com/bbatsov/rubocop/issues/2576
             "--cache" "false"
             ;; would be nice to get this to work with (config-file ..) if possible
             "--config" ,flycheck-docker-rubocoprc
             (option-flag "--lint" flycheck-rubocop-lint-only)
             ;; Rubocop takes the original file name as argument when reading
             ;; from standard input
             "--stdin" source-original)
  :standard-input t
  :working-directory #'flycheck-ruby--find-project-root
  :error-patterns flycheck-ruby-rubocop-error-patterns
  :modes '(enh-ruby-mode ruby-mode)
  :next-checkers '((warning . ruby-reek)
                   (warning . ruby-rubylint)))

(add-to-list 'flycheck-checkers 'ruby-docker-rubocop)

Emacs Applications

mu4e

mu 1.4.13 package installed from source tarball and installed with sudo make install

Rather than using the mu4e package from github, I’m using the version included with mu

(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e")
(require 'mu4e)
(setq mu4e-mu-binary "/usr/local/bin/mu")

mu4e contexts

(setq user-full-name "Grant Shoshin Shangreaux"
      user-mail-address "grant@churls.world")

Migadu SMTP configuration

(require 'smtpmail)
(setq message-send-mail-function 'smtpmail-send-it
      smtpmail-stream-type 'ssl
      smtpmail-smtp-server "smtp.migadu.com"
      smtpmail-smtp-service 465)

check email with offlineimap

this sets the mail command and runs the mu4e update. M-x mu4e-update-mail-and-index will run the command, or press U in the mu4e main buffer

(setq mu4e-get-mail-command "offlineimap -o")

mu4e-alert

allows notifications from mu4e

doom-mode-line has a custom option to support this

(straight-use-package 'mu4e-alert)
(add-hook 'after-init-hook #'mu4e-alert-enable-mode-line-display)

(setq mu4e-alert-interesting-mail-query
      (concat
       "flag:unread"
       " AND NOT flag:trashed"
       " AND maildir:"
       "\"/ChurlsWorld/INBOX\""))

Org Mode

Personal Org Keymap

(defvar my-org-keymap (make-sparse-keymap))
(define-prefix-command 'my-org-keymap)
(define-key my-org-keymap (kbd "s") 'org-insert-structure-template)
(define-key my-org-keymap (kbd "'") 'org-edit-special)

Structure Templates

(add-to-list 'org-structure-template-alist '("se" . "src emacs-lisp"))
(add-to-list 'org-structure-template-alist '("sr" . "src ruby"))

Org babel

(straight-use-package 'ob-restclient)
(org-babel-do-load-languages
 'org-babel-load-languages
 (quote ((emacs-lisp . t)
         (picolisp . t)
         (dot . t)
         (ruby . t)
         (shell . t)
         (js . t)
         (restclient . t))))

org-tree-slide

(straight-use-package 'org-tree-slide)

Company

(setq company-minimum-prefix-length 2
      company-idle-delay 0.3) ;; default is 0.2

(add-hook 'after-init-hook 'global-company-mode)
(add-hook 'company-mode-hook 'company-box-mode)

turn off or improve company in certain modes

  • [ ] shell modes it can be quite annoying

Ivy

(ivy-mode 1)
(setq ivy-use-virtual-buffers t)
(setq ivy-count-format "(%d/%d) ")
(setq all-the-icons-ivy-file-commands
      '(counsel-find-file counsel-file-jump counsel-recentf counsel-projectile-find-file counsel-projectile-find-dir))
(all-the-icons-ivy-setup)
(define-key xah-fly-c-keymap (kbd "e") 'counsel-find-file)

which-key

(which-key-mode 1)

magit

Magit Shortcuts

Right now I only really need magit status:

(define-key 'personal-key-map (kbd "g") 'magit-status)

Eventually I might turn this into its own prefix map

Pretty Magit

Took some code from the modernemacs guy that replaces strings in magit buffers with pretty icons. Its a bit funky though, and you have to pick the right icons, otherwise they’ll get replaced with another one for some unknown reason. There was some report about it on the all-the-icons repo, so perhaps its fixable.

(defmacro pretty-magit (WORD ICON PROPS &optional NO-PROMPT?)
  "Replace sanitized WORD with ICON, PROPS and by default add to prompts."
  `(prog1
       (add-to-list 'pretty-magit-alist
                    (list (rx bow (group ,WORD (eval (if ,NO-PROMPT? "" ":"))))
                          ,ICON ',PROPS))
     (unless ,NO-PROMPT?
       (add-to-list 'pretty-magit-prompt (concat ,WORD ": ")))))

(setq pretty-magit-alist nil)
(setq pretty-magit-prompt nil)

(pretty-magit "Feature" ? (:foreground "slate gray" :height 1.2))
(pretty-magit "Add"     ? (:foreground "#375E97" :height 1.2))
(pretty-magit "Fix"     ? (:foreground "#FB6542" :height 1.2))
(pretty-magit "Clean"   ? (:foreground "#FFBB00" :height 1.2))
(pretty-magit "Docs"    ? (:foreground "#3F681C" :height 1.2))
(pretty-magit "main"    ? (:foreground "LightSeaGreen" :box t :height 1.2) t)
(pretty-magit "origin"  ? (:foreground "LightSeaGreen" :box t :height 1.2) t)

(defun add-magit-faces ()
    "Add face properties and compose symbols for buffer from pretty-magit."
    (interactive)
    (with-silent-modifications
      (--each pretty-magit-alist
        (-let (((rgx icon props) it))
          (save-excursion
            (goto-char (point-min))
            (while (search-forward-regexp rgx nil t)
              (compose-region
               (match-beginning 1) (match-end 1) icon)
              (when props
                (add-face-text-property
                 (match-beginning 1) (match-end 1) props))))))))

(advice-add 'magit-status :after 'add-magit-faces)
(advice-add 'magit-refresh-buffer :after 'add-magit-faces)

Projectile

(setq projectile-completion-system 'ivy)
(projectile-mode 1)
(define-key 'personal-key-map (kbd "p") 'projectile-command-map)

Integrate with Perspective

(straight-use-package 'persp-projectile)
(define-key projectile-command-map (kbd "l") 'projectile-persp-switch-project)

NOTE: this overwrites the binding for projectile-find-file-in-directory

Perspective-el

Perspective creates “workspaces” with isolated buffer lists. It also interacts with projectile, allowing easy opening of perspectives for a particular project.

(persp-mode 1)
(define-key 'xah-fly-leader-key-map (kbd "f") 'persp-counsel-switch-buffer)

Binding the perspective-map to my personal key map:

(define-key 'personal-key-map (kbd "l") 'perspective-map)

And customizing the selected perspective face, the default is often poor:

(set-face-foreground 'persp-selected-face "indian red")

Smartparens

(require 'smartparens-config)
(smartparens-global-mode 1)
(sp-local-pair 'sly-mrepl-mode "'" nil :actions nil)

Docker

(define-key 'personal-key-map (kbd "d") 'docker)

Undo Tree

(global-undo-tree-mode 1)

dired

Add icons to dired

I’m a sucker for the pretty icons everywhere.

(straight-use-package 'all-the-icons-dired)
(add-hook 'dired-mode 'all-the-icons-dired-mode)

restclient

(add-to-list 'auto-mode-alist '("\\.http\\'" . restclient-mode))

emms

(straight-use-package 'emms)
(require 'emms-setup)
(emms-all)
(emms-default-players)
(setq emms-source-file-default-directory "~/Music/")
(add-to-list 'emms-info-functions #'emms-info-metaflac)

Markable Playlists

For my use case of mass editing tags on audio files, it appears that I have to enable markable playlists. It isn’t clear in the docstrings for the functions but, the info manual has this:

emms-mark is also intent to provide a way for user to select tracks for other command to operate on them. Currently, ‘emms-tag-editor.el’ uses the emms-mark to edit the tags of selected tracks

(require 'emms-mark)

This allows marking items in playlists similarly to how dired works. Marking several items and pressing E will open them all up for tag edits, and then you can M-x emms-tag-editor-set-all or C-c C-r to set the value of a tag on all of the marked tracks.

dumb-jump

getting frustrated with LSP taking so much cpu, but missing jump-to-def, this package comes highly recommended

(straight-use-package 'dumb-jump)
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate)

Elpher - gopher/gemini client

(straight-use-package 'elpher)

Emacs GUI

I’m putting this at the end, because its most likely to have order dependent side effects than other configurations.

Basic gui element preferences

I prefer to turn off several things from the default gui:

(blink-cursor-mode 0)
(tool-bar-mode 0)
(menu-bar-mode 0)
(scroll-bar-mode 0)

And with larger screens I can dedicate 16 pixels to the left fringe, which allows larger icons to show up for things like flycheck:

(fringe-mode '(16 . 0))

Theme initialization

  • [ ] make a list of favored themes to pick randomly from
  • [ ] perhaps choose light/dark theme based on time of day
(load-theme 'kaolin-temple)

Theme Switching

The default behavior of loading/enabling a theme in Emacs is to combine all the faces defined in custom-enabled-themes, with the front of the list having precedence. This started annoying me when certain faces from one theme would interfere with the one I was trying to load. There are no hooks around loading or enabling themes (that i could find), so I wrote an advice function to disable any currently enabled theme before loading the next one.

(defun theme-switch (&rest args)
  "Function to advise `load-theme' to ensure only a single theme is enabled."
  (mapc 'disable-theme custom-enabled-themes))

(advice-add 'load-theme :before #'theme-switch)
(define-key 'personal-key-map (kbd "t") 'load-theme)

Modeline

doom-mode-line

I’ve moved to doom-mode-line because it is beautiful and effective.

(straight-use-package 'doom-modeline)
(setq
 doom-modeline-buffer-encoding nil
 doom-modeline-persp-name t
 doom-modeline-height 35
 doom-modeline-minor-modes t
 doom-modeline-mu4e t)    ;; requires ‘mu4e-alert’ package.

(doom-modeline-mode)

Because I’ve enabled minor modes and perpective names, i sometimes have an issue with space. delight helps minimize space taken by minor modes. I should also do something with perspectives as well.

On the other hand, its relatively rare that the minor modes i have running are revealed to me by the modeline anyway, and persp names are generally obvious to me. perhaps that clutter on the modeline is not necessary ;)

Part of it is certainly aesthetics, but perhaps i should consider what use the modeline might be best put towards.

delight

(delight SPEC &optional VALUE FILE)

Modify the lighter value displayed in the mode line for the given mode SPEC if and when the mode is loaded.

(straight-use-package 'delight)
(delight
 '(
   (company-box-mode "" company-box)
   (company-mode " ©" company)
   (ivy-mode "" ivy) ;;
   (projectile-mode "" projectile)
   (undo-tree-mode "" undo-tree)
   (xah-fly-keys "")
   ))

Beacon

helps highlight cursor when switching windows

(straight-use-package 'beacon)
(beacon-mode 1)

SVG screenshot

(defun screenshot-svg ()
  (interactive)
  (let* ((filename (make-temp-file "Emacs" nil ".svg"))
         (data (x-export-frames nil 'svg)))
    (with-temp-file filename
      (insert data))
    (kill-new filename)
    (message filename)))

Emoji

Well… it is 2020 after all 😷

Emojify package

(straight-use-package 'emojify)

List of Modes to Emojify

(add-hook 'org-mode-hook #'emojify-mode)
(add-hook 'mu4e-view-mode-hook #'emojify-mode)

Fonts

Monospace

I’ve really enjoyed Victor Mono for coding, especially the cursive italics. Its a clean, classy font that brings me a bit of joy :)

(setq my-font "Victor Mono-14")

;; (set-face-attribute 'default t :font my-font)
(set-frame-font my-font nil t)

Deja Vu Sans Mono was suggested as a good monospace font for handling special chars in different orthographies, and it does seem ok.

Variable Pitch

Been searching for a nice looking font for Lakota work, and the one I’m set on currently is Linux Libertine O, which handles all characters failry well. The macron on top of consonants can still be a bit funky on t̄ and k̄. but it is ok otherwise

(set-face-attribute 'variable-pitch nil :family "Linux Libertine O")

Testing

docker compose with composer.el

(straight-use-package ‘composer)