French Vanilla
#+blockquote Abstain from every form of evil. 1 Thessalonians 5:22 #+endquote
This is French Vanilla, my literate emacs config. The goal of this config is to rely upon vanilla emacs as much as possible. I hope to keep it light on packaging and alterations and instead lean upon emacs’ built-ins and defaults unless there is a compelling reason not to. This also means that when selecting what packages to use, the packages should embrace emacs concepts and in turn lean upon the emacs way of doing things.
Need an LSP? Use Emacs native Eglot.
Need tree sitter? Use Emacs native treesit.
Need a project tree? Dired exists, use it.
Additionally, it needs to be light. Org’s symbol faces aren’t horrid, we don’t need to prettify them or anything like that, just use them as they are.
Here are some emac configs I’ve found useful:
Boot Scootin Boogie
This isn’t quite bootstrappin’, but it’s near about the same idea. This gets my Emacs config to a point where I can then start using the things that make using Emacs easier. Most this is early-init.el
stuff to speed up load times and fix some things needed for the remainder of the config.
Scripts
These are scripts that exist in the emacs directory but are used outside of emacs, for example with a call to emacs --quick --script foo.el
.
gen-env-file
This script generates a static env file from the shell env. This is needed because emacs on MacOS doesn’t “see” your env values when called from the GUI, like with Finder or the icon on the Dock. This results in lots of breakage. This came from https://roamingbytes.substack.com/p/emacs-on-macos-preserving-the-correct-environment-1735d2e8cb88
(defun gen-env-file (path)
"Save envvars to a file at path"
(let ((dirname (file-name-directory path)))
(make-directory dirname t))
(with-temp-file path
(setq-local coding-system-to-write 'utf-8-unix)
(insert
";; -*- mode: emacs-lisp -*-\n"
";; This file was automatically genereated and will be overwritten.\n")
(insert (pp-to-string process-environment))))
(gen-env-file "~/.config/emacs/local/env.el")
emacs --quick --script $HOME/.config/emacs/scripts/gen-env-file.el
early-init.el
;;; early-init.el --- Early Init File -*- lexical-binding: t; no-byte-compile: t -*-
;; NOTE: this file is generated from ~/org/emacs.org
HACK Work around native compilation on macOS failing with ‘ld: library not found for -lemutlsw‘. https://github.com/d12frosted/homebrew-emacs-plus/issues/554
(setenv "LIBRARY_PATH"
(string-join
'("/opt/homebrew/opt/gcc/lib/gcc/13"
"/opt/homebrew/opt/libgccjit/lib/gcc/13"
"/opt/homebrew/opt/gcc/lib/gcc/13/gcc/aarch64-apple-darwin23/13")
":"))
Tame the GC.
(setq gc-cons-threshold 10000000)
(setq read-process-output-max (* 1024 1024 3) ; 3mb
Make things a little quieter.
(setq byte-compile-warnings '(not obsolete))
(setq warning-suppress-log-types '((comp) (bytecomp)))
(setq native-comp-async-report-warnings-errors 'silent)
(setq inhibit-startup-echo-area-message (user-login-name))
Make some changes to how the UI will load. We’ll do more later, this is just to prevent any early ugliness and flashing if emacs hangs sometime between early-init and when that stuff gets loaded in init.
(setq frame-inhibit-implied-resize t
frame-resize-pixelwise t
default-frame-alist '((ns-transparent-titlebar . t)
(full-screen . maximized)
(vertical-scroll-bar . nil)
(horizontal-scroll-bar . nil))
visual-bell t
display-time-default-load-average nil)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(tooltip-mode -1)
(menu-bar-mode -1)
(set-fringe-mode 10)
https://emacs-lsp.github.io/lsp-mode/page/performance/#use-plists-for-deserialization
(setenv "LSP_USE_PLISTS" "true")
Disable package.el
We disable package.el because we’re using elpaca.el. This is disabled early so it’s not accidentally used, we want elpaca to do all the work of figuring out our packaging needs.
(setq package-enable-at-startup nil
package-quickstart nil)
init.el
The rest of this org file all goes into init.el
unless specified otherwise.
;;; init.el --- Personal configuration file -*- lexical-binding: t; no-byte-compile: t; -*-
;; NOTE: this file is generated from ~/org/emacs.org
Initial Theme
And set a background color early so on bootstrap there’s no jarring color changes after the theme is loaded in. We’ll set the background color to either black or white, depnding on what the system appearnce is.
(defun fv--paint-it (fg bg)
"A small function to entirely 'paint' emacs to `color'"
(set-background-color bg)
(set-foreground-color fg)
(custom-set-faces
'(mode-line-highlight ((t nil)))
`(mode-line ((t (:foreground ,fg :background ,bg))))
`(mode-line-inactive ((t (:background ,bg :foreground ,fg))))
`(fringe ((t :background ,bg :foreground ,fg)))))
(if (boundp 'ns-system-appearance)
(pcase ns-system-appearance
('light (fv--paint-it "black" "white"))
('dark (fv--paint-it "white" "black")))
(fv--paint-it "black" "white"))
Profilin’
Define a hook that will report back on load time. This only loads on debug
(when debug-on-error
(add-hook 'elpaca-after-init-hook
(lambda ()
(message "Emacs loaded in %s with %d garbage collections."
(format "%.2f seconds"
(float-time
(time-subtract (current-time) before-init-time)))
gcs-done))))
Enable the profiler to view the report after init
(when debug-on-error
(profiler-start 'cpu+mem)
(add-hook 'elpaca-after-init-hook (lambda () (profiler-stop) (profiler-report))))
Initdial Buffer Selection
Open emacs in the scratch buffer.
(setq initial-buffer-choice t) ;;*scratch*
(setq inhibit-startup-message t)
Load env file
(defun load-env-file (file)
"Read and set envvars from FILE."
(if (null (file-exists-p file))
(signal 'file-error
(list "No envvar file exists." file
"Run `emacs --script ~/.config/emacs/scripts/gen-env-file.el`."))
(with-temp-buffer
(insert-file-contents file)
(when-let (env (read (current-buffer)))
(let ((tz (getenv-internal "TZ")))
(setq-default
process-environment
(append env (default-value 'process-environment))
exec-path
(append (split-string (getenv "PATH") path-separator t)
(list exec-directory))
shell-file-name
(or (getenv "SHELL")
(default-value 'shell-file-name)))
(when-let (newtz (getenv-internal "TZ"))
(unless (equal tz newtz)
(set-time-zone-rule newtz))))
env))))
(load-env-file "~/.config/emacs/local/env.el")
Package Management With Elpaca
An elisp package manager
;; (setq elpaca-core-date '(20231211))
(defvar elpaca-installer-version 0.7)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
:ref nil
:files (:defaults (:exclude "extensions"))
:build (:not elpaca--activate-package)))
(let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory))
(build (expand-file-name "elpaca/" elpaca-builds-directory))
(order (cdr elpaca-order))
(default-directory repo))
(add-to-list 'load-path (if (file-exists-p build) build repo))
(unless (file-exists-p repo)
(make-directory repo t)
(when (< emacs-major-version 28) (require 'subr-x))
(condition-case-unless-debug err
(if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
((zerop (call-process "git" nil buffer t "clone"
(plist-get order :repo) repo)))
((zerop (call-process "git" nil buffer t "checkout"
(or (plist-get order :ref) "--"))))
(emacs (concat invocation-directory invocation-name))
((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
"--eval" "(byte-recompile-directory \".\" 0 'force)")))
((require 'elpaca))
((elpaca-generate-autoloads "elpaca" repo)))
(progn (message "%s" (buffer-string)) (kill-buffer buffer))
(error "%s" (with-current-buffer buffer (buffer-string))))
((error) (warn "%s" err) (delete-directory repo 'recursive))))
(unless (require 'elpaca-autoloads nil t)
(require 'elpaca)
(elpaca-generate-autoloads "elpaca" repo)
(load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))
We need this to load the ssh keys for git
(elpaca-queue
(elpaca keychain-environment
(keychain-refresh-environment)))
Enable elpaca to act like use-package
:
(elpaca elpaca-use-package
(elpaca-use-package-mode)
(setq elpaca-use-package-by-default t))
Setup a macro to use-feature
.
(defmacro use-feature (name &rest args)
"Like `use-package' but accounting for asynchronous installation.
NAME and ARGS are in `use-package'."
(declare (indent defun))
`(use-package ,name
:ensure nil
,@args))
Setup a macro to use-builtin
.
(defmacro use-builtin (name &rest args)
"Like `use-package' but accounting for asynchronous installation.
NAME and ARGS are in `use-package'."
`(progn
(cl-pushnew (quote ,name) elpaca-ignored-dependencies)
(use-package ,name
:ensure nil
,@args)))
(elpaca-wait)
On debug we want to make elpaca verbose and we want to use benchmark-init.
(when debug-on-error
(setq use-package-verbose t
use-package-expand-minimally nil
use-package-compute-statistics t
use-package-verbose nil
use-package-expand-minimally t)
(use-package benchmark-init
:ensure t
:config
(add-hook 'after-init-hook #'benchmark-init/deactivate))
(elpaca-wait))
Emacs Files
This section is concerned with taming and managing emacs’ files used for various things, like undo history, buffer backups, project lists. The goal of this section is to ensure that emacs does not use the emacs directory for anything other than the configuration, packages, and elc. Evertying else belongs in the .local/share/emacs
directory where it won’t get nuked when I rm -rf
and recompile my emacs config. To get this we’ll be using the no-littering package to help manage all the various emacs files.
First we set the directories this package uses. I’m setting this outside of the package because I’ll need these vars for a few other packages and this way they’re just “there”.
(setq no-littering-etc-directory (expand-file-name "~/.local/share/emacs/etc/")
no-littering-var-directory (expand-file-name "~/.local/share/emacs/var/"))
Now to configure the package
(use-package no-littering
:demand
:config
(with-eval-after-load 'recentf
(add-to-list 'recentf-exclude no-littering-etc-directory)
(add-to-list 'recentf-exclude no-littering-var-directory))
(setq custom-file (no-littering-expand-etc-file-name "custom.el"))
(no-littering-theme-backups))
(elpaca-wait)
Settings
These are mostly setting up non-package things, such as the theme, handling files, and other goodies like that.
Personal Info
(setq user-full-name "Ian S. Pringle"
user-mail-address "[email protected]"
auth-sources '("~/.authinfo"))
Lines
This automatically breaks lines that are too long.
(add-hook 'text-mode-hook 'turn-on-auto-fill)
(setq-default fill-column 80)
(setq colon-double-space nil
adaptive-fill-mode t)
Highlight the current line
(let ((hl-line-hooks '(text-mode-hook prog-mode-hook)))
(mapc (lambda (hook) (add-hook hook 'hl-line-mode)) hl-line-hooks))
We need some modern, sane indentation settings.
(set-default 'indent-tabs-mode nil)
File Management
Re-read files on disk when there’s a change
(setq auto-revert-interval 1)
(setq auto-revert-check-vc-info t)
;; (setq global-auto-revert-non-file-buffers t)
(global-auto-revert-mode)
Emacs can remember the last point of a previously visited file and restore that point when the file is re-opened, this enables that.
(save-place-mode 1)
In addition to remembering where in a file you were last, emacs should remember what files were recently visited, this makes sure emacs remembers recent files
(recentf-mode 1)
This isn’t really file related but it sorta is.
(savehist-mode)
Editor
This section is for things pertaining to the writing of things.
When I select a region and then type I want the region to be killed.
(delete-selection-mode)
This is currently bound to C-S-Backspace
but that’s a rather awkward key-combo so I’m making it a bit closer to what vim uses.
(global-set-key (kbd "C-S-d") 'kill-whole-line)
Auto insert pairs and show pairs when at point.
(electric-pair-mode 1)
(setq electric-pair-pairs '(
(?\" . ?\")
(?\{ . ?\})))
Visuals
Themes
Grab our theme from melpa
(use-package moe-theme
:ensure t
:init
(defvar fv--default-theme-light 'moe-light
"The default light theme.")
(defvar fv--default-theme-dark 'moe-dark
"The default dark theme.")
(defun fv--moe-light ()
(moe-light)
(custom-set-faces '(fringe ((t :background "#9e9e9e" :foreground "#d7d7af")))))
(defun fv--moe-dark ()
(moe-light)
(custom-set-faces '(fringe ((t :background "#9e9e9e" :foreground "#87875f")))))
:config
(setq moe-theme-modeline-color 'blue
moe-theme-highlight-buffer-id t))
I like moe-theme a lot, it matches my wacky aesthetic, but I also love brutally minimal themes and punpun is the most metal of all minimalist emacs themes that I have found thus far 🤟
(use-package punpun-themes)
I’ve just discovered this gem called “stimmung-themes” and it’s even more metal than punpun.
(use-package stimmung-themes
:demand t
:ensure t
:init
(defvar fv--default-theme-light 'stimmung-light
"The default light theme.")
(defvar fv--default-theme-dark 'stimmung-dark
"The default dark theme.")
(defun fv--stimmung-light ()
(stimmung-themes-load-light))
(defun fv--stimmung-dark ()
(stimmung-themes-load-dark))
:config
(setq stimmung-themes-comment 'foreground))
(use-package ef-themes
:demand t
:ensure t
:init
(defvar fv--default-theme-light 'ef-light
"The default light theme.")
(defvar fv--default-theme-dark 'ef-dark
"The default dark theme.")
(defun fv--ef-light ()
(ef-themes-select 'ef-cyprus))
(defun fv--ef-dark ()
(ef-themes-select 'ef-elea-dark)))
Set our default light and dark theme, then create a function to set the current theme based on system settings.
(defun fv--load-theme (theme-name)
"Loads the specified theme"
(mapc #'disable-theme custom-enabled-themes)
(pcase theme-name
;; ('moe-dark (fv--moe-dark))
;; ('moe-light (fv--moe-light))
('stimmung-dark (fv--stimmung-dark))
('stimmung-light (fv--stimmung-light))
;; ('ef-dark (fv--ef-dark))
;; ('ef-light (fv--ef-light))
(_ (load-theme theme-name t))))
(defun fv--set-theme (appearance)
"Calls `load-theme' setting a theme based on system appearance."
(pcase appearance
('light (fv--load-theme fv--default-theme-light))
('dark (fv--load-theme fv--default-theme-dark))))
(if (boundp 'ns-system-appearance)
(add-hook 'ns-system-appearance-change-functions #'fv--set-theme)
(add-hook 'elpaca-after-init-hook #'(fv--load-theme fv--default-theme-light)))
We also need a hook to know when the theme has changed, this can then be used to reapply any changes (like to the mode-line) that the theme might have over written. This was largely taken from the emacs manual and then I just renamed the hook and applied it to both the enable-theme
and load-theme
functions.
(defvar after-theme-applied-hook nil
"Normal hook run after loading a theme.")
(defun run-after-theme-applied-hook (&rest args)
"Run `after-theme-applied-hook'"
(run-hooks 'after-theme-applied-hook))
(advice-add 'enable-theme :after #'run-after-theme-applied-hook)
(advice-add 'load-theme :after #'run-after-theme-applied-hook)
System
Some of emacs’s prompts in GUI mode are new OS windows, this disables that annoying feature.
(setq use-dialog-box nil)'
And of course line numbers!
(column-number-mode)
(global-display-line-numbers-mode t)
(setq line-number-mode t
column-number-mode t)
(dolist (mode '(org-mode-hook
term-mode-hook
eshell-mode-hook))
(add-hook mode (lambda () (display-line-numbers-mode 0))))
Some misc. stuff…
(setq x-underline-at-descent-line nil
switch-to-buffer-obey-display-actions t
mouse-wheel-tilt-scroll t
mouse-wheel-flip-direction t)
(setq-default show-trailing-whitespace nil)
(setq-default indicate-buffer-boundaries 'left)
(blink-cursor-mode -1)
(pixel-scroll-precision-mode)
This is the face that will be used in most places.
(set-face-attribute 'default nil :font "CozetteVector" :height 240)
kill vc
I only use git and I only need magit for that. I have found that multiple lock-ups have been due to vc-*
functions and processes, so just get rid of it.
(with-eval-after-load 'vc
(remove-hook 'find-file-hook 'vc-find-file-hook)
(remove-hook 'find-file-hook 'vc-refresh-state))
(setq vc-hangled-backends nil)
We still need those functions though because project.el
relies on them, so we need to provide those functions.
(defun fv--find-dot-git (dir)
"Find the .git project root"
(let ((dgit (and (setq dir (locate-dominating-file dir ".git"))
(expand-file-name dir))))
(and dgit
(cons 'transient (file-name-directory dgit)))))
(add-hook 'project-find-functions 'fv--find-dot-git)
Completion
This is mostly completion/tab-key stuff but there’s a sprinkle of minibuffer config in here too since they sorta all go together…
(setq enable-recursive-minibuffers t
completion-cycle-threshold 1
completions-detailed t
tab-always-indent t
completion-auto-help 'always
completions-max-height 20
completions-detailed t
completions-format 'one-column
completions-group t
completion-auto-select 'second-tab
read-extended-command-predicate #'command-completion-default-include-p)
(keymap-set minibuffer-mode-map "TAB" 'minibuffer-complete)
Tab Bar
Do not show the tab bar until a new tab is created
(setq tab-bar-show 0)
If there is a tab bar move the modeline’s datetime to the tab bar instead.
(add-to-list 'tab-bar-format 'tab-bar-format-align-right 'append)
(add-to-list 'tab-bar-format 'tab-bar-format-global 'append)
(setq display-time-format "%a %F %T")
(setq display-time-interval 1)
(display-time-mode)
Misc.
This is the unsorted / undocumented
(setq sentence-end-double-space nil)
(when (display-graphic-p)
(context-menu-mode))
C-[
The C-[
binding in Emacs defaults to the ESC
keycode. This isn’t just an emacs thing, it’s a terminal thing. But it’s not like other default bindings, you can’t just unbind it, and if you try to weird stuff happens…
I found a solution in this config that should do the trick though (it doesn’t…)
(global-set-key (kbd "C-[") (kbd "ESC"))
(defun fv--fix-C-open-bracket (&optional frame)
(with-selected-frame (or frame (selected-frame))
(define-key input-decode-map [?\C-\[] (kbd "<C-[>"))))
(fv--fix-C-open-bracket)
(add-hook 'after-make-frame-functions 'fv--fix-C-open-bracket)
Local Lisp
This is for lisp functions and local packages.
strip-html
(defun strip-html ()
"Remove HTML tags from the current buffer,
(this will affect the whole buffer regardless of the restrictions in effect)."
(interactive "*")
(save-excursion
(save-restriction
(widen)
(goto-char (point-min))
(while (re-search-forward "<[^<]*>" (point-max) t)
(replace-match "\\1"))
(goto-char (point-min))
(replace-string "©" "(c)")
(goto-char (point-min))
(replace-string "&" "&")
(goto-char (point-min))
(replace-string "<" "<")
(goto-char (point-min))
(replace-string ">" ">")
(goto-char (point-min)))))
Tangle Funcs
These are some functions I like to have to making tangling easier.
This first one is a function that can be passed to :tangle
and will determine whether a block is tangled by tags on the headline the block is.
(defun fv--tangle-maybe (&optional dir)
(if (member-ignore-case "tangleno" (org-get-tags))
"no"
(or dir ("yes"))))
Hide Modeline
This is a small minor mode taken from bzg that will hide the modeline.
;; See http://bzg.fr/emacs-hide-mode-line.html
(defvar-local hidden-mode-line-mode nil)
(defvar-local hide-mode-line nil)
(define-minor-mode hidden-mode-line-mode
"Minor mode to hide the mode-line in the current buffer."
:init-value nil
:global nil
:variable hidden-mode-line-mode
:group 'editing-basics
(if hidden-mode-line-mode
(setq hide-mode-line mode-line-format
mode-line-format nil)
(setq mode-line-format hide-mode-line
hide-mode-line nil))
(force-mode-line-update)
;; Apparently force-mode-line-update is not always enough to
;; redisplay the mode-line
(redraw-display)
(when (and (called-interactively-p 'interactive)
hidden-mode-line-mode)
(run-with-idle-timer
0 nil 'message
(concat "Hidden Mode Line Mode enabled. "
"Use M-x hidden-mode-line-mode to make the mode-line appear."))))
;; Activate hidden-mode-line-mode
;; (hidden-mode-line-mode 1)
Custom Bindings Mode
This is my mode for all my custom keybindings. I have this so that custom keybindings can be layered after all other modes are called which then ensures my bindings can never be overwritten.
This package doesn’t exist. It’s a pipe dream.
Hasty
This is another non-existent package/pipe-dream.
Hasty is my theoretical HTML AST in elisp. The idea here is that if you can write an AST for HTML as elisp, then it’ll be simpler to write HTML and convert to and from HTML. For example, with an AST we could rewrite emmet-mode as a parser that converts the emmet strings to the AST and then from there we can output that emmet string to HTML, Hiccup, Org, etc..
h.el
Theoretical packages are a common denominator for me. h.el is a small library to express HTML in elisp as elisp.
(defun h (tag)
(format "<%s></%s>" tag tag))
Packages
For the time, I’m listing each package as a header. I might consider changing this in the future. In my neovim config I group packages together, however the nvim package management tool makes this very easy to do since you can list packages as dependencies of one another. I might try “leaf” which is an alternative package management style to use-package, but that’d be a lot of work for what might be little gain. So for the time being… a package per heading (with some exceptions).
I’m tagging some packages, but not all of them. I’ve tagged in the past with something akin to the “category” of a package, but that’s hard sometimes. Instead I’m now tagging just packages that I think of as “core” packages or packages that are really very useless beyond like eye-candy. This is for a few reason, when it comes to debugging, I know I can disable all the “eye-candy” right away and if the problem is resolved well then something is getting deleted. With “core” that’s the last set of packages I’ll disable and if a core package ends up being the problem child then I need to solve that problem or find a different package to replace it. I’m also tagging built-ins because those are sort of special too.
Before kicking things off, we need to provide a place for the rare package that must be installed sooner than others. Those packages will be set to :tangle no
and then will need to be put into the below src block with noweb
. Furthermore, because these packages cannot be easily disabled with a tag on the headline, they’ll be tagged with :noweb:
to signify that they need additional steps if they need to be disabled.
<<seq-el>>
<<transient>>
<<general-el>>
<<which-key>>
(elpaca-wait)
ace-window
Quickly switch windows in emacs
(use-package ace-window
:general
("C-x o" 'ace-window)
("M-x" 'ace-window)
:config
(setq aw-scope 'frame
aw-ignore-current t
aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l ?\;))
(defun fv--set-other-window-scroll-buffer (window)
"Set buffer in window to other-window-scroll-buffer"
(setq other-window-scroll-buffer (window-buffer window)))
(push '(?r fv--set-other-window-scroll-buffer "Set other-window-scroll-buffer")
aw-dispatch-alist))
anzu
https://github.com/emacsorphanage/anzu
(use-package anzu
:defer 5
:config
(global-anzu-mode +1)
(global-set-key [remap query-replace] 'anzu-query-replace)
(global-set-key [remap query-replace-regexp] 'anzu-query-replace-rexexp)
(custom-set-variables
'(anzu-mode-lighter "")
'(anzu-deactivate-region t)
'(anzu-replace-to-string-separator " => ")))
amx
A better M-x I guess.
(use-package amx
:general
("M-x" 'amx)
("C-x C-m" 'amx)
("C-x C-M" 'amx-major-mode-commands))
apheleia
🌷 Run code formatter on buffer contents without moving point, using RCS patches and dynamic programming.
(use-package apheleia
:defer t
:custom
(apheleia-mode-lighter nil)
(js-indent-level 2)
(standard-indent 2)
:config
;; (require apheleia-formatters)
;; (dolist (formatter-cmds '((eslint . (npx "eslint" "--fix-to-stdout" "--stdin" "--stdin-filename" file))))
;; (add-to-list #'apheleia-formatters formatter-cmds))
;; (dolist (formatter-modes '((typescript-mode . apheleia-js-formatter)
;; (javascript-mode . apheleia-js-formatter)
;; (javascript-ts-mode . apheleia-js-formatter)
;; (typescript-ts-mode . apheleia-js-formatter)))
;; (add-to-list #'apheleia-mode-alist formatter-modes))
:init
(apheleia-global-mode)
;; (defvar apheleia-js-formatter 'prettier)
;; (defun apheleia-toggle-js-formatter ()
;; (interactive)
;; (if (equal apheleia-js-formatter 'prettier)
;; (setq apheleia-js-formatter 'eslint)
;; (setq apheleia-js-formatter 'prettier))
;; (dolist (formatter-modes '((typescript-mode . apheleia-js-formatter)
;; (javascript-mode . apheleia-js-formatter)
;; (javascript-ts-mode . apheleia-js-formatter)
;; (typescript-ts-mode . apheleia-js-formatter)))
;; (add-to-list #'apheleia-mode-alist formatter-modes)))
:general
("C-c f" 'apheleia-format-buffer))
avy
Jump to things in Emacs tree-style
https://www.youtube.com/watch?v=o_TlE_U_X3c
(use-package avy
:commands (avy-goto-word-1 avy-goto-char-2 avy-goto-char-timer)
:config
(setq avy-timeout-seconds 0.20)
(setq avy-keys '(?a ?s ?d ?f ?g ?j ?l ?o
?v ?b ?n ?, ?/ ?u ?p ?e ?.
?c ?q ?2 ?3 ?' ?\;))
(setq avy-single-candidate-jump nil)
(setq avy-dispatch-alist '((?m . avy-action-mark)
(?i . avy-action-ispell)
(?z . avy-action-zap-to-char)
(? . avy-action-embark)
(?= . avy-action-define)
(67108896 . avy-action-mark-to-char)
(67108925 . avy-action-tuxi)
;; (?W . avy-action-tuxi)
(?h . avy-action-helpful)
(?x . avy-action-exchange)
(11 . avy-action-kill-line)
(25 . avy-action-yank-line)
(?w . avy-action-easy-copy)
;; (134217847 . avy-action-easy-copy)
(?k . avy-action-kill-stay)
(?y . avy-action-yank)
(?t . avy-action-teleport)
(?W . avy-action-copy-whole-line)
(?K . avy-action-kill-whole-line)
(?Y . avy-action-yank-whole-line)
(?T . avy-action-teleport-whole-line)))
;; (advice-add 'avy-goto-char-timer :around
;; (defun my/avy-with-single-candidate-jump (orig-fn &optional arg)
;; (let ((avy-single-candidate-jump t))
;; (funcall orig-fn arg))))
(defun avy-action-easy-copy (pt)
(unless (require 'easy-kill nil t)
(user-error "Easy Kill not found, please install."))
(goto-char pt)
(cl-letf (((symbol-function 'easy-kill-activate-keymap)
(lambda ()
(let ((map (easy-kill-map)))
(set-transient-map
map
(lambda ()
;; Prevent any error from activating the keymap forever.
(condition-case err
(or (and (not (easy-kill-exit-p this-command))
(or (eq this-command
(lookup-key map (this-single-command-keys)))
(let ((cmd (key-binding
(this-single-command-keys) nil t)))
(command-remapping cmd nil (list map)))))
(ignore
(easy-kill-destroy-candidate)
(unless (or (easy-kill-get mark) (easy-kill-exit-p this-command))
(easy-kill-save-candidate))))
(error (message "%s:%s" this-command (error-message-string err))
nil)))
(lambda ()
(let ((dat (ring-ref avy-ring 0)))
(select-frame-set-input-focus
(window-frame (cdr dat)))
(select-window (cdr dat))
(goto-char (car dat)))))))))
(easy-kill)))
(defun avy-action-exchange (pt)
"Exchange sexp at PT with the one at point."
(set-mark pt)
(transpose-sexps 0))
(defun avy-action-helpful (pt)
(save-excursion
(goto-char pt)
;; (helpful-at-point)
(my/describe-symbol-at-point)
)
(select-window
(cdr (ring-ref avy-ring 0)))
t)
(defun avy-action-define (pt)
(cl-letf (((symbol-function 'keyboard-quit)
#'abort-recursive-edit))
(save-excursion
(goto-char pt)
(dictionary-search-dwim))
(select-window
(cdr (ring-ref avy-ring 0))))
t)
(defun avy-action-tuxi (pt)
(cl-letf (((symbol-function 'keyboard-quit)
#'abort-recursive-edit))
(save-excursion
(goto-char pt)
(google-search-at-point))
(select-window
(cdr (ring-ref avy-ring 0))))
t)
(defun avy-action-embark (pt)
(unwind-protect
(save-excursion
(goto-char pt)
(embark-act))
(select-window
(cdr (ring-ref avy-ring 0))))
t)
(defun avy-action-kill-line (pt)
(save-excursion
(goto-char pt)
(kill-line))
(select-window
(cdr (ring-ref avy-ring 0)))
t)
(defun avy-action-copy-whole-line (pt)
(save-excursion
(goto-char pt)
(cl-destructuring-bind (start . end)
(bounds-of-thing-at-point 'line)
(copy-region-as-kill start end)))
(select-window
(cdr
(ring-ref avy-ring 0)))
t)
(defun avy-action-kill-whole-line (pt)
(save-excursion
(goto-char pt)
(kill-whole-line))
(select-window
(cdr
(ring-ref avy-ring 0)))
t)
(defun avy-action-yank-whole-line (pt)
(avy-action-copy-whole-line pt)
(save-excursion (yank))
t)
(defun avy-action-teleport-whole-line (pt)
(avy-action-kill-whole-line pt)
(save-excursion (yank)) t)
(defun avy-action-mark-to-char (pt)
(activate-mark)
(goto-char pt))
(defun my/avy-goto-char-this-window (&optional arg)
"Goto char in this window with hints."
(interactive "P")
(let ((avy-all-windows t)
(current-prefix-arg (if arg 4)))
(call-interactively 'avy-goto-word-1)))
(defun my/avy-isearch (&optional arg)
"Goto isearch candidate in this window with hints."
(interactive "P")
(let ((avy-all-windows)
(current-prefix-arg (if arg 4)))
(call-interactively 'avy-isearch)))
(defun my/avy--read-char-2 (char1 char2)
"Read two characters from the minibuffer."
(interactive (list (let ((c1 (read-char "char 1: " t)))
(if (memq c1 '(? ?\b))
(keyboard-quit)
c1))
(let ((c2 (read-char "char 2: " t)))
(cond ((eq c2 ?)
(keyboard-quit))
((memq c2 '(8 127))
(keyboard-escape-quit)
(call-interactively 'my/avy-next-char-2))
(t
c2)))))
(when (eq char1 ?
) (setq char1 ?\n))
(when (eq char2 ?
) (setq char2 ?\n))
(string char1 char2))
(defun my/avy-next-char-2 (&optional str2 arg)
"Go to the next occurrence of two characters"
(interactive (list
(call-interactively 'my/avy--read-char-2)
current-prefix-arg))
(let* ((ev last-command-event)
(echo-keystrokes nil))
(push-mark (point) t)
(if (search-forward str2 nil t
(+ (if (looking-at (regexp-quote str2))
1 0)
(or arg 1)))
(backward-char 2)
(pop-mark)))
(set-transient-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd ";") (lambda (&optional arg) (interactive)
(my/avy-next-char-2 str2 arg)))
(define-key map (kbd ",") (lambda (&optional arg) (interactive)
(my/avy-previous-char-2 str2 arg)))
map)))
(defun my/avy-previous-char-2 (&optional str2 arg)
"Go to the next occurrence of two characters"
(interactive (list
(call-interactively 'my/avy--read-char-2)
current-prefix-arg))
(let* ((ev last-command-event)
(echo-keystrokes nil))
(push-mark (point) t)
(unless (search-backward str2 nil t (or arg 1))
(pop-mark)))
(set-transient-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd ";") (lambda (&optional arg) (interactive)
(my/avy-next-char-2 str2 arg)))
(define-key map (kbd ",") (lambda (&optional arg) (interactive)
(my/avy-previous-char-2 str2 arg)))
map)))
(defun my/avy-copy-line-no-prompt (arg)
(interactive "p")
(avy-copy-line arg)
(beginning-of-line)
(zap-to-char 1 32)
(delete-forward-char 1)
(move-end-of-line 1))
(defun my/avy-link-hint (&optional win)
"Find all visible buttons and links in window WIN and open one with Avy.
The current window is chosen if WIN is not specified."
(with-selected-window (or win
(setq win (selected-window)))
(let* ((avy-single-candidate-jump t)
match shr-buttons ov-buttons all-buttons)
;; SHR links
(save-excursion
(goto-char (window-start))
(while (and
(<= (point) (window-end))
(setq match
(text-property-search-forward 'category 'shr t nil)))
(let ((st (prop-match-beginning match)))
(push
`((,st . ,(1+ st)) . ,win)
all-buttons))))
;; Collapsed sections
(thread-last (overlays-in (window-start) (window-end))
(mapc (lambda (ov)
(when (or (overlay-get ov 'button)
(eq (overlay-get ov 'face)
'link))
(let ((st (overlay-start ov)))
(push
`((,st . ,(1+ st)) . ,win)
all-buttons))))))
(when-let
((_ all-buttons)
(avy-action
(lambda (pt)
(goto-char pt)
(let (b link)
(cond
((and (setq b (button-at (1+ pt)))
(button-type b))
(button-activate b))
((shr-url-at-point pt)
(shr-browse-url))
((setq link (or (get-text-property pt 'shr-url)
(thing-at-point 'url)))
(browse-url link)))))))
(let ((cursor-type nil))
(avy-process all-buttons))))))
(custom-set-faces
'(avy-lead-face
((((background dark))
:foreground "LightCoral" :background "Black"
:weight bold :underline t)
(((background light))
:foreground "DarkRed" :background unspecified :box (:line-width (1 . -1)) :height 0.95
:weight bold)))
'(avy-lead-face-0 ((t :background unspecified :inherit avy-lead-face)))
'(avy-lead-face-1 ((t :background unspecified :inherit avy-lead-face)))
'(avy-lead-face-2 ((t :background unspecified :inherit avy-lead-face))))
;; Jump to all paren types with [ and ]
(advice-add 'avy-jump :filter-args
(defun my/avy-jump-parens (args)
(let ((new-regex
(my/avy-replace-syntax-class (car args))))
(cons new-regex (cdr args)))))
(defun my/avy-replace-syntax-class (regex)
(thread-last regex
(string-replace "\\[" "\\s(")
(string-replace "\\]" "\\s)")
(string-replace ";" "\\(?:;\\|:\\)")
(string-replace "'" "\\(?:'\\|\"\\)")))
(defun my/avy-goto-char-timer (&optional arg)
"Read one or many consecutive chars and jump to the first one.
The window scope is determined by `avy-all-windows' (ARG negates it).
This differs from Avy's goto-char-timer in how it processes parens."
(interactive "P")
(let ((avy-all-windows (if arg
(not avy-all-windows)
avy-all-windows)))
(avy-with avy-goto-char-timer
(setq avy--old-cands (avy--read-candidates
(lambda (str) (my/avy-replace-syntax-class
(regexp-quote str)))))
(avy-process avy--old-cands))))
:general
("C-M-'" 'avy-resume
"C-'" '(my/avy-goto-char-this-window :wk "Avy goto char")
"M-j" '(my/avy-goto-char-timer :wk "Avy goto char timer")
"M-s y" '(avy-copy-line :wk "Avy copy line above")
"M-s M-y" '(avy-copy-region :wk "Avy copy region above")
"M-s M-k" '(avy-kill-whole-line :wk "Avy copy line as kill")
"M-s j" '(avy-goto-char-2 :wk "Avy goto char 2")
"M-s M-p" '(avy-goto-line-above :wk "Avy goto line above")
"M-s M-n" '(avy-goto-line-below :wk "Avy goto line below")
"M-s C-w" '(avy-kill-region :wk "Avy kill region")
"M-s M-w" '(avy-kill-ring-save-region :wk "Avy copy as kill")
"M-s t" '(avy-move-line :wk "Avy move line")
"M-s M-t" '(avy-move-region :wk "Avy move region")
"M-s s" '(my/avy-next-char-2 :wk "Avy snipe forward")
"M-s r" '(my/avy-previous-char-2 :wk "Avy snipe backward")
"M-g l" '(avy-goto-end-of-line :wk "Avy goto line")
"M-s z" '(my/avy-copy-line-no-prompt :wk "Avy copy and zap"))
;; (:states '(normal visual)
;; :prefix "g"
;; "s" 'avy-goto-char-timer)
:bind (:map isearch-mode-map
("C-'" . my/avy-isearch)
("M-j" . my/avy-isearch)))
casual-avy
(use-package casual-avy
:ensure t
:bind ("M-s ?" . casual-avy-tmenu))
beacon
(use-package beacon
:ensure t
:custom
(beacon-color "#fd7ae1")
:config
(beacon-mode 1))
bug-hunter
https://github.com/Malabarba/elisp-bug-hunter
(use-package bug-hunter)
burly.el
Save and restore frames and windows with their buffers in Emacs
(use-package burly)
cape
Cape provides Completion At Point Extensions which can be used in combination with Corfu, Company or the default completion UI.
(use-package cape
:bind (("C-c p p" . completion-at-point) ;; capf
("C-c p t" . complete-tag) ;; etags
("C-c p d" . cape-dabbrev) ;; or dabbrev-completion
("C-c p h" . cape-history)
("C-c p f" . cape-file)
("C-c p k" . cape-keyword)
("C-c p s" . cape-symbol)
("C-c p a" . cape-abbrev)
("C-c p l" . cape-line)
("C-c p w" . cape-dict)
("C-c p \\" . cape-tex)
("C-c p _" . cape-tex)
("C-c p ^" . cape-tex)
("C-c p &" . cape-sgml)
("C-c p r" . cape-rfc1345))
:init
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-elisp-block)
;;(add-to-list 'completion-at-point-functions #'cape-history)
;;(add-to-list 'completion-at-point-functions #'cape-keyword)
;;(add-to-list 'completion-at-point-functions #'cape-tex)
;;(add-to-list 'completion-at-point-functions #'cape-sgml)
;;(add-to-list 'completion-at-point-functions #'cape-rfc1345)
;;(add-to-list 'completion-at-point-functions #'cape-abbrev)
;;(add-to-list 'completion-at-point-functions #'cape-dict)
;;(add-to-list 'completion-at-point-functions #'cape-symbol)
;;(add-to-list 'completion-at-point-functions #'cape-line)
)
change-inner
Emacs version of vim’s ci and co commands.
(use-package change-inner
:general
("C-c i" '(change-inner :wk "Change inside surround")
"C-c o" '(change-outer :wk "Delete surround")))
cider
The Clojure Interactive Development Environment that Rocks for Emacs
(use-package cider)
clojure-mode
Emacs support for the Clojure(Script) programming language
(use-package clojure-mode)
consult
Consult provides search and navigation commands based on the Emacs completion function completing-read.
(use-package consult
:demand
:init
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
(setq consult-preview-key "C-l")
(setq consult-narrow-key "<"
consult-widen-key ">")
:bind
(("C-c M-x" . consult-mode-command))
:config
(global-set-key [remap imenu] 'consult-imenu)
(global-set-key [remap switch-to-buffer] 'consult-buffer)
(global-set-key [remap goto-line] 'consult-goto-line)
(consult-customize consult-theme
:preview-key
'("M-."
:debounce 0.5 "<up>" "<down>"
:debounce 1 any)))
consult-dir
Insert paths into the minibuffer prompt in Emacs
(use-package consult-dir
:ensure t
:bind (("C-x C-d" . consult-dir)
:map minibuffer-local-completion-map
("C-x C-d" . consult-dir)
("C-x C-j" . consult-dir-jump-file)))
corfu
Corfu enhances in-buffer completion with a small completion popup.
(use-package corfu
:ensure (corfu :files (:defaults "extensions/*")
:inclues (corfu-history
corfu-popupinfo))
:defer 5
:custom
(corfu-auto nil)
(corfu-auto-delay 0.1)
(corfu-cycle t)
(corfu-excluded-modes '(erc-mode
circe-mode
help-mode
gud-mode))
(corfu-separator ?\s)
(corfu-preview-current 'insert)
(corfu-on-exact-match nil)
(corfu-quit-on-boundary nil)
(corfu-quit-no-match 'separator t)
:config
(defun corfu-enable-always-in-minibuffer ()
"Enable Corfu in the minibuffer if Vertico/Mct are not active."
(unless (or (bound-and-true-p mct--active)
(bound-and-true-p vertico--input)
(eq (current-local-map) read-passwd-map))
;; (setq-local corfu-auto nil) ;; Enable/disable auto completion
(setq-local corfu-echo-delay nil ;; Disable automatic echo and popup
corfu-popupinfo-delay nil)
(corfu-mode 1)))
(add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)
(corfu-history-mode)
(add-to-list 'savehist-additional-variables 'corfu-history)
(add-hook 'corfu-mode #'corfu-popupinfo-mode)
(global-corfu-mode 1))
corfu-candidate-overlay
Show first Corfu’s completion candidate in an overlay while typing.
(use-package corfu-candidate-overlay
:ensure (:type git
:repo "https://code.bsdgeek.org/adam/corfu-candidate-overlay"
:files (:defaults "*.el"))
:after corfu
:config
(corfu-candidate-overlay-mode +1)
(global-set-key (kbd "S-<tab>") #'corfu-complete))
crux
(use-package crux
:general
("C-k" 'crux-smart-kill-line)
("C-RET" 'crux-smart-open-line-above)
("S-RET" 'crux-smart-open-line)
("C-c TAB" 'crux-cleanup-buffer-or-region)
("C-c D" 'crux-delete-file-and-buffer)
("C-c d" 'crux-duplicate-current-line-or-region)
("C-c M-d" 'crux-duplicate-and-comment-current-line-or-region))
delight
Delight enables you to customise the mode names displayed in the mode line.
(use-package delight
:defer 10)
devdocs
devdocs.el is a documentation viewer for Emacs similar to the built-in Info browser, but geared towards documentation distributed by the DevDocs website.
(use-package devdocs
:config
(global-set-key (kbd "C-h D") 'devdocs-lookup))
diminish
This package implements hiding or abbreviation of the mode line displays (lighters) of minor-modes.
(use-package diminish
:defer 10)
dired
Normally I’d have a package per heading, but these are most/all built-ins and they are all basically just dired extensions, so I’m going to keep them under one heading together.
(use-builtin dired
:bind
(:map dired-mode-map
("/" . dired-goto-file)
("," . dired-create-directory)
("." . dired-create-empty-file)
("I" . dired-insert-subdir)
("K" . dired-kill-subdir)
("O" . dired-find-file-other-window)
("[" . dired-prev-dirline)
("]" . dired-next-dirline)
("o" . dired-up-directory)
("^" . mode-line-other-buffer)
("x" . dired-do-delete)
("X" . dired-do-flagged-delete)
("y" . dired-do-copy))
:custom
(dired-listing-switches "-l --almost-all --human-readable --time-style=long-iso --group-directories-first --no-group")
:config
(when (eq system-type 'darwin)
(setq insert-directory-program "gls")))
(use-builtin dired-x
:config
(setq dired-omit-files (concat dired-omit-files "\\|^\\..*$")))
(use-builtin dired-aux)
(use-builtin image-dired
:custom
(image-dired-thumb-size 256)
(image-dired-marking-shows-next nil)
:bind
(:map image-dired-thumbnail-mode-map
("n" . image-dired-next-line)
("p" . image-dired-previous-line)
("i" . image-dired-forward-image)
("o" . image-dired-backward-image)))
(use-builtin wdired)
(use-package diredfl
:hook
('dired-mode-hook #'diredfl-mode)
:config
(set-face-attribute 'diredfl-dir-name nil :bold t))
easy-kill
Kill and Mark things easily in Emacs
(use-package easy-kill
:config
(global-set-key [remap kill-ring-save] 'easy-kill)
(global-set-key [remap mark-sexp] 'easy-mark))
EAT
[E]at is a [T]erminal [E]mulator.
(use-package eat
:ensure (:type git
:host codeberg
:repo "akib/emacs-eat"
:files ("*.el" ("term" "term/*.el") "*.texi"
"*.ti" ("terminfo/e" "terminfo/e/*")
("terminfo/65" "terminfo/65/*")
("integration" "integration/*")
(:exclude ".dir-locals.el" "*-tests.el")))
:commands (eat eat-eshell-mode)
;; :general
;; ("M-o t" 'eat)
;; ("M-o T" 'eat-other-window)
:config
(eat-compile-terminfo))
edit-indirect
(use-package edit-indirect)
eglot
After Emacs 29, Eglot comes baked into Emacs and is thus the definite LSP choice for any config that wishes to stick close to a vanilla emacs experience. Because Eglot comes with Emacs and is ready to go, all we have to do is config it to start automatically (or not) and then config any settings.
(use-builtin eglot
:after (corfu cape orderless)
:defer t
:hook ((js-ts-mode . eglot-ensure)
(typescript-ts-mode. eglot-ensure))
:init
(setq completion-category-overrides '((eglot (styles orderless))))
(advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)
:custom
(eglot-send-change-idle-time 0.1)
(eglot-autoshutdown t)
(eglot-extend-to-xref t)
(eglot-ignored-server-capabilities
'(:documentHighlightProvider
:documentFormattingProvider
:documentRangeFormattingProvider
:documentOnTypeFormattingProvider
:colorProvider))
(eglot-workspace-configuration
'(:typescript-language-server
(:plugins [
((:name "ts-lit-Plugin")
(:location "/Users/IPringle/.asdf/installs/nodejs/18.17.0/lib/node_modules/ts-lit-plugin/"))
((:name "typescript-lit-html-plugin")
(:location "/Users/IPringle/.asdf/installs/nodejs/18.17.0/lib/node_modules/typescript-lit-html-plugin/"))
])))
:config
(fset #'jsonrpc--log-event #'ignore)
(defun fv--orderless-dispatch-passthrough-first ()
"Return a function for orderless dispatch.
Lets all matches through for 1st component, unless it has
changed, in which case flex is used."
(let (last-table initial-pattern)
(lambda (pattern index _total)
(if-let (((eq index 0))
(table (caddr completion-in-region--data)))
(cond
((not (eq table last-table))
(setq last-table table
initial-pattern pattern)
"")
((and initial-pattern
(string-equal pattern initial-pattern))
"")
(t 'orderless-flex))))))
(defun fv--eglot-capf ()
(setf (alist-get 'styles
(alist-get 'eglot completion-category-defaults))
'(orderless))
(make-local-variable 'orderless-style-dispatchers)
(cl-pushnew (fv--orderless-dispatch-passthrough-first)
orderless-style-dispatchers)
(setq-local completion-at-point-functions
(cl-nsubst (cape-capf-buster #'eglot-completion-at-point)
'eglot-completion-at-point
completion-at-point-functions)))
(add-hook 'eglot-managed-mode-hook #'fv--eglot-capf)
(add-to-list 'eglot-server-programs
'(astro-ts-mode . ("astro-ls" "--stdio")))
(add-to-list 'eglot-server-programs
(cl-substitute-if
(cons
'(js-mode js-ts-mode
tsx-ts-mode typescript-ts-mode typescript-mode)
'("typescript-language-server" "--stdio" :initializationOptions
(:preferences
(
;; https://github.com/microsoft/TypeScript/blob/main/src/server/protocol.ts#L3410-L3539
:disableSuggestions :json-false ;; boolean
:quotePreference "double" ;; "auto" | "double" | "single"
:includeCompletionsForModuleExports t ;; boolean
:includeCompletionsForImportStatements t ;; boolean
:includeCompletionsWithSnippetText t ;; boolean
:includeCompletionsWithInsertText t ;; boolean
:includeAutomaticOptionalChainCompletions t ;; boolean
:includeCompletionsWithClassMemberSnippets t ;; boolean
:includeCompletionsWithObjectLiteralMethodSnippets t ;; boolean
:useLabelDetailsInCompletionEntries t ;; boolean
:allowIncompleteCompletions t ;; boolean
:importModuleSpecifierPreference "shortest" ;; "shortest" | "project-relative" | "relative" | "non-relative"
:importModuleSpecifierEnding "minimal" ;; "auto" | "minimal" | "index" | "js"
:allowTextChangesInNewFiles t ;; boolean
:providePrefixAndSuffixTextForRename t ;; boolean
:provideRefactorNotApplicableReason :json-false ;; boolean
:allowRenameOfImportPath t ;; boolean
:jsxAttributeCompletionStyle "auto" ;; "auto" | "braces" | "none"
:displayPartsForJSDoc t ;; boolean
:generateReturnInDocTemplate t ;; boolean
:includeInlayParameterNameHints "all" ;; "none" | "literals" | "all"
:includeInlayParameterNameHintsWhenArgumentMatchesName t ;; boolean
:includeInlayFunctionParameterTypeHints t ;; boolean,
:includeInlayVariableTypeHints t ;; boolean
:includeInlayVariableTypeHintsWhenTypeMatchesName t ;; boolean
:includeInlayPropertyDeclarationTypeHints t ;; boolean
:includeInlayFunctionLikeReturnTypeHints t ;; boolean
:includeInlayEnumMemberValueHints t ;; boolean
:disableLineTextInReferences :json-false ;; boolean
))))
(lambda (server-program)
(and
(listp (car server-program))
(member 'tsx-ts-mode (car server-program))))
eglot-server-programs))
(defun /typescript-ts-base-mode-hook-function ()
(add-hook 'before-save-hook #'/eglot-organize-imports nil t))
(add-hook 'typescript-ts-base-mode-hook #'/typescript-ts-base-mode-hook-function)
(defun /eglot-organize-imports ()
(interactive)
(if (derived-mode-p major-mode #'typescript-ts-base-mode)
(seq-do
(lambda (kind) (interactive)
(ignore-errors
(eglot-code-actions (buffer-end 0) (buffer-end 1) kind t)))
;; https://github.com/typescript-language-server/typescript-language-server#code-actions-on-save
(list
"source.addMissingImports.ts"
"source.fixAll.ts"
"source.addMissingImports.ts"
"source.removeUnusedImports.ts"))
(funcall-interactively #'eglot-code-action-organize-imports))))
eglot-tempel
This is a shim so eglot will use tempel instead of yas.
(use-package eglot-tempel
:after eglot
:ensure (eglot-tempel
:host github
:repo "fejfighter/eglot-tempel"))
eldoc-box
(use-package eldoc-box
:after eglot
:config
(add-hook 'eglot-managed-mode-hook #'eldoc-box-hover-mode t))
embark
https://github.com/oantolin/embark
(use-package embark
:ensure t
:bind
(("C-." . embark-act)
("M-." . embark-dwim)
("C-h B" . embark-bindings))
:custom
(prefix-help-command #'embark-prefix-help-command)
:hook ('eldoc-documentation-function #'embark-eldoc-first-target)
:config
(add-to-list 'display-buffer-alist
'("\\'\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
embark-consult
(use-package embark-consult
:after (embark consult)
:demand t ; only necessary if you have the hook below
;; auto-updating embark collect buffer
:hook
(embark-collect-mode . embark-consult-preview-minor-mode))
embrace.el
Add/Change/Delete pairs based on `expand-region’, similar to `evil-surround’.
I’ve looked at smartparens which is a reimplementation of electirc, plus a lot of movements for navigation, and the ability to add, change, or swap pairs. It’s a very featureful package, but it was overwhelming. I’ve used embrace before and it’s a lot like the vim package by tpope “surround”. I’m going to stick with this unless it’s just not enough.
(use-package embrace
:general
("C-c s" '(embrace-commander :wk "Embrace")
"C-c C-s a" '(embrace-add :wk "Add surround")
"C-c C-s d" '(embrace-delete :wk "Delete surround")
"C-c C-s c" '(embrace-change :wk "Change surround")))
emmet-mode
I’m unsure if I’ll keep this, but I want to test it out, it seems like it might have potential, especially given that between Astro and Lit I write a lot of vanilla HTML.
(use-package emmet-mode
:defer t
:config
(add-hook 'sgml-mode-hook 'emmet-mode)
(add-hook 'css-mode-hook 'emmet-mode))
This is a capf for emmet so that I can expand it with a completion at point.
(defun fv--emmet-expand ()
(let ((bounds (bounds-of-thing-at-point 'symbol)))
(list (car bounds) (cdr bounds)
(lambda (str pred action) (emmet-transform str))
:exclusive 'no)))
(add-to-list 'completion-at-point-functions #'fv--emmet-expand
)
esup
(use-package esup
:custom
(esup-depth 0))
evil-nerd-commenter
(use-package evil-nerd-commenter
:general
("M-;" 'evilnc-comment-or-uncomment-lines))
exec-path-from-shell
(use-package exec-path-from-shell
:init
(when (memq window-system '(mac ns x))
(exec-path-from-shell-initialize)))
expand-region
Emacs extension to increase selected region by semantic units.
(use-package expand-region
:commands expand-region
:general
("C-," 'er/expand-region)
:config
(add-to-list 'expand-region-exclude-text-mode-expansions 'org-mode)
(set-default 'er--show-expansion-message nil)
(setq expand-region-show-usage-message nil
expand-region-fast-keys-enabled nil)
(defvar expand-region-repeat-map
(let ((map (make-sparse-keymap)))
(define-key map "," #'er/expand-region)
(define-key map "." #'er/contract-region)
(dotimes (i 10)
(define-key map
(kbd (number-to-string i))
(lambda ()
(interactive)
(er/expand-region i)
(setq this-command 'er/expand-region))))
map))
(put 'er/expand-region 'repeat-map 'expand-region-repeat-map)
(put 'er/contract-region 'repeat-map 'expand-region-repeat-map)
(advice-add 'er--first-invocation
:override
(defun fv--er--first-invocation ()
"true if this is the first invocation of er/expand-region or er/contract-region"
(not (memq last-command
'(er/expand-region
er/contract-region
easy-kill-expand-region
easy-kill-contract-region))))))
forge
Forge allows you to work with Git forges, such as Gi thub and Gitlab, from the comfort of Magit and the rest of Emacs.
(use-package forge
:after magit)
general.el
general.el provides a more convenient method for binding keys in emacs.
(use-package general
:demand t
:config
(general-override-mode)
(general-auto-unbind-keys))
goggles
(use-package goggles
:hook ((prog-mode text-mode) . goggles-mode)
:config
(setq-default goggles-pulse t))
highlight-parenthes
(use-package highlight-parentheses
:demand t
:ensure t
:hook
((prog-mode . highlight-parentheses-mode)
(minibuffer-setup . highlight-parentheses-mode)))
helpful
Helpful is an alternative to the built-in Emacs help that provides much more contextual information.
(use-package helpful
:custom (elisp-refs-verbose nil)
:bind
(("M-g M-d" . 'helpful-at-point)
([remap describe-key] . helpful-key)
([remap describe-function] . helpful-callable)
([remap describe-command] . helpful-command)
([remap describe-variable] . helpful-variable)))
jinx
Jinx is a fast just-in-time spell-checker for Emacs.
(use-package jinx
:config
(global-set-key [remap ispell-word] 'jinx-correct)
(global-set-key (kbd "C-M-$") 'jinx-languages))
kind-icon
Completion kind text/icon prefix labelling for emacs in-region completion
(use-package kind-icon
:after corfu
:custom
(kind-icon-use-icons t)
(kind-icon-default-face 'corfu-default)
(kind-icon-blend-background nil)
(kind-icon-blend-frac 0.08)
:config
(add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))
orderless
magit
(use-package magit
:defer t
:bind (:map magit-file-section-map
("RET" . magit-diff-visit-file-other-window)
:map magit-hunk-section-map
("RET" . magit-diff-visit-file-other-window))
:config
(transient-bind-q-to-quit))
marginalia
(use-package marginalia
:bind
(:map minibuffer-local-map
("M-A" . 'marginalia-cycle))
:custom
(marginalia-max-relative-age 0)
(marginalia-align 'right)
:init
(marginalia-mode))
miniedit
(use-package miniedit
:ensure t
:init (miniedit-install))
mood-line
This is my mode-line. There are many like it, but this one is mine. My mode-line is my best friend. It is my life. I must master it as I must master my life. Without me my mode-line is useless. Without my mode-line, I am useless.
(use-package mood-line
:custom
(mood-line-glyph-alist mood-line-glyphs-fira-code)
:config
(mood-line-mode))
nov.el
(use-package nov
:mode (("\\.epub\\'" . nov-mode))
:hook ((nov-mode . view-mode)))
orderless
(use-package orderless
:demand t
:config
(defun +orderless--consult-suffix ()
"Regexp which matches the end of string with Consult tofu support."
(if (and (boundp 'consult--tofu-char) (boundp 'consult--tofu-range))
(format "[%c-%c]*$"
consult--tofu-char
(+ consult--tofu-char consult--tofu-range -1))
"$"))
(defun +orderless-consult-dispatch (word _index _total)
(cond
((string-suffix-p "$" word)
`(orderless-regexp . ,(concat (substring word 0 -1) (+orderless--consult-suffix))))
((and (or minibuffer-completing-file-name
(derived-mode-p 'eshell-mode))
(string-match-p "\\`\\.." word))
`(orderless-regexp . ,(concat "\\." (substring word 1) (+orderless--consult-suffix))))))
(orderless-define-completion-style +orderless-with-initialism
(orderless-matching-styles '(orderless-initialism orderless-literal orderless-regexp)))
(setq completion-styles '(orderless basic)
completion-category-defaults nil
completion-category-overrides '((file (styles partial-completion))
(command (styles +orderless-with-initialism))
(variable (styles +orderless-with-initialism))
(symbol (styles +orderless-with-initialism)))
orderless-component-separator #'orderless-escapable-split-on-space
orderless-style-dispatchers (list #'+orderless-consult-dispatch
#'orderless-affix-dispatch)))
org
Set (auto-fill-mode)
when org starts to automatically insert line breaks when a line gets too long.
(use-package org
:defer t
:hook
('org-mode-hook (function fv--hidden-block))
:mode
("\\.org\\'" . org-mode)
Config Preamble
I’m going to try something different here because I want to better document my org config.
:config
Defaults
Now to fix some defaults:
(setq org-directory (concat (getenv "HOME") "/org"))
(setq org-directory org-directory)
(setq deft-directory org-directory)
(setq org-blog-directory (concat org-directory "/blog"))
(setq org-journal-directory (concat org-directory "/journal"))
(setq +org-capture-inbox (concat org-directory "/life.org"))
(setq +org-capture-weekly (concat org-directory "/weekly.org"))
(setq org-use-property-inheritance t
org-log-done 'time
org-list-allow-alphabetical t
org-export-in-background t
org-fold-catch-invisible-edits 'smart
org-auto-align-tags nil
org-tags-column 0
org-special-ctrl-a/e t
org-insert-heading-respect-content t
org-hide-emphasis-markers t
org-pretty-entities t
org-ellipsis "…"
org-agenda-tags-column 0
org-agenda-block-separator ?─
org-agenda-time-grid '((daily today require-timed)
(800 1000 1200 1400 1600 1800 2000)
" ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄")
org-agenda-current-time-string "⭠ now ─────────────────────────────────────────────────")
This is needed for the time being because of an issue with Emacs 29 and Org.
(advice-remove 'org-display-inline-images 'org-display-user-inline-images)
And we need a few extra org-modules
(require 'org-id)
Auto Save
One minute before the start of the next hour, save all org buffers. I should consider making this happen more often…
(run-at-time "00:59" 3600 'org-save-all-org-buffers)
And then just used the baked in git sync feature:
(require 'org-attach-git)
Keywords
The idea here is the minimize states, which minimizes the time I have to think about the state task should be in. Ultimately tasks either need to be started (TODO), are finished (DONE), or cannot be worked on (HOLD). The idea of a “NEXT” is better off-loaded to some algorithm that can determine the next best task to work on based on the effort required to finish it, the priority of the task, and whether it’s a blocker for another task. This is how Taskwarrior does it, and think this is a great thing to embrace. The KILL state is here mostly because it’s a almost zero-effort call on whether or not a task is KILL’d (ie no longer needs to be moved to a DONE state) and KILL’d, or cancelled, task is different enough from a DONE task to merit it’s own face..
Appointments (APPT) are like TODOs that are in HOLD until a specific time and at the appointed time are immediately the active task, until completed, when they immediately are DONE. I mostly added this APPT state so that I can easily see when I have appointments and because, like KILL, it requires next to no energy to know if a task is an APPT — and in fact this face would likely almost only ever been created through a capture template anyway.
Finally we have the last three faces — I don’t want to use ‘state’ for them because they’re really not even tasks. MEET is for, wait for it, meetings — but also other similar types of events. The goal with MEET is to open the capture template, keep it open for the duration of the MEET-thing and then close it. The opening of the MEET-thing would log the start time and then the closing would log the close. Why do I have NOTE and IDEA? No clue, I should probably just keep one or the other, but I feel like there is enough difference between the two concepts to warrant both. An IDEA is something to explore in detail later, like a blog post or a topic to look into. A NOTE is some knowledge or inkling I want to retain and perhaps develop later into a grok. Time will tell if I keep both faces or condense them into one.
Lastly, I have some faces for my reading list. I was controlling this with a file variable but it wasn’t working right and it doesn’t hurt anything to put this in here.
(setq org-todo-keywords
'(
(sequence "TODO(t)" "|" "DONE(d!/!)")
(sequence "HOLD(h@/!)" "|" "KILL(k@/!)")
(sequence "APPT(a)" "|" "DONE(d!)")
(sequence "MEET(m)" "IDEA(i)" "NOTE(n)")
(sequence "READ(r)" "READING(R)" "|" "DONE(d@/!)")))
(setq org-todo-keyword-faces
'(
;; I like the default TODO color...
;; ("TODO" :foreground "red" :weight bold)
("DONE" :foreground "forest green" :weight bold)
("HOLD" :foreground "magenta" :weight bold)
("KILL" :foreground "forest green" :weight bold)
("MEET" :foreground "forest green" :weight bold)
("APPT" :foreground "magenta" :weight bold)
("IDEA" :foreground "gold" :weight bold)
("NOTE" :foreground "blue" :weight bold)
("READ" :foreground "red" :weight bold)
("READING" :foreground "magenta" :weight bold)))
(setq org-use-fast-todo-selection t)
(setq org-treat-S-cursor-todo-selection-as-state-change nil)
(setq org-todo-state-tags-triggers
'(("KILL" ("KILL" . t) ("ARCHIVE" . t))
("HOLD" ("HOLD" . t) ("ARCHIVE"))
(done ("HOLD") ("ARCHIVE". t))
("TODO" ("HOLD") ("KILL") ("ARCHIVE"))
("DONE" ("HOLD") ("KILL") ("ARCHIVE" . t))
("READ" ("ARCHIVE"))
("READING" ("ARCHIVE"))))
Tags
(setq org-tag-alist '(
;; Related to the computer, but not specific to work
("BLOG" . ?b)
("ORG" . ?o)
;; Related to work
("@work" . ?w)
("INCIDENT" . ?i)
("REQUEST" . ?r)
;; Related to IRL
("@home" . ?h)
("@farm" . ?f)
;; Related to traveling
("ERRAND" . ?e)
("@Doniphan" . ?D)
("@PoplarBluff" . ?P)
("@WestPlain" . ?W)
("@Thayer" . ?T)))
I use the :tangleno
tag in my literate emacs config to switch blocks on and off. To make this tag easier to see I’m adding it to the list of org-tag-faces
.
(add-to-list 'org-tag-faces
'("tangleno" . (:foreground "white" :background "red")))
Capture Templates
I’m trying to keep everything to just one file. Or, at least most of everything. So we file everything into the “Inbox” heading, which is the holding tank for almost all my captures so I can later go through them and evaluate if they’re really valuable or not. Capturing is for the purpose of getting all my ideas, tasks, etc. out of my head and somewhere less ephemeral as quickly as possible. We can review the merit of the things that are captures later during a reivew period.
(setq org-capture-templates
'(("t" "Task" entry (file+headline "~/org/life.org" "Inbox")
"* TODO %? %(org-set-tags-command) \nCREATED: %U\n" :clock-in t :clock-resume t)
("T" "Task @ cursor" entry (file+headline "~/org/life.org" "Inbox")
"* TODO %? %(org-set-tags-command) \nCREATED: %U\n%l\n" :clock-in t :clock-resume t)
("i" "Ideas" entry (file+headline "~/org/life.org" "Inbox")
"* IDEA %?\nCREATED: %U\n" :clock-in t :clock-resume t)
("b" "Blog" plain (function fv--create-new-blog-buffer) "#+filetags: %^{filetags}\n\n%?")
("n" "Notes" entry (file+headline "~/org/life.org" "Inbox")
"* NOTE %?\nCREATED: %U\n%l\n" :clock-in t :clock-resume t)
("a" "Appointment" entry (file+headline "~/org/life.org" "Inbox")
"* APPT %? %^gAPPOINTMENT: \nSCHEDULED: %^T\nCREATED: %U\n" :clock-in t :clock-resume t)
("m" "Meeting" entry (file+headline "~/org/life.org" "Meeting")
"* MEET with %? :MEETING:\nCREATED: %U\n" :clock-in t :clock-resume t)
("w" "Weekly Plan" entry (file+olp+datetree "~/org/weekly.org")
"* Goals\n* Changes\n* PTO\n* Notes\n" :clock-in t :clock-resume t :tree-type week)
))
Because we are clocking all captures, we could easily end up with a 0:00 clock, which we want to delete, but than that’d end up with an empty :LOGBOOK:
and so we should delete those since they’re ugly and pointless.
(defun fv--remove-empty-drawer-on-clock-out ()
(interactive)
(save-excursion
(beginning-of-line 0)
(org-remove-empty-drawer-at (point))))
(add-hook 'org-clock-out-hook 'fv--remove-empty-drawer-on-clock-out 'append)
Agenda
(require 'org-agenda)
(setq org-agenda-files '("~/org/life.org")
;; org-agenda-files (apply 'append
;; (mapcar
;; (lambda (directory)
;; (directory-files-recursively
;; directory org-agenda-file-regexp))
;; (list org-directory)))
org-agenda-start-with-log-mode t)
Clocking
;;
;; Resume clocking task when emacs is restarted
(org-clock-persistence-insinuate)
;;
;; Show lot of clocking history so it's easy to pick items off the C-F11 list
(setq org-clock-history-length 23)
;; Resume clocking task on clock-in if the clock is open
(setq org-clock-in-resume t)
;; Separate drawers for clocking and logs
(setq org-drawers (quote ("PROPERTIES" "LOGBOOK")))
;; Save clock data and state changes and notes in the LOGBOOK drawer
(setq org-clock-into-drawer t)
;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration
(setq org-clock-out-remove-zero-time-clocks t)
;; Clock out when moving task to a done state
(setq org-clock-out-when-done t)
;; Save the running clock and all clock history when exiting Emacs, load it on startup
(setq org-clock-persist t)
;; Do not prompt to resume an active clock
(setq org-clock-persist-query-resume nil)
;; Enable auto clock resolution for finding open clocks
(setq org-clock-auto-clock-resolution (quote when-no-clock-is-running))
;; Include current clocking task in clock reports
(setq org-clock-report-include-clocking-task t)
Clocking functions stolen from Norang
(setq bh/keep-clock-running nil)
(defun bh/find-project-task ()
"Move point to the parent (project) task if any"
(save-restriction
(widen)
(let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
(while (org-up-heading-safe)
(when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
(setq parent-task (point))))
(goto-char parent-task)
parent-task)))
(defun bh/punch-in (arg)
"Start continuous clocking and set the default task to the
selected task. If no task is selected set the Organization task
as the default task."
(interactive "p")
(setq bh/keep-clock-running t)
(if (equal major-mode 'org-agenda-mode)
;;
;; We're in the agenda
;;
(let* ((marker (org-get-at-bol 'org-hd-marker))
(tags (org-with-point-at marker (org-get-tags-at))))
(if (and (eq arg 4) tags)
(org-agenda-clock-in '(16))
(bh/clock-in-organization-task-as-default)))
;;
;; We are not in the agenda
;;
(save-restriction
(widen)
; Find the tags on the current task
(if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4))
(org-clock-in '(16))
(bh/clock-in-organization-task-as-default)))))
(defun bh/punch-out ()
(interactive)
(setq bh/keep-clock-running nil)
(when (org-clock-is-active)
(org-clock-out))
(org-agenda-remove-restriction-lock))
(defun bh/clock-in-default-task ()
(save-excursion
(org-with-point-at org-clock-default-task
(org-clock-in))))
(defun bh/clock-in-parent-task ()
"Move point to the parent (project) task if any and clock in"
(let ((parent-task))
(save-excursion
(save-restriction
(widen)
(while (and (not parent-task) (org-up-heading-safe))
(when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
(setq parent-task (point))))
(if parent-task
(org-with-point-at parent-task
(org-clock-in))
(when bh/keep-clock-running
(bh/clock-in-default-task)))))))
(defvar bh/organization-task-id "0a6abfc7-3d86-4a11-8ed4-85154df397f8")
(defun bh/clock-in-organization-task-as-default ()
(interactive)
(org-with-point-at (org-id-find bh/organization-task-id 'marker)
(org-clock-in '(16))))
(defun bh/clock-out-maybe ()
(when (and bh/keep-clock-running
(not org-clock-clocking-in)
(marker-buffer org-clock-default-task)
(not org-clock-resolving-clocks-due-to-idleness))
(bh/clock-in-parent-task)))
(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append)
(require 'org-id)
(defun bh/clock-in-task-by-id (id)
"Clock in a task by id"
(org-with-point-at (org-id-find id 'marker)
(org-clock-in nil)))
(defun bh/clock-in-last-task (arg)
"Clock in the interrupted task if there is one
Skip the default task and get the next one.
A prefix arg forces clock in of the default task."
(interactive "p")
(let ((clock-in-to-task
(cond
((eq arg 4) org-clock-default-task)
((and (org-clock-is-active)
(equal org-clock-default-task (cadr org-clock-history)))
(caddr org-clock-history))
((org-clock-is-active) (cadr org-clock-history))
((equal org-clock-default-task (car org-clock-history)) (cadr org-clock-history))
(t (car org-clock-history)))))
(widen)
(org-with-point-at clock-in-to-task
(org-clock-in nil))))
Utility functions stolen fron Norang to help with the clocking functions stolen from Norang
(defun bh/is-project-p ()
"Any task with a todo keyword subtask"
(save-restriction
(widen)
(let ((has-subtask)
(subtree-end (save-excursion (org-end-of-subtree t)))
(is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
(save-excursion
(forward-line 1)
(while (and (not has-subtask)
(< (point) subtree-end)
(re-search-forward "^\*+ " subtree-end t))
(when (member (org-get-todo-state) org-todo-keywords-1)
(setq has-subtask t))))
(and is-a-task has-subtask))))
(defun bh/is-project-subtree-p ()
"Any task with a todo keyword that is in a project subtree.
Callers of this function already widen the buffer view."
(let ((task (save-excursion (org-back-to-heading 'invisible-ok)
(point))))
(save-excursion
(bh/find-project-task)
(if (equal (point) task)
nil
t))))
(defun bh/is-task-p ()
"Any task with a todo keyword and no subtask"
(save-restriction
(widen)
(let ((has-subtask)
(subtree-end (save-excursion (org-end-of-subtree t)))
(is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
(save-excursion
(forward-line 1)
(while (and (not has-subtask)
(< (point) subtree-end)
(re-search-forward "^\*+ " subtree-end t))
(when (member (org-get-todo-state) org-todo-keywords-1)
(setq has-subtask t))))
(and is-a-task (not has-subtask)))))
(defun bh/is-subproject-p ()
"Any task which is a subtask of another project"
(let ((is-subproject)
(is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
(save-excursion
(while (and (not is-subproject) (org-up-heading-safe))
(when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
(setq is-subproject t))))
(and is-a-task is-subproject)))
(defun bh/list-sublevels-for-projects-indented ()
"Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
This is normally used by skipping functions where this variable is already local to the agenda."
(if (marker-buffer org-agenda-restrict-begin)
(setq org-tags-match-list-sublevels 'indented)
(setq org-tags-match-list-sublevels nil))
nil)
(defun bh/list-sublevels-for-projects ()
"Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
This is normally used by skipping functions where this variable is already local to the agenda."
(if (marker-buffer org-agenda-restrict-begin)
(setq org-tags-match-list-sublevels t)
(setq org-tags-match-list-sublevels nil))
nil)
(defvar bh/hide-scheduled-and-waiting-next-tasks t)
(defun bh/toggle-next-task-display ()
(interactive)
(setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks))
(when (equal major-mode 'org-agenda-mode)
(org-agenda-redo))
(message "%s WAITING and SCHEDULED NEXT Tasks" (if bh/hide-scheduled-and-waiting-next-tasks "Hide" "Show")))
(defun bh/skip-stuck-projects ()
"Skip trees that are not stuck projects"
(save-restriction
(widen)
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(if (bh/is-project-p)
(let* ((subtree-end (save-excursion (org-end-of-subtree t)))
(has-next ))
(save-excursion
(forward-line 1)
(while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
(unless (member "WAITING" (org-get-tags-at))
(setq has-next t))))
(if has-next
nil
next-headline)) ; a stuck project, has subtasks but no next task
nil))))
(defun bh/skip-non-stuck-projects ()
"Skip trees that are not stuck projects"
;; (bh/list-sublevels-for-projects-indented)
(save-restriction
(widen)
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(if (bh/is-project-p)
(let* ((subtree-end (save-excursion (org-end-of-subtree t)))
(has-next ))
(save-excursion
(forward-line 1)
(while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
(unless (member "WAITING" (org-get-tags-at))
(setq has-next t))))
(if has-next
next-headline
nil)) ; a stuck project, has subtasks but no next task
next-headline))))
(defun bh/skip-non-projects ()
"Skip trees that are not projects"
;; (bh/list-sublevels-for-projects-indented)
(if (save-excursion (bh/skip-non-stuck-projects))
(save-restriction
(widen)
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(cond
((bh/is-project-p)
nil)
((and (bh/is-project-subtree-p) (not (bh/is-task-p)))
nil)
(t
subtree-end))))
(save-excursion (org-end-of-subtree t))))
(defun bh/skip-non-tasks ()
"Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
(save-restriction
(widen)
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(cond
((bh/is-task-p)
nil)
(t
next-headline)))))
(defun bh/skip-project-trees-and-habits ()
"Skip trees that are projects"
(save-restriction
(widen)
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(cond
((bh/is-project-p)
subtree-end)
((org-is-habit-p)
subtree-end)
(t
nil)))))
(defun bh/skip-projects-and-habits-and-single-tasks ()
"Skip trees that are projects, tasks that are habits, single non-project tasks"
(save-restriction
(widen)
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(cond
((org-is-habit-p)
next-headline)
((and bh/hide-scheduled-and-waiting-next-tasks
(member "WAITING" (org-get-tags-at)))
next-headline)
((bh/is-project-p)
next-headline)
((and (bh/is-task-p) (not (bh/is-project-subtree-p)))
next-headline)
(t
nil)))))
(defun bh/skip-project-tasks-maybe ()
"Show tasks related to the current restriction.
When restricted to a project, skip project and sub project tasks, habits, NEXT tasks, and loose tasks.
When not restricted, skip project and sub-project tasks, habits, and project related tasks."
(save-restriction
(widen)
(let* ((subtree-end (save-excursion (org-end-of-subtree t)))
(next-headline (save-excursion (or (outline-next-heading) (point-max))))
(limit-to-project (marker-buffer org-agenda-restrict-begin)))
(cond
((bh/is-project-p)
next-headline)
((org-is-habit-p)
subtree-end)
((and (not limit-to-project)
(bh/is-project-subtree-p))
subtree-end)
((and limit-to-project
(bh/is-project-subtree-p)
(member (org-get-todo-state) (list "NEXT")))
subtree-end)
(t
nil)))))
(defun bh/skip-project-tasks ()
"Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
(save-restriction
(widen)
(let* ((subtree-end (save-excursion (org-end-of-subtree t))))
(cond
((bh/is-project-p)
subtree-end)
((org-is-habit-p)
subtree-end)
((bh/is-project-subtree-p)
subtree-end)
(t
nil)))))
(defun bh/skip-non-project-tasks ()
"Show project tasks.
Skip project and sub-project tasks, habits, and loose non-project tasks."
(save-restriction
(widen)
(let* ((subtree-end (save-excursion (org-end-of-subtree t)))
(next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(cond
((bh/is-project-p)
next-headline)
((org-is-habit-p)
subtree-end)
((and (bh/is-project-subtree-p)
(member (org-get-todo-state) (list "NEXT")))
subtree-end)
((not (bh/is-project-subtree-p))
subtree-end)
(t
nil)))))
(defun bh/skip-projects-and-habits ()
"Skip trees that are projects and tasks that are habits"
(save-restriction
(widen)
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(cond
((bh/is-project-p)
subtree-end)
((org-is-habit-p)
subtree-end)
(t
nil)))))
(defun bh/skip-non-subprojects ()
"Skip trees that are not projects"
(let ((next-headline (save-excursion (outline-next-heading))))
(if (bh/is-subproject-p)
nil
next-headline)))
Additional clock functions
This will toggle between a “break” task and the previously clocked task.
(defvar fv--previous-clocked-task-id "")
(defvar fv--break-task-id "855c4e50-a1ff-4ef8-8615-cd593acdbf4a")
(defun fv--clock-in-break-task (_)
"Clock in the break-task-id task"
(interactive "p")
(bh/clock-in-task-by-id fv--break-task-id))
(defun fv--toggle-clock-on-break-task (_)
"Toggle the break-task-id task"
(interactive "p")
(bh/clock-in-task-by-id fv--break-task-id))
Keybindings for clock related functions
(map! :map org-mode-map
:after org
:localleader
(:prefix "c"
:desc "Clock in previous task" "m" #'bh/clock-in-last-task
:desc "Punch-In" "p" #'bh/punch-in
:desc "Punch-Out" "P" #'bh/punch-out
:desc "Clock in break" "b" #'0x44/clock-in-break-task))
Keybindings
:general
("C-x c" 'org-capture
"C-c l" 'org-store-link
"C-c a" 'org-agenda)
Config Postamble
This needs to be at the end of the config to close the use-package
. org config.
)
Org Functions
These are some functions, mostly/all autoloads, that work with org in some way, shape, or form.
Tangle Into Dir
This is a helper function to make tangling a bit less verbose. This function can be called inside of a block header that uses the tangle
command. If the root of the document has a header for tangle-dir
the block’s output will be prepended with that path. This lets you specify the output for all tangle blocks in a document and then only change the name of a file in each block.
(defun fv--org-tangle-into-dir (sub-path)
"Expand the SUB-PATH into the directory given by the tangle-dir
property if that property exists, else use the
`default-directory'."
(expand-file-name sub-path
(or
(org-entry-get (point) "tangle-dir" 'inherit)
(default-directory))))
Blog Template
This function will create a new orgfile for a blog post. This can be called alone, but it also works as the function argument to an org-capture template.
;;(require 'org-capture)
(defun fv--create-new-blog-buffer ()
"Created a new blog from the specified template in a new buffer"
(interactive)
(let* ((title (read-from-minibuffer "Post Title: "))
($timestamp (format-time-string "<%Y-%m-%d %a %H:%M>" ))
(fname (concat org-blog-directory "/" (fv--slugify title) ".org")))
(set-buffer (org-capture-target-buffer fname))
(insert (format
":PROPERTIES:\n:AUTHOR: %s\n:CREATED: %s\n:MODIFIED: %s\n:TYPE: blog\n:END:\n#+title: %s\n"
user-full-name $timestamp $timestamp title))
(goto-char (point-max))))
This is the “slugify” function that the blog template uses.
(require 'ffap)
(defun fv--slugify (str)
"Convert string STR to a `slug' and return that string."
(let* (;; All lower-case
(str (downcase str))
;; Remove "<FOO>..</FOO>" HTML tags if present.
(str (replace-regexp-in-string "<\\(?1:[a-z]+\\)[^>]*>.*</\\1>" "" str))
;; Remove URLs if present in the string. The ")" in the
;; below regexp is the closing parenthesis of a Markdown
;; link: [Desc](Link).
(str (replace-regexp-in-string (concat "\\](" ffap-url-regexp "[^)]+)") "]" str))
;; Replace "&" with " and ", "." with " dot ", "+" with
;; " plus ".
(str (replace-regexp-in-string
"&" " and "
(replace-regexp-in-string
"\\." " dot "
(replace-regexp-in-string
"\\+" " plus " str))))
;; Replace all characters except alphabets, numbers and
;; parentheses with spaces.
(str (replace-regexp-in-string "[^[:alnum:]()]" " " str))
;; Remove leading and trailing whitespace.
(str (replace-regexp-in-string "\\(^[[:space:]]*\\|[[:space:]]*$\\)" "" str))
;; Replace 2 or more spaces with a single space.
(str (replace-regexp-in-string "[[:space:]]\\{2,\\}" " " str))
;; Replace parentheses with double-hyphens.
(str (replace-regexp-in-string "\\s-*([[:space:]]*\\([^)]+?\\)[[:space:]]*)\\s-*" " -\\1- " str))
;; Remove any remaining parentheses character.
(str (replace-regexp-in-string "[()]" "" str))
;; Replace spaces with hyphens.
(str (replace-regexp-in-string " " "-" str))
;; Remove leading and trailing hyphens.
(str (replace-regexp-in-string "\\(^[-]*\\|[-]*$\\)" "" str)))
str))
Hidden Blocks
This function will start blocks taged as :hidden
folded. It’s handy for really long source blocks, or blocks that are important to have but not important to look at. This came from here but it’s not working right at the moment…
;;;###autoload
(defun fv--hidden-block ()
"Fold some blocks in the current buffer."
(interactive)
(org-show-block-all)
(org-block-map
(lambda ()
(let ((case-fold-search t))
(when (and
(save-excursion
(beginning-of-line 1)
(looking-at org-block-regexp))
(cl-assoc
':hidden
(cl-third
(org-babel-get-src-block-info))))
(org-hide-block-toggle)))))
(org-mode))
org-appear
Toggle visibility of hidden Org mode element parts upon entering and leaving an element.
(use-package org-appear
:after org
:hook (org-mode . org-appear-mode)
:config
(setq org-appear-autoemphasis t
org-appear-autolinks t
org-appear-autosubmarks t))
org-contrib
(use-package org-contrib
:ensure (org-contrib :repo "https://git.sr.ht/~bzg/org-contrib"))
org-journal
(use-package org-journal
:after org
:init
(setq org-journal-prefix-key "C-c j")
:config
(setq org-journal-dir org-journal-directory
org-journal-file-type 'daily))
ob-mermaid
(use-package ob-mermaid
:after org
:init
(setq ob-mermaid-cli-path "~/.asdf/shims/mmdc")
(org-babel-do-load-languages
'org-babel-load-languages
'((mermaid .t))))
persistent-scratch
Preserve the scratch buffer across Emacs sessions.
(use-package persistent-scratch
:demand
:config
(persistent-scratch-setup-default))
perspective.el
(use-package perspective
:after (consult)
:general
;; ("C-x C-b" 'persp-counsel-switch-buffer)
;; ("C-x b" 'persp-switch-to-buffer*)
("C-x k" 'persp-kill-buffer*)
:custom
(persp-mode-prefix-key (kbd "C-z"))
:init
(persp-mode)
(consult-customize consult--source-buffer :hidden t :default nil)
(add-to-list 'consult-buffer-sources persp-consult-source))
rainbow-delimiters
(use-package rainbow-delimiters
:demand t
:ensure t
:hook
((prog-mode . rainbow-delimiters-mode)
(minibuffer-setup . rainbow-delimiters-mode)))
repeat-mode
(use-builtin repeat-mode
:general ("s-r repeat")
:hook (after-init . repeat-mode)
:config
(setq repeat-keep-prefix t
repeat-echo-function #'repeat-echo-mode-line
repeat-echo-mode-line-string (propertize "[R]" 'face 'mode-line-emphasis)))
request.el
Might switch this out to plz, but it’s what I’ve been using so… https://github.com/tkf/emacs-request
(use-package request)
seq.el
This ensures we’re using the latest version of seq and not the one bundled with Emacs. This is important as some packages, like magit, want the dev version of this built-in.
(defun +elpaca-unload-seq (e)
(and (featurep 'seq) (unload-feature 'seq t))
(elpaca--continue-build e))
;; You could embed this code directly in the reicpe, I just abstracted it into a function.
(defun +elpaca-seq-build-steps ()
(append (butlast (if (file-exists-p (expand-file-name "seq" elpaca-builds-directory))
elpaca--pre-built-steps elpaca-build-steps))
(list '+elpaca-unload-seq 'elpaca--activate-package)))
(use-package seq :ensure `(seq :build ,(+elpaca-seq-build-steps)))
sly
(use-package sly
:defer
:init
(setq inferior-lisp-program "sbcl"))
sly-overlay
(use-package sly-overlay
:general
(:keymaps 'sly-mode-map
"C-M-x" 'sly-overlay-eval-defun
))
sly-quicklisp
(use-package sly-quicklisp)
sly-asdf
(use-package sly-asdf
:after sly
:ensure
:init
(add-to-list 'sly-contribs 'sly-asdf 'append))
sly-macrosetp
(use-package sly-macrostep)
sly-reply-ansi-color
(use-package sly-repl-ansi-color
:init
(add-to-list 'sly-contribs 'sly-repl-ansi-color 'append))
tempel
It’s a better snippet system, I’ve been told.
(use-package tempel
:init
(defun tempel-setup-capf ()
(setq-local completion-at-point-functions
(cons #'tempel-expand
completion-at-point-functions)))
(add-hook 'conf-mode-hook 'tempel-setup-capf)
(add-hook 'prog-mode-hook 'tempel-setup-capf)
(add-hook 'prog-mode-hook 'tempel-abbrev-mode)
(add-hook 'text-mode-hook 'tempel-setup-capf)
(global-tempel-abbrev-mode))
(use-package tempel-collection
:after tempel)
These are my tempel templates.
;; ~/.config/fv/templates
transient
This ensures we’re using the latest version of seq and not the one bundled with Emacs. This is important as some packages, like magit, want the dev version of this built-in.
(defun +elpaca-unload-transient (e)
(and (featurep 'transient) (unload-feature 'transient t))
(elpaca--continue-build e))
;; You could embed this code directly in the reicpe, I just abstracted it into a function.
(defun +elpaca-transient-build-steps ()
(append (butlast (if (file-exists-p (expand-file-name "transient" elpaca-builds-directory))
elpaca--pre-built-steps elpaca-build-steps))
(list '+elpaca-unload-transient 'elpaca--activate-package)))
(use-package seq :ensure `(transient :build ,(+elpaca-transient-build-steps)))
treesit
(use-feature treesit
:init
(add-to-list 'auto-mode-alist '("\\.tsx?$" . typescript-ts-mode))
(add-to-list 'auto-mode-alist '("\\.jsx?$" . js-ts-mode)))
treesit-auto
This gets us free configs for most/all TS langs that come natively with treesit and also adds in the hooks to trigger TS modes.
(use-package treesit-auto
:after (treesit)
:custom
(treesit-auto-install 'prompt)
:config
(treesit-auto-add-to-auto-mode-alist 'all)
(global-treesit-auto-mode))
Grammars
I’m going to keep treesit specific packages in here, if those packages are for new grammars. Other packages that use treesit but are not grammars will live elsewhere, like Combobulate.
Astro
(use-package astro-ts-mode
:after (treesit-auto)
:config
(setq astro-tsauto-config
(make-treesit-auto-recipe
:lang 'astro
:ts-mode 'astro-ts-mode
:url "https://github.com/virchau13/tree-sitter-astro"
:revision "master"
:source-dir "src"
:ext "\\.astro\\'"))
(add-to-list 'treesit-auto-recipe-list astro-tsauto-config))
undo-fu
Simple, stable linear undo with redo for Emacs.
(use-package undo-fu
:config
(global-set-key [remap undo] 'undo-fu-only-undo)
(global-set-key [remap undo-redo] 'undo-fu-only-redo)
(global-set-key (kbd "C-S-/") 'undo-fu-only-redo))
vertico
(use-package vertico
:ensure (vertico :files (:defaults "extensions/*"))
:custom
(vertico-count 13)
(vertico-resize t)
(vertico-cycle t)
(vertico-multiform-categories '((embark-keybinding grid)))
:bind (:map vertico-map
("RET" . vertico-directory-enter)
("DEL" . vertico-directory-delete-char)
("S-DEL" . vertico-directory-delete-word))
:hook
('rfn-eshadow-update-overlay #'vertico-directory-tidy)
:init
(vertico-mode))
which-key
which-key is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command ( a prefix) in a popup.
(use-package which-key
:demand t
:config
(which-key-mode)
:custom
(which-key-side-window-location 'bottom)
(which-key-sort-order 'which-key-key-order-alpha)
;;(which-key-idle-delay 0.1/)
:diminish which-key-mode)
wiki-summary.el
Grab the Wikipedia summary for a term and display the result in a buffer.
(use-package wiki-summary
:bind
("M-g w" . 'wiki-summary))
Test Packages
These are packages I’m testing and might or might not keep.
flycheck
(use-package flycheck
:ensure t
:init (global-flycheck-mode))
lsp-mode
(use-package lsp-mode
:init
(setq lsp-keymap-prefix "C-c C-c")
:custom
(lsp-log-io nil) ; ensure this is off when not debugging
(lsp-eldoc-render-all t)
:init
(defun fv--lsp-mode-capf-setup ()
(setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
'(flex)))
:hook ((lsp-mode . fv--lsp-mode-capf-setup)
(lsp-mode . lsp-enable-which-key-integration))
:commands (lsp lsp-deferred))
(use-package lsp-ui
:commands lsp-ui-mode)
treesitter
(use-package tree-sitter
:hook (tree-sitter-after-on-hook . tree-sitter-hl-mode)
:init (global-tree-sitter-mode))
(use-package tree-sitter-langs)
org-clock-convenience
https://github.com/dfeich/org-clock-convenience
(use-package org-clock-convenience
:ensure t
:after org
:general
('org-agenda-mode-map
("s-p" 'org-clock-convenience-timestamp-up)
("s-n" 'org-clock-convenience-timestamp-down)
("o" 'org-clock-convenience-fill-gap)
("e" 'org-clock-convenience-fill-gap-both)))
lispy
(use-package lispy
:init
(defun conditionally-enable-lispy ()
(when (eq this-command 'eval-expression)
(lispy-mode 1)))
:hook
((lisp-mode . lispy-mode)
(minibuffer-setup . conditionally-enable-lispy)))
puni
(use-package puni
:hook ((lisp-mode . puni-mode))
:general
('puni-mode-map
("(" 'puni-wrap-round)
("[" 'puni-wrap-square)
("<" 'puni-wrap-angle)
("{" 'puni-wrap-curly)
;; Sexp nav
("C-M-f" 'puni-forward-sexp-or-up-list)
("C-M-b" 'puni-backward-sexp-or-up-list)
("C-M-a" 'puni-beginning-of-sexp)
("C-M-e" 'puni-end-of-sexp)
("C-M-t" 'puni-transpose)
;; Barf
("s-f" 'puni-barf-forward)
("M-]" 'puni-barf-forward)
("s-b" 'puni-barf-backward)
("M-[" 'puni-barf--backward)
;;Slurp
("M-s-f" 'puni-slurp-forward)
("C-)" 'puni-slurp-forward)
("M-s-b" 'puni-slurp-backward)
("C-(" 'puni-slurp-backward)
;; Other
("M-p" 'puni-raise)
("M-n" 'puni-splice)
("M-s-p" 'puni-splice-killing-forward)
("M-s-n" 'puni-splice-killing-backward)))
paredit
(use-package paredit
:hook (lisp-mode enable-paredit-mode))
bufler.el
(use-package bufler
:init
(bufler-mode)
:general
("C-x b" 'bufler-switch-buffer)
)
combobulate
Combobulate is a package that adds structured editing and movement to a wide range of programming languages.
(use-package combobulate
:ensure (:host github :repo "mickeynp/combobulate")
:custom
(setq combobulate-key-prefix "C-c o")
:hook ((python-ts-mode . combobulate-mode)
(js-ts-mode . combobulate-mode)
(jsx-ts-mode . combobulate-mode)
(typescript-ts-mode . combobulate-mode)
(tsx-ts-mode . combobulate-mode)
(css-ts-mode . combobulate-mode)
(yaml-ts-mode . combobulate-mode)
;; (html-ts-mode . combobulate-mode)
(json-ts-mode . combobulate-mode))
)
hydra
This is an experiment using hydra, not sure if I’ll keep it around though.
key | function | column |
---|---|---|
< | lispy-barf | |
A | lispy-beginning-of-defun | |
j | lispy-down | |
Z | lispy-edebug-stop | |
B | lispy-ediff-regions | |
G | lispy-goto-local | |
h | lispy-left | |
N | lispy-narrow | |
y | lispy-occur | |
o | lispy-other-mode | |
J | lispy-outline-next | |
K | lispy-outline-prev | |
P | lispy-paste | |
l | lispy-right | |
I | lispy-shifttab | |
> | lispy-slurp | |
SPC | lispy-space | |
xB | lispy-store-region-and-buffer | |
u | lispy-undo | |
k | lispy-up | |
v | lispy-view | |
V | lispy-visit | |
W | lispy-widen | |
D | pop-tag-mark | |
x | see | |
L | unbound | |
U | unbound | |
X | unbound | |
Y | unbound | |
H | lispy-ace-symbol-replace | Edit |
c | lispy-clone | Edit |
C | lispy-convolute | Edit |
n | lispy-new-copy | Edit |
O | lispy-oneline | Edit |
r | lispy-raise | Edit |
R | lispy-raise-some | Edit |
\ | lispy-splice | Edit |
S | lispy-stringify | Edit |
i | lispy-tab | Edit |
xj | lispy-debug-step-in | Eval |
xe | lispy-edebug | Eval |
xT | lispy-ert | Eval |
e | lispy-eval | Eval |
E | lispy-eval-and-insert | Eval |
xr | lispy-eval-and-replace | Eval |
p | lispy-eval-other-window | Eval |
q | lispy-ace-paren | Move |
z | lispy-knight | Move |
s | lispy-move-down | Move |
w | lispy-move-up | Move |
t | lispy-teleport | Move |
Q | lispy-ace-char | Nav |
– | lispy-ace-subword | Nav |
a | lispy-ace-symbol | Nav |
b | lispy-back | Nav |
d | lispy-different | Nav |
f | lispy-flow | Nav |
F | lispy-follow | Nav |
g | lispy-goto | Nav |
xb | lispy-bind-variable | Refactor |
xf | lispy-flatten | Refactor |
xc | lispy-to-cond | Refactor |
xd | lispy-to-defun | Refactor |
xi | lispy-to-ifs | Refactor |
xl | lispy-to-lambda | Refactor |
xu | lispy-unbind-variable | Refactor |
M | lispy-multiline | Other |
xh | lispy-describe | Other |
m | lispy-mark-list | Other |
(use-package hydra
:ensure t
:config
(eval
(append
'(defhydra my/lispy-cheat-sheet (:hint nil :foreign-keys run)
("<f10>" nil :exit t))
(cl-loop for x in bindings
unless (string= "" (elt x 2))
collect
(list (car x)
(intern (elt x 1))
(when (string-match "lispy-\\(?:eval-\\)?\\(.+\\)"
(elt x 1))
(match-string 1 (elt x 1)))
:column
(elt x 2)))))
(with-eval-after-load "lispy"
(define-key lispy-mode-map (kbd "<f10>") 'my/lispy-cheat-sheet/body)))
meow-mode
(use-package meow
:demand t
:init
<<meow-init>>
:config
(meow-setup)
(meow-global-mode))
Init
(defun meow-setup ()
;; -------------------- ;;
;; THING TABLE ;;
;; -------------------- ;;
(meow-thing-register 'angle
'(pair ("<") (">"))
'(pair ("<") (">")))
(setq meow-char-thing-table
'((?f . round)
(?d . square)
(?s . curly)
(?a . angle)
(?r . string)
(?v . paragraph)
(?c . line)
(?x . buffer)))
;; -------------------- ;;
;; MAPPINGS ;;
;; -------------------- ;;
(meow-define-keys 'normal
; expansion
'("0" . meow-expand-0)
'("1" . meow-expand-1)
'("2" . meow-expand-2)
'("3" . meow-expand-3)
'("4" . meow-expand-4)
'("5" . meow-expand-5)
'("6" . meow-expand-6)
'("7" . meow-expand-7)
'("8" . meow-expand-8)
'("9" . meow-expand-9)
'("'" . meow-reverse)
; movement
'("i" . meow-prev)
'("k" . meow-next)
'("j" . meow-left)
'("l" . meow-right)
'("y" . meow-search)
'("/" . meow-visit)
; expansion
'("I" . meow-prev-expand)
'("K" . meow-next-expand)
'("J" . meow-left-expand)
'("L" . meow-right-expand)
'("u" . meow-back-word)
'("U" . meow-back-symbol)
'("o" . meow-next-word)
'("O" . meow-next-symbol)
'("a" . meow-mark-word)
'("A" . meow-mark-symbol)
'("s" . meow-line)
'("S" . meow-goto-line)
'("w" . meow-block)
'("q" . meow-join)
'("g" . meow-grab)
'("G" . meow-pop-grab)
'("m" . meow-swap-grab)
'("M" . meow-sync-grab)
'("p" . meow-cancel-selection)
'("P" . meow-pop-selection)
'("x" . meow-till)
'("z" . meow-find)
'("," . meow-beginning-of-thing)
'("." . meow-end-of-thing)
'("<" . meow-inner-of-thing)
'(">" . meow-bounds-of-thing)
; editing
'("d" . meow-kill)
'("f" . meow-change)
'("t" . meow-delete)
'("c" . meow-save)
'("v" . meow-yank)
'("V" . meow-yank-pop)
'("e" . meow-insert)
'("E" . meow-open-above)
'("r" . meow-append)
'("R" . meow-open-below)
'("h" . undo-only)
'("H" . undo-redo)
'("b" . open-line)
'("B" . split-line)
'("[" . indent-rigidly-left-to-tab-stop)
'("]" . indent-rigidly-right-to-tab-stop)
; prefix n
'("nf" . meow-comment)
'("nt" . meow-start-kmacro-or-insert-counter)
'("nr" . meow-start-kmacro)
'("ne" . meow-end-or-call-kmacro)
;; ...etc
; prefix ;
'(";f" . save-buffer)
'(";F" . save-some-buffers)
'(";d" . meow-query-replace-regexp)
;; ... etc
; ignore escape
'("<escape>" . ignore))
(require 'expand-region)
(setq er/try-expand-list
'(er/mark-inside-quotes
er/mark-outside-quotes
er/mark-inside-pairs
er/mark-outside-pairs))
(setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty))
Archive
vterm
(use-package vterm
:ensure (vterm :post-build
(progn
(setq vterm-always-compile-module t)
(require 'vterm)
(with-current-buffer (get-buffer-create vterm-install-buffer-name)
(goto-char (point-min))
(while (not (eobp))
(message "%S"
(buffer-substring (line-beginning-position)
(line-end-position)))
(forward-line)))
(when-let ((so (expand-file-name "./vterm-module.so"))
((file-exists-p so)))
(make-symbolic-link
so (expand-file-name (file-name-nondirectory so)
"../../builds/vterm")
'ok-if-already-exists))))
:commands (vterm vterm-other-window)
:custom
(vterm-max-scrollback 100000)
(vterm-buffer-name "vterm")
:general
("M-o t" 'vterm)
("M-o T" 'vterm-other-window)
:config
(setq vterm-shell (executable-find "zsh")))