I use Emacs for almost all general computing needs besides web browsing. I used to maintain its configuration separately from my machine configurations, but since I am moving to a more literate approach, it makes sense to move it here.
Look at Building Emacs to see how the sausage is made.
;; -*- lexical-binding: t; -*-
(setq gc-cons-threshold most-positive-fixnum)
(add-hook 'after-init-hook
`(lambda ()
(setq file-name-handler-alist ',file-name-handler-alist))
0)
(setq file-name-handler-alist nil)
(setq package-enable-at-startup nil
frame-resize-pixelwise t
frame-inhibit-implied-resize t
frame-title-format '("%b")
ring-bell-function 'ignore
use-dialog-box nil
use-file-dialog nil
use-short-answers t
inhibit-splash-screen t
inhibit-startup-screen t
inhibit-x-resources t
inhibit-startup-echo-area-message user-login-name ; read the docstring
inhibit-startup-buffer-menu t)
;; Prevent the glimpse of un-styled Emacs by disabling these UI elements early.
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)
(defvar user-cache-directory "~/.cache/emacs/"
"Location where files created by emacs are placed.")
(defun vlaci/in-cache-directory (name)
"Return NAME appended to cache directory"
(expand-file-name name user-cache-directory))
(defvar vlaci/init-directory user-emacs-directory)
(defun vlaci/in-init-directory (name)
"Return NAME appended to init directory"
(expand-file-name name vlaci/init-directory))
(startup-redirect-eln-cache (vlaci/in-cache-directory "eln-cache"))
(setq user-emacs-directory user-cache-directory)final: prev: {
setup = prev.setup.overrideAttrs (_: {
ignoreCompilationError = true;
});
};; -*- lexical-binding: t; -*-
;; Use font from Gsettings from /org/gnome/desktop/interface/
;; The keys read are:
;; - ‘font-name’
;; - 'monospace-font-name’
(require 'vlaci-emacs)
(setq font-use-system-font t)
(set-face-attribute 'fixed-pitch nil :height 1.0)
(set-face-attribute 'variable-pitch nil :height 1.0)
(setup (:package gcmh)
(:hook-into on-first-buffer-hook)
(setq gcmh-verbose init-file-debug
gcmh-high-cons-threshold (* 128 1024 1024)))
(setup emacs
(setq user-emacs-directory user-cache-directory)
(setq
custom-file (vlaci/in-cache-directory "custom.el")
auto-save-interval 2400
auto-save-timeout 300
auto-save-list-file-name (vlaci/in-cache-directory "auto-save.lst")
auto-save-file-name-transforms `((".*" ,(vlaci/in-cache-directory "auto-save/") t))
backup-directory-alist `(("." . ,(vlaci/in-cache-directory "backup/")))
backup-by-copying t
version-control t
delete-old-versions t
kept-new-versions 10
kept-old-versions 5)
(make-directory (vlaci/in-cache-directory "auto-save/") :parents)
(load custom-file :no-error-if-file-is-missing))
(setup recentf
(setq recentf-max-saved-items 200
recentf-auto-cleanup 300)
(define-advice recentf-cleanup (:around (fun) silently)
(let ((inhibit-message t)
(message-log-max nil))
(funcall fun)))
(recentf-mode 1))
(setup savehist
(:hook-into on-first-file-hook)
(setq history-length 1000
history-delete-duplicates t
savehist-save-minibuffer-history t
savehist-additional-variables
'(kill-ring ; clipboard
register-alist ; macros
mark-ring global-mark-ring ; marks
search-ring regexp-search-ring))) ; searches
(setup save-place
(:hook-into on-first-file-hook)
(setq save-place-limit 600))
<<init-el>>{ lib, pkgs, ... }:
let
e = pkgs.writeShellScriptBin "e" ''
if [ -n "$INSIDE_EMACS" ]; then
emacsclient -n "$@"
else
emacsclient --alternate-editor="" -t "$@"
fi
'';
in
{
home.sessionVariables.EDITOR = lib.getExe e;
home.packages = [ e ];
}My helper package
This contains my to-be compiled code to keep init.el clean.
final: prev: {
vlaci-emacs = final.mkPackage {
pname = "vlaci-emacs";
version = "1.0";
src = pkgs.writeText "vlaci-emacs.el" ''
;;; vlaci-emacs.el --- local extensions -*- lexical-binding: t; -*-
<<vlaci-emacs>>
(provide 'vlaci-emacs)
'';
packageRequires = with final; [
<<vlaci-emacs-requires>>
];
};
}Help
(setup emacs
(setq help-window-keep-selected t)) ;; navigating to e.g. source from help window reuses said window(setup which-key
(setq which-key-popup-type 'minibuffer) ;; required for embark-prefix-command (C-h) to work
(:hook-into on-first-input-hook))(setup (:package helpful elisp-demos)
(setq help-window-select t)
(:global
[remap describe-command] #'helpful-command
[remap describe-function] #'helpful-callable
[remap describe-macro] #'helpful-macro
[remap describe-key] #'helpful-key
[remap describe-symbol] #'helpful-symbol
[remap describe-variable] #'helpful-variable)
(:when-loaded
(require 'elisp-demos)
(advice-add 'helpful-update :after #'elisp-demos-advice-helpful-update)))More hooks
(setup (:package on)
(:require on))Keymaps
(map! :leader
:desc "M-x" "x" #'execute-extended-command
:desc "Resume last search" "'" #'vl/vertico-resume-or-repeat
:desc "help" "h" help-map
(:prefix-map ("b" . "buffer")
:desc "Switch buffer" "b" #'consult-buffer
:desc "Kill buffer&window" "d" #'kill-buffer-and-window
:desc "Kill buffer" :repeat "k" #'kill-current-buffer)
(:prefix-map ("w" . "window")
:desc "Switch window" "w" #'ace-window
:desc "Delete window" "d" #'delete-window
:desc "Delete other windows" "D" #'delete-other-windows
:desc "Undo layout change" :repeat "u" #'tab-bar-history-back
:desc "Redo layout change" :repeat "U" #'tab-bar-history-forward)
(:prefix-map ("o" . "open")
:desc "Directory" "/" #'dirvish
:desc "Browser" "b" #'browse-url-of-file
:desc "Dired" "d" #'dired-jump
:desc "EShell" "e" #'eshell
:desc "Project" "p" #'dirvish-side
:desc "Terminal" "t" #'eat)
(:prefix-map ("t" . "toggle")
:desc "Inlay hints" "i" #'lsp-inlay-hints-mode))
;; (dolist (cmd '(tab-bar-history-back tab-bar-history-forward))
;; (put cmd 'repeat-map 'doom-leader-window-map))(defun vl/vertico-resume-or-repeat ()
"If `vertico-suspend' is active then resume, otherwise call `vertico-repeat'."
(interactive)
(condition-case _
(vertico-suspend)
(user-error
(vertico-repeat))))UI
(setup emacs
(:global [remap kill-buffer] #'kill-current-buffer)
(:with-mode window-divider-mode
(:hook-into on-init-ui-hook))
(setq window-divider-default-right-width 4))(setup (:package doom-modeline auto-dark spacious-padding)
(setq split-window-preferred-direction 'horizontal
spacious-padding-subtle-mode-line t
spacious-padding-widths
'( :internal-border-width 15
:header-line-width 4
:mode-line-width 6
:tab-width 4
:right-divider-width 1
:scroll-bar-width 8
:fringe-width 8)
auto-dark-themes '((modus-vivendi-tinted) (modus-operandi-tinted)))
(defun vl/window-narrow-p ()
"Return non-nil if window is narrow.
Check if the `window-width' is less than `split-width-threshold'."
(and (numberp split-width-threshold)
(< (window-total-width) split-width-threshold)))
(defsubst vl/evil-state-inverse-face(face)
(intern (format "vl/%s-inverse" face)))
(dolist (state '(normal insert motion visual operator replace user))
(let ((face (format "doom-modeline-evil-%s-state" state)))
(eval `(defface ,(vl/evil-state-inverse-face face)
'((t (:inherit ,(intern face) :inverse-video t)))
,(face-documentation (intern face))))))
(defsubst vl/doom-modeline--evil ()
"The current evil state. Requires `evil-mode' to be enabled."
(when (bound-and-true-p evil-local-mode)
(let-alist (pcase evil-state
('normal '((face . doom-modeline-evil-normal-state)
(icon . "nf-md-alpha_n_box")
(unicode . "🅝")
(label . "NORMAL")))
('emacs '((face . doom-modeline-evil-emacs-state)
(icon . "nf-md-alpha_e_box")
(unicode . "🅔")
(label . "EMACS")))
('insert '((face . doom-modeline-evil-insert-state)
(icon . "nf-md-alpha_i_box")
(unicode . "🅘")
(label . "INSERT")))
('motion '((face . doom-modeline-evil-motion-state)
(icon . "nf-md-alpha_m_box")
(unicode . "🅜")
(label . "MOTION")))
('visual (pcase evil-visual-selection
('line '((face . doom-modeline-evil-visual-state)
(icon . "nf-md-alpha_l_box")
(unicode . "🅥")
(label . "VISUAL LINE")))
('screen-line '((face . doom-modeline-evil-visual-state)
(icon . "nf-md-alpha_l_box_outline")
(unicode . "🅥")
(label . "SCREEN LINE")))
('block '((face . doom-modeline-evil-visual-state)
(icon . "nf-md-alpha_b_box")
(unicode . "🅥")
(label . "VISUAL BLOCK")))
(t '((face . doom-modeline-evil-visual-state)
(icon . "nf-md-alpha_v_box")
(unicode . "🅥")
(label . "VISUAL")))))
('operator '((face . doom-modeline-evil-operator-state)
(icon . "nf-md-alpha_o_box")
(unicode . "🅞")
(label . "OPERATOR")))
('replace '((face . doom-modeline-evil-replace-state)
(icon . "nf-md-alpha_r_box")
(unicode . "🅡")
(label . "REPLACE")))
(t '((face . doom-modeline-evil-user-state)
(icon . "nf-md-alpha_u_box")
(unicode . "🅤")
(label . "USER"))))
(let ((content (if (vl/window-narrow-p)
(doom-modeline-icon
'mdicon .icon .unicode
(let ((tag (evil-state-property evil-state :tag t)))
(if (stringp tag) tag (funcall tag)))
:face (doom-modeline-face .face))
(propertize (format " %s " .label) 'face (doom-modeline-face (vl/evil-state-inverse-face .face))))))
(propertize content 'help-echo (evil-state-property evil-state :name t))))))
(custom-set-faces
'(doom-modeline-evil-normal-state ((t (:inherit doom-modeline-info))))
'(doom-modeline-evil-operator-state ((t (:inherit doom-modeline-urgent)))))
(:with-feature doom-modeline
(:when-loaded
(doom-modeline-def-segment vl/evil
"Displays evil states."
(let* ((evil (vl/doom-modeline--evil))
(vsep (doom-modeline-vspc))
(sep (and (or evil ow god ryo xf boon meow) (doom-modeline-spc))))
(concat sep
evil
vsep
sep)))
(doom-modeline-def-modeline 'main
'(eldoc window-state window-number vl/evil matches follow buffer-info remote-host buffer-position word-count selection-info)
'(compilation misc-info project-name battery debug repl lsp minor-modes input-method indent-info buffer-encoding major-mode process vcs check time))))
(defun vlaci--load-theme-h ()
(require-theme 'modus-themes)
(setq modus-themes-italic-constructs t
modus-themes-bold-constructs t
modus-themes-prompts '(background)
modus-themes-mixed-fonts nil
modus-themes-org-blocks 'gray-background
modus-themes-headings '((0 . (2.0))
(1 . (rainbow background overline 1.5))
(2 . (background overline 1.4))
(3 . (background overline 1.3))
(4 . (background overline 1.2))
(5 . (overline 1.2))
(t . (no-bold 1.1)))
modus-themes-common-palette-overrides
`(,@modus-themes-preset-overrides-faint
(builtin magenta)
(comment fg-dim)
(constant magenta-cooler)
(docstring magenta-faint)
(docmarkup green-faint)
(fnname magenta-warmer)
(keyword cyan)
(preprocessor cyan-cooler)
(string red-cooler)
(type magenta-cooler)
(variable blue-warmer)
(rx-construct magenta-warmer)
(rx-backslash blue-cooler)))
(load-theme 'modus-operandi-tinted :no-confirm)
(doom-modeline-mode)
(spacious-padding-mode)
(auto-dark-mode)
(tab-bar-mode)
(tab-bar-history-mode))
(:with-function vlaci--load-theme-h
(:hook-into window-setup-hook)))(setup (:package repeat-help)
(:hook-into repeat-mode-hook)
(:hook (defun vlaci/reset-repeat-echo-function-h ()
(setq repeat-echo-function repeat-help--echo-function))))
(setup repeat
(:hook-into on-first-input-hook))(setup emacs
(setq display-line-numbers-type 'relative
display-line-numbers-width 3
display-line-numbers-widen t
truncate-lines t
window-combination-resize t))
(setup prog
(:hook #'display-line-numbers-mode))(setup (:package lin)
(:with-mode lin-global-mode
(:hook-into on-first-buffer-hook)))(setup (:package colorful-mode)
(:with-mode global-colorful-mode
(:hook-into on-first-buffe-hook)))(setup (:package popper))Window management
(setup emacs
(setq switch-to-buffer-obey-display-actions t
display-buffer-alist
`(((or (major-mode . help-mode)
(major-mode . helpful-mode)
(major-mode . Info-mode))
(display-buffer-reuse-window ;; if this buffer is open somewhere, focus it
display-buffer-in-side-window) ;; otherwise open to the side
(side . right)
(slot . 0)
(window-width . 80))
((or (major-mode . eat-mode)
,(rx bol "*eshell"))
(display-buffer-reuse-window
display-buffer-in-side-window)
(side . bottom)
(slot . 0)
(window-height . 20)
(window-parameters
(no-delete-other-windows . t))))))More fine grained lazy loading
once = { url = "github:emacs-magus/once"; flake = false; };final: prev: {
once = final.mkPackage {
pname = "once";
src = inputs.once;
files = [
"*.el"
"once-setup/*.el"
];
packageRequires = [
(final.setup.overrideAttrs (_: {
ignoreCompilationError = true;
}))
];
};
}(setup (:package once)
(setq once-shorthand t)
(:require once once-conditions))
(setup once-setup
(:require once-setup))setup(defvar vlaci-incremental-packages '(t)
"A list of packages to load incrementally after startup. Any large packages
here may cause noticeable pauses, so it's recommended you break them up into
sub-packages. For example, `org' is comprised of many packages, and can be
broken up into:
(vlaci-load-packages-incrementally
'(calendar find-func format-spec org-macs org-compat
org-faces org-entities org-list org-pcomplete org-src
org-footnote org-macro ob org org-clock org-agenda
org-capture))
This is already done by the lang/org module, however.
If you want to disable incremental loading altogether, either remove
`doom-load-packages-incrementally-h' from `emacs-startup-hook' or set
`doom-incremental-first-idle-timer' to nil.")
(defvar vlaci-incremental-first-idle-timer 2.0
"How long (in idle seconds) until incremental loading starts.
Set this to nil to disable incremental loading.")
(defvar vlaci-incremental-idle-timer 0.75
"How long (in idle seconds) in between incrementally loading packages.")
(defvar vlaci-incremental-load-immediately nil
;; (daemonp)
"If non-nil, load all incrementally deferred packages immediately at startup.")
(defun vlaci-load-packages-incrementally (packages &optional now)
"Registers PACKAGES to be loaded incrementally.
If NOW is non-nil, load PACKAGES incrementally, in `doom-incremental-idle-timer'
intervals."
(if (not now)
(setq vlaci-incremental-packages (append vlaci-incremental-packages packages))
(while packages
(let ((req (pop packages)))
(unless (featurep req)
(message "Incrementally loading %s" req)
(condition-case e
(or (while-no-input
;; If `default-directory' is a directory that doesn't exist
;; or is unreadable, Emacs throws up file-missing errors, so
;; we set it to a directory we know exists and is readable.
(let ((default-directory user-emacs-directory)
(gc-cons-threshold most-positive-fixnum)
file-name-handler-alist)
(require req nil t))
t)
(push req packages))
((error debug)
(message "Failed to load '%s' package incrementally, because: %s"
req e)))
(if (not packages)
(message "Finished incremental loading")
(run-with-idle-timer vlaci-incremental-idle-timer
nil #'vlaci-load-packages-incrementally
packages t)
(setq packages nil)))))))
;;;###autoload
(defun vlaci-load-packages-incrementally-h ()
"Begin incrementally loading packages in `vlaci-incremental-packages'.
If this is a daemon session, load them all immediately instead."
(if vlaci-incremental-load-immediately
(mapc #'require (cdr vlaci-incremental-packages))
(when (numberp vlaci-incremental-first-idle-timer)
(run-with-idle-timer vlaci-incremental-first-idle-timer
nil #'vlaci-load-packages-incrementally
(cdr vlaci-incremental-packages) t))))
(add-hook 'emacs-startup-hook #'vlaci-load-packages-incrementally-h)
(require 'setup)
(setup-define :package
(lambda (package))
:documentation "Fake installation of PACKAGE."
:repeatable t
:shorthand #'cadr)
(setup-define :defer-incrementally
(lambda (&rest targets)
(vlaci-load-packages-incrementally targets)
:documentation "Load TARGETS incrementally"))(setup-define :autoload
(lambda (func)
(let ((fn (if (memq (car-safe func) '(quote function))
(cadr func)
func)))
`(unless (fboundp (quote ,fn))
(autoload (function ,fn) ,(symbol-name (setup-get 'feature)) nil t))))
:documentation "Autoload COMMAND if not already bound."
:repeatable t
:signature '(FUNC ...))(setup emacs
(defun vl/welcome ()
(with-current-buffer (get-buffer-create "*scratch*")
(insert (format ";;
;; ██╗ ██╗██╗ ███████╗███╗ ███╗ █████╗ ██████╗███████╗
;; ██║ ██║██║ ██╔════╝████╗ ████║██╔══██╗██╔════╝██╔════╝
;; ╚██╗ ██╔╝██║ █████╗ ██╔████╔██║███████║██║ ███████╗
;; ╚████╔╝ ██║ ██╔══╝ ██║╚██╔╝██║██╔══██║██║ ╚════██║
;; ╚██╔╝ ██████╗ ███████╗██║ ╚═╝ ██║██║ ██║╚██████╗███████║
;; ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚══════╝
;;
;; Loading time : %s
;; Features : %s
"
(emacs-init-time)
(length features))))
(message (emacs-init-time)))
(:with-function vl/welcome
(:hook-into after-init-hook)))Better keyboard-quit
Based on Prot’s Basic and capable configuration article.
(setup vlaci-emacs
(:global [remap keyboard-quit] #'vlaci-keyboard-quit-dwim));;;###autoload
(defun vlaci-keyboard-quit-dwim ()
"Do-What-I-Mean behaviour for a general `keyboard-quit'.
The generic `keyboard-quit' does not do the expected thing when
the minibuffer is open. Whereas we want it to close the
minibuffer, even without explicitly focusing it.
The DWIM behaviour of this command is as follows:
- When the region is active, disable it.
- When a minibuffer is open, but not focused, close the minibuffer.
- When the Completions buffer is selected, close it.
- In every other case use the regular `keyboard-quit'."
(interactive)
(cond
((region-active-p)
(keyboard-quit))
((derived-mode-p 'completion-list-mode) ;; Do I need this?
(delete-completion-window))
((> (minibuffer-depth) 0)
(abort-recursive-edit))
(t
(keyboard-quit))))Icons, icons, icons
(setup (:package nerd-icons))
(setup (:package nerd-icons-completion)
(:with-function nerd-icons-completion-marginalia-setup
(:hook-into marginalia-mode-hook)))
(setup (:package nerd-icons-corfu)
(:with-feature corfu
(:when-loaded
(add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))))
(setup (:package nerd-icons-dired)
(:hook-into dired-mode-hook))Undo
(setup (:package undo-fu)
(setq undo-limit (* 80 1024 1024)
undo-strong-limit (* 120 1024 1024)
undo-outer-limit (* 360 1024 1024)))
(setup (:package undo-fu-session)
(:with-mode undo-fu-session-global-mode
(:hook-into on-first-buffer-hook)))
(setup (:package vundo)
(setq vundo-compact-display t)
(:bind [remap keyboard-quit] #'vundo-quit))Editing
(add-hook 'prog-mode-hook
(defun setup-newline-h ()
(local-set-key (kbd "RET") (or (key-binding (kbd "C-M-j"))
(key-binding (kbd "M-j"))))))Evil
(defmacro vl/key-dispatch (op action)
`'(menu-item
""
nil
:filter (lambda (&rest _)
(when (eq evil-this-operator ,op)
,action))))general;;; Commentary:
;;
;; A centralized keybinds system, integrated with `which-key' to preview
;; available keybindings. All built into one powerful macro: `map!'. If evil is
;; never loaded, then evil bindings set with `map!' are ignored (i.e. omitted
;; entirely for performance reasons).
;;
;;; Code:
(defun doom-unquote (exp)
"Return EXP unquoted."
(declare (pure t) (side-effect-free t))
(while (memq (car-safe exp) '(quote function))
(setq exp (cadr exp)))
exp)
(defmacro prependq! (sym &rest lists)
"Prepend LISTS to SYM in place."
`(setq ,sym (append ,@lists ,sym)))
(defun doom-keyword-name (keyword)
"Returns the string name of KEYWORD (`keywordp') minus the
leading colon."
(declare (pure t) (side-effect-free t))
(cl-check-type keyword keyword)
(substring (symbol-name keyword) 1))
(defvar doom-leader-key "SPC"
"The leader prefix key for Evil users.")
(defvar doom-leader-alt-key "M-SPC"
"An alternative leader prefix key, used for Insert and Emacs states, and for
non-evil users.")
(defvar doom-leader-key-states '(normal visual motion)
"which evil modes to activate the leader key for")
(defvar doom-leader-alt-key-states '(emacs insert)
"which evil modes to activate the alternative leader key for")
(defvar doom-localleader-key "SPC m"
"The localleader prefix key, for major-mode specific commands.")
(defvar doom-localleader-alt-key "M-SPC m"
"The localleader prefix key, for major-mode specific commands. Used for Insert
and Emacs states, and for non-evil users.")
(defvar doom-leader-map (make-sparse-keymap)
"An overriding keymap for <leader> keys.")
;;
;;; Global keybind settings
;; (cond
;; (doom--system-macos-p
;; ;; mac-* variables are used by the special emacs-mac build of Emacs by
;; ;; Yamamoto Mitsuharu, while other builds use ns-*.
;; (setq mac-command-modifier 'super
;; ns-command-modifier 'super
;; mac-option-modifier 'meta
;; ns-option-modifier 'meta
;; ;; Free up the right option for character composition
;; mac-right-option-modifier 'none
;; ns-right-option-modifier 'none))
;; (doom--system-windows-p
;; (setq w32-lwindow-modifier 'super
;; w32-rwindow-modifier 'super)))
;; ;; HACK: Emacs can't distinguish C-i from TAB, or C-m from RET, in either GUI or
;; ;; TTY frames. This is a byproduct of its history with the terminal, which
;; ;; can't distinguish them either, however, Emacs has separate input events for
;; ;; many contentious keys like TAB and RET (like [tab] and [return], aka
;; ;; "<tab>" and "<return>"), which are only triggered in GUI frames, so here, I
;; ;; create one for C-i. Won't work in TTY frames, though. Doom's :os tty module
;; ;; has a workaround for that though.
;; (pcase-dolist (`(,key ,fallback . ,events)
;; '(([C-i] [?\C-i] tab kp-tab)
;; ([C-m] [?\C-m] return kp-return)))
;; (define-key
;; input-decode-map fallback
;; (cmd! (if (when-let ((keys (this-single-command-raw-keys)))
;; (and (display-graphic-p)
;; (not (cl-loop for event in events
;; if (cl-position event keys)
;; return t))
;; ;; Use FALLBACK if nothing is bound to KEY, otherwise we've
;; ;; broken all pre-existing FALLBACK keybinds.
;; (key-binding
;; (vconcat (if (= 0 (length keys)) [] (cl-subseq keys 0 -1))
;; key) nil t)))
;; key fallback))))
;; ;;
;; ;;; Universal, non-nuclear escape
;; ;; `keyboard-quit' is too much of a nuclear option. I wanted an ESC/C-g to
;; ;; do-what-I-mean. It serves four purposes (in order):
;; ;;
;; ;; 1. Quit active states; e.g. highlights, searches, snippets, iedit,
;; ;; multiple-cursors, recording macros, etc.
;; ;; 2. Close popup windows remotely (if it is allowed to)
;; ;; 3. Refresh buffer indicators, like diff-hl and flycheck
;; ;; 4. Or fall back to `keyboard-quit'
;; ;;
;; ;; And it should do these things incrementally, rather than all at once. And it
;; ;; shouldn't interfere with recording macros or the minibuffer. This may require
;; ;; you press ESC/C-g two or three times on some occasions to reach
;; ;; `keyboard-quit', but this is much more intuitive.
;; (defvar doom-escape-hook nil
;; "A hook run when C-g is pressed (or ESC in normal mode, for evil users).
;; More specifically, when `doom/escape' is pressed. If any hook returns non-nil,
;; all hooks after it are ignored.")
;; (defun doom/escape (&optional interactive)
;; "Run `doom-escape-hook'."
;; (interactive (list 'interactive))
;; (let ((inhibit-quit t))
;; (cond ((minibuffer-window-active-p (minibuffer-window))
;; ;; quit the minibuffer if open.
;; (when interactive
;; (setq this-command 'abort-recursive-edit))
;; (abort-recursive-edit))
;; ;; Run all escape hooks. If any returns non-nil, then stop there.
;; ((run-hook-with-args-until-success 'doom-escape-hook))
;; ;; don't abort macros
;; ((or defining-kbd-macro executing-kbd-macro) nil)
;; ;; Back to the default
;; ((unwind-protect (keyboard-quit)
;; (when interactive
;; (setq this-command 'keyboard-quit)))))))
;; (global-set-key [remap keyboard-quit] #'doom/escape)
;; (with-eval-after-load 'eldoc
;; (eldoc-add-command 'doom/escape))
;;
;;; General + leader/localleader keys
(require 'general)
;; Convenience aliases
(defalias 'define-key! #'general-def)
(defalias 'undefine-key! #'general-unbind)
;; ;; Prevent "X starts with non-prefix key Y" errors except at startup.
;; (add-hook 'doom-after-modules-init-hook #'general-auto-unbind-keys)
;; HACK: `map!' uses this instead of `define-leader-key!' because it consumes
;; 20-30% more startup time, so we reimplement it ourselves.
(defmacro doom--define-leader-key (&rest keys)
(let (prefix forms wkforms)
(while keys
(let ((key (pop keys))
(def (pop keys)))
(if (keywordp key)
(when (memq key '(:prefix :infix))
(setq prefix def))
(when prefix
(setq key `(general--concat t ,prefix ,key)))
(let* ((udef (cdr-safe (doom-unquote def)))
(bdef (if (general--extended-def-p udef)
(general--extract-def (general--normalize-extended-def udef))
def)))
(unless (eq bdef :ignore)
(push `(define-key doom-leader-map (general--kbd ,key)
,bdef)
forms))
(when-let (desc (cadr (memq :which-key udef)))
(prependq!
wkforms `((which-key-add-key-based-replacements
(general--concat t doom-leader-alt-key ,key)
,desc)
(which-key-add-key-based-replacements
(general--concat t doom-leader-key ,key)
,desc))))))))
(macroexp-progn
(append (and wkforms `((with-eval-after-load 'which-key ,@(nreverse wkforms))))
(nreverse forms)))))
(defmacro define-leader-key! (&rest args)
"Define <leader> keys.
Uses `general-define-key' under the hood, but does not support :states,
:wk-full-keys or :keymaps. Use `map!' for a more convenient interface.
See `doom-leader-key' and `doom-leader-alt-key' to change the leader prefix."
`(general-define-key
:states nil
:wk-full-keys nil
:keymaps 'doom-leader-map
,@args))
(defmacro define-localleader-key! (&rest args)
"Define <localleader> key.
Uses `general-define-key' under the hood, but does not support :major-modes,
:states, :prefix or :non-normal-prefix. Use `map!' for a more convenient
interface.
See `doom-localleader-key' and `doom-localleader-alt-key' to change the
localleader prefix."
`(general-define-key
:states '(normal visual motion emacs insert)
:major-modes t
:prefix doom-localleader-key
:non-normal-prefix doom-localleader-alt-key
,@args))
;; PERF: We use a prefix commands instead of general's
;; :prefix/:non-normal-prefix properties because general is incredibly slow
;; binding keys en mass with them in conjunction with :states -- an effective
;; doubling of Doom's startup time!
(define-prefix-command 'doom/leader 'doom-leader-map)
(define-key doom-leader-map [override-state] 'all)
;; Bind `doom-leader-key' and `doom-leader-alt-key' as late as possible to give
;; the user a chance to modify them.
(add-hook 'after-init-hook
(defun doom-init-leader-keys-h ()
"Bind `doom-leader-key' and `doom-leader-alt-key'."
(let ((map general-override-mode-map))
(if (not (featurep 'evil))
(progn
(cond ((equal doom-leader-alt-key "C-c")
(set-keymap-parent doom-leader-map mode-specific-map))
((equal doom-leader-alt-key "C-x")
(set-keymap-parent doom-leader-map ctl-x-map)))
(define-key map (kbd doom-leader-alt-key) 'doom/leader))
(evil-define-key* doom-leader-key-states map (kbd doom-leader-key) 'doom/leader)
(evil-define-key* doom-leader-alt-key-states map (kbd doom-leader-alt-key) 'doom/leader))
(general-override-mode +1))))
;;
;;; Packages
;; (use-package! which-key
;; :hook (doom-first-input . which-key-mode)
;; :init
;; (setq which-key-sort-order #'which-key-key-order-alpha
;; which-key-sort-uppercase-first nil
;; which-key-add-column-padding 1
;; which-key-max-display-columns nil
;; which-key-min-display-lines 6
;; which-key-side-window-slot -10)
;; :config
;; (put 'which-key-replacement-alist 'initial-value which-key-replacement-alist)
;; (add-hook! 'doom-before-reload-hook
;; (defun doom-reset-which-key-replacements-h ()
;; (setq which-key-replacement-alist (get 'which-key-replacement-alist 'initial-value))))
;; ;; general improvements to which-key readability
;; (which-key-setup-side-window-bottom)
;; (setq-hook! 'which-key-init-buffer-hook line-spacing 3)
;; (which-key-add-key-based-replacements doom-leader-key "<leader>")
;; (which-key-add-key-based-replacements doom-localleader-key "<localleader>"))
;;
;;; `map!' macro
(defvar doom-evil-state-alist
'((?n . normal)
(?v . visual)
(?i . insert)
(?e . emacs)
(?o . operator)
(?m . motion)
(?r . replace)
(?g . global))
"A list of cons cells that map a letter to a evil state symbol.")
(defun doom--map-keyword-to-states (keyword)
"Convert a KEYWORD into a list of evil state symbols.
For example, :nvi will map to (list 'normal 'visual 'insert). See
`doom-evil-state-alist' to customize this."
(cl-loop for l across (doom-keyword-name keyword)
if (assq l doom-evil-state-alist) collect (cdr it)
else do (error "not a valid state: %s" l)))
;; specials
(defvar doom--map-forms nil)
(defvar doom--map-fn nil)
(defvar doom--map-batch-forms nil)
(defvar doom--map-state '(:dummy t))
(defvar doom--map-parent-state nil)
(defun doom--map-process (rest)
(let ((doom--map-fn doom--map-fn)
doom--map-state
doom--map-forms
desc
repeat)
(while rest
(let ((key (pop rest)))
(cond ((listp key)
(doom--map-nested nil key))
((keywordp key)
(pcase key
(:leader
(doom--map-commit)
(setq doom--map-fn 'doom--define-leader-key))
(:localleader
(doom--map-commit)
(setq doom--map-fn 'define-localleader-key!))
(:after
(doom--map-nested `(with-eval-after-load ',(pop rest)) rest)
(setq rest nil))
(:desc
(setq desc (pop rest)))
(:repeat
(setq repeat t))
(:map
(doom--map-set :keymaps `(backquote ,(ensure-list (pop rest)))))
(:mode
(push (cl-loop for m in (ensure-list (pop rest))
collect (intern (concat (symbol-name m) "-map")))
rest)
(push :map rest))
((or :when :unless)
(doom--map-nested (list (intern (doom-keyword-name key)) (pop rest)) rest)
(setq rest nil))
(:prefix-map
(cl-destructuring-bind (prefix . desc)
(let ((arg (pop rest)))
(if (consp arg) arg (list arg)))
(let ((keymap (intern (format "doom-leader-%s-map" desc))))
(setq rest
(append (list :desc desc prefix keymap
:prefix prefix)
rest))
(doom--map-set :vl-prefix-map keymap)
(push `(defvar ,keymap (make-sparse-keymap))
doom--map-forms))))
(:prefix
(cl-destructuring-bind (prefix . desc)
(let ((arg (pop rest)))
(if (consp arg) arg (list arg)))
(doom--map-set (if doom--map-fn :infix :prefix)
prefix)
(when (stringp desc)
(setq rest (append (list :desc desc "" nil) rest)))))
(:textobj
(let* ((key (pop rest))
(inner (pop rest))
(outer (pop rest)))
(push `(map! (:map evil-inner-text-objects-map ,key ,inner)
(:map evil-outer-text-objects-map ,key ,outer))
doom--map-forms)))
(_
(condition-case _
(doom--map-def (pop rest) (pop rest)
(doom--map-keyword-to-states key)
desc
repeat)
(error
(error "Not a valid `map!' property: %s" key)))
(setq desc nil
repeat nil))))
((doom--map-def key (pop rest) nil desc repeat)
(setq desc nil
repeat nil)))))
(doom--map-commit)
(macroexp-progn (nreverse (delq nil doom--map-forms)))))
(defun doom--map-append-keys (prop)
(let ((a (plist-get doom--map-parent-state prop))
(b (plist-get doom--map-state prop)))
(if (and a b)
`(general--concat t ,a ,b)
(or a b))))
(defun doom--map-nested (wrapper rest)
(doom--map-commit)
(let ((doom--map-parent-state (doom--map-state)))
(push (if wrapper
(append wrapper (list (doom--map-process rest)))
(doom--map-process rest))
doom--map-forms)))
(defun doom--map-set (prop &optional value)
(unless (equal (plist-get doom--map-state prop) value)
(doom--map-commit))
(setq doom--map-state (plist-put doom--map-state prop value)))
(defun doom--map-def (key def &optional states desc repeat)
(when (or (memq 'global states)
(null states))
(setq states (cons 'nil (delq 'global states))))
(when desc
(let (unquoted)
(cond ((and (listp def)
(keywordp (car-safe (setq unquoted (doom-unquote def)))))
(setq def (list 'quote (plist-put unquoted :which-key desc))))
((setq def (cons 'list
(if (and (equal key "")
(null def))
`(:ignore t :which-key ,desc)
(plist-put (general--normalize-extended-def def)
:which-key desc))))))))
(dolist (state states)
(push (list repeat key def)
(alist-get state doom--map-batch-forms)))
t)
(defun doom--map-commit ()
(when doom--map-batch-forms
(cl-loop with attrs = (doom--map-state)
with pmap = (plist-get attrs :vl-prefix-map)
for (state . defs) in doom--map-batch-forms
collect (progn
`(,(or doom--map-fn 'general-define-key)
,@(if state `(:states ',state)) ,@attrs
,@(mapcan (lambda (def)
(cl-destructuring-bind (repeat . def) def
(when (and pmap repeat)
(let* ((d (cdr (cadr def)))
(cmd (cadr (or (plist-get d :def) (cadr d)))))
(put cmd 'repeat-map pmap)))
def))
(nreverse defs))))
into forms
finally do (push (macroexp-progn forms) doom--map-forms))
(setq doom--map-batch-forms nil)))
(defun doom--map-state ()
(let ((plist
(append (list :prefix (doom--map-append-keys :prefix)
:infix (doom--map-append-keys :infix)
:keymaps
(append (plist-get doom--map-parent-state :keymaps)
(plist-get doom--map-state :keymaps)))
doom--map-state
nil))
newplist)
(while plist
(let ((key (pop plist))
(val (pop plist)))
(when (and val (not (plist-member newplist key)))
(push val newplist)
(push key newplist))))
newplist))
;;
(defmacro map! (&rest rest)
"A convenience macro for defining keybinds, powered by `general'.
If evil isn't loaded, evil-specific bindings are ignored.
Properties
:leader [...] an alias for (:prefix doom-leader-key ...)
:localleader [...] bind to localleader; requires a keymap
:mode [MODE(s)] [...] inner keybinds are applied to major MODE(s)
:map [KEYMAP(s)] [...] inner keybinds are applied to KEYMAP(S)
:prefix [PREFIX] [...] set keybind prefix for following keys. PREFIX
can be a cons cell: (PREFIX . DESCRIPTION)
:prefix-map [PREFIX] [...] same as :prefix, but defines a prefix keymap
where the following keys will be bound. DO NOT
USE THIS IN YOUR PRIVATE CONFIG.
:after [FEATURE] [...] apply keybinds when [FEATURE] loads
:textobj KEY INNER-FN OUTER-FN define a text object keybind pair
:when [CONDITION] [...]
:unless [CONDITION] [...]
Any of the above properties may be nested, so that they only apply to a
certain group of keybinds.
States
:n normal
:v visual
:i insert
:e emacs
:o operator
:m motion
:r replace
:g global (binds the key without evil `current-global-map')
These can be combined in any order, e.g. :nvi will apply to normal, visual and
insert mode. The state resets after the following key=>def pair. If states are
omitted the keybind will be global (no emacs state; this is different from
evil's Emacs state and will work in the absence of `evil-mode').
These must be placed right before the key string.
Do
(map! :leader :desc \"Description\" :n \"C-c\" #'dosomething)
Don't
(map! :n :leader :desc \"Description\" \"C-c\" #'dosomething)
(map! :leader :n :desc \"Description\" \"C-c\" #'dosomething)"
(when (or (bound-and-true-p byte-compile-current-file)
(not noninteractive))
(doom--map-process rest)))(setup (:package evil evil-collection)
(:hook-into after-init-hook)
(setq
;; Will be handled by evil-collections
evil-want-keybinding nil
;; Make `Y` behave like `D`
evil-want-Y-yank-to-eol t
;; Do not extend visual selection to whole lines for ex commands
evil-ex-visual-char-range t
;; `*` and `#` selects symbols instead of words
evil-symbol-word-search t
;; Only highlight in the current window
evil-ex-interactive-search-highlight 'selected-window
;; Use vim-emulated search implementation
evil-search-module 'evil-search
;; Do not spam with error messages
evil-kbd-macro-suppress-motion-error t
evil-undo-system 'undo-fu
evil-visual-state-cursor 'hollow
evil-visual-update-x-selection-p nil
evil-move-cursor-back nil
evil-move-beyond-eol t)
(:also-load evil-collection)
(:when-loaded
(delete 'evil-mc evil-collection-mode-list)
;;; delay loading evil-collection modules until they are needed
(dolist (mode evil-collection-mode-list)
(dolist (req (or (cdr-safe mode) (list mode)))
(with-eval-after-load req
(message "Loading evil-collection for mode %s" req)
(evil-collection-init (list mode)))))
(evil-collection-init
'(help
(buff-menu "buff-menu")
calc
image
elisp-mode
replace
(indent "indent")
(process-menu simple)
shortdoc
tabulated-list
tab-bar))
(evil-global-set-key 'insert [remap evil-complete-next] #'complete-symbol))
(define-advice evil-force-normal-state (:after (&rest _) vl/evil-force-normal-state-a)
"Universal escape"
(when (called-interactively-p 'any)
(call-interactively #'vlaci-keyboard-quit-dwim))))
(defvar vl/evil-states
'((?n . normal)
(?v . visual)
(?i . insert)
(?e . emacs)
(?o . operator)
(?m . motion)
(?r . replace)
(?g . global)))
(defun vl/setup--get-evil-states (states)
(cl-loop for l across (substring (symbol-name states) 1)
if (assq l vl/evil-states) collect (cdr it)
else do (error "not a valid state: %s" l)))
(setup-define :key
(lambda (states key command)
`(evil-define-key ',(vl/setup--get-evil-states states) ,(setup-get 'map) ,key ,command))
:documentation ""
:repeatable t
:indent 0)
(setup-define :map
(lambda (&rest body)
`(map! :map ,(setup-get 'map) ,@body)))(setup (:package evil-mc)
(setq
evil-mc-undo-cursors-on-keyboard-quit t)
;; from doomemacs
(defvar evil-mc-key-map (make-sparse-keymap))
(:autoload
evil-mc-make-cursor-here
evil-mc-make-all-cursors
evil-mc-undo-all-cursors
evil-mc-pause-cursors
evil-mc-resume-cursors
evil-mc-make-and-goto-first-cursor
evil-mc-make-and-goto-last-cursor
evil-mc-make-cursor-in-visual-selection-beg
evil-mc-make-cursor-in-visual-selection-end
evil-mc-make-cursor-move-next-line
evil-mc-make-cursor-move-prev-line
evil-mc-make-cursor-at-pos
evil-mc-has-cursors-p
evil-mc-undo-last-added-cursor
evil-mc-make-and-goto-next-cursor
evil-mc-skip-and-goto-next-cursor
evil-mc-make-and-goto-prev-cursor
evil-mc-skip-and-goto-prev-cursor
evil-mc-make-and-goto-next-match
evil-mc-skip-and-goto-next-match
evil-mc-skip-and-goto-next-match
evil-mc-make-and-goto-prev-match
evil-mc-skip-and-goto-prev-match)
(defvar-keymap vl/evil-mc-repeat-map
:repeat (:exit
(evil-mc-make-all-cursors)
:hints
((evil-mc-make-and-goto-next-match . "▶")
(evil-mc-make-and-goto-prev-match . "◀")
(evil-mc-skip-and-goto-next-match . "⏭")
(evil-mc-skip-and-goto-prev-match . "⏮")
(evil-mc-make-all-cursors . "⛶")
(evil-mc-make-cursor-here . "⊹")
(evil-mc-undo-last-added-cursor . "↶")))
"l" #'evil-mc-make-and-goto-next-match
"L" #'evil-mc-make-and-goto-prev-match
">" #'evil-mc-skip-and-goto-next-match
"<" #'evil-mc-skip-and-goto-prev-match
"c" #'evil-mc-make-all-cursors
"h" #'evil-mc-make-cursor-here
"u" #'evil-mc-undo-last-added-cursor)
(:with-map evil-mc-key-map
(:map
:nv "C-n" #'evil-mc-make-and-goto-next-cursor
:nv "C-S-n" #'evil-mc-make-and-goto-last-cursor
:nv "C-p" #'evil-mc-make-and-goto-prev-cursor
:nv "C-S-p" #'evil-mc-make-and-goto-first-cursor))
(:with-map global
(:map
:m "gc" vl/evil-mc-repeat-map))
(:when-loaded
(add-hook 'evil-insert-state-entry-hook #'evil-mc-resume-cursors)
;; HACK evil-mc's design is bizarre. Its variables and hooks are lazy loaded
;; rather than declared at top-level, some hooks aren't defined or
;; documented, it's a bit initializer-function drunk, and its minor modes
;; are intended to be perpetually active -- even when no cursors are active
;; (causing #6021). I undo all of that here.
(evil-mc-define-vars)
(evil-mc-initialize-vars)
(add-hook 'evil-mc-before-cursors-created #'evil-mc-pause-incompatible-modes)
(add-hook 'evil-mc-before-cursors-created #'evil-mc-initialize-active-state)
(add-hook 'evil-mc-after-cursors-deleted #'evil-mc-teardown-active-state)
(add-hook 'evil-mc-after-cursors-deleted #'evil-mc-resume-incompatible-modes)
(advice-add #'evil-mc-initialize-hooks :override #'ignore)
(advice-add #'evil-mc-teardown-hooks :override #'evil-mc-initialize-vars)
(advice-add #'evil-mc-initialize-active-state :before #'turn-on-evil-mc-mode)
(advice-add #'evil-mc-teardown-active-state :after #'turn-off-evil-mc-mode)
(define-advice evil-mc-mode (:around (fn &rest args) vl/evil-mc-dont-reinit-vars-a)
(cl-letf (((symbol-function 'evil-mc-initialize-vars) #'ignore))
(apply fn args)))))Navigation
evil-ts-obj = { url = "github:vlaci/evil-ts-obj"; flake = false; };
treesit-jump = { url = "github:vlaci/treesit-jump"; flake = false; };final: prev: {
evil-ts-obj = final.mkPackage {
pname = "evil-ts-obj";
src = inputs.evil-ts-obj;
files = [ "lisp/*.el" ];
packageRequires = [
final.avy
final.evil
];
};
#evil-textobj-tree-sitter
treesit-jump = final.mkPackage {
pname = "treesit-jump";
src = inputs.treesit-jump;
files = [
"treesit-jump.el"
"treesit-queries"
];
packageRequires = [ final.avy ];
};
}(setup (:package ace-window)
(setq aw-keys '(?a ?r ?s ?t ?g ?n ?e ?i ?o)
aw-dispatch-always t)
(:global (kbd "M-o") #'ace-window))
(setup (:package avy)
(setq avy-keys '(?a ?r ?s ?t ?d ?h ?n ?e ?i ?o ?w ?f ?p ?l ?u ?y))
(:with-map global
(:map
:m "g/" #'vl/goto-char-timer-or-isearch))
(:with-feature evil
(:when-loaded
(defvar avy-all-windows)
(evil-define-motion vl/goto-char-timer-or-isearch (_count)
:type inclusive
:jump t
:repat abort
(evil-without-repeat
(evil-enclose-avy-for-motion
(when (eq (avy-goto-char-timer) t)
(add-hook 'isearch-update-post-hook #'vl/isearch-update-hook t)
(add-hook 'isearch-mode-end-hook #'vl/isearch-mode-end t)
(isearch-mode t)
(isearch-yank-string avy-text)))))))
(defun vl/isearch-update-hook()
(goto-char (match-beginning 0)))
(defun vl/isearch-mode-end()
(remove-hook 'isearch-update-post-hook 'vl-isearch-update-hook t)
(remove-hook 'isearch-mode-end-hook 'vl-isearch-mode-end t))
(advice-add 'avy-resume :after #'evil-normal-state))
(setup (:package evil-ts-obj)
(:hook-into
bash-ts-mode-hook
c-ts-mode-hook
c++-ts-mode-hook
nix-ts-mode-hook
python-ts-mode-hook
rust-ts-mode-hook
yaml-ts-mode-hook)
;; replaced inject from s/S to i/I
(setq
evil-ts-obj-enabled-keybindings '(generic-navigation navigation text-objects avy))
(:map
:nv "zx" #'evil-ts-obj-swap
:nv "zR" #'evil-ts-obj-replace
:nv "zr" #'evil-ts-obj-raise
:nv "zc" #'evil-ts-obj-clone-after
:nv "zC" #'evil-ts-obj-clone-before
:nv "zt" #'evil-ts-obj-teleport-after
:nv "zT" #'evil-ts-obj-teleport-before
:nv "zE" #'evil-ts-obj-extract-up
:nv "ze" #'evil-ts-obj-extract-down
:nv "zi" #'evil-ts-obj-inject-down
:nv "zI" #'evil-ts-obj-inject-up
:n "M-r" #'evil-ts-obj-raise-dwim
:n "M-j" #'evil-ts-obj-drag-down
:n "M-k" #'evil-ts-obj-drag-up
:n "M-c" #'evil-ts-obj-clone-after-dwim
:n "M-C" #'evil-ts-obj-clone-before-dwim
:n "M-h" #'evil-ts-obj-extract-up-dwim
:n "M-l" #'evil-ts-obj-extract-down-dwim
:n "M-i" #'evil-ts-obj-inject-down-dwim
:n "M-I" #'evil-ts-obj-inject-up-dwim
:n "M->" #'evil-ts-obj-slurp
:n "M-<" #'evil-ts-obj-barf))
(setup (:package evil-textobj-tree-sitter)
(defmacro vl/evil-textobj-goto (group &optional previous end query)
`(defun ,(intern (format "vl/evil-textobj-goto-%s%s%s" (if previous "previous-" "") (if end "end-" "") group)) ()
(interactive)
(evil-textobj-tree-sitter-goto-textobj ,group ,previous ,end ,query)))
(:map
;;"]a" param.outer is bound by evil-ts-obj
:m "]c" (vl/evil-textobj-goto "comment.outer")
:m "]d" (vl/evil-textobj-goto "function.outer")
:m "]D" (vl/evil-textobj-goto "call.outer")
:m "]C" (vl/evil-textobj-goto "class.outer")
:m "]v" (vl/evil-textobj-goto "conditional.outer")
:m "]l" (vl/evil-textobj-goto "loop.outer")
;;"[a" param.outer is bound by evil-ts-obj
:m "[c" (vl/evil-textobj-goto "comment.outer" t)
:m "[d" (vl/evil-textobj-goto "function.outer" t)
:m "[D" (vl/evil-textobj-goto "call.outer" t)
:m "[C" (vl/evil-textobj-goto "class.outer" t)
:m "[v" (vl/evil-textobj-goto "conditional.outer" t)
:m "[l" (vl/evil-textobj-goto "loop.outer")
:textobj "A" (evil-textobj-tree-sitter-get-textobj ("parameter.inner" "call.inner")) (evil-textobj-tree-sitter-get-textobj ("parameter.outer" "call.outer"))
:textobj "f" (evil-textobj-tree-sitter-get-textobj "function.inner") (evil-textobj-tree-sitter-get-textobj "function.outer")
:textobj "F" (evil-textobj-tree-sitter-get-textobj "call.inner") (evil-textobj-tree-sitter-get-textobj "call.outer")
:textobj "C" (evil-textobj-tree-sitter-get-textobj "class.inner") (evil-textobj-tree-sitter-get-textobj "class.outer")
:textobj "c" (evil-textobj-tree-sitter-get-textobj "comment.inner") (evil-textobj-tree-sitter-get-textobj "comment.outer")
:textobj "v" (evil-textobj-tree-sitter-get-textobj "conditional.inner") (evil-textobj-tree-sitter-get-textobj "conditional.outer")
:textobj "l" (evil-textobj-tree-sitter-get-textobj "loop.inner") (evil-textobj-tree-sitter-get-textobj "loop.outer")))
(setup (:package treesit-jump)
(:with-map global
(:map
:n "zj" #'treesit-jump-jump)))
(setup (:package evil-snipe)
(:hook-into on-first-input-hook)
(:with-mode evil-snipe-override-mode
(:hook-into on-first-input-hook))
(setq evil-snipe-override-evil-repeat-keys nil
evil-snipe-scope 'visible
evil-snipe-repeat-scope 'whole-visible
evil-snipe-smart-case t
evil-snipe-tab-increment t))(setup xref
(:with-map global
(:map
:n "gd" #'xref-find-definitions
:n "gA" #'xref-find-references
:n "gs" #'xref-find-apropos)))
(setup flymake
(:map
:n "g]" #'flymake-goto-next-error
:n "g[" #'flymake-goto-prev-error))
(setup emacs
(:with-map global
(:map
:n "gh" #'display-local-help)))Completion
Vertico
Configuration based on its README.
(setup (:package vertico vertico-posframe)
(:with-mode (vertico-mode vertico-multiform-mode)
(:hook-into on-first-input-hook))
(:with-map minibuffer-local-map
(:bind [escape] #'keyboard-quit))
(let ((display-in-buffer-down '(buffer
(vertico-buffer-display-action . ((display-buffer-in-side-window)
(side . bottom)
(slot . 0)
(window-height . 15)))))
(display-in-buffer-left '(buffer
(vertico-buffer-display-action . ((display-buffer-in-side-window)
(side . left)
(slot . 0)
(window-width . 40)))))
(display-in-posframe '(posframe
(vertico-posframe-poshandler . posframe-poshandler-frame-top-center)
(vertico-posframe-fallback-mode . vertico-buffer-mode))))
(setq vertico-scroll-margin 0
vertico-count 17
vertico-resize 'grow-only
vertico-cycle t
vertico-multiform-categories `((consult-grep ,@display-in-buffer-down)
(consult-location ,@display-in-buffer-left)
(imenu ,@display-in-buffer-left)
(buffer ,@display-in-posframe)
(file)
(t ,@display-in-posframe))
vertico-posframe-width 100)))
;; A few more useful configurations...
(setup emacs
(setq
;; Support opening new minibuffers from inside existing minibuffers.
enable-recursive-minibuffers t
;; Hide commands in M-x which do not work in the current mode. Vertico
;; commands are hidden in normal buffers. This setting is useful beyond
;; Vertico.
read-extended-command-predicate #'command-completion-default-include-p)
;; Add prompt indicator to `completing-read-multiple'.
;; We display [CRM<separator>], e.g., [CRM,] if the separator is a comma.
(defun crm-indicator (args)
(cons (format "[CRM%s] %s"
(replace-regexp-in-string
"\\`$$.*?]\\*\\|\\[.*?]\\*\\'" ""
crm-separator)
(car args))
(cdr args)))
(advice-add #'completing-read-multiple :filter-args #'crm-indicator)
;; Do not allow the cursor in the minibuffer prompt
(setq minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode))Orderless
Based on Minad’s configuration:
(setup (:package orderless)
(defun vl/orderless--consult-suffix ()
"Regexp which matches the end of string with Consult tofu support."
(if (boundp 'consult--tofu-regexp)
(concat consult--tofu-regexp "*\\'")
"\\'"))
;; Recognizes the following patterns:
;; * .ext (file extension)
;; * regexp$ (regexp matching at end)
(defun vl/orderless-consult-dispatch (word _index _total)
(cond
;; Ensure that $ works with Consult commands, which add disambiguation suffixes
((string-suffix-p "$" word)
`(orderless-regexp . ,(concat (substring word 0 -1) (vl/orderless--consult-suffix))))
;; File extensions
((and (or minibuffer-completing-file-name
(derived-mode-p 'eshell-mode))
(string-match-p "\\`\\.." word))
`(orderless-regexp . ,(concat "\\." (substring word 1) (vl/orderless--consult-suffix))))))
(:once 'on-first-input-hook
(:require orderless)
;; Define orderless style with initialism by default
(orderless-define-completion-style vl/orderless-with-initialism
(orderless-matching-styles '(orderless-initialism orderless-literal orderless-regexp))))
;; Certain dynamic completion tables (completion-table-dynamic) do not work
;; properly with orderless. One can add basic as a fallback. Basic will only
;; be used when orderless fails, which happens only for these special
;; tables. Also note that you may want to configure special styles for special
;; completion categories, e.g., partial-completion for files.
(setq completion-styles '(orderless basic)
completion-category-defaults nil
;;; Enable partial-completion for files.
;;; Either give orderless precedence or partial-completion.
;;; Note that completion-category-overrides is not really an override,
;;; but rather prepended to the default completion-styles.
;; completion-category-overrides '((file (styles orderless partial-completion))) ;; orderless is tried first
completion-category-overrides '((file (styles partial-completion)) ;; partial-completion is tried first
;; enable initialism by default for symbols
(command (styles vl/orderless-with-initialism))
(variable (styles vl/orderless-with-initialism))
(symbol (styles vl/orderless-with-initialism)))
orderless-component-separator #'orderless-escapable-split-on-space ;; allow escaping space with backslash!
orderless-style-dispatchers (list #'vl/orderless-consult-dispatch
#'orderless-kwd-dispatch
#'orderless-affix-dispatch)))Marginalia
(setup (:package marginalia)
(:hook-into after-init-hook)
(:with-map minibuffer-local-map
(:bind "M-A" marginalia-cycle)))Consult
(setup (:package consult)
(:global ;; C-c bindings in `mode-specific-map'
"C-c M-x" consult-mode-command
"C-c h" consult-history
"C-c k" consult-kmacro
"C-c m" consult-man
"C-c i" consult-info
[remap Info-search] #'consult-info
;; C-x bindings in `ctl-x-map'
"C-x M-:" consult-complex-command ;; orig. repeat-complex-command
"C-x b" consult-buffer ;; orig. switch-to-buffer
"C-x 4 b" consult-buffer-other-window ;; orig. switch-to-buffer-other-window
"C-x 5 b" consult-buffer-other-frame ;; orig. switch-to-buffer-other-frame
"C-x t b" consult-buffer-other-tab ;; orig. switch-to-buffer-other-tab
"C-x r b" consult-bookmark ;; orig. bookmark-jump
"C-x p b" consult-project-buffer ;; orig. project-switch-to-buffer
;; Custom M-# bindings for fast register access
"M-#" consult-register-load
"M-'" consult-register-store ;; orig. abbrev-prefix-mark (unrelated
"C-M-#" consult-register
;; Other custom bindings
"M-y" consult-yank-pop ;; orig. yank-pop
;; M-g bindings in `goto-map'
"M-g e" consult-compile-error
"M-g f" consult-flymake ;; Alternative: consult-flycheck
"M-g g" consult-goto-line ;; orig. goto-line
"M-g M-g" consult-goto-line ;; orig. goto-line
"M-g o" consult-outline ;; Alternative: consult-org-heading
"M-g m" consult-mark
"M-g k" consult-global-mark
"M-g i" consult-imenu
"M-g I" consult-imenu-multi
;; M-s bindings in `search-map'
"M-s d" consult-fd ;; Alternative: consult-find
"M-s c" consult-locate
"M-s g" consult-grep
"M-s G" consult-git-grep
"M-s r" consult-ripgrep
"M-s l" consult-line
"M-s L" consult-line-multi
"M-s k" consult-keep-lines
"M-s u" consult-focus-lines
;; Isearch integration
"M-s e" consult-isearch-history)
(:with-map isearch-mode-map
(:bind
"M-e" consult-isearch-history ;; orig. isearch-edit-string
"M-s e" consult-isearch-history ;; orig. isearch-edit-string
"M-s l" consult-line ;; needed by consult-line to detect isearch
"M-s L" consult-line-multi)) ;; needed by consult-line to detect isearch
;; Minibuffer history
(:with-map minibuffer-local-map
(:bind
"M-s" consult-history ;; orig. next-matching-history-element
"M-r" consult-history)) ;; orig. previous-matching-history-element
;; Enable automatic preview at point in the *Completions* buffer. This is
;; relevant when you use the default completion UI.
(:with-mode consult-preview-at-point-mode
(:hook-into completion-list-mode))
;; Tweak the register preview for `consult-register-load',
;; `consult-register-store' and the built-in commands. This improves the
;; register formatting, adds thin separator lines, register sorting and hides
;; the window mode line.
(advice-add #'register-preview :override #'consult-register-window)
(setq register-preview-delay 0.5)
;; Use Consult to select xref locations with preview
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
(:when-loaded
;; Optionally configure preview. The default value
;; is 'any, such that any key triggers the preview.
;; (setq consult-preview-key 'any)
;; (setq consult-preview-key "M-.")
;; (setq consult-preview-key '("S-<down>" "S-<up>"))
;; For some commands and buffer sources it is useful to configure the
;; :preview-key on a per-command basis using the `consult-customize' macro.
(consult-customize
consult-theme :preview-key '(:debounce 0.2 any)
consult-ripgrep consult-git-grep consult-grep
consult-bookmark consult-recent-file consult-xref
consult--source-bookmark consult--source-file-register
consult--source-recent-file consult--source-project-recent-file
;; :preview-key "M-."
:preview-key '(:debounce 0.4 any))
;; Optionally configure the narrowing key.
;; Both < and C-+ work reasonably well.
(setq consult-narrow-key "<") ;; "C-+"
;; Optionally make narrowing help available in the minibuffer.
;; You may want to use `embark-prefix-help-command' or which-key instead.
;; (keymap-set consult-narrow-map (concat consult-narrow-key " ?") #'consult-narrow-help)
))Shorten recentf paths in consult-buffer
From Consult wiki.
(defun vl/consult--source-recentf-items-uniq ()
(let ((ht (consult--buffer-file-hash))
file-name-handler-alist ;; No Tramp slowdown please.
items)
(dolist (file (vl/recentf-list-uniq) (nreverse items))
;; Emacs 29 abbreviates file paths by default, see
;; `recentf-filename-handlers'.
(unless (eq (aref (cdr file) 0) ?/)
(setcdr file (expand-file-name (cdr file))))
(unless (gethash (cdr file) ht)
(push (propertize
(car file)
'multi-category `(file . ,(cdr file)))
items)))))
(with-eval-after-load 'consult
(plist-put consult--source-recent-file
:items #'vl/consult--source-recentf-items-uniq))
(defun vl/recentf-list-uniq ()
(let* ((proposed (mapcar (lambda (f)
(cons (file-name-nondirectory f) f))
recentf-list))
(recentf-uniq proposed)
conflicts resol file)
;; collect conflicts
(while proposed
(setq file (pop proposed))
(if (assoc (car file) conflicts)
(push (cdr file) (cdr (assoc (car file) conflicts)))
(if (assoc (car file) proposed)
(push (list (car file) (cdr file)) conflicts))))
;; resolve conflicts
(dolist (name conflicts)
(let* ((files (mapcar (lambda (f)
;; data structure:
;; (file remaining-path curr-propos)
(list f
(file-name-directory f)
(file-name-nondirectory f)))
(cdr name)))
(curr-step (mapcar (lambda (f)
(file-name-nondirectory
(directory-file-name (cadr f))))
files)))
;; Quick check, if there are no duplicates, we are done.
(if (eq (length curr-step) (length (seq-uniq curr-step)))
(setq resol
(append resol
(mapcar (lambda (f)
(cons (car f)
(file-name-concat
(file-name-nondirectory
(directory-file-name (cadr f)))
(file-name-nondirectory (car f)))))
files)))
(while files
(let (files-remain)
(dolist (file files)
(let ((curr-propos (caddr file))
(curr-part (file-name-nondirectory
(directory-file-name (cadr file))))
(rest-path (file-name-directory
(directory-file-name (cadr file))))
(curr-step
(mapcar (lambda (f)
(file-name-nondirectory
(directory-file-name (cadr f))))
files)))
(cond ((length= (seq-uniq curr-step) 1)
;; If all elements of curr-step are equal, we skip
;; this path part.
(push (list (car file)
rest-path
curr-propos)
files-remain))
((member curr-part (cdr (member curr-part curr-step)))
;; There is more than one curr-part in curr-step
;; for this candidate.
(push (list (car file)
rest-path
(file-name-concat curr-part curr-propos))
files-remain))
(t
;; There is no repetition of curr-part in curr-step
;; for this candidate.
(push (cons (car file)
(file-name-concat curr-part curr-propos))
resol)))))
(setq files files-remain))))))
;; apply resolved conflicts
(let (items)
(dolist (file recentf-uniq (nreverse items))
(let ((curr-resol (assoc (cdr file) resol)))
(if curr-resol
(push (cons (cdr curr-resol) (cdr file)) items)
(push file items)))))))Corfu
(setup (:package corfu)
(:with-mode global-corfu-mode
(:hook-into on-first-input-hook))
(:with-mode corfu-popupinfo-mode
(:hook-into on-first-input-hook)))
(setup emacs
;; TAB cycle if there are only few candidates
;; (completion-cycle-threshold 3)
;; Enable indentation+completion using the TAB key.
;; `completion-at-point' is often bound to M-TAB.
(setq tab-always-indent 'complete
;; Emacs 30 and newer: Disable Ispell completion function.
;; Try `cape-dict' as an alternative.
text-mode-ispell-word-completion nil
;; Hide commands in M-x which do not apply to the current mode. Corfu
;; commands are hidden, since they are not used via M-x. This setting is
;; useful beyond Corfu.
read-extended-command-predicate #'command-completion-default-include-p))
;; Use Dabbrev with Corfu!
(setup dabbrev
;; Swap M-/ and C-M-/
(:global "M-/" dabbrev-completion
"C-M-/" dabbrev-expand)
(:when-loaded
(add-to-list 'dabbrev-ignored-buffer-regexps "\\` ")
;; Since 29.1, use `dabbrev-ignored-buffer-regexps' on older.
(add-to-list 'dabbrev-ignored-buffer-modes 'doc-view-mode)
(add-to-list 'dabbrev-ignored-buffer-modes 'pdf-view-mode)
(add-to-list 'dabbrev-ignored-buffer-modes 'tags-table-mode)))Embark
(setup (:package embark embark-consult)
(setq embark-indicators
'(embark-minimal-indicator ; default is embark-mixed-indicator
embark-highlight-indicator
embark-isearch-highlight-indicator))
(:with-feature vertico
(:when-loaded
(add-to-list 'vertico-multiform-categories '(embark-keybinding grid))))
(setq prefix-help-command #'embark-prefix-help-command)
(:global [remap describe-bindings] #'embark-bindings
"C-;" #'embark-act
"M-." #'embark-dwim)
(:with-map minibuffer-local-map
(:bind "C-;" #'embark-act)))Tree-Sitter
final: prev: {
treesit-grammars = prev.treesit-grammars.with-grammars (
grammars:
with pkgs.lib;
pipe grammars [
(filterAttrs (name: _: name != "recurseForDerivations"))
builtins.attrValues
]
);
}(setup (:package treesit-auto treesit-grammars)
(:autoload 'global-treesit-auto-mode)
(:with-mode global-treesit-auto-mode
(:hook-into after-init-hook))
(:when-loaded
(delete 'dockerfile treesit-auto-langs)
(treesit-auto-add-to-auto-mode-alist 'all)))LSP
emacs-lsp-booster = { url = "github:blahgeek/emacs-lsp-booster"; flake = false; };final: prev:
{
emacs-lsp-booster = pkgs.rustPlatform.buildRustPackage rec {
pname = "emacs-lsp-booster";
version = "0.2.1";
src = inputs.emacs-lsp-booster;
cargoLock = {
lockFile = "${src}/Cargo.lock";
};
doCheck = false;
};
}final: prev:
{
lsp-mode = prev.lsp-mode.overrideAttrs (_: {
postPatch = ''
substituteInPlace lsp-protocol.el \
--replace '(getenv "LSP_USE_PLISTS")' 't'
'';
});
}(setup (:package lsp-mode)
(setq lsp-use-plist t
lsp-eldoc-render-all t
lsp-keymap-prefix "C-c l"
lsp-diagnostics-provider :flymake
lsp-completion-provider :none
lsp-headerline-breadcrumb-enable nil)
(:map
:m "gD" #'lsp-find-declaration
:m "gy" #'lsp-find-type-definition
:m "gI" #'lsp-find-implementation
:n "g." #'lsp-execute-code-action
:o "d" (vl/key-dispatch 'evil-change #'lsp-rename)
:i "C-." #'lsp-execute-code-action)
(:when-loaded
(add-to-list 'lsp-file-watch-ignored-directories "[/\\$$\\.jj\\'")))
(setup eldoc
(setq eldoc-echo-area-use-multiline-p 1))
(setup (:package eldoc-box)
(map!
:m "gH" #'eldoc-box-help-at-point))
(setup (:package yasnippet yasnippet-snippets)
(:with-mode yas-global-mode
(:hook-into on-first-input-hook)))
(setup (:package consult-lsp)
(:with-mode lsp-mode
(:map
:e [remap xref-find-apropos] #'consult-lsp-symbols
:m "gs" #'consult-lsp-file-symbols
:m "gS" #'consult-lsp-symbols)))
(setup-define :lsp
(lambda ()
`(:hook lsp-deferred))
:documentation "Configure LSP")
(define-advice lsp-resolve-final-command (:around (old-fn cmd &optional test?) vl/lsp-resolve-final-command-lsp-booster-a)
"Prepend emacs-lsp-booster command to lsp CMD."
(let* ((orig-result (funcall old-fn cmd test?))
(exe (executable-find (car orig-result)))
(args (cdr orig-result)))
(if (and (not test?) ;; for check lsp-server-present?
(not (file-remote-p default-directory)) ;; see lsp-resolve-final-command, it would add extra shell wrapper
lsp-use-plists
(not (functionp 'json-rpc-connection)) ;; native json-rpc
(executable-find "emacs-lsp-booster"))
(progn
(message "Using emacs-lsp-booster for %s!" orig-result)
(append (list "emacs-lsp-booster" "--disable-bytecode" "--") (list exe) args))
orig-result)))emacs-lsp-boosterDired
(setup dired
(setq dired-listing-switches "-Alh --group-directories-first --time-style=iso"
dired-kill-when-opening-new-dired-buffer t)
(:global "M-i" vl/window-dired-vc-root-left)
(:bind "C-<return>" vl/window-dired-open-directory)
(defun vl/window-dired-vc-root-left (&optional directory-path)
"Creates *Dired-Side* like an IDE side explorer"
(interactive)
(add-hook 'dired-mode-hook 'dired-hide-details-mode)
(let ((dir (if directory-path
(dired-noselect directory-path)
(if (eq (vc-root-dir) nil)
(dired-noselect default-directory)
(dired-noselect (vc-root-dir))))))
(display-buffer-in-side-window
dir `((side . left)
(slot . 0)
(window-width . 30)
(window-parameters . ((no-other-window . t)
(no-delete-other-windows . t)
(mode-line-format . (" "
"%b"))))))
(with-current-buffer dir
(let ((window (get-buffer-window dir)))
(when window
(select-window window)
(rename-buffer "*Dired-Side*"))))))
(defun vl/window-dired-open-directory ()
"Open the current directory in *Dired-Side* side window."
(interactive)
(vl/window-dired-vc-root-left (dired-get-file-for-visit))))vips ffmpegthumbnailer mediainfo epub-thumbnailer p7zip(setup (:package dirvish)
(:when-loaded
(dirvish-override-dired-mode))
(:map
:n "?" #'dirvish-dispatch
:n "q" #'dirvish-quit
:n "b" #'dirvish-quick-access
:ng "f" #'dirvish-file-info-menu
:n "p" #'dirvish-yank
:ng "S" #'dirvish-quicksort
:n "F" #'dirvish-layout-toggle
:n "z" #'dirvish-history-jump
:n "gh" #'dirvish-subtree-up
:n "gl" #'dirvish-subtree-toggle
:n "h" #'dired-up-directory
:n "l" #'dired-find-file
:gm [left] #'dired-up-directory
:gm [right] #'dired-find-file
:m "[h" #'dirvish-history-go-backward
:m "]h" #'dirvish-history-go-forward
:m "[e" #'dirvish-emerge-next-group
:m "]e" #'dirvish-emerge-previous-group
:n "TAB" #'dirvish-subtree-toggle
:ng "M-b" #'dirvish-history-go-backward
:ng "M-f" #'dirvish-history-go-forward
:ng "M-n" #'dirvish-narrow
:ng "M-m" #'dirvish-mark-menu
:ng "M-s" #'dirvish-setup-menu
:ng "M-e" #'dirvish-emerge-menu
(:prefix ("y" . "yank")
:n "l" #'dirvish-copy-file-true-path
:n "n" #'dirvish-copy-file-name
:n "p" #'dirvish-copy-file-path
:n "r" #'dirvish-copy-remote-path
:n "y" #'dired-do-copy)
(:prefix ("s" . "symlinks")
:n "s" #'dirvish-symlink
:n "S" #'dirvish-relative-symlink
:n "h" #'dirvish-hardlink)))Languages
nil llvmPackages.clang-tools rust-analyzer basedpyright(setup (:package polymode))
(setup (:package nix-ts-mode)
(define-hostmode poly-nix-hostmode
:mode 'nix-mode)
(define-auto-innermode poly-nix-dynamic-innermode
:head-matcher (rx "#" blank (+ (any "a-z" "-")) (+ (any "\n" blank)) "''\n")
:tail-matcher (rx bol (+ blank) "'';")
:mode-matcher (cons (rx "#" blank (group (+ (any "a-z" "-"))) (* anychar)) 1)
:head-mode 'host
:tail-mode 'host)
(define-innermode poly-nix-interpolation-innermode
:mode 'nix-mode
:head-matcher (rx "${")
:tail-matcher #'pm-forward-sexp-tail-matcher
:head-mode 'body
:tail-mode 'body
:can-nest t)
(define-polymode poly-nix-mode
:hostmode 'poly-nix-hostmode
:innermodes '(poly-nix-dynamic-innermode))
(:with-mode poly-nix-mode
(:file-match "\\.nix\\'"))
(defalias 'nix-mode 'nix-ts-mode) ;; For org-mode code blocks to work
(:lsp))
(setup rust-ts-mode
(:lsp)
(setq rust-mode-treesitter-derive t)
(:hook (defun vl/remove-rust-ts-flymake-diagnostic-function-h()
(remove-hook 'flymake-diagnostic-functions #'rust-ts-flymake 'local))))
(setup (:package lsp-pyright)
(setq lsp-pyright-langserver-command "basedpyright")
(:with-mode python-base-mode
(:when-loaded
(:require lsp-pyright))))
(setup python-ts-mode
(:lsp))
(setup c-or-c++-ts-mode
(:lsp))
(setup (:package just-ts-mode)
(define-hostmode poly-just-hostmode
:mode 'just-ts-mode)
(defun vlaci/poly-get-innermode-for-exe (re)
(re-search-forward re (point-at-eol) t)
(let ((exe (match-string-no-properties 1)))
(cond ((equal exe "emacs") "emacs-lisp")
(t exe))))
(define-auto-innermode poly-just-innermode
:head-matcher (rx bol (+ (any blank)) "#!" (+ (any "a-z0-9_/ -")) "\n")
:tail-matcher #'pm-same-indent-tail-matcher
:mode-matcher (apply-partially #'vlaci/poly-get-innermode-for-exe (rx (+? anychar) "bin/env " (? "-S ") (group (+ (any "a-z-"))) (* anychar)))
:head-mode 'host
:tail-mode 'host)
(define-auto-innermode poly-just-script-innermode
:head-matcher (rx bol "[script('" (+? anychar) ":" (* (not "\n")) "\n")
:tail-matcher #'pm-same-indent-tail-matcher
:mode-matcher (apply-partially #'vlaci/poly-get-innermode-for-exe (rx bol "[script('" (group (+ (not "'"))) (* anychar)))
:head-mode 'host
:tail-mode 'host)
(define-polymode poly-just-mode
:hostmode 'poly-just-hostmode
:innermodes '(poly-just-innermode poly-just-script-innermode))
(:with-mode poly-just-mode
(:file-match (rx (or "justfile" ".just") string-end))))
(setup (:package markdown-mode))Direnv
Customizations are lifted from doomemacs
(setup (:package envrc)
(:with-mode envrc-global-mode
(:hook-into on-first-file-hook))
(defun vl/direnv-init-global-mode-earlier-h ()
(let ((fn #'envrc-global-mode-enable-in-buffer))
(if (not envrc-global-mode)
(remove-hook 'change-major-mode-after-body-hook fn)
(remove-hook 'after-change-major-mode-hook fn)
(add-hook 'change-major-mode-after-body-hook fn 100))))
(add-hook 'envrc-global-mode-hook #'vl/direnv-init-global-mode-earlier-h)
(defvar vl/orig-exec-path exec-path)
(define-advice envrc--update (:around (fn &rest args) vl/envrc--debounce-add-extra-path-a)
"Update only on non internal envrc related buffers keeping original path entries as well"
(when (not (string-prefix-p "*envrc" (buffer-name)))
(apply fn args)
(setq-local exec-path (append exec-path vl/orig-exec-path)))))Spell-checking
(setup (:package jinx)
(:with-mode global-jinx-mode
(:hook-into on-first-buffer-hook))
(setq jinx-languages "en_US hu_HU")
(:map
:m [remap evil-next-flyspell-error] #'jinx-next
:m [remap evil-prev-flyspell-error] #'jinx-previous
:m [remap ispell-word] #'jinx-correct)
(:when-loaded
(add-to-list 'vertico-multiform-categories
'(jinx grid (vertico-grid-annotate . 20)))))Magit
(setup (:package magit)
(setq magit-prefer-remote-upstream t
magit-save-repository-buffers nil
magit-diff-refine-hunk t
magit-define-global-key-bindings 'recommended
git-commit-major-mode 'markdown-mode)
(:when-loaded
(transient-append-suffix 'magit-pull "-r"
'("-a" "Autostash" "--autostash"))
(transient-append-suffix 'magit-commit "-n"
'("-s" "Dont show status" "--no-status"))
(add-to-list 'font-lock-ignore '(git-commit-mode markdown-fontify-headings)))
(:with-feature magit-commit
(:when-loaded
(transient-replace-suffix 'magit-commit 'magit-commit-autofixup
'("x" "Absorb changes" magit-commit-absorb))
(setq transient-levels '((magit-commit (magit-commit-absorb . 1))))))
(:with-feature project
(:when-loaded
(define-key project-prefix-map "m" #'magit-project-status)
(add-to-list 'project-switch-commands '(magit-project-status "Magit") t)))
(:with-feature smerge-mode
(:when-loaded
(map-keymap
(lambda (_key cmd)
(when (symbolp cmd)
(put cmd 'repeat-map 'smerge-basic-map)))
smerge-basic-map))))(setup ediff
(setq ediff-keep-variants nil
ediff-split-window-function #'split-window-horizontally
ediff-window-setup-function #'ediff-setup-windows-plain))I beautify diff-hl fringe indicators based on doomemacs’s idea mixed with u/jimegheek’s suggestion1.
(setup (:package diff-hl)
(:map
:m "]c" #'diff-hl-next-hunk
:m "[c" #'diff-hl-previous-hunk
:o "o" (vl/key-dispatch 'evil-delete #'diff-hl-show-hunk)
:o "p" (vl/key-dispatch 'evil-delete #'diff-hl-revert-hunk))
(:with-mode global-diff-hl-mode
(:hook-into on-first-buffer-hook))
(:with-mode diff-hl-dired-mode
(:hook-into dired-mode-hook))
(define-advice diff-hl-define-bitmaps (:after (&rest _) vl/diff-hl-thin-bitmaps-a)
(define-fringe-bitmap 'diff-hl-bmp-middle [#b11100000] nil nil '(center repeated))
(define-fringe-bitmap 'diff-hl-bmp-delete
[#b10000000
#b11000000
#b11100000
#b11110000
#b11110000
#b11100000
#b11000000
#b10000000]
nil nil 'center))
(defun vl/diff-hl-type-at-pos-fn (type _pos)
(if (eq type 'delete)
'diff-hl-bmp-delete
'diff-hl-bmp-middle))
(setq diff-hl-fringe-bmp-function #'vl/diff-hl-type-at-pos-fn)
(setq diff-hl-draw-borders nil)
(:hook (defun vl/make-diff-hl-faces-transparent-h ()
(set-face-background 'diff-hl-insert nil)
(set-face-background 'diff-hl-delete nil)
(set-face-background 'diff-hl-change nil))))Modus uses colored fringe background and grayscale foreground by default. We remap the foreground to the old background color, as we want thin fringes with transparent background.
(setup modus-themes
(:with-hook enable-theme-functions
(:hook (defun vl/modus-themes-prettify-diff-hl-fringes-h (theme)
(when (string-prefix-p "modus-" (symbol-name theme))
(with-eval-after-load 'diff-hl
(set-face-foreground 'diff-hl-insert (modus-themes-get-color-value 'bg-added-fringe))
(set-face-foreground 'diff-hl-delete (modus-themes-get-color-value 'bg-removed-fringe))
(set-face-foreground 'diff-hl-change (modus-themes-get-color-value 'bg-changed-fringe))))))))Formatting
(setup emacs
(setq indent-tabs-mode nil
mouse-yank-at-point t)) ;; paste at keyboard cursor instead of mouse pointer locationnixfmt-rfc-style nodePackages.prettier(setup (:package apheleia)
(:with-mode apheleia-global-mode
(:hook-into on-first-file-hook))
(:when-loaded
;; do not use apheleia-npx wrapper
(dolist (key (list
'prettier
'prettier-css
'prettier-html
'prettier-graphql
'prettier-javascript
'prettier-json
'prettier-markdown
'prettier-ruby
'prettier-scss
'prettier-scsss
'prettier-svelte
'pretter-typescript
'prettier-yaml))
(setf (alist-get key apheleia-formatters) (cdr (alist-get key apheleia-formatters))))
(setf (alist-get 'ruff-check apheleia-formatters) (list "ruff" "check" "--fix" "--exit-zero" "-"))
(setf (alist-get 'ruff-format apheleia-formatters) (list "ruff" "format" "-"))
(setf (alist-get 'rustfmt apheleia-formatters) (list "rustfmt" "--quiet" "--emit" "stdout" "--edition" "2024"))
(setf (alist-get 'python-mode apheleia-mode-alist) '(ruff-check ruff-format))
(setf (alist-get 'python-ts-mode apheleia-mode-alist) '(ruff-check ruff-format))))Passwords
(setup (:package auth-source-1password)
(:with-function auth-source-1password-enable
(:hook-into on-first-buffer-hook))
(setq auth-source-1password-vault "Emacs"))AI
(setup (:package chatgpt-shell)
(setq
chatgpt-shell-perplexity-api-key
(lambda() (auth-source-pick-first-password :host "Perplexity" :user "credential"))))
(setup (:package gptel)
(:when-loaded
(setq gptel-model 'sonar
gptel-backend (gptel-make-perplexity "Perplexity"
:key (lambda() (auth-source-pick-first-password :host "Perplexity" :user "credential"))
:stream t))))Projects
(setup project
(define-advice project-current (:around (fun &rest args) vl/project-current-per-frame-a)
(let ((proj (frame-parameter nil 'vl/project-current)))
(unless proj
(setq proj (apply fun args))
(modify-frame-parameters nil `((vl/project-current . ,proj))))
proj))
(define-advice project-switch-project (:before (&rest _) vl/project-switch-project-per-frame-a)
(modify-frame-parameters nil '((vl/project-current . nil)))))Eshell
Eat terminal
(setup (:package eat)
(add-hook 'eshell-load-hook #'eat-eshell-mode)){ lib, ... }:
{
programs.zsh.initContent = lib.mkBefore ''
[[ -n "$EAT_SHELL_INTEGRATION_DIR" ]] &&
source "$EAT_SHELL_INTEGRATION_DIR/zsh"
'';
}