diff --git a/home.org b/home.org index 3da09b7..fb99323 100644 --- a/home.org +++ b/home.org @@ -840,7 +840,6 @@ These are just some general settings that apply everywhere. First, a few package free-keys ligature mixed-pitch - perspective popper ]; extraConfig = builtins.readFile ./emacs-global.el; @@ -851,26 +850,9 @@ These are just some general settings that apply everywhere. First, a few package Turn off some default settings that I don't find useful. #+BEGIN_SRC elisp - (setopt inhibit-startup-screen t) - (setopt initial-scratch-message nil) - (setopt sentence-end-double-space nil) - (menu-bar-mode -1) - (scroll-bar-mode -1) - (tool-bar-mode -1) - (setopt make-backup-files nil) - (setopt split-width-threshold 200) - (setopt split-height-threshold nil) - (setopt display-buffer-base-action - '((display-buffer-reuse-window display-buffer-same-window - display-buffer-in-previous-window - display-buffer-use-some-window))) - (setopt view-read-only t) (setopt save-interprogram-paste-before-kill t) (setopt kill-whole-line t) - (delete-selection-mode t) (column-number-mode t) - (setopt auto-save-default nil) - (setopt create-lockfiles nil) (setopt enable-recursive-minibuffers t) (setopt line-spacing 0.15) @@ -881,6 +863,50 @@ Turn off some default settings that I don't find useful. (tab-bar-mode) #+END_SRC +Don't show the welcome screen, and keep the scratch buffer empty by default. These are not useful +after you've started emacs more than a few times. + +#+begin_src elisp + (setopt inhibit-startup-screen t) + (setopt initial-scratch-message nil) +#+end_src + +Turn off all the ugly graphical crap. + +#+begin_src elisp + (menu-bar-mode -1) + (scroll-bar-mode -1) + (tool-bar-mode -1) +#+end_src + +I don't find emacs's backup files useful, and they seem to litter my directories. I could maybe see +a use if I wasn't using git. But, git is so easy to use, that I pretty much use it everywhere. I +don't need a poor man's local history. + +Also, why do I have to set so many variables just to get emacs to stop putting extra files +everywhere? + +#+begin_src elisp + (setopt make-backup-files nil) + (setopt create-lockfiles nil) + (setopt auto-save-default nil) +#+end_src + +Turn on ~view-mode~ automatically when the buffer is read-only. This just helps out with navigation +a bit. I almost want to open files read-only by default, just so I can use ~view-mode~ (maybe with +some other keybindings) all the time. But, I think one more invisible flag to manage is a bit much. + +#+begin_src elisp + (setopt view-read-only t) +#+end_src + +I don't really do anything with sentences. But, I also never put two spaces after a period. So, at +least this setting is correct. + +#+begin_src elisp + (setopt sentence-end-double-space nil) +#+end_src + Make sure which-key mode is always on. The on-the-fly keymap help it gives is indespensable. #+BEGIN_SRC elisp @@ -909,23 +935,13 @@ already indented, then try to complete the thing at point." Recommended by [[htt #+END_SRC Set the cursor to be a bar. I'm not sure what the benefit would be of a block cursor, since the -current character isn't really "selected" like it would be in Helix. When I had God mode set up, I -switched to a block cursor during normal mode, just because I was so used to it from Helix and -Vim. Now, I don't think there's really a need to ever switch. +current character isn't really "selected" like it would be in Helix. I switch this to a box when in +god mode / read-only buffers, like vim would. #+BEGIN_SRC elisp (setq-default cursor-type 'bar) #+END_SRC -Emacs is supposed to be all about automation (maybe?). There's basically never a case where I don't -want changes I've made to be immediately saved to disk. So let's have Emacs do that. There's still a -fairly long (~5 sec) delay before changes are saved, which is kind of annoying. Maybe I'll look into -that at some point? - -#+BEGIN_SRC elisp - ; (auto-save-visited-mode) -#+END_SRC - Repeat-mode takes care of eliminating some annoying duplicate keypresses. I mostly use repeat-mode for two-character commands that need to be repeated; ~devil~ also has some single-key-with-modifier repeats set up that only apply in that mode. @@ -1012,6 +1028,9 @@ Set up a few project- / workspace-related key bindings. ("s-s" . tab-bar-switch-to-tab))) #+END_SRC +#+RESULTS: +: tab-bar-switch-to-tab + Don't run a server. I'm not using this, I can always turn it back on if I need to. #+BEGIN_SRC elisp @@ -1060,229 +1079,227 @@ windows. I place windows in the bottom or on the right mostly depending on how w content to be. #+BEGIN_SRC elisp - (defun jf/window-children (window) - (let ((result nil) - (child (window-child window))) - (while child - (push child result) - (setq child (window-next-sibling child))) - (reverse result))) + (defun jf/window-children (window) + (let ((result nil) + (child (window-child window))) + (while child + (push child result) + (setq child (window-next-sibling child))) + (reverse result))) - (defun jf/enforce-right-side-heights (win) - (let ((slot-heights '((-1 . 20))) - (parent (window-parent win))) - (when parent - (let ((children (jf/window-children parent))) - (when (> (length children) 1) - (cl-loop for child in children - for slot = (window-parameter child 'window-slot) - for height = (alist-get slot slot-heights) - when height do (progn (window-resize child (- height (window-height child))) - (window-preserve-size child nil t)))))))) + (defun jf/window-main-root (&optional frame) + "Return FRAME's main (non-side) window or window subtree." + (let* ((root (frame-root-window (or frame (selected-frame)))) + (children (jf/window-children root))) + (if (seq-some (lambda (w) (window-parameter w 'window-side)) children) + (seq-find (lambda (w) (not (window-parameter w 'window-side))) children) + root))) - (defun jf/enforce-right-side-width (win) - "Resize side window WIN so its frame keeps 120 columns for the rest of the frame." - (let ((width (max 1 (- (frame-width (window-frame win)) 120)))) - (window-resize win (- width (window-width win)) t))) + (defun jf/window-leaves (window) + "Return the live leaf windows in WINDOW's subtree." + (if (window-live-p window) + (list window) + (mapcan #'jf/window-leaves (jf/window-children window)))) - (defun jf/enforce-right-side-widths (frame) - "Re-apply `jf/enforce-right-side-width' to all of FRAME's right-side windows." - (dolist (win (window-list frame 'nomini)) - (when (eq (window-parameter win 'window-side) 'right) - (jf/enforce-right-side-width win)))) + (defun jf/topright-window (windows) + "Return whichever of WINDOWS sits furthest up, then furthest right." + (car (sort (copy-sequence windows) + (lambda (a b) + (let ((top-a (nth 1 (window-edges a))) + (top-b (nth 1 (window-edges b)))) + (if (= top-a top-b) + (> (nth 0 (window-edges a)) (nth 0 (window-edges b))) + (< top-a top-b))))))) - (add-hook 'window-size-change-functions #'jf/enforce-right-side-widths) + (defun jf/display-buffer-right (buffer alist) + "Display BUFFER on the right of the main editing area. - ;; (require 'perspective) - ;; (setopt persp-mode-prefix-key (kbd "C-x t")) - ;; (setopt persp-show-modestring nil) - ;; (persp-mode) + Applies three rules, based on the shape of the frame's non-side windows: + 1. One window -> split it and show BUFFER in the new right window. + 2. Two windows side-by-side -> show BUFFER in the right one. If the + right window is selected, swap the two windows first (keeping + focus on the buffer that was active) so BUFFER doesn't evict it. + 3. Any other layout -> show BUFFER in the upper-rightmost window." + (let ((main (jf/window-main-root))) + (cond + ((window-live-p main) + (let* ((new (split-window main nil 'right)) + (width (max 1 (- (frame-width) 120))) + (delta (window-resizable new (- width (window-width new)) t))) + (window-resize new delta t) + (window--display-buffer buffer new 'window alist))) + ((and (window-left-child main) + (= (length (jf/window-children main)) 2) + (seq-every-p #'window-live-p (jf/window-children main))) + (let* ((left (window-left-child main)) + (right (window-next-sibling left))) + (when (eq (selected-window) right) + (window-swap-states left right) + (select-window left)) + (window--display-buffer buffer right 'reuse alist))) + (t + (window--display-buffer buffer (jf/topright-window (jf/window-leaves main)) 'reuse alist))))) - ;; (defun jf/popper-group-by-owning-perspective () - ;; "Return the name of the perspective that owns the current buffer. - ;; Unlike `popper-group-by-perspective', which just returns whichever - ;; perspective happens to be active on the frame right now, this looks - ;; up which perspective's buffer list the popup buffer actually belongs - ;; to. This matters because popper re-groups popups on - ;; `window-configuration-change-hook', which perspective.el triggers - ;; *after* switching the frame's active perspective -- so - ;; `popper-group-by-perspective' ends up filing a popup under the - ;; perspective you just switched *to* rather than the one it came from." - ;; (let ((buf (current-buffer))) - ;; (or (cl-loop for persp being the hash-values of (perspectives-hash (selected-frame)) - ;; if (memq buf (persp-buffers persp)) - ;; return (persp-name persp)) - ;; (persp-current-name)))) + (setopt popper-reference-buffers + '("\\*Flymake diagnostics .*" + "\\*Embark Export: .*" + "\\*Embark Collect .*" + "\\*xref\\*" + "\\*compilation\\*" + "\\*pnpm:.*" + "\\*sly-mrepl *" + "\\*lispy-message\\*" + "\\*occur\\*" + ".*eshell\\*" + "\\*SQL: .*" + "magit-revision: .*" + "\\*Help\\*" + "\\*rustfmt\\*" + "\\*slack: .*" + "\\*forge: .*" + "new-pullreq" + "\\*claude:.*" + "\\*Man .*" + "\\*List .*" + "\\*eldoc\\*")) + (setopt popper-display-control nil) - (setopt popper-reference-buffers - '("\\*Flymake diagnostics .*" - "\\*Embark Export: .*" - "\\*Embark Collect .*" - "\\*xref\\*" - "\\*compilation\\*" - "\\*pnpm:.*" - "\\*sly-mrepl *" - "\\*lispy-message\\*" - "\\*occur\\*" - ".*eshell\\*" - "\\*SQL: .*" - "magit-revision: .*" - "\\*Help\\*" - "\\*rustfmt\\*" - "\\*slack: .*" - "\\*forge: .*" - "new-pullreq" - "\\*claude:.*" - "\\*Man .*" - "\\*List .*" - "\\*eldoc\\*")) - (setopt popper-display-control nil) - ;; (setopt popper-group-function 'jf/popper-group-by-owning-perspective) + (popper-mode) + (popper-echo-mode) - (popper-mode) - (popper-echo-mode) + (bind-keys ("C-q" . popper-toggle) + ("C-;" . popper-cycle)) - (bind-keys ("C-q" . popper-toggle) - ("C-;" . popper-cycle)) + (defvar jf/fullscreen-popup--param 'jf/fullscreen-saved-configuration + "Key used to stash the window configuration a fullscreen popup replaced. + Stored per-tab when `tab-bar-mode' is active, otherwise as a plain frame + parameter; see `jf/tab-or-frame-parameter'.") - (defvar jf/fullscreen-popup--param 'jf/fullscreen-saved-configuration - "Key used to stash the window configuration a fullscreen popup replaced. -Stored per-tab when `tab-bar-mode' is active, otherwise as a plain frame -parameter; see `jf/tab-or-frame-parameter'.") + (defvar-local jf/fullscreen-popup-p nil + "Non-nil in a buffer currently being displayed as a fullscreen popup.") - (defvar-local jf/fullscreen-popup-p nil - "Non-nil in a buffer currently being displayed as a fullscreen popup.") + (defun jf/current-tab-cell (&optional frame) + "Return FRAME's current tab's raw data cell, or nil without `tab-bar-mode'." + (and (bound-and-true-p tab-bar-mode) + (assq 'current-tab (tab-bar-tabs frame)))) - (defun jf/current-tab-cell (&optional frame) - "Return FRAME's current tab's raw data cell, or nil without `tab-bar-mode'." - (and (bound-and-true-p tab-bar-mode) - (assq 'current-tab (tab-bar-tabs frame)))) + (defun jf/tab-or-frame-parameter (parameter &optional frame) + "Like `frame-parameter', but scoped to FRAME's selected tab when tabs are in use. - (defun jf/tab-or-frame-parameter (parameter &optional frame) - "Like `frame-parameter', but scoped to FRAME's selected tab when tabs are in use. + A plain frame parameter is shared by every tab on that frame, which is wrong + for state like `jf/fullscreen-popup--param': switching tabs swaps in an + entirely different window layout, so \"the layout to restore\" has to travel + with the tab, not the frame." + (if-let ((tab (jf/current-tab-cell frame))) + (alist-get parameter tab) + (frame-parameter frame parameter))) -A plain frame parameter is shared by every tab on that frame, which is wrong -for state like `jf/fullscreen-popup--param': switching tabs swaps in an -entirely different window layout, so \"the layout to restore\" has to travel -with the tab, not the frame." - (if-let ((tab (jf/current-tab-cell frame))) - (alist-get parameter tab) - (frame-parameter frame parameter))) + (defun jf/set-tab-or-frame-parameter (parameter value &optional frame) + "Set PARAMETER to VALUE, scoped like `jf/tab-or-frame-parameter'." + (if-let ((tab (jf/current-tab-cell frame))) + (let ((cell (assq parameter tab))) + (if cell + (setcdr cell value) + (setcdr tab (cons (cons parameter value) (cdr tab))))) + (set-frame-parameter frame parameter value))) - (defun jf/set-tab-or-frame-parameter (parameter value &optional frame) - "Set PARAMETER to VALUE, scoped like `jf/tab-or-frame-parameter'." - (if-let ((tab (jf/current-tab-cell frame))) - (let ((cell (assq parameter tab))) - (if cell - (setcdr cell value) - (setcdr tab (cons (cons parameter value) (cdr tab))))) - (set-frame-parameter frame parameter value))) + (defun jf/display-buffer-fullscreen (buffer alist) + "Display BUFFER, deleting other windows, saving the prior layout. - (defun jf/display-buffer-fullscreen (buffer alist) - "Display BUFFER, deleting other windows, saving the prior layout. + Intended for use in `display-buffer-alist' for popups (e.g. Magit + status/log) that should take over the frame while displayed, but + restore the previous window configuration once closed via either + `q' or `popper-toggle' -- both end up calling `quit-window', which + is where the restore actually happens; see + `jf/quit-window-restore-fullscreen'." + (unless (jf/tab-or-frame-parameter jf/fullscreen-popup--param) + (jf/set-tab-or-frame-parameter jf/fullscreen-popup--param + (current-window-configuration))) + (when-let ((window (or (display-buffer-reuse-window buffer alist) + (display-buffer-same-window buffer alist) + (display-buffer-pop-up-window buffer alist) + (display-buffer-use-some-window buffer alist)))) + (let ((ignore-window-parameters t)) + (delete-other-windows window)) + (with-current-buffer buffer + (setq jf/fullscreen-popup-p t)) + window)) -Intended for use in `display-buffer-alist' for popups (e.g. Magit -status/log) that should take over the frame while displayed, but -restore the previous window configuration once closed via either -`q' or `popper-toggle' -- both end up calling `quit-window', which -is where the restore actually happens; see -`jf/quit-window-restore-fullscreen'." - (unless (jf/tab-or-frame-parameter jf/fullscreen-popup--param) - (jf/set-tab-or-frame-parameter jf/fullscreen-popup--param - (current-window-configuration))) - (when-let ((window (or (display-buffer-reuse-window buffer alist) - (display-buffer-same-window buffer alist) - (display-buffer-pop-up-window buffer alist) - (display-buffer-use-some-window buffer alist)))) - (let ((ignore-window-parameters t)) - (delete-other-windows window)) - (with-current-buffer buffer - (setq jf/fullscreen-popup-p t)) - window)) + (defun jf/quit-window-restore-fullscreen (orig-fn &optional kill window) + "Restore the saved window configuration after quitting a fullscreen popup. - (defun jf/quit-window-restore-fullscreen (orig-fn &optional kill window) - "Restore the saved window configuration after quitting a fullscreen popup. + Advice around `quit-window' so both `q' (which Magit and other + special-mode buffers bind to buffer-quitting commands that call + `quit-window') and Popper's close/toggle commands (which also call + `quit-window' directly) trigger the restore. Only restores once the + last fullscreen popup on the tab (or frame, without `tab-bar-mode') + has closed, so opening one fullscreen popup from within another + falls back to the outer one first." + (let* ((window (or window (selected-window))) + (was-fullscreen (buffer-local-value 'jf/fullscreen-popup-p + (window-buffer window))) + (frame (window-frame window))) + (funcall orig-fn kill window) + (when (and was-fullscreen + (not (and (window-live-p window) + (buffer-local-value 'jf/fullscreen-popup-p + (window-buffer window)))) + (jf/tab-or-frame-parameter jf/fullscreen-popup--param frame)) + (let ((conf (jf/tab-or-frame-parameter jf/fullscreen-popup--param frame))) + (jf/set-tab-or-frame-parameter jf/fullscreen-popup--param nil frame) + (set-window-configuration conf))))) -Advice around `quit-window' so both `q' (which Magit and other -special-mode buffers bind to buffer-quitting commands that call -`quit-window') and Popper's close/toggle commands (which also call -`quit-window' directly) trigger the restore. Only restores once the -last fullscreen popup on the tab (or frame, without `tab-bar-mode') -has closed, so opening one fullscreen popup from within another -falls back to the outer one first." - (let* ((window (or window (selected-window))) - (was-fullscreen (buffer-local-value 'jf/fullscreen-popup-p - (window-buffer window))) - (frame (window-frame window))) - (funcall orig-fn kill window) - (when (and was-fullscreen - (not (and (window-live-p window) - (buffer-local-value 'jf/fullscreen-popup-p - (window-buffer window)))) - (jf/tab-or-frame-parameter jf/fullscreen-popup--param frame)) - (let ((conf (jf/tab-or-frame-parameter jf/fullscreen-popup--param frame))) - (jf/set-tab-or-frame-parameter jf/fullscreen-popup--param nil frame) - (set-window-configuration conf))))) + (advice-add 'quit-window :around #'jf/quit-window-restore-fullscreen) - (advice-add 'quit-window :around #'jf/quit-window-restore-fullscreen) + (setopt split-width-threshold 200) + (setopt split-height-threshold nil) + (setopt display-buffer-base-action + '((display-buffer-reuse-window display-buffer-same-window + display-buffer-in-previous-window + display-buffer-use-some-window))) - (setq display-buffer-alist - '(((or "magit: *" - "magit-log*" - "\\*Messages\\*" - "\\*trace-output\\*" - "\\*EGLOT connections\\*") - (jf/display-buffer-fullscreen)) - ((or "\\*Flymake diagnostics .*" - "\\*Embark Export: .*" - "\\*Embark Collect .*" - "\\*xref\\*" - "\\*compilation\\*" - "\\*pnpm:.*" - "\\*sly-mrepl *" - "\\*lispy-message\\*" - "\\*occur\\*" - ".*eshell\\*" - "\\*SQL: .*") - (display-buffer-in-side-window) - (side . bottom) - (slot . 0) - (window-height . 0.33) - (window-parameters (no-delete-other-windows . t) - (no-other-window . t))) - ((or "magit-revision: .*" - "\\*Help\\*" - "\\*rustfmt\\*" - "\\*slack: .*" - "\\*forge: .*" - "new-pullreq" - "\\*claude:.*" - "\\*Man .*" - "\\*List .*") - (display-buffer-in-side-window) - (side . right) - (slot . 0) - (window-width . jf/enforce-right-side-width) - (window-height . jf/enforce-right-side-heights) - (preserve-size . (nil . nil)) - (window-parameters (no-delete-other-windows . t) - (no-other-window . t))) - ((or "\\*Embark Actions\\*" - "\\*eldoc\\*") - (display-buffer-in-side-window) - (side . right) - (slot . -1) - (window-width . jf/enforce-right-side-width) - (window-height . jf/enforce-right-side-heights) - (preserve-size . (nil . t)) - (window-parameters (no-delete-other-windows . t) - (no-other-window . t))))) + (setq display-buffer-alist + '(((or "magit: *" + "magit-log*" + "\\*Messages\\*" + "\\*trace-output\\*" + "\\*EGLOT connections\\*") + (jf/display-buffer-fullscreen)) + ((or "\\*Flymake diagnostics .*" + "\\*Embark Export: .*" + "\\*Embark Collect .*" + "\\*xref\\*" + "\\*compilation\\*" + "\\*pnpm:.*" + "\\*sly-mrepl *" + "\\*lispy-message\\*" + "\\*occur\\*" + ".*eshell\\*" + "\\*SQL: .*") + (display-buffer-in-side-window) + (side . bottom) + (slot . 0) + (window-height . 0.33) + (window-parameters (no-delete-other-windows . t) + (no-other-window . t))) + ((or "magit-revision: .*" + "\\*Help\\*" + "\\*rustfmt\\*" + "\\*slack: .*" + "\\*forge: .*" + "new-pullreq" + "\\*claude:.*" + "\\*Man .*" + "\\*List .*" + "\\*Embark Actions\\*" + "\\*eldoc\\*") + (jf/display-buffer-right)))) - (setopt window-persistent-parameters - (cl-list* '(no-delete-other-windows . writable) - '(no-other-window . writable) - window-persistent-parameters)) + (setopt window-persistent-parameters + (cl-list* '(no-delete-other-windows . writable) + '(no-other-window . writable) + window-persistent-parameters)) #+END_SRC @@ -1425,7 +1442,7 @@ Just "silently" wrap when searching at the bottom of a document. programs.emacs = { enable = true; extraPackages = epkgs: with epkgs; [ - # bufferlo + buffer-terminator ]; extraConfig = builtins.readFile ./emacs-workflow.el; }; @@ -1433,24 +1450,10 @@ Just "silently" wrap when searching at the bottom of a document. #+END_SRC #+BEGIN_SRC elisp - ;; (bufferlo-mode) - - ;; (setopt bufferlo-include-buried-buffers nil) - - ;; (with-eval-after-load 'bufferlo - ;; (setq mode-line-misc-info - ;; (assq-delete-all 'bufferlo-mode mode-line-misc-info))) - - ;; (defun my/bury-or-kill-buffer () - ;; (interactive) - ;; (bury-buffer) - ;; (bufferlo-kill-orphan-buffers)) + (buffer-terminator-mode 1) (bind-keys - ("s-b" . consult-buffer) - ;; ("C-x t 0" . bufferlo-tab-close-kill-buffers) - ;; ("C-x k" . my/bury-or-kill-buffer) - ) + ("s-b" . consult-project-buffer)) #+END_SRC ** Language Support @@ -1929,21 +1932,6 @@ for completion. That makes it really annoying to use; don't use yasnippet with e (eval-when-compile (require 'consult)) - ;; (require 'perspective) - ;; (with-eval-after-load 'consult - ;; (consult-customize - ;; consult-theme :preview-key '(:debounce 0.2 any) - ;; consult-ripgrep consult-git-grep consult-grep consult-man - ;; consult-bookmark consult-recent-file consult-xref - ;; consult-source-bookmark consult-source-file-register - ;; consult-source-recent-file - ;; consult-source-buffer - ;; ;; :preview-key "M-." - ;; consult-source-project-recent-file - ;; :preview-key '(:debounce 0.4 any)) - ;; (consult-customize consult-source-buffer :hidden t :default nil) - ;; (add-to-list 'consult-buffer-sources persp-consult-source)) - (with-eval-after-load 'consult (consult-customize consult-theme :preview-key '(:debounce 0.2 any)