Files
dotfiles/emacs/init.el
Tim McCarthy 51565fadfe Streamline C-o bindings
- Replace wasd with hycontrol
- Remove repeat map for n/p
2025-04-03 22:51:48 -07:00

987 lines
30 KiB
EmacsLisp

;;; Preamble
;; Suppress native compilation warnings
(setq native-comp-async-report-warnings-errors nil)
;; Set a location for the custom file so it doesn't pollute this file.
(setq custom-file (locate-user-emacs-file "custom-vars.el"))
;;; Elpaca package manager
(defvar elpaca-installer-version 0.10)
(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 :depth 1
:files (:defaults "elpaca-test.el" (: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 (apply #'call-process `("git" nil ,buffer t "clone"
,@(when-let ((depth (plist-get order :depth)))
(list (format "--depth=%d" depth) "--no-single-branch"))
,(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))
(elpaca elpaca-use-package
(elpaca-use-package-mode))
(elpaca-wait)
;;; OS-specific tweaks
(defconst ON-LINUX (eq system-type 'gnu/linux))
(defconst ON-MAC (eq system-type 'darwin))
(defconst ON-BSD (or ON-MAC (eq system-type 'berkeley-unix)))
(defconst ON-WINDOWS (memq system-type '(cygwin windows-nt ms-dos)))
;; Emacs on macOS doesn't naturally find shell variables like PATH,
;; so to help it out we run a shell and load some variables from the env command.
(use-package exec-path-from-shell
:ensure t
:if (and ON-MAC (memq window-system '(mac ns)))
:config
(dolist (var '("NIX_SSL_CERT_FILE"
"NIX_PATH"
"NIX_PROFILES"))
(add-to-list 'exec-path-from-shell-variables var))
(exec-path-from-shell-initialize))
(elpaca-wait)
;;; Keybindings
(use-package emacs
:bind (("C-c o b" . reveal-in-file-browser)
("C-h C-i" . info-apropos)
("C-x )" . kmacro-end-or-call-macro)
;; Eval related mappings
("C-x e" . nil)
("C-x e b" . eval-buffer)
("C-x e d" . eval-defun)
("C-x e r" . eval-region)
("C-x e e" . eval-expression)
;; Window-management
("C-o" . nil)
("C-o C-o" . other-window)
("C-o o" . other-window)
("C-o b" . consult-buffer-other-window-no-focus)
("C-o B" . consult-buffer-other-window)
("C-o /" . split-window-right)
("C-o -" . split-window-below)
("C-o C-k" . delete-window)
("C-o 1" . delete-other-windows)
("C-o w" . hycontrol-windows)
("C-o f" . hycontrol-frame)
;; TODO C-/ for undo window state change
("C-o z" . my/zoom-window)
;; TODO other-window scrolling
("C-o n" . next-buffer)
("C-o p" . previous-buffer)
:repeat-map kmacro-repeat-map
(")" . kmacro-end-or-call-macro)))
;;;; which-key
(use-package which-key
:ensure t
:init
(which-key-mode)
(which-key-setup-side-window-bottom))
;;;; Mac modifier keys
;; Use right-option as regular option on Mac
(setq ns-alternate-modifier 'meta)
(setq ns-right-alternate-modifier 'none)
(setq ns-right-command-modifier 'meta)
;;; Editing
;;;; MWIM
(use-package mwim
:ensure t
:bind (([remap move-beginning-of-line] . mwim-beginning)
([remap move-end-of-line] . mwim-end)
:map visual-line-mode-map
("C-a" . mwim-beginning)
("C-e" . mwim-end))
:init
(defun mwim-visual-line-end ()
(mwim-point-at
(end-of-visual-line)))
(defun mwim-visual-line-beginning ()
(mwim-point-at
(beginning-of-visual-line)))
:custom
(mwim-beginning-position-functions
'(mwim-visual-line-beginning
mwim-line-beginning
mwim-code-beginning
mwim-comment-beginning))
(mwim-end-position-functions
'(mwim-visual-line-end
mwim-block-end
mwim-code-end
mwim-line-end)))
;;;; Isearch
;; Allow movement during isearch
;; C/M-v move to next/previous match not currently visible
;; M-</> move to first/last match in buffer
(setq isearch-allow-motion t)
(setq isearch-wrap-pause nil)
;;;; Avy
(use-package avy
:ensure t
:bind
(("C-j" . avy-goto-char-timer)
([remap goto-line] . avy-goto-line)
:map isearch-mode-map
("C-j" . avy-isearch)
:map lisp-interaction-mode-map
("C-j" . nil))
:demand t
:config
(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-embark-dwim (pt)
(unwind-protect
(save-excursion
(goto-char pt)
(embark-dwim))
(select-window
(cdr (ring-ref avy-ring 0))))
t)
(defun avy-action-isearch (pt)
(unwind-protect
(goto-char pt)
(isearch-forward-thing-at-point))
t)
(setf (alist-get ?\; avy-dispatch-alist) 'avy-action-embark)
(setf (alist-get ?\: avy-dispatch-alist) 'avy-action-embark-dwim)
(setf (alist-get ?i avy-dispatch-alist) 'avy-action-isearch)
(setq avy-timeout-seconds 0.25)
;; Always show candidates even when there's just one, to give an
;; opportunity to select an avy action
(setq avy-single-candidate-jump nil))
(use-package avy-zap
:ensure t
:bind (("C-z" . avy-zap-up-to-char-dwim)
("M-z" . avy-zap-to-char-dwim)))
;;;; Expand-region
(use-package expand-region
:ensure t
:bind (("C-=" . er/expand-region)))
;;;; Dot-mode
(use-package dot-mode
:ensure t
:config
(global-dot-mode t))
;;;; Vundo
(use-package vundo
:ensure t
:bind ("M-/" . vundo))
;;;; Outli
(use-package outli
:ensure (:host github :repo "jdtsmith/outli")
:bind (:map outli-mode-map
("C-c C-p" . (lambda () (interactive) (outline-back-to-heading))))
:hook ((prog-mode . outli-mode)
(text-mode . outli-mode)
(outli-mode . reveal-mode)))
(use-package reveal
:bind (:map reveal-mode-map
([remap move-beginning-of-line] . mwim-beginning)
([remap move-end-of-line] . mwim-end)))
;;;; Jinx
(use-package jinx
:hook (emacs-startup . global-jinx-mode)
:bind ([remap transpose-chars] . jinx-correct))
;;; Completion
;;;; Vertico
(use-package vertico
:ensure t
:init
(vertico-mode)
(vertico-multiform-mode 1)
(add-to-list 'vertico-multiform-categories
'(jinx grid (vertico-grid-annotate . 20))))
(use-package vertico-directory
:after vertico
:bind (:map vertico-map
("C-h" . vertico-directory-delete-word)
("C-l" . vertico-directory-enter)))
;;;; Marginalia
(use-package marginalia
:ensure t
;; Either bind `marginalia-cycle' globally or only in the minibuffer
:bind (:map minibuffer-local-map
("M-A" . marginalia-cycle))
:init
(marginalia-mode))
;;;; Orderless
(use-package orderless
:ensure t
:init
(setq completion-ignore-case t)
(setq completion-styles '(orderless basic)
completion-category-defaults nil
completion-category-overrides '((file (styles partial-completion)))))
;;;; Consult
(use-package consult
:ensure t
;; Replace bindings. Lazily loaded due by `use-package'.
:bind (("C-x b" . consult-buffer)
("C-x C-b" . consult-buffer)
("C-x p g" . consult-ripgrep)
("C-x p b" . consult-project-buffer)
("C-x p F" . consult-fd)
("M-i" . consult-imenu)
("M-y" . consult-yank-from-kill-ring))
;; Enable automatic preview at point in the *Completions* buffer. This is
;; relevant when you use the default completion UI.
;; :hook (completion-list-mode . consult-preview-at-point-mode)
:init
;; Optionally configure the register formatting. This improves the register
;; preview for `consult-register', `consult-register-load',
;; `consult-register-store' and the Emacs built-ins.
(setq register-preview-delay 0.5
register-preview-function #'consult-register-format)
;; Optionally tweak the register preview window.
;; This adds thin lines, sorting and hides the mode line of the window.
(advice-add #'register-preview :override #'consult-register-window)
;; Use Consult to select xref locations with preview
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
(defun consult-buffer-other-window-no-focus ()
(interactive)
(with-selected-window (next-window)
(consult-buffer)))
:config
;; 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 consult-xref consult-bookmark
consult-ripgrep consult-git-grep consult-grep
:preview-key '(:debounce 0.4 any)
consult-recent-file consult-buffer
consult--source-bookmark consult--source-recent-file
consult--source-project-recent-file
:preview-key "C-."))
(use-package consult-dir
:ensure t
:bind (("C-x C-d" . consult-dir)
:map vertico-map
("C-x C-d" . consult-dir)
("C-x C-j" . consult-dir-jump-file)))
;;;; Embark
(use-package embark
:ensure t
:bind
(("C-;" . embark-act) ;; pick some comfortable binding
("C-:" . embark-dwim) ;; good alternative: M-.
("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
:init
;; Optionally replace the key help with a completing-read interface
(setq prefix-help-command #'embark-prefix-help-command)
:config
;; Hide the mode line of the Embark live/completions buffers
(add-to-list 'display-buffer-alist
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
;; Consult users will also want the embark-consult package.
(use-package embark-consult
:ensure t
:after (embark consult)
:demand t ; only necessary if you have the hook below
;; if you want to have consult previews as you move around an
;; auto-updating embark collect buffer
:hook
(embark-collect-mode . consult-preview-at-point-mode))
;;;; completion-at-point
(use-package corfu
:ensure t
:bind
(("C-'" . completion-at-point))
:init
(global-corfu-mode))
(use-package cape
:ensure t
;; Bind prefix keymap providing all Cape commands under a mnemonic key.
:bind ("C-c p" . cape-prefix-map) ;; Alternative keys: M-p, M-+, ...
:custom
(cape-dabbrev-check-other-buffers nil)
(cape-file-directory-must-exist nil)
:init
(add-hook 'completion-at-point-functions #'cape-dabbrev)
(add-hook 'completion-at-point-functions #'cape-file)
(add-hook 'completion-at-point-functions #'cape-elisp-block))
;;;; yasnippet
(use-package yasnippet
:ensure t
:init
(require 'em-cmpl)
(yas-global-mode 1))
;;; Appearance
;;;; Theme
(use-package doom-themes
:ensure t
:config
(setq doom-themes-enable-bold t
doom-themes-enable-italic t)
(load-theme 'doom-monokai-pro t)
;; Lighten comments slightly for better contrast
(custom-theme-set-faces
'doom-monokai-pro
'(font-lock-comment-face
((t (:foreground "#9A989A")))))
(enable-theme 'doom-monokai-pro)
;; Enable flashing mode-line on errors
(doom-themes-visual-bell-config)
;; Corrects (and improves) org-mode's native fontification.
(doom-themes-org-config))
;;;; Modeline
(use-package doom-modeline
:ensure t
:init
(doom-modeline-mode 1)
(setq doom-modeline-total-line-number nil))
(use-package nerd-icons
:ensure t)
;;;; Window size
(modify-all-frames-parameters '((alpha 99 99)
(top . 50)
(left . 100)
(width . 120)
(height . 60)))
;;;; Fonts
(setq my/font-candidates
'("FiraCode Nerd Font Mono" "Fira Code" "Menlo" "Deja Vu Sans"))
(defvar my/font
(seq-find #'x-list-fonts my/font-candidates)
"The default font to use.")
(defvar my/font-size
(if ON-MAC
14
12)
"The default font size to use.")
(modify-all-frames-parameters
`((font . ,(concat my/font "-" (number-to-string my/font-size)))))
;;;; Hide clutter
(setq inhibit-startup-message t
use-dialog-box nil)
(tool-bar-mode -1)
(scroll-bar-mode -1)
;; On a Mac, the menu bar doesn't take up screen real-estate, so leave it on
(unless ON-MAC
(menu-bar-mode -1))
;;; Window management
(setq switch-to-buffer-obey-display-actions t)
(use-package emacs
:bind (("s-t" . tab-bar-new-tab)
("s-w" . tab-bar-close-tab)
("s-}" . tab-bar-switch-to-next-tab)
("s-{" . tab-bar-switch-to-prev-tab))
:init
(tab-bar-mode 1))
;;;; Popper
(use-package popper
:ensure t
:bind (("M-o" . popper-toggle)
("C-o t" . popper-toggle-type)
("C-o ." . popper-cycle))
:init
(setq popper-reference-buffers
'("\\*Messages\\*"
"Output\\*$"
"\\*Async Shell Command\\*"
"\\*Backtrace\\*"
"\\*Compile-Log\\*"
"\\*Flycheck errors\\*"
help-mode
compilation-mode
"\\*eshell.*\\*" eshell-mode))
(defun my/popper-window-height (window)
"Make eshell popups take half the frame height, otherwise defer to popper--fit-window-height"
(let ((buffer (window-buffer window)))
(if (with-current-buffer buffer
(derived-mode-p 'eshell-mode))
(/ (frame-height (window-frame window)) 2)
(popper--fit-window-height window))))
(setq popper-window-height #'my/popper-window-height)
;; (setq popper-group-function #'popper-group-by-project)
(popper-mode +1)
(popper-echo-mode +1))
;;;; Zooming
(defvar my/zoom-saved-windows nil
"Variable to store the current window configuration for my/zoom.")
(defun my/zoom-window ()
"Delete other windows, or restore the saved window configuration if available."
(interactive)
;; Clean up if the saved config isn't valid anymore.
(if (and my/zoom-saved-windows
(not (window-configuration-p my/zoom-saved-windows)))
(setq my/zoom-saved-windows nil))
(if my/zoom-saved-windows
(progn
(set-window-configuration my/zoom-saved-windows)
(setq my/zoom-saved-windows nil))
(setq my/zoom-saved-windows (current-window-configuration))
(if (window-parameter (selected-window) 'window-side)
(shrink-window -100)
(delete-other-windows))))
;;; Dired
(use-package dired
:bind (:map dired-mode-map
("C-o" . nil)
("h" . dired-up-directory)))
(use-package dirvish
:ensure t
:init
(dirvish-override-dired-mode))
;;; Misc tweaks
;; When scrolling by page and hitting top/bottom, move cursor to top/bottom of buffer
(setq-default scroll-error-top-bottom t)
;; Revert buffers when the underlying file has changed
(global-auto-revert-mode 1)
;; Remember recent files
(recentf-mode 1)
(setq recentf-max-menu-items 20)
(setq recentf-max-saved-items 1000)
;; Save what you enter into minibuffer prompts
(setq history-length 25)
(savehist-mode 1)
;; Visually mark the line the cursor is on
(global-hl-line-mode 1)
;; Enable repeat mode. Keymaps are defined through use-package's :repeat-map directive.
(repeat-mode 1)
;; Answer questions with y/n instead of yes/no
(defalias 'yes-or-no-p 'y-or-n-p)
;; when opening a help window, switch focus to it unless a help window was already open
(setq-default help-window-select t)
;; tabs are for monsters
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq-default sentence-end-double-space nil)
;; Don't clobber the OS clipboard completely
(setq save-interprogram-paste-before-kill t)
;; automatically cleanup whitespace on save
(defun my-whitespace-cleanup ()
"Run `whitespace-cleanup' except for TSV files."
(unless (or (derived-mode-p 'text-mode) ; Adjust this check if necessary
(string-match "\\.tsv\\'" buffer-file-name))
(whitespace-cleanup)))
(add-hook 'before-save-hook 'my-whitespace-cleanup)
;; clean up backup file spam
(setq backup-directory-alist `(("." . "~/.saves")
("/etc/\\.*" . nil))
delete-old-versions t
kept-new-versions 4
kept-old-versions 2
version-control t)
;;;; Project
(use-package project
:config
;; Make project.el recognize directories with a .project file as project roots
(add-to-list 'project-vc-extra-root-markers ".project"))
;;;; hl-todo
(use-package hl-todo
:ensure t
:init
(global-hl-todo-mode))
;;;; View-mode
;; Read-only buffers activate view mode, toggle read only with C-x C-q
(use-package view
:init
(setq view-read-only t)
:bind (:map view-mode-map
("v" . View-scroll-half-page-forward)
("V" . View-scroll-half-page-backward)))
;;;; Direnv
(use-package direnv
:ensure t
:config
(setq direnv-always-show-summary nil)
(direnv-mode))
;;;; Explain-pause
(use-package explain-pause-mode
:ensure (:host github :repo "lastquestion/explain-pause-mode"))
;;;; Reveal-in-finder
(defun first-executable (candidates)
(seq-find #'executable-find candidates))
;; Reveal in finder/nautilus/whatever
(defun reveal-in-file-browser ()
(interactive)
(call-process
(first-executable '("xdg-open" "open"))
nil nil nil "."))
;;;; Hyperbole
(use-package hyperbole
:ensure (:repo "https://git.savannah.gnu.org/git/hyperbole.git" :tag "hyperbole-9.0.1")
:bind (("C-o h" . hyperbole)
:map hyperbole-mode-map
("M-<return>" . nil)
("C-o <return>" . hkey-either))
:custom
(hkey-init nil)
:init
(hyperbole-mode 1))
;;;; Emacs Server
(server-start)
;;; Org-mode
(use-package org
:bind (("C-c o ," . my/org-clear-all)
("C-c o s" . org-screenshot)
("C-c SPC" . org-table-blank-field)
;; Unbind to make room for avy and mwim
:map org-mode-map
("C-j" . nil)
("C-a" . mwim-beginning)
("C-e" . mwim-end)
:map org-src-mode-map
("C-c C-c" . org-edit-src-exit))
:hook ((org-mode . visual-line-mode))
:custom
(org-special-ctrl-a/e 'reversed)
(org-hide-emphasis-markers t)
(org-use-speed-commands t)
(add-to-list 'org-speed-commands '("h" . org-fold-hide-sublevels))
(org-use-fast-todo-selection 'expert)
(org-todo-keywords '((sequence "TODO(t)" "BLOCKED(b)" "|" "DONE(d)")))
(org-image-actual-width '(800)))
(use-package org-bullets
:ensure t
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))))
;; Eagerly load org-mode
(with-temp-buffer (org-mode))
(defun my/org-clear-all ()
(interactive)
(goto-char 0)
(org-map-entries
(lambda ()
(org-todo "")))
;;(flush-lines "CLOSED")
(message "Entries cleared."))
;;;; Screenshots for games
;; Steps to set up:
;; 1. Install ShareX, add ShareX folder to PATH
;; 2. Create hotkey named GameNotes with settings:
;; Capture preconfigured window
;; Capture -> Pre configured window title: <game name>
;; Override after capture tasks to "Save image to file"
;; Override screenshots folder to org-screenshot-import-path
;; Override Upload settings -> File naming -> Name pattern
;; "GameNotes_%t_%y-%mo-%d-%h-%mi-%s.%ms"
;; 3. Put the following at the top of the org file:
;; #+STARTUP: inlineimages
;; #+ATTR_HTML: :width 500px
(defvar org-screenshot-import-path "/mnt/c/Users/my/Seafile/Games/Screenshots/")
(defvar org-screenshot-export-path org-screenshot-import-path)
(defvar org-screenshot-exec-path "ShareX.exe")
(defvar org-screenshot-exec-args "-s -workflow GameNotes")
(defun org-screenshot ()
(interactive)
(let* ((heading-name (org-get-heading t t t t))
;; Strip out the font information from the heading name
(_ (set-text-properties 0 (length heading-name) nil heading-name))
(_ (shell-command (concat org-screenshot-exec-path " " org-screenshot-exec-args)))
;; shell-command returns immediately, so wait until the screenshot is likely to have been taken
(_ (sit-for 0.35))
(in-file (car (nreverse (directory-files org-screenshot-import-path 'full "GameNotes"))))
(out-dir-name (file-name-sans-extension (file-name-nondirectory buffer-file-name)))
(out-dir (file-name-as-directory (concat (file-name-as-directory org-screenshot-export-path) out-dir-name)))
(out-file (concat out-dir heading-name "-" (org-id-uuid) ".png")))
(if (stringp in-file)
(save-excursion
(unless (file-exists-p out-dir)
(make-directory out-dir))
(rename-file in-file out-file)
(message (format "Saved %S to %S" in-file out-file))
(org-next-visible-heading 1)
(org-open-line 1)
(org-insert-link nil out-file))
(progn
(message "Failed to find saved screenshot.")))))
;;; Magit
(use-package magit
:after seq
:ensure t
:custom
(magit-save-repository-buffers 'dontask)
;; On Mac, use system git because Nix-installed git is very slow under magit
(magit-git-executable (if ON-MAC "/usr/bin/git" "git"))
:bind (("C-x g" . magit-status)
:map magit-mode-map
(":" . execute-extended-command)
("x" . magit-discard)
:map magit-diff-section-map
("C-j" . nil)
:map magit-file-section-map
;; Unbind to stop overriding avy
("C-j" . nil)))
;;; Programming languages
;;;; Treesitter
(use-package treesit-auto
:ensure t
:custom
(treesit-auto-install 'prompt)
:config
(treesit-auto-add-to-auto-mode-alist 'all)
(global-treesit-auto-mode))
;;;; Markdown
(use-package markdown-mode
:ensure t
:mode ("README\\.md\\'" . gfm-mode)
:hook ((markdown-mode . visual-line-mode)))
;; TODO - replace with https://github.com/sshaw/copy-as-format if necessary
(defun copy-source-for-reddit ()
(interactive)
(let ((contents (buffer-substring (point) (mark))))
(with-temp-buffer
(insert contents)
(mark-whole-buffer)
(indent-rigidly (point) (mark) 4 t)
(mark-whole-buffer)
(kill-ring-save 0 0 t))))
(keymap-global-set "C-c o r" #'copy-source-for-reddit)
;;;; YAML
(use-package yaml-mode
:ensure t)
;;;;
(use-package csv-mode
:ensure t)
;;;; Rust
(use-package rust-mode
:ensure t)
;;;; Python
(setq major-mode-remap-alist
'((python-mode . python-ts-mode)))
(setq lsp-pylsp-plugins-flake8-ignore '("D100" "D101" "D102"))
;;;; Docker
(use-package dockerfile-mode
:ensure t)
;;;; LSP
(use-package lsp-mode
:ensure t
:commands lsp
:custom
(lsp-completion-provider :none)
:init
(setq lsp-keymap-prefix "C-c l")
;; from https://github.com/minad/corfu/wiki
(defun my/lsp-mode-setup-completion ()
(setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
'(orderless)))
(defun lsp-booster--advice-json-parse (old-fn &rest args)
"Try to parse bytecode instead of json."
(or
(when (equal (following-char) ?#)
(let ((bytecode (read (current-buffer))))
(when (byte-code-function-p bytecode)
(funcall bytecode))))
(apply old-fn args)))
(advice-add (if (progn (require 'json)
(fboundp 'json-parse-buffer))
'json-parse-buffer
'json-read)
:around
#'lsp-booster--advice-json-parse)
(defun lsp-booster--advice-final-command (old-fn cmd &optional test?)
"Prepend emacs-lsp-booster command to lsp CMD."
(let ((orig-result (funcall old-fn cmd test?)))
(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
(when-let ((command-from-exec-path (executable-find (car orig-result)))) ;; resolve command from exec-path (in case not found in $PATH)
(setcar orig-result command-from-exec-path))
(message "Using emacs-lsp-booster for %s!" orig-result)
(cons "emacs-lsp-booster" orig-result))
orig-result)))
(advice-add 'lsp-resolve-final-command :around #'lsp-booster--advice-final-command)
:hook ((rust-ts-mode . lsp-deferred)
(python-ts-mode . lsp-deferred)
(lsp-mode . lsp-enable-which-key-integration)
(lsp-completion-mode . my/lsp-mode-setup-completion)))
(use-package lsp-ui
:ensure t
:commands lsp-ui-mode
:custom
(lsp-ui-sideline-show-diagnostics nil))
;;;; Flycheck
(use-package flycheck
:ensure t
:init
(setq flycheck-keymap-prefix (kbd "C-c e"))
(global-flycheck-mode)
;; Elisp lint errors are annoying, so disable flycheck in elisp modes
:hook (emacs-lisp-mode . (lambda () (flycheck-mode -1)))
:bind (:repeat-map flycheck-repeat-map
("n" . flycheck-next-error)
("p" . flycheck-previous-error)))
;;;; Nix
(use-package nix-mode
:ensure t
:mode "\\.nix\\'")
;;;; Lisp
(if (string-match "x86_64-apple" system-configuration)
;; The latest version of parinfer-rust-mode is incompatible with x86 Macs, so fall back on an earlier commit
(use-package parinfer-rust-mode
:ensure (:host
github
:repo
"justinbarclay/parinfer-rust-mode"
:ref
"8df117a3b54d9e01266a3905b132a1d082944702")
:hook emacs-lisp-mode)
(use-package parinfer-rust-mode
:ensure t
:hook emacs-lisp-mode))
;;;; PHP
(use-package php-mode
:ensure t)
;;; Eshell
;;;; Eshell
(use-package eshell
:bind (("M-`" . my/eshell-toggle))
:config
(setq eshell-destroy-buffer-when-process-dies t
eshell-scroll-to-bottom-on-input t
eshell-history-size 10000
eshell-save-history-on-exit t
eshell-visual-commands '()
eshell-banner-message "")
(defun my/eshell-toggle ()
"Toggle eshell based on context.
If in eshell, call `popper-toggle`.
If not in eshell but in a project, call `project-eshell`.
Otherwise, call `eshell`."
(interactive)
(if (derived-mode-p 'eshell-mode)
(popper-toggle)
(if (project-current)
(project-eshell)
(eshell))))
;; From https://karthinks.com/software/jumping-directories-in-eshell/
(defun eshell/j (&optional regexp)
"Navigate to a previously visited directory in eshell, or to
any directory proffered by `consult-dir'."
(let ((eshell-dirs (delete-dups
(mapcar 'abbreviate-file-name
(ring-elements eshell-last-dir-ring)))))
(cond
((and (not regexp) (featurep 'consult-dir))
(let* ((consult-dir--source-eshell `(:name "Eshell"
:narrow ?e
:category file
:face consult-file
:items ,eshell-dirs))
(consult-dir-sources (cons consult-dir--source-eshell
consult-dir-sources)))
(eshell/cd (substring-no-properties
(consult-dir--pick "Switch directory: ")))))
(t (eshell/cd (if regexp (eshell-find-previous-directory regexp)
(completing-read "cd: " eshell-dirs))))))))
(use-package eshell
:bind (:map eshell-hist-mode-map
("M-r" . consult-history))
:after em-hist)
(use-package pcre2el
:ensure t
:config
(defmacro prx (&rest rx-sexp)
"Convert rx-compatible regular expressions to PCRE."
`(rxt-elisp-to-pcre (rx ,@rx-sexp))))
(use-package eshell-p10k
:ensure (:host github :repo "elken/eshell-p10k")
:config
(eshell-p10k-def-segment time
""
(format-time-string "%H:%M" (current-time))
'eshell-p10k-distro-face)
(defun num-exitcode-string ()
(if (= eshell-last-command-status 0)
(number-to-string eshell-p10k--prompt-num-index)
(format "%d (%d)" eshell-p10k--prompt-num-index eshell-last-command-status)))
(defun num-exitcode-face ()
(if (= eshell-last-command-status 0)
'eshell-p10k-git-clean-face
'eshell-p10k-git-dirty-face))
(eshell-p10k-def-segment num-exitcode
""
(num-exitcode-string)
(num-exitcode-face))
(defun eshell-p10k-prompt-function ()
"Prompt defining function."
(eshell-p10k-def-prompt '(num-exitcode time dir)))
(setq eshell-prompt-function #'eshell-p10k-prompt-function
eshell-prompt-regexp eshell-p10k-prompt-string))
(use-package eshell-syntax-highlighting
:after eshell-mode
:ensure t
:config
(eshell-syntax-highlighting-global-mode +1))
;;;; Eat
(use-package eat
:ensure t
:custom
(eat-term-name "xterm-256color")
:init
(eval-after-load 'eshell #'eat-eshell-mode)
(eval-after-load 'eshell #'eat-eshell-visual-command-mode)
:config
(defun advise-eat-keymap (map)
(define-key map [?\C-o] nil)
(define-key map (vector meta-prefix-char ?`) nil)
(define-key map [S-v] #'eat-yank))
(define-advice eat-eshell-semi-char-mode
(:after (&rest r))
(advise-eat-keymap eat-eshell-semi-char-mode-map))
(define-advice eat-semi-char-mode
(:after (&rest r))
(advise-eat-keymap eat-semi-char-mode-map))
:hook (eat-mode . (lambda ()
(setq display-line-numbers nil)
(hl-line-mode -1))))