987 lines
30 KiB
EmacsLisp
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))))
|