Skip to content

My general purpose, web-dev-centric, emacs configuration

License

Notifications You must be signed in to change notification settings

jeremygooch/jeremacs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jeremy’s Emacs Config

./assets/screenshot.png

Getting Started

Opinionated emacs config centered around general web development (primarily for angular at this time), minimal lisp development, and documentation. This is a literate config so simply tangle this file and any additional org files in this repo you might like to use. The additional org files include functionality like my personal keybindings and common snippets for template completion, etc.

This config requires Emacs >= 29 compiled with treesitter. It is possible to run this config on older Emacs versions, but you may need to turn off certain blocks (i.e. treesitter) to get it to work correctly. For instance, I’m running this config on an Android phone via termux for Emacs 28.3 and it works great but did require turning off certain features via trial-and-error.

Note, this config will attempt to install language grammers for treesitter as you need them, so no need to install those manually.

To use this project:

  1. clone this repo
  2. install dependencies
  3. update readme in Basic Setup for your personal binary paths and such
  4. Tangle the readme. To tangle the file, open it in emacs, then run C-c C-v t.
  5. Tangle any other org files in this repo that you would like to use

Notes

To omit a block from being tangled, just set the src header to include :tangle no. More info on header arguments.

Known Issues

  • The modeline does not update to doom-modeline on initial install. Workaround: close and reopen emacs and the modeline should show up and work properly.

Dependencies

  • ripgrep (rg) for better grep
  • node for JS cli, recommend (but not require) nvm for managing versions
  • Typescript server. Make sure it is on your PATH
  • Angular Language Server
  • eslint: npm i -g eslint
  • html support: npm install -g vscode-langservers-extracted
  • Roboto and Inconsolata fonts
  • Pandoc for converting between org mode and other formats like markdown

OS Specific Deps

MacOS

  • Aspell for spell-checking, can be installed with brew

Basic Setup

Update the following variables to match your setup.

(setq jrm/custom-vars
      '((path-to-here . "/Users/jeremygooch/src/jeremacs/")
        (node . "/Users/jeremygooch/.nvm/versions/node/v19.9.0/")
        (html-language-server . "/Users/jeremygooch/.nvm/versions/node/v19.9.0/bin/html-languageserver")
        (angular-language-server . "/Users/jeremygooch/.nvm/versions/node/v19.9.0/lib/node_modules/@angular/language-server")
        (global-node-modules . "/Users/jeremygooch/.nvm/versions/node/v19.9.0/lib/node_modules")
        (name . "Jeremy Gooch")
        (email . "jeremy.gooch@gmail.com")))

Detailed Install

Initialization

early-init.el

In Emacs >= 27.1, the early-init.el file is run before the GUI is created. This can be used to take care of a few miscellaneous odds and ends.

;; -*- lexical-binding: t; -*-

;; -------------------------------------------------------------------------------- ;;
;; This early-init.el file was auto-tangled from an orgmode file.                   ;;
;; -------------------------------------------------------------------------------- ;;

;; Garbage Collections
(setq gc-cons-threshold 100000000) ;; ~100mb
(setq read-process-output-max 3000000) ;; ~3mb
(setq gc-cons-percentage 0.6)

;; Compile Warnings
(setq comp-async-report-warnings-errors nil) ;; native-comp warning
(setq byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))

;; Whether to make packages available when Emacs starts
(setq package-enable-at-startup nil)

;; Disables bi-directional editing (i.e. writing in both Arabic and English)
(setq-default bidi-display-reordering 'left-to-right 
              bidi-paragraph-direction 'left-to-right)
(setq bidi-inhibit-bpa t)  ; emacs 27 only - disables bidirectional parenthesis

;; Misc UI optimizations
(setq fast-but-imprecise-scrolling t)
(setq inhibit-compacting-font-caches t)
;; Slow down the UI updates a bit
(setq idle-update-delay 1.0)

Personal Information

Header of tangled output

;; -*- lexical-binding: t; -*-
;;;
;;; Jeremy's Emacs Configuration
;;;

;; Copyright (C) Jeremy Gooch
;; Author: Jeremy Gooch <jeremy.gooch@gmail.com>
;; URL: https://github.com/jeremygooch/dotemacs
;; This file is not part of GNU Emacs.
;; This file is free software.

;; ------- The following code was auto-tangled from an Orgmode file. ------- ;;

For the sake of completeness, configure name and email address

<<basic-setup>>
  (setq user-full-name (cdr (assoc 'name jrm/custom-vars))
        user-mail-address (cdr (assoc 'name jrm/custom-vars)))

Packages

Set Sources

(require 'package)
(setq package-archives '(("melpa-stable" . "http://stable.melpa.org/packages/")
                         ("elpa" . "https://elpa.gnu.org/packages/")
                         ("gnu" . "http://elpa.gnu.org/packages/")
                         ("melpa" . "https://melpa.org/packages/")))
(package-initialize)

use-package Setup

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

(require 'use-package-ensure)
(setq use-package-always-ensure t)
(setq use-package-verbose nil)

;; Allow use-package to install missing system packages
(use-package use-package-ensure-system-package :ensure t)

Better Garbage Collection Strategy

(use-package gcmh
  :diminish gcmh-mode
  :config
  (setq gcmh-idle-delay 5
        gcmh-high-cons-threshold (* 16 1024 1024))  ; 16mb
  (gcmh-mode 1))

(add-hook 'emacs-startup-hook
          (lambda ()
            (setq gc-cons-percentage 0.1))) ;; Default value for `gc-cons-percentage'

Path

Ensure environment variables inside Emacs look the same as in the standard shell.

(setq exec-path (append exec-path '("/usr/local/bin")))
(use-package exec-path-from-shell
  :init
  (exec-path-from-shell-initialize))

Set custom exec path for git and node

(setq exec-path (append exec-path '("/usr/local/git/bin")))
(setq exec-path (append exec-path (list (concat (cdr (assoc 'node jrm/custom-vars)) "bin/"))))

Ensure node is on path

(setenv "PATH" (concat (getenv "PATH") (concat ":" (cdr (assoc 'node jrm/custom-vars)) "bin/")))

Interface

General Global Preferences

Prompts

I prefer emacs to just ask y/n not yes/no

(fset 'yes-or-no-p 'y-or-n-p)

Silence alarms

(setq ring-bell-function 'ignore)

Shells

Prevent async shell command buffers from popping-up:

(add-to-list 'display-buffer-alist
  '("\\*Async Shell Command\\*.*" display-buffer-no-window))

Regex

Fix emacs’ regex

(setq-default pcre-mode t)

Spellcheck

Use aspell for Mac (aspell can be installed with brew)

(setq ispell-program-name "/usr/local/bin/aspell")

Scrollbars and Toolbars

Remove default scrollbars and toolbars.

(scroll-bar-mode -1)
(menu-bar-mode -1)
(tool-bar-mode -1)

Buffers

Remember where I left off after killing a file

(save-place-mode 1)

When killing a buffer always pick the current buffer by default

(defun kill-current-buffer ()
  "Kills the current buffer."
  (interactive)
  (kill-buffer (current-buffer)))
(global-set-key (kbd "C-x k") 'kill-current-buffer)

When a file changes on disk, automatically reload its buffer silently

(global-auto-revert-mode 1)
(setq global-auto-revert-non-file-buffers t)
(setq auto-revert-verbose nil)

Symbols

(global-prettify-symbols-mode 1)

Dired

Layout

Default dired flags (uses ls style syntax)

(setq dired-listing-switches "-alh")

Icons & Subfolders

See child folders without having to open child in a new buffer. Always refresh the buffer on showing a subfolder.

(defun jrm/dired-subtree-toggle-and-refresh ()
  "Calls dired toggle and refreshes the buffer."
  (interactive)
  (dired-subtree-toggle)
  (revert-buffer))

(use-package dired-subtree
    :after dired
    :config
    (bind-key "<tab>" #'jrm/dired-subtree-toggle-and-refresh dired-mode-map)
    (bind-key "<backtab>" #'dired-subtree-cycle dired-mode-map))

Look and feel

(use-package all-the-icons-dired
  :config (unless (member "all-the-icons" (font-family-list)) (all-the-icons-install-fonts t)))
(add-hook 'dired-mode-hook 'all-the-icons-dired-mode)

Zip Files

Allow uncompressing zip files

(eval-after-load "dired-aux"
   '(add-to-list 'dired-compress-file-suffixes 
		   '("\\.zip\\'" ".zip" "unzip")))

IBuffer

Add a little organization to the default ibuffer view

(setq ibuffer-saved-filter-groups
(quote (("default"
         ("dired" (mode . dired-mode))
         ("org" (mode . org-mode))
         ("shell" (mode . shell-mode))
         ("git" (name . "^magit\*"))
         ("Slack" (or (mode . slack-mode)
                                        (name . "^\\*Slack.*$")))
         ("email" (name . "^\\*mu4e-.*\\*$"))
         ("ecmascript" (or (mode . javascript-mode)
                                 (name . "^.*.js$")
                                 (name . "^.*.ts")
                                 (name . "^.*.json$")))
         ("markup" (or (mode . web-mode)
                                         (name . "^.*.tpl")
                                         (name . "^.*.mst")
                                         (name . "^.*.html")))
         ("images" (name . "^.*png$"))
         ("process" (or (mode . grep-mode)
                        (name . "^\\*tramp*$")))
         ("emacs" (or (name . "^\\*scratch\\*$")
                                        (name . "^\\*Messages\\*$")
                                        (name . "^\\*eww\\*$")
                                        (name . "^\\*GNU Emacs\\*$")))))))
(add-hook 'ibuffer-mode-hook (lambda () (ibuffer-switch-to-saved-filter-groups "default")))

Searching

RipGrep

Use ripgrep by default

(use-package rg)

Popups

GPG Pinentry

Instead of using the display’s popup, prompt for gpg creds in the minibuffer

(setq epa-pinentry-mode 'loopback)

Completion

Ivy

Generic auto-complete with Ivy (+ counsel swipper).

(use-package ivy :demand
  :diminish ivy-mode
  :config
  (setq ivy-use-virtual-buffers t
	   ivy-count-format "%d/%d ")
  (global-set-key (kbd "C-x b") 'ivy-switch-buffer))
(ivy-mode 1)
(setq ivy-use-selectable-prompt t)

(use-package ivy-prescient
  :config (ivy-prescient-mode))

Ivy enhanced search (swiper) and common Emacs meta commands (counsel)

(use-package counsel
  :config
  (global-set-key (kbd "M-x") 'counsel-M-x)
  (global-set-key (kbd "C-M-SPC") 'counsel-git))

(use-package swiper
  :config
  (global-set-key (kbd "C-s") 'swiper-isearch))

Which Key

Some quick help for when getting stuck in the middle of a command

(use-package which-key :config (which-key-mode))

Yasnippet

(use-package yasnippet
  :init (setq yas-snippet-dirs '("~/.emacs.d/snippets"))
  :config (yas-global-mode))

Theme

Nord Theme

This config uses the Nord theme port for emacs.

(use-package nord-theme
  :config
  (load-theme 'nord :no-confirm))

Dashboard

(use-package dashboard
  :config
  (dashboard-setup-startup-hook)
  (setq dashboard-startup-banner (concat (cdr (assoc 'path-to-here jrm/custom-vars)) "/assets/Lambda_transparent.png"))
  (setq dashboard-items '((recents  . 10)))
  (setq dashboard-banner-logo-title ""))

Highlight Line

(global-hl-line-mode +1)

Modeline and Minibuffer

Uses doom-modeline for performance reasons. Spaceline is also nice, but the icons cause performance issues when opening emacs (see: domtronn/spaceline-all-the-icons.el#55).

Fortunately, doom-modeline uses nerd icons which don’t suffer from the performance hit and the modeline still looks nice.

(use-package doom-modeline
  :hook (after-init . doom-modeline-mode)
  :config (unless (member "Symbols Nerd Font Mono" (font-family-list)) (nerd-icons-install-fonts t))
  :custom
  (doom-modeline-height 25)
  (doom-modeline-bar-width 1)
  (doom-modeline-icon t)
  (doom-modeline-major-mode-icon t)
  (doom-modeline-major-mode-color-icon t)
  (doom-modeline-buffer-file-name-style 'truncate-upto-project)
  (doom-modeline-buffer-state-icon t)
  (doom-modeline-buffer-modification-icon t)
  (doom-modeline-minor-modes nil)
  (doom-modeline-enable-word-count nil)
  (doom-modeline-buffer-encoding t)
  (doom-modeline-indent-info nil)
  (doom-modeline-checker-simple-format t)
  (doom-modeline-vcs-max-length 12)
  (doom-modeline-env-version t)
  (doom-modeline-irc-stylize 'identity)
  (doom-modeline-github-timer nil)
  (doom-modeline-gnus-timer nil))

(add-hook 'after-init-hook #'doom-modeline-mode)

For the minibuffer show the current time and battery indicator

(setq display-time-24hr-format t)
(setq display-time-format "%H:%M - %d.%b.%y")
(display-time-mode 1)
(display-battery-mode 1)

Font

(set-face-attribute 'default nil :height 140)
(set-face-attribute 'default nil :font "Inconsolata-14")
(set-face-attribute 'default nil :font "Inconsolata-18")

Org Mode

Minor Modes

Load some basic minor modes by default

(add-hook 'org-mode-hook 'no-trailing-whitespace)
(add-hook 'org-mode-hook 'flyspell-mode)
(add-hook 'org-mode-hook 'variable-pitch-mode)

Formatting Marks and symbols

Show the asterisks as bullets and set up indentation

(use-package org-bullets :config (add-hook 'org-mode-hook (lambda () (org-bullets-mode))))
(add-hook 'org-mode-hook 'org-indent-mode)

Hide formatting characters

(setq org-hide-emphasis-markers t)

Show lists with a bullet rather than the - character.

(font-lock-add-keywords 'org-mode
                        '(("^ *\\([-]\\) "
                           (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) ""))))))

Source Blocks

  (custom-set-faces
   '(org-block ((t (:inherit shadow :extend t :background "#202531"))))
   '(org-block-begin-line ((t (:extend t :background "#191d27" :foreground "#617776" 
:height 0.9))))
   '(org-block-end-line ((t (:extend t :background "#191d27" :foreground "#617776" :height 0.9)))))

Turn off automatic actual width for inline images

This is helpful so images can be easily resized inline with org attributes

(setq org-image-actual-width nil)

Additional Export Backends

Markdown
(require 'ox-md nil t)

Helpful utility using pandoc as the backend for converting markdown back to org mode. Lifted from here.

(defun markdown-convert-buffer-to-org ()
  "Convert the current buffer's content from markdown to orgmode format and save it with the current buffer's file name but with .org extension."
  (interactive)
  (shell-command-on-region (point-min) (point-max)
                           (format "pandoc -f markdown -t org -o %s"
                                   (concat (file-name-sans-extension (buffer-file-name)) ".org"))))

Additional Font Settings

Org tables formatting depends on fixed pitch fonts, so the follow setting overrides variable pitch just for tables.

(set-face-attribute 'org-table nil :inherit 'fixed-pitch)

Glyphs/Symbols/Ligatures for common words/expressions

Some nice eye candy for code buffers

(defun jrm/ecma-prettify-symbols ()
  "Adds common ECMA symobls to prettify-symbols-alist."
  (push '(">=" . ?≥) prettify-symbols-alist)
  (push '("=>" . ?⇒) prettify-symbols-alist)
  (push '("<=" . ?≤) prettify-symbols-alist)
  (push '("===" . ?≡) prettify-symbols-alist)
  (push '("!=" . ?≠) prettify-symbols-alist)
  (push '("!==" . ?≢) prettify-symbols-alist)
  (push '("&&" . ?∧) prettify-symbols-alist)
  (prettify-symbols-mode))
(global-prettify-symbols-mode 1)

Narrowing Buffers

DWIM Narrow

The following narrow was lifted from Protesilaos Stavrou blog/video: https://protesilaos.com/codelog/2021-07-24-emacs-misc-custom-commands/

(defun prot-common-window-bounds ()
  "Determine start and end points in the window."
  (list (window-start) (window-end)))
;;;###autoload
(defun prot-simple-narrow-visible-window ()
  "Narrow buffer to wisible window area.
Also check `prot-simple-narrow-dwim'."
  (interactive)
  (let* ((bounds (prot-common-window-bounds))
         (window-area (- (cadr bounds) (car bounds)))
         (buffer-area (- (point-max) (point-min))))
    (if (/= buffer-area window-area)
        (narrow-to-region (car bounds) (cadr bounds))
      (user-error "Buffer fits in the window; won't narrow"))))
;;;###autoload
(defun prot-simple-narrow-dwim ()
  "Do-what-I-mean narrowing.
If region is active, narrow the buffer to the region's
boundaries.
If no region is active, narrow to the visible portion of the
window.
If narrowing is in effect, widen the view."
  (interactive)
  (unless mark-ring                  ; needed when entering a new buffer
    (push-mark (point) t nil))
  (cond
   ((and (use-region-p)
         (null (buffer-narrowed-p)))
    (let ((beg (region-beginning))
          (end (region-end)))
      (narrow-to-region beg end)))
   ((null (buffer-narrowed-p))
    (prot-simple-narrow-visible-window))
   (t
    (widen)
    (recenter))))
(global-set-key (kbd "C-x n n") 'prot-simple-narrow-dwim)

Global Font Sizes Quick Adjustments

I find myself need specific font sizes for different scenarios, i.e. projecting, screen-sharing on conference calls, etc. So, binding these to a quick way to toggle through them with M-x jrm/adjust-font-size.

Note: there might be a better way to handle this but things like M-+/M– won’t zoom things like line numbers, etc.

(defvar jrm/screens-alist '((?0 "xsmall" (lambda () (set-face-attribute 'default nil :height 70) 'default))
                            (?1 "small" (lambda () (set-face-attribute 'default nil :height 110) 'default))
                            (?2 "medium" (lambda () (set-face-attribute 'default nil :height 120) 'proj))
                            (?3 "large" (lambda () (set-face-attribute 'default nil :height 140) 'proj))
                            (?4 "xtra-large" (lambda () (set-face-attribute 'default nil :height 160) 'projLg))
                            (?5 "xxtra-large" (lambda () (set-face-attribute 'default nil :height 190) 'projLg))
                            (?6 "xxxtra-large" (lambda () (set-face-attribute 'default nil :height 210) 'projLg)))
  "List that associates number letters to descriptions and actions.")
(defun jrm/adjust-font-size ()
  "Lets the user choose the the font size and takes the corresponding action.
Returns whatever the action returns."
  (interactive)
  (let ((choice (read-char-choice
                 (mapconcat (lambda (item) (format "%c: %s" (car item) (cadr item)))
                            jrm/screens-alist "; ")
                 (mapcar #'car jrm/screens-alist))))
    (funcall (nth 2 (assoc choice jrm/screens-alist)))))

Whitespace

I prefer to see trailing whitespace but not for every mode (e.g. org, elfeed, etc)

(use-package whitespace
  :config
  (setq-default show-trailing-whitespace t)
  (defun no-trailing-whitespace ()
    (setq show-trailing-whitespace nil))
  (add-hook 'minibuffer-setup-hook              'no-trailing-whitespace)
  (add-hook 'dashboard-mode-hook                'no-trailing-whitespace)
  (add-hook 'eww-mode-hook                      'no-trailing-whitespace)
  (add-hook 'vterm-mode-hook                    'no-trailing-whitespace)
  (add-hook 'shell-mode-hook                    'no-trailing-whitespace)
  (add-hook 'mu4e:view-mode-hook                'no-trailing-whitespace)
  (add-hook 'eshell-mode-hook                   'no-trailing-whitespace)
  (add-hook 'help-mode-hook                     'no-trailing-whitespace)
  (add-hook 'term-mode-hook                     'no-trailing-whitespace)
  (add-hook 'slack-message-buffer-mode-hook     'no-trailing-whitespace)
  (add-hook 'mu4e:view-mode-hook                'no-trailing-whitespace)
  (add-hook 'calendar-mode-hook                 'no-trailing-whitespace))

Frame

Fullscreen

(set-frame-parameter nil 'fullscreen 'fullboth)

Border for Mac

(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
(add-to-list 'default-frame-alist '(ns-appearance . dark))

;; Autohide the top panel if necessary
(setq ns-auto-hide-menu-bar t)
(toggle-frame-maximized)

(set-face-attribute 'default nil :height 120)

Line Wrapping / Truncate Lines

(global-visual-line-mode t)

Movement

Avy

Avy is great for speed-of-thought navigation. Only install it when needed.

(use-package avy)

Org Mode

Setup an easy way to jump to an org headline using org-goto C-c C-j

 (setq org-goto-interface 'outline-path-completion
	org-goto-max-level 10)

 (setq org-outline-path-complete-in-steps nil)

Simple hack (found on redit) to ensure you can expand a heading even if the cursor is on the ellipses after a collapsed heading:

(defun my-org-prepare-expand-heading ()
  "Move point to before ellipsis, if after ellipsis."
  (when (and (not (org-at-heading-p))
             (save-excursion
               (org-end-of-line)
               (org-at-heading-p)))
    (org-end-of-line)))

(add-hook 'org-tab-first-hook #'my-org-prepare-expand-heading)

File Editing

File Backups

Keep temporary and backup buffers out of current directory like a civilized editor.

(custom-set-variables
 '(auto-save-file-name-transforms '((".*" "~/.emacs.d/autosaves/\\1" t)))
 '(backup-directory-alist '((".*" . "~/.emacs.d/backups/")))
 '(delete-old-versions t))

(make-directory "~/.emacs.d/autosaves/" t)
(setq create-lockfiles nil)

Text Overwriting

Replace region with next keystroke.

(delete-selection-mode 1)

Bi-directional Editing

Disable bidirectional editing for performance issues when opening large files.

(setq bidi-paragraph-direction 'left-to-right)

Programming Specific

HTML/(S)CSS

(use-package sass-mode
  :config
  (add-to-list 'auto-mode-alist '("\\.scss\\'" . scss-mode)))

(use-package web-mode
  :config
  (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.tpl\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.mst\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.hbs\\'" . web-mode))
  :custom (web-mode-enable-auto-indentation nil))
(use-package emmet-mode
  :defer
  :config
  (add-hook 'sgml-mode-hook 'emmet-mode)
  (add-hook 'css-mode-hook 'emmet-mode)
  (add-hook 'web-mode-hook 'emmet-mode)
  (add-hook 'sass-mode-hook 'emmet-mode))

PHP

(use-package php-mode
  :config
  (autoload 'php-mode "php-mode-improved" "Major mode for editing php code." t)
  (add-to-list 'auto-mode-alist '("\\.php$" . php-mode))
  (add-to-list 'auto-mode-alist '("\\.inc$" . php-mode)))

Rest Client

(use-package restclient)
(use-package ob-restclient)

Typescript

(use-package typescript-mode
      :hook (typescript-mode . jrm/ecma-prettify-symbols)
      :hook (typescript-ts-mode . jrm/ecma-prettify-symbols))
(use-package ob-typescript :diminish typescript-mode)

LSP

This config uses lsp-mode instead of eglot for better angular template support out of the box.

(use-package lsp-mode
  ;; :hook ((typescript-mode . lsp-mode)
  ;;        (typescript-ts-mode . lsp-mode)
  ;;        (javascript-mode . lsp-mode)
  ;;        (js-mode . lsp-mode)
  ;;        (js2-mode . lsp-mode)
  ;;        (html-mode . lsp)
  ;;        (scss-mode . lsp)
  ;;        (sass-mode . lsp)
  ;;        (css-mode . lsp)
  ;;        (web-mode . lsp)
  ;;        (terraform-mode . lsp)
  ;;        (clojure-mode . lsp)
  ;;        (lsp-mode . lsp-enable-which-key-integration))
  :hook ((prog-mode . lsp-deferred)
         (lsp-mode . lsp-enable-which-key-integration))
  :custom
  (read-processs-output-max (* 1024 1024))
  :commands lsp
  :bind (("M-." . lsp-find-definition))
  :config (setq lsp-idle-delay 1)
  :init
  (setq lsp-keymap-prefix "C-c")
  (setq lsp-diagnostics-provider :flycheck))

(use-package lsp-ui
  :commands lsp-ui-mode
  :hook (lsp-mode . lsp-ui-mode))
(use-package helm-lsp :commands helm-lsp-workspace-symbol)
(use-package lsp-treemacs :commands lsp-treemacs-errors-list)
(use-package dap-mode)
(add-to-list 'auto-mode-alist '("\\.js[mx]?\\'" . js2-mode))
;; Clean up the javascript-mode entry from the alist to avoid issues with overriding it
(delete '("\\.js[mx]?\\'" . javascript-mode) auto-mode-alist)
Angular Server
(setq lsp-html-server-command `(,(cdr (assoc 'html-language-server jrm/custom-vars)) "--stdio"))
(setq lsp-clients-angular-language-server-command
      `("node"
        ,(cdr (assoc 'angular-language-server jrm/custom-vars))
        "--ngProbeLocations"
        ,(cdr (assoc 'global-node-modules jrm/custom-vars))
        "--tsProbeLocations"
        ,(cdr (assoc 'global-node-modules jrm/custom-vars))
        "--stdio"))
Autocomplete with Company
(use-package company
  :defer t
  :after lsp-mode
  :hook (prog-mode . company-mode)
  :config
  (setq company-minimum-prefix-length 2)
  (setq company-idle-delay 0.2))

(global-company-mode)
(global-set-key (kbd "TAB") #'company-indent-or-complete-common)
(setq company-tooltip-align-annotations t)

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

Inline Compilation Errors with Flycheck

(use-package flycheck
  :diminish flycheck-mode
  :init
  (setq flycheck-javascript-eslint-executable "eslint_d")
  :hook (lsp-mode . flycheck-mode)
  :custom (flycheck-display-error-delay .3))

TreeSitter

Auto install treesitter sources if they’re not present

(use-package treesit-auto
  :custom
  (treesit-auto-install 'prompt)
  :config
  (setq treesit-auto-langs '(javascript typescript tsx css html))
  (treesit-auto-add-to-auto-mode-alist '(javascript typescript tsx css html))
  (global-treesit-auto-mode))

Lisps

Paredit

Paredit is pretty much mandatory for me these days when writing in a lisp dialect.

(use-package paredit
  :hook ((emacs-lisp-mode . paredit-mode)
         (lisp-mode . paredit-mode)
         (scheme-mode . paredit-mode)
         (clojure-mode . paredit-mode)))

Code Folding

By default, code folding is bound to C-<return>.

(use-package yafolding
  :hook ((js-mode . yafolding-mode)
         (js2-mode . yafolding-mode)
         (css-mode . yafolding-mode)
         (scss-mode . yafolding-mode)
         (typescript-mode . yafolding-mode)
         (typescript-ts-mode . yafolding-mode)
         (fundamental-mode . yafolding-mode)))

Yaml

(use-package yaml-mode :config (add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode)))

Utilities

Code un-minification

Having a tool like this at your fingertips (without having to switch to another ui/tool/website/whatever) is worth having this installed by default. It’s not bound to any key by default as I don’t use it that often though.

(use-package web-beautify)
Editor config

Most teams/tools use an editor config in project roots. So to avoid friction with setting indentation size, tabs/spaces, etc based on major-mode or otherwise trying to glean it from the project just look at the editor config.

(use-package editorconfig :config (editorconfig-mode 1))

Advanced Version Control with Magit

(use-package magit
  :config
  (global-set-key (kbd "C-x g") 'magit-status)
  (add-hook 'magit-status-sections-hook 'magit-insert-stashes))

;; Getting an alist-void error when running magit commands that refresh the buffer. Narrowed down to this variable so turning off for now
(setq magit-section-cache-visibility nil)

Open magit in full screen every time

(setq magit-display-buffer-function
      #'magit-display-buffer-fullframe-status-v1)
(setq magit-bury-buffer-function
      #'magit-restore-window-configuration)

Edit readonly file as sudo

Thank you mastering emacs!

(defun sudo ()
  "Use TRAMP to `sudo' the current buffer."
  (interactive)
  (when buffer-file-name
    (find-alternate-file
     (concat "/sudo:root@localhost:"
             buffer-file-name))))

File Path

Copy current File path. Lifted from (http://ergoemacs.org/emacs/emacs_copy_file_path.html)

(defun jrm/copy-file-path (&optional *dir-path-only-p)
  "Copy the current buffer's file path or dired path to `kill-ring'.
Result is full path."
  (interactive "P")
  (let ((-fpath
	   (if (equal major-mode 'dired-mode)
	       (expand-file-name default-directory)
	     (if (buffer-file-name)
		 (buffer-file-name)
	       (user-error "Current buffer is not associated with a file.")))))
    (kill-new
     (if *dir-path-only-p
	   (progn
	     (message "Directory path copied: 「%s" (file-name-directory -fpath))
	     (file-name-directory -fpath))
	 (progn (message "File path copied: 「%s" -fpath) -fpath )))))

Advanced Terminals

Ansi Terminal

Ansi term is a great built in terminal. By default force it to use bash.

(defvar my-term-shell "/bin/bash")
(defadvice ansi-term (before force-bash)
  (interactive (list my-term-shell)))
(ad-activate 'ansi-term)

Interactive Shells outside of shell buffer

Make shells interactive (i.e. M-!, or source blocks in org)

(setq shell-command-switch "-c")

Org Mode Source Blocks

When evaluating a source code block in org mode do not prompt for input, just run it.

(setq org-confirm-babel-evaluate nil)

For org source blocks, I prefer the pre-v9 syntax to expanding source blocks that feels similar to yasnippent. Also, split the window when editing a source block.

(require 'org-tempo)
(setq org-src-window-setup 'other-window)

(add-to-list
 'org-structure-template-alist
 '("r" . "src restclient"))
(add-to-list
 'org-structure-template-alist
 '("js" . "src js"))
(add-to-list
 'org-structure-template-alist
 '("ts" . "src typescript"))
(add-to-list
 'org-structure-template-alist
 '("el" . "src emacs-lisp"))
(add-to-list
 'org-structure-template-alist
 '("b" . "src bash"))
(add-to-list
 'org-structure-template-alist
 '("jv" . "src java"))
(add-to-list 'org-tempo-keywords-alist '("n" . "name"))

Additional Source Modes

Add some export modes for getting content out of org. Adding diminish to ob-clojure throws a Wrong type argument: stringp, :defer error.

(use-package ox-twbs)
(use-package ob-rust)
(use-package ob-restclient)
(require 'ob-clojure)
(use-package ob-typescript :diminish typescript-mode)

Allow asynchronous execution of org-babel src blocks so you can keep using emacs during long running scripts

(use-package ob-async)

Load some languages by default

(add-to-list 'org-src-lang-modes '("js" . "javascript")
	       '("php" . "php"))
(org-babel-do-load-languages
 'org-babel-load-languages
 '((python . t)
   (js . t)
   (lisp . t)
   (clojure . t)
   (typescript . t)
   (groovy . t)
   (rust . t)
   (sql . t)
   (shell . t)
   (java . t)))

I like org source blocks for typescript to use different compiler settings than what ships with ob-typescript. Not sure if there’s a better way to do this, but just overwriting the function from the source with the code below using the configuration I prefer.

 (defun org-babel-execute:typescript (body params)
   "Execute a block of Typescript code with org-babel. This function is called by `org-babel-execute-src-block'"
   (let* ((tmp-src-file (org-babel-temp-file "ts-src-" ".ts"))
	   (tmp-out-file (org-babel-temp-file "ts-src-" ".js"))
	   (cmdline (cdr (assoc :cmdline params)))
	   (cmdline (if cmdline (concat " " cmdline) ""))
	   (jsexec (if (assoc :wrap params) ""
		     (concat " ; node " (org-babel-process-file-name tmp-out-file)))))
     (with-temp-file tmp-src-file (insert body))
     (let ((results (org-babel-eval (format "tsc %s --lib 'ES7,DOM' -out %s %s %s"
					     cmdline
					     (org-babel-process-file-name tmp-out-file)
					     (org-babel-process-file-name tmp-src-file)
					     jsexec) ""))
	    (jstrans (with-temp-buffer
		       (insert-file-contents tmp-out-file)
		       (buffer-substring-no-properties (point-min) (point-max)))))
	(if (eq jsexec "") jstrans results))))

Latex

Use xelatex for more latex options like fontspec

(setq org-latex-compiler "xelatex")

Show any latex previews by default

(custom-set-variables '(org-startup-with-latex-preview t))

Project Management with Project.el

Killing project buffers

I like to keep the magit buffer open when closing all project files

(setq project-kill-buffer-conditions '(buffer-file-name
 (and
  (major-mode . fundamental-mode)
  "\\`[^ ]")
 (and
  (derived-mode . special-mode)
  (not
   (major-mode . help-mode))
  (not
   (major-mode . magit-status-mode))
  (not
   (derived-mode . gnus-mode)))
 (derived-mode . compilation-mode)
 (derived-mode . dired-mode)
 (derived-mode . diff-mode)
 (derived-mode . comint-mode)
 (derived-mode . eshell-mode)
 (derived-mode . change-log-mode)))

Load Additional Configs

Load additional configs based on file name patterns

(let ((emacs-dir (directory-file-name (file-name-parent-directory user-init-file))))
  (dolist (file (directory-files emacs-dir))
    (when (string-match "^init\\.[A-Za-z0-9_-]+\\.el$" file)
      (load (expand-file-name file emacs-dir)))))

Final Pieces

(provide 'emacs)