- Checkout https://github.com/joaotavora/eglot
- Checkout https://github.com/TommyX12/company-tabnine
- Checkout https://github.com/gregsexton/origami.el
- Checkout https://github.com/alphapapa/bufler.el
- Checkout https://www.reddit.com/r/emacs/comments/bb3nw7/connecting_workflows_with_aws_redshift/
- Checkout https://xenodium.com/a-chatgpt-emacs-shell/
- Spellchecking
There are two main approaches to writing your emacs configuration in org. I’m
choosing to run org-babel-load-file
manually after editing this file. It’s
a bit tedious, but preferable to imposing startup delays.
I’d ideally like to never do anything that invokes Customize options, but
this setup isn’t there yet. I want to be able to clone this repository to
~/.emacs.d
and run org-babel-load-file
to generate an init.el
in place
that is used to boot emacs from then on. At present, doing this generates
custom options that are written to ~/.emacs
. The presence of that file then
blocks emacs from using ~/.emacs.d/init.el
during startup.
Declaring a file, just for Customize options, works around this.
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(load custom-file)
I use straight and use-package to manage packages.
(setq load-prefer-newer t)
(setq straight-repository-branch "master")
(setq straight-check-for-modifications '(find-when-checking))
(setq straight-use-package-by-default t)
(setq straight-cache-autoloads t)
(setq use-package-inject-hooks t)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(straight-use-package 'use-package)
Run M-x straight-pull-all
to update all packages, and then run M-x
straight-freeze-versions
to save the currently checked out revisions of all
packages. The ~~/.emacs.d/straight/versions/default.el~ file, together with
this file, define the package configuration.
Run emacs as a server.
(server-start)
I put an executable script in ~/bin
to invoke emacs from a shell and have
the single instance receive calls to it. I use the d12frosted/emacs-plus and
emacsclient
packages.
#!/bin/sh
/opt/homebrew/bin/emacsclient -n $@
Reformatter runs command-line formatters to automatically format code in a buffer.
(straight-use-package 'reformatter)
I don’t want distractions like the startup screen, the toolbar or scrollbars.
I want the modeline to show line and column numbers. Don’t waste my time
asking me to type yes
and no
when y
or n
will suffice.
(setq inhibit-startup-screen t)
(setq initial-scratch-message nil)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(setq line-number-mode 1)
(setq column-number-mode 1)
(defalias 'yes-or-no-p 'y-or-n-p)
I don’t want a spurious Cmd-q to kill emacs on macOS.
(setq confirm-kill-emacs 'yes-or-no-p)
I never want to hear a single peep out of my editor.
(setq ring-bell-function #'ignore)
I’ve tried several themes, mostly within the base16 family.
(use-package base16-theme
:init
(load-theme 'base16-irblack t))
(defun get-base16-color (id)
(plist-get base16-irblack-theme-colors id))
I use set-face-attribute
for global faces instead of custom-set-faces
because I want to avoid using the Customize interface, which would mutate my
init file. It lets me twiddle any individual part of any face (see the full
list of attributes) without going through Customize. For package-specific
faces, use-package
offers the :custom-face
keyword, which goes through
Customize while avoiding its major downside.
(cond ((eq system-type 'gnu/linux)
(set-face-attribute 'default nil :family "Input"
:height 100))
;; ((eq system-type 'darwin)
;; (set-face-attribute 'default nil :family "Monaco"
;; :height 160)))
((eq system-type 'darwin)
(set-face-attribute 'default nil :family "Source Code Pro"
:height 180)))
(set-face-attribute 'fixed-pitch nil :family 'unspecified
:inherit 'default)
exec-path-from-shell ensures that Emacs.app on macOS uses the same paths as my shell environment.
(use-package exec-path-from-shell
:if (eq system-type 'darwin)
:custom
(exec-path-from-shell-check-startup-files nil)
(exec-path-from-shell-variables '("PATH"
"MANPATH"
"GOPATH"
"GOPRIVATE"
"OPENAI_API_KEY"))
:config
(exec-path-from-shell-initialize))
I want emacs to fit in naturally with the rest of my environment and give me access to functionality I don’t otherwise have, if the ergonomics are good. Saving clipboard contents to the kill ring provides access to data that is otherwise easily lost.
(setq save-interprogram-paste-before-kill t)
I never run more than one copy of emacs, nor do I ever make use of backup files. Don’t keep junk lying around if it’ll never be used.
(setq auto-save-default nil)
(setq auto-save-list-file-prefix nil)
(setq create-lockfiles nil)
(setq make-backup-files nil)
Uniquify buffer names using a style that matches file paths as much as possible.
(setq uniquify-buffer-name-style 'forward)
I prefer ibuffer
to the builtin buffer menu functionality.
(global-set-key (kbd "C-x C-b") 'ibuffer)
I like closing all buffers to reset emacs to a clean slate when I switch from
one task to another. Using C-x C-b % n <ENTER> D
is pretty tedious. M-x
close-all-buffers
is a touch easier.
(defun close-all-buffers ()
(interactive)
(mapc 'kill-buffer (buffer-list)))
Show the full path to the file in the current buffer in the window title.
(setq frame-title-format
(list '(buffer-file-name "%f" (dired-directory dired-directory "%b"))))
Scroll one line at a time. The default behaviour is jarring.
(setq scroll-conservatively 1)
Show line numbers to make it easier to pair with others.
(global-display-line-numbers-mode t)
(setq display-line-numbers-width 3)
I prefer spaces to tabs and use a single space after a period. I also want code and text to fit within 80 characters whenever reasonable.
(setq sentence-end-double-space nil)
(setq-default indent-tabs-mode nil)
(setq-default fill-column 80)
(setq default-tab-width 4)
(setq tab-width 4)
Ideally, formatting would be taken care of by tooling that limits length whenever reasonable. highlight-80+-mode highlights lines that exceed 80 characters. This provides a useful signal to think about whether the line should be broken up or not.
The builtin whitespace-mode can do this now. It’d be good to switchover to it and drop this unmaintained package.
(use-package highlight-80+
:straight (highlight-80+ :type git :host github :repo "jkakar/highlight-80-mode"))
(setq highlight-80+-columns 81)
(set-face-attribute 'highlight-80+ nil :foreground 'unspecified
:background (get-base16-color ':base01))
I don’t want to leave trailing whitespace in files. ws-butler only deletes tailing whitespace from edited lines, which helps keeps diffs clean.
(setq-default show-trailing-whitespace t)
(use-package ws-butler
:demand t
:config
(setq ws-butler-keep-whitespace-before-point nil)
(ws-butler-global-mode 1))
The builtin move-beginning-of-line
function jumps to the beginning of the
line, but most of the time I want to move to the first non-whitespace
character. crux-move-beginning-of-line
moves to the first non-whitespace
character on the line, or if the point is already there, to the beginning of
the line. Invoking it repeatedly toggles between these positions.
(use-package crux
:bind (("C-a" . crux-move-beginning-of-line)))
Using C-u M-^
to join one line to another is really tedious. Let’s make
this easier.
(defun join-next-line ()
(interactive)
(join-line t))
(global-set-key (kbd "C-j") 'join-next-line)
Highlight all the text between matching parentheses without any delay.
(set-face-attribute 'show-paren-match nil :foreground 'unspecified
:background (get-base16-color ':base01))
(setq show-paren-delay 0)
(setq show-paren-style (quote expression))
(show-paren-mode 1)
Ivy, counsel and swiper provide a simple and unified way to quickly navigate buffers, find files, etc.
(use-package swiper
:config
(global-set-key (kbd "C-s") 'swiper))
(use-package counsel
:config
(global-set-key (kbd "M-x") 'counsel-M-x)
(global-set-key (kbd "C-x C-f") 'counsel-find-file)
(global-set-key (kbd "C-c f") 'counsel-fzf)
(global-set-key (kbd "C-c k") 'counsel-rg)
(define-key minibuffer-local-map (kbd "C-r") 'counsel-minibuffer-history)
(setenv "FZF_DEFAULT_COMMAND" "git ls-files --exclude-standard --others --cached")
(setq counsel-git-cmd "rg --files")
(setq counsel-async-filter-update-time 100000)
(setq counsel-rg-base-command "rg -i -M 120 --no-heading --line-number --color never %s ."))
(use-package ivy
:init (setq ivy-use-virtual-buffers t
ivy-count-format "(%d/%d) ")
:bind (("C-c C-r" . ivy-resume)
:map ivy-minibuffer-map ("RET" . ivy-alt-done))
:config
(global-set-key (kbd "C-c C-r") 'ivy-resume)
(setq ivy-height 15)
(ivy-mode 1))
I want counsel-M-x
to show me the most recently used commands. Installing
smex makes this the default behaviour.
(use-package smex)
I mainly use projectile for fuzzy searching an entire project’s files and
buffers. It’s quite refreshing to never think about which files are open and
which ones aren’t. The concept of a root directory is also important for
things like rg
searching.
(use-package projectile
:custom
(projectile-globally-ignored-file-suffixes '(".pdf"))
(projectile-globally-unignored-files '(".projectile" ".dir-locals.el"))
:demand t
:config
(setq projectile-enable-caching t)
(setq projectile-indexing-method 'alien)
(projectile-mode 1))
I want to be able to jump to any file quickly without having to navigate through directories by hand. counsel-projectile provides a nice way to do this.
(use-package counsel-projectile
:demand t
:config
(counsel-projectile-mode 1))
I frequently want to share a GitHub link to code I’m working with in emacs. Navigating to files and selecting lines in the browser is rather tedious. git-link provides a way to quickly generate GitHub (and other code hosting service) URLs.
(use-package git-link
:config
(global-set-key (kbd "C-c g l") 'git-link))
dump-jump uses brute force very effectively. It provides decent jump to
definition behaviour while avoiding the tedium that comes with managing TAGS
files and such. I’ve found rg
provides the best results.
(use-package dumb-jump
:bind (("M-g o" . dumb-jump-go-other-window)
("M-g j" . dumb-jump-go)
("M-g i" . dumb-jump-go-prompt)
("M-g x" . dumb-jump-go-prefer-external)
("M-g z" . dumb-jump-go-prefer-external-other-window))
:config (setq dumb-jump-force-searcher 'rg)
(setq dumb-jump-max-find-time 10)
(setq dumb-jump-selector 'ivy))
wgrep integrates with ivy-occur to provide multi-line editing capabilities.
(use-package wgrep)
Search for text you want to edit, hit C-o C-o (ivy-occur) to open the matches in a buffer. Use C-x C-q (ivy-wgrep-change-to-wgrep-mode) in the buffer to switch into editing mode. Finally, use C-c C-c (wgrep-finish-edit) to apply the changes.
Enable ANSI colors in compilation buffers.
(defun colorize-compilation-buffer ()
(toggle-read-only)
(ansi-color-apply-on-region compilation-filter-start (point))
(toggle-read-only))
(add-hook 'compilation-filter-hook 'colorize-compilation-buffer)
(use-package lsp-mode
:commands lsp)
(use-package lsp-ui :commands lsp-ui-mode)
(use-package company-lsp :commands company-lsp)
(use-package lsp-ivy :straight (lsp-ivy :type git :host github :repo "emacs-lsp/lsp-ivy"))
(setq gc-cons-threshold 100000000)
(setq lsp-enable-doc t)
(setq lsp-enable-snippet nil)
(setq lsp-idle-delay 0.500)
(setq lsp-prefer-flymake :none)
(setq lsp-ui-doc-enable t)
(setq read-process-output-max (* 1024 1024))
(global-set-key (kbd "C-c h") 'lsp-ui-doc-glance)
Define C-c C-d
and C-c C-g
to show and hide LSP UI docs, respectively.
(define-key lsp-mode-map (kbd "C-c C-d") 'lsp-ui-doc-show)
(define-key lsp-mode-map (kbd "C-c C-g") 'lsp-ui-doc-hide)
The GPTel package provides commands for interacting with LLMs to respond to prompts, explain snippets, generate tests, and more.
(use-package gptel)
(global-set-key (kbd "C-c RET") 'gptel-send)
The Copilot package provides completions using GitHub Copilot. Use M-x
copilot-install-server
and M-x copilot-login
to get started.
;; (use-package copilot
;; :straight (:host github :repo "copilot-emacs/copilot.el" :files ("*.el"))
;; :ensure t)
;; (add-hook 'prog-mode-hook 'copilot-mode)
;; (define-key copilot-completion-map (kbd "<tab>") 'copilot-accept-completion)
;; (define-key copilot-completion-map (kbd "TAB") 'copilot-accept-completion)
;; (defun jkakar/no-copilot-mode ()
;; "Helper for `jkakar/no-copilot-modes'."
;; (copilot-mode -1))
;; (defvar jkakar/no-copilot-modes '(shell-mode
;; inferior-python-mode
;; eshell-mode
;; term-mode
;; vterm-mode
;; comint-mode
;; compilation-mode
;; debugger-mode
;; dired-mode-hook
;; compilation-mode-hook
;; flutter-mode-hook
;; minibuffer-mode-hook)
;; "Modes in which copilot is inconvenient.")
;; (defun jkakar/copilot-disable-predicate ()
;; "When copilot should not automatically show completions."
;; (or jkakar/copilot-manual-mode
;; (member major-mode jkakar/no-copilot-modes)
;; (company--active-p)))
;; (add-to-list 'copilot-disable-predicates #'jkakar/copilot-disable-predicate)
https://robert.kra.hn/posts/2023-02-22-copilot-emacs-setup/ has useful information about customized the copilot.el package.
(use-package dockerfile-mode
:config
(add-hook 'dockerfile-mode-hook 'highlight-80+-mode))
(use-package elixir-mode
:commands elixir-mode
:config
(add-hook 'elixir-mode-hook 'highlight-80+-mode))
(reformatter-define erlfmt
:program "/Users/jkakar/bin/erlfmt"
:args '("-"))
(use-package erlang
:init
(add-to-list 'auto-mode-alist '("\\.P\\'" . erlang-mode))
(add-to-list 'auto-mode-alist '("\\.E\\'" . erlang-mode))
(add-to-list 'auto-mode-alist '("\\.S\\'" . erlang-mode))
:config
(add-hook 'erlang-mode-hook 'highlight-80+-mode)
(add-hook 'erlang-mode-hook
(lambda ()
(setq mode-name "erl"
erlang-compile-extra-opts '((i . "../include"))
erlang-root-dir "/usr/local/lib/erlang"))
(add-hook 'erlang-mode-hook #'lsp))
;; TODO Figure out how to turn this off for *.yrl files.
(add-hook 'erlang-mode-hook 'erlfmt-on-save-mode))
;;(use-package edts
;; :init
;; (setq edts-inhibit-package-check t
;; edts-man-root "~/.emacs.d/edts/doc/18.2.1"))
(use-package flycheck
:init
;; (setq flycheck-ruby-executable (expand-file-name "~/.rbenv/shims/ruby"))
;; (setq flycheck-ruby-rubocop-executable (expand-file-name "~/.rbenv/shims/rubocop"))
(setq flycheck-erlang-include-path '("../include"))
(setq flycheck-erlang-library-path '())
(add-hook 'ruby-mode-hook (lambda () (flycheck-disable-checker 'ruby-reek)))
:config
(setq-default flycheck-disabled-checkers
(append flycheck-disabled-checkers
'(javascript-jshint)
'(json-jsonlist)))
(global-flycheck-mode))
(defun lsp-go-install-save-hooks ()
(add-hook 'before-save-hook #'lsp-format-buffer t t)
(add-hook 'before-save-hook #'lsp-organize-imports t t))
(use-package go-mode
:ensure t)
(use-package go-ts-mode
:config
(setq gofmt-command "goimports")
(setq-default tab-width 4)
(add-hook 'go-ts-mode-hook #'lsp-deferred)
:custom
(gofmt-show-errors nil)
:hook
(go-ts-mode . (lambda () (add-hook 'before-save-hook 'gofmt-before-save nil t)))
(go-ts-mode . (lambda ()
(setq tab-width 4)
(setq go-ts-mode-indent-offset tab-width)
(setq indent-tabs-mode t)))
:init
(add-hook 'go-ts-mode-hook (lambda () (setq tab-width 4)))
(add-hook 'go-ts-mode-hook 'highlight-80+-mode)
(add-hook 'go-ts-mode-hook #'lsp-deferred)
(add-hook 'go-ts-mode-hook #'lsp-go-install-save-hooks)
(add-hook 'go-ts-mode-local-vars-hook #'lsp!)
:defer t)
We install go-mode
because it defines the gofmt-before-save
hook. We
don’t want to add gofmt-before-save
to the global before-save-hook
,
because that would cause go-mode
to be loaded in every buffer, whether it
was a Go buffer or not. Instead we add to the local before-save-hook
. We
then have to explicitly request deferred loading. Normally :hook
implies
:defer t
, but only if the target of the hook is a function symbol. If it’s
a lambda, then use-package
will resort to its default behavior of demanding
the package, to ensure that the package is loaded when the lambda runs. In
our case, we know the lambda doesn’t need that, so we can safely ask for
deferral.
(use-package graphviz-dot-mode
:init
(add-hook 'graphviz-dot-mode-hook 'highlight-80+-mode)
(add-hook 'graphviz-dot-mode-hook (lambda () (setq tab-width 4))))
(defun biome-format-and-save ()
"Format the current file with Biome and refresh the buffer."
(interactive)
(let ((current-file (buffer-file-name)))
(when current-file
(let ((exit-code (call-process "biome" nil nil nil "check" "--apply" current-file)))
(if (eq exit-code 0)
(progn
(message "Formatting successful, reverting buffer.")
(revert-buffer t t t))
(message "Error formatting file with Biome."))))))
(defun setup-js-ts-modes ()
(add-hook 'after-save-hook 'biome-format-and-save nil t))
;; Apply this setup for js, ts, jsx, and tsx files
(add-hook 'js-ts-mode-hook 'setup-js-ts-modes)
(add-hook 'tsx-ts-mode-hook 'setup-js-ts-modes)
(add-hook 'typescript-ts-mode-hook 'setup-js-ts-modes)
(add-hook 'js-mode-hook 'setup-js-ts-modes) ;; Assuming js-mode for JavaScript
(add-hook 'js-ts-mode-hook (lambda () (setq js-indent-level 2)))
(add-hook 'js-ts-mode-hook #'lsp)
(add-hook 'tsx-ts-mode-hook (lambda () (setq js-indent-level 2)))
(add-hook 'tsx-ts-mode-hook #'lsp)
(add-hook 'typescript-ts-mode-hook (lambda () (setq js-indent-level 2)))
(add-hook 'typescript-ts-mode-hook #'lsp)
(add-hook 'tsx-ts-mode-hook 'auto-revert-mode)
(add-hook 'typescript-ts-mode-hook 'auto-revert-mode)
(add-hook 'js-ts-mode-hook 'auto-revert-mode)
(use-package treesit-auto
:custom
(treesit-auto-install 'prompt)
:config
(treesit-auto-add-to-auto-mode-alist 'all)
(global-treesit-auto-mode))
;; this fixes a problem where v0.20.4 of this grammar blows up with emacs
(defvar jkakar/tsx-treesit-auto-recipe
(make-treesit-auto-recipe
:lang 'tsx
:ts-mode 'tsx-ts-mode
:remap '(typescript-tsx-mode)
:requires 'typescript
:url "https://github.com/tree-sitter/tree-sitter-typescript"
:revision "v0.20.3"
:source-dir "tsx/src"
:ext "\\.tsx\\'")
"Recipe for libtree-sitter-tsx.dylib")
(add-to-list 'treesit-auto-recipe-list jkakar/tsx-treesit-auto-recipe)
(defvar jkakar/typescript-treesit-auto-recipe
(make-treesit-auto-recipe
:lang 'typescript
:ts-mode 'typescript-ts-mode
:remap 'typescript-mode
:requires 'tsx
:url "https://github.com/tree-sitter/tree-sitter-typescript"
:revision "v0.20.3"
:source-dir "typescript/src"
:ext "\\.ts\\'")
"Recipe for libtree-sitter-typescript.dylib")
(add-to-list 'treesit-auto-recipe-list jkakar/typescript-treesit-auto-recipe)
(use-package typescript-ts-mode
:config
(add-hook 'typescript-ts-mode-hook #'lsp)
:mode
(("\\.ts\\'" . typescript-ts-mode)
("\\.mts\\'" . typescript-ts-mode)
("\\.tsx\\'" . tsx-ts-mode)))
C-c C-l
is the keyboard shortcut for markdown-insert-link
which is needed
to edit URLs because markdown-hide-urls
is enabled.
(use-package markdown-mode
:custom
(markdown-hide-urls t)
:init
(add-hook 'markdown-mode-hook 'highlight-80+-mode))
(use-package protobuf-mode
:init
(defconst my-protobuf-style '((c-basic-offset . 2) (indent-tabs-mode . nil)))
(add-hook 'protobuf-mode-hook (lambda () (c-add-style "my-style" my-protobuf-style t)))
(add-hook 'protobuf-mode-hook 'highlight-80+-mode))
(use-package python-mode
:init
(add-hook 'python-mode-hook 'highlight-80+-mode))
(use-package ruby-mode
:init
(add-to-list 'auto-mode-alist '("\\.\\(?:cap\\|gemspec\\|irbrc\\|gemrc\\|rake\\|rb\\|rbi\\|ru\\|thor\\)\\'" . ruby-mode))
(add-hook 'ruby-mode-hook 'highlight-80+-mode)
:config
(setq ruby-insert-encoding-magic-comment nil))
;; (add-hook 'ruby-mode-hook #'lsp)
(use-package rust-mode
:custom
(rust-format-on-save t)
:defer t)
(use-package flycheck-rust
:hook (rust-mode . flycheck-rust-setup))
(use-package scala-mode
:interpreter ("scala" . scala-mode))
(use-package terraform-mode)
(use-package thrift)
(use-package tree-sitter-langs)
(use-package web-mode
:config
(setq web-mode-markup-indent-offset 2)
(setq web-mode-attr-indent-offset 2)
(setq web-mode-css-indent-offset 2)
(setq web-mode-code-indent-offset 2)
(setq web-mode-enable-auto-pairing t)
(setq web-mode-enable-css-colorization t)
(add-hook 'before-save-hook 'delete-trailing-whitespace nil 'local)
:mode ("\\.html?\\'" "\\.erb\\'" "\\.hbs\\'"
"\\.json\\'" "\\.s?css\\'" "\\.less\\'" "\\.sass\\'"))
(use-package yaml-mode
:config
(add-hook 'yaml-mode-hook 'highlight-80+-mode))
(use-package zig-mode
:config
(add-hook 'zig-mode-hook #'lsp))