Skip to content

Latest commit

 

History

History
1607 lines (1302 loc) · 44.3 KB

configuration.org

File metadata and controls

1607 lines (1302 loc) · 44.3 KB

Emacs Configuration

Introduction

This document is a way for me to organize and navigate my configuration. It’s not a teaching moment or an exercise in literate programming.

Appearance

Emacs GUI

This goes first so that we don’t see unsightly stuff appear while Emacs is loading.

(when (display-graphic-p)
  (menu-bar-mode 1)
  (tool-bar-mode -1)
  (scroll-bar-mode -1)
  (tooltip-mode -1))

Emacs Menu

Is this a useful thing, I wonder?

(easy-menu-define keith-menu nil "My Menu"
  '("Keith"
    ["Light Theme" kfi/modus-light]
    ["Dark Theme" kfi/modus-dark]
    "-----------"
    ["Bookmarks" bookmark-bmenu-list]
    ["Double Width" kfi/dw]
    ["Single Width" kfi/sw]))

(define-key-after global-map [menu-bar keith]
  (cons "Keith" keith-menu) 'tools)

All the icons

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

Fonts

(use-package emacs
  :config
  (when (display-graphic-p)
    (defvar kfi/font-choices '("Menlo" "Monaco")
      "Font faces I'm fond of.")

    (defun kfi/switch-font ()
      "Choose a font."
      (interactive)
      (let ((font (completing-read "Font:" kfi/font-choices)))
        (if font
            (set-face-attribute 'default nil :family font)
          (message "ERROR: Invalid font: %s." font))))

    (set-face-attribute 'default nil
                        :family "Menlo"
                        :height 140
                        :weight 'medium)))

Themes

(use-package modus-themes
  :ensure t
  :config
  (setq modus-operandi-theme-slanted-constructs t)
  (setq modus-operandi-theme-bold-constructs t)
  (setq modus-operandi-theme-proportional-fonts nil)
  (setq modus-operandi-theme-distinct-org-blocks t)
  (setq modus-operandi-theme-visible-fringes nil)
  (setq modus-operandi-theme-scale-headings nil))

Add a face for Org Mode checklists such that when you check them off, they grey out and get a strike through. The regex matcher isn’t perfect because you can make your text contain another checklist, such as:

- [ ] Finish the task 2) [X] again

The word “again” will get the font face. I need to check for spaces/nums at the beginning, or something: or just don’t do the kind of thing up there.

(defface org-checkbox-done-text '((t (:inherit org-done)))
  "Face for the text part of a checked org-mode checkbox.")

(font-lock-add-keywords
 'org-mode
 `(("^\\(.*?[\\)\\.\\+\\-]+? \\[X\\] \\)\\(.*\\)$"
    2 ;; use the second match
    'org-checkbox-done-text prepend))
 'append)

(set-face-attribute 'org-checkbox-done-text nil
                    :strike-through t
                    :foreground "gray40")

Some utility functions for switching themes. The dark theme switcher updates an Org Mode face, so it has to be loaded after Org Mode.

(use-package emacs
  :config
  (defun kfi/unload-themes ()
    "Disable all custom themes."
    (interactive)
    (dolist (theme custom-enabled-themes)
      (disable-theme theme)))

  (defun kfi/modus-light ()
    "Use the modus light theme."
    (interactive)
    (kfi/unload-themes)
    (load-theme 'modus-operandi t)
    ;; (set-face-attribute 'mode-line nil :box nil)
    (set-face-attribute 'org-default nil :foreground "black")
    (set-face-attribute 'bold nil :foreground "black")
    (set-face-attribute 'org-headline-done nil :strike-through t :foreground "gray40"))

  (with-eval-after-load 'org
    (defun kfi/modus-dark ()
      "Use the modus dark theme."
      (interactive)
      (kfi/unload-themes)
      (load-theme 'modus-vivendi t)
      ;; (set-face-attribute 'mode-line nil :box nil)
      (set-face-attribute 'org-default nil :foreground "#c7c7c7")
      (set-face-attribute 'bold nil :foreground "white")
      (set-face-attribute 'org-headline-done nil :strike-through t :foreground "gray40"))
    (when (display-graphic-p)
      (kfi/modus-light))))

Modeline (disabled)

https://github.com/seagle0128/doom-modeline

(use-package doom-modeline
  :ensure t
  :disabled
  :config
  (when (display-graphic-p)
    (doom-modeline-mode 1)
    ;; Some themes set this, so unset it just in case.
    (set-face-attribute 'mode-line nil :box nil)))

Emoji

(use-package company-emoji
  :ensure t
  :pin melpa
  :config
  (when (and (display-graphic-p) (eq system-type 'darwin))
    (set-fontset-font "fontset-default" 'unicode "Apple Color Emoji" nil 'prepend)
    (add-to-list 'company-backends 'company-emoji)))

Features

company

(use-package company
  :ensure t
  :defer t)

exec-path

-from-shell

(use-package exec-path-from-shell
  :if (memq window-system '(mac ns))
  :ensure t
  :config
  (setq exec-path-from-shell-check-startup-files nil)
  (exec-path-from-shell-initialize)
  (exec-path-from-shell-copy-env "JAVA_HOME")
  (exec-path-from-shell-copy-env "GOPATH")
  )

flycheck

(use-package flycheck
  :ensure t
  :commands (flycheck-mode global-flycheck-mode))

fullframe

(use-package fullframe
  :ensure t)

git-gutter

Ok, I’ve discovered this annoys me more than it helps. Editors with more, uh, graphical affordances make this kind of thing work well. Emacs, not so much, or not this module, anyway. I’ll keep it here in case I want to flip it on, but for now, it’s default off.

(use-package git-gutter
  :ensure t
  :config
  (global-git-gutter-mode 0))

ibuffers

(use-package ibuffer
  :bind (("C-x C-b" . ibuffer)
         ("s-8" . ibuffer))
  :config
  (setq ibuffer-saved-filter-groups
        (quote (("default"
                 ("documents" (or (mode . org-mode)
                                  (mode . markdown-mode)
                                  (mode . text-mode)))
                 ("swift" (mode . swift-mode))
                 ("elisp" (mode . emacs-lisp-mode))
                 ("web" (or (mode . css-mode)
                            (mode . html-mode)
                            (mode . nxml-mode)
                            (mode . js-mode)
                            (mode . web-mode)))
                 ("salesforce" (or (name . "[.]vfp$")
                                   (name . "[.]apxc$")
                                   (name . "[.]vfc$")))
                 ("python" (mode . python-mode))
                 ("json" (mode . json-mode))
                 ("java" (mode . java-mode))
                 ("clojure" (mode . clojure-mode))
                 ("erc" (name . "^\\#"))
                 ("dirs" (mode . dired-mode))
                 ("temps" (name . "^\\*.*\\*$"))))))

  (defun kfi/setup-ibuffer ()
    (hl-line-mode 1)
    (ibuffer-switch-to-saved-filter-groups "default"))

  (add-hook 'ibuffer-mode-hook 'kfi/setup-ibuffer))

ivy

Completion utility (https://oremacs.com/swiper/).

(use-package ivy
  :ensure t
  :config
  (ivy-mode 1)
  (setq ivy-display-style 'fancy)
  (setq ivy-use-virtual-buffers t)
  ;; (setq ivy-count-format "(%d/%d) ")
  (setq ivy-count-format "") ;; "("%d/%d) "
  (setq ivy-re-builders-alist '((t . ivy--regex-fuzzy)))
  (setq ivy-use-selectable-prompt t))
(use-package swiper
  :ensure t
  :after ivy)
(use-package counsel
  :ensure t
  :config
  (global-set-key (kbd "C-x C-f") 'counsel-find-file)
  (global-set-key (kbd "C-h v") 'counsel-describe-variable)
  (global-set-key (kbd "C-h f") 'counsel-describe-function)
  (global-set-key (kbd "M-x") 'counsel-M-x)
  (global-set-key (kbd "s-f") 'swiper)
  (global-set-key (kbd "s-b") 'ivy-switch-buffer)
  ;; Has to be set here because counsel overrides ivy.
  ;;  - Remove the ^ regex assumption for all searches
  (setq ivy-initial-inputs-alist nil))
(use-package flx ; used by regex-fuzzy, I think.
  :ensure t)
(use-package smex ; counsel-M-x will use this?
  :ensure t)
(use-package ivy-rich
  :ensure t
  :after ivy
  :config
  (ivy-rich-mode 1)
  (setq ivy-rich-path-style 'abbrev))

multiple-cursors

(use-package multiple-cursors
  :commands multiple-cursors-mode
  :ensure t
  :pin melpa
  :config
  (setq mac-command-modifier 'super)
  :bind (("C-s-c k" . mc/edit-lines)
         ("C-M->" . mc/mark-all-like-this)
         ("C-<" . mc/mark-previous-like-this)
         ("C->" . mc/mark-next-like-this)))

paredit

(use-package paredit
  :ensure t
  :commands paredit-mode)

paren-face

(use-package paren-face
  :ensure t
  :config
  (global-paren-face-mode 1))

projectile

(use-package projectile
  :ensure t
  :bind (("s-p" . projectile-find-file)
         ("C-c p" . projectile-find-file))
  :init
  (setq projectile-completion-system 'ivy))

restclient

https://github.com/pashky/restclient.el https://github.com/alf/ob-restclient.el https://github.com/iquiw/company-restclient

(use-package restclient
  :ensure t
  :config
  (add-hook 'restclient-mode-hook (lambda ()
                                    (company-mode 1))))

(use-package ob-restclient
  :ensure t
  :config
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((restclient . t))))

(use-package company-restclient
  :ensure t
  :config
  (add-to-list 'company-backends 'company-restclient))

ripgrep

Control-meta-super-F

(use-package ripgrep
  :ensure t
  :bind (("C-M-s-f" . projectile-ripgrep)))

treemacs

https://github.com/Alexander-Miller/treemacs

;; (use-package treemacs
;;   :ensure t
;;   :defer t
;;   :config
;;   (setq treemacs-width 30)
;;   (treemacs-resize-icons 16))

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

which-key

which-key is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup.

https://github.com/justbur/emacs-which-key

(use-package which-key
  :ensure t
  :init (which-key-mode 1))

yasnippet

http://joaotavora.github.io/yasnippet/

(use-package yasnippet
  :ensure t
  :init
  ;; I can't get this to work as a minor mode, for some reason.
  (yas-global-mode))

Application Modes

Directory Editor (Dired)

(use-package dired
  :bind ("C-x C-q" . wdired-change-to-wdired-mode)
  :config
  (defun kfi/dired-mode-hook ()
    (dired-hide-details-mode)
    (when (display-graphic-p)
      (hl-line-mode)))
  (add-hook 'dired-mode-hook 'kfi/dired-mode-hook)
  (setq dired-listing-switches "-l")
  (when (eq system-type 'darwin)
    (setq trash-directory "~/.Trash")
    (setq delete-by-moving-to-trash t))

  (when (eq system-type 'darwin)
    (setq dired-use-ls-dired nil)))

Erc (IRC Client)

(use-package erc
  :config
  (defun kfi/erc-mode-hook ()
    (make-local-variable 'global-hl-line-mode)
    (setq global-hl-line-mode nil))

  (add-hook 'erc-mode-hook 'kfi/erc-mode-hook)

  ;; Not sure if this is necessary.
  (add-to-list 'erc-modules 'truncate)
  (add-to-list 'erc-modules 'scrolltobottom)
  (erc-update-modules)

  (erc-scrolltobottom-mode 1)

  (setq erc-hide-list '("JOIN" "PART" "QUIT"))
  (setq erc-fill-prefix "    ")
  (setq erc-prompt (lambda () (concat "\n" (buffer-name) " >")))

  (setq erc-fill-column 79)
  (setq erc-scroll-to-bottom -2)
  (setq erc-truncate-buffer-on-save t)
  (setq erc-max-buffer-size 30000)

  (add-hook 'erc-insert-post-hook 'erc-truncate-buffer)
  (setq erc-truncate-buffer-on-save t))

(use-package erc-hl-nicks
  :ensure t)

Magit (Git Client)

(use-package magit
  :ensure t
  :bind (("C-c g" . kfi/magit-start-session)
         ("C-c l"  . kfi/magit-log-session)
         :map magit-status-mode-map
         ("q" . kfi/magit-quit-session))
  :config

  (defun kfi/magit-start-session ()
    "Go full screen when invoking magit-status."
    (interactive)
    (window-configuration-to-register :magit-fullscreen)
    (call-interactively 'magit-status)
    (delete-other-windows))

  (defun kfi/magit-log-session ()
    "Go full screen when invoking magit-log."
    (interactive)
    (window-configuration-to-register :magit-fullscreen)
    (call-interactively 'magit-log)
    (delete-other-windows))

  (defun kfi/magit-quit-session ()
    "Quit the magit session and restore windows."
    (interactive)
    (kill-buffer)
    (jump-to-register :magit-fullscreen)))

Persistent Scratch Buffer

(use-package persistent-scratch
  :ensure t
  :config
  (persistent-scratch-setup-default)
  (persistent-scratch-autosave-mode 1))

VTerm (Terminal Client)

https://github.com/akermu/emacs-libvterm

(use-package vterm
  :ensure t
  :bind (("C-c v" . vterm)
         ("C-c h" . vterm))
  :init (setq vterm-kill-buffer-on-exit t)
  :config
  (defun kfi/setup-vterm ()
    (define-key vterm-mode-map (kbd "C-v") 'vterm-yank)
    (define-key vterm-mode-map (kbd "s-v") 'vterm-yank)
    (set-face-attribute 'vterm-color-cyan nil :foreground "dodgerblue")
    (set-face-attribute 'vterm-color-yellow nil :foreground "peru")
    (if (member 'modus-vivendi custom-enabled-themes)
        (set-face-attribute 'vterm-color-default nil :foreground "#c7c7c7")
      (set-face-attribute 'vterm-color-default nil :foreground "#444444")))

  (add-hook 'vterm-mode-hook 'kfi/setup-vterm))

Configuration Modes

Dockerfile Mode

(use-package dockerfile-mode
  :commands dockerfile-mode
  :ensure t
  :defer t)

Document Modes

CSS Mode

(use-package css-mode
  :commands css-mode
  :init
  (defun kfi/css-setup ()
    (company-mode 1)
    ;; (set (make-local-variable 'company-backends) '(company-css))
    ;; (turn-on-css-eldoc)
    (setq css-indent-offset 2)
    (local-set-key (kbd "TAB") 'company-complete)
    (local-set-key (kbd "RET") 'newline-and-indent))

  (add-hook 'css-mode-hook 'kfi/css-setup))

(use-package css-eldoc
  :ensure t
  :defer t)

HTML Mode

(use-package html-mode
  :commands html-mode
  :init
  (add-hook 'html-mode-hook
            (lambda ()
              (local-set-key (kbd "RET") 'newline-and-indent))))

JSON Mode

(use-package json-mode
  :ensure t
  :commands json-mode
  :init
  (add-hook 'js-mode-hook (lambda ()
                            (setq indent-tabs-mode nil)
                            (setq js-indent-level 2)
                            (local-set-key (kbd "RET") 'newline-and-indent))))

Markdown Mode

(use-package markdown-mode
  :ensure t
  :commands (markdown-mode gfm-mode)
  :mode (("README\\.md\\'" . gfm-mode)
         ("readme\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :bind (("C-b" . markdown-insert-bold)
         ("s-i" . markdown-insert-italic)
         ("s-k" . kfi/markdown-insert-small))
  :config
  ;; Cut/paste from markdown-mode.el
  (defun kfi/markdown-insert-small ()
    (interactive)
    (if (markdown-use-region-p)
        ;; Active region
        (let ((bounds (markdown-unwrap-things-in-region
                       (region-beginning) (region-end)
                       markdown-regex-code 1 3)))
          (markdown-wrap-or-insert
           "<small>" "</small>" nil (car bounds) (cdr bounds)))
      (if (markdown-inline-code-at-point)
          (markdown-unwrap-thing-at-point nil 0 2)
        (markdown-wrap-or-insert "<small>" "</small>" 'word nil nil))))
  ;;
  (add-hook 'markdown-mode-hook 'turn-on-flyspell)
  (add-hook 'markdown-mode-hook (lambda ()
                                  (visual-line-mode 1)
                                  ;;(auto-fill-mode 1)
                                  )))

Org Mode

Provide “pretty” bullets for Org Mode.

(use-package org-superstar
  :ensure t
  :after org)

I’ve configured the Org Mode to use hard line-breaks for paragraphs at 70 characters (rather than using visual-line mode) so that an org document will be readable (except for tags) at the command line.

Turn off indentation, pretty bullets and so on when running in terminal mode because it just makes things even uglier for those few times you need it.

(use-package org
  :ensure t
  :pin org
  :config
  (setq org-ellipsis ""
        org-fontify-done-headline t
        org-agenda-start-on-weekday 0
        org-hide-emphasis-markers t
        org-src-window-setup 'split-window-below
        org-support-shift-select t
        org-src-tab-acts-natively t
        org-src-window-setup 'current-window)

  (defun kfi/inline-css-hook (exporter)
    "Merge ./style.css or ~/.emacs.d/org-style.css into HTML document."
    ;; credits: https://stackoverflow.com/a/37132338
    (when (eq exporter 'html)
      (let* ((dir (ignore-errors (file-name-directory (buffer-file-name))))
             (path (concat dir "style.css"))
             (homestyle (or (null dir) (null (file-exists-p path))))
             (final (if homestyle "~/.emacs.d/org-style.css" path))) ;; <- set your own style file path
        (setq org-html-head-include-default-style nil)
        (setq org-html-head (concat
                             "<style type=\"text/css\">\n"
                             "<!--/*--><![CDATA[/*><!--*/\n"
                             (with-temp-buffer
                               (insert-file-contents final)
                               (buffer-string))
                             "/*]]>*/-->\n"
                             "</style>\n")))))

  (defun kfi/org-align-tags ()
    "Align tags to their columns."
    (interactive)
    (org-align-tags t))

  (defun kfi/org-toggle-markers ()
    "Toggle showing markup markers (italics, bold, etc)."
    (interactive)
    (org-toggle-link-display))

  (defun kfi/org-keys-setup ()
    (define-key org-mode-map (kbd "<f1>") 'org-tree-to-indirect-buffer)
    (define-key org-mode-map (kbd "<s-escape>") 'org-tree-to-indirect-buffer)
    (define-key org-mode-map (kbd "C-c a") 'org-agenda)
    (define-key org-mode-map (kbd "C-c t") 'kfi/org-align-tags))

  (defun kfi/org-graphic-mode-setup ()
    (when (display-graphic-p)
      ;; Have to set this so that I can adjust it when switching to
      ;; the theme.
      (buffer-face-set 'org-default)
      (org-indent-mode 1)
      (org-superstar-mode 1)
      (setq-local global-hl-line-mode nil)))

  (defun kfi/org-standard-setup ()
    (auto-fill-mode 1)
    (yas-minor-mode-on))

  ;; Is this necessary? I have a vague memory that this
  ;; helps with org exports to HTML.
  (use-package htmlize
    :ensure t)

  (global-set-key (kbd "C-x <tab>") 'outline-hide-subtree)
  (add-hook 'org-export-before-processing-hook 'kfi/inline-css-hook)
  (add-hook 'org-mode-hook 'kfi/org-graphic-mode-setup)
  (add-hook 'org-mode-hook 'kfi/org-keys-setup)
  (add-hook 'org-mode-hook 'kfi/org-standard-setup))

Web Mode

Facilitates a web page with CSS, Javascript and HTML mixed together.

(use-package web-mode
  :ensure t
  :commands web-mode
  :mode (("\\.html?\\'" . web-mode)
         ("\\.vfp?\\'" . web-mode)
         ("\\.cmp?\\'" . web-mode)
         ("\\.design?\\'" . web-mode))
  :config
  (setq web-mode-markup-indent-offset 2
        web-mode-css-indent-offset 2
        web-mode-code-indent-offset 2
        web-mode-indent-style 2)
  (setq web-mode-content-types (cons '("jsx" . "\\.js\\'") web-mode-content-types))
  (set-face-attribute 'web-mode-html-tag-face nil :foreground "cornflowerblue")
  (set-face-attribute 'web-mode-html-tag-bracket-face nil :foreground "goldenrod"))

YAML Mode

(use-package yaml-mode
  :commands yaml-mode
  :ensure t)

Programming Modes

Clojure Mode

This needs to be revisited if I start using Clojure again.

(use-package cider
  :disabled
  :ensure t
  :after company
  :config
  (setq cider-font-lock-dynamically '(macro core function var))
  (setq cider-eldoc-display-context-dependent-info t)
  (setq cider-repl-use-clojure-font-lock t)
  (setq cider-repl-use-pretty-printing t)
  (setq cider-repl-wrap-history t)
  (setq cider-repl-history-size 3000)
  (setq cider-repl-display-help-banner nil)
  (add-hook 'cider-mode-hook #'eldoc-mode)
  (add-hook 'cider-repl-mode-hook #'company-mode)
  (add-hook 'cider-mode-hook #'company-mode))

(use-package clojure-mode-extra-font-locking
  :disabled
  :ensure t)

(use-package clojure-mode
  :disabled
  :commands clojure-mode
  :ensure t
  :delight "clj"
  :config
  (put-clojure-indent 'Conditional 1)
  (put-clojure-indent 'ControlBar 0)
  (put-clojure-indent 'DisplayBlock 1)
  (put-clojure-indent 'Container 2)
  (put-clojure-indent 'IncludeIf 0)
  (put-clojure-indent 'Table 1)
  (put-clojure-indent 'protobuf 1)
  (put-clojure-indent 'POST 2)
  (put-clojure-indent 'GET 2)
  (add-hook 'clojure-mode-hook 'prettify-symbols-mode)
  (add-hook 'clojure-mode-hook 'paredit-mode)
  (add-hook 'clojure-mode-hook 'cider-mode)
  (setq clojure-indent-style nil))

Emacs Lisp Mode

(use-package emacs
  :ensure t
  :init
  (defun kfi/emacs-lisp-mode-setup ()
    (paredit-mode 1)
    (setq indent-tabs-mode nil)
    (local-set-key (kbd "RET") 'newline-and-indent)
    (company-mode 1)
    (when (display-graphic-p)
      (hl-line-mode 1)))
  (add-hook 'lisp-interaction-mode-hook 'turn-on-eldoc-mode)
  (add-hook 'ielm-mode-hook 'turn-on-eldoc-mode)
  (add-hook 'emacs-lisp-mode-hook 'kfi/emacs-lisp-mode-setup))

Go Mode

Revisit if I use Go again.

https://github.com/golang/tools/blob/master/gopls/doc/emacs.md

;; (use-package flycheck-golangci-lint
;;   :ensure t
;;   :hook (go-mode . flycheck-golangci-lint-setup))

;; (use-package go-mode
;;   :ensure t
;;   :commands go-mode
;;   :init

;;   (add-hook 'go-mode-hook 'flycheck-mode)

;;   :config
;;   ;;(setq gofmt-command "goimports")
;;   )

;; (use-package golint
;;   :ensure t)

Groovy Mode

;; (use-package groovy-mode
;;   :disabled
;;   :ensure t
;;   :defer t
;;   :mode (("\\.groovy" . groovy-mode)
;;          ("\\.gradle" . groovy-mode)))

Java Mode

TODO: Check out this config hlissner’s config.el to see how I might customize lsp so it’s not so facey.

(use-package cc-mode
  :init
  (add-hook 'java-mode-hook (lambda ()
                              (hl-line-mode t)
                              (display-line-numbers-mode 1)
                              (setq c-basic-offset 4)
                              (setq tab-width 4)
                              (setq indent-tabs-mode nil))))

;; (use-package lsp-mode
;;   :ensure t)

;; (use-package hydra
;;   :ensure t)

;; (use-package company-lsp
;;   :ensure t)

;; (use-package lsp-ui
;;   :ensure t
;;   :config
;;   ;; If the popups get too tiresome, use this:
;;   (setq lsp-auto-configure nil))

;; (use-package lsp-java
;;   :ensure t
;;   :after lsp
;;   :config (add-hook 'java-mode-hook (lambda ()
;;                                       (company-mode)
;;                                       (lsp)
;;                                       (flycheck-mode 1))))

;; (use-package dap-mode
;;   :ensure t
;;   :disabled
;;   :after lsp-mode
;;   :config (progn (dap-mode t)
;;                  (dap-ui-mode t)))

Javascript Mode

(use-package js-mode
  :commands js-mode
  :init
  (add-hook 'js-mode-hook (lambda ()
                            (setq indent-tabs-mode nil)
                            (setq js-indent-level 4)
                            (local-set-key (kbd "RET") 'newline-and-indent))))

LSP Mode

(use-package lsp-mode
  :ensure t
  :commands (lsp lsp-deferred)
  :hook (go-mode . lsp-deferred)
  :config

  (defun kfi/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 #'kfi/lsp-go-install-save-hooks)

  ;; https://emacs-lsp.github.io/lsp-mode/tutorials/how-to-turn-off/
  (setq lsp-gopls-staticcheck t)
  (setq lsp-eldoc-render-all t)
  (setq lsp-eldoc-enable-hover t)
  (setq lsp-ui-doc-enable nil) ;; doc popups above cursor word
  (setq lsp-rust-analyzer-proc-macro-enable t)
  (setq lsp-rust-analyzer-cargo-load-out-dirs-from-check t)
  (setq lsp-gopls-complete-unimported t))

(use-package lsp-ui
  :ensure t
  :commands lsp-ui-mode
  :init
  (setq lsp-ui-doc-enable nil
        lsp-ui-peek-enable t
        lsp-ui-sideline-enable t
        lsp-ui-imenu-enable t
        lsp-ui-flycheck-enable t))

;; No longer available
;; (use-package company-lsp
;;   :ensure t)

Python Mode

;; (use-package elpy
;;   :ensure t
;;   :defer t
;;   :init
;;   (advice-add 'python-mode :before 'elpy-enable))

;; (use-package py-autopep8
;;   :ensure t
;;   :defer t
;;   :init
;;   (add-hook 'elpy-mode-hook 'py-autopep8-enable-on-save)
;;   (setq flycheck-python-pycompile- executable "python3"))

(use-package lsp-python-ms
  :ensure t
  :hook (python-mode . (lambda ()
                         (require 'lsp-python-ms)
                         (lsp))))

Rust Mode

Check: https://robert.kra.hn/posts/2021-02-07_rust-with-emacs/

;; (use-package rustic
;;   :ensure t
;;   :init
;;   (setq rustic-lsp-server 'rust-analyzer)
;;   (setq rustic-flycheck-setup-mode-line-p nil)
;;   :hook ((rustic-mode . (lambda ()
;;                           (lsp-ui-doc-mode)
;;                           (company-mode))))
;;   :config
;;   (setq rust-indent-method-chain t)
;;   (setq rustic-format-on-save t))

;; (use-package flycheck-rust
;;   :ensure t)

Scheme Mode

See also: http://wiki.call-cc.org/emacs

;; (use-package racket-mode
;;   :ensure t
;;   :config
;;   (defun kfi/racket-mode-hook ()
;;     (company-mode 1)
;;     (paredit-mode 1))
;;   (add-hook 'racket-mode-hook 'kfi/racket-mode-hook)
;;   (add-hook 'racket-repl-mode-hook 'kfi/racket-mode-hook))

;; (use-package emacs
;;   ;; :ensure t
;;   :config
;;   ;; http://wiki.call-cc.org/emacs
;;   (defun scheme-module-indent (state indent-point normal-indent) 2)
;;   (put 'module 'scheme-indent-function 'scheme-module-indent)
;;   (put 'and-let* 'scheme-indent-function 1)
;;   (put 'parameterize 'scheme-indent-function 1)
;;   (put 'handle-exceptions 'scheme-indent-function 1)
;;   (put 'when 'scheme-indent-function 1)
;;   (put 'if 'scheme-indent-function 1)
;;   (put 'unless 'scheme-indent-function 1)
;;   (put 'match 'scheme-indent-function 1)
;;   (defun kfi/scheme-mode-hook ()
;;     (company-mode 1)
;;     (paredit-mode 1))
;;   (add-hook 'scheme-mode-hook 'kfi/scheme-mode-hook))

Swift Mode

;; (use-package swift-mode
;;   :ensure t
;;   :defer t
;;   :config
;;   (defun kfi/swift-mode ()
;;     ;;(lsp) ;; I don't think this works for XCode projects.
;;     (setq swift-mode:basic-offset 4)
;;     (setq indent-tabs-mode nil)
;;     (hl-line-mode 1)
;;     (company-mode 1))
;;   (add-hook 'swift-mode-hook 'kfi/swift-mode))

See if lsp-sourcekit is a pleasant diversion from Xcode.

;; (use-package lsp-sourcekit
;;   :after lsp-mode
;;   :ensure t
;;   :config
;;   (setenv "SOURCEKIT_TOOLCHAIN_PATH" "/Library/Developer/Toolchains/swift-latest.xctoolchain")
;;   ;; xcrun --find sourcekit-lsp
;;   (setq lsp-sourcekit-executable (expand-file-name "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp")))

I don’t think this works too well if you point it at a random Xcode based project. I get a lot of errors like:

could not find manifest, or not a SwiftPM package: /Path/To/Xcode/Project

So, this is a no go for the time being.

Settings

Default directory

(setq default-directory "~/")

Hollow cursor

(set-default 'cursor-type 'hollow)

Global auto revert files (load if changed on disk)

Global Auto Revert mode is a global minor mode that reverts any buffer associated with a file when the file changes on disk.

(global-auto-revert-mode 1)

Truncate lines

Do not wrap lines when they’re too long.

(setq-default truncate-lines t)

Electric parens

Toggle automatic parens pairing (Electric Pair mode).

(electric-pair-mode 1)

Replace selected text when you type something

When Delete Selection mode is enabled, typed text replaces the selection if the selection is active. Otherwise, typed text is just inserted at point regardless of any selection.

(delete-selection-mode 1)

Show column numbers in the mode line

Toggle column number display in the mode line (Column Number mode).

(column-number-mode 1)

Use regular keys for cut/copy/paste (cua-mode)

CUA mode is a global minor mode. When enabled, typed text replaces the active selection, and you can use C-z, C-x, C-c, and C-v to undo, cut, copy, and paste in addition to the normal Emacs bindings. The C-x and C-c keys only do cut and copy when the region is active, so in most cases, they do not conflict with the normal function of these prefix keys.

(cua-mode 1)

Show matching parens

Toggle visualization of matching parens (Show Paren mode).

(show-paren-mode t)

Font resizing keyboard bindings

Mimics normal Mac keybindings for font resizing.

(global-set-key (kbd "s-+") 'text-scale-increase)
(global-set-key (kbd "s-_") 'text-scale-decrease)

Window navigation keyboard commands

(global-set-key (kbd "M-`") 'other-frame)
(global-set-key (kbd "s-<right>") 'windmove-right)
(global-set-key (kbd "s-<left>") 'windmove-left)
(global-set-key (kbd "s-<up>") 'windmove-up)
(global-set-key (kbd "s-<down>") 'windmove-down)

(defun kfi/goto-scratch ()
  "Switch (or create) the *scratch* buffer."
  (interactive)
  (switch-to-buffer (get-buffer-create "*scratch*"))
  (lisp-interaction-mode))

(global-set-key (kbd "s-0") 'delete-window)
(global-set-key (kbd "s-1") 'delete-other-windows)
(global-set-key (kbd "s-2") 'split-window-below)
(global-set-key (kbd "s-3") 'split-window-right)
(global-set-key (kbd "s-9") 'kfi/goto-scratch)

Window placement

Rules:

  • Terminal windows appear at the bottom.
  • Help windows appear docked on the left.
(use-package emacs
  :hook ((help-mode . visual-line-mode))
  :init
  (setq display-buffer-alist
        '(("\\*Help.*"
           (display-buffer-in-side-window)
           (window-parameters . ( ;;(no-other-window . t)
                                 (mode-line-format . (" " mode-line-buffer-identification))))
           (window-width . 0.5)
           (side . left)
           (slot . 0))
          ;; ("\\*eshell.*"
          ;;  (display-buffer-in-side-window)
          ;;  (window-parameters . ((mode-line-format . (" " mode-line-buffer-identification))))
          ;;  (window-height . 0.2)
          ;;  (side . bottom)
          ;;  (slot . 0))
          )))

Uncategorized

;; banish custom config to another file
(setq custom-file "~/.emacs.d/custom.el")

;; UTF0-8 (still necessary?)
(prefer-coding-system 'utf-8)
(when (display-graphic-p)
  (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)))

;; Don't require spelling out yes or no.
(fset 'yes-or-no-p 'y-or-n-p)

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

(if (display-graphic-p)
    (progn (scroll-bar-mode 0)
           (fringe-mode '(10 . 10)))
  (progn (menu-bar-mode 0)))

(setq ns-use-mwheel-momentum nil)
(pixel-scroll-mode 0)

(custom-set-variables '(indent-tabs-mode nil))
(setq-default line-spacing 1)
(setq-default inhibit-startup-screen t)
(setq-default standard-indent 2)
(setq-default tab-width 2)
(add-hook 'before-save-hook 'whitespace-cleanup)
(blink-cursor-mode 0)
(setq ring-bell-function 'ignore)

;; Set the frame title to the visited file's path
;; using the abbreviated form (~ for home).
(setq frame-title-format
      '((:eval (if (buffer-file-name)
                   (abbreviate-file-name (buffer-file-name))
                 "%b"))))

(setq mac-command-modifier 'super)

;; Quick access to shell
(global-set-key (kbd "C-c e") 'eshell)

(setq ns-use-srgb-colorspace t)

Custom Functions

Sort lines (nocase)

(defun kfi/sort-lines-nocase ()
  "Sort lines ignoring case"
  (interactive)
  (let ((sort-fold-case t))
    (call-interactively 'sort-lines)))

Insert timestamp

What I use when I can’t use Keyboard Maestro. Each of the formats could be made into a yasnippet (which is what I do with KB Maestro) using an elisp expression as its body. But I like the completing-read minibuffer menu thing here.

(defvar kfi/timestamp-formats
  '(("March 15, 2020 @ 9:33 PM" . "%B %-d, %Y @ %-I:%M %p")
    ("March 15, 2020"           . "%B %-d, %Y")
    ("2020-03-15T21:33:54"      . "%Y-%m-%dT%H:%M:%S")
    ("2020-03-15"               . "%Y-%m-%d")
    ("2020-03-15 09:35 PM"      . "%Y-%m-%d %I:%M %p")
    ("2020-03-15T21:39:20-0700" . "%FT%T%z"))
  "Formats for inserting a timestamp into a document.")

(defun kfi/timestamp ()
  "Choose a format, then insert a timestamp."
  (interactive)
  (if-let* ((choices (mapcar #'car kfi/timestamp-formats))
            (choice (completing-read "Format:" choices))
            (format (cdr (assoc choice kfi/timestamp-formats))))
      (insert (format-time-string format))
    (insert "<error:nil-selection>")))

Remove smart quotes from a buffer

(defun kfi/unsmart ()
  "Remove smart quotes from buffer."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "" nil t)
      (replace-match "'"))
    (goto-char (point-min))
    (while (re-search-forward "" nil t)
      (replace-match "\""))
    (goto-char (point-min))
    (while (re-search-forward "" nil t)
      (replace-match "\""))))

Remove newlines until end of paragraph @bug

Actually, this doesn’t do what I think it does: it seems to go to the stop of the buffer, when it should instead find the beginning of the paragraph. I wonder what the original intent was?

(defun kfi/unwrap-line ()
  "Remove newlines until end-of-paragraph."
  (interactive)
  (let ((start (point-min))
        (end (copy-marker (or (search-forward "\n\n" nil t)
                              (point-max))))
        (fill-column (point-max)))
    (fill-region start end)
    (goto-char end)
    (newline)
    (goto-char start)))

Set frame sizing functions

(defun kfi/set-frame-width (arg)
  "Set the width of the frame to ARG."
  (interactive "nFrame width: ")
  (set-frame-width (selected-frame) arg))

(defun kfi/dw ()
  "Set frame to double-wide."
  (interactive)
  (kfi/set-frame-width 180))

(defun kfi/sw ()
  "Set frame to single-wide."
  (interactive)
  (kfi/set-frame-width 90))

(global-set-key (kbd "C-c C-x w") 'kfi/dw)
(global-set-key (kbd "C-c C-x s") 'kfi/sw)

(defun kfi/set-frame-height (arg)
  "Set frame height to ARG."
  (interactive "nFrame height: ")
  (set-frame-height (selected-frame) arg))

(defun kfi/set-frame-dimensions (w h)
  "Set frame dimensions to W and H."
  (interactive "nFrame width: \nnFrame height: ")
  (set-frame-width (selected-frame) w)
  (set-frame-height (selected-frame) h))

Go back to the previous window

(defun kfi/back-window ()
  "Go back to previously visited window."
  (interactive)
  (other-window -1))

(global-set-key (kbd "C-x p") 'kfi/back-window)

Open the current file in whatever MacOS thinks is the appropriate handler

(defun kfi/open-this ()
  "Open the buffer using MacOS defaults."
  (interactive)
  (shell-command (concat "open '" (buffer-file-name) "'")))

(global-set-key (kbd "C-c C-x o") 'kfi/open-this)

Adjust font size up or down

(defun kfi/set-font-size (size)
  "Set the font SIZE."
  (interactive "nNew size: ")
  (set-face-attribute 'default nil :height size))

(defun kfi/font-size-up ()
  "Shift font size up by 10 units."
  (interactive)
  (kfi/set-font-size (+ (face-attribute 'default :height) 10)))

(defun kfi/font-size-down ()
  "Shift font size down by 10 units."
  (interactive)
  (kfi/set-font-size (- (face-attribute 'default :height) 10)))

Generate some lorem ipsum text

(defun kfi/lorem ()
  "Output a bunch of lorem ipsum text."
  (interactive)
  (insert
   "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do "
   "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim"
   "ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut "
   "aliquip ex ea commodo consequat. Duis aute irure dolor in "
   "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla "
   "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in "
   "culpa qui officia deserunt mollit anim id est laborum."))

Sort the words in the current selection

(defun kfi/sort-words (reverse beg end)
  "Sort region words REVERSE if negative from BEG to END.
Prefixed with negative \\[universal-argument], sorts in reverse.
The variable `sort-fold-case' determines whether alphabetic case
affects the sort order.  See `sort-regexp-fields'."
  (interactive "*P\nr")
  (sort-regexp-fields reverse "\\w+" "\\&" beg end))

Make the background (and foreground) slightly transparent (or not)

(defun kfi/transparency-on ()
  "Turn on frame transparency."
  (interactive)
  (set-frame-parameter nil 'alpha '(95 95))
  (add-to-list 'default-frame-alist '(alpha 95 95)))

(defun kfi/transparency-no ()
  "Turn off frame transparency."
  (interactive)
  (set-frame-parameter nil 'alpha '(100 100))
  (add-to-list 'default-frame-alist '(alpha 100 100)))

Visit Emacs’ init.el file

(defun kfi/edit-init-el ()
  "Visit the init.el file."
  (interactive)
  (find-file "~/.emacs.d/init.el"))

Visit configuration.org for Emacs

(defun kfi/my-config ()
  "Visit my configuration.org file for Emacs."
  (interactive)
  (find-file "~/.emacs.d/configuration.org"))

Futures

Find a way for fly-spell to not check certain words

Do something like the following, and have that function check for words beginning and ending with “=” or “@” or “#” or “~”.

(add-hook 'java-mode-hook
          (lambda ()
            (setq flyspell-generic-check-word-predicate 'my-new-function)))

Also check: https://emacs.stackexchange.com/a/31302 for using faces to avoid spellchecking in comments, though I’m not sure this will work in org-mode given it’s not a programming mode. Hm.

(setq flyspell-prog-text-faces
      (delq 'font-lock-string-face
            flyspell-prog-text-faces))

Advice

How do you add words to the spell check local dictionary?

Try M-$, then i then yes to accept the word. Maybe sure the word is highlighted.

To double check your learned words, look in ~/.aspell.pws.

Where does Flyspell put your “learned” words?

Look in ~/.aspell.pws.

How do I fix issues with lsp-java always wanting to rename packages?

Quit emacs, then go into ~/.emacs.d and remove the workspace directory. When you restart, things should be better.

Also, visit build.gradle and run M-x lsp-java-update-project-configuration, and make lsp-restart-workspace.

How do I add a new project to Treemacs without triggering “project already exists”?

When you enter in the path to the new project for your workspace, remove the trailing / from the path.