Files
dotfiles/home.org
2026-02-16 23:28:42 -05:00

55 KiB
Raw Blame History

Packages

Home-manager "packages" are simply binaries that are placed on the path, without any attached configuration. The code blocks in this section contain package lists that go under the packages section above.

SBCL is the current leading open-source common lisp compiler. It requires any libraries you want to be availble to be installed using withPackages. Many lisp projects I work on have this overridden in a nix devshell I think I'd also like to use it for scripting / exploratory development, though, so I'm installing it globally with a useful set of libraries. I should curate this somewhat carefully so it doesn't get carried away…

  {pkgs, ...}:
  {
    home.packages = with pkgs; [
      rlwrap
      (sbcl.withPackages (ps: with ps; [
        alexandria
        cl-markup
        cl-mock
        cl-rdkafka
        clack
        clack-handler-woo
        dbd-mysql
        dbi
        dexador
        fare-csv
        group-by
        lack-request
        lack-response
        local-time
        log4cl
        mito
        myway
        prove
        prove-asdf
        replic
        serapeum
        shasht
        slynk
        smug
        split-sequence
        str
        sxql-composer
        trivia
        trivial-ws
        trivial-open-browser
        trivial-timeout
        uuid
      ]))
    ];
  }

rage is a rust implementation of age. It implements a simple system for generating public / private encryption keys, and encrypting / decrypting files with those keys. age-plugin-yubikey, as the name implies, allows generating and storing age keys on a Yubikey. rage is required over age when using age.el, because it supports pinentry. That allows it to prompt for yubikey pins using a dialog, rather than a command line prompt.

  {pkgs, ...}:
  {
    home.packages = with pkgs; [
      rage
      age-plugin-yubikey
      yubikey-manager
    ];
  }

dust, fd, ripgrep, and xh are rust re-implementations of a few longtime Linux command-line tools. They boost better speed, or simply more streamlined features.

Command Original
dust du
fd find
ripgrep grep
xh curl
  {pkgs, ...}:
  {
    home.packages = with pkgs; [
      dust
      fd
      ripgrep
      xh
    ];
  }

ledger is how I manage my budget. It's a plain-text accounting tool that's really awesome.

  {pkgs, ...}:
  {
    home.packages = with pkgs; [
      ledger   
    ];
  }

nil is an LSP server for nix files.

  {pkgs, ...}:
  {
    home.packages = with pkgs; [
      nil   
    ];
  }

Magic Wormhole is a quick-and-easy way to securely transfer a file or directory between any two computers in the world, assuming that you have an out-of-band way to communicate a passphrase between them. Once installed, the actual command is wormhole.

  {pkgs, ...}:
  {
    home.packages = with pkgs; [
      magic-wormhole   
    ];
  }

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.

  {pkgs, kube, ...}:
  {
    home.packages = with pkgs; [
      aws-iam-authenticator
      awscli2
      iosevka
      kube
      kubectl
      mariadb.client
      nerd-fonts.jetbrains-mono
      openssh
      tokei
    ];

   fonts.fontconfig.enable = true;
  }

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 path).

Simple Programs

Although Home-Manager generates config files for these programs, they don't need their own dedicated configuration. I'm not sure what benefit there is to enabling jq as a Home Manager program, rather than just as an available package, but there doesn't seem to be a downside.

  {
    programs = {
      bash.enable = true;
      gh.enable = true;
      home-manager.enable = true;
      jq.enable = true;
      lazygit.enable = true;
      zoxide.enable = true;
    };
  }

Bat

bat is a less alternative that performs syntax highlighting for a variety of languages. It also provides syntax-highlighting versions of a few other commands. It's honestly not incredibly useful, but still nice to have.

  {
    programs.bat = {
      enable = true;
      config.style = "numbers";
    };
  }

Direnv

direnv allows loading per-project paths as I navigate to their directories on the command line. I leverage this with the emacs envrc package.

  {
    programs.direnv = {
      enable = true;
      nix-direnv.enable = true;
    };
  }

Eza

eza is an ls replacement that offers icons, colors, and some easier command-line options.

  {
    programs.eza = {
      enable = true;
      enableBashIntegration = true;
      enableZshIntegration = true;
      icons = "auto";
    };
  }

Fzf

fzf offers a variety of fuzzy finders right from the shell. I haven't been using this, so I'll turn it off for now.

  {
    programs.fzf = {
      defaultCommand = "fd --type f";
      fileWidgetCommand = "fd --type f";
      fileWidgetOptions = ["--preview" "'bat --color=always --style=numbers --line-range=:500 {}'"];
      changeDirWidgetCommand = "fd --type d";
      tmux.enableShellIntegration = true;
    };
  }

Git

We all know what git does.

  {
    programs.git = {
      enable = true;
      settings.user.name = "Joe Frikker";
      settings.user.email = "joe.frikker@shibumi.com";
    };

    programs.delta = {
      enable = true;
      enableGitIntegration = true;
    };
  }

Helix

The best text editor ever? Heresy!

  {
    programs.helix = {
      enable = true;
      defaultEditor = true;

      settings.editor = {
        auto-format = false;
        auto-pairs = false;
        auto-save.after-delay.enable = true;
        auto-save.after-delay.timeout = 1000;
        cursorline = true;
        idle-timeout = 0;
        rulers = [100];
        text-width = 100;
        lsp.goto-reference-include-declaration = false;
        cursor-shape.insert = "bar";
        smart-tab.supersede-menu = true;
        whitespace.render.newline = "all";
      };

      settings.keys.normal.space = {
        space = "file_picker";
        "," = "buffer_picker";
        # b = "@:sh wezterm cli spawn --cwd . -- tig blame <C-r>%<ret>";
      };


      languages.language-server.eslint = {
        args = ["--stdio"];
        command = "vscode-eslint-language-server";
        config.nodePath = "";
        config.quiet = false;
        config.rulesCustomizations = [];
        config.run = "onType";
        config.validate = "on";
        config.codeAction.disableRuleComment.enable = true;
        config.codeAction.disableRuleComment.location = "separateLine";
        config.codeAction.showDocumentation.enable = true;
        config.problems.shortenToSingleLine = false;
      };

      languages.language-server.jdtls.config.java = {
        autobuild.enabled = true;
        completion.maxResults = 1000;
        format.settings.url = "https://raw.githubusercontent.com/google/styleguide/gh-pages/eclipse-java-google-style.xml";
      };

      languages.language-server.rust-analyzer.config.files.watcher = "client";

      languages.language = [
        {
          name = "typescript";
          language-servers = [ "typescript-language-server" "vscode-eslint-language-server" ];
          indent.tab-width = 4;
          indent.unit = "    ";
        }
        {
          name = "tsx";
          language-servers = [ "typescript-language-server" "vscode-eslint-language-server" ];
          indent.tab-width = 4;
          indent.unit = "    ";
        }
        {
          name = "java";
          indent.tab-width = 4;
          indent.unit = "    ";
        }
      ];
    };
  }

Starship

starship gives me a nice shell prompt, mostly showing the versions of relevant software I have installed. It's shell-agnostic, so it works with both bash and zsh.

  {
    programs.starship = {
      enable = true;
      settings = {
        add_newline = false;
        container.disabled = true;
        git_branch.symbol = " ";
        git_status.ahead = "⇡\${count}";
        git_status.behind = "⇣\${count}";
        git_status.diverged = "⇕⇡\${ahead_count}⇣\${behind_count}";
        git_status.format = "([\\[\$conflicted\$staged\$ahead_behind\\]](\$style) )";
        java.symbol = " ";
        line_break.disabled = true;
        nix_shell.symbol = " ";
        nodejs.symbol = " ";
        package.disabled = true;
        palette = "catppuccin_frappe";
        python.symbol = " ";
        package.symbol = " ";
        rust.symbol = " ";
        docker_context.disabled = true;
        shell.disabled = false;
      };
    };
  }

Tmux

I'm using wezterm instead of tmux for now, so this is disabled, but I'm keeping the config here in case I need it later.

  {pkgs, ...}:
  {
    programs.tmux = {
      baseIndex = 1;
      clock24 = true;
      customPaneNavigationAndResize = true;
      historyLimit = 50000;
      enable = true;
      escapeTime = 0;
      keyMode = "vi";
      mouse = true;
      shell = pkgs.zsh + "/bin/zsh";
      shortcut = "Space";
      tmuxinator.enable = true;
      terminal = "xterm-256color";
      extraConfig = ''
  bind-key -T copy-mode-vi 'v' send -X begin-selection
  bind-key -T copy-mode-vi 'y' send -X copy-selection-and-cancel

  bind-key -T launcher g popup -h 100% -w 100% -E lazygit
  bind-key g switch-client -Tlauncher
  set -g default-command ""
      '';
    };
  }

Wezterm

wezterm is a fast terminal emulator and multiplexer. It is configured using lua. We'll define its config in two blocks, so we can get lua syntax highlighting in org. First, the nix configuration:

  {
    programs.wezterm = {
      enable = true;
      extraConfig = builtins.readFile ./wezterm.lua;
    };
  }

Then the real lua config:

  local config = {}
  config.default_prog = { '/Users/jfrikker/.nix-profile/bin/zsh', '-l' }
  config.font_size = 13.0
  -- config.font = wezterm.font("JetBrains Mono")
  config.inactive_pane_hsb = {
    saturation = 0.5,
    brightness = 0.5,
  }
  config.pane_focus_follows_mouse = true
  config.switch_to_last_active_tab_when_closing_tab = true
  config.tab_bar_at_bottom = true
  config.use_fancy_tab_bar = false
  config.window_decorations = "RESIZE"

I use a workspace switcher plugin, bouned to cmd-s, that lets me quickly switch between per-project workspaces using a single OS window.

  local workspace_switcher = wezterm.plugin.require("https://github.com/MLFlexer/smart_workspace_switcher.wezterm")
  workspace_switcher.zoxide_path='~/.nix-profile/bin/zoxide'

Set up a default window layout when switching to a workspace that hasn't been opened in this session yet. This will create three tabs, caled "edit", "run", and "term", and open helix in the first one. I use "edit" for running the editor (obviously), "run" for building / running the application, and "term" for miscellaneous other tasks. I sometimes split the "run" window into multiple panes if the project requires multiple things to run simultaneously.

  wezterm.on('smart_workspace_switcher.workspace_switcher.created', function(window, path, workspace)
    local editor = window:active_tab()
    editor:set_title("edit")
    editor:panes()[1]:send_text("hx\n")
    local run = window:spawn_tab {}
    run:set_title "run"
    local term = window:spawn_tab {}
    term:set_title "term"
    editor:activate()
    end)

Put the current workspace name in the lower-right corner of the window. Taken from [https://alexplescan.com/posts/2024/08/10/wezterm/].

  local function segments_for_right_status(window)
     return {
        window:active_workspace(),
     }
  end

  wezterm.on('update-status',
             function(window, _)
                local SOLID_LEFT_ARROW = utf8.char(0xe0b2)
                local segments = segments_for_right_status(window)
                
                local color_scheme = window:effective_config().resolved_palette
                -- Note the use of wezterm.color.parse here, this returns
                -- a Color object, which comes with functionality for lightening
                -- or darkening the colour (amongst other things).
                local bg = wezterm.color.parse(color_scheme.background)
                local fg = color_scheme.foreground

                -- Each powerline segment is going to be coloured progressively
                -- darker/lighter depending on whether we're on a dark/light colour
                -- scheme. Let's establish the "from" and "to" bounds of our gradient.
                local gradient_to, gradient_from = bg
                gradient_from = gradient_to:lighten(0.2)
                
                -- Yes, WezTerm supports creating gradients, because why not?! Although
                -- they'd usually be used for setting high fidelity gradients on your terminal's
                -- background, we'll use them here to give us a sample of the powerline segment
                -- colours we need.
                local gradient = wezterm.color.gradient(
                   {
                      orientation = 'Horizontal',
                      colors = { gradient_from, gradient_to },
                   },
                   #segments -- only gives us as many colours as we have segments.
                )

                -- We'll build up the elements to send to wezterm.format in this table.
                local elements = {}

                for i, seg in ipairs(segments) do
                   local is_first = i == 1

                   if is_first then
                      table.insert(elements, { Background = { Color = 'none' } })
                   end
                   table.insert(elements, { Foreground = { Color = gradient[i] } })
                   table.insert(elements, { Text = SOLID_LEFT_ARROW })

                   table.insert(elements, { Foreground = { Color = fg } })
                   table.insert(elements, { Background = { Color = gradient[i] } })
                   table.insert(elements, { Text = ' ' .. seg .. ' ' })
                end

                window:set_right_status(wezterm.format(elements))
  end)

Now we'll set up some keybindings:

Key Action
s-s Switch Workspace
s Vertical Split
s-\ Horizontal split
s-hjkl Switch to the pane in that direction
s-g Launch lazygit in a new tab. The tab closes when lazygit exits.
s-z Toggle full-screen zoom for the active pane.
s-t Launch a terminal in a new tab.
s-d Close the current tab (with confirmation).
s-123456789 Switch to the tab with the corresponding number.
  config.leader = { key = ' ', mods = 'CTRL', timeout_milliseconds = 10000 }
  config.keys = {
    {
  key = "s",
  mods = "CMD",
  action = workspace_switcher.switch_workspace(),
    },
    {
  key = "-",
  mods = "CMD",
  action = wezterm.action.SplitVertical {},
    },
    {
  key = "\\",
  mods = "CMD",
  action = wezterm.action.SplitHorizontal {},
    },
    {
  key = "h",
  mods = "CMD",
  action = wezterm.action.ActivatePaneDirection "Left",
    },
    {
  key = "j",
  mods = "CMD",
  action = wezterm.action.ActivatePaneDirection "Down",
    },
    {
  key = "k",
  mods = "CMD",
  action = wezterm.action.ActivatePaneDirection "Up",
    },
    {
  key = "l",
  mods = "CMD",
  action = wezterm.action.ActivatePaneDirection "Right",
    },
    {
  key = "g",
  mods = "CMD",
  action = wezterm.action.SpawnCommandInNewTab {
    args = { wezterm.home_dir .. "/.nix-profile/bin/zsh", "-l", "-c", "lazygit" }
  }
    },
    {
  key = "t",
  mods = "CMD",
  action = wezterm.action.SpawnCommandInNewTab {}
    },
    {
  key = 'z',
  mods = 'CMD',
  action = wezterm.action.TogglePaneZoomState,
    },
    {
  key = 'd',
  mods = 'CMD',
  action = wezterm.action.CloseCurrentTab { confirm = true },
    },
  }

  for i = 1, 9 do
    -- CTRL+ALT + number to activate that tab
    table.insert(config.keys, {
  key = tostring(i),
  mods = 'LEADER',
  action = wezterm.action.ActivateTab(i - 1),
    })
  end

Zsh

This is my default shell for now. Unfortunately, nix requires a posix-compliant shell, so bash or zsh it is.

  {
    programs.zsh = {
      enable = true;
      oh-my-zsh.enable = true;
      syntaxHighlighting.enable = true;
      autosuggestion.enable = true;
      historySubstringSearch.enable = true;
      history.share = false;
      cdpath = ["~/source"];
    };
  }

Actually, nix really requires bash. This is a plugin that allows using zsh from nix-shell.

  {pkgs, ...}:
  {
    programs.zsh.plugins = [
      {
        name = "zsh-nix-shell";
        file = "nix-shell.plugin.zsh";
        src = pkgs.fetchFromGitHub {
          owner = "chisui";
          repo = "zsh-nix-shell";
          rev = "v0.7.0";
          sha256 = "149zh2rm59blr2q458a5irkfh82y3dwdich60s9670kl3cl5h2m1";
        };
      }
    ];
  }

Emacs

Emacs is a huge portion of this configuration, so although it's a home-manager "program", I'm breaking it out into its own top-level section. Many of the sections here have two code blocks: a nix code block listing the emacs packages to include, and an elisp code block to configure them in emacs.

We'll put the actual emacs configs into separate files, rather than into nix string literals. This helps with a few places where nix's string interpolation was causing problems.

Keybindings

These keybindings are scattered throughout this file, I'm collecting them all here to make it easier to track them.

Key Normal Key Action Mode
s-b Switch project buffer
s-B Switch buffer
s-f C-x p f Open file in current project
s-F C-x p p Switch project
S-s C-x t RET Switch tab
C-c a Org agenda
C-c A Eglot code actions prog
C-c c Org capture
C-c D Show flymake diagnostics prog
C-c h Consult shell history
C-c I Eglot find implementation prog
C-c l Org store link
C-c n Org Roam prefix
C-c R Eglot rename prog
C-c u Cycle string inflection various
C-c M-g Magit file dispatch

Global Settings

These are just some general settings that apply everywhere. First, a few packages:

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        doom-modeline
        ligature
        mixed-pitch
        ace-window
      ];
      extraConfig = builtins.readFile ./emacs-global.el;
    };
  }

Turn off some default settings that I don't find useful.

  (setq inhibit-startup-screen t)
  (setq initial-scratch-message nil)
  (menu-bar-mode -1)
  (scroll-bar-mode -1)
  (tool-bar-mode -1)
  (setq make-backup-files nil)
  (setq split-width-threshold 200)
  (setq 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 view-read-only t)

Make sure which-key mode is always on. The on-the-fly keymap help it gives is indespensable.

  (which-key-mode)

Automatically refresh dired buffers when switching to them, instead of requiring an explicit refresh.

  (setopt dired-auto-revert-buffer t)

Similarly, highlighting the line the cursor is on is really useful for figuring out where you are, especially on monitors with a lot of vertical space.

  (global-hl-line-mode)

From the docs: "If complete, TAB first tries to indent the current line, and if the line was already indented, then try to complete the thing at point." Recommended by the corfu readme.

  (setq tab-always-indent 'complete)

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.

  (setq-default cursor-type 'bar)

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?

  (auto-save-visited-mode)

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.

  (repeat-mode)

Save the minibuffer history to disk, so recently-used options show up at the top of the picker across restarts.

  (savehist-mode)

The doom modeline looks nice. I'm turning off IRC notifications, because it was slow to update (and it would update after every command, slowing down everything). I don't need the workspace name when I have the tab bar at the top of the window. The other settings just clear up some space, and make things look at little nicer.

  (setopt doom-modeline-irc nil)
  (setopt doom-modeline-workspace-name nil)
  (setopt doom-modeline-buffer-encoding nil)
  (setopt doom-modeline-vcs-max-length 30)
  (doom-modeline-mode)

Ligature mode combines character sequences like <= into a single on-screen glyph. It looks kind of cool, so I'm turning it on in prog mode. The enabled ligatures are font-specific, taken from the ligature wiki.

  (require 'ligature)
  (ligature-set-ligatures 'prog-mode 
                          '("--" "---" "==" "===" "!=" "!==" "=!="
                            "=:=" "=/=" "<=" ">=" "&&" "&&&" "&=" "++" "+++" "***" ";;" "!!"
                            "??" "???" "?:" "?." "?=" "<:" ":<" ":>" ">:" "<:<" "<>" "<<<" ">>>"
                            "<<" ">>" "||" "-|" "_|_" "|-" "||-" "|=" "||=" "##" "###" "####"
                            "#{" "#[" "]#" "#(" "#?" "#_" "#_(" "#:" "#!" "#=" "^=" "<$>" "<$"
                            "$>" "<+>" "<+" "+>" "<*>" "<*" "*>" "</" "</>" "/>" "<!--" "<#--"
                            "-->" "->" "->>" "<<-" "<-" "<=<" "=<<" "<<=" "<==" "<=>" "<==>"
                            "==>" "=>" "=>>" ">=>" ">>=" ">>-" ">-" "-<" "-<<" ">->" "<-<" "<-|"
                            "<=|" "|=>" "|->" "<->" "<~~" "<~" "<~>" "~~" "~~>" "~>" "~-" "-~"
                            "~@" "[||]" "|]" "[|" "|}" "{|" "[<" ">]" "|>" "<|" "||>" "<||"
                            "|||>" "<|||" "<|>" "..." ".." ".=" "..<" ".?" "::" ":::" ":=" "::="
                            ":?" ":?>" "//" "///" "/*" "*/" "/=" "//=" "/==" "@_" "__" "???"
                            "<:<" ";;;"))
  (global-ligature-mode t)

Use variable-width fonts in text buffers. mixed-pitch-mode keeps most code blocks as monospace.

  (add-hook 'text-mode-hook 'mixed-pitch-mode)

  (defun no-mixed-pitch ()
    (mixed-pitch-mode -1))

  (add-hook 'yaml-ts-mode-hook 'no-mixed-pitch)
  (add-hook 'mhtml-mode 'no-mixed-pitch)

A basic ace-window setup. Allows switching between windows quickly (although avy can do the same…).

  (require 'ace-window)
  (bind-keys ("M-o" . ace-window)
             ("s-o" . ace-window))
  (setopt aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  (setopt aw-dispatch-always t)

Set up a few project- / workspace-related key bindings.

  (bind-keys ("s-f" . project-find-file)
             ("s-F" . project-switch-project))
  (bind-keys :map tab-bar-mode-map
             ("s-s" . 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.

  (server-mode -1)

All the beeping is annoying. This flashes a huge icon over the screen, which is still annoying, but less so?

  (setq visible-bell t)

I'm trying this out. This disables visual selections. It's bold, which is why I like it :)

  (transient-mark-mode -1)

I use tabs to manage multiple "workspaces". I don't tie a project to a tab or anything; often fixing a single bug might involve pulling in files from multiple projects. Rather, I open a file per bug / fix / issue, and leave it open until I'm done with that issue.

tab-bar-history-mode allows quickly rolling back window changes; it can be used to undo a context change when drilling into a help page or something. I've set up repeat mode to make it easier to move backward / forward through history.

  (require 'tab-bar)

  (bind-keys :repeat-map tab-bar-repeat-map
             ("<left>" . tab-bar-history-back)
             ("<right>" . tab-bar-history-forward))
  (tab-bar-history-mode)

Window Placement

This section contains rules about where display-buffer should place windows. I guess I'm currently going for an IDE-style layout, with one main editor window, and auxiliary bottom and right side windows. I place windows in the bottom or on the right mostly depending on how wide I expect their content to be.

  (setq display-buffer-alist
        '(((or "\\*Flymake diagnostics *"
               "\\*Embark Export *"
               "\\*Embark Collect *"
                 "\\*xref*"
                 "\\*compilation\\*"
                 "\\*sly-mrepl *"
                 "\\*Messages\\*"
                 "\\*lispy-message\\*"
                 "\\*EGLOT connections\\*"
                 "\\*trace-output\\*"
                 "\\*occur\\*"
                 "*eshell\\*")
             (display-buffer-reuse-window 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\\*"
                 "\\*Help\\*"
                 "\\*rustfmt\\*"
                 "\\*slack: *"
                 "\\*forge: *"
                 "new-pullreq")
             (display-buffer-reuse-window display-buffer-in-side-window)
             (side . right)
             (slot . 0)
             (window-width . 0.5)
             (window-parameters (no-delete-other-windows . t)
                                (no-other-window . t)))
            ("\\*Embark Actions\\*"
             (display-buffer-reuse-window display-buffer-in-side-window)
                  (side . right)
                  (slot . -1)
                  (window-width . 0.5)
                  (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))

Editing

This configuration affects general text editing, across all modes. First, some packages:

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        avy
        edit-indirect
        embark
        god-mode
        string-inflection
      ];
      extraConfig = builtins.readFile ./emacs-editing.el;
    };
  }

I find Emacs to be almost unusable on a normal keyboard unless there's some way to avoid all the modifier keys. I tried Evil mode first. But although I love Vim, it just seemed to be hiding too much of the Emacs native functionality. The keybindings were also much harder to configure. I tried God mode, which has the benefit of re-using the normal Emacs keybindings and commands. It was annoying, though, because some read-only buffers (like magit buffers) have their own "normal-mode" commands that don't use modifiers. It felt jarring to switch to "insert mode" to use those keys. It's also hard to repeat sequences of M-f, M-b, etc. I could have made a ton of repeat maps, but I'm worried that having too many repeat maps will also be annoying (I'll have to explicitly 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.

  (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 <backspace>"))))
  (require 'god-mode)
  (require 'which-key)

  (bind-keys :map god-local-mode-map
             ("i" . (lambda () (interactive) (god-mode-all -1)))
             ("z" . repeat))
  (bind-keys ("<escape>" . (lambda () (interactive) (god-mode-all 1))))
  (defun my-god-mode-update-cursor-type ()
    (setq cursor-type (cond
                       (org-present-mode nil)
                       ((or god-local-mode buffer-read-only) 'box)
                       (t 'bar))))

  (add-hook 'post-command-hook 'my-god-mode-update-cursor-type)
  (which-key-enable-god-mode-support)

Avy allows jumping to any place on the screen with just a few keystrokes. I've stolen most of these ideas from Karthinks' article Avy can do Anything. I need to find more uses for this, in line with his article.

  (require 'avy)
  (require 'embark)

  (defun avy-goto-char-timer-embark ()
    (interactive)
    (let ((avy-action-oneshot
           (lambda (pt)
             (unwind-protect
                 (save-excursion
                   (goto-char pt)
                   (embark-act))
               (select-window
                (cdr (ring-ref avy-ring 0)))))))
      (avy-goto-char-timer)))

  (bind-keys
   ("M-j" . avy-goto-char-timer)
   ("M-J" . avy-goto-char-timer-embark)
   ("C-." . embark-act))

Just "silently" wrap when searching at the bottom of a document.

  (setopt isearch-wrap-pause 'no-ding)
  (require 'string-inflection)
  (defun string-inflection-rust-style-cycle-function (str)
    (cond
     ((string-inflection-snake-case-p str)
      (string-inflection-upcase-function str))
     ((string-inflection-upcase-p str)
      (string-inflection-pascal-case-function str))
     (t
      (string-inflection-snake-case-function str))))

  (defun string-inflection-rust-style-cycle ()
    (interactive)
    (string-inflection--symbol-or-region #'string-inflection-rust-style-cycle-function))

  (bind-keys :repeat-map string-inflection-java-map
             ("u" . string-inflection-java-style-cycle))

  (bind-keys :repeat-map string-inflection-rust-map
             ("u" . string-inflection-rust-style-cycle))

  (bind-keys ("C-x 4 n" . edit-indirect-region))

Language Support

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        consult-eglot
        haskell-ts-mode
        jenkinsfile-mode
        lispy
        lua-mode
        nix-mode
        rust-mode
        sly
        sly-macrostep
        sql-indent
        tree-sitter
        treesit-grammars.with-all-grammars
        yaml-pro
      ];
      extraConfig = builtins.readFile ./emacs-languages.el;
    };
  }

I had some weird problems with jsx code where the default M-q would just do a text fill rather than a re-indent. The solution seems to be to only do the syntax-ppss stuff if tree-sitter isn't available? It seems to work for now anyway…

  (electric-pair-mode 1)

  (require 'prog-mode)
  (defun my-prog-fill-reindent-defun (&optional argument)
    "Refill or reindent the paragraph or defun that contains point.

  If the point is in a string or a comment, fill the paragraph that
  contains point or follows point.

  Otherwise, reindent the function definition that contains point
  or follows point."
    (interactive "P")
    (save-excursion
      (let ((treesit-text-node
             (and (treesit-available-p)
                  (treesit-parser-list)
                  (treesit-node-match-p
                   (treesit-node-at (point)) 'text t))))
        (if (or treesit-text-node
                (and (not (and (treesit-available-p) (treesit-parser-list))) (nth 8 (syntax-ppss)))
                (re-search-forward "\\s-*\\s<" (line-end-position) t))
            (fill-paragraph argument (region-active-p))
          (beginning-of-defun)
          (let ((start (point)))
            (end-of-defun)
            (indent-region start (point) nil))))))

  (advice-add 'prog-fill-reindent-defun :override #'my-prog-fill-reindent-defun)

  (require 'flymake)
  (bind-keys :map prog-mode-map
             ("C-c D" . flymake-show-project-diagnostics)
             ("M-n" . flymake-goto-next-error)
             ("M-p" . flymake-goto-prev-error))

  (setq-default indent-tabs-mode nil)
  (setq-default tab-width 4)
  (global-tree-sitter-mode 1)
  (setq-default fill-column 100)

  (defun jf-init-fill-column ()
    (display-fill-column-indicator-mode 1))
  (add-hook 'prog-mode-hook 'jf-init-fill-column)

  (setopt eglot-ignored-server-capabilities '(:inlayHintProvider))
  (add-hook 'eglot-managed-mode-hook (lambda () (eglot-inlay-hints-mode -1)) nil t)
  (require 'eglot)
  (bind-keys :map eglot-mode-map
             ("C-c A" . eglot-code-actions)
             ("C-c R" . eglot-rename)
             ("C-c I" . eglot-find-implementation))

  (setopt ediff-split-window-function 'split-window-horizontally)

  (defun project-nix-flake ()
    (interactive)
    (nix-flake (project-root (project-current t))))
  (bind-keys ("C-x p n" . project-nix-flake))

Rust

  ;; (setq major-mode-remap-alist '((rust-mode . rust-mode)))

  (setopt rust-mode-treesitter-derive t)

  (require 'rust-ts-mode)
  (bind-keys :map rust-ts-mode-map
             ("C-c u" . string-inflection-rust-style-cycle))

YAML

  (defun yaml-validate ()
    (interactive)
    (compile (concat "yamllint -d '{extends: default, rules: {indentation: {indent-sequences: false}, braces: {max-spaces-inside: 1}}}' " (buffer-file-name)) t))

  (add-hook 'yaml-ts-mode-hook 'yaml-pro-ts-mode)

Lisp

  (setopt inferior-lisp-program "sbcl")

  (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)))
  (require 'lispy)
  (bind-keys :map lispy-mode-map
             ("i" . (lambda ()
                      (interactive)
                      (if god-global-mode
                          (god-mode-all -1)
                        (special-lispy-tab)))))

Typescript

  (setopt typescript-ts-mode-indent-offset 4)
  (require 'typescript-ts-mode)
  (defun my-tsx-ts-mode--indent-compatibility-b893426 ()
    "Indent rules helper, to handle different releases of tree-sitter-tsx.
  Check if a node type is available, then return the right indent rules."
    ;; handle https://github.com/tree-sitter/tree-sitter-typescript/commit/b893426b82492e59388a326b824a346d829487e8
    (condition-case nil
        (progn (treesit-query-capture 'tsx '((jsx_fragment) @capture))
               `(((match "<" "jsx_fragment") parent 0)
                 ((parent-is "jsx_fragment") parent typescript-ts-mode-indent-offset)))
      (treesit-query-error
       `(((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)

Java

  (require 'java-ts-mode)
  (bind-keys :map java-ts-mode-map
             ("C-c u" . string-inflection-java-style-cycle))
  (push `((n-p-gp nil "block" "lambda_expression") parent-bol java-ts-mode-indent-offset)
        (cdar java-ts-mode--indent-rules))
  (push `((n-p-gp "}" "block" "lambda_expression") parent-bol 0)
        (cdar java-ts-mode--indent-rules))

  (add-hook 'java-mode-hook (lambda () (c-set-offset 'arglist-intro '+)))

  (defvar jf/class-name-to-file-search-path (list "src/main/java" "src/test/java"))

  (defun jf/class-name-to-file (class-name)
    (let ((root (project-root (project-current t)))
          (fragment (format "%s.java" (replace-regexp-in-string "\\." "/" class-name)))
          (result))
      (dolist (p jf/class-name-to-file-search-path result)
        (unless result
          (let ((path (format "%s/%s" p fragment)))
            (when (file-exists-p (concat root path))
              (setq result path)))))))

  (defun jf/compile-class-to-file ()
    (jf/class-name-to-file (concat (match-string 1) (match-string 2))))

                                          ;(add-to-list 'compilation-error-regexp-alist-alist
                                          ;             '(java-stack-trace .
                                          ;                                ("^[[:space:]]*at \\(\\(?:[[:lower:]]+\\.\\)+\\)[^(]+(\\([[:alnum:]]+\\)\\.java:\\([[:digit:]]+\\))"
                                          ;                                 jf/compile-class-to-file 3)))
                                          ;(add-to-list 'compilation-error-regexp-alist 'java-stack-trace)

SQL

  (add-hook 'sql-mode-hook 'sqlind-minor-mode)

Haskell

  (setopt haskell-ts-use-indent t)

Envrc

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        envrc
      ];
      extraConfig = builtins.readFile ./emacs-envrc.el;
    };
  }
  (add-hook 'after-init-hook 'envrc-global-mode)
  (setopt envrc-show-summary-in-minibuffer nil)

Magit

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        consult-gh
        diff-hl
        forge
        git-timemachine
        magit
      ];
      extraConfig = builtins.readFile ./emacs-magit.el;
    };
  }
  (setopt magit-define-global-key-bindings 'recommended)
  (bind-keys ("s-g" . magit-status))

  (setopt magit-save-repository-buffers 'dontask)
  (setopt magit-commit-show-diff nil)
  (with-eval-after-load 'magit
    (set-face-attribute 'git-commit-summary nil :inherit nil)
    (defun magit-fetch-all-on-load ()
      (magit-run-git-async "fetch" "--all"))
    (add-hook 'magit-status-mode-hook 'magit-fetch-all-on-load)
    (remove-hook 'magit-status-headers-hook 'magit-insert-tags-header)
    (require 'forge)
    (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh))

  (with-eval-after-load 'forge
    (add-hook 'forge-edit-post-hook 'forge-create-pullreq-insert-single-commit-message)
    (setopt forge-status-buffer-default-topic-filters
            (forge--topics-spec :type 'topic :active nil :state 'open :order 'newest)))

  (global-diff-hl-mode)

Vertico

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        cape
        consult
        corfu
        embark-consult
        marginalia
        orderless
        vertico
      ];
      extraConfig = builtins.readFile ./emacs-vertico.el;
    };
  }
  (bind-keys 
   ;; ("C-c M-x" . consult-mode-command)
   ("C-c h" . consult-history)
   ;; ("C-c k" . consult-kmacro)
   ;; ("C-c m" . consult-man)
   ;; ([remap Info-search] . consult-info)

   ;; ("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 r b" . consult-bookmark)
   ("C-x p b" . consult-project-buffer)

   ("M-#" . consult-register-load)
   ("M-'" . consult-register-store)
   ("C-M-#" . consult-register)
   ("M-y" . consult-yank-pop)
   ("M-g e" . consult-compile-error)
   ("M-g f" . consult-flymake)
   ("M-g g" . consult-goto-line)
   ("M-g M-g" . consult-goto-line)
   ("M-g o" . consult-org-heading)
   ("M-g m" . consult-mark)
   ("M-g k" . consult-global-mark)
   ("M-g i" . consult-imenu)
   ("M-g I" . consult-imenu-multi)
   ("M-s r" . consult-ripgrep)
   ("M-s l" . consult-line)
   ("M-s L" . consult-line-multi)
   ("M-s e" . consult-isearch-history)
   :map isearch-mode-map
   ("M-e" . consult-isearch-history)
   ("M-s e" . consult-isearch-history)
   ("M-s l" . consult-line)
   ("M-s L" . consult-line-multi)
   :map minibuffer-local-map
   ("M-s" . consult-history)
   ("M-r" . consult-history))

  (add-hook 'completion-list-mode-hook 'consult-preview-at-point-mode)

  (advice-add 'register-preview :override #'consult-register-window)
  (setopt register-preview-delay 0.5)

  (setopt xref-show-xrefs-function 'consult-xref)
  (setopt xref-show-definitions-function 'consult-xref)

  (require '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
   ;; :preview-key "M-."
   :preview-key '(:debounce 0.4 any))

  (setopt consult-narrow-key "<") ;; "C-+"

  (setopt vertico-count 25)
  (vertico-mode)

  (setopt completion-styles '(orderless basic))

  (marginalia-mode)

  (setopt corfu-cycle t)
  (setopt corfu-popupinfo-mode nil)
  (setopt corfu-quit-at-boundary nil)
  (setopt corfu-on-exact-match 'show)
  (global-corfu-mode)

  ;; Consult users will also want the embark-consult package.
  (add-hook 'embark-collect-mode-hook 'consult-preview-at-point-mode)

  (add-hook 'completion-at-point-functions 'cape-file)

Org

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        org-modern
        org-present
        org-roam
        ox-slack
        verb
        visual-fill-column
      ];
      extraConfig = builtins.readFile ./emacs-org.el;
    };
  }
  (require 'org)

  (bind-keys ("C-c a" . org-agenda)
             ("C-c c" . org-capture)
             ("C-c l" . org-id-store-link))

  (add-hook 'org-mode-hook 'visual-line-mode)
  (setopt org-link-keep-stored-after-insertion t
          org-capture-templates
          '(("t" "Todo" entry
             (here)
             "** TODO %?\n- State \"TODO\" %U\n")
            ("w" "Work stuff")
            ("wp" "Work Project" entry
             (file+headline "~/org/todo/work.org.age" "Next Actions")
             "*** PROJ %?\n- State \"PROJ\" %U\n")
            ("wa" "Work Next Action" entry
             (file+headline "~/org/todo/work.org.age" "Next Actions")
             "*** TODO %?\n- State \"TODO\" %U\n")
            ("p" "Personal stuff")
            ("pp" "Personal Project" entry
             (file+headline "~/org/todo/personal.org.age" "Next Actions")
             "*** PROJ %?\n- State \"PROJ\" %U\n")
            ("pa" "Personal Next Action" entry
             (file+headline "~/org/todo/personal.org.age" "Next Actions")
             "*** TODO %?\n- State \"TODO\" %U\n"))
          org-agenda-files '("~/org/todo/work.org.age" "~/org/todo/personal.org.age")
          org-agenda-todo-ignore-scheduled 'future
          org-agenda-tags-todo-honor-ignore-options t
          org-enforce-todo-dependencies t
          org-agenda-dim-blocked-tasks 'invisible)
  (set-face-attribute 'org-level-1 nil :font "Iosevka Aile")
  (set-face-attribute 'org-level-2 nil :font "Iosevka Aile")
  (set-face-attribute 'org-level-3 nil :font "Iosevka Aile")
  (set-face-attribute 'org-level-4 nil :font "Iosevka Aile")
  (set-face-attribute 'org-level-5 nil :font "Iosevka Aile")
  (set-face-attribute 'org-level-6 nil :font "Iosevka Aile")
  (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))
    
    ;; 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)

    ;; 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))
    
    ;; 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 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)

  (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)

  (add-hook 'org-mode-hook 'org-modern-mode)

  (define-key org-mode-map (kbd "C-c C-r") verb-command-map)

Terminal

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        eat
      ];
      extraConfig = builtins.readFile ./emacs-terminal.el;
    };
  }
  (eat-eshell-mode)

Slack

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        slack
      ];
      extraConfig = builtins.readFile ./emacs-slack.el;
    };
  }
  (setopt lui-time-stamp-format "[%Y-%m-%d %H:%M]")
  (setopt slack-render-profile-images-p nil)
  (setopt slack-buffer-function #'switch-to-buffer)

Kubernetes

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        kubed
      ];
      extraConfig = builtins.readFile ./emacs-kubernetes.el;
    };
  }
  (use-package kubed
    :bind-keymap
    ("s-k" . kubed-prefix-map))

Ledger

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        ledger-mode
      ];
    };
  }

Age

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        age
      ];
      extraConfig = builtins.readFile ./emacs-age.el;
    };
  }
  (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)

Theme

I like Catppuccin Frappe as my theme. Let's use it in as many places as we can.

Bat

  {pkgs, ...}:
  {
    programs.bat = {
      config.theme = "catppuccin-frappe";
      themes = {
        catppuccin-frappe = {
  	      src = pkgs.fetchFromGitHub {
  	        owner = "catppuccin";
  	        repo = "sublime-text"; # Bat uses sublime syntax for its themes
  	        rev = "3d8625d937d89869476e94bc100192aa220ce44a";
  	        sha256 = "3ABUsfJpb6RO6AiuuSL5gwDofJIwC5tlEMzBrlY9/s0=";
  	      };
  	      file = "Frappe.tmTheme";
        };
      };
    };
  }

Helix

  {
    programs.helix.settings.theme = "catppuccin_frappe";
  }

Tmux

  {pkgs, ...}:
  {
    programs.tmux.plugins = [
      {
        plugin = pkgs.tmuxPlugins.catppuccin;
        extraConfig = ''set -g @catppuccin_window_tabs_enabled on
  set -g @catppuccin_window_default_text "#W"
  set -g @catppuccin_window_current_text "#W"'';
      }
    ];
  }

Lazygit

  {pkgs, ...}:
  {
    xdg = {
      enable = true;
      configFile."lazygit/config.yml" = {
        enable = true;
        source = pkgs.fetchFromGitHub
          {
            owner = "catppuccin";
            repo = "lazygit";
            rev = "b2ecb6d41b6f54a82104879573c538e8bdaeb0bf"; # Replace with the latest commit hash
            sha256 = "9BBmWRcjNaJE9T0RKVEJaSnkrbMom0CLYE8PzAT6yFw=";
          } + /themes/frappe.yml;
      };
    };
  }

Starship

  {pkgs, ...}:
  {
    programs.starship.settings = builtins.fromTOML (builtins.readFile
      (pkgs.fetchFromGitHub
        {
          owner = "catppuccin";
          repo = "starship
  ";
          rev = "3e3e54410c3189053f4da7a7043261361a1ed1bc"; # Replace with the latest commit hash
          sha256 = "soEBVlq3ULeiZFAdQYMRFuswIIhI9bclIU8WXjxd7oY=";
        } + /palettes/frappe.toml));
  }

Wezterm

  config.color_scheme = 'Catppuccin Frappe'

  return config

Emacs

  {
    programs.emacs = {
      enable = true;
      extraPackages = epkgs: with epkgs; [
        catppuccin-theme
      ];
      extraConfig = builtins.readFile ./emacs-theme.el;
    };
  }
  (require 'catppuccin-theme)
  (setopt catppuccin-flavor 'frappe)
  (load-theme 'catppuccin :no-confirm)

  ;; (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" :height 130 :width 'expanded)
  (set-face-attribute 'fixed-pitch nil :font "Iosevka" :height 130 :width 'expanded)
  (set-face-attribute 'variable-pitch nil :font "Iosevka Etoile" :height 130)