Kris Molendyke
All configuration described here is performed in an =after-init-hook= function.
I primarily use Emacs locally on Apple hardware. Most, if not all,
of the remote editing that I do is done via tramp
. I keep an eye
on the log at the official Emacs git repository. When something
interesting lands there I will build a fresh Emacs installation via
Homebrew and try it out.
brew info emacs
I install Emacs with the following options. The standard Homebrew installation has lost a lot of MacOS customization flags over the years so I’ve switched to the Emacs Plus formula instead. First, tap that keg:
brew tap d12frosted/emacs-plus
And then install:
brew install emacs-plus@29
source-directory
is helpful to have set properly when exploring built-in
C functions via find-function
.
Finding the source code directory via Homebrew can be done with the following command:
echo $(brew --cache)/emacs*
/Library/Caches/Homebrew/emacs--git
Note: source-directory
must be set during Emacs initialization time to
persist. See =init.el=.
The =init.el= file bootstraps the entire process. It is found by Emacs automatically if it is located in one of several well-defined locations.
When Emacs is started, it normally tries to load a Lisp program from an “initialization file”, or “init file” for short. This file, if it exists, specifies how to initialize Emacs for you. Emacs looks for your init file using the filenames `~/.emacs’, `~/.emacs.el’, or `~/.emacs.d/init.el’; you can choose to use any one of these three names (*note Find Init::). Here, `~/’ stands for your home directory.
Once it’s found, init.el
sets “global” variables and defines
several functions to be executed during initialization and one
that should be executed after initialization has completed.
k20e/after-init-hook
is a hook run after initialization. The most
interesting bit of this function is the call to
org-babel-load-file
:
(org-babel-load-file FILE &optional COMPILE)
Load Emacs Lisp source code blocks in the Org-mode FILE. This function exports the source code using `org-babel-tangle’ and then loads the resulting file using `load-file’. With prefix arg (noninteractively: 2nd arg) COMPILE the tangled Emacs Lisp file to byte-code before it is loaded.
This function invocation exports all =org-mode= source blocks in any
org-mode
files in the .emacs.d
directory to a single Emacs Lisp
file and then instructs Emacs to load that file. =custom.org= is
the biggest of those org-mode
files and … this is that file!
This git post-commit
hook is located at .git/hooks/post-commit
and automatically generates the HTML content of this GitHub Pages
site.
#!/bin/bash
repo_root=$(git rev-parse --show-toplevel)
htmlize_dir=$(dirname "$(find "${repo_root}" -type f -name htmlize.el)")
emacs --batch \
--directory "${htmlize_dir}" \
--directory "$HOME/.emacs.d/site-lisp/org-mode/lisp" \
--load "$HOME/.emacs.d/elisp/k20e-org-html-export.el" \
--visit "$HOME/.emacs.d/custom.org" \
--execute '(org-html-export-to-html)' > /dev/null 2>&1
git checkout gh-pages
mv custom.html index.html
git add index.html
git commit --message "post-commit hook regeneration."
git checkout master
All of the interesting customization of the exported document is performed in =elisp/k20e-org-html-export.el=.
Before use-package
I had 70 packages installed and
emacs-init-time
was "8.678111 seconds"
.
(eval-when-compile (require 'use-package))
(use-package package
:config
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t))
Mucking with these solely based on output from lsp-doctor
and its
recommendations since I use this all day writing Go programs.
(eval-when-compile
(let ((mib (expt 2 20)))
(setq gc-cons-threshold (* 500 mib))))
(eval-when-compile
(let ((mib (expt 2 20)))
(setq read-process-output-max (* 1 mib))))
These global key bindings override built-in functions only. Package-specific or custom function defunition key bindings are made in their own dedicated sections where other specific settings are made.
OS X annoyance – C-M-d
is a “hot key” bound to dictionary lookup
and masks the key binding in Emacs. Disabling it can currently
only be done by editing a default and restarting.
defaults write com.apple.symbolichotkeys AppleSymbolicHotKeys \
-dict-add 70 '<dict><key>enabled</key><false/></dict>'
(global-unset-key (kbd "<f1> h"))
(global-unset-key (kbd "<f11>"))
(global-unset-key (kbd "C-h"))
(global-unset-key (kbd "C-q"))
(global-unset-key (kbd "M-`"))
(global-unset-key (kbd "M-c"))
(global-unset-key (kbd "M-h"))
(global-unset-key (kbd "M-u"))
(global-set-key (kbd "<f1> F") 'find-function)
(global-set-key (kbd "<f1> V") 'find-variable)
(global-set-key (kbd "<f6>") 'k20e/font-toggle-proportional)
(global-set-key (kbd "<f7>") 'previous-error) ;; ◀◀
(global-set-key (kbd "<f9>") 'next-error) ;; ▶▶
(global-set-key (kbd "C-M-;") 'comment-line)
(global-set-key (kbd "C-S-h") 'kill-whole-line)
(global-set-key (kbd "C-c DEL") 'join-line)
(global-set-key (kbd "C-h") 'delete-backward-char)
(global-set-key (kbd "C-j") 'join-line)
(global-set-key (kbd "C-x C-t") 'transpose-lines)
(global-set-key (kbd "H-h H-f") 'find-function)
(global-set-key (kbd "H-h H-v") 'find-variable)
(global-set-key (kbd "H-t") 'toggle-frame-fullscreen)
(global-set-key (kbd "M-+") 'text-scale-adjust)
(global-set-key (kbd "M-.") 'imenu)
(global-set-key (kbd "M-/") 'hippie-expand)
(global-set-key (kbd "M-`") 'other-window)
(global-set-key (kbd "M-h") 'backward-kill-word)
(global-set-key (kbd "M-t") 'transpose-words)
Sort by relevancy.
(setq-default apropos-sort-by-scores t)
Back up files to a single location.
(defvar k20e/backup-dir (expand-file-name "backup" user-emacs-directory)
"A single directory for storing backup files within.")
(unless (file-exists-p k20e/backup-dir) (make-directory k20e/backup-dir))
(setq backup-by-copying t
backup-directory-alist `(("." . ,k20e/backup-dir))
delete-old-versions t
version-control t)
Commands disabled by default prompt at first use. Enabling commands disables the prompt.
(defvar k20e/enabled-commands
'(downcase-region
upcase-region
narrow-to-region
narrow-to-page
scroll-left
scroll-right)
"Normally disabled commands.")
(defun k20e/enable-commands ()
"Enabled normally disabled commands."
(dolist (command k20e/enabled-commands)
(put command 'disabled nil)))
(k20e/enable-commands)
Defaulting to sh
seems to work well.
(setq shell-file-name "/bin/sh")
What a time to be alive.
(use-package emacs
:init
(pixel-scroll-precision-mode))
This is a bunch of stuff that I just dumped here and need to go through yet.
Show the active region and delete it when selected if a character is inserted.
(transient-mark-mode t)
(delete-selection-mode 1)
“Electric” indentation is generally what I consider to be sensible.
(electric-indent-mode)
Cycle through the mark ring faster.
(setq set-mark-command-repeat-pop t)
Splitting windows horizontally makes more sense on all of the wide screen monitors I work on.
(setq split-width-threshold 81)
;; What's going on here?
(setq echo-keystrokes 0.1)
;; Automatically reload buffers when files change on disk.
(global-auto-revert-mode 1)
;; y is the new yes. n is the new no.
(defalias 'yes-or-no-p 'y-or-n-p)
Stuff in review from https://www.masteringemacs.org/article/whats-new-in-emacs-28-1.
(setq-default
completions-detailed t
describe-bindings-outline t
mode-line-compact t
next-error-message-highlight t
frame-title-format '(multiple-frames "%b" ("%b\t%f")))
I have found these to be useful enough to keep around permanently.
(defun k20e/mark-current-line (arg)
"Mark the current line.
If the mark is already set simply move the point forward a single
line. If it is not set, set it at the beginning of the current
line and then move the point forward a single line."
(interactive "p")
(unless mark-active
(beginning-of-line)
(set-mark (point)))
(forward-line arg))
(defun k20e/open-line-below (arg)
"Insert a new line below the current line."
(interactive "p")
(end-of-line)
(newline arg)
(indent-for-tab-command))
(defun k20e/open-line-above (arg)
"Insert a new line above the current line."
(interactive "p")
(beginning-of-line)
(newline arg)
(forward-line (- 0 arg))
(indent-for-tab-command))
This one is stolen from magnars:
(defun k20e/eval-and-replace ()
"Replace the preceding sexp with its value."
(interactive)
(backward-kill-sexp)
(condition-case nil
(prin1 (eval (read (current-kill 0)))
(current-buffer))
(error (message "Invalid expression")
(insert (current-kill 0)))))
Bind editing functions:
(global-set-key (kbd "M-l") 'k20e/mark-current-line)
(global-set-key (kbd "<M-return>") 'k20e/open-line-below)
(global-set-key (kbd "<M-S-return>") 'k20e/open-line-above)
(defun k20e/display-buffer-file-name ()
"Message the full path to the currently visited file."
(interactive)
(message "%s" (buffer-file-name)))
If this gets any smarter it should be refactored into its own package.
(defun k20e/test-buffer-p ()
"Is the current buffer a test buffer?
This function naïvely assumes that the file name suffix '_test'
is indicative of a test file."
(string-suffix-p
"_test"
(file-name-sans-extension (buffer-file-name))))
(defun k20e/switch-to-test-buffer ()
"Switch to the test buffer associated with the current source buffer.
FIX: when >1 buffer w/ same name this is wrong because the buffer
name is prepended w/ dir name or whatever"
(let ((d (file-name-directory (buffer-file-name)))
(f (format "%s_test.%s"
(file-name-sans-extension (buffer-name))
(file-name-extension (buffer-file-name)))))
(find-file (expand-file-name f d))))
(defun k20e/switch-to-source-buffer ()
"Switch to the source buffer associated with the current test buffer."
(let ((e (file-name-extension (buffer-file-name)))
(f (car (split-string (file-name-sans-extension (buffer-file-name))
"_test"))))
(find-file (format "%s.%s" f e))))
(defun k20e/toggle-test-buffer ()
"Toggle between a source and test buffer.
This function naïvely assumes that the file name suffix '_test'
is indicative of a test file. Therefore it should only be useful
in major modes where that convention is expected."
(interactive)
(if (k20e/test-buffer-p)
(k20e/switch-to-source-buffer)
(k20e/switch-to-test-buffer)))
When working on a widescreen monitor it can be useful to have
windows arranged a bit differently than they would on smaller
monitors. In particular, a function like fit-window-to-buffer
which adjusts the window’s width is helpful.
(defun k20e/get-longest-line-length ()
"Get the length of the longest line in the selected window."
(save-excursion
(goto-char (point-min))
(let ((max-length 0)
(last-line (count-lines (point-min) (point-max))))
(while (<= (line-number-at-pos) last-line)
(setq max-length (max max-length (- (line-end-position) (line-beginning-position))))
(forward-line))
(1+ max-length))))
(defun k20e/fit-window-to-buffer-horizontally ()
"Fit the selected window to the width of its longest line.
Return the window width delta."
(interactive)
(let* ((current-width (window-width))
(longest-line (k20e/get-longest-line-length))
(delta (* -1 (- current-width longest-line))))
(if (zerop (window-resizable (selected-window) delta t)) nil
(window-resize (selected-window) delta t))
delta))
(global-set-key (kbd "C-x w") 'k20e/fit-window-to-buffer-horizontally)
(use-package windmove :ensure t)
(use-package emacs
:init
(defun k20e/delete-window-balance ()
"Balance windows after deleting."
(interactive)
(delete-window)
(balance-windows-area))
(defun k20e/split-window-right-switch-buffer (&optional arg)
"Split the window to the right, balance, and switch to buffer.
Optional argument ARG Prefix argument will prompt for buffer
name."
(interactive "P")
(split-window-right)
(balance-windows-area)
(windmove-right)
(if arg
(switch-to-buffer (read-buffer "Buffer to display to right: "))
(switch-to-buffer nil)))
:bind (("C-x 3" . k20e/split-window-right-switch-buffer)
("C-x 0" . k20e/delete-window-balance)))
(use-package net-utils :ensure t)
(use-package tramp
:ensure t
:commands tramp-parse-shosts)
(defun k20e/known-hosts ()
"Get a host name from ~./ssh/known_hosts file."
(completing-read "host: "
(let ((value))
(dolist (elt (tramp-parse-shosts "~/.ssh/known_hosts") value)
(if elt (setq value (cons (cadr elt) value)))))))
(defun k20e/host-ip ()
"Insert the current IP of a host using `dns-lookup-program'.
Similar to but simpler than `dns-lookup-host'."
(interactive)
(let ((host (k20e/known-hosts)))
(insert (car (last (split-string (shell-command-to-string
(concat dns-lookup-program " " host))))))))
(require 'calendar)
(require 'lunar)
(defun k20e/full-moons-info ()
"Get a list of upcoming full moons info beginning with the current month.
See `lunar-phase-list' and `lunar-phase-name'."
(let* ((current-date (calendar-current-date))
(current-month (car current-date))
(current-year (car (last current-date)))
(full-moon-phase-index 2)
(k20e/full-moons-info '()))
(dolist (phase (lunar-phase-list current-month current-year))
(if (= (car (last phase)) full-moon-phase-index)
(setq k20e/full-moons-info (cons phase k20e/full-moons-info))))
(reverse k20e/full-moons-info)))
(defun k20e/full-moons ()
"Display upcoming full moons beginning with the current month."
(interactive)
(with-output-to-temp-buffer "*full-moons*"
(princ
(mapconcat
#'(lambda (x)
(format "%s %s" (calendar-date-string (car x)) (car (cdr x))))
(k20e/full-moons-info)
"\n"))))
When to turn on auto-fill and set fill-column to a reasonable value. This would probably be better dealt with by a data structure that maps mode hooks to fill-column values.
(use-package emacs
:config
(setq-default fill-column 118))
- https://github.com/rranelli/auto-package-update.el
- https://github.com/jwiegley/use-package#package-installation
(use-package auto-package-update
:ensure t
:config
(setq-default auto-package-update-delete-old-versions t
auto-package-update-interval 7
auto-package-update-prompt-before-update t
auto-package-update-show-preview t))
Disable auto-save
.
(use-package emacs
:config
(setq auto-save-default nil
auto-save-timeout 0))
(use-package avy
:ensure t
:bind (("s-g" . avy-goto-char-timer))
:config
(setq-default
avy-keys '(?a ?o ?e ?u ?i ?d ?h ?t ?n)
avy-all-windows nil
avy-style 'at-full))
Move the current buffer up/down/left/right easily.
(use-package buffer-move
:ensure t
:bind (("M-S-<up>" . buf-move-up)
("M-S-<down>" . buf-move-down)
("M-S-<left>" . buf-move-left)
("M-S-<right>" . buf-move-right)))
(use-package cc-mode
:after (flycheck)
:custom
(flycheck-gcc-pedantic-errors t)
:hook ((c-mode . flycheck-mode)))
This is a ridiculous package that I wrote to insert a Unicode clock face character for the nearest current half-hour. 🕙
(use-package clock-face
:ensure nil
:load-path "site-lisp/clock-face.el")
Along with tree-sitter this looks interesting.
- https://www.masteringemacs.org/article/combobulate-structured-movement-editing-treesitter
- https://github.com/mickeynp/combobulate#how-do-i-install-combobulate
(use-package compile
:custom-face (compilation-error ((t (:foreground "tomato1")))))
Functions to execute after compilation has finished:
(require 'hl-line)
(require 'subr-x)
(defun k20e/compilation-finish-function-delay-delete (buf result)
"Delete and bury BUF after short delay.
Do so only if compilation is successful."
(if (string= (string-trim result) "finished")
(run-with-timer
1.0 nil
(lambda (buf)
(with-current-buffer buf
(delete-window)
(bury-buffer)))
buf)))
(defun k20e/compilation-finish-function-select-window (buf result)
"Switch to the compilation buffer BUF.
When compilation completes, regardless of result."
(let ((win (get-buffer-window buf)))
(select-window (get-buffer-window buf))
(goto-char (point-max))
(forward-line -1)
(hl-line-mode)))
(use-package dired
:custom-face (dired-flagged ((t (:foreground "tomato1" :strike-through t))))
:config
(use-package autorevert
:config
(setq auto-revert-verbose nil))
(use-package dired-x
:config
(load "dired-x.el"))
:hook ((dired-mode . auto-revert-mode)))
(use-package elec-pair
:hook ((prog-mode . electric-pair-mode)))
(use-package elisp-mode
:config
(defun k20e/ert ()
"Run all the tests in the universe!"
(interactive)
(ert t))
:bind (:map emacs-lisp-mode-map ("H-t" . k20e/ert))
:hook ((emacs-lisp-mode . eldoc-mode)))
(use-package expand-region
:ensure t
:bind (("C-M-SPC" . er/expand-region)))
(use-package find-file-in-project
:ensure t
:bind (("C-x o" . find-file-in-project))
:config
(setq-default ffip-limit 8192
ffip-find-options "-type f -not -path '*/node_modules/*' -and -not -path '*/.git/*' -and -not -path '*/dist/*' -and -not -path '*/dist-types/*' -and -not -path '*/.yarn/*'"
ffip-full-paths t
ffip-patterns (list "*.el"
"*.html"
"*.js"
"*.json"
"*.go"
"*.md"
"*.org"
"*.py"
"*.sh"
"*.ts"
"*.txt"
"*.yaml"
"*.yml"
"Dockerfile"
"Makefile")
ffip-prune-patterns (list ".git" "build" "node_modules")))
(use-package flycheck
:ensure t
:config
(setq-default flycheck-pylintrc "pylintrc"
flycheck-check-syntax-automatically '(mode-enabled save)))
Setup ispell
to use =aspell=, then setup flyspell
itself. It requires ispell
.
(use-package flyspell
:ensure t
:init
(setq-default ispell-program-name "aspell"
ispell-extra-args (list "--sug-mode=ultra"))
:config
(setq flyspell-issue-message-flag nil
flyspell-issue-welcome-flag nil)
:hook ((text-mode . flyspell-mode)))
Install =aspell=
Install aspell
via Homebrew:
brew install aspell --with-lang-en
(global-prettify-symbols-mode 1)
(defvar k20e/font-list '(("PragmataPro-Mono-Liga" . 20)
("PragmataPro-Liga" . 20))
"Ordered list of preferred fonts and sizes.")
(defun k20e/font--set (font-alist)
"Set the font family and size to the given font alist of the
format (family . point)."
(let ((font (replace-regexp-in-string "-" " " (car font-alist)))
(height (* 10 (cdr font-alist))))
(set-frame-font font)
(set-face-attribute 'default nil :height height)))
(defun k20e/font-set-from-list (l)
"Set the font to first available font alist in the given list."
(if (null l) nil
(k20e/font--set (car l))
(if (string= (replace-regexp-in-string "-" " "(caar l))
(face-attribute 'default :family (selected-frame)))
(caar l)
(k20e/font-set-from-list (cdr l)))))
(defun k20e/font-set ()
"Set a font from the `k20e/font-list'."
(interactive)
(let ((ignore-case completion-ignore-case))
(unwind-protect
(progn
(setq completion-ignore-case t)
(let ((font (completing-read "Font: " k20e/font-list)))
(k20e/font--set (assoc font k20e/font-list))))
(setq completion-ignore-case ignore-case))))
(k20e/font-set-from-list k20e/font-list)
Symbola is a nice font for displaying Unicode characters 🍺👍.
(when (member "Symbola" (font-family-list))
(set-fontset-font t 'unicode "Noto Color Emoji" nil 'prepend))
This is a naïve package that I wrote to help insert Font Awesome icons into buffers.
(use-package font-awesome
:ensure nil
:load-path "site-lisp/font-awesome.el")
(use-package geiser
:ensure t
:config
(setq-default geiser-active-implementations '(racket chicken)
geiser-default-implementation 'racket))
A simple function to insert starter .gitignore
file contents from
the github/gitignore repository.
(require 'url)
(defun k20e/gh--gitignore-url (language)
"Get GitHub .gitignore URL for LANGUAGE."
(format "https://raw.githubusercontent.com/github/gitignore/master/%s.gitignore"
(capitalize language)))
(defun k20e/gh--gitignore-get-region (response-buffer)
"Get GitHub .gitignore response body bounds.
Argument RESPONSE-BUFFER HTTP GET response."
(with-current-buffer response-buffer
(goto-char (point-min))
(let ((start (1+ (search-forward-regexp "^$")))
(end (point-max)))
(list start end))))
(defun k20e/gh-gitignore-insert (language)
"Insert Github .gitignore for LANGUAGE."
(interactive "sLanguage: ")
(let* ((response-buffer (url-retrieve-synchronously
(k20e/gh--gitignore-url language) t))
(gitignore-region (k20e/gh--gitignore-get-region response-buffer)))
(insert-buffer-substring-no-properties
response-buffer (car gitignore-region) (cadr gitignore-region))))
Install commands:
go install golang.org/x/tools/gopls@latest
(use-package go-mode
:ensure t
:bind
(:map go-mode-map
("C-c C-t" . 'k20e/toggle-test-buffer)
("M-q" . 'lsp-format-buffer))
:custom-face (button ((t (:box nil))))
:hook ((before-save . (lambda ()
(when (eq major-mode 'go-mode)
(lsp-format-buffer)
(lsp-organize-imports))))
(go-mode . lsp-deferred)
(go-mode . (lambda ()
(let ((gopath (string-trim (shell-command-to-string (string-join `(,(executable-find "go") "env" "GOPATH") " "))))
(goroot (string-trim (shell-command-to-string (string-join `(,(executable-find "go") "env" "GOROOT") " ")))))
(setenv "GOPATH" gopath)
(setenv "GOROOT" goroot))
(setq-local
compile-command "go run main.go"
indent-tabs-mode t
tab-width 4))))
:config
;; https://github.com/golang/tools/blob/master/gopls/doc/settings.md#officially-supported
(setq-default lsp-go-directory-filters ["-**/testdata"]
lsp-go-link-target "pkg.go.dev"
lsp-go-use-placeholders t)
;; https://github.com/golang/tools/blob/master/gopls/doc/emacs.md#configuring-lsp-mode
;; https://github.com/golang/tools/blob/master/gopls/doc/settings.md
(lsp-register-custom-settings '(("gopls.staticcheck" t t)
("go.inlayHints.assignVariableTypes" t t)
("go.inlayHints.compositeLiteralFields" t t)
("go.inlayHints.compositeLiteralTypes" t t )
("go.inlayHints.constantValues" t t)
("go.inlayHints.functionTypeParameters" t t)
("go.inlayHints.parameterNames" t t)
("go.inlayHints.rangeVariableTypes" t t))))
Tree sitter for Go is installed with defaults.
- Language:
gomod
- Repository: https://github.com/camdencheek/tree-sitter-go-mod
- Language:
templ
- Repository: https://github.com/vrischmann/tree-sitter-templ
- [ ] Steal some of these https://github.com/dominikh/yasnippet-go
- [ ] https://github.com/nlamirault/gotest.el
- [ ] https://github.com/alecthomas/gometalinter
(use-package highlight-indent-guides
:ensure t
:config
(setq highlight-indent-guides-method 'character
highlight-indent-guides-responsive 'stack))
(use-package highlight-parentheses
:ensure t
:hook ((emacs-lisp-mode . highlight-parentheses-mode)
(lisp-mode-hook . highlight-parentheses-mode)))
(use-package htmlize
:ensure t)
(require 'face-remap)
(defalias 'list-buffers 'ibuffer)
(use-package ibuffer
:custom
(ibuffer-formats '((mark " "
(modified)
" "
(name 40 40 :right :elide)
" "
(filename-and-process -1 -1 :left :elide))
(mark " "
(filename-and-process 70 70 :left :elide)
" "
name)
(mark " "
(mode 20 20 :left :elide)
" "
(modified 6 6 :left)
" "
(name 40 40 :left :elide)
" "
(filename-and-process -1 -1 :left :elide)))))
(use-package ielm
:hook ((ielm-mode . eldoc-mode)
(ielm-mode . paredit-mode)))
Re-scan the buffer for new menu items automatically.
(use-package imenu
:config
(setq-default imenu-auto-rescan t))
This is my Google search module.
(use-package im-feeling-lucky
:ensure nil
:load-path "site-lisp/im-feeling-lucky.el"
:bind (("H-l" . ifl-region-or-query)))
(use-package js
:mode ("\\.json\\'" . js-mode)
:hook ((js-mode . flycheck-mode)))
(use-package tex-mode
:ensure t
:bind (:map latex-mode-map ("C-j" . join-line)))
(use-package ligature-pragmatapro
:ensure t
:config
(ligature-pragmatapro-setup)
(global-ligature-mode t))
Avoid creating temporary symbolic links and disturbing working directory state at the expense of avoiding editing collisions that I do not ever anticipate.
(setq create-lockfiles nil)
(use-package lsp-mode
:ensure t
:config
(setq-default
lsp-eldoc-render-all nil
lsp-enable-snippet nil
lsp-signature-doc-lines 20 ; limit lines shown in docs
lsp-enable-links nil ; disable clickable links in files
)
(set-face-attribute 'lsp-face-highlight-textual nil :underline t))
(use-package lua-mode
:ensure t)
Setting a width avoids a possibly (likely) poorly chosen automatic width.
(setq-default Man-width 80)
(use-package markdown-mode
:ensure t
:config (setq markdown-open-command "open")
:hook ((markdown-mode . prettify-symbols-mode)))
Scale up the minibuffer text size and limit how tall it can get.
(defun k20e/minibuffer-setup-hook ()
"Bump up minibuffer text size and height."
(text-scale-set 3)
(setq max-mini-window-height 20))
(add-hook 'minibuffer-setup-hook 'k20e/minibuffer-setup-hook)
Set enable-recursive-minibufers
to t
to allow minibuffers
within minibuffers. A good use-case of this feature is described
in Executing Shell Commands in Emacs.
(setq enable-recursive-minibuffers t)
Enable eldoc
in the modeline.
(require 'eldoc)
(defun k20e/eval-expression-minibuffer-setup-hook ()
(eldoc-mode 1))
(add-hook 'eval-expression-minibuffer-setup-hook
'k20e/eval-expression-minibuffer-setup-hook)
(use-package multiple-cursors
:ensure t
:bind (("M-L" . mc/edit-lines)
("C-M-." . k20e/mark-next)
("C-M-," . k20e/mark-previous)
("C-M-<return>" . mc/mark-all-like-this))
:config
(defun k20e/mark-next (extended)
"Wrap multiple-cursors mark-more/next.
Call `mc/mark-next-like-this' without a prefix argument.
Argument EXTENDED Prefix argument to call function
`mc/mark-more-like-this-extended'."
(interactive "P")
(if extended
(call-interactively 'mc/mark-more-like-this-extended)
(call-interactively 'mc/mark-next-like-this)))
(defun k20e/mark-previous (extended)
"Wrap multiple-cursors mark-more/previous.
Call `mc/mark-previous-like-this' without a prefix argument.
Argument EXTENDED Prefix argument to call function
`mc/mark-more-like-this-extended'."
(interactive "P")
(if extended
(call-interactively 'mc/mark-more-like-this-extended)
(call-interactively 'mc/mark-previous-like-this))))
Keep preferences sync’d across machines.
(setq mc/list-file (expand-file-name ".mc-lists.el" k20e/google-drive-directory))
Install opam
, then opam init
(see https://github.com/krismolendyke/dotfiles). Then:
opam install ocamlformat ocaml-lsp-server
- tuareg is a popular OCaml major mode
- ocamlformat is the recommended formatter
ocp-indent
is provided byocamlformat
(see~/.opam/default/share/emacs/site-lisp
) for more, includingdune
(use-package tuareg
:ensure t
:after (flycheck lsp-mode)
:bind (:map tuareg-mode-map
("C-h" . 'delete-backward-char))
:hook ((tuareg-mode . lsp-deferred)))
(use-package ocamlformat
:ensure t)
(use-package ocp-indent
:ensure t)
olivetti is a minimal minor mode to provide a more distraction free writing environment.
(use-package olivetti
:ensure t
:config (setq olivetti-style 'fancy))
(defun k20e/insert-mit-license ()
"Insert MIT license file contents.
Populate the current year and user name."
(interactive)
(with-current-buffer (get-buffer-create "LICENSE.txt")
(insert (format "The MIT License (MIT)
Copyright (c) %s %s
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the \"Software\"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
" (format-time-string "%Y") (user-full-name)))))
(use-package org
:custom-face
(variable-pitch ((t (:family "ETBembo" :height 1.3))))
(fixed-pitch ((t (:family "PragmataPro Mono Liga" :height 0.8))))
(org-block ((t (:inherit fixed-pitch))))
(org-block-begin-line ((t (:inherit fixed-pitch))))
(org-block-end-line ((t (:inherit fixed-pitch))))
(org-code ((t (:inherit (shadow fixed-pitch)))))
(org-date ((t (:inherit (shadow fixed-pitch)))))
(org-document-info-keyword ((t (:inherit (shadow fixed-pitch)))))
(org-done ((t (:inherit (shadow fixed-pitch)))))
(org-drawer ((t (:inherit (shadow fixed-pitch)))))
(org-ellipsis ((t (:inherit (shadow fixed-pitch))))) ; "CANCELED" for some reason is this
(org-indent ((t (:inherit (org-hide fixed-pitch)))))
(org-meta-line ((t (:inherit (font-lock-comment-face fixed-pitch)))))
(org-property-value ((t (:inherit fixed-pitch))))
(org-quote ((t (:inherit (shadow)))))
(org-special-keyword ((t (:inherit (font-lock-comment-face fixed-pitch)))))
(org-table ((t (:inherit fixed-pitch))))
(org-tag ((t (:inherit (shadow fixed-pitch)))))
(org-todo ((t (:inherit (shadow fixed-pitch)))))
(org-verbatim ((t (:inherit (shadow fixed-pitch)))))
:hook
((org-mode . auto-fill-mode)
(org-mode . prettify-symbols-mode)
(org-mode . variable-pitch-mode)
(org-mode . (lambda () (visual-line-mode 0)) ; TODO how to do this w/ :hook?
))
:config
(setq org-default-notes-file (expand-file-name "notes.org" org-directory)
org-directory (expand-file-name "org" k20e/google-drive-directory)
org-ellipsis "…"
org-fontify-quote-and-verse-blocks t
org-fontify-whole-heading-line t
org-hide-emphasis-markers t
org-log-redeadline 'time
org-log-reschedule 'time
org-outline-path-complete-in-steps nil
org-pretty-entities t
org-special-ctrl-a/e t
org-use-speed-commands t
truncate-lines nil)
(setq-default org-adapt-indentation t
org-goto-interface 'outline-path-completion
org-startup-folded t)
(setq-local global-hl-line-mode nil)
:functions
company-indent-or-complete-common
company-select-next
company-select-previous
lsp-format-buffer
lsp-organize-imports
lsp-register-custom-settings)
cd ~/.emacs.d/site-lisp/org-mode
g fetch origin --tags
g co -b release_9.6.12 release_9.6.12
make clean
make
cd ~/.emacs.d
g add site-lisp/org-mode
g commit -m "UPGRAYEDD org-mode"
=contrib/= was split out into a separate repo so that needs to be updated separately.
Try to get the width of images displayed inline from a #+ATTR.*
keyword, e.g., #+ATTR_HTML: :width 800px
, fall back to original
image width if no attribute keyword is found:
(setq org-image-actual-width nil)
(global-set-key (kbd "<f12>") 'org-agenda-list)
(global-set-key (kbd "C-c a") 'org-agenda)
(global-set-key (kbd "C-c l") 'org-store-link)
(global-set-key (kbd "C-x c") 'org-switchb)
(define-key org-mode-map (kbd "<return>") 'org-return-indent)
(define-key org-mode-map (kbd "M-<return>") 'org-meta-return)
(define-key org-mode-map (kbd "C-j") 'join-line)
(define-key org-mode-map (kbd "C-m") 'org-return-indent)
(define-key org-mode-map (kbd "H-<tab>") 'pcomplete)
(define-key org-mode-map (kbd "M-h") 'backward-kill-word)
Most non-interactive export settings are defined in a file loaded
during initialization. Those settings are defined during
initialization time to support a fast batch process for exporting
this document to HTML in a Git post-commit
hook.
(require 'k20e-org-html-export)
Interactive customization can be done here.
(require 'ox-publish)
(setq
;; Enable "expert" export interface.
org-export-dispatch-use-expert-ui t
;; Continue export when links are broken, but mark them
org-export-with-broken-links `mark)
A message in *Messages*
like:
user-error: Unable to resolve link: nil
indicates that a link somewhere is malformed. Adding the option:
#+OPTIONS: broken-links:mark
and exporting will insert BROKEN LINK
into the HTML document. Searching for that token makes finding the
offending broken link much easier. Keeping this option set all the time would let broken links slip through the
export process undetected.
(require 'ox-md)
(add-to-list 'org-export-backends 'md)
(use-package ox-tufte :ensure t)
(setq org-publish-project-alist
`(("k20e.com-org-files"
:base-directory ,(expand-file-name "source" (expand-file-name "k20e.com" k20e/google-drive-directory))
:base-extension "org"
:recursive t
:exclude "ga.org\\|level-0.org\\|todo.org\\|.DS_Store"
:publishing-directory ,(expand-file-name "published" (expand-file-name "k20e.com" k20e/google-drive-directory))
:publishing-function org-html-publish-to-html
:with-planning t)
("k20e.com-static-files"
:base-directory ,(expand-file-name "source" (expand-file-name "k20e.com" k20e/google-drive-directory))
:base-extension "jpg\\|png\\|ico"
:recursive t
:publishing-directory ,(expand-file-name "published" (expand-file-name "k20e.com" k20e/google-drive-directory))
:publishing-function org-publish-attachment)
("k20e.com"
:components ("k20e.com-org-files" "k20e.com-static-files"))
("work-org-files"
:base-directory ,(expand-file-name "work" org-directory)
:base-extension "org"
:publishing-directory ,(expand-file-name "published" (expand-file-name "work" org-directory))
:publishing-function org-html-publish-to-html
:with-planning t)
("work-static-files"
:base-directory ,(expand-file-name "work" org-directory)
:base-extension "pdf\\|csv\\|sql\\|png"
:publishing-directory ,(expand-file-name "published" (expand-file-name "work" org-directory))
:publishing-function org-publish-attachment)
("work"
:components ("work-org-files" "work-static-files"))
("house-org-files"
:base-directory ,(expand-file-name "house" org-directory)
:base-extension "org"
:recursive t
:publishing-directory ,(expand-file-name "published" (expand-file-name "house" org-directory))
:publishing-function org-html-publish-to-html
:with-planning t)
("house-static-files"
:base-directory ,(expand-file-name "house" org-directory)
:base-extension "pdf\\|csv\\|png\\|xls\\|doc"
:recursive t
:publishing-directory ,(expand-file-name "published" (expand-file-name "house" org-directory))
:publishing-function org-publish-attachment)
("house"
:components ("house-org-files" "house-static-files"))))
Define which languages org-babel
should support.
(defvar k20e/org-babel-load-languages
'((ditaa . t)
(emacs-lisp . t)
(js . t)
(org . t)
(python . t)
(scheme . t)
(shell . t)
(sql . t))
"Languages to evaluate in `org-mode'.")
(org-babel-do-load-languages 'org-babel-load-languages
k20e/org-babel-load-languages)
Disable interactive prompt for executing code blocks. This is dangerous but I never execute any org files that I didn’t author.
(setq org-confirm-babel-evaluate nil)
Automatically insert a timestamp when a task is marked DONE
.
(setq org-log-done t)
Custom keywords and faces.
(setq org-todo-keywords '((sequence
"TODO(t)"
"STARTED(s!)"
"|"
"DONE(d!)"
"CANCELED(c@)"))
org-todo-keyword-faces '(("TODO" . org-todo)
("STARTED" . org-code)
("CANCELED" . org-ellipsis)
("DONE" . org-done)))
(require 'face-remap)
(require 'org)
(require 'org-agenda)
(require 'winner)
(defun k20e/org-agenda-mode-hook ()
(define-key org-agenda-mode-map (kbd "q")
(lambda (x)
(interactive "p")
(winner-undo)
(kill-buffer "*Org Agenda*")))
(delete-other-windows)
(text-scale-set 2))
(add-hook 'org-agenda-mode-hook 'k20e/org-agenda-mode-hook)
(setq org-agenda-files (list (expand-file-name "work" org-directory)))
Non-nil means skip scheduling line if same entry shows because of deadline.
In the agenda of today, an entry can show up multiple times because it is both scheduled and has a nearby deadline, and maybe a plain time stamp as well.
When set to t, then only the deadline is shown and the fact that the entry is scheduled today or was scheduled previously is not shown.
(setq org-agenda-skip-scheduled-if-deadline-is-shown nil)
Default to showing only today in the agenda list.
(setq org-agenda-span 'day)
(require 'org-habit)
(setq org-habit-completed-glyph ?✓
org-habit-today-glyph ?|)
Insert state change notes and time stamps into a drawer rather than simply “loose” after a headline.
(setq org-log-into-drawer t)
(defvar org-clock-idle-time 5)
Successor to org-bullets
, org-superstar-mode.
(use-package org-superstar
:ensure t
:custom
(setq org-superstar-special-todo-items t
org-superstar-todo-bullet-alist '(("TODO" . 9744) ; ☐
("STARTED" . 9744) ; ☐
("DONE" . 9745) ; ☑
("CANCELED" . 10008))) ; ✘
:hook ((org-mode . org-superstar-mode)))
(use-package paredit
:ensure t
:bind (:map paredit-mode-map
([?\)] . paredit-close-parenthesis)
([(meta ?\))] . paredit-close-parenthesis-and-newline)
("C-h" . paredit-backward-delete)
("C-j" . join-line))
:hook ((emacs-lisp-mode . paredit-mode)
(geiser-mode . paredit-mode)
(geiser-repl-mode . paredit-mode)
(lisp-mode . paredit-mode)
(scheme-mode . paredit-mode)))
Specific configuration for PragmataPro from https://github.com/fabrizioschiavi/pragmatapro/tree/master/emacs_snippets.
(use-package prog-mode
:config
(load (expand-file-name "pragmatapro-prettify-symbols-v0.830.el" k20e/elisp-directory))
:hook ((prog-mode . prettify-hook)))
- https://blog.jft.rocks/emacs/unicode-for-orgmode-checkboxes.html
- http://www.modernemacs.com/post/prettify-mode/
- https://www.emacswiki.org/emacs/DontReadItsName
- http://endlessparentheses.com/using-prettify-symbols-in-clojure-and-elisp-without-breaking-indentation.html
- https://pixelambacht.nl/2015/sans-bullshit-sans/
- https://github.com/ekaschalk/.spacemacs.d/blob/master/layers/display/local/pretty-fonts/pretty-fonts.el
- https://unix.stackexchange.com/questions/247108/how-to-find-out-which-unicode-codepoints-are-defined-in-a-ttf-file
(use-package python
:ensure t
:after (flycheck lsp-mode)
:bind (:map python-mode-map
("M-a" . python-nav-beginning-of-statement)
("M-e" . python-nav-end-of-statement)
("M-n" . python-nav-forward-statement)
("M-p" . python-nav-backward-statement)
("C-." . completion-at-point)
("C-M-f" . python-nav-forward-sexp)
("C-M-b" . python-nav-backward-sexp)
("C-M-n" . python-nav-forward-block)
("C-M-p" . python-nav-backward-block))
:hook ((python-mode . electric-pair-mode)
(python-mode . flycheck-mode)
(python-mode . lsp-deferred)
(python-mode . prettify-symbols-mode)
(python-mode . superword-mode)))
(use-package re-builder
:config
(setq reb-re-syntax 'string))
(require 'recentf)
(use-package emacs
:config
(setq recentf-save-file (expand-file-name ".recentf" k20e/google-drive-directory)
recentf-max-saved-items 250)
(recentf-mode 1)
:bind (("C-x C-r" . recentf-open)))
(use-package flycheck :ensure t)
(use-package flycheck-rust :ensure t)
(use-package rust-mode
:ensure t
:config
(defun k20e/rust-mode-hook ()
(setq flycheck-checker 'rust
rust-format-on-save t)
(flycheck-mode 1)
(flycheck-rust-setup))
:hook ((rust-mode . k20e/rust-mode-hook)))
(use-package emacs
:init
(save-place-mode t))
(use-package savehist
:config
(setq savehist-file (expand-file-name ".savehist" k20e/google-drive-directory)))
(savehist-mode)
Begin with an empty *scratch*
file.
(setq initial-scratch-message nil)
Set it to Emacs Lisp mode.
(with-current-buffer (get-buffer-create "*scratch*")
(emacs-lisp-mode))
With a preset list of major modes that I find often need scratch pads for.
(defconst k20e/scratch-buffer-modes
'(fundamental-mode
emacs-lisp-mode
python-mode
javascript-mode
org-mode
sql-mode
text-mode
yaml-mode)
"Common major modes to create scratch buffers for.")
(defun k20e/scratch-buffer ()
"Generate a new scratch buffer.
Choose from `k20e/scratch-buffer-modes' list of major modes to
enable in the newly created scratch buffer and switch to it."
(interactive)
(let ((mode (read (completing-read "New *scratch* buffer with mode: "
(mapcar (lambda (el) (format "%s" el))
k20e/scratch-buffer-modes)))))
(switch-to-buffer (generate-new-buffer (format "*scratch-%s*" mode)))
(funcall mode)))
Bind it globally.
(global-set-key (kbd "<f10>") 'k20e/scratch-buffer)
(use-package sh-script
:after (flycheck)
:functions flycheck-select-checker
:config
(defun k20e/sh-script-hook ()
(flycheck-select-checker 'sh-shellcheck))
:hook ((sh-mode . k20e/sh-script-hook)
(sh-mode . flycheck-mode)
(sh-mode . prettify-symbols-mode)))
Start the Emacs server if it not already running.
(require 'server)
(unless (server-running-p)
(server-start))
(use-package smex
:ensure t
:config
(smex-initialize)
(setq-default smex-save-file (expand-file-name ".smex-items" k20e/google-drive-directory)))
(use-package sql
:config
(setq sql-product 'mysql
tab-width 4))
Always highlight the current line:
(use-package hl-line)
(use-package emacs
:config
(global-hl-line-mode t))
Simple stuff:
(use-package simple)
(use-package emacs
:config
(line-number-mode)
(column-number-mode)
(size-indication-mode))
Highlight matching parentheses:
(use-package paren)
(use-package emacs
:config
(show-paren-mode))
Blink the cursor:
(use-package frame)
(use-package emacs
:config
(setq-default blink-cursor-mode t))
Truncate lines and enable fringes to indicate truncated lines:
(use-package fringe)
(use-package emacs
:config
(setq-default
truncate-lines t
truncate-partial-width-windows nil)
(fringe-mode))
No bell:
(use-package emacs
:config
(setq-default ring-bell-function 'ignore))
Make sure syntax highlighting is enabled:
(use-package font-core)
(use-package emacs
:config
(global-font-lock-mode))
Set theme automatically on MacOS thanks to https://github.com/d12frosted/homebrew-emacs-plus#system-appearance-change. Just set it to dark on Linux for now.
(use-package color-theme-sanityinc-tomorrow
:ensure t
:config
(cond ((eq system-type 'darwin)
(defun k20e/load-theme (mode)
"Load light or dark theme depending on which mode the system is currently in."
(pcase mode
('light (load-theme 'sanityinc-tomorrow-day t))
('dark (load-theme 'sanityinc-tomorrow-bright t))))
(add-hook 'ns-system-appearance-change-functions 'k20e/load-theme))
((string-equal system-type "gnu/linux")
(load-theme 'sanityinc-tomorrow-bright t))))
(use-package terraform-mode
:ensure t
:hook ((terraform-mode . terraform-format-on-save-mode)))
(use-package simple
:hook ((text-mode . auto-fill-mode)))
(use-package toml-mode
:ensure t
:mode ("Pipfile\\'" . toml-mode))
Fix ControlPath too long
errors due to OS X pitching a long temporary directory to ssh
.
Unfortunately, setting this is blowing up the server-start
which
can no longer find the socket stored in the original TMPDIR
value.
;; (setenv "TMPDIR" "/tmp")
Eureka! It appears that the ControlMaster
option for ssh
should be set to yes
instead of auto
to avoid the ControlPath
too long
error. Here is the interesting section of man 5
ssh_config
:
ControlMaster Enables the sharing of multiple sessions over a single network connection. When set to ``yes'', ssh(1) will listen for connections on a control socket specified using the ControlPath argument. Additional sessions can connect to this socket using the same ControlPath with ControlMaster set to ``no'' (the default). These sessions will try to reuse the master instance's network connection rather than initiating new ones, but will fall back to connecting normally if the control socket does not exist, or is not listening. Setting this to ``ask'' will cause ssh to listen for control connections, but require confir- mation using the SSH_ASKPASS program before they are accepted (see ssh-add(1) for details). If the ControlPath cannot be opened, ssh will continue without connecting to a master instance. X11 and ssh-agent(1) forwarding is supported over these multiplexed connections, however the display and agent forwarded will be the one belonging to the master connection i.e. it is not possible to forward multiple displays or agents. Two additional options allow for opportunistic multiplexing: try to use a master connection but fall back to creating a new one if one does not already exist. These options are: ``auto'' and ``autoask''. The latter requires confirmation like the ``ask'' option.
The tramp-ssh-controlmaster-options
variable is responsible for
the ControlMaster
value as well as a few other options which have
not been changed from their default values.
(use-package tramp
:defines tramp-ssh-controlmaster-options
:config
(setq tramp-ssh-controlmaster-options "-o ControlPath=%t.%%r@%%h:%%p -o ControlMaster=yes -o ControlPersist=no"))
Do not inline copy files. This is to avoid File exists, but
cannot be read
errors.
(setq-default tramp-copy-size-limit -1)
Do not backup files edited by tramp
to avoid possibly sharing
copies of privileged files with non-privileged users.
(add-to-list 'backup-directory-alist (list tramp-file-name-regexp))
Level 3
by default.
;; (setq tramp-verbose 6)
Will create a detailed log buffer.
- State “STARTED” from “TODO” [2024-01-08 Mon 09:20]
For now I’ve just committed whatever parsers I’ve needed at the time into ~/.emacs.d/tree-sitter
. I need to invest
more time into getting this properly setup so I can try out combobulate.
Check out system-configuration-features
to make sure that TREE_SITTER
is present.
- https://tree-sitter.github.io/tree-sitter/
- https://www.masteringemacs.org/article/how-to-get-started-tree-sitter
There are two grammars for Tree-sitter. The first is for TypeScript, the second is for TSX and each must be
installed separately, e.g., M-x treesit-install-language-grammar
, then for TypeScript enter typescript
as the
language, and take the defaults until prompted for the src
directory. Change this to typescript/src
and continue
with the defaults. For TSX, do not use typescript
as the language but rather tsx
. It will fail to find the
repository so paste in https://github.com/tree-sitter/tree-sitter-typescript. When prompted for the src
directory,
enter tsx/src
and continue with the defaults. There will then be two grammars compiled in the tree-sitter
directory:
libtree-sitter-tsx.dylib libtree-sitter-typescript.dylib
Configuration:
(use-package typescript-mode
:ensure t
:mode ("\\.ts\\'" . typescript-mode)
:hook ((typescript-mode . lsp-deferred)))
Name multiple identical buffer names in a sensible manner.
(use-package uniquify
:config
(setq uniquify-buffer-name-style 'forward))
Since vertico is meant to be plug & play, I’m bundling the extension configuration under this section, as well.
(use-package vertico
:ensure t
:init
(vertico-mode)
(setq vertico-cycle t
vertico-preselect 'first))
- State “STARTED” from “TODO” [2023-12-16 Sat 21:12]
- [ ] https://github.com/minad/vertico#extensions
- [ ] https://github.com/minad/vertico#completion-at-point-and-completion-in-region
- [X] https://github.com/minad/vertico/wiki#make-vertico-and-vertico-directory-behave-more-like-ivyido
vertico-directory is an extension which provides Ido-like directory navigation. It is vendored in the vertico
repo. so do not :ensure t
here.
(use-package vertico-directory
:after vertico
:demand
:bind (:map vertico-map
("RET" . vertico-directory-enter)
("DEL" . vertico-directory-delete-char)
("M-DEL" . vertico-directory-delete-word))
:hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
orderless is an alternative completion style extension.
(use-package orderless
:ensure t
:init
(setq completion-styles '(orderless basic)))
consult is a completing-read
extension.
(use-package consult
:ensure t
:demand
:config
(defun consult-line-symbol-at-point ()
(interactive)
(consult-line (thing-at-point 'symbol)))
:bind (("C-s" . consult-line)
("C-x b". consult-buffer)
("C-x C-a" . consult-ripgrep)
("C-x C-g" . consult-git-grep)
("M-g M-g" . consult-goto-line)
:map org-mode-map
("C-c C-j" . consult-org-heading)))
- State “STARTED” from “TODO” [2023-12-08 Fri 17:08]
corfu is an in-buffer completion popup extension.
(use-package corfu
:ensure t
:custom (setq corfu-cycle t)
:init (global-corfu-mode))
(use-package emacs
:init (setq tab-always-indent 'complete))
- [ ] https://github.com/minad/corfu#orderless-completion
- [ ] https://github.com/minad/corfu#alternatives particularly
consult-completion-in-region
: https://github.com/minad/consult#miscellaneous as an alternative tocorfu
marginalia is an extension that adds information as annotations in the minibuffer.
(use-package marginalia
:ensure t
:after vertico
:bind (:map minibuffer-local-map ("M-A" . marginalia-cycle))
:init (marginalia-mode))
(use-package windmove
:config
(windmove-default-keybindings 'super)
(setq windmove-wrap-around t)
:bind (("M-SPC" . windmove-right)
("M-S-SPC" . windmove-left)))
(use-package elec-pair
:functions electric-pair-default-inhibit)
(use-package web-mode
:ensure t
:functions flycheck-add-mode
:after (elec-pair flycheck)
:mode
("\\.html?\\'" . web-mode)
("\\.jsx?\\'" . web-mode)
("\\.tsx?\\'" . web-mode)
:config
(defun k20e/web-mode-hook ()
(set-default 'web-mode-engines-alist '(("django" . "\\.html?\\'")))
(setq web-mode-markup-indent-offset 2
web-mode-enable-auto-quoting nil)
(flycheck-add-mode 'typescript-tslint 'web-mode)
;; Disable pairing { to avoid {{ }}}} from web-mode block templates
(setq-local electric-pair-inhibit-predicate
(lambda (c)
(if (char-equal c ?{) t (electric-pair-default-inhibit c)))))
:hook ((web-mode . k20e/web-mode-hook)))
Remember window configurations.
(use-package winner
:config
(winner-mode))
Take care of some whitespace issues.
- Kill trailing whitespace on save
- Insert a new line at the end of file on save
- Prefer
space
overtab
(use-package whitespace
:ensure t
:config
(setq-default require-final-newline t
mode-require-final-newline t
indent-tabs-mode nil)
(defun k20e/before-save-hook ()
(whitespace-cleanup)
(delete-trailing-whitespace 0 nil))
:hook ((before-save . k20e/before-save-hook)))
(use-package flycheck-yamllint
:ensure t
:after (flycheck))
(use-package highlight-indent-guides
:ensure t)
(use-package yaml-mode
:ensure t
:after (flycheck-yamllint flyspell highlight-indent-guides)
:bind (:map yaml-mode-map ("C-m" . newline-and-indent))
:config
(defun k20e/yaml-mode-hook ()
(flycheck-select-checker 'yaml-yamllint)
(flycheck-yamllint-setup)
(flyspell-mode-off))
:hook ((yaml-mode . k20e/yaml-mode-hook)
(yaml-mode . flycheck-mode)
(yaml-mode . highlight-indent-guides-mode)
(yaml-mode . prettify-symbols-mode)))
(use-package zig-mode
:ensure t
:bind
(:map zig-mode-map ("C-." . 'company-indent-or-complete-common))
:functions
lsp-register-client
make-lsp-client
:hook
((before-save . (lambda () (when (eq major-mode 'zig-mode)
(lsp-format-buffer))))
(zig-mode . lsp-deferred))
:custom (zig-format-on-save nil)
:config
(lsp-register-client
(make-lsp-client
:major-modes '(zig-mode)
:server-id 'zls)))
zig config
then modified a bit to arrive at:
{
"enable_semantic_tokens": true,
"enable_snippets": true,
"include_at_in_builtins": false,
"include_at_in_builtins": true,
"max_detail_length": 1048576,
"operator_completions": true,
"warn_style": true,
"zig_exe_path": "/bin/zig",
"zig_lib_path": "/usr/lib/zig"
}