diff --git a/flake.nix b/flake.nix index 45ade45..7f89b65 100644 --- a/flake.nix +++ b/flake.nix @@ -3,15 +3,20 @@ inputs = { # Specify the source of Home Manager and Nixpkgs. - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05"; + claude-code = { + url = "github:sadjow/claude-code-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + home-manager = { url = "github:nix-community/home-manager/master"; inputs.nixpkgs.follows = "nixpkgs"; }; - kube = { - url = "https://gitea.home-frikker.dedyn.io/jfrikker/kube/archive/1.0.1.tar.gz"; - inputs.nixpkgs.follows = "nixpkgs"; - }; + # kube = { + # url = "https://gitea.home-frikker.dedyn.io/jfrikker/kube/archive/1.0.1.tar.gz"; + # inputs.nixpkgs.follows = "nixpkgs"; + # }; nix-index-database = { url = "github:nix-community/nix-index-database"; @@ -19,7 +24,7 @@ }; }; - outputs = { nixpkgs, home-manager, kube, nix-index-database, ... }: + outputs = { nixpkgs, home-manager, claude-code, nix-index-database, ... }: let modules-for-system = pkgs: pkgs.stdenv.mkDerivation { name = "home-manager-modules"; src = pkgs.lib.fileset.toSource { @@ -41,7 +46,7 @@ ''; }; in { - homeConfigurations.shibumi = + homeConfigurations.redbook = let pkgs = import nixpkgs { system = "aarch64-darwin"; config.allowUnfree = true; }; modules = modules-for-system pkgs; in home-manager.lib.homeManagerConfiguration { @@ -50,13 +55,11 @@ modules = [ nix-index-database.homeModules.default + "${modules}/base.nix" + "${modules}/desktop.nix" + "${modules}/redbook.nix" + "${modules}/redbook-desktop.nix" ] ++ - (pkgs.lib.filter - (n: pkgs.lib.strings.hasSuffix ".nix" n) - (pkgs.lib.filesystem.listFilesRecursive "${modules}/base")) ++ - (pkgs.lib.filter - (n: pkgs.lib.strings.hasSuffix ".nix" n) - (pkgs.lib.filesystem.listFilesRecursive "${modules}/desktop")) ++ [{ home.stateVersion = "22.05"; home.username = "jfrikker"; @@ -64,7 +67,7 @@ home.packages = [ pkgs.pinentry_mac ]; }]; - extraSpecialArgs = { kube = kube.packages.aarch64-darwin.default; }; + extraSpecialArgs = { claude-code = claude-code.packages.aarch64-darwin.default; }; }; homeConfigurations.home = let pkgs = import nixpkgs { system = "x86_64-linux"; config.allowUnfree = true; }; @@ -84,7 +87,7 @@ home.homeDirectory = "/home/jfrikker"; }]; - extraSpecialArgs = { kube = kube.packages.x86_64-linux.default; }; + extraSpecialArgs = { claude-code = claude-code.packages.x86_64-linux.default; }; }; homeConfigurations.home-laptop = let pkgs = import nixpkgs { system = "x86_64-linux"; config.allowUnfree = true; }; diff --git a/home.org b/home.org index f782241..618fda0 100644 --- a/home.org +++ b/home.org @@ -1,5 +1,38 @@ #+PROPERTY: header-args :mkdirp yes #+PROPERTY: header-args:elisp :lexical t +* Categories + +#+BEGIN_SRC nix :tangle target/base.nix + {lib, ...}: { + imports = lib.filter + (n: lib.strings.hasSuffix ".nix" n) + (lib.filesystem.listFilesRecursive ./base); + } +#+END_SRC + +#+BEGIN_SRC nix :tangle target/desktop.nix + {lib, ...}: { + imports = lib.filter + (n: lib.strings.hasSuffix ".nix" n) + (lib.filesystem.listFilesRecursive ./desktop); + } +#+END_SRC + +#+BEGIN_SRC nix :tangle target/redbook.nix + {lib, ...}: { + imports = lib.filter + (n: lib.strings.hasSuffix ".nix" n) + (lib.filesystem.listFilesRecursive ./redbook); + } +#+END_SRC + +#+BEGIN_SRC nix :tangle target/redbook-desktop.nix + {lib, ...}: { + imports = lib.filter + (n: lib.strings.hasSuffix ".nix" n) + (lib.filesystem.listFilesRecursive ./redbook-desktop); + } +#+END_SRC * Packages Home-manager "packages" are simply binaries that are placed on the path, without any attached @@ -21,10 +54,8 @@ carefully so it doesn't get carried away... alexandria cl-markup cl-mock - cl-rdkafka clack clack-handler-woo - dbd-mysql dbi defstar dexador @@ -90,6 +121,7 @@ tools. They boost better speed, or simply more streamlined features. dust fd ripgrep + tokei xh ]; } @@ -131,20 +163,14 @@ between them. Once installed, the actual command is ~wormhole~. #+end_src These are the other programs I have installed globally. A few (like ~awscli~, ~mysql-client~, etc.) -are still Shibumi-specific; ideally I'd find another way to install them. +are still Redbook-specific; ideally I'd find another way to install them. - #+BEGIN_SRC nix :tangle target/base/other-packages.nix - {pkgs, kube, ...}: + #+BEGIN_SRC nix :tangle target/redbook/other-packages.nix + {pkgs, ...}: { home.packages = with pkgs; [ - aws-iam-authenticator - awscli2 - kube - kubectl - mariadb.client - nerd-fonts.jetbrains-mono + postgresql openssh - tokei ]; fonts.fontconfig.enable = true; @@ -158,18 +184,36 @@ These programs require a full desktop environment: { home.packages = with pkgs; [ google-chrome + nerd-fonts.jetbrains-mono + nerd-fonts.symbols-only (iosevka-bin.override { variant = "Aile"; }) (iosevka-bin.override { variant = "Etoile"; }) - (iosevka-bin.override { variant = "SGr-IosevkaTerm"; }) + (iosevka-bin.override { variant = "SGr-IosevkaTermSS14"; }) nerd-fonts.symbols-only keymapp proton-pass - slack wireshark ]; } #+END_SRC +#+BEGIN_SRC nix :tangle target/redbook-desktop/desktop-packages.nix + {pkgs, ...}: + { + home.packages = with pkgs; [ + linear + (slack.overrideAttrs { + version = "4.50.143"; + src = pkgs.fetchurl { + url = "https://downloads.slack-edge.com/desktop-releases/mac/arm64/4.50.143/Slack-4.50.143-macOS.dmg"; + hash = "sha256-f5oYi8NPKXWpDcowFYMUjptbQs4V9hK2bm1TuaQeJn0="; + }; + }) + teams + ]; + } +#+END_SRC + * Program Configurations This section contains applications whose config files will be generated by Home Manager. Home Manager calls these "programs" (as opposed to "packages", which are just binaries to put on the @@ -273,7 +317,9 @@ We all know what ~git~ does. programs.git = { enable = true; settings.user.name = "Joe Frikker"; - settings.user.email = "joe.frikker@shibumi.com"; + settings.user.email = "joe@redbookcattle.com"; + settings.github.user = "joe@redbookcattle.com"; + settings.merge.conflictstyle = "zdiff3"; }; programs.delta = { @@ -300,7 +346,7 @@ The best text editor ever? Heresy! auto-save.after-delay.timeout = 1000; cursorline = true; idle-timeout = 0; - rulers = [100]; + rulers = [120]; text-width = 100; lsp.goto-reference-include-declaration = false; cursor-shape.insert = "bar"; @@ -463,7 +509,11 @@ config in two blocks, so we can get lua syntax highlighting in org. First, the n Then the real lua config: #+BEGIN_SRC lua - config.font = wezterm.font("Iosevka Term", {stretch = "Expanded"}) + local config = {} + config.default_prog = { '/Users/jfrikker/.nix-profile/bin/zsh', '-l' } + config.font = wezterm.font 'Iosevka Term SS14' + config.font_size = 13.0 + -- config.font = wezterm.font("JetBrains Mono") config.inactive_pane_hsb = { saturation = 0.5, brightness = 0.5, @@ -619,7 +669,7 @@ Now we'll set up some keybindings: key = "g", mods = "CMD", action = wezterm.action.SpawnCommandInNewTab { - args = { wezterm.home_dir .. "/.nix-profile/bin/zsh", "-l", "-c", "lazygit" } + args = { wezterm.home_dir .. "/.nix-profile/bin/zsh", "-l", "-i", "-c", "lazygit" } } }, { @@ -649,6 +699,48 @@ Now we'll set up some keybindings: end #+END_SRC +** Kitty +#+BEGIN_SRC nix :tangle target/desktop/kitty.nix + {pkgs, ...}: { + programs.kitty = { + enable = true; + font = { + name = "Iosevka Term SS14"; + package = pkgs.iosevka-bin.override { variant = "SGr-IosevkaTermSS14"; }; + size = 13; + }; + themeFile = "Catppuccin-Frappe"; + keybindings = { + "ctrl+1" = "goto_tab 1"; + "ctrl+2" = "goto_tab 2"; + "ctrl+3" = "goto_tab 3"; + "ctrl+4" = "goto_tab 4"; + "ctrl+5" = "goto_tab 5"; + "ctrl+6" = "goto_tab 6"; + "ctrl+7" = "goto_tab 7"; + "ctrl+8" = "goto_tab 8"; + "ctrl+9" = "goto_tab 9"; + }; + }; + } +#+END_SRC + +** Ghostty + +#+BEGIN_SRC nix :tangle target/desktop/ghostty.nix + {pkgs, ...}: { + programs.ghostty = { + enable = true; + package = pkgs.ghostty-bin; + settings = { + adjust-cell-height = "15%"; + font-family = "Iosevka Term SS14"; + font-size = 13; + }; + }; + } +#+END_SRC + ** Zsh This is my default shell for now. Unfortunately, nix requires a posix-compliant shell, so bash or @@ -691,9 +783,10 @@ Actually, nix *really* requires ~bash~. This is a plugin that allows using zsh f ** Claude Code #+BEGIN_SRC nix :tangle target/base/claude-code.nix - { + {claude-code, ...}: { programs.claude-code = { enable = true; + package = claude-code; }; } #+END_SRC @@ -741,10 +834,13 @@ These are just some general settings that apply everywhere. First, a few package programs.emacs = { enable = true; extraPackages = epkgs: with epkgs; [ + ace-window doom-modeline + free-keys ligature mixed-pitch - ace-window + perspective + popper ]; extraConfig = builtins.readFile ./emacs-global.el; }; @@ -756,6 +852,7 @@ 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) @@ -767,6 +864,20 @@ Turn off some default settings that I don't find useful. 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) + + (setopt mouse-wheel-tilt-scroll t) + (setq-default truncate-lines t) + + (setopt tab-bar-show nil) + (tab-bar-mode) #+END_SRC Make sure which-key mode is always on. The on-the-fly keymap help it gives is indespensable. @@ -874,13 +985,15 @@ Use variable-width fonts in text buffers. ~mixed-pitch-mode~ keeps most code blo (add-hook 'yaml-ts-mode-hook 'no-mixed-pitch) (add-hook 'mhtml-mode 'no-mixed-pitch) + (add-hook 'nxml-mode 'no-mixed-pitch) #+END_SRC A basic ace-window setup. Allows switching between windows quickly (although avy can do the same...). #+BEGIN_SRC elisp (bind-keys ("M-o" . ace-window) - ("s-o" . ace-window)) + ("s-o" . ace-window) + ("C-c o" . ace-window)) (setopt aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)) (setopt aw-dispatch-always t) #+END_SRC @@ -889,8 +1002,10 @@ Set up a few project- / workspace-related key bindings. #+BEGIN_SRC elisp (bind-keys ("s-f" . project-find-file) - ("s-F" . project-switch-project)) - + ("s-F" . project-switch-project) + ;; ("s-s" . persp-switch) + ) + (with-eval-after-load 'tab-bar (bind-keys :map tab-bar-mode-map ("s-s" . tab-bar-switch-to-tab))) @@ -912,7 +1027,7 @@ less so? I'm trying this out. This disables visual selections. It's bold, which is why I like it :) #+BEGIN_SRC elisp - ;; (transient-mark-mode -1) + (transient-mark-mode -1) #+END_SRC I use tabs to manage multiple "workspaces". I don't tie a project to a tab or anything; often fixing @@ -944,54 +1059,229 @@ windows. I place windows in the bottom or on the right mostly depending on how w content to be. #+BEGIN_SRC elisp - (setq display-buffer-alist - '(((or "\\*Flymake diagnostics .*" - "\\*Embark Export: .*" - "\\*Embark Collect .*" - "\\*xref\\*" - "\\*compilation\\*" - "\\*sly-mrepl *" - "\\*Messages\\*" - "\\*lispy-message\\*" - "\\*EGLOT connections\\*" - "\\*trace-output\\*" - "\\*occur\\*" - ".*eshell\\*") + (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/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/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)))) + + (add-hook 'window-size-change-functions #'jf/enforce-right-side-widths) + + ;; (require 'perspective) + ;; (setopt persp-mode-prefix-key (kbd "C-x t")) + ;; (setopt persp-show-modestring nil) + ;; (persp-mode) + + ;; (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-group-function 'jf/popper-group-by-owning-perspective) + + (popper-mode) + (popper-echo-mode) + + (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-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/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))) + + (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. + +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. + +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) + + (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: *" - "magit-revision: .*" - "magit-log*" - "\\*eldoc\\*" + ((or "magit-revision: .*" "\\*Help\\*" "\\*rustfmt\\*" "\\*slack: .*" "\\*forge: .*" "new-pullreq" "\\*claude:.*" - "\\*Man .*") - (display-buffer-reuse-window display-buffer-in-side-window) + "\\*Man .*" + "\\*List .*") + (display-buffer-in-side-window) (side . right) (slot . 0) - (window-width . 0.5) + (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))) - ("\\*Embark Actions\\*" + ((or "\\*Embark Actions\\*" + "\\*eldoc\\*") (display-buffer-in-side-window) - (side . right) - (slot . -1) - (window-width . 0.5) - (window-parameters (no-delete-other-windows . t) - (no-other-window . t))))) + (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))))) - (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 @@ -1008,11 +1298,13 @@ This configuration affects general text editing, across all modes. First, some p enable = true; extraPackages = epkgs: with epkgs; [ avy - devil edit-indirect embark + format-all god-mode - meow + # evil + # evil-collection + # evil-org string-inflection ]; extraConfig = builtins.readFile ./emacs-editing.el; @@ -1031,114 +1323,18 @@ but I'm worried that having too many repeat maps will also be annoying (I'll hav cancel them more frequently). In the end, I opted for Devil mode. It avoids modifier keys, and has its own devil-mode-only repeat maps that don't interfere when using normal modifier key sequences. -#+BEGIN_SRC elisp :tangle nil - (use-package devil - :disable t - :config - (global-devil-mode) - (setq devil-repeatable-keys - '(("%k /") - ("%k d") - ("%k k") - ("%k m ^") - ("%k m b" "%k m f" "%k m a" "% k m e") - ("%k m @" "%k m h") - ("%k m y") - ("%k p" "%k n" "%k b" "%k f" "%k a" "%k e") - ("%k s") - ("%k m d" "%k m ")))) -#+END_SRC - -#+BEGIN_SRC elisp - ;; (defun meow-setup () - ;; (setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty) - ;; (meow-motion-define-key - ;; '("j" . meow-next) - ;; '("k" . meow-prev) - ;; '("" . ignore)) - ;; (meow-leader-define-key - ;; ;; Use SPC (0-9) for digit arguments. - ;; '("1" . meow-digit-argument) - ;; '("2" . meow-digit-argument) - ;; '("3" . meow-digit-argument) - ;; '("4" . meow-digit-argument) - ;; '("5" . meow-digit-argument) - ;; '("6" . meow-digit-argument) - ;; '("7" . meow-digit-argument) - ;; '("8" . meow-digit-argument) - ;; '("9" . meow-digit-argument) - ;; '("0" . meow-digit-argument) - ;; '("/" . meow-keypad-describe-key) - ;; '("?" . meow-cheatsheet)) - ;; (meow-normal-define-key - ;; '("0" . meow-expand-0) - ;; '("9" . meow-expand-9) - ;; '("8" . meow-expand-8) - ;; '("7" . meow-expand-7) - ;; '("6" . meow-expand-6) - ;; '("5" . meow-expand-5) - ;; '("4" . meow-expand-4) - ;; '("3" . meow-expand-3) - ;; '("2" . meow-expand-2) - ;; '("1" . meow-expand-1) - ;; '("-" . negative-argument) - ;; '(";" . meow-reverse) - ;; '("," . meow-inner-of-thing) - ;; '("." . meow-bounds-of-thing) - ;; '("[" . meow-beginning-of-thing) - ;; '("]" . meow-end-of-thing) - ;; '("a" . meow-append) - ;; '("A" . meow-open-below) - ;; '("b" . meow-back-word) - ;; '("B" . meow-back-symbol) - ;; '("c" . meow-change) - ;; '("d" . meow-delete) - ;; '("D" . meow-backward-delete) - ;; '("e" . meow-next-word) - ;; '("E" . meow-next-symbol) - ;; '("f" . meow-find) - ;; '("g" . meow-cancel-selection) - ;; '("G" . meow-grab) - ;; '("h" . meow-left) - ;; '("H" . meow-left-expand) - ;; '("i" . meow-insert) - ;; '("I" . meow-open-above) - ;; '("j" . meow-next) - ;; '("J" . meow-next-expand) - ;; '("k" . meow-prev) - ;; '("K" . meow-prev-expand) - ;; '("l" . meow-right) - ;; '("L" . meow-right-expand) - ;; '("m" . meow-join) - ;; '("n" . meow-search) - ;; '("o" . meow-block) - ;; '("O" . meow-to-block) - ;; '("p" . meow-yank) - ;; '("q" . meow-quit) - ;; '("Q" . meow-goto-line) - ;; '("r" . meow-replace) - ;; '("R" . meow-swap-grab) - ;; '("s" . meow-kill) - ;; '("t" . meow-till) - ;; '("u" . meow-undo) - ;; '("U" . meow-undo-in-selection) - ;; '("v" . meow-visit) - ;; '("w" . meow-mark-word) - ;; '("W" . meow-mark-symbol) - ;; '("x" . meow-line) - ;; '("X" . meow-goto-line) - ;; '("y" . meow-save) - ;; '("Y" . meow-sync-grab) - ;; '("z" . meow-pop-selection) - ;; '("'" . repeat) - ;; '("" . ignore))) - - ;; (require 'meow) - ;; (meow-setup) - ;; (meow-global-mode) -#+END_SRC - #+BEGIN_SRC elisp + ;; (setopt evil-want-keybinding nil) + ;; (require 'evil) + ;; (evil-mode 1) + ;; (require 'evil-collection) + ;; (evil-collection-init) + + ;; (require 'evil-org) + ;; (add-hook 'org-mode-hook 'evil-org-mode) + ;; (evil-org-set-key-theme '(navigation insert textobjects additional calendar)) + ;; (require 'evil-org-agenda) + ;; (evil-org-agenda-set-keys) (require 'god-mode) (require 'which-key) @@ -1148,9 +1344,8 @@ its own devil-mode-only repeat maps that don't interfere when using normal modif (bind-keys ("" . (lambda () (interactive) (god-mode-all 1)))) (defun my-god-mode-update-cursor-type () (setq cursor-type (cond - (org-present-mode nil) - ((or (and (featurep 'god-mode) god-local-mode) buffer-read-only - (and (featurep 'meow-mode) meow-mode (not (eq 'insert meow--current-state)))) 'box) + ((and (featurep 'org-present) org-present-mode) nil) + ((or (and (featurep 'god-mode) god-local-mode) buffer-read-only) 'box) (t 'bar)))) (add-hook 'post-command-hook 'my-god-mode-update-cursor-type) @@ -1176,10 +1371,17 @@ his article. (avy-goto-char-timer))) (bind-keys - ("M-j" . avy-goto-char-timer) - ("C-c j" . avy-goto-char-timer) - ("M-J" . avy-goto-char-timer-embark) + ("C-j" . avy-goto-char-timer) + ("M-j" . avy-goto-char-timer-embark) ("C-." . embark-act)) + + (with-eval-after-load 'lispy + (bind-keys :map lispy-mode-map + ("C-j" . avy-goto-char-timer))) + + (with-eval-after-load 'org + (bind-keys :map org-mode-map + ("C-j" . avy-goto-char-timer))) #+END_SRC Just "silently" wrap when searching at the bottom of a document. @@ -1212,6 +1414,44 @@ Just "silently" wrap when searching at the bottom of a document. (bind-keys ("C-x 4 n" . edit-indirect-region)) #+END_SRC +** Workflow +:PROPERTIES: +:header-args:elisp: :tangle target/desktop/emacs-workflow.el +:END: + +#+BEGIN_SRC nix :tangle target/desktop/emacs-workflow.nix + { + programs.emacs = { + enable = true; + extraPackages = epkgs: with epkgs; [ + # bufferlo + ]; + extraConfig = builtins.readFile ./emacs-workflow.el; + }; + } +#+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)) + + (bind-keys + ("s-b" . consult-buffer) + ;; ("C-x t 0" . bufferlo-tab-close-kill-buffers) + ;; ("C-x k" . my/bury-or-kill-buffer) + ) +#+END_SRC + ** Language Support :PROPERTIES: :header-args:elisp: :tangle target/desktop/emacs-languages.el @@ -1223,12 +1463,21 @@ Just "silently" wrap when searching at the bottom of a document. enable = true; extraPackages = epkgs: with epkgs; [ consult-eglot + graphql-ts-mode haskell-ts-mode jenkinsfile-mode jq-mode - lispy + (lispy.overrideAttrs { + packageRequires = [ + ace-window iedit swiper zoutline hydra + ]; + ignoreCompilationError = true; + }) + # lispyville lua-mode nix-mode + pnpm-mode + prisma-ts-mode rust-mode sly sly-macrostep @@ -1259,6 +1508,7 @@ Just "silently" wrap when searching at the bottom of a document. g.tree-sitter-nix g.tree-sitter-org g.tree-sitter-php + g.tree-sitter-prisma g.tree-sitter-python g.tree-sitter-rust g.tree-sitter-rust-format-args @@ -1296,7 +1546,7 @@ Show a vertical bar 100 characters out; ideally I don't want lines longer than t is about the max you can have and still fit two files side-by-side on the screen. #+begin_src elisp - (setq-default fill-column 100) + (setq-default fill-column 120) (defun jf-init-fill-column () (display-fill-column-indicator-mode 1)) @@ -1336,6 +1586,10 @@ available? It seems to work for now anyway... (advice-add 'prog-fill-reindent-defun :override #'my-prog-fill-reindent-defun)) #+end_src +#+BEGIN_SRC elisp + (add-hook 'compilation-filter-hook 'ansi-color-compilation-filter) +#+END_SRC + *** Flymake #+begin_src elisp (autoload 'flymake-show-project-diagnostics "flymake") @@ -1360,6 +1614,7 @@ I don't like UI elements that insert fake text into the file. They mess up the s (setopt eglot-ignored-server-capabilities '(:inlayHintProvider :semanticTokensProvider)) (add-hook 'eglot-managed-mode-hook (lambda () (eglot-inlay-hints-mode -1)) nil t) (setopt eglot-code-action-indications nil) + (setopt eglot-events-buffer-config '(:size 300000 :format full)) #+END_SRC Set up a few useful eglot keys. @@ -1407,7 +1662,10 @@ for completion. That makes it really annoying to use; don't use yasnippet with e (setopt lispy-compat '(god-mode edebug)) (add-hook 'emacs-lisp-mode-hook (lambda () (lispy-mode 1))) (add-hook 'lisp-mode-hook (lambda () (lispy-mode 1))) + (add-hook 'lisp-data-mode-hook (lambda () (lispy-mode 1))) (with-eval-after-load 'lispy + ;; (require 'lispyville) + ;; (add-hook 'lispy-mode-hook #'lispyville-mode) (bind-keys :map lispy-mode-map ("i" . (lambda () (interactive) @@ -1418,11 +1676,11 @@ for completion. That makes it really annoying to use; don't use yasnippet with e *** Typescript #+BEGIN_SRC elisp - (push '("\\.ts\\'" . typescript-mode) auto-mode-alist) - (push '(typescript-mode . typescript-ts-mode) major-mode-remap-alist) - (push '("\\.tsx\\'" . tsx-mode) auto-mode-alist) - (push '(tsx-mode . tsx-ts-mode) major-mode-remap-alist) - (setopt typescript-ts-mode-indent-offset 4) + (add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode)) + (add-to-list 'major-mode-remap-alist '(typescript-mode . typescript-ts-mode)) + (add-to-list 'auto-mode-alist '("\\.tsx\\'" . tsx-mode)) + (add-to-list 'major-mode-remap-alist '(tsx-mode . tsx-ts-mode)) + (setopt typescript-ts-mode-indent-offset 2) (with-eval-after-load 'typescript-ts-mode (defun my-tsx-ts-mode--indent-compatibility-b893426 () "Indent rules helper, to handle different releases of tree-sitter-tsx. @@ -1436,6 +1694,52 @@ for completion. That makes it really annoying to use; don't use yasnippet with e `(((match "<" "jsx_text") parent 0) ((parent-is "jsx_text") grand-parent typescript-ts-mode-indent-offset))))) (advice-add 'tsx-ts-mode--indent-compatibility-b893426 :override #'my-tsx-ts-mode--indent-compatibility-b893426)) + + (defvar my/edit-indirect-tag-modes + '(("graphql" . graphql-ts-mode) + ("gql" . graphql-ts-mode) + ("sql" . sql-mode) + ("css" . css-mode)) + "Map of tagged-template tag names to major modes.") + + (defun my/edit-indirect--node-bounds (node) + "Return (BEG . END) for NODE's contents, excluding delimiters." + (cons (1+ (treesit-node-start node)) + (1- (treesit-node-end node)))) + + (defun my/edit-indirect-tagged-template-at-point () + (interactive) + (let* ((node (treesit-node-at (point))) + (tmpl-node (treesit-parent-until + node + (lambda (n) (equal (treesit-node-type n) "template_string")))) + (str-node (unless tmpl-node + (treesit-parent-until + node + (lambda (n) (equal (treesit-node-type n) "string"))))) + (target-node (or tmpl-node str-node))) + (unless target-node + (user-error "Point is not inside a string or template literal")) + (let* ((tagged-node (and tmpl-node (treesit-node-parent tmpl-node))) + (tag-node (and tagged-node + (equal (treesit-node-type tagged-node) "call_expression") + (treesit-node-child-by-field-name tagged-node "function"))) + (tag-name (and tag-node (treesit-node-text tag-node t))) + (mode (cond + (tag-name (or (cdr (assoc tag-name my/edit-indirect-tag-modes)) + major-mode)) + (t 'text-mode))) + (bounds (my/edit-indirect--node-bounds target-node)) + (buf (edit-indirect-region (car bounds) (cdr bounds) t))) + (with-current-buffer buf + (funcall mode))))) + + (with-eval-after-load 'typescript-ts-mode + (define-key typescript-ts-mode-map (kbd "C-c '") #'my/edit-indirect-tagged-template-at-point) + (define-key tsx-ts-mode-map (kbd "C-c '") #'my/edit-indirect-tagged-template-at-point)) + + (with-eval-after-load 'eglot + (add-to-list 'eglot-server-programs '((prisma-ts-mode) "prisma-language-server" "--stdio"))) #+END_SRC *** Java @@ -1582,11 +1886,10 @@ for completion. That makes it really annoying to use; don't use yasnippet with e ;; ("C-x M-:" . consult-complex-command) ("C-x b" . consult-buffer) - ("s-b" . consult-project-buffer) ("s-B" . consult-buffer) ("C-x 4 b" . consult-buffer-other-window) ("C-x 5 b" . consult-buffer-other-frame) - ("C-x t b" . consult-buffer-other-tab) + ;; ("C-x t b" . consult-buffer-other-tab) ("C-x r b" . consult-bookmark) ("C-x p b" . consult-project-buffer) @@ -1625,14 +1928,31 @@ 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) 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-project-recent-file + consult-source-recent-file + consult-source-buffer ;; :preview-key "M-." + consult-source-project-recent-file :preview-key '(:debounce 0.4 any))) (setopt consult-narrow-key "<") ;; "C-+" @@ -1683,6 +2003,8 @@ for completion. That makes it really annoying to use; don't use yasnippet with e ("C-c C" . org-capture) ("C-c l" . org-id-store-link)) + (setopt org-babel-load-languages '((emacs-lisp . t) (sql . t))) + (setopt org-confirm-babel-evaluate nil) (add-hook 'org-mode-hook 'visual-line-mode) (setopt org-link-keep-stored-after-insertion t org-capture-templates @@ -1717,77 +2039,78 @@ for completion. That makes it really annoying to use; don't use yasnippet with e (set-face-attribute 'org-level-7 nil :font "Iosevka Aile") (set-face-attribute 'org-level-8 nil :font "Iosevka Aile") - (require 'org-present) - (require 'face-remap) - (require 'visual-fill-column) - (defvar-local my/org-present-face-remapping-cookies nil) - (defun my/org-present-start () - (setq-local my/org-present-face-remapping-cookies nil) - ;; Tweak font sizes - (dolist (face '((default :height 2.0) - (header-line :height 4.0 :background "#303446") - (org-document-title :height 1.75) - (org-code :height 2.0) - (org-verbatim :height 2.0) - (org-block :height 1.25) - (org-block-begin-line :height 0.7) - (org-level-1 :height 3.0) - (org-level-2 :height 3.0) - (org-level-3 :height 3.0) - (org-level-4 :height 3.0) - (org-level-5 :height 3.0) - (org-level-6 :height 3.0) - (org-level-7 :height 3.0) - (org-level-8 :height 3.0))) - (push (apply #'face-remap-add-relative face) my/org-present-face-remapping-cookies)) + (with-eval-after-load 'org-present + (require 'face-remap) + (require 'visual-fill-column) + + (defun my/org-present-start () + (setq-local my/org-present-face-remapping-cookies nil) + ;; Tweak font sizes + (dolist (face '((default :height 2.0) + (header-line :height 4.0 :background "#303446") + (org-document-title :height 1.75) + (org-code :height 2.0) + (org-verbatim :height 2.0) + (org-block :height 1.25) + (org-block-begin-line :height 0.7) + (org-level-1 :height 3.0) + (org-level-2 :height 3.0) + (org-level-3 :height 3.0) + (org-level-4 :height 3.0) + (org-level-5 :height 3.0) + (org-level-6 :height 3.0) + (org-level-7 :height 3.0) + (org-level-8 :height 3.0))) + (push (apply #'face-remap-add-relative face) my/org-present-face-remapping-cookies)) - ;; Set a blank header line string to create blank space at the top - (setq header-line-format " ") + ;; Set a blank header line string to create blank space at the top + (setq header-line-format " ") - ;; Display inline images automatically - (org-display-inline-images) - (org-present-hide-cursor) + ;; Display inline images automatically + (org-display-inline-images) + (org-present-hide-cursor) - ;; Center the presentation and wrap lines - (visual-fill-column-mode 1) - (setq visual-fill-column-center-text t) - ;; (visual-line-mode 1) - ) + ;; Center the presentation and wrap lines + (visual-fill-column-mode 1) + (setq visual-fill-column-center-text t) + ;; (visual-line-mode 1) + ) - (defun my/org-present-end () - ;; Reset font customizations - (dolist (cookie my/org-present-face-remapping-cookies) - (face-remap-remove-relative cookie)) + (defun my/org-present-end () + ;; Reset font customizations + (dolist (cookie my/org-present-face-remapping-cookies) + (face-remap-remove-relative cookie)) - ;; Clear the header line string so that it isn't displayed - (setq header-line-format nil) + ;; Clear the header line string so that it isn't displayed + (setq header-line-format nil) - ;; Stop displaying inline images - (org-remove-inline-images) - (org-present-show-cursor) + ;; Stop displaying inline images + (org-remove-inline-images) + (org-present-show-cursor) - ;; Stop centering the document - (visual-fill-column-mode 0) - ;; (visual-line-mode 0) - ) + ;; Stop centering the document + (visual-fill-column-mode 0) + ;; (visual-line-mode 0) + ) - (add-hook 'org-present-mode-hook 'my/org-present-start) - (add-hook 'org-present-mode-quit-hook 'my/org-present-end) + (add-hook 'org-present-mode-hook 'my/org-present-start) + (add-hook 'org-present-mode-quit-hook 'my/org-present-end)) - (require 'org-roam) - (bind-keys ("C-c n l" . org-roam-buffer-toggle) - ("C-c n f" . org-roam-node-find) - ("C-c n g" . org-roam-graph) - ("C-c n i" . org-roam-node-insert) - ("C-c n c" . org-roam-capture)) + ;; (require 'org-roam) + ;; (bind-keys ("C-c n l" . org-roam-buffer-toggle) + ;; ("C-c n f" . org-roam-node-find) + ;; ("C-c n g" . org-roam-graph) + ;; ("C-c n i" . org-roam-node-insert) + ;; ("C-c n c" . org-roam-capture)) ;; (setq org-roam-node-display-template (concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag))) - (org-roam-db-autosync-mode) + ;; (org-roam-db-autosync-mode) - (add-hook 'org-mode-hook 'org-modern-mode) + (use-package org-modern + :hook org-mode-hook) - (define-key org-mode-map (kbd "C-c C-r") verb-command-map) + ;; (define-key org-mode-map (kbd "C-c C-r") verb-command-map) #+END_SRC ** Terminal @@ -1808,7 +2131,9 @@ for completion. That makes it really annoying to use; don't use yasnippet with e #+END_SRC #+BEGIN_SRC elisp - (eat-eshell-mode) + (use-package eat + :commands (eat) + :hook (eshell-load-hook . eat-eshell-mode)) #+END_SRC ** Slack @@ -1829,32 +2154,16 @@ for completion. That makes it really annoying to use; don't use yasnippet with e #+END_SRC #+BEGIN_SRC elisp - (setopt lui-time-stamp-format "[%Y-%m-%d %H:%M]") - (setopt slack-render-profile-images-p nil) - (setopt slack-buffer-function #'switch-to-buffer) -#+END_SRC + (use-package lui + :defer t + :custom + (lui-time-stamp-format "[%Y-%m-%d %H:%M]")) -** Kubernetes -:PROPERTIES: -:header-args:elisp: :tangle target/desktop/emacs-kubernetes.el -:END: - -#+BEGIN_SRC nix :tangle target/desktop/emacs-kubernetes.nix - { - programs.emacs = { - enable = true; - extraPackages = epkgs: with epkgs; [ - kubed - ]; - extraConfig = builtins.readFile ./emacs-kubernetes.el; - }; - } -#+END_SRC - -#+BEGIN_SRC elisp - (use-package kubed - :bind-keymap - ("s-k" . kubed-prefix-map)) + (use-package slack + :commands slack-start + :custom + (slack-render-profile-images-p nil) + (slack-buffer-function #'switch-to-buffer)) #+END_SRC ** Ledger @@ -1888,10 +2197,13 @@ for completion. That makes it really annoying to use; don't use yasnippet with e #+END_SRC #+BEGIN_SRC elisp - (setopt age-default-identity "~/source/dotfiles/keys/yubi1age.txt") - (setopt age-default-recipient "~/source/dotfiles/keys/myself.txt") - (setenv "PINENTRY_PROGRAM" "pinentry-mac") - (age-file-enable) + (use-package age + :custom + (setopt age-default-identity "~/source/dotfiles/keys/yubi1age.txt") + (age-default-recipient "~/source/dotfiles/keys/myself.txt") + :config + (setenv "PINENTRY_PROGRAM" "pinentry-mac") + (age-file-enable)) #+END_SRC ** Claude Code @@ -1906,12 +2218,13 @@ for completion. That makes it really annoying to use; don't use yasnippet with e enable = true; extraPackages = epkgs: with epkgs; [ (epkgs.trivialBuild { - name = "claude-code"; + pname = "claude-code"; + version = "0.4.5"; src = pkgs.fetchFromGitHub { owner = "stevemolitor"; repo = "claude-code.el"; - rev = "4a9914bd4161eb43f489820f9174c62390e5adc8"; - sha256 = "sha256-ISlD6q1hceckry1Jd19BX1MfobHJxng5ulX2gq9f644="; + rev = "03199df8b3a1e9cd4857f0851f7a912ba524aff3"; + sha256 = "sha256-5QJrWIu4EgnHcOqMwlrs2JBBx7aI9OaSJswesr6Apfk="; }; packageRequires = [ epkgs.inheritenv ]; }) @@ -1922,8 +2235,9 @@ for completion. That makes it really annoying to use; don't use yasnippet with e #+END_SRC #+BEGIN_SRC elisp - (autoload 'claude-code-command-map "claude-code" :type 'keymap) - ;; (keymap-global-set "C-c c" claude-code-command-map) + (use-package claude-code + :bind-keymap ("C-c c" . claude-code-command-map) + :custom (claude-code-optimize-window-resize nil)) #+END_SRC * Theme I like Catppuccin Frappe as my theme. Let's use it in as many places as we can. @@ -2014,6 +2328,13 @@ I like Catppuccin Frappe as my theme. Let's use it in as many places as we can. return config #+END_SRC +** Ghostty +#+BEGIN_SRC nix :tangle target/desktop/ghostty-theme.nix + { + programs.ghostty.settings.theme = "Catppuccin Frappe"; + } +#+END_SRC + ** Emacs :PROPERTIES: :header-args:elisp: :tangle target/desktop/emacs-theme.el @@ -2039,7 +2360,7 @@ I like Catppuccin Frappe as my theme. Let's use it in as many places as we can. ;; (set-face-attribute 'default nil :font "JetBrainsMono Nerd Font" :height 130) ;; (set-face-attribute 'fixed-pitch nil :font "JetBrainsMono Nerd Font" :height 130) - (set-face-attribute 'default nil :font "Iosevka Term" :height my-base-font-size :width 'expanded) - (set-face-attribute 'fixed-pitch nil :font "Iosevka Term" :height my-base-font-size :width 'expanded) + (set-face-attribute 'default nil :font "Iosevka Term SS14" :height my-base-font-size) + (set-face-attribute 'fixed-pitch nil :font "Iosevka Term SS14" :height my-base-font-size) (set-face-attribute 'variable-pitch nil :font "Iosevka Etoile" :height my-base-font-size) #+END_SRC