GitHub Octicon View on GitHub

Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do. — Donald Knuth

1. Introduction

Once upon a time I was a wee little lad playing around with vim. After that, my “ricing” addiction grew, and soon it turned into a dotfiles repo. Since I moved machines often, I wanted a simple way to install all dependencies for my system. What started off as a simple install.sh script turned into a dotfiles repo managed via YADM. However this raised a few issues:

  1. It was slow and clunky. Apps like Discord and Firefox started to clutter up my ~/.config directory, and my .gitignore kept growing. With nix, my config is stored in one folder, and symlinked into place
  2. Applications were all configured using different languages. With home-manager for the most part I can stick to using nix,
  3. Building apps was a pain, and switching laptops was getting annoying.

1.1. Note On Installing

If you like the look of this, that’s marvellous, and I’m really happy that I’ve made something which you may find interesting, however:

This config is insidious. Copying the whole thing blindly can easily lead to undesired effects. I recommend copying chunks instead.

Oh, did I mention that I started this config when I didn’t know any nix or lisp, and this whole thing is a hack job? If you can suggest any improvements, please do so, no matter how much criticism you include I’ll appreciate it :)

1.2. Why Nix?

Nix consists of two parts: a package manager and a language. The language is a rather simple lazy (almost) pure functional language with dynamic typing that specializes in building packages. The package manager, on the other hand, is interesting and pretty unique. It all starts with one idea.

Nix stems from the idea that FHS is fundamentally incompatible with reproducibility. Every time you see a path like /bin/python or /lib/libudev.so, there are a lot of things that you don’t know about the file that’s located there.

What’s the version of the package it came from? What are the libraries it uses? What configure flags were enabled during the build? Answers to these questions can (and most likely will) change the behaviour of an application that uses those files. There are ways to get around this in FHS – for example, link directly to /lib/libudev.so.1.6.3 or use /bin/python3.7 in your shebang. However, there are still a lot of unknowns.

This means that if we want to get any reproducibility and consistency, FHS does not work since there is no way to infer a lot of properties of a given file.

One solution is tools like Docker, Snap, and Flatpak that create isolated FHS environments containing fixed versions of all the dependencies of a given application, and distribute those environments. However, this solution has a host of problems.

What if we want to apply different configure flags to our application or change one of the dependencies? There is no guarantee that you would be able to get the build artifact from build instructions, since putting all the build artifacts in an isolated container guarantees consistency, not reproducibility, because during build-time, tools from host’s FHS are often used, and besides the dependencies that come from other isolated environments might change.

For example, two people using the same docker image will always get the same results, but two people building the same Dockerfile can (and often do) end up with two different images.

1.3. Drawbacks of Nix (on macOS)

The biggest issue with Nix on darwin is that NixOS (and Nix on linux) takes priority. This means:

  1. Apps aren’t guaranteed to build on macOS
  2. External dependencies and overlays (e.g. home-manager) aren’t guaranteed to work perfectly on darwin
  3. GUI application support is almost nonexistent

MacOS is also quite locked down compared to linux, which limits the customization you can do. You also need nix-darwin to manage flake configurations and macOS settings. Be prepared for nix (and other package managers) to break in a future macOS update. On top of this, aarch64-darwin is a Tier 4 platform, if packages that are failing the test aren’t critical, they get merged. You will run into packages that don’t run on m1 at all, and will likely have to PR or open an issue to get them fixed. Lastly, remember that aarch64-darwin is fairly new. Especially if you use the stable channel, expect to have to build the majority of packages from source. Even if you use the unstable/master channels, you will likely end up building some packages from source

1.4. Nix vs Homebrew, Pkgsrc, and Macports

The main package managers on macOS are:

  1. Nix
  2. Macports
  3. Pkgsrc
  4. Homebrew
Pkg Manager Pkg Availability Pkg Freshness Ease of Use Features Performance
Nix (Unstable) 5 5 2.5 4.5 4
Macports 3.5 3 3 3 4
Pkgsrc 2 2 2 4 5
Homebrew 3.5 4.5 4.5 2 2
Radar chart comparing my thoughts on a few macOS package managers

Package management on macOS has a somewhat complex history, mostly owing to the fact that unlike most Linux distributions, macOS does not ship with a default package manager out of the box. It’s not surprising that one of the first projects to solve the problem of package management, Fink, was created very early, with its initial releases predating that of Mac OS X 10.0 by several months. Using Debian’s dpkg and apt as its backend, Fink is still actively maintained, though I haven’t looked at it very closely.

MacPorts, on the other hand, was released in 2002 as part of OpenDarwin, while Homebrew was released seven years later as a “solution” to many of the shortcomings that the author saw in MacPorts. Pkgsrc is an older package manager for UNIX-like systems, and supports several BSD’s, as well as Linux and MacOS. Nix is a cross-platform package manager that utilizes a purely functional deployment model where software is installed into unique directories generated through cryptographic hashes. It is also the name of the tool’s programming language. A package’s hash takes into account the dependencies. This package management model advertises more reliable, reproducible, and portable packages.

Homebrew makes several questionable design decisions, but one of these deserves its own section: the choice to explicitly eschew root (in fact, it will refuse to work at all if run this way). This fundamentally is a very bad idea: package managers that install software for all users of your computer, as Homebrew does by default, should always require elevated privileges to function correctly. This decision has important consequences for both security and usability, especially with the advent of System Integrity Protection in OS X El Capitan.

For quite a while, Homebrew essentially considered itself the owner of /usr/local (both metaphorically and literally, as it would change the permissions of the directory), to the point where it would do things like plop its README down directly into this folder. After rootless was introduced, it moved most of its files to subdirectories; however, to maintain the charade of “sudo-less” installation, Homebrew will still trash the permissions of folders inside /usr/local. Homebrew’s troubleshooting guide lists these out, because reinstalling macOS sets the permissions back to what they’re supposed to be and breaks Homebrew in the process.

If commands fail with permissions errors, check the permissions of /usr/local’s subdirectories. If you’re unsure what to do, you can run cd /usr/local && sudo chown -R $(whoami) bin etc include lib sbin share var opt Cellar Caskroom Frameworks.

MacPorts, on the other hand, swings so far in the other direction that it’s actually borderline inconvenient to use in some sense. Philosophically, MacPorts has a very different perspective of how it should work: it tries to prevent conflicts with the system as much as possible. To achieve this, it sets up a hierarchy under /opt (which is the annoying bit, because this directory is not on $PATH by default, nor is picked up by compilers without some prodding).

Of course, this design means that there is a single shared installation is among users, so running port requires elevated privileges whenever performing an operation that affects all users (which, admittedly, is most of the time). MacPorts is smart about this, though: it will shed permissions and run as the macports user whenever possible.

In line with their stated philosophy to prevent conflicts with macOS, MacPorts will set up its own tools in isolation from those provided by the system (in fact, builds run in “sandboxes” under the macports user, where attempts to access files outside of the build directory–which includes system tools–are intercepted and blocked). This means MacPorts needs to install some “duplicate” tools (whereas Homebrew will try to use the ones that come with your system where possible), the downside of which is that there is an one-time “up-front” cost as it installs base packages. The upside is that this approach is significantly more contained, which makes it easier to manage and more likely to continue working as macOS changes under it.

Finally, MacPorts just seems to have a lot of thought put into it with regards to certain aspects: for example, the MacPorts Registry database is backed by SQLite by default, which makes easily introspectable in case something goes wrong. Another useful feature is built-in “livechecks” for most ports, which codify upstream version checks and make it easy to see when MacPorts’s package index need to be updated.

I won’t delve too much into why I choose nix in the end (as I’ve covered it before), but I feel like nix takes the best of both worlds and more. You have the ease of use that homebrew provides, the sandboxing and though that was put into MacPorts, while having excellent sandboxing and the seperate nixbld user.

2. Installing and notes

NOTE: These are available as an executable script ./extra/install.sh

Install Nix. I have it setup for multi-user, but you can remove the --daemon if you want a single user install

bash
#
    sh <(curl -L https://nixos.org/nix/install) --daemon

Launch an ephemeral shell with git, nixUnstable, and Emacs

bash
#
    nix-shell -p nixUnstable git emacs

Tangle the .org files (not needed, but recommend in case I forgot to update tangled files)

bash
#
    git clone --depth 1 https://github.com/shaunsingh/nix-darwin-dotfiles.git ~/nix-darwin-dotfiles/ && cd nix-darwin-dotfiles
    emacs --batch --eval "(progn (require 'org) (setq org-confirm-babel-evaluate nil) (org-babel-tangle-file \"~/nix-darwin-dotfiles/nix-config.org\"))"
    emacs --batch --eval "(progn (require 'org) (setq org-confirm-babel-evaluate nil) (org-babel-tangle-file \"~/nix-darwin-dotfiles/configs/doom/config.org\"))"

(if emacs asks you for comment syntax, put `# ` for everything) Build, and switch to the dotfiles

bash
#
    nix build ~/nix-darwin-dotfiles\#darwinConfigurations.shaunsingh-laptop.system --extra-experimental-features nix-command --extra-experimental-features flakes
    ./result/sw/bin/darwin-rebuild switch --flake .#shaunsingh-laptop

(note, --extra-experimental-features is only needed the first time around. After that the configuration will edit /etc/nix/nix.conf to enable flakes and nix-command by default) Symlinking with nix (and managing doom with nix-doom-emacs) is very finicky, so for now we need to manually symlink them

bash
#
    ln -s ~/nix-darwin-dotfiles/configs/doom/ ~/.config/doom

Install doom emacs

bash
#
    git clone --depth 1 https://github.com/hlissner/doom-emacs ~/.config/emacs
    ~/.config/emacs/bin/doom install

2.1. Using Nix unstable OOTB

If you want to use nix unstable out of of the box then you can use the following script

bash
#
RELEASE="nix-2.5pre20211019_4a2b7cc"
URL="https://github.com/numtide/nix-unstable-installer/releases/download/$RELEASE/install"

# install using workaround for darwin systems
if [[ $(uname -s) = "Darwin" ]]; then
    FLAG="--darwin-use-unencrypted-nix-store-volume"
fi

[[ ! -z "$1" ]] && URL="$1"

if command -v nix > /dev/null; then
    echo "nix is already installed on this system."
else
    bash <(curl -L $URL) --daemon $FLAG
fi

2.2. Additional Configuration

2.2.1. Emacs

If you want to use Emacs-NG, use the following build options

bash
#
  git clone --depth 1 https://github.com/emacs-ng/emacs-ng.git
  cd emacs-ng
  ./autogen.sh
  ./configure CFLAGS="-Wl,-rpath,shared,--disable-new-dtags -g -O3 -mtune=native -march=native -fomit-frame-pointer" \
              --prefix=/usr/local/ \
              --with-json --with-modules --with-compress-install \
              --with-threads --with-included-regex --with-zlib --with-libsystemd \
              --with-rsvg --with-native-compilation --with-webrender --without-javascript \
              --without-sound --without-imagemagick --without-makeinfo --without-gpm --without-dbus \
              --without-pop --without-toolkit-scroll-bars --without-mailutils --without-gsettings \
              --with-all
  make -j$(($(nproc) * 2)) NATIVE_FULL_AOT=1
  make install-strip

If you want to update the doom configuration, you can run

bash
#
doom upgrade

If you modify your shell configuration, please do run doom env to regenerate env vars

  1. Mu4e and Gmail

    Email will have a few issues, since its hardcoded to my account. Replace instances of my name and email in ~/.doom.d/config.org Indexed mail will go under ~/.mbsync/, you can either manually run mbsync or use emacs to update mail.

  2. Org Mode

    My org mode config includes two additional plugins, org-agenda and org-roam. Both these plugins need a set directory. All org files can go under the created ~/org dir. Roam files go under ~/org/roam

2.2.2. Fonts

SFMono must be installed seperately due to liscensing issues, all other fonts are managed via nix.

2.2.3. Neovim

Run :PackerSync to install packer and plugins. Run :checkhealth to check for possible issues. If you want to take advantage of the LSP and/or treesitter, you can install language servers and parsers using the following command: :LspInstall (language) :TSInstall (language) NOTE: If you want to use neorg’s treesitter parser on macOS, you need to link GCC to CC. Instructions here. I also recommend installing Neovide

3. Flakes

3.1. Why Flakes

Once upon a time, Nix pioneered reproducible builds: it tries hard to ensure that two builds of the same derivation graph produce an identical result. Unfortunately, the evaluation of Nix files into such a derivation graph isn’t nearly as reproducible, despite the language being nominally purely functional.

For example, Nix files can access arbitrary files (such as ~/.config/nixpkgs/config.nix), environment variables, Git repositories, files in the Nix search path ($NIX_PATH), command-line arguments (--arg) and the system type (builtins.currentSystem). In other words, evaluation isn’t as hermetic as it could be. In practice, ensuring reproducible evaluation of things like NixOS system configurations requires special care.

Furthermore, there is no standard way to compose Nix-based projects. It’s rare that everything you need is in Nixpkgs; consider for instance projects that use Nix as a build tool, or NixOS system configurations. Typical ways to compose Nix files are to rely on the Nix search path (e.g. import <nixpkgs>) or to use fetchGit or fetchTarball. The former has poor reproducibility, while the latter provides a bad user experience because of the need to manually update Git hashes to update dependencies.

There is also no easy way to deliver Nix-based projects to users. Nix has a “channel” mechanism (essentially a tarball containing Nix files), but it’s not easy to create channels and they are not composable. Finally, Nix-based projects lack a standardized structure. There are some conventions (e.g. shell.nix or release.nix) but they don’t cover many common use cases; for instance, there is no way to discover the NixOS modules provided by a repository.

Flakes are a solution to these problems. A flake is simply a source tree (such as a Git repository) containing a file named flake.nix that provides a standardized interface to Nix artifacts such as packages or NixOS modules. Flakes can have dependencies on other flakes, with a “lock file” pinning those dependencies to exact revisions to ensure reproducible evaluation.

When you clone this flake and install it, your system should theoretically be the exactly the same as mine, down to the commit of nixpkgs. There are also other benefits, such as that nix evaluations are cached.

3.2. Notes on using the flake

When you install this config, there are 3 useful commands you need to know

  • Updating the flake. This will update the flake.lock lockfile to the latest commit of nixpkgs, emacs-overlay, etc
bash
#
nix flake update
  • Building and Installing the flake. This will first build and download everything you need, then rebuild your machine, so it “installs”
bash
#
nix build ~/nix-darwin-dotfiles\#darwinConfigurations.shaunsingh-laptop.system --extra-experimental-features nix-command --extra-experimental-features flakes
    ./result/sw/bin/darwin-rebuild switch --flake .#shaunsingh-laptop
  • Testing the flake. If you have any errors when you play around with this config, then this will let you know what went wrong.
bash
#
nix flake check

The flake.nix below does the following:

  1. Add a binary cache for nix-community overlays
  2. Add inputs (nixpkgs-master, nix-darwin, home-manager, and spacebar)
  3. Add overlays to get the latest versions of neovim (nightly) and emacs (emacs29)
  4. Create a nix-darwin configuration for my hostname
  5. Source the mac, home, and pam modules
  6. Configure home-manager and the nix-daemon
  7. Enable the use of touch-id for sudo authentication
  8. Configure nixpkgs to use the overlays above, and allow unfree packages
  9. Configure nix to enable flakes and nix-command by default, and add x86-64-darwin as a platform (to install packages through rosetta)
  10. Install my packages and config dependencies
  11. Install the required fonts
nix
#
{
  description = "Shaurya's Nix Environment";

  nixConfig = {
    # Add binary cache for neovim-nightly/emacsGcc
    extra-substituters =
      [ "https://cachix.cachix.org" "https://nix-community.cachix.org" ];
    extra-trusted-public-keys = [
      "cachix.cachix.org-1:eWNHQldwUO7G2VkjpnjDbWwy4KQ/HNxht7H4SSoMckM="
      "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
    ];
  };

  inputs = {
    # All packages should follow latest nixpkgs
    unstable.url = "github:nixos/nixpkgs/master";
    nur.url = "github:nix-community/NUR";
    darwin = {
      url = "github:LnL7/nix-darwin/master";
      inputs.nixpkgs.follows = "unstable";
    };
    home-manager = {
      url = "github:nix-community/home-manager/master";
      inputs.nixpkgs.follows = "unstable";
    };
    # Bar
    spacebar = {
      url = "github:shaunsingh/spacebar/master";
      inputs.nixpkgs.follows = "unstable";
    };
    # Editors
    neovim = {
      url = "github:nix-community/neovim-nightly-overlay";
      inputs.nixpkgs.follows = "unstable";
    };
    emacs = {
      url = "github:shaunsingh/emacs";
      inputs.nixpkgs.follows = "unstable";
    };
    # overlays
    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs.nixpkgs.follows = "unstable";
    };

    nixpkgs-s2k = {
      url = "github:shaunsingh/nixpkgs-s2k";
      inputs.nixpkgs.follows = "unstable";
    };
  };

  outputs = { self, nixpkgs, nixpkgs-s2k, darwin, home-manager, ...
    }@inputs: {
      darwinConfigurations."shaunsingh-laptop" = darwin.lib.darwinSystem {
        system = "aarch64-darwin";
        modules = [
          { nixpkgs.overlays = [ nixpkgs-s2k.overlay ]; }
          ./modules/mac.nix
          ./modules/home.nix
          ./modules/pam.nix
          home-manager.darwinModule
          {
            home-manager = {
              useGlobalPkgs = true;
              useUserPackages = true;
            };
          }
          ({ pkgs, lib, ... }: {
            services.nix-daemon.enable = true;
            security.pam.enableSudoTouchIdAuth = true;
            nixpkgs = {
              overlays = with inputs; [
                nur.overlay
                spacebar.overlay
                neovim.overlay
                emacs.overlay
                rust-overlay.overlay
              ];
              config.allowUnfree = true;
            };
            nix = {
              package = pkgs.nix;
              extraOptions = ''
                system = aarch64-darwin
                extra-platforms = aarch64-darwin x86_64-darwin
                experimental-features = nix-command flakes
                build-users-group = nixbld
              '';
            };
            environment.systemPackages = with pkgs; [
              # Emacs deps
              ((emacsPackagesNgGen emacs).emacsWithPackages
                (epkgs: [ epkgs.vterm ]))
              ## make sure ripgrep supports pcre2 (for vertico)
              (ripgrep.override { withPCRE2 = true; })
              sqlite
              ## Required for plots but not installed by default
              gnuplot
              # pandoc
              ## Required for dictionaries but not installed by default
              # sdcv
              (aspellWithDicts (ds: with ds; [ en en-computers en-science ]))
              (texlive.combine {
                inherit (texlive)
                  scheme-small dvipng dvisvgm l3packages xcolor soul adjustbox
                  collectbox amsmath siunitx cancel mathalpha capt-of chemfig
                  wrapfig mhchem fvextra cleveref latexmk tcolorbox environ arev
                  amsfonts simplekv alegreya sourcecodepro newpx svg catchfile
                  transparent hanging biblatex biblatex-mla;
              })

              # Language Deps
              ## Build Tools
              jdk
              luajit
              rust-bin.nightly.latest.default

              ## Language Servers
              nodePackages.pyright
              rust-analyzer
              languagetool

              ## Formatting
              nixfmt
              black
              shellcheck

              # Terminal utils and rust alternatives :tm:
              uutils-coreutils
              xcp
              lsd
              procs
              tree
              fd
              zoxide
              bottom
              discocss
              # eww

            ];
            fonts = {
              enableFontDir = true;
              fonts = with pkgs; [
                overpass
                alegreya
                alegreya-sans
                emacs-all-the-icons-fonts
                sf-mono-liga-bin
              ];
            };
          })
        ];
      };
    };
}

4. Modules

4.1. Home.nix

Home Manager allows you to use Nix’s declarative approach to manage your user-level configuration and packages. It works on any *nix system supported by Nix, including MacOS.

nix
#
{ pkgs, lib, config, home-manager, nix-darwin, inputs, ... }: {

4.1.1. Doom-emacs

Nix via doom-emacs is very, very annoying. Initially I was using Nix-doom-emacs. However, this has a few drawbacks

  1. It doesn’t support straight :recipe, so all packages must be from melpa or elpa
  2. It pins the version of doom, so you need to update doom and its dependencies painstakingly manually
  3. It just ends up breaking anyways.

A simpler solution is just to have nix clone doom-emacs to ~/.config/emacs, and the user can handle doom manually

nix
#
#   home-manager.users.shauryasingh.home.file = {
#     "~/.config/doom" = {
#       recursive = true;
#       source = ../configs/doom;
#     };
#   };

4.1.2. Git

As opposed to what the xcode CLT provides, I want lfs enabled with git, and use delta instead of the default diff tool (rust alternatives go brr). MacOS is also quite annoying with its .DS_Store’s everywhere, so lets ignore that

nix
#
  home-manager.users.shauryasingh.programs.git = {
    enable = true;
    userName = "shaunsingh";
    userEmail = "shaunsingh0207@gmail.com";
    lfs.enable = true;
    delta = {
      enable = true;
      options = {
        syntax-theme = "Nord";
        line-numbers = true;

        width = 1;
        navigate = false;

        hunk-header-style = "file line-number syntax";
        hunk-header-decoration-style = "bold black";

        file-modified-label = "modified:";

        zero-style = "dim";

        minus-style = "bold red";
        minus-non-emph-style = "dim red";
        minus-emph-style = "bold red";
        minus-empty-line-marker-style = "normal normal";

        plus-style = "green normal bold";
        plus-non-emph-style = "dim green";
        plus-emph-style = "bold green";
        plus-empty-line-marker-style = "normal normal";

        whitespace-error-style = "reverse red";
      };
    };
    ignores = [ ".dir-locals.el" ".envrc" ".DS_Store" ];
  };

4.1.3. IdeaVim

Intellij Idea ships with a very nice Vim emulation plugin. This is configured via a vimrc-like file (~/.ideavimrc). Since it doesn’t have proper support in home-manger, we can just generate a file and symlink it into place

nix
#
  home-manager.users.shauryasingh.home.file = {
    ".ideavimrc".text = ''
      " settings
      set ignorecase
      set smartcase
      set scrolloff=3 " 3 lines above/below cursor when scrolling
      set nonumber
      set clipboard+=unnamed
      set multiple-cursors
      set numberwidth=2
      set expandtab=true
      set shiftwidth=4

      " plugins
      set easymotion
      set NERDTree
      set surround
      set highlightedyank


      " bindings
      let mapleader = " "
      nmap <leader>. :action GotoFile<cr>
      nmap <leader>fr :action RecentFiles<cr>
      nmap <leader>ww <Plug>(easymotion-w)
      nmap <leader>tz :action Enter Zen Mode<cr>
      nmap <leader>op :NERDTreeToggle<cr>
      nmap <leader>ot :Terminal<cr>
      nmap <leader>: :action SearchEverywhere<cr>
      nmap <leader>/ :action Find<cr>

      " use ; to enter command
      nmap ; :

      " use jk for escaping
      inoremap jk <Esc>
      cnoremap jk <Esc>

      " move by visual lines"
      nmap j gj
      nmap k gk

      " use C-hjkl to navigate splits
      nmap <C-h> <c-w>h
      nmap <C-l> <c-w>l
      nmap <C-k> <c-w>k
      nmap <C-j> <c-w>j

      nmap <leader>E :action Tool_External Tools_emacsclient<cr>
    '';

4.1.4. Discocss

Discocss is a way to inject custom CSS into discord. Similar to ideavim, it doesn’t have proper support but we can generate a file for ~/.config/discocss/custom.css

nix
#
    ".config/discocss/custom.css".text = ''
      /*
          Discord Nord
          https://github.com/joinemm/discord-css
      */

      /* define colors */
      :root {
          --background-primary: #242730;
          --background-secondary: #2a2e38;
          --background-secondary-alt: #2a2e38;
          --background-tertiary: #242730;
          --background-accent: #242730;
          --channeltextarea-background: #242730;
          --text-normal: #6c605a;
          --text-muted: #ce9c85;
          --channels-default: #a09c80;
          --interactive-normal: #242730;
          --interactive-hover: #a09c80;
          --interactive-active: #a09c80;
          --interactive-muted: #665c54;
          --header-primary: #6c605a;
          --header-secondary: #5c605a;
          --background-floating: #242730;
          --scrollbar-auto-thumb: #1d2021;
          --scrollbar-auto-track: #3c3836;
          --text-link: #8f8678;
          --selection: #92837480;
      }

      * {
          font-family: Liga SFMono Nerd Font;
          font-size: 11px;
      }

      /* main backgrounds */
      .scrollerThemed-2oenus.themeDark-2cjlUp.scrollerTrack-1ZIpsv>.scroller-2FKFPG::-webkit-scrollbar-track,
      .scrollerThemed-2oenus.themeDark-2cjlUp.scrollerTrack-1ZIpsv>.scrollerStore-390omS::-webkit-scrollbar-track,
      .theme-dark .scrollerWrap-2lJEkd.scrollerTrack-1ZIpsv>.scroller-2FKFPG::-webkit-scrollbar-track,
      .theme-dark .scrollerWrap-2lJEkd.scrollerTrack-1ZIpsv>.scrollerStore-390omS::-webkit-scrollbar-track,
      .theme-dark .da-messageGroupWrapper,
      .theme-dark .bodyInner-245q0L,
      .theme-dark .bottomDivider-1K9Gao,
      .theme-dark .headerNormal-T_seeN,
      .theme-dark .root-1gCeng,
      .tabBody-3YRQ8W,
      .theme-dark .container-1D34oG
      .theme-dark .uploadModal-2ifh8j,
      .theme-dark .modal-yWgWj-,
      .uploadModal-2ifh8j,
      .theme-dark .emojiAliasInput-1y-NBz .emojiInput-1aLNse,
      .theme-dark .selected-1Tbx07,
      .theme-dark .option-96V44q.selected-rZcOL- {
          background-color: var(--background-primary) !important;
      }

      .da-popouts .da-container,
      .da-friendsTableHeader,
      .da-friendsTable,
      .da-autocomplete,
      .da-themedPopout,
      .da-footer,
      .da-userPopout>*,
      .da-autocompleteHeaderBackground,
      .theme-dark .bar-2Qqk5Z,
      .theme-dark .payment-xT17Mq,
      .theme-dark .paymentPane-3bwJ6A,
      .theme-dark .paginator-166-09,
      .theme-dark .codeRedemptionRedirect-1wVR4b,
      .theme-dark .scrollerThemed-2oenus.themedWithTrack-q8E3vB .scroller-2FKFPG::-webkit-scrollbar-thumb,
      .theme-dark .footer-3mqk7D,
      .theme-dark .footer-2gL1pp,
      .scrollerThemed-2oenus.themeDark-2cjlUp .scroller-2FKFPG::-webkit-scrollbar-thumb,
      .theme-dark .scrollerWrap-2lJEkd .scroller-2FKFPG::-webkit-scrollbar-thumb,
      .theme-dark .inset-3sAvek,
      .theme-dark .quickMessage-1yeL4E,
      .wrapper-2aW0bm,
      .theme-dark .autocomplete-1vrmpx,
      .searchBar-3dMhjb,
      .theme-dark .body-3iLsc4,
      .theme-dark .footer-2gL1pp,
      .theme-dark .footer-1fjuF6,
      .cardPrimary-1Hv-to,
      .theme-dark .card-FDVird:before,
      .theme-dark .colorPickerCustom-2CWBn2 {
          background-color: var(--background-secondary);
      }

      /* scrollbars */
      .da-messagesWrapper .da-scroller::-webkit-scrollbar,
      .da-messagesWrapper .da-scroller::-webkit-scrollbar-track-piece {
          background-color: var(--background-tertiary) !important;
          border-color: rgba(0, 0, 0, 0) !important;
      }

      .da-scrollerThemed .da-scroller::-webkit-scrollbar-thumb,
      .da-scrollerWrap .da-scroller::-webkit-scrollbar-thumb {
          background-color: var(--background-secondary) !important;
          border-color: var(--background-tertiary) !important;
      }

      .theme-dark .scrollerThemed-2oenus.themedWithTrack-q8E3vB .scroller-2FKFPG::-webkit-scrollbar-track-piece {
          background-color: var(--background-primary) !important;
          border-color: rgba(0, 0, 0, 0) !important;
      }

      .theme-dark .selectorSelected-1_M1WV,
      .newMessagesBar-mujexs,
      .theme-dark .searchAnswer-3Dz2-q,
      .theme-dark .searchFilter-2ESiM3,
      .theme-dark .progress-1IcQ3A,
      .themeDefault-24hCdX,
      .theme-dark .lookFilled-1Gx00P.colorPrimary-3b3xI6 {
          background-color: var(--background-accent);
      }

      .theme-dark .container-3ayLPN {
          background-color: var(--background-tertiary);
      }

      .theme-dark .option-96V44q.selected-rZcOL- {
          background-color: var(--background-floating);
      }

      .theme-dark .pageIndicator-1gAbyA,
      .theme-dark .pageButtonNext-V2kUq0,
      .theme-dark .pageButtonPrev-1Y-47D {
          border-color: var(--background-accent);
      }

      .scroller-2FKFPG::-webkit-scrollbar,
      .barButtonBase-3UKlW2,
      .theme-dark .scrollerThemed-2oenus.themeGhostHairline-DBD-2d .scroller-2FKFPG::-webkit-scrollbar-thumb {
          background: transparent;
      }

      /* remove gradients */
      .theme-dark .da-option:after, .theme-dark .option-96V44q:after {
          background-image: none !important;
      }

      /* search text */
      .theme-dark .searchOption-zQ-1l6 .answer-1n6g43,
      .theme-dark .option-96V44q .filter-3Y_im- {
          color: var(--text-muted)
      }

      .theme-dark .searchOption-zQ-1l6 .filter-3Y_im- {
          color: var(--text-normal)
      }

      /* side panel bottom section border */
      .panels-j1Uci_ {
          border-top: 2px solid var(--background-modifier-accent);
      }

      .container-1giJp5 {
          border-width: 2px;
      }

      /* selected text */
      ::selection {
          background: var(--selection);
      }

      /* hide that stupid nitro gift button */
      .button-38aScr[aria-label="Send a gift"] {
          display: none;
      }

      /* hide blocked messages */
      [class="groupStart-23k01U"] {
        display: none;
      }

      /* enhanceddiscord server count text */
      .theme-dark .keybind-KpFkfr {
          color: var(--channels-default)
      }

      /* unloaded emojis */
      .theme-dark .imageLoading-bpSr0M {
          background-image: none;
          background: var(--background-primary);
          border-radius: 50%;
      }

      .sprite-2iCowe {
        filter: none !important;
      }

      /* Doom style (dark) (c) Pavel Pertsev (original style at https://github.com/morhetz/gruvbox) */

      .hljs {
        display: block;
        overflow-x: auto;
        padding: 0.5em;
        background: #242730;
      }

      .hljs,
      .hljs-subst {
        color: #ebdbb2;
      }

      /* Doom Red */
      .hljs-deletion,
      .hljs-formula,
      .hljs-keyword,
      .hljs-link,
      .hljs-selector-tag {
        color: #fb4934;
      }

      /* Doom Blue */
      .hljs-built_in,
      .hljs-emphasis,
      .hljs-name,
      .hljs-quote,
      .hljs-strong,
      .hljs-title,
      .hljs-variable {
        color: #83a598;
      }

      /* Doom Yellow */
      .hljs-attr,
      .hljs-params,
      .hljs-template-tag,
      .hljs-type {
        color: #fabd2f;
      }

      /* Doom Purple */
      .hljs-builtin-name,
      .hljs-doctag,
      .hljs-literal,
      .hljs-number {
        color: #8f3f71;
      }

      /* Doom Orange */
      .hljs-code,
      .hljs-meta,
      .hljs-regexp,
      .hljs-selector-id,
      .hljs-template-variable {
        color: #fe8019;
      }

      /* Doom Green */
      .hljs-addition,
      .hljs-meta-string,
      .hljs-section,
      .hljs-selector-attr,
      .hljs-selector-class,
      .hljs-string,
      .hljs-symbol {
        color: #b8bb26;
      }

      /* Doom Aqua */
      .hljs-attribute,
      .hljs-bullet,
      .hljs-class,
      .hljs-function,
      .hljs-function .hljs-keyword,
      .hljs-meta-keyword,
      .hljs-selector-pseudo,
      .hljs-tag {
        color: #8ec07c;
      }

      /* Doom Gray */
      .hljs-comment {
        color: #928374;
      }

      /* Doom Purple */
      .hljs-link_label,
      .hljs-literal,
      .hljs-number {
        color: #d3869b;
      }
      .hljs-comment,
      .hljs-emphasis {
        font-style: italic;
      }

      .hljs-section,
      .hljs-strong,
      .hljs-tag {
        font-weight: bold;
      }

      /* Auto Hide by \xynstr#0300 */
          /* Transition */
          .sidebar-2K8pFh:hover {
              width: 240px !important;
              transition: 0.1s ease !important;
              transition-delay: 0.3s !important;
          }
          /* Detects screen size to show channels if screen is big enough */
          @media screen and (max-width: 1100px) {
              .sidebar-2K8pFh {
                  width: 0px !important;
                  transition: 0.3s ease !important;
                  position: fixed !important;
                  height: calc(100% - 47.988px) !important;
                  z-index: 1 !important;
                  bottom: 0px !important;
              }
              .wrapper-3NnKdC:hover + .base-3dtUhz > .content-98HsJk > .sidebar-2K8pFh {
                  width: 240px !important;
              }
              .sidebar-2K8pFh:hover {
                  width: 240px !important;
    '';
  };

4.1.5. Firefox

Although safari is my main browser, firefox looks very appealing with its excellent privacy and speed

nix
#
  home-manager.users.shauryasingh.programs.firefox.enable = true;

GUI apps are very finicky with nix, and so I create a fake package so that we can still use the configuration from home-manager without having to install it via nix. The user can then install firefox manually to ~/Applications

nix
#
  home-manager.users.shauryasingh.programs.firefox.package =
    pkgs.runCommand "firefox-0.0.0" { } "mkdir $out";
  home-manager.users.shauryasingh.programs.firefox.extensions =
      with pkgs.nur.repos.rycee.firefox-addons; [
        ublock-origin
        tridactyl
        duckduckgo-privacy-essentials
        reddit-enhancement-suite
        betterttv
      ];

Now for the configuration. We want firefox to use the css at ./configs/userChrome.css, and we want to configure the UI. Lets also enable the (rust powered ftw) webrender/servo renderer.

nix
#
  home-manager.users.shauryasingh.programs.firefox.profiles = let
    userChrome = builtins.readFile ../configs/userChrome.css;
    settings = {
      "app.update.auto" = true;
      "browser.startup.homepage" = "https://tilde.cade.me";
      "browser.search.region" = "US";
      "browser.search.countryCode" = "US";
      "browser.ctrlTab.recentlyUsedOrder" = false;
      "browser.newtabpage.enabled" = false;
      "browser.bookmarks.showMobileBookmarks" = true;
      "browser.uidensity" = 1;
      "browser.urlbar.placeholderName" = "SearX";
      "browser.urlbar.update1" = true;
      "identity.fxaccounts.account.device.name" = config.networking.hostName;
      "privacy.trackingprotection.enabled" = true;
      "privacy.trackingprotection.socialtracking.enabled" = true;
      "privacy.trackingprotection.socialtracking.annotate.enabled" = true;
      "reader.color_scheme" = "sepia";
      "services.sync.declinedEngines" = "addons,passwords,prefs";
      "services.sync.engine.addons" = false;
      "services.sync.engineStatusChanged.addons" = true;
      "services.sync.engine.passwords" = false;
      "services.sync.engine.prefs" = false;
      "services.sync.engineStatusChanged.prefs" = true;
      "signon.rememberSignons" = false;
      "gfx.webrender.all" = true;
      "toolkit.legacyUserProfileCustomizations.stylesheets" = true;
    };
  in {
    home = {
      inherit settings;
      inherit userChrome;
      id = 0;
    };
  };

And of course for the css itself

CSS
#
/*
 *  Hide window controls
 */
.titlebar-buttonbox-container{
    display: none !important;
}

.titlebar-placeholder,
#TabsToolbar .titlebar-spacer{ display: none; }
#navigator-toolbox::after{ display: none !important; }


/*
 *  Hide all the clutter in the navbar
 */
#main-window :-moz-any(#back-button,
      #forward-button,
      #stop-reload-button,
      #home-button,
      #library-button,
      #sidebar-button,
      #star-button,
      #pocket-button,
      #permissions-granted-icon,
      #fxa-toolbar-menu-button,
      #_d7742d87-e61d-4b78-b8a1-b469842139fa_-browser-action, /* Vimium */
      #ublock0_raymondhill_net-browser-action) { display: none !important; }

/*
 *  Hide tabs if only one tab
 */
#titlebar .tabbrowser-tab[first-visible-tab="true"][last-visible-tab="true"]{
    display: none !important;
}

/*
 *  Minimal theme
 */
#navigator-toolbox toolbarspring {
    display: none;
}

/* Hide filler */
#customizableui-special-spring2{
    display:none;
}

.tab-background{
    padding-bottom: 0 !important;
}

#navigator-toolbox #urlbar-container {
    padding: 0 !important;
    margin: 0 !important;
}

#navigator-toolbox #urlbar {
    border: none !important;
    border-radius: 0 !important;
    box-shadow: none !important;
}

#navigator-toolbox #PanelUI-button {
    padding: 0 !important;
    margin: 0 !important;
    border: none !important;
}

#navigator-toolbox #nav-bar {
    box-shadow: none !important;
}

#navigator-toolbox #pageActionButton {
    display: none;
}

#navigator-toolbox #pageActionSeparator {
    display: none;
}

#fullscr-toggler {
    height: 0 !important;
}

#navigator-toolbox .urlbar-history-dropmarker {
    display: none;
}

#navigator-toolbox #tracking-protection-icon-container {
    padding-right: 0 !important;
    border: none !important;
    display: none !important;
}

#navigator-toolbox .tab-close-button, #navigator-toolbox #tabs-newtab-button {
    display: none;
}

#navigator-toolbox #urlbar {
    padding: 0 !important;
    padding-left: 1ch !important;
    font-size: 13px;
}

#navigator-toolbox #urlbar-background {
    border: none !important;
    margin: 0 !important;
}

#navigator-toolbox .toolbarbutton-1 {
    width: 22px;
}

#navigator-toolbox .tabbrowser-tab {
    font-size: 14px
}

#navigator-toolbox .tab-background {
    box-shadow: none!important;
    border: none !important;
}

#navigator-toolbox .tabbrowser-tab::after {
    display: none !important;
}

#navigator-toolbox #urlbar-zoom-button {
    border: none !important;
}

#appMenu-fxa-container, #appMenu-fxa-container + toolbarseparator {
    display: none !important;
}

#sync-setup {
    display: none !important;
}

/*
 *  Hamburger menu to the left
 */

#PanelUI-button {
    -moz-box-ordinal-group: 0;
    border-left: none !important;
    border-right: none !important;
    position: absolute;
}

#toolbar-context-menu .viewCustomizeToolbar {
    display: none !important;
}

:root[uidensity=compact] #PanelUI-button {
    margin-top: -30px;
}

#PanelUI-button {
    margin-top: -30px;
}

:root[uidensity=touch] #PanelUI-button {
    margin-top: -36px;
}

/*
 *  Tabs to the right of the urlbar
 */

/* Modify these to change relative widths or default height */
#navigator-toolbox{
    --uc-navigationbar-width: 40vw;
    --uc-toolbar-height: 40px;
}
/* Override for other densities */
:root[uidensity="compact"] #navigator-toolbox{ --uc-toolbar-height: 30px; }
:root[uidensity="touch"] #navigator-toolbox{ --uc-toolbar-height: 40px; }

:root[uidensity=compact] #urlbar-container.megabar{
    --urlbar-container-height: var(--uc-toolbar-height) !important;
    padding-block: 0 !important;
}
:root[uidensity=compact] #urlbar.megabar{
    --urlbar-toolbar-height: var(--uc-toolbar-height) !important;
}

/* prevent urlbar overflow on narrow windows */
/* Dependent on how many items are in navigation toolbar ADJUST AS NEEDED */
@media screen and (max-width: 1300px){
    #urlbar-container{ min-width:unset !important }
}

#TabsToolbar{ margin-left: var(--uc-navigationbar-width); }
#tabbrowser-tabs{ --tab-min-height: var(--uc-toolbar-height) !important; }

/* This isn't useful when tabs start in the middle of the window */
.titlebar-placeholder[type="pre-tabs"],
.titlebar-spacer[type="pre-tabs"]{ display: none }

#navigator-toolbox > #nav-bar{
    margin-right:calc(100vw - var(--uc-navigationbar-width));
    margin-top: calc(0px - var(--uc-toolbar-height));
}

/* Zero window drag space  */
:root[tabsintitlebar="true"] #nav-bar{ padding-left: 0px !important; padding-right: 0px !important; }

/* 1px margin on touch density causes tabs to be too high */
.tab-close-button{ margin-top: 0 !important }

/* Hide dropdown placeholder */
#urlbar-container:not(:hover) .urlbar-history-dropmarker{ margin-inline-start: -30px; }

/* Fix customization view */
#customization-panelWrapper > .panel-arrowbox > .panel-arrow{ margin-inline-end: initial !important; }

4.1.6. Alacritty

Alacritty is my terminal emulator of choice. Similar to firefox, we want to create a fake package, and then configure it as normal

nix
#
  home-manager.users.shauryasingh.programs.alacritty = {
    enable = true;
    # We need to give it a dummy package
    package = pkgs.runCommand "alacritty-0.0.0" { } "mkdir $out";
    settings = {
      window.padding.x = 45;
      window.padding.y = 45;
      window.decorations = "buttonless";
      window.dynamic_title = true;
      live_config_reload = true;
      mouse.hide_when_typing = true;
      use_thin_strokes = true;
      cursor.style = "Beam";

      font = {
        size = 14;
        normal.family = "Liga SFMono Nerd Font";
        normal.style = "Light";
        bold.family = "Liga SFMono Nerd Font";
        bold.style = "Bold";
        italic.family = "Liga SFMono Nerd Font";
        italic.style = "Italic";
      };

      colors = {
        cursor.cursor = "#bbc2cf";
        primary.background = "#242730";
        primary.foreground = "#bbc2cf";
        normal = {
          black =   "#2a2e38";
          red =     "#ff665c";
          green =   "#7bc275";
          yellow =  "#FCCE7B";
          blue =    "#5cEfFF";
          magenta = "#C57BDB";
          cyan =    "#51afef";
          white =   "#bbc2cf";
        };
        bright = {
          black =    "#484854";
          red =      "#ff665c";
          green =    "#7bc275";
          yellow =   "#fcce7b";
          blue =     "#5cefff";
          magenta =  "#c57bdb";
          cyan =     "#51afef";
          white =    "#bbc2cf";
        };
      };
    };
  };

4.1.7. Kitty

I no longer use kitty (its quite slow to start and has too many features I don’t need), but I keep the config around just in case

nix
#
  # home-manager.users.shauryasingh.programs.kitty = {
  #   enable = true;
  #   package = builtins.path {
  #     path = /Applications/kitty.app/Contents/MacOS;
  #     filter = (path: type: type == "directory" || builtins.baseNameOf path == "kitty");
  #   };
  #   # enable = true;
  #   settings = {
  #     font_family = "Liga SFMono Nerd Font";
  #     font_size = "14.0";
  #     adjust_line_height = "120%";
  #     disable_ligatures = "cursor";
  #     hide_window_decorations = "yes";
  #     scrollback_lines = "50000";
  #     cursor_blink_interval = "0.5";
  #     cursor_stop_blinking_after = "10.0";
  #     window_border_width = "0.7pt";
  #     draw_minimal_borders = "yes";
  #     macos_option_as_alt = "no";
  #     cursor_shape = "beam";

  #     foreground           =   "#D8DEE9";
  #     background           =   "#2E3440";
  #     selection_foreground =   "#000000";
  #     selection_background =   "#FFFACD";
  #     url_color            =   "#0087BD";
  #     cursor               =   "#81A1C1";
  #     color0               =   "#3B4252";
  #     color8               =   "#4C566A";
  #     color1               =   "#BF616A";
  #     color9               =   "#BF616A";
  #     color2               =   "#A3BE8C";
  #     color10              =   "#A3BE8C";
  #     color3               =   "#EBCB8B";
  #     color11              =   "#EBCB8B";
  #     color4               =   "#81A1C1";
  #     color12              =   "#81A1C1";
  #     color5               =   "#B48EAD";
  #     color13              =   "#B48EAD";
  #     color6               =   "#88C0D0";
  #     color14              =   "#8FBCBB";
  #     color7               =   "#E5E9F0";
  #     color15              =   "#B48EAD";
  #   };
  # };

4.1.8. Fish

I like to use the fish shell. Although it isn’t POSIX, it has the best autocompletion and highlighting I’ve seen.

nix
#
  programs.fish.enable = true;
  environment.shells = with pkgs; [ fish ];
  users.users.shauryasingh = {
    home = "/Users/shauryasingh";
    shell = pkgs.fish;
  };
  1. Settings fish as default

    On macOS nix doesn’t set the fish shell to the main shell by default (like it does on NixOS), so lets do that manually

    nix
    #
      system.activationScripts.postActivation.text = ''
        # Set the default shell as fish for the user
        sudo chsh -s ${lib.getBin pkgs.fish}/bin/fish shauryasingh
      '';
    
  2. Aliases

    I also like to alias common commands with other, better rust alternatives :tm:

    nix
    #
      programs.fish.shellAliases = with pkgs; {
        ":q" = "exit";
        vi = "emacsclient -c";
        git-rebsae = "git rebase -i HEAD~2";
        ll =
          "exa -lF --color-scale --no-user --no-time --no-permissions --group-directories-first --icons -a";
        ls = "exa -lF --group-directories-first --icons -a";
        ps = "ps";
        tree = "tree -a -C";
        cat = "bat";
        top = "btm";
        cp = "xcp";
        find = "fd";
        calc = "emacs -f full-calc";
        neovide =
          "/Applications/Neovide.app/Contents/MacOS/neovide --frameless --multigrid";
        nix-fish = "nix-shell --command fish";
      };
    
  3. Prompt

    I like to make my prompt look pretty (along with some nix-shell and git integration)

    nix
    #
      programs.fish.promptInit = ''
    
        set -g fish_greeting ""
    
        set -U fish_color_autosuggestion      brblack
        set -U fish_color_cancel              -r
        set -U fish_color_command             green
        set -U fish_color_comment             magenta
        set -U fish_color_cwd                 green
        set -U fish_color_cwd_root            red
        set -U fish_color_end                 magenta
        set -U fish_color_error               red
        set -U fish_color_escape              cyan
        set -U fish_color_history_current     --bold
        set -U fish_color_host                normal
        set -U fish_color_normal              normal
        set -U fish_color_operator            cyan
        set -U fish_color_param               blue
        set -U fish_color_quote               yellow
        set -U fish_color_redirection         yellow
        set -U fish_color_search_match        'yellow' '--background=brightblack'
        set -U fish_color_selection           'white' '--bold' '--background=brightblack'
        set -U fish_color_status              red
        set -U fish_color_user                green
        set -U fish_color_valid_path          --underline
        set -U fish_pager_color_completion    normal
        set -U fish_pager_color_description   yellow
        set -U fish_pager_color_prefix        'white' '--bold' '--underline'
        set -U fish_pager_color_progress      'white' '--background=cyan'
    
        # prompt
        set fish_prompt_pwd_dir_length 1
        set __fish_git_prompt_show_informative_status 1
    
        set fish_color_command green
        set fish_color_param $fish_color_normal
    
        set __fish_git_prompt_showdirtystate 'yes'
        set __fish_git_prompt_showupstream 'yes'
    
        set __fish_git_prompt_color_branch brown
        set __fish_git_prompt_color_dirtystate FCBC47
        set __fish_git_prompt_color_stagedstate yellow
        set __fish_git_prompt_color_upstream cyan
        set __fish_git_prompt_color_cleanstate green
        set __fish_git_prompt_color_invalidstate red
    
        set __fish_git_prompt_char_dirtystate '~~'
        set __fish_git_prompt_char_stateseparator ' '
        set __fish_git_prompt_char_untrackedfiles ' ...'
        set __fish_git_prompt_char_cleanstate '✓'
        set __fish_git_prompt_char_stagedstate '-> '
        set __fish_git_prompt_char_conflictedstate "✕"
    
        set __fish_git_prompt_char_upstream_prefix ""
        set __fish_git_prompt_char_upstream_equal ""
        set __fish_git_prompt_char_upstream_ahead '>>='
        set __fish_git_prompt_char_upstream_behind '=<<'
        set __fish_git_prompt_char_upstream_diverged '<=>'
    
        function _print_in_color
          set -l string $argv[1]
          set -l color  $argv[2]
    
          set_color $color
          printf $string
          set_color normal
        end
    
        function _prompt_color_for_status
          if test $argv[1] -eq 0
            echo magenta
          else
            echo red
          end
        end
    
        function fish_prompt
            set -l last_status $status
    
            set -l nix_shell_info (
              if test -n "$IN_NIX_SHELL"
                echo -n " [nix-shell]"
              end
            )
    
            if test $HOME != $PWD
                _print_in_color ""(prompt_pwd) blue
            end
            __fish_git_prompt " (%s)"
    
            _print_in_color "$nix_shell_info λ " (_prompt_color_for_status $last_status) ]
    
        end
      '';
    
  4. Init

    I also want to disable the default greeting, and use tmux with fish. Lets also set nvim as the default editor, and add emacs to my path

    nix
    #
      programs.fish.interactiveShellInit = ''
        set -g fish_greeting ""
        if not set -q TMUX
          tmux new-session -A -s main
        end
    
        zoxide init fish --cmd cd | source
    
        set -x EDITOR "nvim"
        set -x PATH ~/.config/emacs/bin $PATH
      '';
    

4.1.9. Neovim

Lastly, I didn’t feel like nix-ifying my neovim lua config. Lets cheat a bit and just symlink it instead

nix
#
  home-manager.users.shauryasingh.programs.neovim = {
    enable = true;
    package = pkgs.neovim-nightly;
    vimAlias = true;
    extraPackages = with pkgs; [
        tree-sitter
        # neovide-git
        nodejs
        tree-sitter
    ];
    extraConfig = builtins.concatStringsSep "\n" [
      ''
        lua << EOF
        ${lib.strings.fileContents ../configs/nyoom.nvim/nix.lua}
        EOF
      ''
    ];
  };

4.1.10. Bat

Bat is another rust alternative :tm: to cat, and provides syntax highlighting. Lets theme it to match nord

nix
#
  home-manager.users.shauryasingh.programs.bat = {
    enable = true;
    config = { theme = "Nord"; };
  };

4.1.11. Tmux

Lastly, lets make tmux look just as pretty as our prompt, and enable truecolor support.

nix
#
  programs.tmux.enable = true;
  programs.tmux.extraConfig = ''
    # make sure fish works in tmux
    set -g default-terminal "screen-256color"
    set -sa terminal-overrides ',xterm-256color:RGB'
    # so that escapes register immidiately in vim
    set -sg escape-time 1
    set -g focus-events on
    # mouse support
    set -g mouse on
    # change prefix to C-a
    set -g prefix C-a
    bind C-a send-prefix
    unbind C-b
    # extend scrollback
    set-option -g history-limit 5000
    # vim-like pane resizing
    bind -r C-k resize-pane -U
    bind -r C-j resize-pane -D
    bind -r C-h resize-pane -L
    bind -r C-l resize-pane -R
    # vim-like pane switching
    bind -r k select-pane -U
    bind -r j select-pane -D
    bind -r h select-pane -L
    bind -r l select-pane -R
    # styling
    set -g status-style fg=white,bg=default
    set -g status-left ""
    set -g status-right ""
    set -g status-justify centre
    set -g status-position bottom
    set -g pane-active-border-style bg=default,fg=default
    set -g pane-border-style fg=default
    set -g window-status-current-format "#[fg=cyan]#[fg=black]#[bg=cyan]#I #[bg=brightblack]#[fg=white] #W#[fg=brightblack]#[bg=default] #[bg=default] #[fg=magenta]#[fg=black]#[bg=magenta]λ #[fg=white]#[bg=brightblack] %a %d %b #[fg=magenta]%R#[fg=brightblack]#[bg=default]"
    set -g window-status-format "#[fg=magenta]#[fg=black]#[bg=magenta]#I #[bg=brightblack]#[fg=white] #W#[fg=brightblack]#[bg=default] "
  '';
}

4.2. Mac.nix

There are mac-specific tweaks I need to do. In the future if I switch to nixOS full-time, then I wuold likely need to remove the mac-specific packages. An easy way to do this is just keep them in a seperate file:

nix
#
{ pkgs, lib, spacebar, ... }: {

4.2.1. Yabai

Yabai is my tiling WM of choice. As this is an m1 (aarch64-darwin) laptop, I use the the-future branch, which enables the SA addon on m1 machines and monterey support

Now to configure the package via nix

nix
#
  services.yabai = {
    enable = false;
    enableScriptingAddition = false;
    package = pkgs.yabai-m1;
    config = {
      window_border = "off";
      # window_border_width = 5;
      # active_window_border_color = "0xff3B4252";
      # normal_window_border_color = "0xff2E3440";
      focus_follows_mouse = "autoraise";
      mouse_follows_focus = "off";
      mouse_drop_action = "stack";
      window_placement = "second_child";
      window_opacity = "off";
      window_topmost = "on";
      window_shadow = "on";
      active_window_opacity = "1.0";
      normal_window_opacity = "1.0";
      split_ratio = "0.50";
      auto_balance = "on";
      mouse_modifier = "fn";
      mouse_action1 = "move";
      mouse_action2 = "resize";
      layout = "bsp";
      top_padding = 18;
      bottom_padding = 46;
      left_padding = 18;
      right_padding = 18;
      window_gap = 18;
    };
  };

4.2.2. Spacebar

Spacebar is my bar of choice on macOS. Its lighter than any web-based ubersicht bar, and looks nice

nix
#
  services.spacebar = {
    enable = true;
    package = pkgs.spacebar;
    config = {
      position = "bottom";
      height = 28;
      title = "on";
      spaces = "on";
      power = "on";
      clock = "off";
      right_shell = "off";
      padding_left = 20;
      padding_right = 20;
      spacing_left = 25;
      spacing_right = 25;
      text_font = ''"Menlo:16.0"'';
      icon_font = ''"Menlo:16.0"'';
      background_color = "0xff242730";
      foreground_color = "0xffbbc2cf";
      space_icon_color = "0xff51afef";
      power_icon_strip = " ";
      space_icon_strip = "I II III IV V VI VII VIII IX X";
      spaces_for_all_displays = "on";
      display_separator = "on";
      display_separator_icon = "|";
      clock_format = ''"%d/%m/%y %R"'';
      right_shell_icon = " ";
      right_shell_command = "whoami";
    };
  };

4.2.3. SKHD

Skhd is the hotkey daemon for yabai. As yabai is disabled, it makes sense to disable skhd too for the time being

nix
#
  services.skhd = {
    enable = false;
    package = pkgs.skhd;
    skhdConfig = ''
      ctrl + alt - h : yabai -m window --focus west
      ctrl + alt - j : yabai -m window --focus south
      ctrl + alt - k : yabai -m window --focus north
      ctrl + alt - l : yabai -m window --focus east

      # Fill space with window
      ctrl + alt - 0 : yabai -m window --grid 1:1:0:0:1:1

      # Move window
      ctrl + alt - e : yabai -m window --display 1; yabai -m display --focus 1
      ctrl + alt - d : yabai -m window --display 2; yabai -m display --focus 2
      ctrl + alt - f : yabai -m window --space next; yabai -m space --focus next
      ctrl + alt - s : yabai -m window --space prev; yabai -m space --focus prev

      # Close current window
      ctrl + alt - w : $(yabai -m window $(yabai -m query --windows --window | jq -re ".id") --close)

      # Rotate tree
      ctrl + alt - r : yabai -m space --rotate 90

      # Open application
      ctrl + alt - enter : alacritty
      ctrl + alt - e : emacs
      ctrl + alt - b : open -a Safari
      ctrl + alt - t : yabai -m window --toggle float;\
        yabai -m window --grid 4:4:1:1:2:2
      ctrl + alt - p : yabai -m window --toggle sticky;\
        yabai -m window --toggle topmost;\
        yabai -m window --toggle pip
    '';
  };

4.2.4. Homebrew

GUI apps with Nix are finicky at best. As much as I would like to fully give up homebrew, its very annoying having to re-install GUI apps on new systems

nix
#
homebrew = {
    brewPrefix = "/opt/homebrew/bin";
    enable = true;
    autoUpdate = true;
    cleanup = "zap"; # keep it clean
    global = {
      brewfile = true;
      noLock = true;
    };

    taps = [
      "homebrew/core" # core
      "homebrew/cask" # we're using this for casks, after all
      "homebrew/cask-versions" # needed for firefox-nightly and discord-canary
    ];

    casks = [
      "firefox-nightly" # my browser of choice
      "discord-canary" # chat client of choice
      "nvidia-geforce-now" # game streaming
      "via" # keyboard config
      "hammerspoon" # "wm"
      "blender" # blender
    ];

  };

4.2.5. Hammerspoon

Yabai breaks very, very often and amethyst just isn’t my cup of tea. Lets use hammerspoon to configure some of our system and implement our own tiling wm. I’ve implemented this elsewhere. We also need to build the spaces dependency from source, since this is an arm64 machine

nix
#
  system.activationScripts.postUserActivation.text = ''
    sudo cp -r ~/nix-darwin-dotfiles/configs/hammerspoon/ ~/.hammerspoon
    git clone --depth 1 https://github.com/asmagill/hs._asm.undocumented.spaces.git spaces && cd spaces && make install && cd .. && rm -f -r spaces
  '';

4.2.6. MacOS Settings

I like my hostname to be the same as the flake’s target

nix
#
  networking.hostName = "shaunsingh-laptop";
  system.stateVersion = 4;

Along with that, lets

  • Increase key repeat rate
  • Remap Caps to Esc
  • Save screenshots to /tmp
  • Autohide the dock and menubar
  • Show extensions in Finder (and allow it to “quit”)
  • Set macOS to use the dark theme
  • Configure Trackpad and mouse behavior
  • Enable subpixel antialiasing on internal/external displays
nix
#
  system.keyboard = {
    enableKeyMapping = true;
    remapCapsLockToEscape = true;
  };
  system.defaults = {
    screencapture = { location = "/tmp"; };
    dock = {
      autohide = true;
      showhidden = true;
      mru-spaces = false;
    };
    finder = {
      AppleShowAllExtensions = true;
      QuitMenuItem = true;
      FXEnableExtensionChangeWarning = true;
    };
    NSGlobalDomain = {
      AppleInterfaceStyle = "Dark";
      AppleKeyboardUIMode = 3;
      ApplePressAndHoldEnabled = false;
      AppleFontSmoothing = 1;
      _HIHideMenuBar = false;
      InitialKeyRepeat = 10;
      KeyRepeat = 1;
      "com.apple.mouse.tapBehavior" = 1;
      "com.apple.swipescrolldirection" = true;
    };
  };
}

4.3. Pam.nix

Apple’s touchid is an excellent way of authenticating anything quickly and securely. Sadly, sudo doesn’t support it by default, but its an easy fix. T do this, we edit /etc/pam.d/sudo via sed to include the relevent code to enable touchid.

We don’t use environment.etc because this would require that the user manually delete /etc/pam.d/sudo which seems unwise given that applying the nix-darwin configuration requires sudo. We also can’t use system.patches since it only runs once, and so won’t patch in the changes again after OS updates (which remove modifications to this file).

As such, we resort to line addition/deletion in place using sed. We add a comment to the added line that includes the name of the option, to make it easier to identify the line that should be deleted when the option is disabled.

nix
#
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.security.pam;
  mkSudoTouchIdAuthScript = isEnabled:
    let
      file = "/etc/pam.d/sudo";
      option = "security.pam.enableSudoTouchIdAuth";
    in ''
      ${if isEnabled then ''
        # Enable sudo Touch ID authentication, if not already enabled
        if ! grep 'pam_tid.so' ${file} > /dev/null; then
          sed -i "" '2i\
        auth       sufficient     pam_tid.so # nix-darwin: ${option}
          ' ${file}
        fi
      '' else ''
        # Disable sudo Touch ID authentication, if added by nix-darwin
        if grep '${option}' ${file} > /dev/null; then
          sed -i "" '/${option}/d' ${file}
        fi
      ''}
    '';

in {
  options = {
    security.pam.enableSudoTouchIdAuth = mkEnableOption ''
      Enable sudo authentication with Touch ID
      When enabled, this option adds the following line to /etc/pam.d/sudo:
          auth       sufficient     pam_tid.so
      (Note that macOS resets this file when doing a system update. As such, sudo
      authentication with Touch ID won't work after a system update until the nix-darwin
      configuration is reapplied.)
    '';
  };

  config = {
    system.activationScripts.extraActivation.text = ''
      # PAM settings
      echo >&2 "setting up pam..."
      ${mkSudoTouchIdAuthScript cfg.enableSudoTouchIdAuth}
    '';
  };
}

5. Editors

5.1. Emacs

5.1.1. Note: If you want a proper Emacs Config, look here:

https://tecosaur.github.io/emacs-config/config.html, this is just a compilation of different parts of his (and other’s) configs, as well as a few parts I wrote by my own. I’m slowly working on making my config “mine”

  1. Credit:
    • Tecosaur - For all his help and the excellent config
    • Dr. Elken - For his EXWM Module and help on the DOOM Server
    • Henrik - For making Doom Emacs in the first place

    Includes (snippets) of other software related under the MIT license:

    Includes (snippets) of other software related under the GPLv3 license:

5.1.2. Intro

Real Programmers

Customizing an editor can be very rewarding … until you have to leave it. For years I have been looking for ways to avoid this pain. Then I discovered vim-anywhere. The issue is

  1. I use neovim (and neovide), not vim (and gvim)
  2. Firenvim is only for browsers
  3. Even if I found a neovim alternative, you can’t do everything in neovim

I wanted everything, in one place. Hence why I (mostly) switched to Emacs.

Separately, online I have seen the following statement enough times I think it’s a catchphrase

Redditor 1: I just discovered this thing, isn’t it cool.
Redditor 2: Oh, there’s an Emacs mode for that.

This was enough for me to install Emacs, but there are many other reasons to keep using it.

I tried out the spacemacs distribution a bit, but it wasn’t quite to my liking. Then I heard about doom emacs and thought I may as well give that a try.

With Org, I’ve discovered the wonders of literate programming, and with the help of others I’ve switched more and more to just using Emacs (just replace “Linux” with “Emacs” in the comic below).

Cautionary

Thats not to say using Emacs doesn’t have its pitfalls. The performance leaves something to be desired, but the benefits far outweigh the drawbacks. Its unrivaled in extensibility.

Editor Extensibility Ecosystem Ease of Use Comfort Completion Performance
IDLE 1 1 3 1 1 2
VSCode 3 3 4 3.5 4 3
Emacs 5 4 2 4 3.5 3
(Neo)Vim 4 3 2.5 3.5 4 5
Radar chart comparing my thoughts on a few editors.
  1. Why Emacs?

    Emacs is not a text editor, this is a common misnomer. It is far more apt to describe Emacs as a Lisp machine providing a generic user-centric text manipulation environment. That’s quite a mouthful. In simpler terms one can think of Emacs as a platform for text-related applications. It’s a vague and generic definition because Emacs itself is generic.

    Good with text. How far does that go? A lot further than one initially thinks:

    Ideally, one may use Emacs as the interface to perform input → transform → output cycles, i.e. form a bridge between the human mind and information manipulation.

    1. The enveloping editor

      Emacs allows one to do more in one place than any other application. Why is this good?

      • Enables one to complete tasks with a consistent, standard set of keybindings, GUI and editing methods — learn once, use everywhere
      • Reduced context-switching
      • Compressing the stages of a project — a more centralised workflow can progress with greater ease
      • Integration between tasks previously relegated to different applications, but with a common subject — e.g. linking to an email in a to-do list

      Emacs can be thought of as a platform within which various elements of your workflow may settle, with the potential for rich integrations between them — a life IDE if you will.

      Today, many aspects of daily computer usage are split between different applications which act like islands, but this often doesn’t mirror how we actually use our computers. Emacs, if one goes down the rabbit hole, can give users the power to bridge this gap.

  2. Notes for the unwary adventurer

    The lovely doom doctor is good at diagnosing most missing things, but here are a few extras.

    My nix config should handle installing all these dependencies, If you aren’t using it, here is the list of packages you may need:

    nix
    #
    environment.systemPackages = with pkgs; [
      # Emacs deps
      ((emacsPackagesNgGen emacsGcc).emacsWithPackages
        (epkgs: [ epkgs.vterm epkgs.pdf-tools ]))
      ## make sure ripgrep supports pcre2 (for vertico)
      (ripgrep.override { withPCRE2 = true; })
      sqlite
      zstd
      ## Required for plots but not installed by default
      gnuplot
      ## Required for dictionaries but not installed by default
      sdcv
      # aspell and \latex
      (aspellWithDicts (ds: with ds; [ en en-computers en-science ]))
      (texlive.combine {
        inherit (texlive)
          scheme-small dvipng dvisvgm l3packages xcolor soul adjustbox
          collectbox amsmath siunitx cancel mathalpha capt-of chemfig
          wrapfig mhchem fvextra cleveref latexmk tcolorbox environ arev
          amsfonts simplekv alegreya sourcecodepro newpx svg catchfile
          transparent hanging;
      })
    
      # Language deps
      tree-sitter
      python39Packages.grip
      python39Packages.pyflakes
      python39Packages.isort
      python39Packages.pytest
      nodePackages.pyright
      pipenv
      nixfmt
      black
      rust-analyzer
      rust-bin.nightly.latest.default
      shellcheck
      # jdk
    
      # Terminal utils and rust alternatives :tm:
      fd
    ];
    fonts = {
      enableFontDir = true;
      fonts = with pkgs; [
        overpass
        alegreya
        alegreya-sans
        emacs-all-the-icons-fonts
        sf-mono-liga-bin
      ];
    };
    
    • A LaTeX Compiler is required for the mathematics rendering performed in org, and that wonderful pdf/html export we have going. I recommend Tectonic if you are new, this config uses XeLaTeX.
    • I use the Overpass font as a go-to sans serif. It’s used as my doom-variable-pitch-font I have chosen it because it possesses a few characteristics I consider desirable, namely:
      • A clean, and legible style. Highway-style fonts tend to be designed to be clear at a glance, and work well with a thicker weight, and this is inspired by Highway Gothic.
      • It’s slightly quirky. Look at the diagonal cut on stems for example. Helvetica is a masterful design, but I like a bit more pizzazz now and then.
      • Note: Alegreya is used for my latex export and writeroom mode configurations
    • I use my patched SFMono font as a go-to monospace. I have chosen it because it possesses a few characteristics I consider desirable, namely:
      • Elegent characters, and good ligatures/unicode support
      • It fits will with the rest of my system
    • A few LSP servers. Take a look at init.el’ to see which modules have the +lsp flag.
    • Gnuplot, used for org-plot.
    • A build of emacs with modules and xwidgets support. I also recommend the native-comp flag with emacs28.

5.1.3. Doom Configuration

  1. Modules

    Doom has this lovely modular configuration base that takes a lot of work out of configuring Emacs. Each module (when enabled) can provide a list of packages to install (on doom sync) and configuration to be applied. The modules can also have flags applied to tweak their behaviour.

    init.elEmacs Lisp
    #
    ;;; init.el -*- lexical-binding: t; -*-
    
    ;; This file controls what Doom modules are enabled and what order they load in.
    ;; Press 'K' on a module to view its documentation, and 'gd' to browse its directory.
    
    (doom! :completion
           <<doom-completion>>
    
           :ui
           <<doom-ui>>
    
           :editor
           <<doom-editor>>
    
           :emacs
           <<doom-emacs>>
    
           :term
           <<doom-term>>
    
           :checkers
           <<doom-checkers>>
    
           :tools
           <<doom-tools>>
    
           :os
           <<doom-os>>
    
           :lang
           <<doom-lang>>
    
           :email
           <<doom-email>>
    
    
           :app
           <<doom-app>>
    
           :config
           <<doom-config>>)
    
    1. Structure

      As you may have noticed by this point, this is a literate configuration. Doom has good support for this which we access though the literate module.

      While we’re in the :config section, we’ll use Dooms nicer defaults, along with the bindings and smartparens behaviour (the flags aren’t documented, but they exist).

      doom-configEmacs Lisp
      #
      literate
      (default +bindings +smartparens)
      
      1. Asynchronous config tangling

        Doom adds an org-mode hook +literate-enable-recompile-h. This is a nice idea, but it’s too blocking for my taste. Since I trust my tangling to be fairly straightforward, I’ll just redefine it to a simpler, async, function.

        Emacs Lisp
        #
        (defvar +literate-tangle--proc nil)
        (defvar +literate-tangle--proc-start-time nil)
        
        (defadvice! +literate-tangle-async-h ()
          "A very simplified version of `+literate-tangle-h', but async."
          :override #'+literate-tangle-h
          (unless (getenv "__NOTANGLE")
            (let ((default-directory doom-private-dir))
              (when +literate-tangle--proc
                (message "Killing outdated tangle process...")
                (set-process-sentinel +literate-tangle--proc #'ignore)
                (kill-process +literate-tangle--proc)
                (sit-for 0.3)) ; ensure the message is seen for a bit
              (setq +literate-tangle--proc-start-time (float-time)
                    +literate-tangle--proc
                    (start-process "tangle-config"
                                   (get-buffer-create " *tangle config*")
                                   "emacs" "--batch" "--eval"
                                   (format "(progn \
        (require 'ox) \
        (require 'ob-tangle) \
        (setq org-confirm-babel-evaluate nil \
              org-inhibit-startup t \
              org-mode-hook nil \
              write-file-functions nil \
              before-save-hook nil \
              after-save-hook nil \
              vc-handled-backends nil \
              org-startup-folded nil \
              org-startup-indented nil) \
        (org-babel-tangle-file \"%s\" \"%s\"))"
                                           +literate-config-file
                                           (expand-file-name (concat doom-module-config-file ".el")))))
              (set-process-sentinel +literate-tangle--proc #'+literate-tangle--sentinel)
              (run-at-time nil nil (lambda () (message "Tangling config.org"))) ; ensure shown after a save message
              "Tangling config.org...")))
        
        (defun +literate-tangle--sentinel (process signal)
          (cond
           ((and (eq 'exit (process-status process))
                 (= 0 (process-exit-status process)))
            (message "Tangled config.org sucessfully (took %.1fs)"
                     (- (float-time) +literate-tangle--proc-start-time))
            (setq +literate-tangle--proc nil))
           ((memq (process-status process) (list 'exit 'signal))
            (+popup-buffer (get-buffer " *tangle config*"))
            (message "Failed to tangle config.org (after %.1fs)"
                     (- (float-time) +literate-tangle--proc-start-time))
            (setq +literate-tangle--proc nil))))
        
        (defun +literate-tangle-check-finished ()
          (when (and (process-live-p +literate-tangle--proc)
                     (yes-or-no-p "Config is currently retangling, would you please wait a few seconds?"))
            (switch-to-buffer " *tangle config*")
            (signal 'quit nil)))
        (add-hook! 'kill-emacs-hook #'+literate-tangle-check-finished)
        
    2. Interface

      There’s a lot that can be done to enhance Emacs’ capabilities. I reckon enabling half the modules Doom provides should do it.

      doom-completionEmacs Lisp
      #
      (company                     ; the ultimate code completion backend
       +childframe)                ; ... when your children are better than you
      ;;helm                       ; the *other* search engine for love and life
      ;;ido                        ; the other *other* search engine...
      ;;(ivy                       ; a search engine for love and life
      ;; +icons                    ; ... icons are nice
      ;; +prescient)               ; ... I know what I want(ed)
      (vertico +icons)             ; the search engine of the future
      
      doom-uiEmacs Lisp
      #
      ;;deft                       ; notational velocity for Emacs
      doom                         ; what makes DOOM look the way it does
      doom-dashboard               ; a nifty splash screen for Emacs
      doom-quit                    ; DOOM quit-message prompts when you quit Emacs
      ;;(emoji +unicode)           ; 🙂
      ;;fill-column                ; a `fill-column' indicator
      hl-todo                      ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
      ;;hydra                      ; quick documentation for related commands
      ;;indent-guides              ; highlighted indent columns, notoriously slow
      (ligatures                   ; ligatures and symbols to make your code pretty again
       +extra)                     ; for those who dislike letters
      minimap                      ; show a map of the code on the side
      modeline                     ; snazzy, Atom-inspired modeline, plus API
      ;; +light)                   ; the doom modeline is a bit much, the default is a bit little
      nav-flash                    ; blink the current line after jumping
      ;;neotree                    ; a project drawer, like NERDTree for vim
      ophints                      ; highlight the region an operation acts on
      (popup                       ; tame sudden yet inevitable temporary windows
       +all                        ; catch all popups that start with an asterix
       +defaults)                  ; default popup rules
      ;;(tabs                      ; an tab bar for Emacs
      ;;  +centaur-tabs)           ; ... with prettier tabs
      treemacs                     ; a project drawer, like neotree but cooler
      ;;unicode                    ; extended unicode support for various languages
      vc-gutter                    ; vcs diff in the fringe
      vi-tilde-fringe              ; fringe tildes to mark beyond EOB
      ;;(window-select +numbers)   ; visually switch windows
      workspaces                   ; tab emulation, persistence & separate workspaces
      zen                          ; distraction-free coding or writing
      
      doom-editorEmacs Lisp
      #
      (evil +everywhere)           ; come to the dark side, we have cookies
      file-templates               ; auto-snippets for empty files
      fold                         ; (nigh) universal code folding
      (format +onsave)             ; automated prettiness
      ;;god                        ; run Emacs commands without modifier keys
      ;;lispy                      ; vim for lisp, for people who don't like vim
      ;;multiple-cursors           ; editing in many places at once
      ;;objed                      ; text object editing for the innocent
      ;;parinfer                   ; turn lisp into python, sort of
      ;;rotate-text                ; cycle region at point between text candidates
      snippets                     ; my elves. They type so I don't have to
      ;;word-wrap                  ; soft wrapping with language-aware indent
      
      doom-emacsEmacs Lisp
      #
      (dired +icons)               ; making dired pretty [functional]
      electric                     ; smarter, keyword-based electric-indent
      (ibuffer +icons)             ; interactive buffer management
      undo                         ; persistent, smarter undo for your inevitable mistakes
      vc                           ; version-control and Emacs, sitting in a tree
      
      doom-termEmacs Lisp
      #
      ;;eshell                     ; the elisp shell that works everywhere
      ;;shell                      ; simple shell REPL for Emacs
      ;;term                       ; basic terminal emulator for Emacs
      vterm                        ; the best terminal emulation in Emacs
      
      doom-checkersEmacs Lisp
      #
      syntax                                         ; tasing you for every semicolon you forget
      (:if (executable-find "aspell") spell)         ; tasing you for misspelling mispelling
      (:if (executable-find "languagetool") grammar) ; tasing grammar mistake every you make
      
      doom-toolsEmacs Lisp
      #
      ;;ansible                    ; a crucible for infrastructure as code
      ;;biblio                     ; Writes a PhD for you (citation needed)
      (debugger +lsp)              ; FIXME stepping through code, to help you add bugs
      ;;direnv                     ; be direct about your environment
      ;;docker                     ; port everything to containers
      ;;editorconfig               ; let someone else argue about tabs vs spaces
      ;;ein                        ; tame Jupyter notebooks with emacs
      (eval +overlay)              ; run code, run (also, repls)
      ;;gist                       ; interacting with github gists
      (lookup                      ; helps you navigate your code and documentation
       +dictionary                 ; dictionary/thesaurus is nice
       +docsets)                   ; ...or in Dash docsets locally
      lsp                          ; Language Server Protocol
      (magit                       ; a git porcelain for Emacs
       +forge)                     ; interface with git forges
      ;;make                       ; run make tasks from Emacs
      ;;pass                       ; password manager for nerds
      pdf                          ; pdf enhancements
      ;;prodigy                    ; FIXME managing external services & code builders
      ;;rgb                        ; creating color strings
      ;;taskrunner                 ; taskrunner for all your projects
      ;;terraform                  ; infrastructure as code
      ;;tmux                       ; an API for interacting with tmux
      ;;tree-sitter                ; ... sitting in a tree
      ;;upload                     ; map local to remote projects via ssh/ftp
      
      doom-osEmacs Lisp
      #
      (:if IS-MAC macos)           ; improve compatibility with macOS
      ;;tty                        ; improve the terminal Emacs experience
      
    3. Language support

      We can be rather liberal with enabling support for languages as the associated packages/configuration are (usually) only loaded when first opening an associated file.

      doom-langEmacs Lisp
      #
      ;;agda                       ; types of types of types of types...
      ;;beancount                  ; mind the GAAP
      (cc +lsp)                    ; C/C++/Obj-C madness
      ;;clojure                    ; java with a lisp
      ;;common-lisp                ; if you've seen one lisp, you've seen them all
      ;;coq                        ; proofs-as-programs
      ;;crystal                    ; ruby at the speed of c
      ;;csharp                     ; unity, .NET, and mono shenanigans
      ;;data                       ; config/data formats
      ;;(dart +flutter)            ; paint ui and not much else
      ;;dhall                      ; JSON with FP sprinkles
      ;;elixir                     ; erlang done right
      ;;elm                        ; care for a cup of TEA?
      emacs-lisp                   ; drown in parentheses
      ;;erlang                     ; an elegant language for a more civilized age
      ;;ess                        ; emacs speaks statistics
      ;;faust                      ; dsp, but you get to keep your soul
      ;;fsharp                     ; ML stands for Microsoft's Language
      ;;fstar                      ; (dependent) types and (monadic) effects and Z3
      ;;gdscript                   ; the language you waited for
      ;;(go +lsp)                  ; the hipster dialect
      ;;(haskell +lsp)             ; a language that's lazier than I am
      ;;hy                         ; readability of scheme w/ speed of python
      ;;idris                      ;
      ;;json                       ; At least it ain't XML
      ;;(java +lsp)                ; the poster child for carpal tunnel syndrome
      ;;(javascript +lsp)          ; all(hope(abandon(ye(who(enter(here))))))
      ;;(julia +lsp)               ; Python, R, and MATLAB in a blender
      ;;(kotlin +lsp)              ; a better, slicker Java(Script)
      (latex                       ; writing papers in Emacs has never been so fun
       +fold                       ; fold the clutter away nicities
       +latexmk                    ; modern latex plz
       +cdlatex                    ; quick maths symbols
       +lsp)
      ;;lean                       ; proof that mathematicians need help
      ;;factor                     ; for when scripts are stacked against you
      ;;ledger                     ; an accounting system in Emacs
      (lua                         ; one-based indices? one-based indices
       +lsp)
      (markdown +grip)             ; writing docs for people to ignore
      ;;nim                        ; python + lisp at the speed of c
      nix                          ; I hereby declare "nix geht mehr!"
      ;;ocaml                      ; an objective camel
      (org                         ; organize your plain life in plain text
       +pretty                     ; yessss my pretties! (nice unicode symbols)
       +dragndrop                  ; drag & drop files/images into org buffers
       ;;+hugo                     ; use Emacs for hugo blogging
       ;;+noter                    ; enhanced PDF notetaking
       +jupyter                    ; ipython/jupyter support for babel
       +pandoc                     ; export-with-pandoc support
       +gnuplot                    ; who doesn't like pretty pictures
       +pomodoro                   ; be fruitful with the tomato technique
       +present                    ; using org-mode for presentations
       +roam2)                     ; wander around notes
      ;;php                        ; perl's insecure younger brother
      ;;plantuml                   ; diagrams for confusing people more
      ;;purescript                 ; javascript, but functional
      (python +lsp +pyright)       ; beautiful is better than ugly
      ;;qt                         ; the 'cutest' gui framework ever
      ;;racket                     ; a DSL for DSLs
      ;;raku                       ; the artist formerly known as perl6
      ;;rest                       ; Emacs as a REST client
      ;;rst                        ; ReST in peace
      ;;(ruby +rails)              ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
      (rust +lsp)                  ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
      ;;scala                      ; java, but good
      ;;scheme                     ; a fully conniving family of lisps
      sh                           ; she sells {ba,z,fi}sh shells on the C xor
      ;;sml                        ; no, the /other/ ML
      ;;solidity                   ; do you need a blockchain? No.
      ;;swift                      ; who asked for emoji variables?
      ;;terra                      ; Earth and Moon in alignment for performance.
      ;;web                        ; the tubes
      ;;yaml                       ; JSON, but readable
      ;;zig                        ; C, but simpler
      
    4. Everything in Emacs

      It’s just too convenient being able to have everything in Emacs. I couldn’t resist the Email and Feed modules.

      doom-emailEmacs Lisp
      #
      (:if (executable-find "mu") (mu4e +org +gmail))
      ;;notmuch
      ;;(wanderlust +gmail)
      
      doom-appEmacs Lisp
      #
      ;;calendar                   ; A dated approach to timetabling
      ;;emms                       ; Multimedia in Emacs is music to my ears
      ;;everywhere                 ; *leave* Emacs!? You must be joking.
      ;;irc                        ; how neckbeards socialize
      ;;(rss +org)                 ; emacs as an RSS reader
      ;;twitter                    ; twitter client https://twitter.com/vnought
      
  2. Packages

    Unlike most literate configurations I am lazy like to keep all my packages in one place

    packages.elEmacs Lisp
    #
    ;; -*- no-byte-compile: t; -*-
    ;;; $DOOMDIR/packages.el
    
    ;;org
    <<org>>
    
    ;;latex
    <<latex>>
    
    ;;markdown and html
    <<web>>
    
    ;;looks
    <<looks>>
    
    ;;emacs additions
    <<emacs>>
    
    ;;lsp
    <<lsp>>
    
    ;;fun
    <<fun>>
    
    1. Org:

      The majority of my work in emacs is done in org mode, even this configuration was written in org! It makes sense that the majority of my packages are for tweaking org then

      orgEmacs Lisp
      #
      (package! doct)
      (package! citar)
      (package! citeproc)
      (package! org-appear)
      (package! org-roam-ui)
      (package! org-cite-csl-activate
        :recipe (:host github
                 :repo "andras-simonyi/org-cite-csl-activate"))
      (package! org-pandoc-import ;https://github.com/melpa/melpa/pull/7326
        :recipe (:host github
                 :repo "tecosaur/org-pandoc-import"
                 :files ("*.el" "filters" "preprocessors")))
      
    2. \LaTeX:

      When I’m not working in org, I’m probably exporting it to latex. Lets adjust that a bit too

      latexEmacs Lisp
      #
      (package! aas)
      (package! laas)
      (package! org-fragtog)
      (package! engrave-faces)
      
    3. Web:

      Sometimes I need to use markdown too. Note: emacs-webkit is temporarily disabled because of its refusal to work without requiring org

      webEmacs Lisp
      #
      (package! ox-gfm)
      (package! websocket)
      ;;(package! webkit
      ;;          :recipe (:host github
      ;;                   :repo "akirakyle/emacs-webkit"
      ;;                   :branch "main"
      ;;                   :files (:defaults "*.js" "*.css" "*.so" "*.nix")
      ;;                   :pre-build (("nix-shell" "shell.nix" "--command make"))))
      
    4. Looks:

      Making emacs look good is first priority, actually working in it is second

      looksEmacs Lisp
      #
      (unpin! doom-themes)
      (unpin! doom-modeline)
      (package! modus-themes)
      (package! solaire-mode :disable t)
      (package! ox-chameleon :recipe (:host github :repo "tecosaur/ox-chameleon")) ;soon :tm:
      
    5. Emacs Tweaks:

      Emacs is missing just a few packages that I need to make it my OS. Specifically, screenshot capabilities are nice, and using the same dictionaries accross operating systems bootloaders would be nice too!

      emacsEmacs Lisp
      #
      (package! lexic)
      (package! pdf-tools)
      (package! magit-delta)
      (package! screenshot :recipe (:host github :repo "Jimmysit0/screenshot")) ;https://github.com/melpa/melpa/pull/7327
      
    6. LSP:

      I like to live life on the edge

      lspEmacs Lisp
      #
      (unpin! lsp-ui)
      (unpin! lsp-mode)
      
    7. Fun:

      We do a little trolling (and reading)

      funEmacs Lisp
      #
      (package! nov)
      (package! xkcd)
      (package! keycast)
      (package! selectric-mode :recipe (:local-repo "lisp/selectric-mode"))
      

5.1.4. Basic Configuration

Make this file run (slightly) faster with lexical binding

elisp
#
;;; config.el -*- lexical-binding: t; -*-
  1. Personal information

    Of course we need to tell emacs who I am

    elisp
    #
    (setq user-full-name "Shaurya Singh"
          user-mail-address "shaunsingh0207@gmail.com")
    
  2. Authinfo

    I frequently delete my ~/.emacs.d for fun, so having authinfo in a seperate file sounds like a good idea

    elisp
    #
    (setq auth-sources '("~/.authinfo.gpg")
          auth-source-cache-expiry nil) ; default is 7200 (2h)
    
  3. Emacsclient

    mu4e is a bit finicky with emacsclient, and org takes forever to load. The solution? Use tecosaurs greedy daemon startup

    elisp
    #
    (defun greedily-do-daemon-setup ()
      (require 'org)
      (require 'vertico)
      (require 'consult)
      (require 'marginalia)
      (when (require 'mu4e nil t)
        (setq mu4e-confirm-quit t)
        (setq +mu4e-lock-greedy t)
        (setq +mu4e-lock-relaxed t)
        (+mu4e-lock-add-watcher)
        (when (+mu4e-lock-available t)
          (mu4e~start))))
    
    (when (daemonp)
      (add-hook 'emacs-startup-hook #'greedily-do-daemon-setup)
      (add-hook 'emacs-startup-hook #'init-mixed-pitch-h))
    
  4. Shell

    I use the fish shell. If you use zsh/bash, be sure to change this

    elisp
    #
    (setq explicit-shell-file-name (executable-find "fish"))
    
    1. Vterm

      Vterm is my terminal emulator of choice. We can tell it to use ligatures, and also tell it to compile automatically Vterm clearly wins the terminal war. Also doesn’t need much configuration out of the box, although the shell integration does. You can find that in ~/.config/fish/config.fish

      1. Always compile

        Fixes a weird bug with native-comp

        Emacs Lisp
        #
        (setq vterm-always-compile-module t)
        
      2. Kill buffer

        If the process exits, kill the vterm buffer

        Emacs Lisp
        #
        (setq vterm-kill-buffer-on-exit t)
        
      3. Functions

        Useful functions for the shell-side integration provided by vterm.

        Emacs Lisp
        #
        (after! vterm
          (setf (alist-get "magit-status" vterm-eval-cmds nil nil #'equal)
                '((lambda (path)
                    (magit-status path)))))
        

        I also want to hook Delta into Magit

        elisp
        #
        (after! magit
           (magit-delta-mode +1))
        
      4. Ligatures

        Use ligatures from within vterm (and eshell), we do this by redefining the variable where not to show ligatures. On the other hand, in select modes we want to use extra ligatures, so lets enable that.

        elisp
        #
        (setq +ligatures-in-modes t)
        (setq +ligatures-extras-in-modes '(org-mode emacs-lisp-mode))
        
  5. Fonts

    Papyrus

    I like the apple fonts for programming, so I’ll go with Liga SFMono Nerd Font. I prefer a rounder font for plain text, so I’ll go with Overpass for that. I have a retina display as well, so lets keep the fonts light.

    elisp
    #
    ;;fonts
    (setq doom-font (font-spec :family "Liga SFMono Nerd Font" :size 14)
          doom-big-font (font-spec :family "Liga SFMono Nerd Font" :size 20)
          doom-variable-pitch-font (font-spec :family "Overpass" :size 16)
          doom-unicode-font (font-spec :family "Liga SFMono Nerd Font")
          doom-serif-font (font-spec :family "Liga SFMono Nerd Font" :weight 'light))
    

    For mixed pitch, I would go with something comfier. I like Alegreya Sans for a minimalist feel, so lets go with that

    elisp
    #
    ;;mixed pitch modes
    (defvar mixed-pitch-modes '(org-mode LaTeX-mode markdown-mode gfm-mode Info-mode)
      "Modes that `mixed-pitch-mode' should be enabled in, but only after UI initialisation.")
    (defun init-mixed-pitch-h ()
      "Hook `mixed-pitch-mode' into each mode in `mixed-pitch-modes'.
    Also immediately enables `mixed-pitch-modes' if currently in one of the modes."
      (when (memq major-mode mixed-pitch-modes)
        (mixed-pitch-mode 1))
      (dolist (hook mixed-pitch-modes)
        (add-hook (intern (concat (symbol-name hook) "-hook")) #'mixed-pitch-mode)))
    (add-hook 'doom-init-ui-hook #'init-mixed-pitch-h)
    (add-hook! 'org-mode-hook #'+org-pretty-mode) ;enter mixed pitch mode in org mode
    
    ;;set mixed pitch font
     (after! mixed-pitch
      (defface variable-pitch-serif
        '((t (:family "serif")))
        "A variable-pitch face with serifs."
        :group 'basic-faces)
      (setq mixed-pitch-set-height t)
      (setq variable-pitch-serif-font (font-spec :family "Alegreya Sans" :size 16 :weight 'Medium))
      (set-face-attribute 'variable-pitch-serif nil :font variable-pitch-serif-font)
      (defun mixed-pitch-serif-mode (&optional arg)
        "Change the default face of the current buffer to a serifed variable pitch, while keeping some faces fixed pitch."
        (interactive)
        (let ((mixed-pitch-face 'variable-pitch-serif))
          (mixed-pitch-mode (or arg 'toggle)))))
    

    Harfbuzz is missing the beautiful ff ffi ffj ffl fft fi fj ft Th ligatures, lets add those back in with the help of composition-function-table

    elisp
    #
    (set-char-table-range composition-function-table ?f '(["\\(?:ff?[fijlt]\\)" 0 font-shape-gstring]))
    (set-char-table-range composition-function-table ?T '(["\\(?:Th\\)" 0 font-shape-gstring]))
    
    1. Font collections

      Using the lovely conditional preamble, I’ll define a number of font collections that can be used for LaTeX exports. Who knows, maybe I’ll use it with other export formats too at some point.

      To start with I’ll create a default state variable and register fontset as part of #+options.

      Emacs Lisp
      #
      (after! ox-latex
      (defvar org-latex-default-fontset 'alegreya
        "Fontset from `org-latex-fontsets' to use by default.
      As cm (computer modern) is TeX's default, that causes nothing
      to be added to the document.
      
      If \"nil\" no custom fonts will ever be used.")
      (eval '(cl-pushnew '(:latex-font-set nil "fontset" org-latex-default-fontset)
                         (org-export-backend-options (org-export-get-backend 'latex)))))
      

      Then a function is needed to generate a LaTeX snippet which applies the fontset. It would be nice if this could be done for individual styles and use different styles as the main document font. If the individual typefaces for a fontset are defined individually as :serif, :sans, :mono, and :maths. I can use those to generate LaTeX for subsets of the full fontset. Then, if I don’t let any fontset names have - in them, I can use -sans and -mono as suffixes that specify the document font to use.

      Emacs Lisp
      #
      (after! ox-latex
      (defun org-latex-fontset-entry ()
        "Get the fontset spec of the current file.
      Has format \"name\" or \"name-style\" where 'name' is one of
      the cars in `org-latex-fontsets'."
        (let ((fontset-spec
               (symbol-name
                (or (car (delq nil
                               (mapcar
                                (lambda (opt-line)
                                  (plist-get (org-export--parse-option-keyword opt-line 'latex)
                                             :latex-font-set))
                                (cdar (org-collect-keywords '("OPTIONS"))))))
                    org-latex-default-fontset))))
          (cons (intern (car (split-string fontset-spec "-")))
                (when (cadr (split-string fontset-spec "-"))
                  (intern (concat ":" (cadr (split-string fontset-spec "-"))))))))
      
      (defun org-latex-fontset (&rest desired-styles)
        "Generate a LaTeX preamble snippet which applies the current fontset for DESIRED-STYLES."
        (let* ((fontset-spec (org-latex-fontset-entry))
               (fontset (alist-get (car fontset-spec) org-latex-fontsets)))
          (if fontset
              (concat
               (mapconcat
                (lambda (style)
                  (when (plist-get fontset style)
                    (concat (plist-get fontset style) "\n")))
                desired-styles
                "")
               (when (memq (cdr fontset-spec) desired-styles)
                 (pcase (cdr fontset-spec)
                   (:serif "\\renewcommand{\\familydefault}{\\rmdefault}\n")
                   (:sans "\\renewcommand{\\familydefault}{\\sfdefault}\n")
                   (:mono "\\renewcommand{\\familydefault}{\\ttdefault}\n"))))
            (error "Font-set %s is not provided in org-latex-fontsets" (car fontset-spec))))))
      

      Now that all the functionality has been implemented, we should hook it into our preamble generation.

      Emacs Lisp
      #
      (after! ox-latex
      (add-to-list 'org-latex-conditional-features '(org-latex-default-fontset . custom-font) t)
      (add-to-list 'org-latex-feature-implementations '(custom-font :snippet (org-latex-fontset :serif :sans :mono) :order 0) t)
      (add-to-list 'org-latex-feature-implementations '(.custom-maths-font :eager t :when (custom-font maths) :snippet (org-latex-fontset :maths) :order 0.3) t))
      

      Finally, we just need to add some fonts.

      Emacs Lisp
      #
      (after! ox-latex
      (defvar org-latex-fontsets
        '((cm nil) ; computer modern
          (## nil) ; no font set
          (alegreya
           :serif "\\usepackage[osf]{Alegreya}"
           :sans "\\usepackage{AlegreyaSans}"
           :mono "\\usepackage[scale=0.88]{sourcecodepro}"
           :maths "\\usepackage[varbb]{newpxmath}")
          (biolinum
           :serif "\\usepackage[osf]{libertineRoman}"
           :sans "\\usepackage[sfdefault,osf]{biolinum}"
           :mono "\\usepackage[scale=0.88]{sourcecodepro}"
           :maths "\\usepackage[libertine,varvw]{newtxmath}")
          (fira
           :sans "\\usepackage[sfdefault,scale=0.85]{FiraSans}"
           :mono "\\usepackage[scale=0.80]{FiraMono}"
           :maths "\\usepackage{newtxsf} % change to firamath in future?")
          (kp
           :serif "\\usepackage{kpfonts}")
          (newpx
           :serif "\\usepackage{newpxtext}"
           :sans "\\usepackage{gillius}"
           :mono "\\usepackage[scale=0.9]{sourcecodepro}"
           :maths "\\usepackage[varbb]{newpxmath}")
          (noto
           :serif "\\usepackage[osf]{noto-serif}"
           :sans "\\usepackage[osf]{noto-sans}"
           :mono "\\usepackage[scale=0.96]{noto-mono}"
           :maths "\\usepackage{notomath}")
          (plex
           :serif "\\usepackage{plex-serif}"
           :sans "\\usepackage{plex-sans}"
           :mono "\\usepackage[scale=0.95]{plex-mono}"
           :maths "\\usepackage{newtxmath}") ; may be plex-based in future
          (source
           :serif "\\usepackage[osf]{sourceserifpro}"
           :sans "\\usepackage[osf]{sourcesanspro}"
           :mono "\\usepackage[scale=0.95]{sourcecodepro}"
           :maths "\\usepackage{newtxmath}") ; may be sourceserifpro-based in future
          (times
           :serif "\\usepackage{newtxtext}"
           :maths "\\usepackage{newtxmath}"))
        "Alist of fontset specifications.
      Each car is the name of the fontset (which cannot include \"-\").
      
      Each cdr is a plist with (optional) keys :serif, :sans, :mono, and :maths.
      A key's value is a LaTeX snippet which loads such a font."))
      

      When we’re using Alegreya we can apply a lovely little tweak to tabular which (locally) changes the figures used to lining fixed-width.

      Emacs Lisp
      #
      (after! ox-latex
      (add-to-list 'org-latex-conditional-features '((string= (car (org-latex-fontset-entry)) "alegreya") . alegreya-typeface))
      (add-to-list 'org-latex-feature-implementations '(alegreya-typeface) t)
      (add-to-list 'org-latex-feature-implementations'(.alegreya-tabular-figures :eager t :when (alegreya-typeface table) :order 0.5 :snippet "
      \\makeatletter
      % tabular lining figures in tables
      \\renewcommand{\\tabular}{\\AlegreyaTLF\\let\\@halignto\\@empty\\@tabular}
      \\makeatother\n") t))
      

      Due to the Alegreya’s metrics, the \LaTeX symbol doesn’t quite look right. We can correct for this by redefining it with subtlety shifted kerning.

      Emacs Lisp
      #
      (after! ox-latex
      (add-to-list 'org-latex-conditional-features '("LaTeX" . latex-symbol))
      (add-to-list 'org-latex-feature-implementations '(latex-symbol :when alegreya-typeface :order 0.5 :snippet "
      \\makeatletter
      % Kerning around the A needs adjusting
      \\DeclareRobustCommand{\\LaTeX}{L\\kern-.24em%
              {\\sbox\\z@ T%
               \\vbox to\\ht\\z@{\\hbox{\\check@mathfonts
                                    \\fontsize\\sf@size\\z@
                                    \\math@fontsfalse\\selectfont
                                    A}%
                              \\vss}%
              }%
              \\kern-.10em%
              \\TeX}
      \\makeatother\n") t))
      

      Just in case the fonts aren’t there, lets add check to notify the user of the issue. Seems like I forget ot install fonts every time I switch between distros emacs bootloaders

      detect-missing-fontselisp
      #
      ;; (defvar required-fonts '("Overpass" "Liga SFMono Nerd Font" "Alegreya" ))
      ;; (defvar available-fonts
      ;;   (delete-dups (or (font-family-list)
      ;;                    (split-string (shell-command-to-string "fc-list : family")
      ;;                                  "[,\n]"))))
      ;; (defvar missing-fonts
      ;;   (delq nil (mapcar
      ;;              (lambda (font)
      ;;                (unless (delq nil (mapcar (lambda (f)
      ;;                            (string-match-p (format "^%s$" font) f))
      ;;                                          available-fonts))
      ;;                                          font))
      ;;                                          required-fonts)))
      ;; (if missing-fonts
      ;;     (pp-to-string
      ;;      `(unless noninteractive
      ;;         (add-hook! 'doom-init-ui-hook
      ;;           (run-at-time nil nil
      ;;                        (lambda ()
      ;;                          (message "%s missing the following fonts: %s"
      ;;                                   (propertize "Warning!" 'face '(bold warning))
      ;;                                   (mapconcat (lambda (font)
      ;;                                                (propertize font 'face 'font-lock-variable-name-face))
      ;;                                              ',missing-fonts
      ;;                                              ", "))
      ;;                          (sleep-for 0.5))))))
      ;;  ";; No missing fonts detected")
      
      Emacs Lisp
      #
      ;; <<detect-missing-fonts()>>
      
  6. Themes

    Right now I’m using nord, but I use doom-vibrant sometimes

    elisp
    #
    (setq doom-theme 'modus-vivendi)
    (setq doom-fw-padded-modeline t)
    (setq doom-one-light-padded-modeline t)
    (setq doom-nord-padded-modeline t)
    (setq doom-vibrant-padded-modeline t)
    
    1. Modus Themes

      Generally I use doom-themes, but I also like the new Modus-themes bundled with emacs28/29

      elisp
      #
      (use-package modus-themes
        :init
        ;; Add all your customizations prior to loading the themes
        (setq modus-themes-italic-constructs t
              modus-themes-completions 'opinionated
              modus-themes-variable-pitch-headings t
              modus-themes-scale-headings t
              modus-themes-variable-pitch-ui nil
              modus-themes-org-agenda
              '((header-block . (variable-pitch scale-title))
                (header-date . (grayscale bold-all)))
              modus-themes-org-blocks
              '(grayscale)
              modus-themes-mode-line
              '(borderless)
              modus-themes-region '(bg-only no-extend))
      
        ;; Load the theme files before enabling a theme
        (modus-themes-load-themes)
        :config
        (modus-themes-load-vivendi)
        :bind ("<f5>" . modus-themes-toggle))
      
  7. Company

    I think company is a bit too quick to recommend some stuff

    elisp
    #
    (after! company
       (setq company-idle-delay 0.1
          company-minimum-prefix-length 1
          company-selection-wrap-around t
          company-require-match 'never
          company-dabbrev-downcase nil
          company-dabbrev-ignore-case t
          company-dabbrev-other-buffers nil
          company-tooltip-limit 5
          company-tooltip-minimum-width 50))
    (set-company-backend!
      '(text-mode
        markdown-mode
        gfm-mode)
      '(:seperate
        company-yasnippet
        company-files))
    
    ;;nested snippets
    (setq yas-triggers-in-field t)
    

    Lets add some snippets for latex

    elisp
    #
    (use-package! aas
      :commands aas-mode)
    
    (use-package! laas
      :hook (LaTeX-mode . laas-mode)
      :config
      (defun laas-tex-fold-maybe ()
        (unless (equal "/" aas-transient-snippet-key)
          (+latex-fold-last-macro-a)))
      (add-hook 'org-mode #'laas-mode)
      (add-hook 'aas-post-snippet-expand-hook #'laas-tex-fold-maybe))
    
    

    And with a little help from henrik, lets use those snippets in org mode

    elisp
    #
    (defadvice! fixed-org-yas-expand-maybe-h ()
      "Expand a yasnippet snippet, if trigger exists at point or region is active.
    Made for `org-tab-first-hook'."
      :override #'+org-yas-expand-maybe-h
      (when (and (featurep! :editor snippets)
                 (require 'yasnippet nil t)
                 (bound-and-true-p yas-minor-mode))
        (and (let ((major-mode (cond ((org-in-src-block-p t)
                                      (org-src-get-lang-mode (org-eldoc-get-src-lang)))
                                     ((org-inside-LaTeX-fragment-p)
                                      'latex-mode)
                                     (major-mode)))
                   (org-src-tab-acts-natively nil) ; causes breakages
                   ;; Smart indentation doesn't work with yasnippet, and painfully slow
                   ;; in the few cases where it does.
                   (yas-indent-line 'fixed))
               (cond ((and (or (not (bound-and-true-p evil-local-mode))
                               (evil-insert-state-p)
                               (evil-emacs-state-p))
                           (or (and (bound-and-true-p yas--tables)
                                    (gethash major-mode yas--tables))
                               (progn (yas-reload-all) t))
                           (yas--templates-for-key-at-point))
                      (yas-expand)
                      t)
                     ((use-region-p)
                      (yas-insert-snippet)
                      t)))
             ;; HACK Yasnippet breaks org-superstar-mode because yasnippets is
             ;;      overzealous about cleaning up overlays.
             (when (bound-and-true-p org-superstar-mode)
               (org-superstar-restart)))))
    

    Source code blocks are a pain in org-mode, so lets make a few functions to help with our snippets

    Emacs Lisp
    #
    (defun +yas/org-src-header-p ()
      "Determine whether `point' is within a src-block header or header-args."
      (pcase (org-element-type (org-element-context))
        ('src-block (< (point) ; before code part of the src-block
                       (save-excursion (goto-char (org-element-property :begin (org-element-context)))
                                       (forward-line 1)
                                       (point))))
        ('inline-src-block (< (point) ; before code part of the inline-src-block
                              (save-excursion (goto-char (org-element-property :begin (org-element-context)))
                                              (search-forward "]{")
                                              (point))))
        ('keyword (string-match-p "^header-args" (org-element-property :value (org-element-context))))))
    

    Now let’s write a function we can reference in yasnippets to produce a nice interactive way to specify header args.

    Emacs Lisp
    #
    (defun +yas/org-prompt-header-arg (arg question values)
      "Prompt the user to set ARG header property to one of VALUES with QUESTION.
    The default value is identified and indicated. If either default is selected,
    or no selection is made: nil is returned."
      (let* ((src-block-p (not (looking-back "^#\\+property:[ \t]+header-args:.*" (line-beginning-position))))
             (default
               (or
                (cdr (assoc arg
                            (if src-block-p
                                (nth 2 (org-babel-get-src-block-info t))
                              (org-babel-merge-params
                               org-babel-default-header-args
                               (let ((lang-headers
                                      (intern (concat "org-babel-default-header-args:"
                                                      (+yas/org-src-lang)))))
                                 (when (boundp lang-headers) (eval lang-headers t)))))))
                ""))
             default-value)
        (setq values (mapcar
                      (lambda (value)
                        (if (string-match-p (regexp-quote value) default)
                            (setq default-value
                                  (concat value " "
                                          (propertize "(default)" 'face 'font-lock-doc-face)))
                          value))
                      values))
        (let ((selection (consult--read question values :default default-value)))
          (unless (or (string-match-p "(default)$" selection)
                      (string= "" selection))
            selection))))
    

    Finally, we fetch the language information for new source blocks.

    Since we’re getting this info, we might as well go a step further and also provide the ability to determine the most popular language in the buffer that doesn’t have any header-args set for it (with #+properties).

    Emacs Lisp
    #
    (defun +yas/org-src-lang ()
      "Try to find the current language of the src/header at `point'.
    Return nil otherwise."
      (let ((context (org-element-context)))
        (pcase (org-element-type context)
          ('src-block (org-element-property :language context))
          ('inline-src-block (org-element-property :language context))
          ('keyword (when (string-match "^header-args:\\([^ ]+\\)" (org-element-property :value context))
                      (match-string 1 (org-element-property :value context)))))))
    
    (defun +yas/org-last-src-lang ()
      "Return the language of the last src-block, if it exists."
      (save-excursion
        (beginning-of-line)
        (when (re-search-backward "^[ \t]*#\\+begin_src" nil t)
          (org-element-property :language (org-element-context)))))
    
    (defun +yas/org-most-common-no-property-lang ()
      "Find the lang with the most source blocks that has no global header-args, else nil."
      (let (src-langs header-langs)
        (save-excursion
          (goto-char (point-min))
          (while (re-search-forward "^[ \t]*#\\+begin_src" nil t)
            (push (+yas/org-src-lang) src-langs))
          (goto-char (point-min))
          (while (re-search-forward "^[ \t]*#\\+property: +header-args" nil t)
            (push (+yas/org-src-lang) header-langs)))
    
        (setq src-langs
              (mapcar #'car
                      ;; sort alist by frequency (desc.)
                      (sort
                       ;; generate alist with form (value . frequency)
                       (cl-loop for (n . m) in (seq-group-by #'identity src-langs)
                                collect (cons n (length m)))
                       (lambda (a b) (> (cdr a) (cdr b))))))
    
        (car (cl-set-difference src-langs header-langs :test #'string=))))
    

    Lets also include << to autocomplete, as with () and {}

    Emacs Lisp
    #
    (sp-local-pair
     '(org-mode)
     "<<" ">>"
     :actions '(insert))
    

    And lastly lets add some helpful snippets for org-mode, and add a better templete

    elisp
    #
    (set-file-template! "\\.org$" :trigger "__" :mode 'org-mode)
    
  8. LSP

    I think the LSP is a bit intrusive (especially with inline suggestions), so lets make it behave a bit more

    elisp
    #
    (use-package! lsp-ui
      :hook (lsp-mode . lsp-ui-mode)
      :config
      (setq lsp-ui-sideline-enable nil; not anymore useful than flycheck
            lsp-lens-enable t
            lsp-ui-doc-enable t
            lsp-tex-server 'digestif
            lsp-headerline-breadcrumb-enable nil
            lsp-ui-peek-enable t
            lsp-ui-peek-fontify 'on-demand
            lsp-enable-symbol-highlighting nil))
    

    The rust language server also has some extra features I would like to enable

    elisp
    #
    (after! lsp-rust
      (setq lsp-rust-server 'rust-analyzer
      lsp-rust-analyzer-display-chaining-hints t
      lsp-rust-analyzer-display-parameter-hints t
      lsp-rust-analyzer-server-display-inlay-hints t
      lsp-rust-analyzer-cargo-watch-command "clippy"
      rustic-format-on-save t))
    

    I also want to use clippy for linting, and those sweet org-mode docs

    elisp
    #
    (use-package rustic
      :after lsp
      :config
      (setq lsp-rust-analyzer-cargo-watch-command "clippy"
            rustic-lsp-server 'rust-analyzer)
      (rustic-doc-mode t))
    
  9. Better Defaults

    The defaults for emacs aren’t so good nowadays. Lets fix that up a bit

    elisp
    #
    (setq undo-limit 80000000                          ;I mess up too much
          evil-want-fine-undo t                        ;By default while in insert all changes are one big blob. Be more granular
          scroll-margin 2                              ;having a little margin is nice
          auto-save-default t                          ;I dont like to lose work
          ;; display-line-numbers-type 'relative       ;If I have to use line numbers, at least make them relative
          display-line-numbers-type nil                ;I dislike line numbers
          history-length 25                            ;Slight speedup
          delete-by-moving-to-trash t                  ;delete to system trash instead
          browser-url-browser-function 'eww-browse-url ; use the builtin eww to browse links
          truncate-string-ellipsis "…")                ;default ellipses suck
    
    (fringe-mode 0) ;;disable fringe
    (global-subword-mode 1) ;;navigate through Camel Case words
    (tool-bar-mode +1) ;;re-enable the toolbar
    (global-so-long-mode -1) ;;i almost never want this on
    
    ;; emacs29 fixes
    (general-auto-unbind-keys :off)
    (remove-hook 'doom-after-init-modules-hook #'general-auto-unbind-keys)
    

    There’s issues with emacs flickering on mac (and sometimes wayland). This should fix it

    Emacs Lisp
    #
    (add-to-list 'default-frame-alist '(inhibit-double-buffering . t))
    

    Instead of fundamental mode, lisp-interaction-mode seems much more useful

    Emacs Lisp
    #
    (setq doom-scratch-initial-major-mode 'lisp-interaction-mode)
    

    Ask where to open splits

    elisp
    #
    (setq evil-vsplit-window-right t
          evil-split-window-below t)
    

    …and open a buffer for it

    elisp
    #
    (defadvice! prompt-for-buffer (&rest _)
      :after '(evil-window-split evil-window-vsplit)
      (consult-buffer))
    

    The default bindings of doom are pretty good. I’m not so good with motions though, so lets make life easier with avy

    elisp
    #
    (map! :leader
          :desc "hop to word" "w w" #'avy-goto-word-or-subword-1)
    (map! :leader
          :desc "hop to word" "w W" #'avy-goto-char-2)
    (map! :leader
          :desc "hop to line"
          "l" #'avy-goto-line)
    

    I also fine ; more intuitive than : for entering command mode

    elisp
    #
    (after! evil
      (map! :nmv ";" #'evil-ex))
    

    When im doing regexes, its usually with /g anyways, lets make that the default

    elisp
    #
    (after! evil
      (setq evil-ex-substitute-global t     ; I like my s/../.. to by global by default
            evil-move-cursor-back nil       ; Don't move the block cursor when toggling insert mode
            evil-kill-on-visual-paste nil)) ; Don't put overwritten text in the kill ring
    

    Doom looks much cleaner with the dividers removed. Not sure why it isn’t the default honestly

    elisp
    #
    (custom-set-faces!
      `(vertical-border :background ,(doom-color 'bg) :foreground ,(doom-color 'bg)))
    

    I don’t like seeing the cursorline, especially while writing. Lets disable that

    elisp
    #
    (remove-hook 'doom-first-buffer-hook #'global-hl-line-mode)
    

    Doom has a weird bug with emacs-plus where the cursor will just turn white on a light theme. Lets fix that.

    elisp
    #
    (defadvice! fix-+evil-default-cursor-fn ()
      :override #'+evil-default-cursor-fn
      (evil-set-cursor-color (face-background 'cursor)))
    (defadvice! fix-+evil-emacs-cursor-fn ()
      :override #'+evil-emacs-cursor-fn
      (evil-set-cursor-color (face-foreground 'warning)))
    

    I like a bit of padding, both inside and outside, and lets make the line spacing comfier

    elisp
    #
    (use-package frame
      :config
      (setq-default default-frame-alist
                    (append (list
                    '(internal-border-width . 24)
                    '(left-fringe    . 0)
                    '(right-fringe   . 0)
                    '(tool-bar-lines . 0)
                    '(menu-bar-lines . 0)
                    '(line-spacing . 0.24)
                    '(vertical-scroll-bars . nil))))
      (setq-default window-resize-pixelwise t)
      (setq-default frame-resize-pixelwise t))
    
  10. Selectric NK-Creams mode

    Instead of using the regular selectric-mode, I modified it with a few notable tweaks, mainly:

    1. Support for EVIL mode
    2. It uses NK Cream sounds instead of the typewritter ones

    The samples used here are taken from monketype, but heres a similar board

    elisp
    #
    (use-package! selectric-mode
      :commands selectric-mode)
    

5.1.5. Visual configuration

  1. Treesitter

    Nvim-treesitter is based on three interlocking features: language parsers, queries, and modules, where modules provide features – e.g., highlighting – based on queries for syntax objects extracted from a given buffer by language parsers. Allowing this to work in doom will reduce the lag introduced by fontlock as well as improve textobjects.

    Since I use an apple silicon mac, I prefer if nix handles compiling the parsers for me

    elisp
    #
    ;; (use-package! tree-sitter
    ;;   :config
    ;;   (cl-pushnew (expand-file-name "~/.config/tree-sitter") tree-sitter-load-path)
    ;;   (require 'tree-sitter-langs)
    ;;   (global-tree-sitter-mode)
    ;;   (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode))
    
  2. Modeline

    Doom modeline already looks good, but it can be better. Lets add some icons, the battery status, and make sure we don’t lose track of time

    elisp
    #
    (after! doom-modeline
      (setq evil-normal-state-tag "<λ>"
            evil-insert-state-tag "<I>"
            evil-visual-state-tag "<V>"
            evil-motion-state-tag "<M>"
            evil-emacs-state-tag "<EMACS>")
    
      (setq doom-modeline-modal-icon nil
            doom-modeline-major-mode-icon t
            doom-modeline-major-mode-color-icon t
            doom-modeline-continuous-word-count-modes '(markdown-mode gfm-mode org-mode)
            doom-modeline-buffer-encoding nil
            inhibit-compacting-font-caches t
            find-file-visit-truename t)
    
      (custom-set-faces!
        '(doom-modeline-evil-insert-state :inherit doom-modeline-urgent)
        '(doom-modeline-evil-visual-state :inherit doom-modeline-warning)
        '(doom-modeline-evil-normal-state :inherit doom-modeline-buffer-path))
    
      (setq doom-modeline-enable-word-count t))          ;Show word count
    
  3. Centaur tabs

    There isn’t much of a point having tabs when you only have one buffer open. This checks the number of tabs, and hides them if theres only one left

    elisp
    #
    (defun centaur-tabs-get-total-tab-length ()
      (length (centaur-tabs-tabs (centaur-tabs-current-tabset))))
    
    (defun centaur-tabs-hide-on-window-change ()
      (run-at-time nil nil
                   (lambda ()
                     (centaur-tabs-hide-check (centaur-tabs-get-total-tab-length)))))
    
    (defun centaur-tabs-hide-check (len)
      (shut-up
        (cond
         ((and (= len 1) (not (centaur-tabs-local-mode))) (call-interactively #'centaur-tabs-local-mode))
         ((and (>= len 2) (centaur-tabs-local-mode)) (call-interactively #'centaur-tabs-local-mode)))))
    

    I also like to have icons with my tabs.

    elisp
    #
    (after! centaur-tabs
      (centaur-tabs-mode -1)
      (centaur-tabs-headline-match)
      (centaur-tabs-change-fonts "Liga SFMono Nerd Font" 150)
    
      (setq centaur-tabs-style "wave"
            centaur-tabs-set-icons t
            centaur-tabs-set-bar 'nil
            centaur-tabs-gray-out-icons 'buffer
            centaur-tabs-height  30
            centaur-tabs-close-button ""
            centaur-tabs-modified-marker nil
            centaur-tabs-show-navigation-buttons nil
            centaur-tabs-show-new-tab-button nil
            centaur-tabs-down-tab-text " ✦"
            centaur-tabs-backward-tab-text " ⏴ "
            centaur-tabs-forward-tab-text " ⏵ ")
    
      (custom-set-faces!
        `(tab-line :background ,(doom-color 'base1) :foreground ,(doom-color 'base1))
        `(centaur-tabs-default :background ,(doom-color 'base1) :foreground ,(doom-color 'base1))
        `(centaur-tabs-active-bar-face :background ,(doom-color 'base1) :foreground ,(doom-color 'base1))
        `(centaur-tabs-unselected-modified :background ,(doom-color 'base1) :foreground ,(doom-color 'fg))
        `(centaur-tabs-unselected :background ,(doom-color 'base1) :foreground ,(doom-color 'base4))
        `(centaur-tabs-selected-modified :background ,(doom-color 'bg) :foreground ,(doom-color 'fg))
        `(centaur-tabs-selected :background ,(doom-color 'bg) :foreground ,(doom-color 'blue)))
    
      (add-hook 'window-configuration-change-hook 'centaur-tabs-hide-on-window-change))
    
  4. Vertico

    For marginalia (vertico), lets use relative time, along with some other things

    elisp
    #
    (after! marginalia
      (setq marginalia-censor-variables nil)
    
      (defadvice! +marginalia--anotate-local-file-colorful (cand)
        "Just a more colourful version of `marginalia--anotate-local-file'."
        :override #'marginalia--annotate-local-file
        (when-let (attrs (file-attributes (substitute-in-file-name
                                           (marginalia--full-candidate cand))
                                          'integer))
          (marginalia--fields
           ((marginalia--file-owner attrs)
            :width 12 :face 'marginalia-file-owner)
           ((marginalia--file-modes attrs))
           ((+marginalia-file-size-colorful (file-attribute-size attrs))
            :width 7)
           ((+marginalia--time-colorful (file-attribute-modification-time attrs))
            :width 12))))
    
      (defun +marginalia--time-colorful (time)
        (let* ((seconds (float-time (time-subtract (current-time) time)))
               (color (doom-blend
                       (face-attribute 'marginalia-date :foreground nil t)
                       (face-attribute 'marginalia-documentation :foreground nil t)
                       (/ 1.0 (log (+ 3 (/ (+ 1 seconds) 345600.0)))))))
          ;; 1 - log(3 + 1/(days + 1)) % grey
          (propertize (marginalia--time time) 'face (list :foreground color))))
    
      (defun +marginalia-file-size-colorful (size)
        (let* ((size-index (/ (log10 (+ 1 size)) 7.0))
               (color (if (< size-index 10000000) ; 10m
                          (doom-blend 'orange 'green size-index)
                        (doom-blend 'red 'orange (- size-index 1)))))
          (propertize (file-size-human-readable size) 'face (list :foreground color)))))
    
  5. Treemacs

    Lets theme treemacs while we’re at it

    elisp
    #
    (setq treemacs-width 25)
    (setq doom-themes-treemacs-theme "doom-colors")
    
  6. Emojis

    Disable some annoying emojis

    elisp
    #
    (defvar emojify-disabled-emojis
      '(;; Org
        "◼" "☑" "☸" "⚙" "⏩" "⏪" "⬆" "⬇" "❓"
        ;; Terminal powerline
        "✔"
        ;; Box drawing
        "▶" "◀")
      "Characters that should never be affected by `emojify-mode'.")
    
    (defadvice! emojify-delete-from-data ()
      "Ensure `emojify-disabled-emojis' don't appear in `emojify-emojis'."
      :after #'emojify-set-emoji-data
      (dolist (emoji emojify-disabled-emojis)
        (remhash emoji emojify-emojis)))
    
    (add-hook! '(mu4e-compose-mode org-msg-edit-mode) (emoticon-to-emoji 1))
    
  7. Splash screen

    Emacs can render an image as the splash screen, and the emacs logo looks pretty cool Now we just make it theme-appropriate, and resize with the frame.

    Emacs Lisp
    #
    (defvar fancy-splash-image-template
      (expand-file-name "misc/splash-images/emacs-e-template.svg" doom-private-dir)
      "Default template svg used for the splash image, with substitutions from ")
    
    (defvar fancy-splash-sizes
      `((:height 300 :min-height 50 :padding (0 . 2))
        (:height 250 :min-height 42 :padding (2 . 4))
        (:height 200 :min-height 35 :padding (3 . 3))
        (:height 150 :min-height 28 :padding (3 . 3))
        (:height 100 :min-height 20 :padding (2 . 2))
        (:height 75  :min-height 15 :padding (2 . 1))
        (:height 50  :min-height 10 :padding (1 . 0))
        (:height 1   :min-height 0  :padding (0 . 0)))
      "list of plists with the following properties
      :height the height of the image
      :min-height minimum `frame-height' for image
      :padding `+doom-dashboard-banner-padding' (top . bottom) to apply
      :template non-default template file
      :file file to use instead of template")
    
    (defvar fancy-splash-template-colours
      '(("$colour1" . keywords) ("$colour2" . type) ("$colour3" . base5) ("$colour4" . base8))
      "list of colour-replacement alists of the form (\"$placeholder\" . 'theme-colour) which applied the template")
    
    (unless (file-exists-p (expand-file-name "theme-splashes" doom-cache-dir))
      (make-directory (expand-file-name "theme-splashes" doom-cache-dir) t))
    
    (defun fancy-splash-filename (theme-name height)
      (expand-file-name (concat (file-name-as-directory "theme-splashes")
                                theme-name
                                "-" (number-to-string height) ".svg")
                        doom-cache-dir))
    
    (defun fancy-splash-clear-cache ()
      "Delete all cached fancy splash images"
      (interactive)
      (delete-directory (expand-file-name "theme-splashes" doom-cache-dir) t)
      (message "Cache cleared!"))
    
    (defun fancy-splash-generate-image (template height)
      "Read TEMPLATE and create an image if HEIGHT with colour substitutions as
       described by `fancy-splash-template-colours' for the current theme"
      (with-temp-buffer
        (insert-file-contents template)
        (re-search-forward "$height" nil t)
        (replace-match (number-to-string height) nil nil)
        (dolist (substitution fancy-splash-template-colours)
          (goto-char (point-min))
          (while (re-search-forward (car substitution) nil t)
            (replace-match (doom-color (cdr substitution)) nil nil)))
        (write-region nil nil
                      (fancy-splash-filename (symbol-name doom-theme) height) nil nil)))
    
    (defun fancy-splash-generate-images ()
      "Perform `fancy-splash-generate-image' in bulk"
      (dolist (size fancy-splash-sizes)
        (unless (plist-get size :file)
          (fancy-splash-generate-image (or (plist-get size :template)
                                           fancy-splash-image-template)
                                       (plist-get size :height)))))
    
    (defun ensure-theme-splash-images-exist (&optional height)
      (unless (file-exists-p (fancy-splash-filename
                              (symbol-name doom-theme)
                              (or height
                                  (plist-get (car fancy-splash-sizes) :height))))
        (fancy-splash-generate-images)))
    
    (defun get-appropriate-splash ()
      (let ((height (frame-height)))
        (cl-some (lambda (size) (when (>= height (plist-get size :min-height)) size))
                 fancy-splash-sizes)))
    
    (setq fancy-splash-last-size nil)
    (setq fancy-splash-last-theme nil)
    (defun set-appropriate-splash (&rest _)
      (let ((appropriate-image (get-appropriate-splash)))
        (unless (and (equal appropriate-image fancy-splash-last-size)
                     (equal doom-theme fancy-splash-last-theme)))
        (unless (plist-get appropriate-image :file)
          (ensure-theme-splash-images-exist (plist-get appropriate-image :height)))
        (setq fancy-splash-image
              (or (plist-get appropriate-image :file)
                  (fancy-splash-filename (symbol-name doom-theme) (plist-get appropriate-image :height))))
        (setq +doom-dashboard-banner-padding (plist-get appropriate-image :padding))
        (setq fancy-splash-last-size appropriate-image)
        (setq fancy-splash-last-theme doom-theme)
        (+doom-dashboard-reload)))
    
    (add-hook 'window-size-change-functions #'set-appropriate-splash)
    (add-hook 'doom-load-theme-hook #'set-appropriate-splash)
    

    Lets add a little phrase in there as well

    elisp
    #
    (defvar splash-phrase-source-folder
      (expand-file-name "misc/splash-phrases" doom-private-dir)
      "A folder of text files with a fun phrase on each line.")
    
    (defvar splash-phrase-sources
      (let* ((files (directory-files splash-phrase-source-folder nil "\\.txt\\'"))
             (sets (delete-dups (mapcar
                                 (lambda (file)
                                   (replace-regexp-in-string "\\(?:-[0-9]+-\\w+\\)?\\.txt" "" file))
                                 files))))
        (mapcar (lambda (sset)
                  (cons sset
                        (delq nil (mapcar
                                   (lambda (file)
                                     (when (string-match-p (regexp-quote sset) file)
                                       file))
                                   files))))
                sets))
      "A list of cons giving the phrase set name, and a list of files which contain phrase components.")
    
    (defvar splash-phrase-set
      (nth (random (length splash-phrase-sources)) (mapcar #'car splash-phrase-sources))
      "The default phrase set. See `splash-phrase-sources'.")
    
    (defun splase-phrase-set-random-set ()
      "Set a new random splash phrase set."
      (interactive)
      (setq splash-phrase-set
            (nth (random (1- (length splash-phrase-sources)))
                 (cl-set-difference (mapcar #'car splash-phrase-sources) (list splash-phrase-set))))
      (+doom-dashboard-reload t))
    
    (defvar splase-phrase--cache nil)
    
    (defun splash-phrase-get-from-file (file)
      "Fetch a random line from FILE."
      (let ((lines (or (cdr (assoc file splase-phrase--cache))
                       (cdar (push (cons file
                                         (with-temp-buffer
                                           (insert-file-contents (expand-file-name file splash-phrase-source-folder))
                                           (split-string (string-trim (buffer-string)) "\n")))
                                   splase-phrase--cache)))))
        (nth (random (length lines)) lines)))
    
    (defun splash-phrase (&optional set)
      "Construct a splash phrase from SET. See `splash-phrase-sources'."
      (mapconcat
       #'splash-phrase-get-from-file
       (cdr (assoc (or set splash-phrase-set) splash-phrase-sources))
       " "))
    
    (defun doom-dashboard-phrase ()
      "Get a splash phrase, flow it over multiple lines as needed, and make fontify it."
      (mapconcat
       (lambda (line)
         (+doom-dashboard--center
          +doom-dashboard--width
          (with-temp-buffer
            (insert-text-button
             line
             'action
             (lambda (_) (+doom-dashboard-reload t))
             'face 'doom-dashboard-menu-title
             'mouse-face 'doom-dashboard-menu-title
             'help-echo "Random phrase"
             'follow-link t)
            (buffer-string))))
       (split-string
        (with-temp-buffer
          (insert (splash-phrase))
          (setq fill-column (min 70 (/ (* 2 (window-width)) 3)))
          (fill-region (point-min) (point-max))
          (buffer-string))
        "\n")
       "\n"))
    
    (defadvice! doom-dashboard-widget-loaded-with-phrase ()
      :override #'doom-dashboard-widget-loaded
      (setq line-spacing 0.2)
      (insert
       "\n\n"
       (propertize
        (+doom-dashboard--center
         +doom-dashboard--width
         (doom-display-benchmark-h 'return))
        'face 'doom-dashboard-loaded)
       "\n"
       (doom-dashboard-phrase)
       "\n"))
    

    Lastly, the doom dashboard “useful commands” are no longer useful to me. So, we’ll disable them and then for a particularly clean look disable the modeline, then also hide the cursor.

    Emacs Lisp
    #
    (remove-hook '+doom-dashboard-functions #'doom-dashboard-widget-shortmenu)
    (add-hook! '+doom-dashboard-mode-hook (hide-mode-line-mode 1) (hl-line-mode -1))
    (setq-hook! '+doom-dashboard-mode-hook evil-normal-state-cursor (list nil))
    
  8. Writeroom

    For starters, I think Doom is a bit over-zealous when zooming in

    Emacs Lisp
    #
    (setq +zen-text-scale 0.8)
    

    Then, when using Org it would be nice to make a number of other aesthetic tweaks. Namely:

    • Use a serif-ed variable-pitch font
    • Hiding headline leading stars
    • Using fleurons as headline bullets
    • Hiding line numbers
    • Removing outline indentation
    • Centering the text
    • Disabling doom-modeline
    elisp
    #
    (defvar +zen-serif-p t
      "Whether to use a serifed font with `mixed-pitch-mode'.")
    (after! writeroom-mode
      (defvar-local +zen--original-org-indent-mode-p nil)
      (defvar-local +zen--original-mixed-pitch-mode-p nil)
      (defun +zen-enable-mixed-pitch-mode-h ()
        "Enable `mixed-pitch-mode' when in `+zen-mixed-pitch-modes'."
        (when (apply #'derived-mode-p +zen-mixed-pitch-modes)
          (if writeroom-mode
              (progn
                (setq +zen--original-mixed-pitch-mode-p mixed-pitch-mode)
                (funcall (if +zen-serif-p #'mixed-pitch-serif-mode #'mixed-pitch-mode) 1))
            (funcall #'mixed-pitch-mode (if +zen--original-mixed-pitch-mode-p 1 -1)))))
      (pushnew! writeroom--local-variables
                'display-line-numbers
                'visual-fill-column-width
                'org-adapt-indentation
                'org-superstar-headline-bullets-list
                'org-superstar-remove-leading-stars)
      (add-hook 'writeroom-mode-enable-hook
                (defun +zen-prose-org-h ()
                  "Reformat the current Org buffer appearance for prose."
                  (when (eq major-mode 'org-mode)
                    (setq display-line-numbers nil
                          visual-fill-column-width 60
                          org-adapt-indentation nil)
                    (when (featurep 'org-superstar)
                      (setq-local org-superstar-headline-bullets-list '("◉" "○" "✸" "✿" "✤" "✜" "◆" "▶")
                                  org-superstar-remove-leading-stars t)
                      (org-superstar-restart))               (setq
                     +zen--original-org-indent-mode-p org-indent-mode)
                    (org-indent-mode -1))))
      (add-hook! 'writeroom-mode-hook
        (if writeroom-mode
            (add-hook 'post-command-hook #'recenter nil t)
          (remove-hook 'post-command-hook #'recenter t)))
      (add-hook 'writeroom-mode-enable-hook #'doom-disable-line-numbers-h)
      (add-hook 'writeroom-mode-disable-hook #'doom-enable-line-numbers-h)
      (add-hook 'writeroom-mode-disable-hook
                (defun +zen-nonprose-org-h ()
                  "Reverse the effect of `+zen-prose-org'."
                  (when (eq major-mode 'org-mode)
                    (when (featurep 'org-superstar)
                      (org-superstar-restart))
                    (when +zen--original-org-indent-mode-p (org-indent-mode 1))))))
    
  9. Font Display

    Mixed pitch is great. As is +org-pretty-mode, let’s use them.

    Emacs Lisp
    #
    (add-hook 'org-mode-hook #'+org-pretty-mode)
    

    However, the subscripts (and superscripts) are confusing with latex fragments, so lets turn those off

    Emacs Lisp
    #
    (setq org-pretty-entities-include-sub-superscripts nil)
    

    Let’s make headings a bit bigger

    Emacs Lisp
    #
    (custom-set-faces!
      '(org-document-title :height 1.2)
      '(outline-1 :weight extra-bold :height 1.25)
      '(outline-2 :weight bold :height 1.15)
      '(outline-3 :weight bold :height 1.12)
      '(outline-4 :weight semi-bold :height 1.09)
      '(outline-5 :weight semi-bold :height 1.06)
      '(outline-6 :weight semi-bold :height 1.03)
      '(outline-8 :weight semi-bold)
      '(outline-9 :weight semi-bold))
    

    It seems reasonable to have deadlines in the error face when they’re passed.

    Emacs Lisp
    #
    (setq org-agenda-deadline-faces
          '((1.0 . error)
            (1.0 . org-warning)
            (0.5 . org-upcoming-deadline)
            (0.0 . org-upcoming-distant-deadline)))
    

    We can then have quote blocks stand out a bit more by making them italic.

    Emacs Lisp
    #
    (setq org-fontify-quote-and-verse-blocks t)
    
    Emacs Lisp
    #
    (use-package! org-appear
      :hook (org-mode . org-appear-mode)
      :config
      (setq org-appear-autoemphasis t
            org-appear-autosubmarkers t
            org-appear-autolinks nil)
      (run-at-time nil nil #'org-appear--set-elements))
    

    Org files can be rather nice to look at, particularly with some of the customizations here. This comes at a cost however, expensive font-lock. Feeling like you’re typing through molasses in large files is no fun, but there is a way I can defer font-locking when typing to make the experience more responsive.

    Emacs Lisp
    #
    (defun locally-defer-font-lock ()
      "Set jit-lock defer and stealth, when buffer is over a certain size."
      (when (> (buffer-size) 50000)
        (setq-local jit-lock-defer-time 0.05
                    jit-lock-stealth-time 1)))
    
    (add-hook 'org-mode-hook #'locally-defer-font-lock)
    
    1. Fontifying inline src blocks

      Org does lovely things with #+begin_src blocks, like using font-lock for language’s major-mode behind the scenes and pulling out the lovely colourful results. By contrast, inline src_ blocks are somewhat neglected.

      I am not the first person to feel this way, thankfully others have taken to stackexchange to voice their desire for inline src fontification. I was going to steal their work, but unfortunately they didn’t perform true source code fontification, but simply applied the org-code face to the content.

      We can do better than that, and we shall! Using org-src-font-lock-fontify-block we can apply language-appropriate syntax highlighting. Then, continuing on to {{{results(...)}}} , it can have the org-block face applied to match, and then the value-surrounding constructs hidden by mimicking the behaviour of prettify-symbols-mode.

      Emacs Lisp
      #
      (defvar org-prettify-inline-results t
        "Whether to use (ab)use prettify-symbols-mode on {{{results(...)}}}.
      Either t or a cons cell of strings which are used as substitutions
      for the start and end of inline results, respectively.")
      
      (defvar org-fontify-inline-src-blocks-max-length 200
        "Maximum content length of an inline src block that will be fontified.")
      
      (defun org-fontify-inline-src-blocks (limit)
        "Try to apply `org-fontify-inline-src-blocks-1'."
        (condition-case nil
            (org-fontify-inline-src-blocks-1 limit)
          (error (message "Org mode fontification error in %S at %d"
                          (current-buffer)
                          (line-number-at-pos)))))
      
      (defun org-fontify-inline-src-blocks-1 (limit)
        "Fontify inline src_LANG blocks, from `point' up to LIMIT."
        (let ((case-fold-search t)
              (initial-point (point)))
          (while (re-search-forward "\\_<src_\\([^ \t\n[{]+\\)[{[]?" limit t) ; stolen from `org-element-inline-src-block-parser'
            (let ((beg (match-beginning 0))
                  pt
                  (lang-beg (match-beginning 1))
                  (lang-end (match-end 1)))
              (remove-text-properties beg lang-end '(face nil))
              (font-lock-append-text-property lang-beg lang-end 'face 'org-meta-line)
              (font-lock-append-text-property beg lang-beg 'face 'shadow)
              (font-lock-append-text-property beg lang-end 'face 'org-block)
              (setq pt (goto-char lang-end))
              ;; `org-element--parse-paired-brackets' doesn't take a limit, so to
              ;; prevent it searching the entire rest of the buffer we temporarily
              ;; narrow the active region.
              (save-restriction
                (narrow-to-region beg (min (point-max) limit (+ lang-end org-fontify-inline-src-blocks-max-length)))
                (when (ignore-errors (org-element--parse-paired-brackets ?\[))
                  (remove-text-properties pt (point) '(face nil))
                  (font-lock-append-text-property pt (point) 'face 'org-block)
                  (setq pt (point)))
                (when (ignore-errors (org-element--parse-paired-brackets ?\{))
                  (remove-text-properties pt (point) '(face nil))
                  (font-lock-append-text-property pt (1+ pt) 'face '(org-block shadow))
                  (unless (= (1+ pt) (1- (point)))
                    (if org-src-fontify-natively
                        (org-src-font-lock-fontify-block (buffer-substring-no-properties lang-beg lang-end) (1+ pt) (1- (point)))
                      (font-lock-append-text-property (1+ pt) (1- (point)) 'face 'org-block)))
                  (font-lock-append-text-property (1- (point)) (point) 'face '(org-block shadow))
                  (setq pt (point))))
              (when (and org-prettify-inline-results (re-search-forward "\\= {{{results(" limit t))
                (font-lock-append-text-property pt (1+ pt) 'face 'org-block)
                (goto-char pt))))
          (when org-prettify-inline-results
            (goto-char initial-point)
            (org-fontify-inline-src-results limit))))
      
      (defun org-fontify-inline-src-results (limit)
        (while (re-search-forward "{{{results(\\(.+?\\))}}}" limit t)
          (remove-list-of-text-properties (match-beginning 0) (point)
                                          '(composition
                                            prettify-symbols-start
                                            prettify-symbols-end))
          (font-lock-append-text-property (match-beginning 0) (match-end 0) 'face 'org-block)
          (let ((start (match-beginning 0)) (end (match-beginning 1)))
            (with-silent-modifications
              (compose-region start end (if (eq org-prettify-inline-results t) "⟨" (car org-prettify-inline-results)))
              (add-text-properties start end `(prettify-symbols-start ,start prettify-symbols-end ,end))))
          (let ((start (match-end 1)) (end (point)))
            (with-silent-modifications
              (compose-region start end (if (eq org-prettify-inline-results t) "⟩" (cdr org-prettify-inline-results)))
              (add-text-properties start end `(prettify-symbols-start ,start prettify-symbols-end ,end))))))
      
      (defun org-fontify-inline-src-blocks-enable ()
        "Add inline src fontification to font-lock in Org.
      Must be run as part of `org-font-lock-set-keywords-hook'."
        (setq org-font-lock-extra-keywords
              (append org-font-lock-extra-keywords '((org-fontify-inline-src-blocks)))))
      
      (add-hook 'org-font-lock-set-keywords-hook #'org-fontify-inline-src-blocks-enable)
      
  10. Symbols

    Firstly, I dislike the default stars for org-mode, so lets improve that

    Emacs Lisp
    #
    ;;make bullets look better
    (after! org-superstar
      (setq org-superstar-headline-bullets-list '("◉" "○" "✸" "✿" "✤" "✜" "◆" "▶")
            org-superstar-prettify-item-bullets t ))
    

    I also want to hide leading stars, since they feel redundant

    elisp
    #
    (setq org-ellipsis " ▾ "
          org-hide-leading-stars t
          org-priority-highest ?A
          org-priority-lowest ?E
          org-priority-faces
          '((?A . 'all-the-icons-red)
            (?B . 'all-the-icons-orange)
            (?C . 'all-the-icons-yellow)
            (?D . 'all-the-icons-green)
            (?E . 'all-the-icons-blue)))
    

    Lastly, lets add some ligatures for some org mode stuff

    elisp
    #
    (appendq! +ligatures-extra-symbols
              `(:checkbox      "☐"
                :pending       "◼"
                :checkedbox    "☑"
                :list_property "∷"
                :em_dash       "—"
                :ellipses      "…"
                :arrow_right   "→"
                :arrow_left    "←"
                :property      "☸"
                :options       "⌥"
                :startup       "⏻"
                :html_head     "🅷"
                :html          "🅗"
                :latex_class   "🄻"
                :latex_header  "🅻"
                :beamer_header "🅑"
                :latex         "🅛"
                :attr_latex    "🄛"
                :attr_html     "🄗"
                :attr_org      "⒪"
                :begin_quote   "❝"
                :end_quote     "❞"
                :caption       "☰"
                :header        "›"
                :begin_export  "⏩"
                :end_export    "⏪"
                :properties    "⚙"
                :end           "∎"
                :priority_a   ,(propertize "⚑" 'face 'all-the-icons-red)
                :priority_b   ,(propertize "⬆" 'face 'all-the-icons-orange)
                :priority_c   ,(propertize "■" 'face 'all-the-icons-yellow)
                :priority_d   ,(propertize "⬇" 'face 'all-the-icons-green)
                :priority_e   ,(propertize "❓" 'face 'all-the-icons-blue)))
    (set-ligatures! 'org-mode
      :merge t
      :checkbox      "[ ]"
      :pending       "[-]"
      :checkedbox    "[X]"
      :list_property "::"
      :em_dash       "---"
      :ellipsis      "..."
      :arrow_right   "->"
      :arrow_left    "<-"
      :title         "#+title:"
      :subtitle      "#+subtitle:"
      :author        "#+author:"
      :date          "#+date:"
      :property      "#+property:"
      :options       "#+options:"
      :startup       "#+startup:"
      :macro         "#+macro:"
      :html_head     "#+html_head:"
      :html          "#+html:"
      :latex_class   "#+latex_class:"
      :latex_header  "#+latex_header:"
      :beamer_header "#+beamer_header:"
      :latex         "#+latex:"
      :attr_latex    "#+attr_latex:"
      :attr_html     "#+attr_html:"
      :attr_org      "#+attr_org:"
      :begin_quote   "#+begin_quote"
      :end_quote     "#+end_quote"
      :caption       "#+caption:"
      :header        "#+header:"
      :begin_export  "#+begin_export"
      :end_export    "#+end_export"
      :results       "#+RESULTS:"
      :property      ":PROPERTIES:"
      :end           ":END:"
      :priority_a    "[#A]"
      :priority_b    "[#B]"
      :priority_c    "[#C]"
      :priority_d    "[#D]"
      :priority_e    "[#E]")
    (plist-put +ligatures-extra-symbols :name "⁍")
    

    Lets also add a function that makes it easy to convert from upper to lowercase, since the ligatures don’t work with Uppercase (I can make them work, but lowercase looks better anyways)

    elisp
    #
    (defun org-syntax-convert-keyword-case-to-lower ()
      "Convert all #+KEYWORDS to #+keywords."
      (interactive)
      (save-excursion
        (goto-char (point-min))
        (let ((count 0)
              (case-fold-search nil))
          (while (re-search-forward "^[ \t]*#\\+[A-Z_]+" nil t)
            (unless (s-matches-p "RESULTS" (match-string 0))
              (replace-match (downcase (match-string 0)) t)
              (setq count (1+ count))))
          (message "Replaced %d occurances" count))))
    
  11. Keycast

    Its nice for demonstrations

    elisp
    #
    (use-package! keycast
      :commands keycast-mode
      :config
      (define-minor-mode keycast-mode
        "Show current command and its key binding in the mode line."
        :global t
        (if keycast-mode
            (progn
              (add-hook 'pre-command-hook 'keycast--update t)
              (add-to-list 'global-mode-string '("" mode-line-keycast " ")))
          (remove-hook 'pre-command-hook 'keycast--update)
          (setq global-mode-string (remove '("" mode-line-keycast " ") global-mode-string))))
      (custom-set-faces!
        '(keycast-command :inherit doom-modeline-debug
                          :height 1.0)
        '(keycast-key :inherit custom-modified
                      :height 1.0
                      :weight bold)))
    
  12. Transparency

    I’m not too big of a fan of transparency, but some people like it. You can use this little function to toggle it now. On C-c t inactive windows will dim (85% transparency) and focused windows remain opaque

    elisp
    #
     (defun toggle-transparency ()
       (interactive)
       (let ((alpha (frame-parameter nil 'alpha)))
         (set-frame-parameter
          nil 'alpha
          (if (eql (cond ((numberp alpha) alpha)
                         ((numberp (cdr alpha)) (cdr alpha))
                         ;; Also handle undocumented (<active> <inactive>) form.
                         ((numberp (cadr alpha)) (cadr alpha)))
                   100)
              '(100 . 85) '(100 . 100)))))
     (global-set-key (kbd "C-c t") 'toggle-transparency)
    
  13. RSS

    RSS is a nice simple way of getting my news. Lets set that up

    elisp
    #
    (map! :map elfeed-search-mode-map
          :after elfeed-search
          [remap kill-this-buffer] "q"
          [remap kill-buffer] "q"
          :n doom-leader-key nil
          :n "q" #'+rss/quit
          :n "e" #'elfeed-update
          :n "r" #'elfeed-search-untag-all-unread
          :n "u" #'elfeed-search-tag-all-unread
          :n "s" #'elfeed-search-live-filter
          :n "RET" #'elfeed-search-show-entry
          :n "p" #'elfeed-show-pdf
          :n "+" #'elfeed-search-tag-all
          :n "-" #'elfeed-search-untag-all
          :n "S" #'elfeed-search-set-filter
          :n "b" #'elfeed-search-browse-url
          :n "y" #'elfeed-search-yank)
    (map! :map elfeed-show-mode-map
          :after elfeed-show
          [remap kill-this-buffer] "q"
          [remap kill-buffer] "q"
          :n doom-leader-key nil
          :nm "q" #'+rss/delete-pane
          :nm "o" #'ace-link-elfeed
          :nm "RET" #'org-ref-elfeed-add
          :nm "n" #'elfeed-show-next
          :nm "N" #'elfeed-show-prev
          :nm "p" #'elfeed-show-pdf
          :nm "+" #'elfeed-show-tag
          :nm "-" #'elfeed-show-untag
          :nm "s" #'elfeed-show-new-live-search
          :nm "y" #'elfeed-show-yank)
    
    (after! elfeed-search
      (set-evil-initial-state! 'elfeed-search-mode 'normal))
    (after! elfeed-show-mode
      (set-evil-initial-state! 'elfeed-show-mode   'normal))
    
    (after! evil-snipe
      (push 'elfeed-show-mode   evil-snipe-disabled-modes)
      (push 'elfeed-search-mode evil-snipe-disabled-modes))
    
     (after! elfeed
    
      (elfeed-org)
      (use-package! elfeed-link)
    
      (setq elfeed-search-filter "@1-week-ago +unread"
            elfeed-search-print-entry-function '+rss/elfeed-search-print-entry
            elfeed-search-title-min-width 80
            elfeed-show-entry-switch #'pop-to-buffer
            elfeed-show-entry-delete #'+rss/delete-pane
            elfeed-show-refresh-function #'+rss/elfeed-show-refresh--better-style
            shr-max-image-proportion 0.6)
    
      (add-hook! 'elfeed-show-mode-hook (hide-mode-line-mode 1))
      (add-hook! 'elfeed-search-update-hook #'hide-mode-line-mode)
    
      (defface elfeed-show-title-face '((t (:weight ultrabold :slant italic :height 1.5)))
        "title face in elfeed show buffer"
        :group 'elfeed)
      (defface elfeed-show-author-face `((t (:weight light)))
        "title face in elfeed show buffer"
        :group 'elfeed)
      (set-face-attribute 'elfeed-search-title-face nil
                          :foreground 'nil
                          :weight 'light)
    
      (defadvice! +rss-elfeed-wrap-h-nicer ()
        "Enhances an elfeed entry's readability by wrapping it to a width of
    `fill-column' and centering it with `visual-fill-column-mode'."
        :override #'+rss-elfeed-wrap-h
        (setq-local truncate-lines nil
                    shr-width 120
                    visual-fill-column-center-text t
                    default-text-properties '(line-height 1.1))
        (let ((inhibit-read-only t)
              (inhibit-modification-hooks t))
          (visual-fill-column-mode)
          ;; (setq-local shr-current-font '(:family "Merriweather" :height 1.2))
          (set-buffer-modified-p nil)))
    
      (defun +rss/elfeed-search-print-entry (entry)
        "Print ENTRY to the buffer."
        (let* ((elfeed-goodies/tag-column-width 40)
               (elfeed-goodies/feed-source-column-width 30)
               (title (or (elfeed-meta entry :title) (elfeed-entry-title entry) ""))
               (title-faces (elfeed-search--faces (elfeed-entry-tags entry)))
               (feed (elfeed-entry-feed entry))
               (feed-title
                (when feed
                  (or (elfeed-meta feed :title) (elfeed-feed-title feed))))
               (tags (mapcar #'symbol-name (elfeed-entry-tags entry)))
               (tags-str (concat (mapconcat 'identity tags ",")))
               (title-width (- (window-width) elfeed-goodies/feed-source-column-width
                               elfeed-goodies/tag-column-width 4))
    
               (tag-column (elfeed-format-column
                            tags-str (elfeed-clamp (length tags-str)
                                                   elfeed-goodies/tag-column-width
                                                   elfeed-goodies/tag-column-width)
                            :left))
               (feed-column (elfeed-format-column
                             feed-title (elfeed-clamp elfeed-goodies/feed-source-column-width
                                                      elfeed-goodies/feed-source-column-width
                                                      elfeed-goodies/feed-source-column-width)
                             :left)))
    
          (insert (propertize feed-column 'face 'elfeed-search-feed-face) " ")
          (insert (propertize tag-column 'face 'elfeed-search-tag-face) " ")
          (insert (propertize title 'face title-faces 'kbd-help title))
          (setq-local line-spacing 0.2)))
    
      (defun +rss/elfeed-show-refresh--better-style ()
        "Update the buffer to match the selected entry, using a mail-style."
        (interactive)
        (let* ((inhibit-read-only t)
               (title (elfeed-entry-title elfeed-show-entry))
               (date (seconds-to-time (elfeed-entry-date elfeed-show-entry)))
               (author (elfeed-meta elfeed-show-entry :author))
               (link (elfeed-entry-link elfeed-show-entry))
               (tags (elfeed-entry-tags elfeed-show-entry))
               (tagsstr (mapconcat #'symbol-name tags ", "))
               (nicedate (format-time-string "%a, %e %b %Y %T %Z" date))
               (content (elfeed-deref (elfeed-entry-content elfeed-show-entry)))
               (type (elfeed-entry-content-type elfeed-show-entry))
               (feed (elfeed-entry-feed elfeed-show-entry))
               (feed-title (elfeed-feed-title feed))
               (base (and feed (elfeed-compute-base (elfeed-feed-url feed)))))
          (erase-buffer)
          (insert "\n")
          (insert (format "%s\n\n" (propertize title 'face 'elfeed-show-title-face)))
          (insert (format "%s\t" (propertize feed-title 'face 'elfeed-search-feed-face)))
          (when (and author elfeed-show-entry-author)
            (insert (format "%s\n" (propertize author 'face 'elfeed-show-author-face))))
          (insert (format "%s\n\n" (propertize nicedate 'face 'elfeed-log-date-face)))
          (when tags
            (insert (format "%s\n"
                            (propertize tagsstr 'face 'elfeed-search-tag-face))))
          ;; (insert (propertize "Link: " 'face 'message-header-name))
          ;; (elfeed-insert-link link link)
          ;; (insert "\n")
          (cl-loop for enclosure in (elfeed-entry-enclosures elfeed-show-entry)
                   do (insert (propertize "Enclosure: " 'face 'message-header-name))
                   do (elfeed-insert-link (car enclosure))
                   do (insert "\n"))
          (insert "\n")
          (if content
              (if (eq type 'html)
                  (elfeed-insert-html content base)
                (insert content))
            (insert (propertize "(empty)\n" 'face 'italic)))
          (goto-char (point-min)))))
    
    (after! elfeed-show
      (require 'url)
    
      (defvar elfeed-pdf-dir
        (expand-file-name "pdfs/"
                          (file-name-directory (directory-file-name elfeed-enclosure-default-dir))))
    
      (defvar elfeed-link-pdfs
        '(("https://www.jstatsoft.org/index.php/jss/article/view/v0\\([^/]+\\)" . "https://www.jstatsoft.org/index.php/jss/article/view/v0\\1/v\\1.pdf")
          ("http://arxiv.org/abs/\\([^/]+\\)" . "https://arxiv.org/pdf/\\1.pdf"))
        "List of alists of the form (REGEX-FOR-LINK . FORM-FOR-PDF)")
    
      (defun elfeed-show-pdf (entry)
        (interactive
         (list (or elfeed-show-entry (elfeed-search-selected :ignore-region))))
        (let ((link (elfeed-entry-link entry))
              (feed-name (plist-get (elfeed-feed-meta (elfeed-entry-feed entry)) :title))
              (title (elfeed-entry-title entry))
              (file-view-function
               (lambda (f)
                 (when elfeed-show-entry
                   (elfeed-kill-buffer))
                 (pop-to-buffer (find-file-noselect f))))
              pdf)
    
          (let ((file (expand-file-name
                       (concat (subst-char-in-string ?/ ?, title) ".pdf")
                       (expand-file-name (subst-char-in-string ?/ ?, feed-name)
                                         elfeed-pdf-dir))))
            (if (file-exists-p file)
                (funcall file-view-function file)
              (dolist (link-pdf elfeed-link-pdfs)
                (when (and (string-match-p (car link-pdf) link)
                           (not pdf))
                  (setq pdf (replace-regexp-in-string (car link-pdf) (cdr link-pdf) link))))
              (if (not pdf)
                  (message "No associated PDF for entry")
                (message "Fetching %s" pdf)
                (unless (file-exists-p (file-name-directory file))
                  (make-directory (file-name-directory file) t))
                (url-copy-file pdf file)
                (funcall file-view-function file)))))))
    
  14. Ebooks

    Kindle

    To actually read the ebooks we use nov.

    Emacs Lisp
    #
    (use-package! nov
      :mode ("\\.epub\\'" . nov-mode)
      :config
      (map! :map nov-mode-map
            :n "RET" #'nov-scroll-up)
    
      (advice-add 'nov-render-title :override #'ignore)
      (defun +nov-mode-setup ()
        (face-remap-add-relative 'variable-pitch
                                 :family "Overpass"
                                 :height 1.4
                                 :width 'semi-expanded)
        (face-remap-add-relative 'default :height 1.3)
        (setq-local line-spacing 0.2
                    next-screen-context-lines 4
                    shr-use-colors nil)
        (require 'visual-fill-column nil t)
        (setq-local visual-fill-column-center-text t
                    visual-fill-column-width 81
                    nov-text-width 80)
        (visual-fill-column-mode 1)
        (add-to-list '+lookup-definition-functions #'+lookup/dictionary-definition)
        (add-hook 'nov-mode-hook #'+nov-mode-setup)))
    
  15. Screenshot

    Testing

    Emacs Lisp
    #
    (use-package! screenshot
      :defer t)
    

5.1.6. Org

  1. Org-Mode

    Org mode is the best writing format, no contest. The defaults are more terminal-oriented, so lets make it look a little better

    Some hooks are a bit annoying, so lets make them shut up

    elisp
    #
    (defadvice! shut-up-org-problematic-hooks (orig-fn &rest args)
      :around #'org-fancy-priorities-mode
      :around #'org-superstar-mode
      (ignore-errors (apply orig-fn args)))
    

    Sadly I can’t always work in org, but I can import stuff into it!

    Emacs Lisp
    #
    (use-package! org-pandoc-import
      :after org)
    

    I prefer /org as my directory. Lets change some other defaults too

    elisp
    #
    (setq org-directory "~/org"                      ; let's put files here
          org-use-property-inheritance t              ; it's convenient to have properties inherited
          org-log-done 'time                          ; having the time a item is done sounds convenient
          org-list-allow-alphabetical t               ; have a. A. a) A) list bullets
          org-export-in-background t                  ; run export processes in external emacs process
          org-catch-invisible-edits 'smart)            ; try not to accidently do weird stuff in invisible regions
    

    I want to slightly change the default args for babel

    elisp
    #
    (setq org-babel-default-header-args
          '((:session . "none")
            (:results . "replace")
            (:exports . "code")
            (:cache . "no")
            (:noweb . "no")
            (:hlines . "no")
            (:tangle . "no")
            (:comments . "link")))
    

    I also want to change the order of bullets

    elisp
    #
    (setq org-list-demote-modify-bullet '(("+" . "-") ("-" . "+") ("*" . "+") ("1." . "a.")))
    

    The [[yt:...]] links preview nicely, but don’t export nicely. Thankfully, we can fix that.

    elisp
    #
    (after! ox
    (org-link-set-parameters "yt" :export #'+org-export-yt)
    (defun +org-export-yt (path desc backend _com)
      (cond ((org-export-derived-backend-p backend 'html)
             (format "<iframe width='440' \
    height='335' \
    src='https://www.youtube.com/embed/%s' \
    frameborder='0' \
    allowfullscreen>%s</iframe>" path (or "" desc)))
            ((org-export-derived-backend-p backend 'latex)
             (format "\\href{https://youtu.be/%s}{%s}" path (or desc "youtube")))
            (t (format "https://youtu.be/%s" path)))))
    
    1. HTML
      elisp
      #
      (use-package! ox-gfm
        :after org)
      

      :header-args:emacs-lisp: :noweb-ref ox-html-conf

      For some reason this only works if you have org first

      elisp
      #
      (after! ox-html
        (define-minor-mode org-fancy-html-export-mode
        "Toggle my fabulous org export tweaks. While this mode itself does a little bit,
      the vast majority of the change in behaviour comes from switch statements in:
       - `org-html-template-fancier'
       - `org-html--build-meta-info-extended'
       - `org-html-src-block-collapsable'
       - `org-html-block-collapsable'
       - `org-html-table-wrapped'
       - `org-html--format-toc-headline-colapseable'
       - `org-html--toc-text-stripped-leaves'
       - `org-export-html-headline-anchor'"
        :global t
        :init-value t
        (if org-fancy-html-export-mode
            (setq org-html-style-default org-html-style-fancy
                  org-html-meta-tags #'org-html-meta-tags-fancy
                  org-html-checkbox-type 'html-span)
          (setq org-html-style-default org-html-style-plain
                org-html-meta-tags #'org-html-meta-tags-default
                org-html-checkbox-type 'html)))
      
      (defadvice! org-html-template-fancier (orig-fn contents info)
        "Return complete document string after HTML conversion.
      CONTENTS is the transcoded contents string.  INFO is a plist
      holding export options. Adds a few extra things to the body
      compared to the default implementation."
        :around #'org-html-template
        (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
            (funcall orig-fn contents info)
          (concat
           (when (and (not (org-html-html5-p info)) (org-html-xhtml-p info))
             (let* ((xml-declaration (plist-get info :html-xml-declaration))
                    (decl (or (and (stringp xml-declaration) xml-declaration)
                              (cdr (assoc (plist-get info :html-extension)
                                          xml-declaration))
                              (cdr (assoc "html" xml-declaration))
                              "")))
               (when (not (or (not decl) (string= "" decl)))
                 (format "%s\n"
                         (format decl
                                 (or (and org-html-coding-system
                                          (fboundp 'coding-system-get)
                                          (coding-system-get org-html-coding-system 'mime-charset))
                                     "iso-8859-1"))))))
           (org-html-doctype info)
           "\n"
           (concat "<html"
                   (cond ((org-html-xhtml-p info)
                          (format
                           " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\""
                           (plist-get info :language) (plist-get info :language)))
                         ((org-html-html5-p info)
                          (format " lang=\"%s\"" (plist-get info :language))))
                   ">\n")
           "<head>\n"
           (org-html--build-meta-info info)
           (org-html--build-head info)
           (org-html--build-mathjax-config info)
           "</head>\n"
           "<body>\n<input type='checkbox' id='theme-switch'><div id='page'><label id='switch-label' for='theme-switch'></label>"
           (let ((link-up (org-trim (plist-get info :html-link-up)))
                 (link-home (org-trim (plist-get info :html-link-home))))
             (unless (and (string= link-up "") (string= link-home ""))
               (format (plist-get info :html-home/up-format)
                       (or link-up link-home)
                       (or link-home link-up))))
           ;; Preamble.
           (org-html--build-pre/postamble 'preamble info)
           ;; Document contents.
           (let ((div (assq 'content (plist-get info :html-divs))))
             (format "<%s id=\"%s\">\n" (nth 1 div) (nth 2 div)))
           ;; Document title.
           (when (plist-get info :with-title)
             (let ((title (and (plist-get info :with-title)
                               (plist-get info :title)))
                   (subtitle (plist-get info :subtitle))
                   (html5-fancy (org-html--html5-fancy-p info)))
               (when title
                 (format
                  (if html5-fancy
                      "<header class=\"page-header\">%s\n<h1 class=\"title\">%s</h1>\n%s</header>"
                    "<h1 class=\"title\">%s%s</h1>\n")
                  (if (or (plist-get info :with-date)
                          (plist-get info :with-author))
                      (concat "<div class=\"page-meta\">"
                              (when (plist-get info :with-date)
                                (org-export-data (plist-get info :date) info))
                              (when (and (plist-get info :with-date) (plist-get info :with-author)) ", ")
                              (when (plist-get info :with-author)
                                (org-export-data (plist-get info :author) info))
                              "</div>\n")
                    "")
                  (org-export-data title info)
                  (if subtitle
                      (format
                       (if html5-fancy
                           "<p class=\"subtitle\" role=\"doc-subtitle\">%s</p>\n"
                         (concat "\n" (org-html-close-tag "br" nil info) "\n"
                                 "<span class=\"subtitle\">%s</span>\n"))
                       (org-export-data subtitle info))
                    "")))))
           contents
           (format "</%s>\n" (nth 1 (assq 'content (plist-get info :html-divs))))
           ;; Postamble.
           (org-html--build-pre/postamble 'postamble info)
           ;; Possibly use the Klipse library live code blocks.
           (when (plist-get info :html-klipsify-src)
             (concat "<script>" (plist-get info :html-klipse-selection-script)
                     "</script><script src=\""
                     org-html-klipse-js
                     "\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\""
                     org-html-klipse-css "\"/>"))
           ;; Closing document.
           "</div>\n</body>\n</html>")))
      
      (defadvice! org-html-toc-linked (depth info &optional scope)
        "Build a table of contents.
      
      Just like `org-html-toc', except the header is a link to \"#\".
      
      DEPTH is an integer specifying the depth of the table.  INFO is
      a plist used as a communication channel.  Optional argument SCOPE
      is an element defining the scope of the table.  Return the table
      of contents as a string, or nil if it is empty."
        :override #'org-html-toc
        (let ((toc-entries
               (mapcar (lambda (headline)
                         (cons (org-html--format-toc-headline headline info)
                               (org-export-get-relative-level headline info)))
                       (org-export-collect-headlines info depth scope))))
          (when toc-entries
            (let ((toc (concat "<div id=\"text-table-of-contents\">"
                               (org-html--toc-text toc-entries)
                               "</div>\n")))
              (if scope toc
                (let ((outer-tag (if (org-html--html5-fancy-p info)
                                     "nav"
                                   "div")))
                  (concat (format "<%s id=\"table-of-contents\">\n" outer-tag)
                          (let ((top-level (plist-get info :html-toplevel-hlevel)))
                            (format "<h%d><a href=\"#\" style=\"color:inherit; text-decoration: none;\">%s</a></h%d>\n"
                                    top-level
                                    (org-html--translate "Table of Contents" info)
                                    top-level))
                          toc
                          (format "</%s>\n" outer-tag))))))))
      
       (defvar org-html-meta-tags-opengraph-image
        '(:image "https://tecosaur.com/resources/org/nib.png"
          :type "image/png"
          :width "200"
          :height "200"
          :alt "Green fountain pen nib")
        "Plist of og:image:PROP properties and their value, for use in `org-html-meta-tags-fancy'.")
      
      (defun org-html-meta-tags-fancy (info)
        "Use the INFO plist to construct the meta tags, as described in `org-html-meta-tags'."
        (let ((title (org-html-plain-text
                      (org-element-interpret-data (plist-get info :title)) info))
              (author (and (plist-get info :with-author)
                           (let ((auth (plist-get info :author)))
                             ;; Return raw Org syntax.
                             (and auth (org-html-plain-text
                                        (org-element-interpret-data auth) info))))))
          (append
           (list
            (when (org-string-nw-p author)
              (list "name" "author" author))
            (when (org-string-nw-p (plist-get info :description))
              (list "name" "description"
                    (plist-get info :description)))
            '("name" "generator" "org mode")
            '("name" "theme-color" "#77aa99")
            '("property" "og:type" "article")
            (list "property" "og:title" title)
            (let ((subtitle (org-export-data (plist-get info :subtitle) info)))
              (when (org-string-nw-p subtitle)
                (list "property" "og:description" subtitle))))
           (when org-html-meta-tags-opengraph-image
             (list (list "property" "og:image" (plist-get org-html-meta-tags-opengraph-image :image))
                   (list "property" "og:image:type" (plist-get org-html-meta-tags-opengraph-image :type))
                   (list "property" "og:image:width" (plist-get org-html-meta-tags-opengraph-image :width))
                   (list "property" "og:image:height" (plist-get org-html-meta-tags-opengraph-image :height))
                   (list "property" "og:image:alt" (plist-get org-html-meta-tags-opengraph-image :alt))))
           (list
            (when (org-string-nw-p author)
              (list "property" "og:article:author:first_name" (car (s-split-up-to " " author 2))))
            (when (and (org-string-nw-p author) (s-contains-p " " author))
              (list "property" "og:article:author:last_name" (cadr (s-split-up-to " " author 2))))
            (list "property" "og:article:published_time"
                  (format-time-string
                   "%FT%T%z"
                   (or
                    (when-let ((date-str (cadar (org-collect-keywords '("DATE")))))
                      (unless (string= date-str (format-time-string "%F"))
                        (ignore-errors (encode-time (org-parse-time-string date-str)))))
                    (if buffer-file-name
                        (file-attribute-modification-time (file-attributes buffer-file-name))
                      (current-time)))))
            (when buffer-file-name
              (list "property" "og:article:modified_time"
                    (format-time-string "%FT%T%z" (file-attribute-modification-time (file-attributes buffer-file-name)))))))))
      
      (unless (functionp #'org-html-meta-tags-default)
        (defalias 'org-html-meta-tags-default #'ignore))
      (setq org-html-meta-tags #'org-html-meta-tags-fancy)
      
      (setq org-html-style-plain org-html-style-default
            org-html-htmlize-output-type 'css
            org-html-doctype "html5"
            org-html-html5-fancy t)
      
      (defun org-html-reload-fancy-style ()
        (interactive)
        (setq org-html-style-fancy
              (concat (f-read-text (expand-file-name "misc/org-export-header.html" doom-private-dir))
                      "<script>\n"
                      (f-read-text (expand-file-name "misc/org-css/main.js" doom-private-dir))
                      "</script>\n<style>\n"
                      (f-read-text (expand-file-name "misc/org-css/main.min.css" doom-private-dir))
                      "</style>"))
        (when org-fancy-html-export-mode
          (setq org-html-style-default org-html-style-fancy)))
      (org-html-reload-fancy-style)
      
      (defvar org-html-export-collapsed nil)
      (eval '(cl-pushnew '(:collapsed "COLLAPSED" "collapsed" org-html-export-collapsed t)
                         (org-export-backend-options (org-export-get-backend 'html))))
      (add-to-list 'org-default-properties "EXPORT_COLLAPSED")
      
      (defadvice! org-html-src-block-collapsable (orig-fn src-block contents info)
        "Wrap the usual <pre> block in a <details>"
        :around #'org-html-src-block
        (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
            (funcall orig-fn src-block contents info)
          (let* ((properties (cadr src-block))
                 (lang (mode-name-to-lang-name
                        (plist-get properties :language)))
                 (name (plist-get properties :name))
                 (ref (org-export-get-reference src-block info))
                 (collapsed-p (member (or (org-export-read-attribute :attr_html src-block :collapsed)
                                          (plist-get info :collapsed))
                                      '("y" "yes" "t" t "true" "all"))))
            (format
             "<details id='%s' class='code'%s><summary%s>%s</summary>
      <div class='gutter'>
      <a href='#%s'>#</a>
      <button title='Copy to clipboard' onclick='copyPreToClipbord(this)'>⎘</button>\
      </div>
      %s
      </details>"
             ref
             (if collapsed-p "" " open")
             (if name " class='named'" "")
             (concat
              (when name (concat "<span class=\"name\">" name "</span>"))
              "<span class=\"lang\">" lang "</span>")
             ref
             (if name
                 (replace-regexp-in-string (format "<pre\\( class=\"[^\"]+\"\\)? id=\"%s\">" ref) "<pre\\1>"
                                           (funcall orig-fn src-block contents info))
               (funcall orig-fn src-block contents info))))))
      
      (defun mode-name-to-lang-name (mode)
        (or (cadr (assoc mode
                         '(("asymptote" "Asymptote")
                           ("awk" "Awk")
                           ("C" "C")
                           ("clojure" "Clojure")
                           ("css" "CSS")
                           ("D" "D")
                           ("ditaa" "ditaa")
                           ("dot" "Graphviz")
                           ("calc" "Emacs Calc")
                           ("emacs-lisp" "Emacs Lisp")
                           ("fortran" "Fortran")
                           ("gnuplot" "gnuplot")
                           ("haskell" "Haskell")
                           ("hledger" "hledger")
                           ("java" "Java")
                           ("js" "Javascript")
                           ("latex" "LaTeX")
                           ("ledger" "Ledger")
                           ("lisp" "Lisp")
                           ("lilypond" "Lilypond")
                           ("lua" "Lua")
                           ("matlab" "MATLAB")
                           ("mscgen" "Mscgen")
                           ("ocaml" "Objective Caml")
                           ("octave" "Octave")
                           ("org" "Org mode")
                           ("oz" "OZ")
                           ("plantuml" "Plantuml")
                           ("processing" "Processing.js")
                           ("python" "Python")
                           ("R" "R")
                           ("ruby" "Ruby")
                           ("sass" "Sass")
                           ("scheme" "Scheme")
                           ("screen" "Gnu Screen")
                           ("sed" "Sed")
                           ("sh" "shell")
                           ("sql" "SQL")
                           ("sqlite" "SQLite")
                           ("forth" "Forth")
                           ("io" "IO")
                           ("J" "J")
                           ("makefile" "Makefile")
                           ("maxima" "Maxima")
                           ("perl" "Perl")
                           ("picolisp" "Pico Lisp")
                           ("scala" "Scala")
                           ("shell" "Shell Script")
                           ("ebnf2ps" "ebfn2ps")
                           ("cpp" "C++")
                           ("abc" "ABC")
                           ("coq" "Coq")
                           ("groovy" "Groovy")
                           ("bash" "bash")
                           ("csh" "csh")
                           ("ash" "ash")
                           ("dash" "dash")
                           ("ksh" "ksh")
                           ("mksh" "mksh")
                           ("posh" "posh")
                           ("ada" "Ada")
                           ("asm" "Assembler")
                           ("caml" "Caml")
                           ("delphi" "Delphi")
                           ("html" "HTML")
                           ("idl" "IDL")
                           ("mercury" "Mercury")
                           ("metapost" "MetaPost")
                           ("modula-2" "Modula-2")
                           ("pascal" "Pascal")
                           ("ps" "PostScript")
                           ("prolog" "Prolog")
                           ("simula" "Simula")
                           ("tcl" "tcl")
                           ("tex" "LaTeX")
                           ("plain-tex" "TeX")
                           ("verilog" "Verilog")
                           ("vhdl" "VHDL")
                           ("xml" "XML")
                           ("nxml" "XML")
                           ("conf" "Configuration File"))))
            mode))
      
       (defadvice! org-html-table-wrapped (orig-fn table contents info)
        "Wrap the usual <table> in a <div>"
        :around #'org-html-table
        (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
            (funcall orig-fn table contents info)
          (let* ((name (plist-get (cadr table) :name))
                 (ref (org-export-get-reference table info)))
            (format "<div id='%s' class='table'>
      <div class='gutter'><a href='#%s'>#</a></div>
      <div class='tabular'>
      %s
      </div>\
      </div>"
                    ref ref
                    (if name
                        (replace-regexp-in-string (format "<table id=\"%s\"" ref) "<table"
                                                  (funcall orig-fn table contents info))
                      (funcall orig-fn table contents info))))))
      
      
      (defadvice! org-html--format-toc-headline-colapseable (orig-fn headline info)
        "Add a label and checkbox to `org-html--format-toc-headline's usual output,
      to allow the TOC to be a collapseable tree."
        :around #'org-html--format-toc-headline
        (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
            (funcall orig-fn headline info)
          (let ((id (or (org-element-property :CUSTOM_ID headline)
                        (org-export-get-reference headline info))))
            (format "<input type='checkbox' id='toc--%s'/><label for='toc--%s'>%s</label>"
                    id id (funcall orig-fn headline info)))))
      
       (defadvice! org-html--toc-text-stripped-leaves (orig-fn toc-entries)
        "Remove label"
        :around #'org-html--toc-text
        (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
            (funcall orig-fn toc-entries)
          (replace-regexp-in-string "<input [^>]+><label [^>]+>\\(.+?\\)</label></li>" "\\1</li>"
                                    (funcall orig-fn toc-entries))))
      
      (setq org-html-text-markup-alist
            '((bold . "<b>%s</b>")
              (code . "<code>%s</code>")
              (italic . "<i>%s</i>")
              (strike-through . "<del>%s</del>")
              (underline . "<span class=\"underline\">%s</span>")
              (verbatim . "<kbd>%s</kbd>")))
      
      (appendq! org-html-checkbox-types
                '((html-span
                   (on . "<span class='checkbox'></span>")
                   (off . "<span class='checkbox'></span>")
                   (trans . "<span class='checkbox'></span>"))))
      (setq org-html-checkbox-type 'html-span)
      
      (pushnew! org-html-special-string-regexps
                '("-&gt;" . "&#8594;")
                '("&lt;-" . "&#8592;"))
      
      (defun org-export-html-headline-anchor (text backend info)
        (when (and (org-export-derived-backend-p backend 'html)
                   (not (org-export-derived-backend-p backend 're-reveal))
                   org-fancy-html-export-mode)
          (unless (bound-and-true-p org-msg-export-in-progress)
            (replace-regexp-in-string
             "<h\\([0-9]\\) id=\"\\([a-z0-9-]+\\)\">\\(.*[^ ]\\)<\\/h[0-9]>" ; this is quite restrictive, but due to `org-reference-contraction' I can do this
             "<h\\1 id=\"\\2\">\\3<a aria-hidden=\"true\" href=\"#\\2\">#</a> </h\\1>"
             text))))
      
      (add-to-list 'org-export-filter-headline-functions
                   'org-export-html-headline-anchor)
      
      (org-link-set-parameters "Https"
                               :follow (lambda (url arg) (browse-url (concat "https:" url) arg))
                               :export #'org-url-fancy-export)
      
       (defun org-url-fancy-export (url _desc backend)
        (let ((metadata (org-url-unfurl-metadata (concat "https:" url))))
          (cond
           ((org-export-derived-backend-p backend 'html)
            (concat
             "<div class=\"link-preview\">"
             (format "<a href=\"%s\">" (concat "https:" url))
             (when (plist-get metadata :image)
               (format "<img src=\"%s\"/>" (plist-get metadata :image)))
             "<small>"
             (replace-regexp-in-string "//\\(?:www\\.\\)?\\([^/]+\\)/?.*" "\\1" url)
             "</small><p>"
             (when (plist-get metadata :title)
               (concat "<b>" (org-html-encode-plain-text (plist-get metadata :title)) "</b></br>"))
             (when (plist-get metadata :description)
               (org-html-encode-plain-text (plist-get metadata :description)))
             "</p></a></div>"))
           (t url))))
      
      (setq org-url-unfurl-metadata--cache nil)
      (defun org-url-unfurl-metadata (url)
        (cdr (or (assoc url org-url-unfurl-metadata--cache)
                 (car (push
                       (cons
                        url
                        (let* ((head-data
                                (-filter #'listp
                                         (cdaddr
                                          (with-current-buffer (progn (message "Fetching metadata from %s" url)
                                                                      (url-retrieve-synchronously url t t 5))
                                            (goto-char (point-min))
                                            (delete-region (point-min) (- (search-forward "<head") 6))
                                            (delete-region (search-forward "</head>") (point-max))
                                            (goto-char (point-min))
                                            (while (re-search-forward "<script[^\u2800]+?</script>" nil t)
                                              (replace-match ""))
                                            (goto-char (point-min))
                                            (while (re-search-forward "<style[^\u2800]+?</style>" nil t)
                                              (replace-match ""))
                                            (libxml-parse-html-region (point-min) (point-max))))))
                               (meta (delq nil
                                           (mapcar
                                            (lambda (tag)
                                              (when (eq 'meta (car tag))
                                                (cons (or (cdr (assoc 'name (cadr tag)))
                                                          (cdr (assoc 'property (cadr tag))))
                                                      (cdr (assoc 'content (cadr tag))))))
                                            head-data))))
                          (let ((title (or (cdr (assoc "og:title" meta))
                                           (cdr (assoc "twitter:title" meta))
                                           (nth 2 (assq 'title head-data))))
                                (description (or (cdr (assoc "og:description" meta))
                                                 (cdr (assoc "twitter:description" meta))
                                                 (cdr (assoc "description" meta))))
                                (image (or (cdr (assoc "og:image" meta))
                                           (cdr (assoc "twitter:image" meta)))))
                            (when image
                              (setq image (replace-regexp-in-string
                                           "^/" (concat "https://" (replace-regexp-in-string "//\\([^/]+\\)/?.*" "\\1" url) "/")
                                           (replace-regexp-in-string
                                            "^//" "https://"
                                            image))))
                            (list :title title :description description :image image))))
                       org-url-unfurl-metadata--cache)))))
      
                      (setq org-html-mathjax-options
            '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js" )
              (scale "1")
              (autonumber "ams")
              (multlinewidth "85%")
              (tagindent ".8em")
              (tagside "right")))
      
      (setq org-html-mathjax-template
            "<script>
      MathJax = {
        chtml: {
          scale: %SCALE
        },
        svg: {
          scale: %SCALE,
          fontCache: \"global\"
        },
        tex: {
          tags: \"%AUTONUMBER\",
          multlineWidth: \"%MULTLINEWIDTH\",
          tagSide: \"%TAGSIDE\",
          tagIndent: \"%TAGINDENT\"
        }
      };
      </script>
      <script id=\"MathJax-script\" async
              src=\"%PATH\"></script>")
      
      )
      

      There are quite a few instances where I want to modify variables defined in ox-html, so we’ll wrap the contents of this section in a

      Emacs Lisp
      #
      (after! ox-html
        <<ox-html-conf>>
      )
      

      Tecosaur has a good collection of fonts, might as well take some

      HTML
      #
      <link rel="icon" href="https://tecosaur.com/resources/org/nib.ico" type="image/ico" />
      <link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/etbookot-roman-webfont.woff2">
      <link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/etbookot-italic-webfont.woff2">
      <link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/Merriweather-TextRegular.woff2">
      <link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/Merriweather-TextItalic.woff2">
      <link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/Merriweather-TextBold.woff2">
      
      Example, fixed width, and property blocksEmacs Lisp
      #
      (defun org-html-block-collapsable (orig-fn block contents info)
        "Wrap the usual block in a <details>"
        (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
            (funcall orig-fn block contents info)
          (let ((ref (org-export-get-reference block info))
                (type (pcase (car block)
                        ('property-drawer "Properties")))
                (collapsed-default (pcase (car block)
                                     ('property-drawer t)
                                     (_ nil)))
                (collapsed-value (org-export-read-attribute :attr_html block :collapsed))
                (collapsed-p (or (member (org-export-read-attribute :attr_html block :collapsed)
                                         '("y" "yes" "t" t "true"))
                                 (member (plist-get info :collapsed) '("all")))))
            (format
             "<details id='%s' class='code'%s>
      <summary%s>%s</summary>
      <div class='gutter'>\
      <a href='#%s'>#</a>
      <button title='Copy to clipboard' onclick='copyPreToClipbord(this)'>⎘</button>\
      </div>
      %s\n
      </details>"
             ref
             (if (or collapsed-p collapsed-default) "" " open")
             (if type " class='named'" "")
             (if type (format "<span class='type'>%s</span>" type) "")
             ref
             (funcall orig-fn block contents info)))))
      
      (advice-add 'org-html-example-block   :around #'org-html-block-collapsable)
      (advice-add 'org-html-fixed-width     :around #'org-html-block-collapsable)
      (advice-add 'org-html-property-drawer :around #'org-html-block-collapsable)
      
  2. Org-Roam

    I would like to get into the habit of using org-roam for my notes, mainly because of that cool reddit post with the server.

    elisp
    #
    (setq org-roam-directory "~/org/roam/")
    

    Lets set up the org-roam-ui as well

    elisp
    #
    (use-package! websocket
      :after org-roam)
    
    (use-package! org-roam-ui
      :after org-roam
      :commands org-roam-ui-open
      :config
          (setq org-roam-ui-sync-theme t
                org-roam-ui-follow t
                org-roam-ui-update-on-save t
                org-roam-ui-open-on-start t))
    

    The doom-modeline is a bit messy with roam, lets adjust that

    elisp
    #
    (defadvice! doom-modeline--buffer-file-name-roam-aware-a (orig-fun)
      :around #'doom-modeline-buffer-file-name ; takes no args
      (if (s-contains-p org-roam-directory (or buffer-file-name ""))
          (replace-regexp-in-string
           "\\(?:^\\|.*/\\)\\([0-9]\\{4\\}\\)\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)[0-9]*-"
           "🢔(\\1-\\2-\\3) "
           (subst-char-in-string ?_ ?  buffer-file-name))
        (funcall orig-fun)))
    

    Now, I want to replace the org-roam buffer with org-roam-ui, to do that, we need to disable the regular buffer

    elisp
    #
    (after! org-roam
       (setq +org-roam-open-buffer-on-find-file nil))
    
  3. Org-Agenda

    Set the directory

    elisp
    #
    (setq org-agenda-files (list "~/org/school.org"
                                 "~/org/todo.org"))
    
  4. Org Cite
    elisp
    #
    (use-package! citar
      :when (featurep! :completion vertico)
      :no-require
      :config
      (setq org-cite-insert-processor 'citar
            org-cite-follow-processor 'citar
            org-cite-activate-processor 'citar
            citar-bibliography '("~/org/references.bib"))
      (when (featurep! :lang org +roam2)
        ;; Include property drawer metadata for 'org-roam' v2.
        (setq citar-file-note-org-include '(org-id org-roam-ref))))
    
    (use-package! citar
      :when (featurep! :completion vertico)
      :after org)
    
    (use-package! citeproc
      :defer t)
    
    ;;; Org-Cite configuration
    (map! :after org
          :map org-mode-map
          :localleader
          :desc "Insert citation" "@" #'org-cite-insert)
    
    (use-package! oc
      :after citar
      :config
      (require 'ox)
      (setq org-cite-global-bibliography
            (let ((paths (or citar-bibliography
                             (bound-and-true-p bibtex-completion-bibliography))))
              ;; Always return bibliography paths as list for org-cite.
              (if (stringp paths) (list paths) paths)))
      ;; setup export processor; default csl/citeproc-el, with biblatex for latex
      (setq org-cite-export-processors
            '((t csl))))
    
    ;;; Org-cite processors
    (use-package! oc-biblatex
      :after oc)
    
    (use-package! oc-csl
      :after oc
      :config
      (setq org-cite-csl-styles-dir "~/.config/bib/styles"))
    
    (use-package! oc-natbib
      :after oc)
    
    ;;;; Third-party
    (use-package! citar-org
      :no-require
      :custom
      (org-cite-insert-processor 'citar)
      (org-cite-follow-processor 'citar)
      (org-cite-activate-processor 'citar)
      (org-support-shift-select t)
      (citar-bibliography '("~/org/references.bib"))
      (when (featurep! :lang org +roam2)
        ;; Include property drawer metadata for 'org-roam' v2.
        (citar-org-note-include '(org-id org-roam-ref)))
      ;; Personal extras
      (setq citar-symbols
            `((file ,(all-the-icons-faicon "file-o" :v-adjust -0.1) . " ")
              (note ,(all-the-icons-material "speaker_notes" :face 'all-the-icons-silver :v-adjust -0.3) . " ")
              (link ,(all-the-icons-octicon "link" :face 'all-the-icons-dsilver :v-adjust 0.01) . " "))))
    
    (use-package! oc-csl-activate
      :after oc
      :config
      (setq org-cite-csl-activate-use-document-style t)
      (defun +org-cite-csl-activate/enable ()
        (interactive)
        (setq org-cite-activate-processor 'csl-activate)
        (add-hook! 'org-mode-hook '((lambda () (cursor-sensor-mode 1)) org-cite-csl-activate-render-all))
        (defadvice! +org-cite-csl-activate-render-all-silent (orig-fn)
          :around #'org-cite-csl-activate-render-all
          (with-silent-modifications (funcall orig-fn)))
        (when (eq major-mode 'org-mode)
          (with-silent-modifications
            (save-excursion
              (goto-char (point-min))
              (org-cite-activate (point-max)))
            (org-cite-csl-activate-render-all)))
        (fmakunbound #'+org-cite-csl-activate/enable)))
    
  5. Org-Capture

    Use doct

    Emacs Lisp
    #
    (use-package! doct
      :commands (doct))
    
    1. Prettify

      Improve the look of the capture dialog (idea borrowed from tecosaur)

      Emacs Lisp
      #
      (defun org-capture-select-template-prettier (&optional keys)
        "Select a capture template, in a prettier way than default
      Lisp programs can force the template by setting KEYS to a string."
        (let ((org-capture-templates
               (or (org-contextualize-keys
                    (org-capture-upgrade-templates org-capture-templates)
                    org-capture-templates-contexts)
                   '(("t" "Task" entry (file+headline "" "Tasks")
                      "* TODO %?\n  %u\n  %a")))))
          (if keys
              (or (assoc keys org-capture-templates)
                  (error "No capture template referred to by \"%s\" keys" keys))
            (org-mks org-capture-templates
                     "Select a capture template\n━━━━━━━━━━━━━━━━━━━━━━━━━"
                     "Template key: "
                     `(("q" ,(concat (all-the-icons-octicon "stop" :face 'all-the-icons-red :v-adjust 0.01) "\tAbort")))))))
      (advice-add 'org-capture-select-template :override #'org-capture-select-template-prettier)
      
      (defun org-mks-pretty (table title &optional prompt specials)
        "Select a member of an alist with multiple keys. Prettified.
      
      TABLE is the alist which should contain entries where the car is a string.
      There should be two types of entries.
      
      1. prefix descriptions like (\"a\" \"Description\")
         This indicates that `a' is a prefix key for multi-letter selection, and
         that there are entries following with keys like \"ab\", \"ax\"…
      
      2. Select-able members must have more than two elements, with the first
         being the string of keys that lead to selecting it, and the second a
         short description string of the item.
      
      The command will then make a temporary buffer listing all entries
      that can be selected with a single key, and all the single key
      prefixes.  When you press the key for a single-letter entry, it is selected.
      When you press a prefix key, the commands (and maybe further prefixes)
      under this key will be shown and offered for selection.
      
      TITLE will be placed over the selection in the temporary buffer,
      PROMPT will be used when prompting for a key.  SPECIALS is an
      alist with (\"key\" \"description\") entries.  When one of these
      is selected, only the bare key is returned."
        (save-window-excursion
          (let ((inhibit-quit t)
                (buffer (org-switch-to-buffer-other-window "*Org Select*"))
                (prompt (or prompt "Select: "))
                case-fold-search
                current)
            (unwind-protect
                (catch 'exit
                  (while t
                    (setq-local evil-normal-state-cursor (list nil))
                    (erase-buffer)
                    (insert title "\n\n")
                    (let ((des-keys nil)
                          (allowed-keys '("\C-g"))
                          (tab-alternatives '("\s" "\t" "\r"))
                          (cursor-type nil))
                      ;; Populate allowed keys and descriptions keys
                      ;; available with CURRENT selector.
                      (let ((re (format "\\`%s\\(.\\)\\'"
                                        (if current (regexp-quote current) "")))
                            (prefix (if current (concat current " ") "")))
                        (dolist (entry table)
                          (pcase entry
                            ;; Description.
                            (`(,(and key (pred (string-match re))) ,desc)
                             (let ((k (match-string 1 key)))
                               (push k des-keys)
                               ;; Keys ending in tab, space or RET are equivalent.
                               (if (member k tab-alternatives)
                                   (push "\t" allowed-keys)
                                 (push k allowed-keys))
                               (insert (propertize prefix 'face 'font-lock-comment-face) (propertize k 'face 'bold) (propertize "›" 'face 'font-lock-comment-face) "  " desc "…" "\n")))
                            ;; Usable entry.
                            (`(,(and key (pred (string-match re))) ,desc . ,_)
                             (let ((k (match-string 1 key)))
                               (insert (propertize prefix 'face 'font-lock-comment-face) (propertize k 'face 'bold) "   " desc "\n")
                               (push k allowed-keys)))
                            (_ nil))))
                      ;; Insert special entries, if any.
                      (when specials
                        (insert "─────────────────────────\n")
                        (pcase-dolist (`(,key ,description) specials)
                          (insert (format "%s   %s\n" (propertize key 'face '(bold all-the-icons-red)) description))
                          (push key allowed-keys)))
                      ;; Display UI and let user select an entry or
                      ;; a sub-level prefix.
                      (goto-char (point-min))
                      (unless (pos-visible-in-window-p (point-max))
                        (org-fit-window-to-buffer))
                      (let ((pressed (org--mks-read-key allowed-keys prompt nil)))
                        (setq current (concat current pressed))
                        (cond
                         ((equal pressed "\C-g") (user-error "Abort"))
                         ((equal pressed "ESC") (user-error "Abort"))
                         ;; Selection is a prefix: open a new menu.
                         ((member pressed des-keys))
                         ;; Selection matches an association: return it.
                         ((let ((entry (assoc current table)))
                            (and entry (throw 'exit entry))))
                         ;; Selection matches a special entry: return the
                         ;; selection prefix.
                         ((assoc current specials) (throw 'exit current))
                         (t (error "No entry available")))))))
              (when buffer (kill-buffer buffer))))))
      (advice-add 'org-mks :override #'org-mks-pretty)
      

      The org-capture bin is rather nice, but I’d be nicer with a smaller frame, and no modeline.

      Emacs Lisp
      #
      (setf (alist-get 'height +org-capture-frame-parameters) 15)
      (setq +org-capture-fn
            (lambda ()
              (interactive)
              (set-window-parameter nil 'mode-line-format 'none)
              (org-capture)))
      

      Sprinkle in some doct utility functions

      Emacs Lisp
      #
      (defun +doct-icon-declaration-to-icon (declaration)
        "Convert :icon declaration to icon"
        (let ((name (pop declaration))
              (set  (intern (concat "all-the-icons-" (plist-get declaration :set))))
              (face (intern (concat "all-the-icons-" (plist-get declaration :color))))
              (v-adjust (or (plist-get declaration :v-adjust) 0.01)))
          (apply set `(,name :face ,face :v-adjust ,v-adjust))))
      
      (defun +doct-iconify-capture-templates (groups)
        "Add declaration's :icon to each template group in GROUPS."
        (let ((templates (doct-flatten-lists-in groups)))
          (setq doct-templates (mapcar (lambda (template)
                                         (when-let* ((props (nthcdr (if (= (length template) 4) 2 5) template))
                                                     (spec (plist-get (plist-get props :doct) :icon)))
                                           (setf (nth 1 template) (concat (+doct-icon-declaration-to-icon spec)
                                                                          "\t"
                                                                          (nth 1 template))))
                                         template)
                                       templates))))
      
      (setq doct-after-conversion-functions '(+doct-iconify-capture-templates))
      
    2. Templates
      Emacs Lisp
      #
      (setq org-capture-templates
            (doct `(("Home" :keys "h"
                     :icon ("home" :set "octicon" :color "cyan")
                     :file "Home.org"
                     :prepend t
                     :headline "Inbox"
                     :template ("* TODO %?"
                                "%i %a"))
                    ("Work" :keys "w"
                     :icon ("business" :set "material" :color "yellow")
                     :file "Work.org"
                     :prepend t
                     :headline "Inbox"
                     :template ("* TODO %?"
                                "SCHEDULED: %^{Schedule:}t"
                                "DEADLINE: %^{Deadline:}t"
                                "%i %a"))
                    ("Note" :keys "n"
                     :icon ("sticky-note" :set "faicon" :color "yellow")
                     :file "Notes.org"
                     :template ("* *?"
                                "%i %a"))
                    ("Project" :keys "p"
                     :icon ("repo" :set "octicon" :color "silver")
                     :prepend t
                     :type entry
                     :headline "Inbox"
                     :template ("* %{keyword} %?"
                                "%i"
                                "%a")
                     :file ""
                     :custom (:keyword "")
                     :children (("Task" :keys "t"
                                 :icon ("checklist" :set "octicon" :color "green")
                                 :keyword "TODO"
                                 :file +org-capture-project-todo-file)
                                ("Note" :keys "n"
                                 :icon ("sticky-note" :set "faicon" :color "yellow")
                                 :keyword "%U"
                                 :file +org-capture-project-notes-file)))
                    )))
      
  6. Org-Plot

    You can’t ever have too many graphs! Lets make it look prettier, and tell it to use the doom theme colors

    elisp
    #
    (after! org-plot
      (defun org-plot/generate-theme (_type)
        "Use the current Doom theme colours to generate a GnuPlot preamble."
        (format "
    fgt = \"textcolor rgb '%s'\" # foreground text
    fgat = \"textcolor rgb '%s'\" # foreground alt text
    fgl = \"linecolor rgb '%s'\" # foreground line
    fgal = \"linecolor rgb '%s'\" # foreground alt line
    
    # foreground colors
    set border lc rgb '%s'
    # change text colors of  tics
    set xtics @fgt
    set ytics @fgt
    # change text colors of labels
    set title @fgt
    set xlabel @fgt
    set ylabel @fgt
    # change a text color of key
    set key @fgt
    
    # line styles
    set linetype 1 lw 2 lc rgb '%s' # red
    set linetype 2 lw 2 lc rgb '%s' # blue
    set linetype 3 lw 2 lc rgb '%s' # green
    set linetype 4 lw 2 lc rgb '%s' # magenta
    set linetype 5 lw 2 lc rgb '%s' # orange
    set linetype 6 lw 2 lc rgb '%s' # yellow
    set linetype 7 lw 2 lc rgb '%s' # teal
    set linetype 8 lw 2 lc rgb '%s' # violet
    
    # border styles
    set tics out nomirror
    set border 3
    
    # palette
    set palette maxcolors 8
    set palette defined ( 0 '%s',\
    1 '%s',\
    2 '%s',\
    3 '%s',\
    4 '%s',\
    5 '%s',\
    6 '%s',\
    7 '%s' )
    "
                (doom-color 'fg)
                (doom-color 'fg-alt)
                (doom-color 'fg)
                (doom-color 'fg-alt)
                (doom-color 'fg)
                ;; colours
                (doom-color 'red)
                (doom-color 'blue)
                (doom-color 'green)
                (doom-color 'magenta)
                (doom-color 'orange)
                (doom-color 'yellow)
                (doom-color 'teal)
                (doom-color 'violet)
                ;; duplicated
                (doom-color 'red)
                (doom-color 'blue)
                (doom-color 'green)
                (doom-color 'magenta)
                (doom-color 'orange)
                (doom-color 'yellow)
                (doom-color 'teal)
                (doom-color 'violet)
                ))
      (defun org-plot/gnuplot-term-properties (_type)
        (format "background rgb '%s' size 1050,650"
                (doom-color 'bg)))
      (setq org-plot/gnuplot-script-preamble #'org-plot/generate-theme)
      (setq org-plot/gnuplot-term-extra #'org-plot/gnuplot-term-properties))
    
  7. XKCD

    In Popular Culture

    Relevent XKCD:

    I link to xkcd’s so much that its better to just have a configuration for them We want to set this up so it loads nicely in org.

    Emacs Lisp
    #
    (use-package! xkcd
      :commands (xkcd-get-json
                 xkcd-download xkcd-get
                 ;; now for funcs from my extension of this pkg
                 +xkcd-find-and-copy +xkcd-find-and-view
                 +xkcd-fetch-info +xkcd-select)
      :config
      (setq xkcd-cache-dir (expand-file-name "xkcd/" doom-cache-dir)
            xkcd-cache-latest (concat xkcd-cache-dir "latest"))
      (unless (file-exists-p xkcd-cache-dir)
        (make-directory xkcd-cache-dir))
      (after! evil-snipe
        (add-to-list 'evil-snipe-disabled-modes 'xkcd-mode))
      :general (:states 'normal
                :keymaps 'xkcd-mode-map
                "<right>" #'xkcd-next
                "n"       #'xkcd-next ; evil-ish
                "<left>"  #'xkcd-prev
                "N"       #'xkcd-prev ; evil-ish
                "r"       #'xkcd-rand
                "a"       #'xkcd-rand ; because image-rotate can interfere
                "t"       #'xkcd-alt-text
                "q"       #'xkcd-kill-buffer
                "o"       #'xkcd-open-browser
                "e"       #'xkcd-open-explanation-browser
                ;; extras
                "s"       #'+xkcd-find-and-view
                "/"       #'+xkcd-find-and-view
                "y"       #'+xkcd-copy))
    

    Let’s also extend the functionality a whole bunch.

    Emacs Lisp
    #
    (after! xkcd
      (require 'emacsql-sqlite)
    
      (defun +xkcd-select ()
        "Prompt the user for an xkcd using `completing-read' and `+xkcd-select-format'. Return the xkcd number or nil"
        (let* (prompt-lines
               (-dummy (maphash (lambda (key xkcd-info)
                                  (push (+xkcd-select-format xkcd-info) prompt-lines))
                                +xkcd-stored-info))
               (num (completing-read (format "xkcd (%s): " xkcd-latest) prompt-lines)))
          (if (equal "" num) xkcd-latest
            (string-to-number (replace-regexp-in-string "\\([0-9]+\\).*" "\\1" num)))))
    
      (defun +xkcd-select-format (xkcd-info)
        "Creates each completing-read line from an xkcd info plist. Must start with the xkcd number"
        (format "%-4s  %-30s %s"
                (propertize (number-to-string (plist-get xkcd-info :num))
                            'face 'counsel-key-binding)
                (plist-get xkcd-info :title)
                (propertize (plist-get xkcd-info :alt)
                            'face '(variable-pitch font-lock-comment-face))))
    
      (defun +xkcd-fetch-info (&optional num)
        "Fetch the parsed json info for comic NUM. Fetches latest when omitted or 0"
        (require 'xkcd)
        (when (or (not num) (= num 0))
          (+xkcd-check-latest)
          (setq num xkcd-latest))
        (let ((res (or (gethash num +xkcd-stored-info)
                       (puthash num (+xkcd-db-read num) +xkcd-stored-info))))
          (unless res
            (+xkcd-db-write
             (let* ((url (format "https://xkcd.com/%d/info.0.json" num))
                    (json-assoc
                     (if (gethash num +xkcd-stored-info)
                         (gethash num +xkcd-stored-info)
                       (json-read-from-string (xkcd-get-json url num)))))
               json-assoc))
            (setq res (+xkcd-db-read num)))
          res))
    
      ;; since we've done this, we may as well go one little step further
      (defun +xkcd-find-and-copy ()
        "Prompt for an xkcd using `+xkcd-select' and copy url to clipboard"
        (interactive)
        (+xkcd-copy (+xkcd-select)))
    
      (defun +xkcd-copy (&optional num)
        "Copy a url to xkcd NUM to the clipboard"
        (interactive "i")
        (let ((num (or num xkcd-cur)))
          (gui-select-text (format "https://xkcd.com/%d" num))
          (message "xkcd.com/%d copied to clipboard" num)))
    
      (defun +xkcd-find-and-view ()
        "Prompt for an xkcd using `+xkcd-select' and view it"
        (interactive)
        (xkcd-get (+xkcd-select))
        (switch-to-buffer "*xkcd*"))
    
      (defvar +xkcd-latest-max-age (* 60 60) ; 1 hour
        "Time after which xkcd-latest should be refreshed, in seconds")
    
      ;; initialise `xkcd-latest' and `+xkcd-stored-info' with latest xkcd
      (add-transient-hook! '+xkcd-select
        (require 'xkcd)
        (+xkcd-fetch-info xkcd-latest)
        (setq +xkcd-stored-info (+xkcd-db-read-all)))
    
      (add-transient-hook! '+xkcd-fetch-info
        (xkcd-update-latest))
    
      (defun +xkcd-check-latest ()
        "Use value in `xkcd-cache-latest' as long as it isn't older thabn `+xkcd-latest-max-age'"
        (unless (and (file-exists-p xkcd-cache-latest)
                     (< (- (time-to-seconds (current-time))
                           (time-to-seconds (file-attribute-modification-time (file-attributes xkcd-cache-latest))))
                        +xkcd-latest-max-age))
          (let* ((out (xkcd-get-json "http://xkcd.com/info.0.json" 0))
                 (json-assoc (json-read-from-string out))
                 (latest (cdr (assoc 'num json-assoc))))
            (when (/= xkcd-latest latest)
              (+xkcd-db-write json-assoc)
              (with-current-buffer (find-file xkcd-cache-latest)
                (setq xkcd-latest latest)
                (erase-buffer)
                (insert (number-to-string latest))
                (save-buffer)
                (kill-buffer (current-buffer)))))
          (shell-command (format "touch %s" xkcd-cache-latest))))
    
      (defvar +xkcd-stored-info (make-hash-table :test 'eql)
        "Basic info on downloaded xkcds, in the form of a hashtable")
    
      (defadvice! xkcd-get-json--and-cache (url &optional num)
        "Fetch the Json coming from URL.
    If the file NUM.json exists, use it instead.
    If NUM is 0, always download from URL.
    The return value is a string."
        :override #'xkcd-get-json
        (let* ((file (format "%s%d.json" xkcd-cache-dir num))
               (cached (and (file-exists-p file) (not (eq num 0))))
               (out (with-current-buffer (if cached
                                             (find-file file)
                                           (url-retrieve-synchronously url))
                      (goto-char (point-min))
                      (unless cached (re-search-forward "^$"))
                      (prog1
                          (buffer-substring-no-properties (point) (point-max))
                        (kill-buffer (current-buffer))))))
          (unless (or cached (eq num 0))
            (xkcd-cache-json num out))
          out))
    
      (defadvice! +xkcd-get (num)
        "Get the xkcd number NUM."
        :override 'xkcd-get
        (interactive "nEnter comic number: ")
        (xkcd-update-latest)
        (get-buffer-create "*xkcd*")
        (switch-to-buffer "*xkcd*")
        (xkcd-mode)
        (let (buffer-read-only)
          (erase-buffer)
          (setq xkcd-cur num)
          (let* ((xkcd-data (+xkcd-fetch-info num))
                 (num (plist-get xkcd-data :num))
                 (img (plist-get xkcd-data :img))
                 (safe-title (plist-get xkcd-data :safe-title))
                 (alt (plist-get xkcd-data :alt))
                 title file)
            (message "Getting comic...")
            (setq file (xkcd-download img num))
            (setq title (format "%d: %s" num safe-title))
            (insert (propertize title
                                'face 'outline-1))
            (center-line)
            (insert "\n")
            (xkcd-insert-image file num)
            (if (eq xkcd-cur 0)
                (setq xkcd-cur num))
            (setq xkcd-alt alt)
            (message "%s" title))))
    
      (defconst +xkcd-db--sqlite-available-p
        (with-demoted-errors "+org-xkcd initialization: %S"
          (emacsql-sqlite-ensure-binary)
          t))
    
      (defvar +xkcd-db--connection (make-hash-table :test #'equal)
        "Database connection to +org-xkcd database.")
    
      (defun +xkcd-db--get ()
        "Return the sqlite db file."
        (expand-file-name "xkcd.db" xkcd-cache-dir))
    
      (defun +xkcd-db--get-connection ()
        "Return the database connection, if any."
        (gethash (file-truename xkcd-cache-dir)
                 +xkcd-db--connection))
    
      (defconst +xkcd-db--table-schema
        '((xkcds
           [(num integer :unique :primary-key)
            (year        :not-null)
            (month       :not-null)
            (link        :not-null)
            (news        :not-null)
            (safe_title  :not-null)
            (title       :not-null)
            (transcript  :not-null)
            (alt         :not-null)
            (img         :not-null)])))
    
      (defun +xkcd-db--init (db)
        "Initialize database DB with the correct schema and user version."
        (emacsql-with-transaction db
          (pcase-dolist (`(,table . ,schema) +xkcd-db--table-schema)
            (emacsql db [:create-table $i1 $S2] table schema))))
    
      (defun +xkcd-db ()
        "Entrypoint to the +org-xkcd sqlite database.
    Initializes and stores the database, and the database connection.
    Performs a database upgrade when required."
        (unless (and (+xkcd-db--get-connection)
                     (emacsql-live-p (+xkcd-db--get-connection)))
          (let* ((db-file (+xkcd-db--get))
                 (init-db (not (file-exists-p db-file))))
            (make-directory (file-name-directory db-file) t)
            (let ((conn (emacsql-sqlite db-file)))
              (set-process-query-on-exit-flag (emacsql-process conn) nil)
              (puthash (file-truename xkcd-cache-dir)
                       conn
                       +xkcd-db--connection)
              (when init-db
                (+xkcd-db--init conn)))))
        (+xkcd-db--get-connection))
    
      (defun +xkcd-db-query (sql &rest args)
        "Run SQL query on +org-xkcd database with ARGS.
    SQL can be either the emacsql vector representation, or a string."
        (if  (stringp sql)
            (emacsql (+xkcd-db) (apply #'format sql args))
          (apply #'emacsql (+xkcd-db) sql args)))
    
      (defun +xkcd-db-read (num)
        (when-let ((res
                    (car (+xkcd-db-query [:select * :from xkcds
                                          :where (= num $s1)]
                                         num
                                         :limit 1))))
          (+xkcd-db-list-to-plist res)))
    
      (defun +xkcd-db-read-all ()
        (let ((xkcd-table (make-hash-table :test 'eql :size 4000)))
          (mapcar (lambda (xkcd-info-list)
                    (puthash (car xkcd-info-list) (+xkcd-db-list-to-plist xkcd-info-list) xkcd-table))
                  (+xkcd-db-query [:select * :from xkcds]))
          xkcd-table))
    
      (defun +xkcd-db-list-to-plist (xkcd-datalist)
        `(:num ,(nth 0 xkcd-datalist)
          :year ,(nth 1 xkcd-datalist)
          :month ,(nth 2 xkcd-datalist)
          :link ,(nth 3 xkcd-datalist)
          :news ,(nth 4 xkcd-datalist)
          :safe-title ,(nth 5 xkcd-datalist)
          :title ,(nth 6 xkcd-datalist)
          :transcript ,(nth 7 xkcd-datalist)
          :alt ,(nth 8 xkcd-datalist)
          :img ,(nth 9 xkcd-datalist)))
    
      (defun +xkcd-db-write (data)
        (+xkcd-db-query [:insert-into xkcds
                         :values $v1]
                        (list (vector
                               (cdr (assoc 'num        data))
                               (cdr (assoc 'year       data))
                               (cdr (assoc 'month      data))
                               (cdr (assoc 'link       data))
                               (cdr (assoc 'news       data))
                               (cdr (assoc 'safe_title data))
                               (cdr (assoc 'title      data))
                               (cdr (assoc 'transcript data))
                               (cdr (assoc 'alt        data))
                               (cdr (assoc 'img        data))
                               )))))
    

    Now to just have this register with org

    Emacs Lisp
    #
    (after! org
      (org-link-set-parameters "xkcd"
                               :image-data-fun #'+org-xkcd-image-fn
                               :follow #'+org-xkcd-open-fn
                               :export #'+org-xkcd-export
                               :complete #'+org-xkcd-complete)
    
      (defun +org-xkcd-open-fn (link)
        (+org-xkcd-image-fn nil link nil))
    
      (defun +org-xkcd-image-fn (protocol link description)
        "Get image data for xkcd num LINK"
        (let* ((xkcd-info (+xkcd-fetch-info (string-to-number link)))
               (img (plist-get xkcd-info :img))
               (alt (plist-get xkcd-info :alt)))
          (message alt)
          (+org-image-file-data-fn protocol (xkcd-download img (string-to-number link)) description)))
    
      (defun +org-xkcd-export (num desc backend _com)
        "Convert xkcd to html/LaTeX form"
        (let* ((xkcd-info (+xkcd-fetch-info (string-to-number num)))
               (img (plist-get xkcd-info :img))
               (alt (plist-get xkcd-info :alt))
               (title (plist-get xkcd-info :title))
               (file (xkcd-download img (string-to-number num))))
          (cond ((org-export-derived-backend-p backend 'html)
                 (format "<img class='invertible' src='%s' title=\"%s\" alt='%s'>" img (subst-char-in-string ?\" ? alt) title))
                ((org-export-derived-backend-p backend 'latex)
                 (format "\\begin{figure}[!htb]
        \\centering
        \\includegraphics[scale=0.4]{%s}%s
      \\end{figure}" file (if (equal desc (format "xkcd:%s" num)) ""
                            (format "\n  \\caption*{\\label{xkcd:%s} %s}"
                                    num
                                    (or desc
                                        (format "\\textbf{%s} %s" title alt))))))
                (t (format "https://xkcd.com/%s" num)))))
    
      (defun +org-xkcd-complete (&optional arg)
        "Complete xkcd using `+xkcd-stored-info'"
        (format "xkcd:%d" (+xkcd-select))))
    
  8. View Exported File

    I have to export files pretty often, lets setup some keybindings to make it easier

    elisp
    #
    (map! :map org-mode-map
          :localleader
          :desc "View exported file" "v" #'org-view-output-file)
    
    (defun org-view-output-file (&optional org-file-path)
      "Visit buffer open on the first output file (if any) found, using `org-view-output-file-extensions'"
      (interactive)
      (let* ((org-file-path (or org-file-path (buffer-file-name) ""))
             (dir (file-name-directory org-file-path))
             (basename (file-name-base org-file-path))
             (output-file nil))
        (dolist (ext org-view-output-file-extensions)
          (unless output-file
            (when (file-exists-p
                   (concat dir basename "." ext))
              (setq output-file (concat dir basename "." ext)))))
        (if output-file
            (if (member (file-name-extension output-file) org-view-external-file-extensions)
                (browse-url-xdg-open output-file)
              (pop-to-bufferpop-to-buffer (or (find-buffer-visiting output-file)
                                 (find-file-noselect output-file))))
          (message "No exported file found"))))
    
    (defvar org-view-output-file-extensions '("pdf" "md" "rst" "txt" "tex" "html")
      "Search for output files with these extensions, in order, viewing the first that matches")
    (defvar org-view-external-file-extensions '("html")
      "File formats that should be opened externally.")
    
  9. Dictionaries

    Lets use lexic instead of the default dictionary

    elisp
    #
    (use-package! lexic
      :commands lexic-search lexic-list-dictionary
      :config
      (map! :map lexic-mode-map
            :n "q" #'lexic-return-from-lexic
            :nv "RET" #'lexic-search-word-at-point
            :n "a" #'outline-show-all
            :n "h" (cmd! (outline-hide-sublevels 3))
            :n "o" #'lexic-toggle-entry
            :n "n" #'lexic-next-entry
            :n "N" (cmd! (lexic-next-entry t))
            :n "p" #'lexic-previous-entry
            :n "P" (cmd! (lexic-previous-entry t))
            :n "E" (cmd! (lexic-return-from-lexic) ; expand
                         (switch-to-buffer (lexic-get-buffer)))
            :n "M" (cmd! (lexic-return-from-lexic) ; minimise
                         (lexic-goto-lexic))
            :n "C-p" #'lexic-search-history-backwards
            :n "C-n" #'lexic-search-history-forwards
            :n "/" (cmd! (call-interactively #'lexic-search))))
    
    (defadvice! +lookup/dictionary-definition-lexic (identifier &optional arg)
      "Look up the definition of the word at point (or selection) using `lexic-search'."
      :override #'+lookup/dictionary-definition
      (interactive
       (list (or (doom-thing-at-point-or-region 'word)
                 (read-string "Look up in dictionary: "))
             current-prefix-arg))
      (lexic-search identifier nil nil t))
    

5.1.7. Latex

File Extensions I have a love-hate relationship with latex. Its extremely powerful, but at the same time its hard to write, hard to understand, and very slow. The solution: write everything in org and then export it to tex. Best of both worlds!

  1. Basic configuration

    First of all, lets use pdf-tools to preview pdfs by defaults

    elisp
    #
    (setq +latex-viewers '(pdf-tools evince zathura okular skim sumatrapdf))
    

    I also want to adjust the look of those previews

    elisp
    #
    (after! org
      (setq org-highlight-latex-and-related '(native script entities))
      (add-to-list 'org-src-block-faces '("latex" (:inherit default :extend t))))
    
    (after! org
      (plist-put org-format-latex-options :background "Transparent"))
    

    Lets add cdlatex org mode integration

    elisp
    #
    (after! org
      (add-hook 'org-mode-hook 'turn-on-org-cdlatex))
    
    (defadvice! org-edit-latex-emv-after-insert ()
      :after #'org-cdlatex-environment-indent
      (org-edit-latex-environment))
    

    I like to preview images inline too

    elisp
    #
    (setq org-display-inline-images t)
    (setq org-redisplay-inline-images t)
    (setq org-startup-with-inline-images "inlineimages")
    

    Obviously we can’t edit a png though. Let use org-fragtog to toggle between previews and text mode

    elisp
    #
    (use-package! org-fragtog
      :hook (org-mode . org-fragtog-mode))
    

    Here’s just my private LaTeX config.

    elisp
    #
    (setq org-format-latex-header "\\documentclass{article}
    \\usepackage[usenames]{xcolor}
    
    \\usepackage[T1]{fontenc}
    
    \\usepackage{booktabs}
    
    \\pagestyle{empty}             % do not remove
    % The settings below are copied from fullpage.sty
    \\setlength{\\textwidth}{\\paperwidth}
    \\addtolength{\\textwidth}{-3cm}
    \\setlength{\\oddsidemargin}{1.5cm}
    \\addtolength{\\oddsidemargin}{-2.54cm}
    \\setlength{\\evensidemargin}{\\oddsidemargin}
    \\setlength{\\textheight}{\\paperheight}
    \\addtolength{\\textheight}{-\\headheight}
    \\addtolength{\\textheight}{-\\headsep}
    \\addtolength{\\textheight}{-\\footskip}
    \\addtolength{\\textheight}{-3cm}
    \\setlength{\\topmargin}{1.5cm}
    \\addtolength{\\topmargin}{-2.54cm}
    ")
    
  2. PDF-Tools

    DocView gives me a headache, but pdf-tools can be improved, lets configure it a little more

    elisp
    #
    (use-package pdf-view
      :hook (pdf-tools-enabled . pdf-view-themed-minor-mode)
      :hook (pdf-tools-enabled . hide-mode-line-mode)
      :config
      (setq pdf-view-resize-factor 1.1)
      (setq-default pdf-view-display-size 'fit-page))
    
  3. Export
    1. Conditional features
      Emacs Lisp
      #
      (defvar org-latex-italic-quotes t
        "Make \"quote\" environments italic.")
      (defvar org-latex-par-sep t
        "Vertically seperate paragraphs, and remove indentation.")
      
      (defvar org-latex-conditional-features
        '(("\\[\\[\\(?:file\\|https?\\):\\(?:[^]]\\|\\\\\\]\\)+?\\.\\(?:eps\\|pdf\\|png\\|jpeg\\|jpg\\|jbig2\\)\\]\\]" . image)
          ("\\[\\[\\(?:file\\|https?\\):\\(?:[^]]+?\\|\\\\\\]\\)\\.svg\\]\\]" . svg)
          ("^[ \t]*|" . table)
          ("cref:\\|\\cref{\\|\\[\\[[^\\]]+\\]\\]" . cleveref)
          ("[;\\\\]?\\b[A-Z][A-Z]+s?[^A-Za-z]" . acronym)
          ("\\+[^ ].*[^ ]\\+\\|_[^ ].*[^ ]_\\|\\\\uu?line\\|\\\\uwave\\|\\\\sout\\|\\\\xout\\|\\\\dashuline\\|\\dotuline\\|\\markoverwith" . underline)
          (":float wrap" . float-wrap)
          (":float sideways" . rotate)
          ("^[ \t]*#\\+caption:\\|\\\\caption" . caption)
          ("\\[\\[xkcd:" . (image caption))
          ((and org-latex-italic-quotes "^[ \t]*#\\+begin_quote\\|\\\\begin{quote}") . italic-quotes)
          (org-latex-par-sep . par-sep)
          ("^[ \t]*\\(?:[-+*]\\|[0-9]+[.)]\\|[A-Za-z]+[.)]\\) \\[[ -X]\\]" . checkbox)
          ("^[ \t]*#\\+begin_warning\\|\\\\begin{warning}" . box-warning)
          ("^[ \t]*#\\+begin_info\\|\\\\begin{info}"       . box-info)
          ("^[ \t]*#\\+begin_success\\|\\\\begin{success}" . box-success)
          ("^[ \t]*#\\+begin_error\\|\\\\begin{error}"     . box-error))
        "Org feature tests and associated LaTeX feature flags.
      
      Alist where the car is a test for the presense of the feature,
      and the cdr is either a single feature symbol or list of feature symbols.
      
      When a string, it is used as a regex search in the buffer.
      The feature is registered as present when there is a match.
      
      The car can also be a
      - symbol, the value of which is fetched
      - function, which is called with info as an argument
      - list, which is `eval'uated
      
      If the symbol, function, or list produces a string: that is used as a regex
      search in the buffer. Otherwise any non-nil return value will indicate the
      existance of the feature.")
      
      Emacs Lisp
      #
      (defvar org-latex-caption-preamble "
      \\usepackage{subcaption}
      \\usepackage[hypcap=true]{caption}
      \\setkomafont{caption}{\\sffamily\\small}
      \\setkomafont{captionlabel}{\\upshape\\bfseries}
      \\captionsetup{justification=raggedright,singlelinecheck=true}
      \\usepackage{capt-of} % required by Org
      "
        "Preamble that improves captions.")
      
      (defvar org-latex-checkbox-preamble "
      \\newcommand{\\checkboxUnchecked}{$\\square$}
      \\newcommand{\\checkboxTransitive}{\\rlap{\\raisebox{-0.1ex}{\\hspace{0.35ex}\\Large\\textbf -}}$\\square$}
      \\newcommand{\\checkboxChecked}{\\rlap{\\raisebox{0.2ex}{\\hspace{0.35ex}\\scriptsize \\ding{52}}}$\\square$}
      "
        "Preamble that improves checkboxes.")
      
      (defvar org-latex-box-preamble "
      % args = #1 Name, #2 Colour, #3 Ding, #4 Label
      \\newcommand{\\defsimplebox}[4]{%
        \\definecolor{#1}{HTML}{#2}
        \\newenvironment{#1}[1][]
        {%
          \\par\\vspace{-0.7\\baselineskip}%
          \\textcolor{#1}{#3} \\textcolor{#1}{\\textbf{\\def\\temp{##1}\\ifx\\temp\\empty#4\\else##1\\fi}}%
          \\vspace{-0.8\\baselineskip}
          \\begin{addmargin}[1em]{1em}
        }{%
          \\end{addmargin}
          \\vspace{-0.5\\baselineskip}
        }%
      }
      "
        "Preamble that provides a macro for custom boxes.")
      
      Emacs Lisp
      #
      (defvar org-latex-feature-implementations
        '((image         :snippet "\\usepackage{graphicx}" :order 2)
          (svg           :snippet "\\usepackage{svg}" :order 2)
          (table         :snippet "\\usepackage{longtable}\n\\usepackage{booktabs}" :order 2)
          (cleveref      :snippet "\\usepackage[capitalize]{cleveref}" :order 1)
          (underline     :snippet "\\usepackage[normalem]{ulem}" :order 0.5)
          (float-wrap    :snippet "\\usepackage{wrapfig}" :order 2)
          (rotate        :snippet "\\usepackage{rotating}" :order 2)
          (caption       :snippet org-latex-caption-preamble :order 2.1)
          (acronym       :snippet "\\newcommand{\\acr}[1]{\\protect\\textls*[110]{\\scshape #1}}\n\\newcommand{\\acrs}{\\protect\\scalebox{.91}[.84]\\hspace{0.15ex}s}" :order 0.4)
          (italic-quotes :snippet "\\renewcommand{\\quote}{\\list{}{\\rightmargin\\leftmargin}\\item\\relax\\em}\n" :order 0.5)
          (par-sep       :snippet "\\setlength{\\parskip}{\\baselineskip}\n\\setlength{\\parindent}{0pt}\n" :order 0.5)
          (.pifont       :snippet "\\usepackage{pifont}")
          (checkbox      :requires .pifont :order 3
                         :snippet (concat (unless (memq 'maths features)
                                            "\\usepackage{amssymb} % provides \\square")
                                          org-latex-checkbox-preamble))
          (.fancy-box    :requires .pifont    :snippet org-latex-box-preamble :order 3.9)
          (box-warning   :requires .fancy-box :snippet "\\defsimplebox{warning}{e66100}{\\ding{68}}{Warning}" :order 4)
          (box-info      :requires .fancy-box :snippet "\\defsimplebox{info}{3584e4}{\\ding{68}}{Information}" :order 4)
          (box-success   :requires .fancy-box :snippet "\\defsimplebox{success}{26a269}{\\ding{68}}{\\vspace{-\\baselineskip}}" :order 4)
          (box-error     :requires .fancy-box :snippet "\\defsimplebox{error}{c01c28}{\\ding{68}}{Important}" :order 4))
        "LaTeX features and details required to implement them.
      
      List where the car is the feature symbol, and the rest forms a plist with the
      following keys:
      - :snippet, which may be either
        - a string which should be included in the preamble
        - a symbol, the value of which is included in the preamble
        - a function, which is evaluated with the list of feature flags as its
          single argument. The result of which is included in the preamble
        - a list, which is passed to `eval', with a list of feature flags available
          as \"features\"
      
      - :requires, a feature or list of features that must be available
      - :when, a feature or list of features that when all available should cause this
          to be automatically enabled.
      - :prevents, a feature or list of features that should be masked
      - :order, for when ordering is important. Lower values appear first.
          The default is 0.
      
      Features that start with ! will be eagerly loaded, i.e. without being detected.")
      
      Emacs Lisp
      #
      (defun org-latex-detect-features (&optional buffer info)
        "List features from `org-latex-conditional-features' detected in BUFFER."
        (let ((case-fold-search nil))
          (with-current-buffer (or buffer (current-buffer))
            (delete-dups
             (mapcan (lambda (construct-feature)
                       (when (let ((out (pcase (car construct-feature)
                                          ((pred stringp) (car construct-feature))
                                          ((pred functionp) (funcall (car construct-feature) info))
                                          ((pred listp) (eval (car construct-feature)))
                                          ((pred symbolp) (symbol-value (car construct-feature)))
                                          (_ (user-error "org-latex-conditional-features key %s unable to be used" (car construct-feature))))))
                               (if (stringp out)
                                   (save-excursion
                                     (goto-char (point-min))
                                     (re-search-forward out nil t))
                                 out))
                         (if (listp (cdr construct-feature)) (cdr construct-feature) (list (cdr construct-feature)))))
                     org-latex-conditional-features)))))
      
      Emacs Lisp
      #
      (defun org-latex-expand-features (features)
        "For each feature in FEATURES process :requires, :when, and :prevents keywords and sort according to :order."
        (dolist (feature features)
          (unless (assoc feature org-latex-feature-implementations)
            (error "Feature %s not provided in org-latex-feature-implementations" feature)))
        (setq current features)
        (while current
          (when-let ((requirements (plist-get (cdr (assq (car current) org-latex-feature-implementations)) :requires)))
            (setcdr current (if (listp requirements)
                                (append requirements (cdr current))
                              (cons requirements (cdr current)))))
          (setq current (cdr current)))
        (dolist (potential-feature
                 (append features (delq nil (mapcar (lambda (feat)
                                                      (when (plist-get (cdr feat) :eager)
                                                        (car feat)))
                                                    org-latex-feature-implementations))))
          (when-let ((prerequisites (plist-get (cdr (assoc potential-feature org-latex-feature-implementations)) :when)))
            (setf features (if (if (listp prerequisites)
                                   (cl-every (lambda (preq) (memq preq features)) prerequisites)
                                 (memq prerequisites features))
                               (append (list potential-feature) features)
                             (delq potential-feature features)))))
        (dolist (feature features)
          (when-let ((prevents (plist-get (cdr (assoc feature org-latex-feature-implementations)) :prevents)))
            (setf features (cl-set-difference features (if (listp prevents) prevents (list prevents))))))
        (sort (delete-dups features)
              (lambda (feat1 feat2)
                (if (< (or (plist-get (cdr (assoc feat1 org-latex-feature-implementations)) :order) 1)
                       (or (plist-get (cdr (assoc feat2 org-latex-feature-implementations)) :order) 1))
                    t nil))))
      
      Emacs Lisp
      #
      (defun org-latex-generate-features-preamble (features)
        "Generate the LaTeX preamble content required to provide FEATURES.
      This is done according to `org-latex-feature-implementations'"
        (let ((expanded-features (org-latex-expand-features features)))
          (concat
           (format "\n%% features: %s\n" expanded-features)
           (mapconcat (lambda (feature)
                        (when-let ((snippet (plist-get (cdr (assoc feature org-latex-feature-implementations)) :snippet)))
                          (concat
                           (pcase snippet
                             ((pred stringp) snippet)
                             ((pred functionp) (funcall snippet features))
                             ((pred listp) (eval `(let ((features ',features)) (,@snippet))))
                             ((pred symbolp) (symbol-value snippet))
                             (_ (user-error "org-latex-feature-implementations :snippet value %s unable to be used" snippet)))
                           "\n")))
                      expanded-features
                      "")
           "% end features\n")))
      
      Emacs Lisp
      #
      (defvar info--tmp nil)
      
      (defadvice! org-latex-save-info (info &optional t_ s_)
        :before #'org-latex-make-preamble
        (setq info--tmp info))
      
      (defadvice! org-splice-latex-header-and-generated-preamble-a (orig-fn tpl def-pkg pkg snippets-p &optional extra)
        "Dynamically insert preamble content based on `org-latex-conditional-preambles'."
        :around #'org-splice-latex-header
        (let ((header (funcall orig-fn tpl def-pkg pkg snippets-p extra)))
          (if snippets-p header
            (concat header
                    (org-latex-generate-features-preamble (org-latex-detect-features nil info--tmp))
                    "\n"))))
      
    2. Embed Externally Linked Images

      I don’t like to keep images downloaded to my laptop, it clutters up everything. Org has a handy feature where you can pass a link instead, and org will display it inline as usual.

      HTML export handles this use case just fine, if the image isn’t named then it will display the image. However, latex doesn’t have support for this. What we do is instead of linking the image, we can have emacs download the linked image and export that!

      Emacs Lisp
      #
      (defadvice! +org-latex-link (orig-fn link desc info)
        "Acts as `org-latex-link', but supports remote images."
        :around #'org-latex-link
        (setq o-link link
              o-desc desc
              o-info info)
        (if (and (member (plist-get (cadr link) :type) '("http" "https"))
                 (member (file-name-extension (plist-get (cadr link) :path))
                         '("png" "jpg" "jpeg" "pdf" "svg")))
            (org-latex-link--remote link desc info)
          (funcall orig-fn link desc info)))
      
      (defun org-latex-link--remote (link _desc info)
        (let* ((url (plist-get (cadr link) :raw-link))
               (ext (file-name-extension url))
               (target (format "%s%s.%s"
                               (temporary-file-directory)
                               (replace-regexp-in-string "[./]" "-"
                                                         (file-name-sans-extension (substring (plist-get (cadr link) :path) 2)))
                               ext)))
          (unless (file-exists-p target)
            (url-copy-file url target))
          (setcdr link (--> (cadr link)
                         (plist-put it :type "file")
                         (plist-put it :path target)
                         (plist-put it :raw-link (concat "file:" target))
                         (list it)))
          (concat "% fetched from " url "\n"
                  (org-latex--inline-image link info))))
      
    3. LatexMK

      Tectonic is the hot new thing, which also means I can get rid of my tex installation. Dependencies are nice and auto-installed, and I don’t need to bother with ascii stuff

      On the other hand, it still refuses to work with previews and just sucks with emacs overall. Back to LatexMK for me

      Emacs Lisp
      #
      (setq org-latex-pdf-process (list "latexmk -f -pdflatex='xelatex -shell-escape -interaction nonstopmode' -pdf -output-directory=%o %f"))
      
      (setq xdvsvgm
              '(xdvsvgm
                :programs ("xelatex" "dvisvgm")
                :description "xdv > svg"
                :message "you need to install the programs: xelatex and dvisvgm."
                :use-xcolor t
                :image-input-type "xdv"
                :image-output-type "svg"
                :image-size-adjust (1.7 . 1.5)
                :latex-compiler ("xelatex -no-pdf -interaction nonstopmode -output-directory %o %f")
                :image-converter ("dvisvgm %f -n -b min -c %S -o %O")))
      
      (after! org
          (add-to-list 'org-preview-latex-process-alist xdvsvgm)
          (setq org-format-latex-options
            (plist-put org-format-latex-options :scale 1.4))
          (setq org-preview-latex-default-process 'xdvsvgm))
      

      Looks crisp!

      \begin{align*} f(x) &= x^2\\ g(x) &= \frac{1}{x}\\ F(x) &= \int^a_b \frac{1}{3}x^3 \end{align*}
      1. Compilation
        Emacs Lisp
        #
        (setq TeX-save-query nil
              TeX-show-compilation t
              TeX-command-extra-options "-shell-escape")
        
        (after! latex
          (add-to-list 'TeX-command-list '("XeLaTeX" "%`xelatex%(mode)%' %t" TeX-run-TeX nil t)))
        
    4. Classes

      Now for some class setup

      Emacs Lisp
      #
      (after! ox-latex
        (add-to-list 'org-latex-classes
                     '("cb-doc" "\\documentclass{scrartcl}"
                       ("\\section{%s}" . "\\section*{%s}")
                       ("\\subsection{%s}" . "\\subsection*{%s}")
                       ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                       ("\\paragraph{%s}" . "\\paragraph*{%s}")
                       ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))
      

      And some saner defaults for them

      Emacs Lisp
      #
      (after! ox-latex
        (setq org-latex-default-class "cb-doc"
              org-latex-tables-booktabs t
              org-latex-hyperref-template "\\colorlet{greenyblue}{blue!70!green}
      \\colorlet{blueygreen}{blue!40!green}
      \\providecolor{link}{named}{greenyblue}
      \\providecolor{cite}{named}{blueygreen}
      \\hypersetup{
        pdfauthor={%a},
        pdftitle={%t},
        pdfkeywords={%k},
        pdfsubject={%d},
        pdfcreator={%c},
        pdflang={%L},
        breaklinks=true,
        colorlinks=true,
        linkcolor=,
        urlcolor=link,
        citecolor=cite\n}
      \\urlstyle{same}
      "
              org-latex-reference-command "\\cref{%s}"))
      
    5. Packages

      Add some packages. I’m trying to keep it basic for now, Alegreya for non-monospace and SFMono for code

      Emacs Lisp
      #
      (setq org-latex-default-packages-alist
            `(("AUTO" "inputenc" t
               ("pdflatex"))
              ("T1" "fontenc" t
               ("pdflatex"))
              ("" "fontspec" t)
              ("" "graphicx" t)
              ("" "grffile" t)
              ("" "longtable" nil)
              ("" "wrapfig" nil)
              ("" "rotating" nil)
              ("normalem" "ulem" t)
              ("" "amsmath" t)
              ("" "textcomp" t)
              ("" "amssymb" t)
              ("" "capt-of" nil)
              ("dvipsnames" "xcolor" nil)
              ("colorlinks=true, linkcolor=Blue, citecolor=BrickRed, urlcolor=PineGreen" "hyperref" nil)
          ("" "indentfirst" nil)))
      
    6. Pretty code blocks

      Teco is the goto for this, so basically just ripping off him. Engrave faces ftw

      Emacs Lisp
      #
      (use-package! engrave-faces-latex
        :after ox-latex
        :config
        (setq org-latex-listings 'engraved
              engrave-faces-preset-styles (engrave-faces-generate-preset)))
      
      Emacs Lisp
      #
      (defadvice! org-latex-src-block-engraved (orig-fn src-block contents info)
        "Like `org-latex-src-block', but supporting an engraved backend"
        :around #'org-latex-src-block
        (if (eq 'engraved (plist-get info :latex-listings))
            (org-latex-scr-block--engraved src-block contents info)
          (funcall orig-fn src-block contents info)))
      
      (defadvice! org-latex-inline-src-block-engraved (orig-fn inline-src-block contents info)
        "Like `org-latex-inline-src-block', but supporting an engraved backend"
        :around #'org-latex-inline-src-block
        (if (eq 'engraved (plist-get info :latex-listings))
            (org-latex-inline-scr-block--engraved inline-src-block contents info)
          (funcall orig-fn src-block contents info)))
      
      (defvar-local org-export-has-code-p nil)
      
      (defadvice! org-export-expect-no-code (&rest _)
        :before #'org-export-as
        (setq org-export-has-code-p nil))
      
      (defadvice! org-export-register-code (&rest _)
        :after #'org-latex-src-block-engraved
        :after #'org-latex-inline-src-block-engraved
        (setq org-export-has-code-p t))
      
      (setq org-latex-engraved-code-preamble "
      \\usepackage{fvextra}
      \\fvset{
        commandchars=\\\\\\{\\},
        highlightcolor=white!95!black!80!blue,
        breaklines=true,
        breaksymbol=\\color{white!60!black}\\tiny\\ensuremath{\\hookrightarrow}}
      \\renewcommand\\theFancyVerbLine{\\footnotesize\\color{black!40!white}\\arabic{FancyVerbLine}}
      
      \\definecolor{codebackground}{HTML}{f7f7f7}
      \\definecolor{codeborder}{HTML}{f0f0f0}
      
      % TODO have code boxes keep line vertical alignment
      \\usepackage[breakable,xparse]{tcolorbox}
      \\DeclareTColorBox[]{Code}{o}%
      {colback=codebackground, colframe=codeborder,
        fontupper=\\footnotesize,
        colupper=EFD,
        IfNoValueTF={#1}%
        {boxsep=2pt, arc=2.5pt, outer arc=2.5pt,
          boxrule=0.5pt, left=2pt}%
        {boxsep=2.5pt, arc=0pt, outer arc=0pt,
          boxrule=0pt, leftrule=1.5pt, left=0.5pt},
        right=2pt, top=1pt, bottom=0.5pt,
        breakable}
      ")
      
      (add-to-list 'org-latex-conditional-features '((and org-export-has-code-p "^[ \t]*#\\+begin_src\\|^[ \t]*#\\+BEGIN_SRC\\|src_[A-Za-z]") . engraved-code) t)
      (add-to-list 'org-latex-conditional-features '("^[ \t]*#\\+begin_example\\|^[ \t]*#\\+BEGIN_EXAMPLE" . engraved-code-setup) t)
      (add-to-list 'org-latex-feature-implementations '(engraved-code :requires engraved-code-setup :snippet (engrave-faces-latex-gen-preamble) :order 99) t)
      (add-to-list 'org-latex-feature-implementations '(engraved-code-setup :snippet org-latex-engraved-code-preamble :order 98) t)
      
      (defun org-latex-scr-block--engraved (src-block contents info)
        (let* ((lang (org-element-property :language src-block))
               (attributes (org-export-read-attribute :attr_latex src-block))
               (float (plist-get attributes :float))
               (num-start (org-export-get-loc src-block info))
               (retain-labels (org-element-property :retain-labels src-block))
               (caption (org-element-property :caption src-block))
               (caption-above-p (org-latex--caption-above-p src-block info))
               (caption-str (org-latex--caption/label-string src-block info))
               (placement (or (org-unbracket-string "[" "]" (plist-get attributes :placement))
                              (plist-get info :latex-default-figure-position)))
               (float-env
                (cond
                 ((string= "multicolumn" float)
                  (format "\\begin{listing*}[%s]\n%s%%s\n%s\\end{listing*}"
                          placement
                          (if caption-above-p caption-str "")
                          (if caption-above-p "" caption-str)))
                 (caption
                  (format "\\begin{listing}[%s]\n%s%%s\n%s\\end{listing}"
                          placement
                          (if caption-above-p caption-str "")
                          (if caption-above-p "" caption-str)))
                 ((string= "t" float)
                  (concat (format "\\begin{listing}[%s]\n"
                                  placement)
                          "%s\n\\end{listing}"))
                 (t "%s")))
               (options (plist-get info :latex-minted-options))
               (content-buffer
                (with-temp-buffer
                  (insert
                   (let* ((code-info (org-export-unravel-code src-block))
                          (max-width
                           (apply 'max
                                  (mapcar 'length
                                          (org-split-string (car code-info)
                                                            "\n")))))
                     (org-export-format-code
                      (car code-info)
                      (lambda (loc _num ref)
                        (concat
                         loc
                         (when ref
                           ;; Ensure references are flushed to the right,
                           ;; separated with 6 spaces from the widest line
                           ;; of code.
                           (concat (make-string (+ (- max-width (length loc)) 6)
                                                ?\s)
                                   (format "(%s)" ref)))))
                      nil (and retain-labels (cdr code-info)))))
                  (funcall (org-src-get-lang-mode lang))
                  (engrave-faces-latex-buffer)))
               (content
                (with-current-buffer content-buffer
                  (buffer-string)))
               (body
                (format
                 "\\begin{Code}\n\\begin{Verbatim}[%s]\n%s\\end{Verbatim}\n\\end{Code}"
                 ;; Options.
                 (concat
                  (org-latex--make-option-string
                   (if (or (not num-start) (assoc "linenos" options))
                       options
                     (append
                      `(("linenos")
                        ("firstnumber" ,(number-to-string (1+ num-start))))
                      options)))
                  (let ((local-options (plist-get attributes :options)))
                    (and local-options (concat "," local-options))))
                 content)))
          (kill-buffer content-buffer)
          ;; Return value.
          (format float-env body)))
      
      (defun org-latex-inline-scr-block--engraved (inline-src-block _contents info)
        (let ((options (org-latex--make-option-string
                        (plist-get info :latex-minted-options)))
              code-buffer code)
          (setq code-buffer
                (with-temp-buffer
                  (insert (org-element-property :value inline-src-block))
                  (funcall (org-src-get-lang-mode
                            (org-element-property :language inline-src-block)))
                  (engrave-faces-latex-buffer)))
          (setq code (with-current-buffer code-buffer
                       (buffer-string)))
          (kill-buffer code-buffer)
          (format "\\Verb%s{%s}"
                  (if (string= options "") ""
                    (format "[%s]" options))
                  code)))
      
      (defadvice! org-latex-example-block-engraved (orig-fn example-block contents info)
        "Like `org-latex-example-block', but supporting an engraved backend"
        :around #'org-latex-example-block
        (let ((output-block (funcall orig-fn example-block contents info)))
          (if (eq 'engraved (plist-get info :latex-listings))
              (format "\\begin{Code}[alt]\n%s\n\\end{Code}" output-block)
            output-block)))
      
    7. ox-chameleon

      Nice little package to color stuff for us.

      Emacs Lisp
      #
      (use-package! ox-chameleon
        :after ox
        :config
        (setq ox-chameleon-snap-fgbg-to-bw nil))
      
    8. Async

      Run export processes in a background … process

      Emacs Lisp
      #
      (setq org-export-in-background t)
      
    9. (sub|super)script characters

      Annoying having to gate these, so let’s fix that

      Emacs Lisp
      #
      (setq org-export-with-sub-superscripts '{})
      
  4. Calc

    Embedded calc is a lovely feature which let’s us use calc to operate on LaTeX maths expressions. The standard keybinding is a bit janky however (C-x * e), so we’ll add a localleader-based alternative.

    Emacs Lisp
    #
    (map! :map calc-mode-map
          :after calc
          :localleader
          :desc "Embedded calc (toggle)" "e" #'calc-embedded)
    (map! :map org-mode-map
          :after org
          :localleader
          :desc "Embedded calc (toggle)" "E" #'calc-embedded)
    (map! :map latex-mode-map
          :after latex
          :localleader
          :desc "Embedded calc (toggle)" "e" #'calc-embedded)
    

    Unfortunately this operates without the (rather informative) calculator and trail buffers, but we can advice it that we would rather like those in a side panel.

    Emacs Lisp
    #
    (defvar calc-embedded-trail-window nil)
    (defvar calc-embedded-calculator-window nil)
    
    (defadvice! calc-embedded-with-side-pannel (&rest _)
      :after #'calc-do-embedded
      (when calc-embedded-trail-window
        (ignore-errors
          (delete-window calc-embedded-trail-window))
        (setq calc-embedded-trail-window nil))
      (when calc-embedded-calculator-window
        (ignore-errors
          (delete-window calc-embedded-calculator-window))
        (setq calc-embedded-calculator-window nil))
      (when (and calc-embedded-info
                 (> (* (window-width) (window-height)) 1200))
        (let ((main-window (selected-window))
              (vertical-p (> (window-width) 80)))
          (select-window
           (setq calc-embedded-trail-window
                 (if vertical-p
                     (split-window-horizontally (- (max 30 (/ (window-width) 3))))
                   (split-window-vertically (- (max 8 (/ (window-height) 4)))))))
          (switch-to-buffer "*Calc Trail*")
          (select-window
           (setq calc-embedded-calculator-window
                 (if vertical-p
                     (split-window-vertically -6)
                   (split-window-horizontally (- (/ (window-width) 2))))))
          (switch-to-buffer "*Calculator*")
          (select-window main-window))))
    

5.1.8. Mu4e

Focus Knob I’m trying out emails in emacs, should be nice. Related, check .mbsyncrc to setup your emails first

10 minutes is a reasonable update time

Emacs Lisp
#
(setq mu4e-update-interval 300)
elisp
#
(set-email-account! "shaunsingh0207"
  '((mu4e-sent-folder       . "/Sent Mail")
    (mu4e-drafts-folder     . "/Drafts")
    (mu4e-trash-folder      . "/Trash")
    (mu4e-refile-folder     . "/All Mail")
    (smtpmail-smtp-user     . "shaunsingh0207@gmail.com")))

;; don't need to run cleanup after indexing for gmail
(setq mu4e-index-cleanup nil
      mu4e-index-lazy-check t)

(after! mu4e
  (setq mu4e-headers-fields
        '((:flags . 6)
          (:account-stripe . 2)
          (:from-or-to . 25)
          (:folder . 10)
          (:recipnum . 2)
          (:subject . 80)
          (:human-date . 8))
        +mu4e-min-header-frame-width 142
        mu4e-headers-date-format "%d/%m/%y"
        mu4e-headers-time-format "⧖ %H:%M"
        mu4e-headers-results-limit 1000
        mu4e-index-cleanup t)

  (add-to-list 'mu4e-bookmarks
               '(:name "Yesterday's messages" :query "date:2d..1d" :key ?y) t)

  (defvar +mu4e-header--folder-colors nil)
  (appendq! mu4e-header-info-custom
            '((:folder .
               (:name "Folder" :shortname "Folder" :help "Lowest level folder" :function
                (lambda (msg)
                  (+mu4e-colorize-str
                   (replace-regexp-in-string "\\`.*/" "" (mu4e-message-field msg :maildir))
                   '+mu4e-header--folder-colors)))))))

We can also send messages using msmtp

Emacs Lisp
#
(after! mu4e
  (setq sendmail-program "msmtp"
        send-mail-function #'smtpmail-send-it
        message-sendmail-f-is-evil t
        message-sendmail-extra-arguments '("--read-envelope-from")
        message-send-mail-function #'message-send-mail-with-sendmail))

Notifications are quite nifty, especially if I’m as lazy as I am

Emacs Lisp
#
;;(setq alert-default-style 'osx-notifier)

5.1.9. Browsing

  1. Webkit

    Eventually I want to use emacs for everything. Instead of using xwidgets, which requires a custom (non-cached) build of emacs. Emacs-webkit is a good alternative, but is quite buggy right now. Once its stable, I’ll fix this config

    Emacs Lisp
    #
    ;;(use-package org
    ;;  :demand t)
    
    ;; (use-package webkit
    ;;   :defer t
    ;;   :commands webkit
    ;;   :init
    ;;   (setq webkit-search-prefix "https://google.com/search?q="
    ;;         webkit-history-file nil
    ;;         webkit-cookie-file nil
    ;;         browse-url-browser-function 'webkit-browse-url
    ;;         webkit-browse-url-force-new t
    ;;         webkit-download-action-alist '(("\\.pdf\\'" . webkit-download-open)
    ;;                                        ("\\.png\\'" . webkit-download-save)
    ;;                                        (".*" . webkit-download-default)))
    
    ;;   (defun webkit--display-progress (progress)
    ;;     (setq webkit--progress-formatted
    ;;           (if (equal progress 100.0)
    ;;               ""
    ;;             (format "%s%.0f%%  " (all-the-icons-faicon "spinner") progress)))
    ;;    (force-mode-line-update)))
    

    I also want to use evil bindings with this. It’s not upstreamed yet, so I’ll steal the ones from the repo

    elisp
    #
    ;; (use-package evil-collection-webkit
    ;;    :defer t
    ;;    :config
    ;;    (evil-collection-xwidget-setup))
    
  2. IRC

    Team Chat

    I’m trying to move everything to emacs, and discord is the one electron app I need to ditch. With bitlbee and circe it should be possible

    To make this easier, I

    1. Have everything (serverinfo and passwords) in an authinfo.gpg file
    2. Tell circe to use it
    3. Use org syntax for formatting
    4. Add emoji support
    5. Set it up with discord
    irc-authinfo-readerEmacs Lisp
    #
    (defun auth-server-pass (server)
      (if-let ((secret (plist-get (car (auth-source-search :host server)) :secret)))
          (if (functionp secret)
              (funcall secret) secret)
        (error "Could not fetch password for host %s" server)))
    
    (defun register-irc-auths ()
      (require 'circe)
      (require 'dash)
      (let ((accounts (-filter (lambda (a) (string= "irc" (plist-get a :for)))
                               (auth-source-search :require '(:for) :max 10))))
        (appendq! circe-network-options
                  (mapcar (lambda (entry)
                            (let* ((host (plist-get entry :host))
                                   (label (or (plist-get entry :label) host))
                                   (_ports (mapcar #'string-to-number
                                                   (s-split "," (plist-get entry :port))))
                                   (port (if (= 1 (length _ports)) (car _ports) _ports))
                                   (user (plist-get entry :user))
                                   (nick (or (plist-get entry :nick) user))
                                   (channels (mapcar (lambda (c) (concat "#" c))
                                                     (s-split "," (plist-get entry :channels)))))
                              `(,label
                                :host ,host :port ,port :nick ,nick
                                :sasl-username ,user :sasl-password auth-server-pass
                                :channels ,channels)))
                          accounts))))
    

    We’ll just call (register-irc-auths) on a hook when we start Circe up.

    Now we’re ready to go, let’s actually wire-up Circe, with one or two configuration tweaks.

    Emacs Lisp
    #
    (after! circe
      (setq-default circe-use-tls t)
      (setq circe-notifications-alert-icon "/usr/share/icons/breeze/actions/24/network-connect.svg"
            lui-logging-directory "~/.emacs.d/.local/etc/irc"
            lui-logging-file-format "{buffer}/%Y/%m-%d.txt"
            circe-format-self-say "{nick:+13s} ┃ {body}")
    
      (custom-set-faces!
        '(circe-my-message-face :weight unspecified))
    
      (enable-lui-logging-globally)
      (enable-circe-display-images)
    
      <<org-emph-to-irc>>
    
      <<circe-emojis>>
      <<circe-emoji-alists>>
    
      (defun named-circe-prompt ()
        (lui-set-prompt
         (concat (propertize (format "%13s > " (circe-nick))
                             'face 'circe-prompt-face)
                 "")))
      (add-hook 'circe-chat-mode-hook #'named-circe-prompt)
    
      (appendq! all-the-icons-mode-icon-alist
                '((circe-channel-mode all-the-icons-material "message" :face all-the-icons-lblue)
                  (circe-server-mode all-the-icons-material "chat_bubble_outline" :face all-the-icons-purple))))
    
    <<irc-authinfo-reader>>
    
    (add-transient-hook! #'=irc (register-irc-auths))
    

    Let’s do our bold, italic, and underline in org-syntax, using IRC control characters.

    org-emph-to-ircEmacs Lisp
    #
    (defun lui-org-to-irc ()
      "Examine a buffer with simple org-mode formatting, and converts the empasis:
    *bold*, /italic/, and _underline_ to IRC semi-standard escape codes.
    =code= is converted to inverse (highlighted) text."
      (goto-char (point-min))
      (while (re-search-forward "\\_<\\(?1:[*/_=]\\)\\(?2:[^[:space:]]\\(?:.*?[^[:space:]]\\)?\\)\\1\\_>" nil t)
        (replace-match
         (concat (pcase (match-string 1)
                   ("*" "")
                   ("/" "")
                   ("_" "")
                   ("=" ""))
                 (match-string 2)
                 "") nil nil)))
    
    (add-hook 'lui-pre-input-hook #'lui-org-to-irc)
    

    Let’s setup Circe to use some emojis

    circe-emojisEmacs Lisp
    #
    (defun lui-ascii-to-emoji ()
      (goto-char (point-min))
      (while (re-search-forward "\\( \\)?::?\\([^[:space:]:]+\\):\\( \\)?" nil t)
        (replace-match
         (concat
          (match-string 1)
          (or (cdr (assoc (match-string 2) lui-emojis-alist))
              (concat ":" (match-string 2) ":"))
          (match-string 3))
         nil nil)))
    
    (defun lui-emoticon-to-emoji ()
      (dolist (emoticon lui-emoticons-alist)
        (goto-char (point-min))
        (while (re-search-forward (concat " " (car emoticon) "\\( \\)?") nil t)
          (replace-match (concat " "
                                 (cdr (assoc (cdr emoticon) lui-emojis-alist))
                                 (match-string 1))))))
    
    (define-minor-mode lui-emojify
      "Replace :emojis: and ;) emoticons with unicode emoji chars."
      :global t
      :init-value t
      (if lui-emojify
          (add-hook! lui-pre-input #'lui-ascii-to-emoji #'lui-emoticon-to-emoji)
        (remove-hook! lui-pre-input #'lui-ascii-to-emoji #'lui-emoticon-to-emoji)))
    

    Now, some actual emojis to use.

    circe-emoji-alistsEmacs Lisp
    #
    (defvar lui-emojis-alist
      '(("grinning"                      . "😀")
        ("smiley"                        . "😃")
        ("smile"                         . "😄")
        ("grin"                          . "😁")
        ("laughing"                      . "😆")
        ("sweat_smile"                   . "😅")
        ("joy"                           . "😂")
        ("rofl"                          . "🤣")
        ("relaxed"                       . "☺️")
        ("blush"                         . "😊")
        ("innocent"                      . "😇")
        ("slight_smile"                  . "🙂")
        ("upside_down"                   . "🙃")
        ("wink"                          . "😉")
        ("relieved"                      . "😌")
        ("heart_eyes"                    . "😍")
        ("yum"                           . "😋")
        ("stuck_out_tongue"              . "😛")
        ("stuck_out_tongue_closed_eyes"  . "😝")
        ("stuck_out_tongue_wink"         . "😜")
        ("zanzy"                         . "🤪")
        ("raised_eyebrow"                . "🤨")
        ("monocle"                       . "🧐")
        ("nerd"                          . "🤓")
        ("cool"                          . "😎")
        ("star_struck"                   . "🤩")
        ("party"                         . "🥳")
        ("smirk"                         . "😏")
        ("unamused"                      . "😒")
        ("disapointed"                   . "😞")
        ("pensive"                       . "😔")
        ("worried"                       . "😟")
        ("confused"                      . "😕")
        ("slight_frown"                  . "🙁")
        ("frown"                         . "☹️")
        ("persevere"                     . "😣")
        ("confounded"                    . "😖")
        ("tired"                         . "😫")
        ("weary"                         . "😩")
        ("pleading"                      . "🥺")
        ("tear"                          . "😢")
        ("cry"                           . "😢")
        ("sob"                           . "😭")
        ("triumph"                       . "😤")
        ("angry"                         . "😠")
        ("rage"                          . "😡")
        ("exploding_head"                . "🤯")
        ("flushed"                       . "😳")
        ("hot"                           . "🥵")
        ("cold"                          . "🥶")
        ("scream"                        . "😱")
        ("fearful"                       . "😨")
        ("disapointed"                   . "😰")
        ("relieved"                      . "😥")
        ("sweat"                         . "😓")
        ("thinking"                      . "🤔")
        ("shush"                         . "🤫")
        ("liar"                          . "🤥")
        ("blank_face"                    . "😶")
        ("neutral"                       . "😐")
        ("expressionless"                . "😑")
        ("grimace"                       . "😬")
        ("rolling_eyes"                  . "🙄")
        ("hushed"                        . "😯")
        ("frowning"                      . "😦")
        ("anguished"                     . "😧")
        ("wow"                           . "😮")
        ("astonished"                    . "😲")
        ("sleeping"                      . "😴")
        ("drooling"                      . "🤤")
        ("sleepy"                        . "😪")
        ("dizzy"                         . "😵")
        ("zipper_mouth"                  . "🤐")
        ("woozy"                         . "🥴")
        ("sick"                          . "🤢")
        ("vomiting"                      . "🤮")
        ("sneeze"                        . "🤧")
        ("mask"                          . "😷")
        ("bandaged_head"                 . "🤕")
        ("money_face"                    . "🤑")
        ("cowboy"                        . "🤠")
        ("imp"                           . "😈")
        ("ghost"                         . "👻")
        ("alien"                         . "👽")
        ("robot"                         . "🤖")
        ("clap"                          . "👏")
        ("thumpup"                       . "👍")
        ("+1"                            . "👍")
        ("thumbdown"                     . "👎")
        ("-1"                            . "👎")
        ("ok"                            . "👌")
        ("pinch"                         . "🤏")
        ("left"                          . "👈")
        ("right"                         . "👉")
        ("down"                          . "👇")
        ("wave"                          . "👋")
        ("pray"                          . "🙏")
        ("eyes"                          . "👀")
        ("brain"                         . "🧠")
        ("facepalm"                      . "🤦")
        ("tada"                          . "🎉")
        ("fire"                          . "🔥")
        ("flying_money"                  . "💸")
        ("lighbulb"                      . "💡")
        ("heart"                         . "❤️")
        ("sparkling_heart"               . "💖")
        ("heartbreak"                    . "💔")
        ("100"                           . "💯")))
    
    (defvar lui-emoticons-alist
      '((":)"   . "slight_smile")
        (";)"   . "wink")
        (":D"   . "smile")
        ("=D"   . "grin")
        ("xD"   . "laughing")
        (";("   . "joy")
        (":P"   . "stuck_out_tongue")
        (";D"   . "stuck_out_tongue_wink")
        ("xP"   . "stuck_out_tongue_closed_eyes")
        (":("   . "slight_frown")
        (";("   . "cry")
        (";'("  . "sob")
        (">:("  . "angry")
        (">>:(" . "rage")
        (":o"   . "wow")
        (":O"   . "astonished")
        (":/"   . "confused")
        (":-/"  . "thinking")
        (":|"   . "neutral")
        (":-|"  . "expressionless")))
    

5.2. Neovim

There are many neovim configurations that exist (i.e. NvChad, Lunar Vim, etc.). However, many of these configurations suffer from a host of problems:

Some configurations (like NvChad), have very abstracted and complex codebases. Others rely on having as much overall functionality as possible (like LunarVim). While none of this is bad, there are some problems that can arise from these choices:

Complex codebases lead to less freedom for end-user extensiblity and configuration, as there is more reliance on the maintainer of said code. Users may not use half of what is made avalible to them simply because they don’t need all of that functionality, so all of it may not be necessary. This config provides a solution to these problems by providing only the necessary code in order to make a functioning configuration. The end goal of this personal neovim config is to be used as a base config for users to extend and add upon, leading to a more unique editing experience.

The configuration was originally based off of commit 29f04fc of NvChad, but this config has evolved to be much more than that.

You can now find it seperately on github, here: https://github.com/shaunsingh/nyoom.nvim

5.2.1. Develop

When sharing the config, it makes it much easier to handle dependencies with nix. This ensures installing dependencies (including neovim-nightly), as well as adding the new neovim build to path

nix
#
{
  description =
    ":rocket: Blazing Fast Neovim Configuration Written in lua :rocket::rocket::stars:";

  inputs.flake-utils.url = "github:numtide/flake-utils";
  inputs.neovim-nightly-overlay.url =
    "github:nix-community/neovim-nightly-overlay";

  outputs = { self, nixpkgs, flake-utils, neovim-nightly-overlay }:
    flake-utils.lib.simpleFlake {
      inherit self nixpkgs;
      name = "default.nvim";
      overlay = neovim-nightly-overlay.overlay;
      shell = ./shell.nix;
      systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
    };
}
nix
#
{ pkgs ? import <nixpkgs> {
  overlays = [
    (import (builtins.fetchTarball {
      url =
        "https://github.com/nix-community/neovim-nightly-overlay/archive/master.tar.gz";
    }))
  ];
} }:
with pkgs;
mkShell {
  buildInputs = [ neovim-nightly luajit ripgrep nodejs ];

  shellHook = ''
    alias nvim="nvim -u $(pwd)/init.lua"
  '';
}

5.2.2. Init

The init.lua first loads impatient.nvim if available (so we can cache the .lua files). It then disables builtin vim plugins, and loads the required modules for startup (packer_compiled.lua, mappings.lua, and options.lua)

Lua
#
--load impatient first
local impatient, impatient = pcall(require, "impatient")
if impatient then
   -- NOTE: currently broken, will fix soon
   --impatient.enable_profile()
end

--disable builtin plugins
local disabled_built_ins = {
   "2html_plugin",
   "getscript",
   "getscriptPlugin",
   "gzip",
   "logipat",
   "netrw",
   "netrwPlugin",
   "netrwSettings",
   "netrwFileHandlers",
   "matchit",
   "tar",
   "tarPlugin",
   "rrhelper",
   "spellfile_plugin",
   "vimball",
   "vimballPlugin",
   "zip",
   "zipPlugin",
}

for _, plugin in pairs(disabled_built_ins) do
   vim.g["loaded_" .. plugin] = 1
end

-- load options, mappings, and plugins
local nyoom_modules = {
   "options",
   "mappings",
   "packer_compiled",
}

for i = 1, #nyoom_modules, 1 do
   pcall(require, nyoom_modules[i])
end

5.2.3. Packer

My packer configuration is broken into two files: packerInit and pluginList. packerInit downloads packer if it isn’t present, lazy loads it if it is, and configures packer. Notably:

  • Put the packer_compiled file under /nvim/lua instead of /nvim/plugin so it can be chached by impatient.nvim
  • Use packer in a floating window instead of a split, and remove the borders
  • Increaes clone_timeout, just in case I’m on a more finicky network

pluginList contains the list of plugins, as well lazy loads and defines their configuration files.

Lua
#
vim.cmd "packadd packer.nvim"
local present, packer = pcall(require, "packer")

--clone packer if its missing
if not present then
   local packer_path = vim.fn.stdpath "data" .. "/site/pack/packer/opt/packer.nvim"

   print "Cloning packer.."
   -- remove the dir before cloning
   vim.fn.delete(packer_path, "rf")
   vim.fn.system {
      "git",
      "clone",
      "https://github.com/wbthomason/packer.nvim",
      "--depth",
      "20",
      packer_path,
   }

   vim.cmd "packadd packer.nvim"
   present, packer = pcall(require, "packer")

   if present then
      print "Packer cloned successfully."
   else
      error("Couldn't clone packer !\nPacker path: " .. packer_path)
   end
end

-- packer settings
return packer.init {
   -- tell packer to put packer_compiled under the /lua folder so its cached by impatient
   compile_path = vim.fn.stdpath "config" .. "/lua/packer_compiled.lua",
   display = {
      open_fn = function()
         return require("packer.util").float { border = "rounded" }
      end,
      prompt_border = "rounded",
   },
   git = {
      clone_timeout = 600, -- Timeout, in seconds, for git clones
   },
}
Lua
#
local present, packer = pcall(require, "packerInit")

if present then
   packer = require "packer"
else
   return false
end

local use = packer.use
return packer.startup(function()
   -- Have packer manage itself
   use {
      "wbthomason/packer.nvim",
      event = "VimEnter",
   }

   -- Startup optimizations
   use {
      "nathom/filetype.nvim",
   }

   use {
      "lewis6991/impatient.nvim",
   }

   use {
      "tweekmonster/startuptime.vim",
      cmd = "StartupTime",
   }

   use {
      "max397574/better-escape.nvim",
      event = "InsertEnter",
      config = function()
         require("better_escape").setup {
            mapping = { "jk", "jj" },
            clear_empty_lines = true,
            keys = "<Esc>",
         }
      end,
   }

   use {
      "Clutch-Squad-10669/nord.nvim",
      config = function()
         require("nord").set()
      end,
--      "~/.config/nvim/lua/ext/doom.nvim",
--      config = function()
--         vim.cmd[[colorscheme doom]]
--      end,
   }

   use {
      "folke/which-key.nvim",
      keys = "<space>",
      config = function()
         require("which-key").setup()
      end,
   }

   use {
      "kyazdani42/nvim-web-devicons",
   }

   use {
      "~/.config/nvim/lua/ext/statusline.nvim",
      config = function()
         require "plugins.statusline"
      end,
   }

   use {
      "akinsho/bufferline.nvim",
      config = function()
         require "plugins.bufferline"
      end,
   }

   use {
      "lukas-reineke/indent-blankline.nvim",
      config = function()
         require("plugins.others").blankline()
      end,
   }

   use {
      "norcalli/nvim-colorizer.lua",
      cmd = "ColorizerToggle",
      config = function()
         require("plugins.others").colorizer()
      end,
   }

   use {
      "nvim-treesitter/nvim-treesitter",
      config = function()
         require "plugins.treesitter"
      end,
   }

   use {
      "nvim-treesitter/playground",
      cmd = "TSPlayground",
   }

   use {
      "p00f/nvim-ts-rainbow",
      after = "nvim-treesitter",
   }

   use {
      "lewis6991/gitsigns.nvim",
   }

   use {
      "kyazdani42/nvim-tree.lua",
      cmd = { "NvimTreeToggle", "NvimTreeFocus" },
      config = function()
         require "plugins.nvimtree"
      end,
   }

   -- LSP (and copilot
   use {
      "github/copilot.vim",
      event = "InsertEnter",
   }

   use {
      "dccsillag/magma-nvim",
      cmd = { "MagmaInit" },
      run = ":UpdateRemotePlugins",
   }

   use {
      "neovim/nvim-lspconfig",
      config = function()
         require "plugins.lspconfig"
      end,
   }

   use {
      "williamboman/nvim-lsp-installer",
   }

   use {
      "ray-x/lsp_signature.nvim",
      after = "nvim-lspconfig",
      config = function()
         require("plugins.others").signature()
      end,
   }

   use {
      "rafamadriz/friendly-snippets",
      event = "InsertEnter",
   }

   use {
      "numToStr/Comment.nvim",
      after = "friendly-snippets",
      config = function()
         require("plugins.others").comment()
      end,
   }

   use {
      "hrsh7th/nvim-cmp",
      after = "friendly-snippets",
      config = function()
         require "plugins.cmp"
      end,
   }

   use {
      "L3MON4D3/LuaSnip",
      wants = "friendly-snippets",
      after = "nvim-cmp",
      config = function()
         require("plugins.others").luasnip()
      end,
   }

   use {
      "saadparwaiz1/cmp_luasnip",
      after = "LuaSnip",
   }

   use {
      "hrsh7th/cmp-nvim-lua",
      after = "nvim-cmp",
   }

   use {
      "hrsh7th/cmp-nvim-lsp",
      after = "nvim-cmp",
   }

   use {
      "lukas-reineke/cmp-rg",
      after = "nvim-cmp",
   }

   use {
      "ray-x/cmp-treesitter",
      after = "nvim-cmp",
   }

   use {
      "hrsh7th/cmp-path",
      after = "nvim-cmp",
   }

   use {
      "nvim-telescope/telescope.nvim",
      cmd = "Telescope",
      requires = {
         {
            "nvim-telescope/telescope-fzf-native.nvim",
            "nvim-lua/plenary.nvim",
            run = "make",
         },
      },
      config = function()
         require "plugins.telescope"
      end,
   }

   use {
      "VonHeikemen/fine-cmdline.nvim",
      requires = {
         "MunifTanjim/nui.nvim",
      },
      config = function()
         require("plugins.others").fineCmdline()
      end,
   }

   use {
      "VonHeikemen/searchbox.nvim",
      requires = {
         "MunifTanjim/nui.nvim",
      },
      config = function()
         require("plugins.others").searchbox()
      end,
   }

   use {
      "rcarriga/nvim-notify",
      config = function()
         vim.notify = require "notify"
         require("notify").setup {
            stages = "slide",
            timeout = 2500,
            minimum_width = 50,
            icons = {
               ERROR = "",
               WARN = "",
               INFO = "",
               DEBUG = "",
               TRACE = "✎",
            },
         }
      end,
   }

   use {
      "Pocco81/TrueZen.nvim",
      cmd = {
         "TZAtaraxis",
         "TZMinimalist",
         "TZFocus",
      },
      config = function()
         require "plugins.zenmode"
      end,
   }

   use {
      "folke/twilight.nvim",
      cmd = {
         "Twilight",
         "TwilightEnable",
      },
      config = function()
         require("twilight").setup {}
      end,
   }

   use {
      "phaazon/hop.nvim",
      cmd = {
         "HopWord",
         "HopLine",
         "HopChar1",
         "HopChar2",
         "HopPattern",
      },
      as = "hop",
      config = function()
         require("hop").setup()
      end,
   }

   use {
      "sindrets/diffview.nvim",
      after = "neogit",
   }

   use {
      "TimUntersberger/neogit",
      cmd = {
         "Neogit",
         "Neogit commit",
      },
      config = function()
         require "plugins.neogit"
      end,
   }

   use {
      "nvim-neorg/neorg",
      branch = "unstable",
      setup = vim.cmd "autocmd BufRead,BufNewFile *.norg setlocal filetype=norg",
      after = { "nvim-treesitter" }, -- you may also specify telescope
      ft = "norg",
      config = function()
         require "plugins.neorg"
      end,
   }

   use {
      "nvim-orgmode/orgmode",
      ft = "org",
      setup = vim.cmd "autocmd BufRead,BufNewFile *.org setlocal filetype=org",
      after = { "nvim-treesitter" },
      config = function()
         require("orgmode").setup {}
      end,
   }

   use {
      "nvim-neorg/neorg-telescope",
      ft = "norg",
   }
end)

5.2.4. Settings

As I said earlier, there are 3 required modules for startup (packer_compiled.lua, mappings.lua, and options.lua). Of that, packer_compiled.lua is generated using :PackerCompile, so we will focus on the other two.

  • mappings.lua contains all of my mappings. All of the mappings are the same as the defaults for Doom Emacs (with a few exceptions). To list all of the keybinds, run SPC h b b in doom (or SPC h b f for major-mode specific binds). The file also contains some commands, which allow for the lazy loading of packer.
  • options.lua contains all the basic options I want set before loading a buffer. Additionally, I want to disable the tilde fringe and filetype.vim (replaced with filetype.nvim).
Lua
#
-- helper function for clean mappings
local function map(mode, lhs, rhs, opts)
   local options = { noremap = true, silent = true }
   if opts then
      options = vim.tbl_extend("force", options, opts)
   end
   vim.api.nvim_set_keymap(mode, lhs, rhs, options)
end

vim.g.mapleader = " " --leader
map("n", ";", ":") --semicolon to enter command mode
map("n", "<leader>ww", "<cmd>HopWord<CR>") --easymotion/hop
map("n", "<leader>l", "<cmd>HopLine<CR>")
map("n", "<leader>fr", "<cmd>Telescope oldfiles<CR>") --fuzzy
map("n", "<leader>.", "<cmd>Telescope find_files<CR>")
map("n", "<leader>f", "<cmd>Telescope current_buffer_fuzzy_find<CR>")
map("n", "<leader>:", "<cmd>Telescope commands<CR>")
map("n", "<leader>bb", "<cmd>Telescope buffers<CR>")
map("n", "<leader>tz", "<cmd>TZAtaraxis<CR>") --ataraxis
map("n", "<leader>op", "<cmd>NvimTreeToggle<CR>") --nvimtree
map("n", "<leader>tw", "<cmd>set wrap!<CR>") --nvimtree
map("n", "<c-k>", "<cmd>wincmd k<CR>") --ctrlhjkl to navigate splits
map("n", "<c-j>", "<cmd>wincmd j<CR>")
map("n", "<c-h>", "<cmd>wincmd h<CR>")
map("n", "<c-l>", "<cmd>wincmd l<CR>")

-- since we lazy load packer.nvim, we need to load it when we run packer-related commands
vim.cmd "silent! command PackerCompile lua require 'pluginList' require('packer').compile()"
vim.cmd "silent! command PackerInstall lua require 'pluginList' require('packer').install()"
vim.cmd "silent! command PackerStatus lua require 'pluginList' require('packer').status()"
vim.cmd "silent! command PackerSync lua require 'pluginList' require('packer').sync()"
vim.cmd "silent! command PackerUpdate lua require 'pluginList' require('packer').update()"
Lua
#
-- Do not source the default filetype.vim
vim.g.did_load_filetypes = 1
vim.g.shell = "/bin/bash" --fish has speed issues with nvim-tree
vim.g.neovide_cursor_vfx_mode = "pixiedust" -- neovide trail
--vim.opt.fillchars = { eob = " " } -- disable tilde fringe
vim.opt.undofile = true -- enable persistent undo
vim.opt.swapfile = false -- disable swap
vim.opt.cursorline = true -- enable cursorline
vim.opt.mouse = "a" -- enable mouse
vim.opt.signcolumn = "yes" -- enable signcolumn
vim.opt.updatetime = 250
vim.opt.clipboard = "unnamedplus" -- enable universal clipboard
vim.opt.scrolloff = 3 -- leave 3 lines up/down while scrolling
vim.opt.tabstop = 4 -- tabs should be 4 "space" wide
vim.opt.shiftwidth = 4 -- tabs should be 4 "space" wide
vim.opt.expandtab = true -- tabs should be 4 "space" wide
vim.opt.linebreak = true -- clean linebreaks
vim.opt.number = false -- disable numbers
vim.opt.numberwidth = 2 -- two wide number column
vim.opt.guifont = "Liga SFMono Nerd Font:h14" -- set guifont for neovide
vim.opt.shortmess:append "casI" -- disable intro
vim.opt.whichwrap:append "<>hl" -- clean aligned wraps
vim.opt.guicursor:append "i:blinkwait700-blinkon400-blinkoff250"

--Remap for dealing with word wrap
vim.api.nvim_set_keymap("n", "k", "v:count == 0 ? 'gk' : 'k'", { noremap = true, expr = true, silent = true })
vim.api.nvim_set_keymap("n", "j", "v:count == 0 ? 'gj' : 'j'", { noremap = true, expr = true, silent = true })

-- Highlight on yank
vim.cmd [[
  augroup YankHighlight
    autocmd!
    autocmd TextYankPost * silent! lua vim.highlight.on_yank()
  augroup end
]]

5.2.5. Plugin Configuration

  1. Bufferline
    Lua
    #
    local present, bufferline = pcall(require, "bufferline")
    if not present then
       return
    end
    
    local colors = {
       bg = "NONE",
       black = "#242730",
       black2 = "#2a2e38",
       white = "#bbc2cf",
       fg = "#bbc2cf",
       yellow = "#FCCE7B",
       cyan = "#4db5bd",
       darkblue = "#51afef",
       green = "#7bc275",
       orange = "#e69055",
       purple = "#C57BDB",
       magenta = "#C57BDB",
       gray = "#62686E",
       blue = "#51afef",
       red = "#ff665c",
    }
    
    bufferline.setup {
       options = {
          offsets = { { filetype = "NvimTree", text = "", padding = 1 } },
          buffer_close_icon = "",
          modified_icon = "",
          close_icon = "λ",
          show_close_icon = true,
          left_trunc_marker = "",
          right_trunc_marker = "",
          max_name_length = 14,
          max_prefix_length = 13,
          tab_size = 20,
          show_tab_indicators = true,
          enforce_regular_tabs = false,
          view = "multiwindow",
          show_buffer_close_icons = true,
          separator_style = "thin",
          always_show_bufferline = false,
          diagnostics = false, -- "or nvim_lsp"
          custom_filter = function(buf_number)
             -- Func to filter out our managed/persistent split terms
             local present_type, type = pcall(function()
                return vim.api.nvim_buf_get_var(buf_number, "term_type")
             end)
    
             if present_type then
                if type == "vert" then
                   return false
                elseif type == "hori" then
                   return false
                else
                   return true
                end
             else
                return true
             end
          end,
       },
    
       highlights = {
          background = {
             guifg = colors.fg,
             guibg = colors.black2,
          },
    
          -- buffers
          buffer_selected = {
             guifg = colors.white,
             guibg = colors.black,
             gui = "bold",
          },
          buffer_visible = {
             guifg = colors.gray,
             guibg = colors.black2,
          },
    
          -- for diagnostics = "nvim_lsp"
          error = {
             guifg = colors.gray,
             guibg = colors.black2,
          },
          error_diagnostic = {
             guifg = colors.gray,
             guibg = colors.black2,
          },
    
          -- close buttons
          close_button = {
             guifg = colors.gray,
             guibg = colors.black2,
          },
          close_button_visible = {
             guifg = colors.gray,
             guibg = colors.black2,
          },
          close_button_selected = {
             guifg = colors.red,
             guibg = colors.black,
          },
          fill = {
             guifg = colors.fg,
             guibg = colors.black2,
          },
          indicator_selected = {
             guifg = colors.black,
             guibg = colors.black,
          },
    
          -- modified
          modified = {
             guifg = colors.red,
             guibg = colors.black2,
          },
          modified_visible = {
             guifg = colors.red,
             guibg = colors.black2,
          },
          modified_selected = {
             guifg = colors.green,
             guibg = colors.black,
          },
    
          -- separators
          separator = {
             guifg = colors.black2,
             guibg = colors.black2,
          },
          separator_visible = {
             guifg = colors.black2,
             guibg = colors.black2,
          },
          separator_selected = {
             guifg = colors.black2,
             guibg = colors.black2,
          },
          -- tabs
          tab = {
             guifg = colors.gray,
             guibg = colors.black2,
          },
          tab_selected = {
             guifg = colors.black2,
             guibg = colors.darkblue,
          },
          tab_close = {
             guifg = colors.red,
             guibg = colors.black,
          },
       },
    }
    
  2. Nvim-cmp
    Lua
    #
    local present, cmp = pcall(require, "cmp")
    
    if not present then
       return
    end
    
    vim.opt.completeopt = "menuone,noselect"
    
    -- nvim-cmp setup
    cmp.setup {
       snippet = {
          expand = function(args)
             require("luasnip").lsp_expand(args.body)
          end,
       },
       formatting = {
          format = function(entry, vim_item)
             vim_item.menu = ({
                rg = "rg",
                nvim_lsp = "LSP",
                nvim_lua = "Lua",
                Path = "Path",
                luasnip = "LuaSnip",
                neorg = "Neorg",
                orgmode = "Org",
                treesitter = "ts",
             })[entry.source.name]
             vim_item.kind = ({
                Text = "",
                Method = "",
                Function = "",
                Constructor = "",
                Field = "ﰠ",
                Variable = "",
                Class = "ﴯ",
                Interface = "",
                Module = "",
                Property = "ﰠ",
                Unit = "塞",
                Value = "",
                Enum = "",
                Keyword = "",
                Snippet = "",
                Color = "",
                File = "",
                Reference = "",
                Folder = "",
                EnumMember = "",
                Constant = "",
                Struct = "פּ",
                Event = "",
                Operator = "",
                TypeParameter = "",
             })[vim_item.kind]
             return vim_item
          end,
       },
       mapping = {
          ["<C-p>"] = cmp.mapping.select_prev_item(),
          ["<C-n>"] = cmp.mapping.select_next_item(),
          ["<C-d>"] = cmp.mapping.scroll_docs(-4),
          ["<C-f>"] = cmp.mapping.scroll_docs(4),
          ["<C-Space>"] = cmp.mapping.complete(),
          ["<C-e>"] = cmp.mapping.close(),
          ["<CR>"] = cmp.mapping.confirm {
             behavior = cmp.ConfirmBehavior.Replace,
             select = true,
          },
          ["<Tab>"] = function(fallback)
             if cmp.visible() then
                cmp.select_next_item()
             elseif require("luasnip").expand_or_jumpable() then
                vim.fn.feedkeys(vim.api.nvim_replace_termcodes("<Plug>luasnip-expand-or-jump", true, true, true), "")
             else
                fallback()
             end
          end,
          ["<S-Tab>"] = function(fallback)
             if cmp.visible() then
                cmp.select_prev_item()
             elseif require("luasnip").jumpable(-1) then
                vim.fn.feedkeys(vim.api.nvim_replace_termcodes("<Plug>luasnip-jump-prev", true, true, true), "")
             else
                fallback()
             end
          end,
       },
       sources = {
          { name = "nvim_lsp" },
          { name = "luasnip" },
          { name = "rg" },
          { name = "nvim_lua" },
          { name = "neorg" },
          { name = "treesitter" },
          { name = "orgmode" },
       },
    }
    
  3. Gitsigns
    Lua
    #
    local present, gitsigns = pcall(require, "gitsigns")
    if not present then
       return
    end
    
    gitsigns.setup {
       signs = {
          add = { hl = "GitSignsAdd", text = "┃", numhl = "GitSignsAddNr", linehl = "GitSignsAddLn" },
          change = { hl = "GitSignsChange", text = "┃", numhl = "GitSignsChangeNr", linehl = "GitSignsChangeLn" },
          delete = { hl = "GitSignsDelete", text = "▁", numhl = "GitSignsDeleteNr", linehl = "GitSignsDeleteLn" },
          topdelete = { hl = "GitSignsDelete", text = "▔", numhl = "GitSignsDeleteNr", linehl = "GitSignsDeleteLn" },
          changedelete = { hl = "GitSignsChange", text = "~", numhl = "GitSignsChangeNr", linehl = "GitSignsChangeLn" },
       },
       signcolumn = true, -- Toggle with `:Gitsigns toggle_signs`
       numhl = false, -- Toggle with `:Gitsigns toggle_numhl`
       linehl = false, -- Toggle with `:Gitsigns toggle_linehl`
       keymaps = {
          -- Default keymap options
          noremap = true,
    
          ["n ]c"] = { expr = true, "&diff ? ']c' : '<cmd>lua require\"gitsigns.actions\".next_hunk()<CR>'" },
          ["n [c"] = { expr = true, "&diff ? '[c' : '<cmd>lua require\"gitsigns.actions\".prev_hunk()<CR>'" },
    
          ["n <leader>hs"] = '<cmd>lua require"gitsigns".stage_hunk()<CR>',
          ["v <leader>hs"] = '<cmd>lua require"gitsigns".stage_hunk({vim.fn.line("."), vim.fn.line("v")})<CR>',
          ["n <leader>hu"] = '<cmd>lua require"gitsigns".undo_stage_hunk()<CR>',
          ["n <leader>hr"] = '<cmd>lua require"gitsigns".reset_hunk()<CR>',
          ["v <leader>hr"] = '<cmd>lua require"gitsigns".reset_hunk({vim.fn.line("."), vim.fn.line("v")})<CR>',
          ["n <leader>hR"] = '<cmd>lua require"gitsigns".reset_buffer()<CR>',
          ["n <leader>hp"] = '<cmd>lua require"gitsigns".preview_hunk()<CR>',
          ["n <leader>hb"] = '<cmd>lua require"gitsigns".blame_line(true)<CR>',
          ["n <leader>hS"] = '<cmd>lua require"gitsigns".stage_buffer()<CR>',
          ["n <leader>hU"] = '<cmd>lua require"gitsigns".reset_buffer_index()<CR>',
    
          -- Text objects
          ["o ih"] = ':<C-U>lua require"gitsigns.actions".select_hunk()<CR>',
          ["x ih"] = ':<C-U>lua require"gitsigns.actions".select_hunk()<CR>',
       },
       update_debounce = 200,
    }
    
  4. Lspconfig
    Lua
    #
    local present1, lspconfig = pcall(require, "lspconfig")
    local present2, lsp_installer = pcall(require, "nvim-lsp-installer")
    if not (present1 or present2) then
       return
    end
    
    local capabilities = vim.lsp.protocol.make_client_capabilities()
    capabilities.textDocument.completion.completionItem.documentationFormat = { "markdown", "plaintext" }
    capabilities.textDocument.completion.completionItem.snippetSupport = true
    capabilities.textDocument.completion.completionItem.preselectSupport = true
    capabilities.textDocument.completion.completionItem.insertReplaceSupport = true
    capabilities.textDocument.completion.completionItem.labelDetailsSupport = true
    capabilities.textDocument.completion.completionItem.deprecatedSupport = true
    capabilities.textDocument.completion.completionItem.commitCharactersSupport = true
    capabilities.textDocument.completion.completionItem.tagSupport = { valueSet = { 1 } }
    capabilities.textDocument.completion.completionItem.resolveSupport = {
       properties = {
          "documentation",
          "detail",
          "additionalTextEdits",
       },
    }
    
    -- lazy load servers
    lsp_installer.on_server_ready(function(server)
       local opts = {}
       server:setup(opts)
       vim.cmd [[ do User LspAttachBuffers ]]
    end)
    
    -- replace the default lsp diagnostic symbols
    local function lspSymbol(name, icon)
       vim.fn.sign_define("LspDiagnosticsSign" .. name, { text = icon, numhl = "LspDiagnosticsDefaul" .. name })
    end
    lspSymbol("Error", "")
    lspSymbol("Information", "")
    lspSymbol("Hint", "")
    lspSymbol("Warning", "")
    
    -- add smol icon before diagnostics
    vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
       virtual_text = {
          prefix = "",
          spacing = 0,
       },
       signs = true,
       underline = true,
       update_in_insert = false, -- update diagnostics insert mode
    })
    
    -- rounded borders
    vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { border = "rounded" })
    vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, { border = "rounded" })
    
  5. Neogit
    Lua
    #
    local present, neogit = pcall(require, "neogit")
    if not present then
       return
    end
    
    neogit.setup {
       disable_signs = false,
       disable_context_highlighting = false,
       disable_commit_confirmation = false,
       -- customize displayed signs
       signs = {
          -- { CLOSED, OPENED }
          section = { "", "" },
          item = { "", "" },
          hunk = { "", "" },
       },
       integrations = {
          diffview = true,
       },
    }
    
  6. Neorg
    Lua
    #
    local present, neorg = pcall(require, "neorg")
    if not present then
       return
    end
    
    neorg.setup {
       -- Tell Neorg what modules to load
       load = {
          ["core.defaults"] = {}, -- Load all the default modules
          ["core.norg.concealer"] = {}, -- Allows for use of icons
          ["core.norg.dirman"] = { -- Manage your directories with Neorg
             config = {
                workspaces = {
                   my_workspace = "~/org/neorg",
                },
             },
          },
          ["core.norg.completion"] = {
             config = {
                engine = "nvim-cmp", -- We current support nvim-compe and nvim-cmp only
             },
          },
          ["core.keybinds"] = { -- Configure core.keybinds
             config = {
                default_keybinds = true, -- Generate the default keybinds
                neorg_leader = "<Leader>o", -- This is the default if unspecified
             },
          },
          ["core.integrations.telescope"] = {}, -- Enable the telescope module
       },
    }
    
  7. Nvimtree
    Lua
    #
    local present, nvimtree = pcall(require, "nvim-tree")
    
    if not present then
       return
    end
    
    vim.o.termguicolors = true
    
    vim.cmd [[highlight NvimTreeNormal guifg=#D8DEE9  guibg=#2a2e39]]
    
    vim.g.nvim_tree_add_trailing = 0 -- append a trailing slash to folder names
    vim.g.nvim_tree_highlight_opened_files = 0
    vim.g.nvim_tree_indent_markers = 1
    vim.g.nvim_tree_ignore = { ".git", "node_modules", ".cache" }
    vim.g.nvim_tree_quit_on_open = 0 -- closes tree when file's opened
    vim.g.nvim_tree_root_folder_modifier = table.concat { ":t:gs?$?/..", string.rep(" ", 1000), "?:gs?^??" }
    --
    vim.g.nvim_tree_show_icons = {
       folders = 1,
       files = 1,
    }
    
    vim.g.nvim_tree_icons = {
       default = "",
       symlink = "",
       git = {
          deleted = "",
          ignored = "◌",
          renamed = "➜",
          staged = "✓",
          unmered = "",
          unstaged = "✗",
          untracked = "★",
       },
       folder = {
          arrow_open = "",
          arrow_closed = "",
          default = "",
          empty = "",
          empty_open = "",
          open = "",
          symlink = "",
          symlink_open = "",
       },
    }
    
    nvimtree.setup {
       filters = {
          dotfiles = false,
       },
       disable_netrw = true,
       hijack_netrw = true,
       ignore_ft_on_setup = { "dashboard" },
       auto_close = false,
       open_on_tab = false,
       hijack_cursor = true,
       update_cwd = true,
       update_focused_file = {
          enable = true,
          update_cwd = true,
       },
       view = {
          allow_resize = true,
          side = "left",
          width = 30,
       },
    }
    
  8. Others
    Lua
    #
    local M = {}
    
    M.colorizer = function()
       local present, colorizer = pcall(require, "colorizer")
       if present then
          colorizer.setup({ "*" }, {
             RGB = true, -- #RGB hex codes
             RRGGBB = true, -- #RRGGBB hex codes
             names = true, -- "Name" codes like Blue
             RRGGBBAA = true, -- #RRGGBBAA hex codes
             rgb_fn = true, -- CSS rgb() and rgba() functions
             hsl_fn = true, -- CSS hsl() and hsla() functions
             css = true, -- Enable all CSS features: rgb_fn, hsl_fn, names, RGB, RRGGBB
             css_fn = true, -- Enable all CSS *functions*: rgb_fn, hsl_fn
             mode = "foreground", -- Set the display mode.
          })
          vim.cmd "ColorizerReloadAllBuffers"
       end
    end
    
    M.fineCmdline = function()
       local present, fineCmdline = pcall(require, "fine-cmdline")
       if present then
          --remap ex-commands to floating windows
          vim.api.nvim_set_keymap("n", ":", ':lua require("fine-cmdline").open()<CR>', { noremap = true })
          fineCmdline.setup {
             cmdline = {
                smart_history = true,
             },
             popup = {
                border = {
                   style = "rounded",
                   highlight = "TelescopeResultsBorder",
                },
             },
          }
       end
    end
    
    M.searchbox = function()
       local searchbox = pcall(require, "searchbox")
       if searchbox then
          vim.api.nvim_set_keymap(
             "n",
             "/",
             ':lua require("searchbox").match_all({clear_matches = true})<CR>',
             { noremap = true }
          )
          vim.api.nvim_set_keymap(
             "n",
             "<leader>/",
             ':lua require("searchbox").replace({confirm = "menu"})<CR>',
             { noremap = true }
          )
          vim.api.nvim_set_keymap(
             "v",
             "<leader>/",
             '<Esc><cmd>lua require("searchbox").replace({exact = true, visual_mode = true, confirm = "menu"})<CR>',
             { noremap = true }
          )
       end
    end
    
    M.blankline = function()
       require("indent_blankline").setup {
          show_current_context = true,
          context_patterns = {
             "class",
             "return",
             "function",
             "method",
             "^if",
             "^while",
             "jsx_element",
             "^for",
             "^object",
             "^table",
             "block",
             "arguments",
             "if_statement",
             "else_clause",
             "jsx_element",
             "jsx_self_closing_element",
             "try_statement",
             "catch_clause",
             "import_statement",
             "operation_type",
          },
          filetype_exclude = {
             "help",
             "terminal",
             "dashboard",
             "packer",
             "lspinfo",
             "TelescopePrompt",
             "TelescopeResults",
          },
          buftype_exclude = { "terminal" },
          show_trailing_blankline_indent = false,
          show_first_indent_level = false,
       }
    end
    
    M.luasnip = function()
       local present, luasnip = pcall(require, "luasnip")
       if not present then
          return
       end
    
       luasnip.config.set_config {
          history = true,
          updateevents = "TextChanged,TextChangedI",
       }
       require("luasnip/loaders/from_vscode").load()
    end
    
    M.signature = function()
       local present, lspsignature = pcall(require, "lsp_signature")
       if present then
          lspsignature.setup {
             bind = true,
             doc_lines = 2,
             floating_window = true,
             fix_pos = true,
             hint_enable = true,
             hint_prefix = " ",
             hint_scheme = "String",
             hi_parameter = "Search",
             max_height = 22,
             max_width = 120, -- max_width of signature floating_window, line will be wrapped if exceed max_width
             handler_opts = {
                border = "single", -- double, single, shadow, none
             },
             zindex = 200, -- by default it will be on top of all floating windows, set to 50 send it to bottom
             padding = "", -- character to pad on left and right of signature can be ' ', or '|'  etc
          }
       end
    end
    
    M.comment = function()
       local present, comment = pcall(require, "Commment")
       if present then
          comment.setup {
             padding = true,
          }
       end
    end
    
    M.orgmode = function()
       local present, orgmode = pcall(require, "orgmode")
       if present then
          orgmode.setup({ "*" }, {
             org_highlight_latex_and_related = "entities",
             org_agenda_files = "~/org/*",
             org_default_notes_file = "~/org/notes.org",
             org_hide_leading_stars = true,
             org_hide_emphasis_markers = true,
             mappings = {
                global = {
                   org_agenda = "<Leader>oa",
                   org_capture = "<Leader>oc",
                },
                agenda = {
                   org_agenda_later = "f",
                   org_agenda_earlier = "b",
                   org_agenda_goto_today = ".",
                   org_agenda_day_view = "vd",
                   org_agenda_week_view = "vw",
                   org_agenda_month_view = "vm",
                   org_agenda_year_view = "vy",
                   org_agenda_quit = "q",
                   org_agenda_switch_to = "<CR>",
                   org_agenda_goto = { "<TAB>" },
                   org_agenda_goto_date = "J",
                   org_agenda_redo = "r",
                   org_agenda_todo = "t",
                   org_agenda_show_help = "?",
                },
                capture = {
                   org_capture_finalize = "<C-c>",
                   org_capture_refile = "<Leader>or",
                   org_capture_kill = "<Leader>ok",
                   org_capture_show_help = "?",
                },
                org = {
                   org_increase_date = "<C-a>",
                   org_decrease_date = "<C-x>",
                   org_toggle_checkbox = "<C-Space>",
                   org_open_at_point = "<Leader>oo",
                   org_cycle = "<TAB>",
                   org_global_cycle = "<S-TAB>",
                   org_archive_subtree = "<Leader>o$",
                   org_set_tags_command = "<Leader>ot",
                   org_toggle_archive_tag = "<Leader>oA",
                   org_do_promote = "<<",
                   org_do_demote = ">>",
                   org_promote_subtree = "<s",
                   org_demote_subtree = ">s",
                   org_meta_return = "<Leader><CR>", -- Add headling, item or row
                   org_insert_heading_respect_content = "<Leader>oih", -- Add new headling after current heading block with same level
                   org_insert_todo_heading = "<Leader>oiT", -- Add new todo headling right after current heading with same level
                   org_insert_todo_heading_respect_content = "<Leader>oit", -- Add new todo headling after current heading block on same level
                   org_move_subtree_up = "<Leader>oK",
                   org_move_subtree_down = "<Leader>oJ",
                   org_export = "<Leader>oe",
                   org_next_visible_heading = "}",
                   org_previous_visible_heading = "{",
                   org_forward_heading_same_level = "]]",
                   org_backward_heading_same_level = "[[",
                },
             },
          })
       end
    end
    
    return M
    
  9. Statusline
    Lua
    #
    local present, statusline = pcall(require, "statusline")
    if not present then
       return
    end
    statusline.lsp_diagnostics = true
    
  10. Telescope
    Lua
    #
    local present, telescope = pcall(require, "telescope")
    if not present then
       return
    end
    
    telescope.setup {
       defaults = {
          vimgrep_arguments = {
             "rg",
             "--color=never",
             "--no-heading",
             "--with-filename",
             "--line-number",
             "--column",
             "--smart-case",
          },
          prompt_prefix = " λ ",
          selection_caret = " > ",
          entry_prefix = "  ",
          initial_mode = "insert",
          selection_strategy = "reset",
          sorting_strategy = "ascending",
          layout_strategy = "horizontal",
          layout_config = {
             horizontal = {
                prompt_position = "top",
                preview_width = 0.55,
                results_width = 0.8,
             },
             vertical = {
                mirror = false,
             },
             width = 0.87,
             height = 0.80,
             preview_cutoff = 120,
          },
          file_sorter = require("telescope.sorters").get_fuzzy_file,
          file_ignore_patterns = {},
          generic_sorter = require("telescope.sorters").get_generic_fuzzy_sorter,
          path_display = { "absolute" },
          winblend = 0,
          border = {},
          borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
          color_devicons = true,
          use_less = true,
          set_env = { ["COLORTERM"] = "truecolor" }, -- default = nil,
          file_previewer = require("telescope.previewers").vim_buffer_cat.new,
          grep_previewer = require("telescope.previewers").vim_buffer_vimgrep.new,
          qflist_previewer = require("telescope.previewers").vim_buffer_qflist.new,
          -- Developer configurations: Not meant for general override
          buffer_previewer_maker = require("telescope.previewers").buffer_previewer_maker,
       },
       extensions = {
          fzf = {
             fuzzy = true, -- false will only do exact matching
             override_generic_sorter = false, -- override the generic sorter
             override_file_sorter = true, -- override the file sorter
             case_mode = "smart_case", -- or "ignore_case" or "respect_case"
             -- the default case_mode is "smart_case"
          },
       },
    }
    
    local extensions = { "themes", "terms", "fzf" }
    local packer_repos = [["extensions", "telescope-fzf-native.nvim"]]
    
    pcall(function()
       for _, ext in ipairs(extensions) do
          telescope.load_extension(ext)
       end
    end)
    
  11. Treesitter
    Lua
    #
    local present, ts_config = pcall(require, "nvim-treesitter.configs")
    if not present then
       return
    end
    
    local parser_configs = require("nvim-treesitter.parsers").get_parser_configs()
    parser_configs.norg = {
       -- on macOS: https://github.com/nvim-neorg/neorg/issues/74#issuecomment-906627223
       install_info = {
          url = "https://github.com/nvim-neorg/tree-sitter-norg",
          files = { "src/parser.c", "src/scanner.cc" },
          branch = "main",
       },
    }
    parser_configs.org = {
       install_info = {
          url = "https://github.com/milisims/tree-sitter-org",
          revision = "main",
          files = { "src/parser.c", "src/scanner.cc" },
       },
       filetype = "org",
    }
    
    ts_config.setup {
       ensure_installed = { "lua", "nix" },
       indent = { enable = true },
       highlight = {
          enable = true,
          use_languagetree = true,
          additional_vim_regex_highlighting = { "org" },
       },
       rainbow = {
          enable = true,
          extended_mode = true, -- Also highlight non-bracket delimiters like html tags, boolean or table: lang -> boolean
          max_file_lines = nil, -- Do not enable for files with more than n lines, int
       },
       playground = {
          enable = true,
          updatetime = 25, -- Debounced time for highlighting nodes in the playground from source code
          persist_queries = false, -- Whether the query persists across vim sessions
       },
       incremental_selection = {
          enable = true,
          keymaps = {
             init_selection = "<CR>",
             scope_incremental = "<CR>",
             node_incremental = "<TAB>",
             node_decremental = "<S-TAB>",
          },
       },
       textobjects = {
          select = {
             enable = true,
             lookahead = true, -- Automatically jump forward to textobj, similar to targets.vim
             keymaps = {
                -- You can use the capture groups defined in textobjects.scm
                ["af"] = "@function.outer",
                ["if"] = "@function.inner",
                ["ac"] = "@class.outer",
                ["ic"] = "@class.inner",
             },
          },
          move = {
             enable = true,
             set_jumps = true, -- whether to set jumps in the jumplist
             goto_next_start = {
                ["]m"] = "@function.outer",
                ["]]"] = "@class.outer",
             },
             goto_next_end = {
                ["]M"] = "@function.outer",
                ["]["] = "@class.outer",
             },
             goto_previous_start = {
                ["[m"] = "@function.outer",
                ["[["] = "@class.outer",
             },
             goto_previous_end = {
                ["[M"] = "@function.outer",
                ["[]"] = "@class.outer",
             },
          },
       },
    }
    
  12. Zenmode
    Lua
    #
    local present, true_zen = pcall(require, "true-zen")
    if not present then
       return
    end
    
    true_zen.setup {
       ui = {
          bottom = {
             cmdheight = 1,
             laststatus = 0,
             ruler = true,
             showmode = true,
             showcmd = false,
          },
          top = {
             showtabline = 0,
          },
          left = {
             number = false,
             relativenumber = false,
             signcolumn = "no",
          },
       },
       modes = {
          ataraxis = {
             left_padding = 32,
             right_padding = 32,
             top_padding = 1,
             bottom_padding = 1,
             ideal_writing_area_width = { 0 },
             auto_padding = false,
             keep_default_fold_fillchars = false,
             bg_configuration = true,
          },
          focus = {
             margin_of_error = 5,
             focus_method = "experimental",
          },
       },
       integrations = {
          galaxyline = true,
          nvim_bufferline = true,
          twilight = true,
       },
    }
    

5.2.6. External

These are plugin I’m currently developing, but don’t want ready for public release yet. As such, they reside in the nyoom.nvim/lua/ext directory.

  1. doom.nvim

    This is an experimental port of doom-vibrant to neovim, just me playing around with the new highlight API

    Lua
    #
    -- Colorscheme name:    doom.nvim
    -- Description:         Port of hlissner's doom-vibrant theme for neovim
    -- Author:              https://github.com/shaunsingh
    
    local doom = {
       --16 colors
       doom0_gui = "#242730",
       doom1_gui = "#2a2e38",
       doom2_gui = "#484854",
       doom3_gui = "#62686E",
       doom4_gui = "#bbc2cf",
       doom5_gui = "#5D656B",
       doom6_gui = "#bbc2cf",
       doom7_gui = "#4db5bd",
       doom8_gui = "#5cEfFF",
       doom9_gui = "#51afef",
       doom10_gui = "#C57BDB",
       doom11_gui = "#ff665c",
       doom12_gui = "#e69055",
       doom13_gui = "#FCCE7B",
       doom14_gui = "#7bc275",
       doom15_gui = "#C57BDB",
       none = "NONE",
    }
    
    -- Syntax highlight groups
    local syntax = {
       Type = { fg = doom.doom9_gui }, -- int, long, char, etc.
       StorageClass = { fg = doom.doom9_gui }, -- static, register, volatile, etc.
       Structure = { fg = doom.doom9_gui }, -- struct, union, enum, etc.
       Constant = { fg = doom.doom4_gui }, -- any constant
       Character = { fg = doom.doom14_gui }, -- any character constant: 'c', '\n'
       Number = { fg = doom.doom15_gui }, -- a number constant: 5
       Boolean = { fg = doom.doom9_gui }, -- a boolean constant: TRUE, false
       Float = { fg = doom.doom15_gui }, -- a floating point constant: 2.3e10
       Statement = { fg = doom.doom9_gui }, -- any statement
       Label = { fg = doom.doom9_gui }, -- case, default, etc.
       Operator = { fg = doom.doom9_gui }, -- sizeof", "+", "*", etc.
       Exception = { fg = doom.doom9_gui }, -- try, catch, throw
       PreProc = { fg = doom.doom9_gui }, -- generic Preprocessor
       Include = { fg = doom.doom9_gui }, -- preprocessor #include
       Define = { fg = doom.doom9_gui }, -- preprocessor #define
       Macro = { fg = doom.doom9_gui }, -- same as Define
       Typedef = { fg = doom.doom9_gui }, -- A typedef
       PreCondit = { fg = doom.doom13_gui }, -- preprocessor #if, #else, #endif, etc.
       Special = { fg = doom.doom4_gui }, -- any special symbol
       SpecialChar = { fg = doom.doom13_gui }, -- special character in a constant
       Tag = { fg = doom.doom4_gui }, -- you can use CTRL-] on this
       Delimiter = { fg = doom.doom6_gui }, -- character that needs attention like , or .
       SpecialComment = { fg = doom.doom8_gui }, -- special things inside a comment
       Debug = { fg = doom.doom11_gui }, -- debugging statements
       Underlined = { fg = doom.doom14_gui, bg = doom.none, style = "underline" }, -- text that stands out, HTML links
       Ignore = { fg = doom.doom1_gui }, -- left blank, hidden
       Error = { fg = doom.doom11_gui, bg = doom.none, style = "bold,underline" }, -- any erroneous construct
       Todo = { fg = doom.doom13_gui, bg = doom.none, style = "bold" },
       Conceal = { fg = doom.none, bg = doom.doom0_gui },
    
       htmlLink = { fg = doom.doom14_gui, style = "underline" },
       htmlH1 = { fg = doom.doom8_gui, style = "bold" },
       htmlH2 = { fg = doom.doom11_gui, style = "bold" },
       htmlH3 = { fg = doom.doom14_gui, style = "bold" },
       htmlH4 = { fg = doom.doom15_gui, style = "bold" },
       htmlH5 = { fg = doom.doom9_gui, style = "bold" },
       markdownH1 = { fg = doom.doom8_gui, style = "bold" },
       markdownH2 = { fg = doom.doom11_gui, style = "bold" },
       markdownH3 = { fg = doom.doom14_gui, style = "bold" },
       markdownH1Delimiter = { fg = doom.doom8_gui },
       markdownH2Delimiter = { fg = doom.doom11_gui },
       markdownH3Delimiter = { fg = doom.doom14_gui },
       Comment = { fg = doom.doom3_gui }, -- normal comments
       Conditional = { fg = doom.doom9_gui }, -- normal if, then, else, endif, switch, etc.
       Keyword = { fg = doom.doom9_gui }, -- normal for, do, while, etc.
       Repeat = { fg = doom.doom9_gui }, -- normal any other keyword
       Function = { fg = doom.doom8_gui }, -- normal function names
       Identifier = { fg = doom.doom9_gui }, -- any variable name
       String = { fg = doom.doom14_gui }, -- any string
    }
    
    -- Editor highlight groups
    local editor = {
       NormalFloat = { fg = doom.doom4_gui, bg = doom.doom0_gui }, -- normal text and background color
       ColorColumn = { fg = doom.none, bg = doom.doom1_gui }, --  used for the columns set with 'colorcolumn'
       Conceal = { fg = doom.doom1_gui }, -- placeholder characters substituted for concealed text (see 'conceallevel')
       Cursor = { fg = doom.doom4_gui, bg = doom.none, style = "reverse" }, -- the character under the cursor
       CursorIM = { fg = doom.doom5_gui, bg = doom.none, style = "reverse" }, -- like Cursor, but used when in IME mode
       Directory = { fg = doom.doom7_gui, bg = doom.none }, -- directory names (and other special names in listings)
       DiffAdd = { fg = doom.doom14_gui, bg = doom.none, style = "reverse" }, -- diff mode: Added line
       DiffChange = { fg = doom.doom13_gui, bg = doom.none, style = "reverse" }, --  diff mode: Changed line
       DiffDelete = { fg = doom.doom11_gui, bg = doom.none, style = "reverse" }, -- diff mode: Deleted line
       DiffText = { fg = doom.doom15_gui, bg = doom.none, style = "reverse" }, -- diff mode: Changed text within a changed line
       EndOfBuffer = { fg = doom.doom1_gui },
       ErrorMsg = { fg = doom.none },
       Folded = { fg = doom.doom_3_gui_bright, bg = doom.none, style = "italic" },
       FoldColumn = { fg = doom.doom7_gui },
       IncSearch = { fg = doom.doom6_gui, bg = doom.doom10_gui },
       LineNr = { fg = doom.doom3_gui },
       CursorLineNr = { fg = doom.doom4_gui },
       MatchParen = { fg = doom.doom15_gui, bg = doom.none, style = "bold" },
       ModeMsg = { fg = doom.doom4_gui },
       MoreMsg = { fg = doom.doom4_gui },
       NonText = { fg = doom.doom1_gui },
       Pmenu = { fg = doom.doom4_gui, bg = doom.doom2_gui },
       PmenuSel = { fg = doom.doom4_gui, bg = doom.doom10_gui },
       PmenuSbar = { fg = doom.doom4_gui, bg = doom.doom2_gui },
       PmenuThumb = { fg = doom.doom4_gui, bg = doom.doom4_gui },
       Question = { fg = doom.doom14_gui },
       QuickFixLine = { fg = doom.doom4_gui, bg = doom.doom6_gui, style = "reverse" },
       qfLineNr = { fg = doom.doom4_gui, bg = doom.doom6_gui, style = "reverse" },
       Search = { fg = doom.doom1_gui, bg = doom.doom6_gui, style = "reverse" },
       SpecialKey = { fg = doom.doom9_gui },
       SpellBad = { fg = doom.doom11_gui, bg = doom.none, style = "italic,undercurl" },
       SpellCap = { fg = doom.doom7_gui, bg = doom.none, style = "italic,undercurl" },
       SpellLocal = { fg = doom.doom8_gui, bg = doom.none, style = "italic,undercurl" },
       SpellRare = { fg = doom.doom9_gui, bg = doom.none, style = "italic,undercurl" },
       StatusLine = { fg = doom.doom4_gui, bg = doom.doom2_gui },
       StatusLineNC = { fg = doom.doom4_gui, bg = doom.doom1_gui },
       StatusLineTerm = { fg = doom.doom4_gui, bg = doom.doom2_gui },
       StatusLineTermNC = { fg = doom.doom4_gui, bg = doom.doom1_gui },
       TabLineFill = { fg = doom.doom4_gui },
       TablineSel = { fg = doom.doom8_gui, bg = doom.doom3_gui },
       Tabline = { fg = doom.doom4_gui },
       Title = { fg = doom.doom14_gui, bg = doom.none, style = "bold" },
       Visual = { fg = doom.none, bg = doom.doom1_gui },
       VisualNOS = { fg = doom.none, bg = doom.doom1_gui },
       WarningMsg = { fg = doom.doom15_gui },
       WildMenu = { fg = doom.doom12_gui, bg = doom.none, style = "bold" },
       CursorColumn = { fg = doom.none, bg = doom.doom1_gui },
       CursorLine = { fg = doom.none, bg = doom.doom1_gui },
       ToolbarLine = { fg = doom.doom4_gui, bg = doom.doom1_gui },
       ToolbarButton = { fg = doom.doom4_gui, bg = doom.none, style = "bold" },
       NormalMode = { fg = doom.doom4_gui, bg = doom.none, style = "reverse" },
       InsertMode = { fg = doom.doom14_gui, bg = doom.none, style = "reverse" },
       ReplacelMode = { fg = doom.doom11_gui, bg = doom.none, style = "reverse" },
       VisualMode = { fg = doom.doom9_gui, bg = doom.none, style = "reverse" },
       CommandMode = { fg = doom.doom4_gui, bg = doom.none, style = "reverse" },
       Warnings = { fg = doom.doom15_gui },
    
       healthError = { fg = doom.doom11_gui },
       healthSuccess = { fg = doom.doom14_gui },
       healthWarning = { fg = doom.doom15_gui },
    
       -- dashboard
       DashboardShortCut = { fg = doom.doom7_gui },
       DashboardHeader = { fg = doom.doom9_gui },
       DashboardCenter = { fg = doom.doom8_gui },
       DashboardFooter = { fg = doom.doom14_gui, style = "italic" },
    
       -- BufferLine
       BufferLineIndicatorSelected = { fg = doom.doom0_gui },
       BufferLineFill = { bg = doom.doom0_gui },
    
       Normal = { fg = doom.doom4_gui, bg = doom.doom0_gui },
       SignColumn = { fg = doom.doom4_gui, bg = doom.doom0_gui },
       VertSplit = { fg = doom.doom0_gui },
    }
    
    -- TreeSitter highlight groups
    local treesitter = {
       TSCharacter = { fg = doom.doom14_gui }, -- For characters.
       TSConstructor = { fg = doom.doom9_gui }, -- For constructor calls and definitions: `=                                                                          { }` in Lua, and Java constructors.
       TSConstant = { fg = doom.doom13_gui }, -- For constants
       TSFloat = { fg = doom.doom15_gui }, -- For floats
       TSNumber = { fg = doom.doom15_gui }, -- For all number
       TSString = { fg = doom.doom14_gui }, -- For strings.
    
       TSAttribute = { fg = doom.doom15_gui }, -- (unstable) TODO: docs
       TSBoolean = { fg = doom.doom9_gui }, -- For booleans.
       TSConstBuiltin = { fg = doom.doom7_gui }, -- For constant that are built in the language: `nil` in Lua.
       TSConstMacro = { fg = doom.doom7_gui }, -- For constants that are defined by macros: `NULL` in C.
       TSError = { fg = doom.doom11_gui }, -- For syntax/parser errors.
       TSException = { fg = doom.doom15_gui }, -- For exception related keywords.
       TSField = { fg = doom.doom4_gui }, -- For fields.
       TSFuncMacro = { fg = doom.doom7_gui }, -- For macro defined fuctions (calls and definitions): each `macro_rules` in Rust.
       TSInclude = { fg = doom.doom9_gui }, -- For includes: `#include` in C, `use` or `extern crate` in Rust, or `require` in Lua.
       TSLabel = { fg = doom.doom15_gui }, -- For labels: `label:` in C and `:label:` in Lua.
       TSNamespace = { fg = doom.doom4_gui }, -- For identifiers referring to modules and namespaces.
       TSOperator = { fg = doom.doom9_gui }, -- For any operator: `+`, but also `->` and `*` in C.
       TSParameter = { fg = doom.doom10_gui }, -- For parameters of a function.
       TSParameterReference = { fg = doom.doom10_gui }, -- For references to parameters of a function.
       TSProperty = { fg = doom.doom10_gui }, -- Same as `TSField`.
       TSPunctDelimiter = { fg = doom.doom8_gui }, -- For delimiters ie: `.`
       TSPunctBracket = { fg = doom.doom8_gui }, -- For brackets and parens.
       TSPunctSpecial = { fg = doom.doom8_gui }, -- For special punctutation that does not fall in the catagories before.
       TSStringRegex = { fg = doom.doom7_gui }, -- For regexes.
       TSStringEscape = { fg = doom.doom15_gui }, -- For escape characters within a string.
       TSSymbol = { fg = doom.doom15_gui }, -- For identifiers referring to symbols or atoms.
       TSType = { fg = doom.doom9_gui }, -- For types.
       TSTypeBuiltin = { fg = doom.doom9_gui }, -- For builtin types.
       TSTag = { fg = doom.doom4_gui }, -- Tags like html tag names.
       TSTagDelimiter = { fg = doom.doom15_gui }, -- Tag delimiter like `<` `>` `/`
       TSText = { fg = doom.doom4_gui }, -- For strings considedoom11_gui text in a markup language.
       TSTextReference = { fg = doom.doom15_gui }, -- FIXME
       TSEmphasis = { fg = doom.doom10_gui }, -- For text to be represented with emphasis.
       TSUnderline = { fg = doom.doom4_gui, bg = doom.none, style = "underline" }, -- For text to be represented with an underline.
       TSTitle = { fg = doom.doom10_gui, bg = doom.none, style = "bold" }, -- Text that is part of a title.
       TSLiteral = { fg = doom.doom4_gui }, -- Literal text.
       TSURI = { fg = doom.doom14_gui }, -- Any URI like a link or email.
       TSAnnotation = { fg = doom.doom11_gui }, -- For C++/Dart attributes, annotations that can be attached to the code to denote some kind of meta information.
       TSComment = { fg = doom.doom3_gui },
       TSConditional = { fg = doom.doom9_gui }, -- For keywords related to conditionnals.
       TSKeyword = { fg = doom.doom9_gui }, -- For keywords that don't fall in previous categories.
       TSRepeat = { fg = doom.doom9_gui }, -- For keywords related to loops.
       TSKeywordFunction = { fg = doom.doom8_gui },
       TSFunction = { fg = doom.doom8_gui }, -- For fuction (calls and definitions).
       TSMethod = { fg = doom.doom7_gui }, -- For method calls and definitions.
       TSFuncBuiltin = { fg = doom.doom8_gui },
       TSVariable = { fg = doom.doom4_gui }, -- Any variable name that does not have another highlight.
       TSVariableBuiltin = { fg = doom.doom4_gui },
    }
    
    -- Lsp highlight groups
    local lsp = {
       DiagnosticDefaultError = { fg = doom.doom11_gui }, -- used for "Error" diagnostic virtual text
       DiagnosticSignError = { fg = doom.doom11_gui }, -- used for "Error" diagnostic signs in sign column
       DiagnosticFloatingError = { fg = doom.doom11_gui }, -- used for "Error" diagnostic messages in the diagnostics float
       DiagnosticVirtualTextError = { fg = doom.doom11_gui }, -- Virtual text "Error"
       DiagnosticUnderlineError = { fg = doom.doom11_gui, style = "undercurl" }, -- used to underline "Error" diagnostics.
       DiagnosticDefaultWarn = { fg = doom.doom15_gui }, -- used for "Warn" diagnostic signs in sign column
       DiagnosticSignWarn = { fg = doom.doom15_gui }, -- used for "Warn" diagnostic signs in sign column
       DiagnosticFloatingWarn = { fg = doom.doom15_gui }, -- used for "Warn" diagnostic messages in the diagnostics float
       DiagnosticVirtualTextWarn = { fg = doom.doom15_gui }, -- Virtual text "Warn"
       DiagnosticUnderlineWarn = { fg = doom.doom15_gui, style = "undercurl" }, -- used to underline "Warn" diagnostics.
       DiagnosticDefaultInfo = { fg = doom.doom10_gui }, -- used for "Info" diagnostic virtual text
       DiagnosticSignInfo = { fg = doom.doom10_gui }, -- used for "Info" diagnostic signs in sign column
       DiagnosticFloatingInfo = { fg = doom.doom10_gui }, -- used for "Info" diagnostic messages in the diagnostics float
       DiagnosticVirtualTextInfo = { fg = doom.doom10_gui }, -- Virtual text "Info"
       DiagnosticUnderlineInfo = { fg = doom.doom10_gui, style = "undercurl" }, -- used to underline "Info" diagnostics.
       DiagnosticDefaultHint = { fg = doom.doom9_gui }, -- used for "Hint" diagnostic virtual text
       DiagnosticSignHint = { fg = doom.doom9_gui }, -- used for "Hint" diagnostic signs in sign column
       DiagnosticFloatingHint = { fg = doom.doom9_gui }, -- used for "Hint" diagnostic messages in the diagnostics float
       DiagnosticVirtualTextHint = { fg = doom.doom9_gui }, -- Virtual text "Hint"
       DiagnosticUnderlineHint = { fg = doom.doom10_gui, style = "undercurl" }, -- used to underline "Hint" diagnostics.
       LspReferenceText = { fg = doom.doom4_gui, bg = doom.doom1_gui }, -- used for highlighting "text" references
       LspReferenceRead = { fg = doom.doom4_gui, bg = doom.doom1_gui }, -- used for highlighting "read" references
       LspReferenceWrite = { fg = doom.doom4_gui, bg = doom.doom1_gui }, -- used for highlighting "write" references
    }
    
    -- Plugins highlight groups
    local plugins = {
    
       -- LspTrouble
       LspTroubleText = { fg = doom.doom4_gui },
       LspTroubleCount = { fg = doom.doom9_gui, bg = doom.doom10_gui },
       LspTroubleNormal = { fg = doom.doom4_gui, bg = doom.doom0_gui },
    
       -- Diff
       diffAdded = { fg = doom.doom14_gui },
       diffRemoved = { fg = doom.doom11_gui },
       diffChanged = { fg = doom.doom15_gui },
       diffOldFile = { fg = doom.yelow },
       diffNewFile = { fg = doom.doom12_gui },
       diffFile = { fg = doom.doom7_gui },
       diffLine = { fg = doom.doom3_gui },
       diffIndexLine = { fg = doom.doom9_gui },
    
       -- Neogit
       NeogitBranch = { fg = doom.doom10_gui },
       NeogitRemote = { fg = doom.doom9_gui },
       NeogitHunkHeader = { fg = doom.doom8_gui },
       NeogitHunkHeaderHighlight = { fg = doom.doom8_gui, bg = doom.doom1_gui },
       NeogitDiffContextHighlight = { bg = doom.doom1_gui },
       NeogitDiffDeleteHighlight = { fg = doom.doom11_gui, style = "reverse" },
       NeogitDiffAddHighlight = { fg = doom.doom14_gui, style = "reverse" },
    
       -- GitGutter
       GitGutterAdd = { fg = doom.doom14_gui }, -- diff mode: Added line |diff.txt|
       GitGutterChange = { fg = doom.doom15_gui }, -- diff mode: Changed line |diff.txt|
       GitGutterDelete = { fg = doom.doom11_gui }, -- diff mode: Deleted line |diff.txt|
    
       -- GitSigns
       GitSignsAdd = { fg = doom.doom14_gui }, -- diff mode: Added line |diff.txt|
       GitSignsAddNr = { fg = doom.doom14_gui }, -- diff mode: Added line |diff.txt|
       GitSignsAddLn = { fg = doom.doom14_gui }, -- diff mode: Added line |diff.txt|
       GitSignsChange = { fg = doom.doom15_gui }, -- diff mode: Changed line |diff.txt|
       GitSignsChangeNr = { fg = doom.doom15_gui }, -- diff mode: Changed line |diff.txt|
       GitSignsChangeLn = { fg = doom.doom15_gui }, -- diff mode: Changed line |diff.txt|
       GitSignsDelete = { fg = doom.doom11_gui }, -- diff mode: Deleted line |diff.txt|
       GitSignsDeleteNr = { fg = doom.doom11_gui }, -- diff mode: Deleted line |diff.txt|
       GitSignsDeleteLn = { fg = doom.doom11_gui }, -- diff mode: Deleted line |diff.txt|
    
       -- Telescope
       TelescopePromptBorder = { fg = doom.doom8_gui },
       TelescopeResultsBorder = { fg = doom.doom9_gui },
       TelescopePreviewBorder = { fg = doom.doom14_gui },
       TelescopeSelectionCaret = { fg = doom.doom9_gui },
       TelescopeSelection = { fg = doom.doom9_gui },
       TelescopeMatching = { fg = doom.doom8_gui },
    
       -- NvimTree
       NvimTreeNormal = { fg = doom.doom4_gui, bg = doom.doom0_gui },
       NvimTreeRootFolder = { fg = doom.doom7_gui, style = "bold" },
       NvimTreeGitDirty = { fg = doom.doom15_gui },
       NvimTreeGitNew = { fg = doom.doom14_gui },
       NvimTreeImageFile = { fg = doom.doom15_gui },
       NvimTreeExecFile = { fg = doom.doom14_gui },
       NvimTreeSpecialFile = { fg = doom.doom9_gui, style = "underline" },
       NvimTreeFolderName = { fg = doom.doom10_gui },
       NvimTreeEmptyFolderName = { fg = doom.doom1_gui },
       NvimTreeFolderIcon = { fg = doom.doom4_gui },
       NvimTreeIndentMarker = { fg = doom.doom1_gui },
       LspDiagnosticsError = { fg = doom.doom11_gui },
       LspDiagnosticsWarning = { fg = doom.doom15_gui },
       LspDiagnosticsInformation = { fg = doom.doom10_gui },
       LspDiagnosticsHint = { fg = doom.doom9_gui },
    
       -- WhichKey
       WhichKey = { fg = doom.doom4_gui, style = "bold" },
       WhichKeyGroup = { fg = doom.doom4_gui },
       WhichKeyDesc = { fg = doom.doom7_gui, style = "italic" },
       WhichKeySeperator = { fg = doom.doom4_gui },
       WhichKeyFloating = { bg = doom.doom0_gui },
       WhichKeyFloat = { bg = doom.doom0_gui },
    
       -- Sneak
       Sneak = { fg = doom.doom0_gui, bg = doom.doom4_gui },
       SneakScope = { bg = doom.doom1_gui },
    
       -- Cmp
       CmpItemKind = { fg = doom.doom15_gui },
       CmpItemAbbrMatch = { fg = doom.doom9_gui, style = "bold" },
       CmpItemAbbrMatchFuzzy = { fg = doom.doom14_gui, style = "bold" },
       CmpItemAbbr = { fg = doom.doom13_gui },
       CmpItemMenu = { fg = doom.doom14_gui },
    
       -- Indent Blankline
       IndentBlanklineChar = { fg = doom.doom3_gui },
       IndentBlanklineContextChar = { fg = doom.doom10_gui },
    
       -- Illuminate
       illuminatedWord = { bg = doom.doom3_gui },
       illuminatedCurWord = { bg = doom.doom3_gui },
    
       -- nvim-dap
       DapBreakpoint = { fg = doom.doom14_gui },
       DapStopped = { fg = doom.doom15_gui },
    
       -- Hop
       HopNextKey = { fg = doom.doom4_gui, style = "bold" },
       HopNextKey1 = { fg = doom.doom8_gui, style = "bold" },
       HopNextKey2 = { fg = doom.doom4_gui },
       HopUnmatched = { fg = doom.doom3_gui },
    
       -- Fern
       FernBranchText = { fg = doom.doom3_gui },
    
       -- nvim-ts-rainbow
       rainbowcol1 = { fg = doom.doom15_gui },
       rainbowcol2 = { fg = doom.doom13_gui },
       rainbowcol3 = { fg = doom.doom11_gui },
       rainbowcol4 = { fg = doom.doom7_gui },
       rainbowcol5 = { fg = doom.doom8_gui },
       rainbowcol6 = { fg = doom.doom15_gui },
       rainbowcol7 = { fg = doom.doom13_gui },
    }
    
    local set_namespace = vim.api.nvim__set_hl_ns or vim.api.nvim_set_hl_ns
    local namespace = vim.api.nvim_create_namespace "doom"
    local function highlight(statement)
       for name, setting in pairs(statement) do
          vim.api.nvim_set_hl(namespace, name, setting)
       end
    end
    
    local M = {}
    
    M.setup = function()
       vim.cmd "highlight clear"
       vim.o.background = "dark"
       vim.o.termguicolors = true
       vim.g.colors_name = "doom"
       if vim.fn.exists "syntax_on" then
          vim.cmd "syntax reset"
       end
       highlight(syntax)
       highlight(editor)
       highlight(treesitter)
       highlight(plugins)
       highlight(lsp)
       set_namespace(namespace)
    end
    
    return M
    
  2. Statusline.nvim

    Just a little statusline to mess around with

    Lua
    #
    local statusline = require "modules.statusline"
    local M = {}
    M.lsp_diagnostics = true -- Enable Nvim native LSP as default
    
    -- TODO: Clean up this mess
    function M.activeLine()
       if M.lsp_diagnostics == true then
          vim.wo.statusline = "%!v:lua.require'modules.statusline'.wants_lsp()"
       else
          vim.wo.statusline = "%!v:lua.require'modules.statusline'.activeLine()"
       end
    end
    
    function M.simpleLine()
       vim.wo.statusline = statusline.simpleLine()
    end
    
    function M.inActiveLine()
       vim.wo.statusline = statusline.inActiveLine()
    end
    
    return M
    
    1. Modules

      This is the fun part!

      Lua
      #
      local M = {}
      
      local _config = {}
      
      local function get_defaults()
         return {
            options = {
               lsp_client = "native",
            },
         }
      end
      
      local function merge(defaults, user_config)
         if user_config and type(user_config) == "table" then
            user_config = vim.tbl_deep_extend("force", defaults, user_config)
         end
         return user_config
      end
      
      function M.set(user_config)
         user_config = user_config or {}
         local defaults = get_defaults()
         _config = merge(defaults, user_config)
         return _config
      end
      
      Lua
      #
      local modes = require "tables._modes"
      local git_branch = require "sections._git_branch"
      local lsp = require "sections._lsp"
      local signify = require "sections._signify"
      local bufmod = require "sections._bufmodified"
      local bufname = require "sections._bufname"
      local buficon = require "sections._buficon"
      local editable = require "sections._bufeditable"
      local filesize = require "sections._filesize"
      
      local M = {}
      
      -- Separators
      local left_separator = ""
      local right_separator = ""
      
      -- Blank Between Components
      local space = " "
      
      -- Different colors for mode
      local purple = "#C57BDB"
      local blue = "#51afef"
      local yellow = "#FCCE7B"
      local green = "#7bc275"
      local red = "#ff665c"
      
      -- fg and bg
      local white_fg = "#bbc2cf"
      local black_fg = "#242730"
      
      --Statusline colour
      local statusline_bg = "None" --> Set to none, use native bg
      local statusline_fg = white_fg
      -- local statusline_font = 'regular'
      vim.api.nvim_command("hi Status_Line guibg=" .. statusline_bg .. " guifg=" .. statusline_fg)
      
      --LSP Function Highlight Color
      vim.api.nvim_command("hi Statusline_LSP_Func guibg=" .. statusline_bg .. " guifg=" .. green)
      
      -- INACTIVE BUFFER Colours
      local InactiveLine_bg = "#2a2e38"
      local InactiveLine_fg = white_fg
      vim.api.nvim_command("hi InActive guibg=" .. InactiveLine_bg .. " guifg=" .. InactiveLine_fg)
      
      -- Redraw different colors for different mode
      local set_mode_colours = function(mode)
         if mode == "n" then
            vim.api.nvim_command("hi Mode guibg=" .. blue .. " guifg=" .. black_fg .. " gui=bold")
            vim.api.nvim_command("hi ModeSeparator guifg=" .. blue)
         end
         if mode == "i" then
            vim.api.nvim_command("hi Mode guibg=" .. green .. " guifg=" .. black_fg .. " gui=bold")
            vim.api.nvim_command("hi ModeSeparator guifg=" .. green)
         end
         if mode == "v" or mode == "V" or mode == "^V" then
            vim.api.nvim_command("hi Mode guibg=" .. purple .. " guifg=" .. black_fg .. " gui=bold")
            vim.api.nvim_command("hi ModeSeparator guifg=" .. purple)
         end
         if mode == "c" then
            vim.api.nvim_command("hi Mode guibg=" .. yellow .. " guifg=" .. black_fg .. " gui=bold")
            vim.api.nvim_command("hi ModeSeparator guifg=" .. yellow)
         end
         if mode == "t" then
            vim.api.nvim_command("hi Mode guibg=" .. red .. " guifg=" .. black_fg .. " gui=bold")
            vim.api.nvim_command("hi ModeSeparator guifg=" .. red)
         end
      end
      
      function M.activeLine()
         local statusline = ""
         -- Component: Mode
         local mode = vim.api.nvim_get_mode()["mode"]
         set_mode_colours(mode)
         statusline = statusline .. "%#ModeSeparator#" .. space
         statusline = statusline
            .. "%#ModeSeparator#"
            .. left_separator
            .. "%#Mode# "
            .. modes.current_mode[mode]
            .. " %#ModeSeparator#"
            .. right_separator
            .. space
         -- Component: Filetype and icons
         statusline = statusline .. "%#Status_Line#" .. bufname.get_buffer_name()
         statusline = statusline .. buficon.get_file_icon()
      
         if diag_lsp then
            statusline = statusline .. lsp.diagnostics()
         end
      
         statusline = statusline .. signify.signify()
         statusline = statusline .. git_branch.branch()
         statusline = statusline .. lsp.lsp_progress()
         statusline = statusline .. "%="
      
         -- Component: LSP CURRENT FUCTION --> Requires LSP
         statusline = statusline .. "%#Statusline_LSP_Func# " .. lsp.current_function()
         statusline = statusline .. "%#Statusline_LSP_Func# " .. lsp.lightbulb()
      
         -- Component: Modified, Read-Only, Filesize, Row/Col
         statusline = statusline .. "%#Status_Line#" .. bufmod.is_buffer_modified()
         statusline = statusline .. editable.editable() .. filesize.get_file_size() .. [[ʟ %l/%L c %c]] .. space
         vim.api.nvim_command "set noruler"
         return statusline
      end
      
      function M.wants_lsp()
         diag_lsp = true
         return M.activeLine(diag_lsp)
      end
      
      -- statusline for simple buffers such as NvimTree where you don't need mode indicators etc
      function M.simpleLine()
         local statusline = ""
         return statusline .. "%#Status_Line#" .. bufname.get_buffer_name() .. ""
      end
      
      -- INACTIVE FUNCTION DISPLAY
      function M.inActiveLine()
         local statusline = ""
         return statusline .. bufname.get_buffer_name() .. buficon.get_file_icon()
      end
      
      return M
      
      Lua
      #
      
      
    2. Sections

      All the sections of the statusline, lsp, git, etc,

      Lua
      #
      local M = {}
      -- local vim = vim
      function M.editable()
         if vim.bo.filetype == "help" then
            return ""
         end
         if vim.bo.readonly == true then
            return " "
         end
         return ""
      end
      return M
      
      Lua
      #
      local M = {}
      local api = vim.api
      local icons = require "tables._icons"
      local space = " "
      function M.get_file_icon()
         local file_name = api.nvim_buf_get_name(current_buf)
         if string.find(file_name, "term://") ~= nil then
            icon = " " .. api.nvim_call_function("fnamemodify", { file_name, ":p:t" })
         end
         file_name = api.nvim_call_function("fnamemodify", { file_name, ":p:t" })
         if file_name == "" then
            icon = ""
            return icon
         end
         local icon = icons.deviconTable[file_name]
         if icon ~= nil then
            return icon .. space
         else
            return ""
         end
      end
      
      return M
      
      Lua
      #
      local bufname = require "sections._bufname"
      local space = " "
      local M = {}
      function M.is_buffer_modified()
         local file = bufname.get_buffer_name()
         if file == " startify " then
            return ""
         end -- exception check
         if vim.bo.modifiable and vim.bo.modified then
            return "+" .. space
         end
         return ""
      end
      return M
      
      Lua
      #
      local M = {}
      local space = " "
      function M.get_buffer_name() --> IF We are in a buffer such as terminal or startify with no filename just display the buffer 'type' i.e "startify"
         local filename = vim.fn.expand "%:t"
      
         local filetype = vim.bo.ft --> Get vim filetype using nvim api
         if filename ~= "" then --> IF filetype empty i.e in a terminal buffer etc, return name of buffer (filetype)
            return filename .. space
         else
            if filetype ~= "" then
               return filetype .. space
            else
               return "" --> AFAIK buffers tested have types but just incase.
            end
         end
      end
      return M
      
      Lua
      #
      local M = {}
      local vim = vim
      local space = " "
      
      local function file_size(file)
         local size = vim.fn.getfsize(file)
         if size == 0 or size == -1 or size == -2 then
            return ""
         end
         if size < 1024 then
            size = size .. "B"
         elseif size < 1024 * 1024 then
            size = string.format("%d", size / 1024) .. "KB"
         elseif size < 1024 * 1024 * 1024 then
            size = string.format("%d", size / 1024 / 1024) .. "MB"
         else
            size = string.format("%d", size / 1024 / 1024 / 1024) .. "GB"
         end
         return size .. space
      end
      
      function M.get_file_size()
         local file = vim.fn.expand "%:p"
         if string.len(file) == 0 then
            return ""
         end
         return file_size(file)
      end
      
      return M
      
      Lua
      #
      local M = {}
      local git_branch
      local space = " "
      
      local sep = package.config:sub(1, 1)
      local function find_git_dir()
         local file_dir = vim.fn.expand "%:p:h" .. ";"
         local git_dir = vim.fn.finddir(".git", file_dir)
         local git_file = vim.fn.findfile(".git", file_dir)
         if #git_file > 0 then
            git_file = vim.fn.fnamemodify(git_file, ":p")
         end
         if #git_file > #git_dir then
            -- separate git-dir or submodule is used
            local file = io.open(git_file)
            git_dir = file:read()
            git_dir = git_dir:match "gitdir: (.+)$"
            file:close()
            -- submodule / relative file path
            if git_dir:sub(1, 1) ~= sep and not git_dir:match "^%a:.*$" then
               git_dir = git_file:match "(.*).git" .. git_dir
            end
         end
         return git_dir
      end
      
      local function get_git_head(head_file)
         local f_head = io.open(head_file)
         if f_head then
            local HEAD = f_head:read()
            f_head:close()
            local branch = HEAD:match "ref: refs/heads/(.+)$"
            if branch then
               git_branch = branch
            else
               git_branch = HEAD:sub(1, 6)
            end
         end
         return nil
      end
      
      -- event watcher to watch head file
      local file_changed = vim.loop.new_fs_event()
      local function watch_head()
         file_changed:stop()
         local git_dir = find_git_dir()
         if #git_dir > 0 then
            local head_file = git_dir .. sep .. "HEAD"
            get_git_head(head_file)
            file_changed:start(
               head_file,
               {},
               vim.schedule_wrap(function()
                  -- reset file-watch
                  watch_head()
               end)
            )
         else
            git_branch = nil
         end
      end
      
      -- returns the git_branch value to be shown on statusline
      function M.branch()
         if not git_branch or #git_branch == 0 then
            return ""
         end
         local icon = ""
         return icon .. space .. git_branch .. space
      end
      
      -- run watch head on load so branch is present when component is loaded
      watch_head()
      
      return M
      
      Lua
      #
      local M = {}
      local space = " "
      local vim = vim
      
      function M.current_function()
         local lsp_function = vim.b.lsp_current_function
         if lsp_function == nil then
            return ""
         end
         return lsp_function
      end
      
      -- icons       
      function M.diagnostics()
         local diagnostics = ""
         local e = vim.lsp.diagnostic.get_count(0, [[Error]])
         local w = vim.lsp.diagnostic.get_count(0, [[Warning]])
         local i = vim.lsp.diagnostic.get_count(0, [[Information]])
         local h = vim.lsp.diagnostic.get_count(0, [[Hint]])
         diagnostics = e ~= 0 and diagnostics .. " " .. e .. space or diagnostics
         diagnostics = w ~= 0 and diagnostics .. " " .. w .. space or diagnostics
         diagnostics = i ~= 0 and diagnostics .. "𝒊 " .. i .. space or diagnostics
         diagnostics = h ~= 0 and diagnostics .. " " .. h .. space or diagnostics
         return diagnostics
      end
      
      local function format_messages(messages)
         local result = {}
         local spinners = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }
         local ms = vim.loop.hrtime() / 1000000
         local frame = math.floor(ms / 120) % #spinners
         local i = 1
         for _, msg in pairs(messages) do
            -- Only display at most 2 progress messages at a time to avoid clutter
            if i < 3 then
               table.insert(result, (msg.percentage or 0) .. "%% " .. (msg.title or ""))
               i = i + 1
            end
         end
         return table.concat(result, " ") .. " " .. spinners[frame + 1]
      end
      
      -- REQUIRES LSP
      function M.lsp_progress()
         local messages = vim.lsp.util.get_progress_messages()
         if #messages == 0 then
            return ""
         end
         return format_messages(messages)
      end
      
      -- REQUIRES NVIM LIGHTBULB
      function M.lightbulb()
         local has_lightbulb, lightbulb = pcall(require, "nvim-lightbulb")
         if not has_lightbulb then
            return ""
         end
      
         if lightbulb.get_status_text() ~= "" then
            return "" .. space
         else
            return ""
         end
      end
      
      return M
      
      Lua
      #
      local M = {}
      local space = " "
      function M.signify()
         if vim.fn.exists "*sy#repo#get_stats" == 0 then
            return ""
         end
         local added, modified, removed = unpack(vim.fn["sy#repo#get_stats"]())
         if added == -1 then
            return ""
         end
         local symbols = {
            "+",
            "-",
            "~",
         }
         local result = {}
         local data = {
            added,
            removed,
            modified,
         }
         for range = 1, 3 do
            if data[range] ~= nil and data[range] > 0 then
               table.insert(result, symbols[range] .. data[range] .. space)
            end
         end
      
         if result[1] ~= nil then
            return table.concat(result, "")
         else
            return ""
         end
      end
      return M
      
    3. Tables

      Just contains all the icons and modes I use throughout the statusline

      Lua
      #
      local M = {}
      
      local extensionTable = {
         -- Exact Match
         ["gruntfile.coffee"] = "",
         ["gruntfile.js"] = "",
         ["gruntfile.ls"] = "",
         ["gulpfile.coffee"] = "",
         ["gulpfile.js"] = "",
         ["gulpfile.ls"] = "",
         ["mix.lock"] = "",
         ["dropbox"] = "",
         [".ds_store"] = "",
         [".gitconfig"] = "",
         [".gitignore"] = "",
         [".gitlab-ci.yml"] = "",
         [".bashrc"] = "",
         [".zshrc"] = "",
         [".vimrc"] = "",
         [".gvimrc"] = "",
         ["_vimrc"] = "",
         ["_gvimrc"] = "",
         [".bashprofile"] = "",
         ["favicon.ico"] = "",
         ["license"] = "",
         ["node_modules"] = "",
         ["react.jsx"] = "",
         ["procfile"] = "",
         ["dockerfile"] = "",
         ["docker-compose.yml"] = "",
         -- Extension
         ["styl"] = "",
         ["sass"] = "",
         ["scss"] = "",
         ["htm"] = "",
         ["html"] = "",
         ["slim"] = "",
         ["ejs"] = "",
         ["css"] = "",
         ["less"] = "",
         ["md"] = "",
         ["mdx"] = "",
         ["markdown"] = "",
         ["rmd"] = "",
         ["json"] = "",
         ["js"] = "",
         ["mjs"] = "",
         ["jsx"] = "",
         ["rb"] = "",
         ["php"] = "",
         ["py"] = "",
         ["pyc"] = "",
         ["pyo"] = "",
         ["pyd"] = "",
         ["coffee"] = "",
         ["mustache"] = "",
         ["hbs"] = "",
         ["conf"] = "",
         ["ini"] = "",
         ["yml"] = "",
         ["yaml"] = "",
         ["toml"] = "",
         ["bat"] = "",
         ["jpg"] = "",
         ["jpeg"] = "",
         ["bmp"] = "",
         ["png"] = "",
         ["gif"] = "",
         ["ico"] = "",
         ["twig"] = "",
         ["cpp"] = "",
         ["c++"] = "",
         ["cxx"] = "",
         ["cc"] = "",
         ["cp"] = "",
         ["c"] = "",
         ["cs"] = "",
         ["h"] = "",
         ["hh"] = "",
         ["hpp"] = "",
         ["hxx"] = "",
         ["hs"] = "",
         ["lhs"] = "",
         ["lua"] = "",
         ["java"] = "",
         ["sh"] = "",
         ["fish"] = "",
         ["bash"] = "",
         ["zsh"] = "",
         ["ksh"] = "",
         ["csh"] = "",
         ["awk"] = "",
         ["ps1"] = "",
         ["ml"] = "λ",
         ["mli"] = "λ",
         ["diff"] = "",
         ["db"] = "",
         ["sql"] = "",
         ["dump"] = "",
         ["clj"] = "",
         ["cljc"] = "",
         ["cljs"] = "",
         ["edn"] = "",
         ["scala"] = "",
         ["go"] = "",
         ["dart"] = "",
         ["xul"] = "",
         ["sln"] = "",
         ["suo"] = "",
         ["pl"] = "",
         ["pm"] = "",
         ["t"] = "",
         ["rss"] = "",
         ["f#"] = "",
         ["fsscript"] = "",
         ["fsx"] = "",
         ["fs"] = "",
         ["fsi"] = "",
         ["rs"] = "",
         ["rlib"] = "",
         ["d"] = "",
         ["erl"] = "",
         ["hrl"] = "",
         ["ex"] = "",
         ["exs"] = "",
         ["eex"] = "",
         ["leex"] = "",
         ["vim"] = "",
         ["ai"] = "",
         ["psd"] = "",
         ["psb"] = "",
         ["ts"] = "",
         ["tsx"] = "",
         ["jl"] = "",
         ["pp"] = "",
         ["vue"] = "﵂",
         ["elm"] = "",
         ["swift"] = "",
         ["xcplayground"] = "",
      }
      
      M.deviconTable = setmetatable(extensionTable, {
         __index = function(extensionTable, key)
            local i = string.find(key, "[.*]")
            if i ~= nil then
               return extensionTable[string.sub(key, i + 1)]
            end
         end,
      })
      
      return M
      
      Lua
      #
      local M = {}
      M.current_mode = setmetatable({
         ["n"] = "<N>",
         ["no"] = "N·Operator Pending",
         ["v"] = "<V>",
         ["V"] = "<V>",
         ["^V"] = "<V>",
         ["s"] = "<S>",
         ["S"] = "S·Line",
         ["^S"] = "S·Block",
         ["i"] = "<I>",
         ["ic"] = "<I>",
         ["ix"] = "<I>",
         ["R"] = "<R>",
         ["Rv"] = "<V·Replace>",
         ["c"] = "<C>",
         ["cv"] = "<Vim Ex>",
         ["ce"] = "<Ex>",
         ["r"] = "Prompt",
         ["rm"] = "More",
         ["r?"] = "Confirm",
         ["!"] = "Shell",
         ["t"] = "T",
      }, {
         __index = function(_, _)
            return "V·Block"
         end,
      })
      return M
      

6. Extra   ATTACH

6.1. Hammerspoon

Yabai breaks pretty often, so I use hammerspoon as a wm instead! Heres how its done:

Lua
#
-- Load in config as modules
wm = require("wm")
plugins = require("plugins")
bindings = require("bindings")

-- hide window shadows (doesn't work)
hs.window.setShadows(false)

-- Load hs on the command line
hs.ipc.cliInstall()

-- bindings
bindings.enabled = { "grid", "hotkeys", "tiling" }

-- start/stop modules
local modules = { wm, plugins, bindings }

hs.fnutils.each(modules, function(module)
    if module then
        module.start()
    end
end)

-- stop modules on shutdown
hs.shutdownCallback = function()
    hs.fnutils.each(modules, function(module)
        if module then
            module.stop()
        end
    end)
end

-- watch config for changes and reload when they occur
function reloadConfig(files)
    doReload = false
    for _, file in pairs(files) do
        if file:sub(-4) == ".lua" then
            doReload = true
        end
    end
    if doReload then
        hs.reload()
    end
end

hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", reloadConfig):start()
hs.notify.new({ title = "hammerspoon", informativeText = "Config reloaded" }):send()

6.1.1. Hhtwm

This is where most of the magit happens. It interfaces with hammerspoon’s abstractions to macOS’s events, and controlls windows. The layout.lua files defines the usual layouts (grid, 3/2, etc)

Lua
#
-- hhtwm - hackable hammerspoon tiling wm

local createLayouts = require("hhtwm.layouts")
local spaces = require("hs._asm.undocumented.spaces")

local cache = { spaces = {}, layouts = {}, floating = {}, layoutOptions = {} }
local module = { cache = cache }

local layouts = createLayouts(module)
local log = hs.logger.new("hhtwm", "debug")

local SWAP_BETWEEN_SCREENS = false

local getDefaultLayoutOptions = function()
    return {
        mainPaneRatio = 0.5,
    }
end

local capitalize = function(str)
    return str:gsub("^%l", string.upper)
end

local ternary = function(cond, ifTrue, ifFalse)
    if cond then
        return ifTrue
    else
        return ifFalse
    end
end

local ensureCacheSpaces = function(spaceId)
    if spaceId and not cache.spaces[spaceId] then
        cache.spaces[spaceId] = {}
    end
end

local getCurrentSpacesIds = function()
    return spaces.query(spaces.masks.currentSpaces)
end

local getSpaceId = function(win)
    local spaceId

    win = win or hs.window.frontmostWindow()

    if win ~= nil and win:spaces() ~= nil and #win:spaces() > 0 then
        spaceId = win:spaces()[1]
    end

    return spaceId or spaces.activeSpace()
end

local getSpacesIdsTable = function()
    local spacesLayout = spaces.layout()
    local spacesIds = {}

    hs.fnutils.each(hs.screen.allScreens(), function(screen)
        local spaceUUID = screen:spacesUUID()

        local userSpaces = hs.fnutils.filter(spacesLayout[spaceUUID], function(spaceId)
            return spaces.spaceType(spaceId) == spaces.types.user
        end)

        hs.fnutils.concat(spacesIds, userSpaces or {})
    end)

    return spacesIds
end

local getAllWindowsUsingSpaces = function()
    local spacesIds = getSpacesIdsTable()

    local tmp = {}

    hs.fnutils.each(spacesIds, function(spaceId)
        local windows = spaces.allWindowsForSpace(spaceId)

        hs.fnutils.each(windows, function(win)
            table.insert(tmp, win)
        end)
    end)

    return tmp
end

local getScreenBySpaceId = function(spaceId)
    local spacesLayout = spaces.layout()

    return hs.fnutils.find(hs.screen.allScreens(), function(screen)
        local spaceUUID = screen:spacesUUID()

        return hs.fnutils.contains(spacesLayout[spaceUUID], spaceId)
    end)
end

local getCurrentSpacesByScreen = function()
    local currentSpaces = spaces.query(spaces.masks.currentSpaces)

    local spacesIds = {}

    hs.fnutils.each(hs.screen.allScreens(), function(screen)
        local screenSpaces = screen:spaces()

        local visibleSpace = hs.fnutils.find(screenSpaces, function(spaceId)
            return hs.fnutils.contains(currentSpaces, spaceId)
        end)

        spacesIds[screen:id()] = visibleSpace
    end)

    return spacesIds
end

-- iterate through cache.spaces to find matching window:id()
-- returns foundInAllWindows window, its space, and index in array
module.findTrackedWindow = function(win)
    if not win then
        return nil, nil, nil
    end

    local foundSpaceId, foundWinIndex, foundWin

    local didFound = false

    for spaceId, spaceWindows in pairs(cache.spaces) do
        for winIndex, window in pairs(spaceWindows) do
            if not didFound then
                didFound = window:id() == win:id()

                if didFound then
                    foundSpaceId = spaceId
                    foundWinIndex = winIndex
                    foundWin = window
                end
            end
        end
    end

    return foundWin, foundSpaceId, foundWinIndex
end

module.getLayouts = function()
    local layoutNames = {}

    for key in pairs(layouts) do
        table.insert(layoutNames, key)
    end

    return layoutNames
end

-- sets layout for space id,
-- defaults to current windows space
module.setLayout = function(layout, spaceId)
    spaceId = spaceId or getSpaceId()
    if not spaceId then
        return
    end

    cache.layouts[spaceId] = layout

    -- remove all floating windows that are on this space,
    -- so retiling will put them back in the layout
    -- this allows us to switch back from floating layout and get windows back to layout
    cache.floating = hs.fnutils.filter(cache.floating, function(win)
        return win:spaces()[1] ~= spaceId
    end)

    module.tile()
end

-- get layout for space id, priorities:
-- 1. already set layout (cache)
-- 2. layout selected by setLayout
-- 3. layout assigned by tilign.displayLayouts
-- 4. if all else fails - 'monocle'
module.getLayout = function(spaceId)
    spaceId = spaceId or getSpaceId()

    local layout = spaces.layout()
    local foundScreenUUID

    for screenUUID, layoutSpaces in pairs(layout) do
        if not foundScreenUUID then
            if hs.fnutils.contains(layoutSpaces, spaceId) then
                foundScreenUUID = screenUUID
            end
        end
    end

    local screen = hs.fnutils.find(hs.screen.allScreens(), function(screen)
        return screen:spacesUUID() == foundScreenUUID
    end)

    return cache.layouts[spaceId]
        or (screen and module.displayLayouts and module.displayLayouts[screen:id()])
        or (screen and module.displayLayouts and module.displayLayouts[screen:name()])
        or "monocle"
end

-- resbuild cache.layouts table using provided hhtwm.displayLayouts and hhtwm.defaultLayout
module.resetLayouts = function()
    for key in pairs(cache.layouts) do
        cache.layouts[key] = nil
        cache.layouts[key] = module.getLayout(key)
    end
end

module.resizeLayout = function(resizeOpt)
    local spaceId = getSpaceId()
    if not spaceId then
        return
    end

    if not cache.layoutOptions[spaceId] then
        cache.layoutOptions[spaceId] = getDefaultLayoutOptions()
    end

    local calcResizeStep = module.calcResizeStep or function()
        return 0.1
    end
    local screen = getScreenBySpaceId(spaceId)
    local step = calcResizeStep(screen)
    local ratio = cache.layoutOptions[spaceId].mainPaneRatio

    if not resizeOpt then
        ratio = 0.5
    elseif resizeOpt == "thinner" then
        ratio = math.max(ratio - step, 0)
    elseif resizeOpt == "wider" then
        ratio = math.min(ratio + step, 1)
    end

    cache.layoutOptions[spaceId].mainPaneRatio = ratio
    module.tile()
end

module.equalizeLayout = function()
    local spaceId = getSpaceId()
    if not spaceId then
        return
    end

    if cache.layoutOptions[spaceId] then
        cache.layoutOptions[spaceId] = getDefaultLayoutOptions()

        module.tile()
    end
end

-- swap windows in direction
-- works between screens
module.swapInDirection = function(win, direction)
    win = win or hs.window.frontmostWindow()

    if module.isFloating(win) then
        return
    end

    local winCmd = "windowsTo" .. capitalize(direction)
    local ONLY_FRONTMOST = true
    local STRICT_ANGLE = true
    local windowsInDirection = cache.filter[winCmd](cache.filter, win, ONLY_FRONTMOST, STRICT_ANGLE)

    windowsInDirection = hs.fnutils.filter(windowsInDirection, function(testWin)
        return testWin:isStandard() and not module.isFloating(testWin)
    end)

    if #windowsInDirection >= 1 then
        local winInDirection = windowsInDirection[1]

        local _, winInDirectionSpaceId, winInDirectionIdx = module.findTrackedWindow(winInDirection)
        local _, winSpaceId, winIdx = module.findTrackedWindow(win)

        if
            hs.fnutils.some({
                win,
                winInDirection,
                winInDirectionSpaceId,
                winInDirectionIdx,
                winSpaceId,
                winIdx,
            }, function(_)
                return _ == nil
            end)
        then
            log.e("swapInDirection error", hs.inspect({ winInDirectionSpaceId, winInDirectionIdx, winSpaceId, winIdx }))
            return
        end

        local winInDirectionScreen = winInDirection:screen()
        local winScreen = win:screen()

        -- local winInDirectionFrame  = winInDirection:frame()
        -- local winFrame             = win:frame()

        -- if swapping between screens is disabled, then return early if screen ids differ
        if not SWAP_BETWEEN_SCREENS and winScreen:id() ~= winInDirectionScreen:id() then
            return
        end

        -- otherwise, move to screen if they differ
        if winScreen:id() ~= winInDirectionScreen:id() then
            win:moveToScreen(winInDirectionScreen)
            winInDirection:moveToScreen(winScreen)
        end

        ensureCacheSpaces(winSpaceId)
        ensureCacheSpaces(winInDirectionSpaceId)

        -- swap frames
        -- winInDirection:setFrame(winFrame)
        -- win:setFrame(winInDirectionFrame)

        -- swap positions in arrays
        cache.spaces[winSpaceId][winIdx] = winInDirection
        cache.spaces[winInDirectionSpaceId][winInDirectionIdx] = win

        -- ~~no need to retile, assuming both windows were previously tiled!~~
        module.tile()
    end
end

-- throw window to screen - de-attach and re-attach
module.throwToScreen = function(win, direction)
    win = win or hs.window.frontmostWindow()

    if module.isFloating(win) then
        return
    end

    local directions = {
        next = "next",
        prev = "previous",
    }

    if not directions[direction] then
        log.e("can't throw in direction:", direction)
        return
    end

    local screen = win:screen()
    local screenInDirection = screen[directions[direction]](screen)

    if screenInDirection then
        local _, winSpaceId, winIdx = module.findTrackedWindow(win)

        if hs.fnutils.some({ winSpaceId, winIdx }, function(_)
            return _ == nil
        end) then
            log.e("throwToScreen error", hs.inspect({ winSpaceId, winIdx }))
            return
        end

        -- remove from tiling so we re-tile that window after it was moved
        if cache.spaces[winSpaceId] then
            table.remove(cache.spaces[winSpaceId], winIdx)
        else
            log.e("throwToScreen no cache.spaces for space id:", winSpaceId)
        end

        -- move window to screen
        win:moveToScreen(screenInDirection)

        -- retile to update layouts
        if hs.window.animationDuration > 0 then
            hs.timer.doAfter(hs.window.animationDuration * 1.2, module.tile)
        else
            module.tile()
        end
    end
end

module.throwToScreenUsingSpaces = function(win, direction)
    win = win or hs.window.frontmostWindow()

    if module.isFloating(win) then
        return
    end

    local directions = {
        next = "next",
        prev = "previous",
    }

    if not directions[direction] then
        log.e("can't throw in direction:", direction)
        return
    end

    local screen = win:screen()
    local screenInDirection = screen[directions[direction]](screen)
    local currentSpaces = getCurrentSpacesByScreen()
    local throwToSpaceId = currentSpaces[screenInDirection:id()]

    if not throwToSpaceId then
        log.e("no space to throw to")
        return
    end

    local _, winSpaceId, winIdx = module.findTrackedWindow(win)

    if hs.fnutils.some({ winSpaceId, winIdx }, function(_)
        return _ == nil
    end) then
        log.e("throwToScreenUsingSpaces error", hs.inspect({ winSpaceId, winIdx }))
        return
    end

    -- remove from tiling so we re-tile that window after it was moved
    if cache.spaces[winSpaceId] then
        table.remove(cache.spaces[winSpaceId], winIdx)
    else
        log.e("throwToScreenUsingSpaces no cache.spaces for space id:", winSpaceId)
    end

    local newX = screenInDirection:frame().x
    local newY = screenInDirection:frame().y

    spaces.moveWindowToSpace(win:id(), throwToSpaceId)
    win:setTopLeft(newX, newY)

    module.tile()
end

-- throw window to space, indexed
module.throwToSpace = function(win, spaceIdx)
    if not win then
        log.e("throwToSpace tried to throw nil window")
        return false
    end

    local spacesIds = getSpacesIdsTable()
    local spaceId = spacesIds[spaceIdx]

    if not spaceId then
        log.e("throwToSpace tried to move to non-existing space", spaceId, hs.inspect(spacesIds))
        return false
    end

    local targetScreen = getScreenBySpaceId(spaceId)
    local targetScreenFrame = targetScreen:frame()

    if module.isFloating(win) then
        -- adjust frame for new screen offset
        local newX = win:frame().x - win:screen():frame().x + targetScreen:frame().x
        local newY = win:frame().y - win:screen():frame().y + targetScreen:frame().y

        -- move to space
        spaces.moveWindowToSpace(win:id(), spaceId)

        -- ensure window is visible
        win:setTopLeft(newX, newY)

        return true
    end

    local _, winSpaceId, winIdx = module.findTrackedWindow(win)

    if hs.fnutils.some({ winSpaceId, winIdx }, function(_)
        return _ == nil
    end) then
        log.e("throwToSpace error", hs.inspect({ winSpaceId, winIdx }))
        return false
    end

    -- remove from tiling so we re-tile that window after it was moved
    if cache.spaces[winSpaceId] then
        table.remove(cache.spaces[winSpaceId], winIdx)
    else
        log.e("throwToSpace no cache.spaces for space id:", winSpaceId)
    end

    -- move to space
    spaces.moveWindowToSpace(win:id(), spaceId)

    -- ensure window is visible
    win:setTopLeft(targetScreenFrame.x, targetScreenFrame.y)

    -- retile when finished
    module.tile()

    return true
end

-- check if window is floating
module.isFloating = function(win)
    local trackedWin, _, _ = module.findTrackedWindow(win)
    local isTrackedAsTiling = trackedWin ~= nil

    if isTrackedAsTiling then
        return false
    end

    local isTrackedAsFloating = hs.fnutils.find(cache.floating, function(floatingWin)
        return floatingWin:id() == win:id()
    end)

    -- if window is not floating and not tiling, then default to floating?
    if isTrackedAsFloating == nil and trackedWin == nil then
        return true
    end

    return isTrackedAsFloating ~= nil
end

-- toggle floating state of window
module.toggleFloat = function(win)
    win = win or hs.window.frontmostWindow()

    if not win then
        return
    end

    if module.isFloating(win) then
        local spaceId = win:spaces()[1]
        local foundIdx

        for index, floatingWin in pairs(cache.floating) do
            if not foundIdx then
                if floatingWin:id() == win:id() then
                    foundIdx = index
                end
            end
        end

        ensureCacheSpaces(spaceId)

        table.insert(cache.spaces[spaceId], win)
        table.remove(cache.floating, foundIdx)
    else
        local foundWin, winSpaceId, winIdx = module.findTrackedWindow(win)

        if cache.spaces[winSpaceId] then
            table.remove(cache.spaces[winSpaceId], winIdx)
        else
            log.e("window made floating without previous :space()", hs.inspect(foundWin))
        end

        table.insert(cache.floating, win)
    end

    -- update tiling
    module.tile()
end

-- internal function for deciding if window should float when recalculating tiling
local shouldFloat = function(win)
    -- if window is in tiling cache, then it is not floating
    local inTilingCache, _, _ = module.findTrackedWindow(win)
    if inTilingCache then
        return false
    end

    -- if window is already tracked as floating, then leave it be
    local isTrackedAsFloating = hs.fnutils.find(cache.floating, function(floatingWin)
        return floatingWin:id() == win:id()
    end) ~= nil
    if isTrackedAsFloating then
        return true
    end

    -- otherwise detect if window should be floated/tiled
    return not module.detectTile(win)
end

-- tile windows - combine caches with current state, and apply layout
module.tile = function()
    -- ignore tiling if we're doing something with a mouse
    if #hs.mouse.getButtons() ~= 0 then
        return
    end

    -- this allows us to have tabs and do proper tiling!
    local tilingWindows = {}
    local floatingWindows = {}

    local currentSpaces = getCurrentSpacesIds()

    local allWindows = hs.window.allWindows()
    -- local allWindowsFilter = cache.filter:getWindows()
    -- local allWindowsSpaces = getAllWindowsUsingSpaces()

    -- log.d('allWindows', hs.inspect(allWindows))
    -- log.d('allWindowsFilter', hs.inspect(allWindowsFilter))

    hs.fnutils.each(allWindows or {}, function(win)
        -- we don't care about minimized or fullscreen windows
        if win:isMinimized() or win:isFullscreen() then
            return
        end

        -- we also don't care about special windows that have no spaces
        if not win:spaces() or #win:spaces() == 0 then
            return
        end

        if shouldFloat(win) then
            table.insert(floatingWindows, win)
        else
            table.insert(tilingWindows, win)
        end
    end)

    -- add new tiling windows to cache
    hs.fnutils.each(tilingWindows, function(win)
        if not win or #win:spaces() == 0 then
            return
        end

        local spaces = win:spaces()
        local spaceId = spaces[1]
        local tmp = cache.spaces[spaceId] or {}
        local trackedWin, trackedSpaceId, _ = module.findTrackedWindow(win)

        -- log.d('update cache.spaces', hs.inspect({
        --   win = win,
        --   spaces = spaces,
        --   spaceId = spaceId,
        --   trackedWin = trackedWin or 'none',
        --   trackedSpaceId = trackedSpaceId or 'none',
        --   shouldInsert = not trackedWin or trackedSpaceId ~= spaceId
        -- }))

        -- window is "new" if it's not in cache at all, or if it changed space
        if not trackedWin or trackedSpaceId ~= spaceId then
            -- table.insert(tmp, 1, win)
            table.insert(tmp, win)
        end

        cache.spaces[spaceId] = tmp
    end)

    -- clean up tiling cache
    hs.fnutils.each(currentSpaces, function(spaceId)
        local spaceWindows = cache.spaces[spaceId] or {}

        for i = #spaceWindows, 1, -1 do
            -- window exists in cache if there's spaceId and windowId match
            local existsOnScreen = hs.fnutils.find(tilingWindows, function(win)
                return win:id() == spaceWindows[i]:id() and win:spaces()[1] == spaceId
            end)

            -- window is duplicated (why?) if it's tracked more than once
            -- this shouldn't happen, but helps for now...
            local duplicateIdx = 0

            for j = 1, #spaceWindows do
                if spaceWindows[i]:id() == spaceWindows[j]:id() and i ~= j then
                    duplicateIdx = j
                end
            end

            if duplicateIdx > 0 then
                log.e(
                    "duplicate idx",
                    hs.inspect({
                        i = i,
                        duplicateIdx = duplicateIdx,
                        spaceWindows = spaceWindows,
                    })
                )
            end

            if not existsOnScreen or duplicateIdx > 0 then
                table.remove(spaceWindows, i)
            else
                i = i + 1
            end
        end

        cache.spaces[spaceId] = spaceWindows
    end)

    -- add new windows to floating cache
    hs.fnutils.each(floatingWindows, function(win)
        if not module.isFloating(win) then
            table.insert(cache.floating, win)
        end
    end)

    -- clean up floating cache
    cache.floating = hs.fnutils.filter(cache.floating, function(cacheWin)
        return hs.fnutils.find(floatingWindows, function(win)
            return cacheWin:id() == win:id()
        end)
    end)

    -- apply layout window-by-window
    local moveToFloat = {}

    hs.fnutils.each(currentSpaces, function(spaceId)
        local spaceWindows = cache.spaces[spaceId] or {}

        hs.fnutils.each(hs.screen.allScreens(), function(screen)
            local screenWindows = hs.fnutils.filter(spaceWindows, function(win)
                return win:screen():id() == screen:id()
            end)
            local layoutName = module.getLayout(spaceId)

            if not layoutName or not layouts[layoutName] then
                log.e("layout doesn't exist: " .. layoutName)
            else
                for index, window in pairs(screenWindows) do
                    local frame = layouts[layoutName](
                        window,
                        screenWindows,
                        screen,
                        index,
                        cache.layoutOptions[spaceId] or getDefaultLayoutOptions()
                    )

                    -- only set frame if returned,
                    -- this allows for layout to decide if window should be floating
                    if frame then
                        window:setFrame(frame)
                    else
                        table.insert(moveToFloat, window)
                    end
                end
            end
        end)
    end)

    hs.fnutils.each(moveToFloat, function(win)
        local _, spaceId, winIdx = module.findTrackedWindow(win)

        table.remove(cache.spaces[spaceId], winIdx)
        table.insert(cache.floating, win)
    end)
end

-- tile detection:
-- 1. test tiling.filters if exist
-- 2. check if there's fullscreen button -> yes = tile, no = float
module.detectTile = function(win)
    local app = win:application():name()
    local role = win:role()
    local subrole = win:subrole()
    local title = win:title()

    if module.filters then
        local foundMatch = hs.fnutils.find(module.filters, function(obj)
            local appMatches = ternary(obj.app ~= nil and app ~= nil, string.match(app, obj.app or ""), true)
            local titleMatches = ternary(obj.title ~= nil and title ~= nil, string.match(title, obj.title or ""), true)
            local roleMatches = ternary(obj.role ~= nil, obj.role == role, true)
            local subroleMatches = ternary(obj.subrole ~= nil, obj.subrole == subrole, true)

            return appMatches and titleMatches and roleMatches and subroleMatches
        end)

        if foundMatch then
            return foundMatch.tile
        end
    end

    local shouldTileDefault = hs.axuielement.windowElement(win):isAttributeSettable("AXSize")
    return shouldTileDefault
end

-- mostly for debugging
module.reset = function()
    cache.spaces = {}
    cache.layouts = {}
    cache.floating = {}

    module.tile()
end

local loadSettings = function()
    -- load from cache
    local jsonTilingCache = hs.settings.get("hhtwm.tilingCache")
    local jsonFloatingCache = hs.settings.get("hhtwm.floatingCache")

    log.d("reading from hs.settings")
    log.d("hhtwm.tilingCache", jsonTilingCache)
    log.d("hhtwm.floatingCache", jsonFloatingCache)

    -- all windows from window filter
    local allWindows = getAllWindowsUsingSpaces()

    local findWindowById = function(winId)
        return hs.fnutils.find(allWindows, function(win)
            return win:id() == winId
        end)
    end

    -- decode tiling cache
    if jsonTilingCache then
        local tilingCache = hs.json.decode(jsonTilingCache)
        local spacesIds = getSpacesIdsTable()

        hs.fnutils.each(tilingCache, function(obj)
            -- we don't care about spaces that no longer exist
            if hs.fnutils.contains(spacesIds, obj.spaceId) then
                cache.spaces[obj.spaceId] = {}
                cache.layouts[obj.spaceId] = obj.layout
                cache.layoutOptions[obj.spaceId] = obj.layoutOptions

                hs.fnutils.each(obj.windowIds, function(winId)
                    local win = findWindowById(winId)

                    log.d("restoring (spaceId, windowId, window)", obj.spaceId, winId, win)
                    if win then
                        table.insert(cache.spaces[obj.spaceId], win)
                    end
                end)
            end
        end)
    end

    -- decode floating cache
    if jsonFloatingCache then
        local floatingCache = hs.json.decode(jsonFloatingCache)

        hs.fnutils.each(floatingCache, function(winId)
            local win = hs.window.find(winId)

            -- we don't care about windows that no longer exist
            if win then
                table.insert(cache.floating, win)
            end
        end)
    end

    log.d("read from hs.settings")
    log.d("cache.spaces", hs.inspect(cache.spaces))
    log.d("cache.floating", hs.inspect(cache.floating))
end

local saveSettings = function()
    local tilingCache = {}
    local floatingCache = hs.fnutils.map(cache.floating, function(win)
        return win:id()
    end)

    for spaceId, spaceWindows in pairs(cache.spaces) do
        -- only save spaces with windows on them
        if #spaceWindows > 0 then
            local tmp = {}

            for _, window in pairs(spaceWindows) do
                log.d("storing (spaceId, windowId, window)", spaceId, window:id(), window)
                table.insert(tmp, window:id())
            end

            table.insert(tilingCache, {
                spaceId = spaceId,
                layout = module.getLayout(spaceId),
                layoutOptions = cache.layoutOptions[spaceId],
                windowIds = tmp,
            })
        end
    end

    local jsonTilingCache = hs.json.encode(tilingCache)
    local jsonFloatingCache = hs.json.encode(floatingCache)

    log.d("storing to hs.settings")
    log.d("hhtwm.tiling", jsonTilingCache)
    log.d("hhtwm.floating", jsonFloatingCache)

    hs.settings.set("hhtwm.tilingCache", jsonTilingCache)
    hs.settings.set("hhtwm.floatingCache", jsonFloatingCache)
end

module.start = function()
    -- discover windows on spaces as soon as possible
    -- hs.window.filter.forceRefreshOnSpaceChange = true

    -- start window filter
    cache.filter = hs.window.filter.new():setDefaultFilter():setOverrideFilter({
        visible = true, -- only allow visible windows
        fullscreen = false, -- ignore fullscreen windows
        -- currentSpace = true,  -- only windows on current space
        allowRoles = { "AXStandardWindow" },
    })
    -- :setSortOrder(hs.window.filter.sortByCreated)

    -- load window/floating status from saved state
    loadSettings()

    -- retile automatically when windows change
    cache.filter:subscribe({ hs.window.filter.windowsChanged }, module.tile)

    -- update on screens change
    cache.screenWatcher = hs.screen.watcher.new(module.tile):start()

    -- tile on start
    module.tile()
end

module.stop = function()
    -- store cache so we persist layouts between restarts
    saveSettings()

    -- stop filter
    cache.filter:unsubscribeAll()

    -- stop watching screens
    cache.screenWatcher:stop()
end

return module
Lua
#
-- hhtwm layouts

return function(hhtwm)
    local layouts = {}

    local getInsetFrame = function(screen)
        local screenFrame = screen:fullFrame()
        local screenMargin = hhtwm.screenMargin or { top = 0, bottom = 0, right = 0, left = 0 }

        return {
            x = screenFrame.x + screenMargin.left,
            y = screenFrame.y + screenMargin.top,
            w = screenFrame.w - (screenMargin.left + screenMargin.right),
            h = screenFrame.h - (screenMargin.top + screenMargin.bottom),
        }
    end

    layouts["floating"] = function()
        return nil
    end

    layouts["monocle"] = function(_, _, screen)
        local margin = hhtwm.margin or 0
        local insetFrame = getInsetFrame(screen)

        local frame = {
            x = insetFrame.x + margin / 2,
            y = insetFrame.y + margin / 2,
            w = insetFrame.w - margin,
            h = insetFrame.h - margin,
        }

        return frame
    end

    layouts["main-left"] = function(window, windows, screen, index, layoutOptions)
        if #windows == 1 then
            return layouts["main-center"](window, windows, screen, index, layoutOptions)
        end

        local margin = hhtwm.margin or 0
        local insetFrame = getInsetFrame(screen)

        local frame = {
            x = insetFrame.x,
            y = insetFrame.y,
            w = 0,
            h = 0,
        }

        if index == 1 then
            frame.x = frame.x + margin / 2
            frame.y = frame.y + margin / 2
            frame.h = insetFrame.h - margin
            frame.w = insetFrame.w * layoutOptions.mainPaneRatio - margin
        else
            local divs = #windows - 1
            local h = insetFrame.h / divs

            frame.h = h - margin
            frame.w = insetFrame.w * (1 - layoutOptions.mainPaneRatio) - margin
            frame.x = frame.x + insetFrame.w * layoutOptions.mainPaneRatio + margin / 2
            frame.y = frame.y + h * (index - 2) + margin / 2
        end

        return frame
    end

    layouts["main-right"] = function(window, windows, screen, index, layoutOptions)
        if #windows == 1 then
            return layouts["main-center"](window, windows, screen, index, layoutOptions)
        end

        local margin = hhtwm.margin or 0
        local insetFrame = getInsetFrame(screen)

        local frame = {
            x = insetFrame.x,
            y = insetFrame.y,
            w = 0,
            h = 0,
        }

        if index == 1 then
            frame.x = frame.x + insetFrame.w * layoutOptions.mainPaneRatio + margin / 2
            frame.y = frame.y + margin / 2
            frame.h = insetFrame.h - margin
            frame.w = insetFrame.w * (1 - layoutOptions.mainPaneRatio) - margin
        else
            local divs = #windows - 1
            local h = insetFrame.h / divs

            frame.x = frame.x + margin / 2
            frame.y = frame.y + h * (index - 2) + margin / 2
            frame.w = insetFrame.w * layoutOptions.mainPaneRatio - margin
            frame.h = h - margin
        end

        return frame
    end

    layouts["main-center"] = function(window, windows, screen, index, layoutOptions)
        local insetFrame = getInsetFrame(screen)
        local margin = hhtwm.margin or 0
        local mainColumnWidth = insetFrame.w * layoutOptions.mainPaneRatio + margin / 2

        if index == 1 then
            return {
                x = insetFrame.x + (insetFrame.w - mainColumnWidth) / 2 + margin / 2,
                y = insetFrame.y + margin / 2,
                w = mainColumnWidth - margin,
                h = insetFrame.h - margin,
            }
        end

        local frame = {
            x = insetFrame.x,
            y = 0,
            w = (insetFrame.w - mainColumnWidth) / 2 - margin,
            h = 0,
        }

        if (index - 1) % 2 == 0 then
            local divs = math.floor((#windows - 1) / 2)
            local h = insetFrame.h / divs

            frame.x = frame.x + margin / 2
            frame.h = h - margin
            frame.y = insetFrame.y + h * math.floor(index / 2 - 1) + margin / 2
        else
            local divs = math.ceil((#windows - 1) / 2)
            local h = insetFrame.h / divs

            frame.x = frame.x + (insetFrame.w - frame.w - margin) + margin / 2
            frame.h = h - margin
            frame.y = insetFrame.y + h * math.floor(index / 2 - 1) + margin / 2
        end

        return frame
    end

    layouts["tabbed-left"] = function(window, windows, screen, index, layoutOptions)
        if #windows == 1 then
            return layouts["main-center"](window, windows, screen, index, layoutOptions)
        end

        local margin = hhtwm.margin or 0
        local insetFrame = getInsetFrame(screen)

        local frame = {
            x = insetFrame.x,
            y = insetFrame.y,
            w = 0,
            h = 0,
        }

        if index == 1 then
            frame.x = frame.x + insetFrame.w * layoutOptions.mainPaneRatio + margin / 2
            frame.y = frame.y + margin / 2
            frame.w = insetFrame.w * (1 - layoutOptions.mainPaneRatio) - margin
            frame.h = insetFrame.h - margin
        else
            frame.x = frame.x + margin / 2
            frame.y = frame.y + margin / 2
            frame.w = insetFrame.w * layoutOptions.mainPaneRatio - margin
            frame.h = insetFrame.h - margin
        end

        return frame
    end

    layouts["tabbed-right"] = function(window, windows, screen, index, layoutOptions)
        if #windows == 1 then
            return layouts["main-center"](window, windows, screen, index, layoutOptions)
        end

        local margin = hhtwm.margin or 0
        local insetFrame = getInsetFrame(screen)

        local frame = {
            x = insetFrame.x,
            y = insetFrame.y,
            w = 0,
            h = 0,
        }

        if index == 1 then
            frame.x = frame.x + margin / 2
            frame.y = frame.y + margin / 2
            frame.w = insetFrame.w * layoutOptions.mainPaneRatio - margin
            frame.h = insetFrame.h - margin
        else
            frame.x = frame.x + insetFrame.w * layoutOptions.mainPaneRatio + margin / 2
            frame.y = frame.y + margin / 2
            frame.w = insetFrame.w * (1 - layoutOptions.mainPaneRatio) - margin
            frame.h = insetFrame.h - margin
        end

        return frame
    end

    -- TODO
    -- layouts["stacking-columns"] = function(window, windows, screen, index, layoutOptions)
    --   return nil
    -- end

    return layouts
end

6.1.2. Plugins

With that, we need the caffiene plugin, which this file handles configuring

Lua
#
local module = { cache = cache }
local hyper = { "cmd", "ctrl", "shift" }

module.start = function()
    hs.loadSpoon("SpoonInstall")

    spoon.SpoonInstall.use_syncinstall = true

    Install = spoon.SpoonInstall

    Install:andUse("Caffeine", {
        start = false,
        hotkeys = {
            toggle = { hyper, "1" },
        },
    })
end

module.stop = function() end

return module

6.1.3. Bindings

No use having the logic for a WM if we can’t use it. lets define some bindings to help with that

Lua
#
local cache = {}
local module = { cache = cache }

-- modifiers in use:
-- * cltr+alt: move focus between windows
-- * ctrl+shift: do things to windows
-- * ultra: custom/global bindings

module.start = function()
    hs.fnutils.each(bindings.enabled, function(binding)
        cache[binding] = require("bindings." .. binding)
        cache[binding].start()
    end)
end

module.stop = function()
    hs.fnutils.each(cache, function(binding)
        binding.stop()
    end)
end

return module
Lua
#
local module = { cache = cache }

module.start = function()
    local mash = { "ctrl", "cmd" }

    hs.window.animationDuration = 0

    function bindKey(key, fn)
        hs.hotkey.bind(mash, key, fn)
    end

    positions = {
        maximized = { x = 0, y = 0, w = 1, h = 1 },
        centered = { x = 0.17, y = 0.08, w = 0.66, h = 0.85 },
        center = { x = 0.1, y = 0.05, w = 0.77, h = 0.88 },

        left34 = { x = 0, y = 0, w = 0.34, h = 1 },
        left50 = hs.layout.left50,
        left66 = { x = 0, y = 0, w = 0.66, h = 1 },
        left70 = hs.layout.left70,

        right30 = hs.layout.right30,
        right34 = { x = 0.66, y = 0, w = 0.34, h = 1 },
        right50 = hs.layout.right50,
        right66 = { x = 0.34, y = 0, w = 0.66, h = 1 },

        upper50 = { x = 0, y = 0, w = 1, h = 0.5 },
        upper50Left50 = { x = 0, y = 0, w = 0.5, h = 0.5 },
        upper50Right15 = { x = 0.85, y = 0, w = 0.15, h = 0.5 },
        upper50Right30 = { x = 0.7, y = 0, w = 0.3, h = 0.5 },
        upper50Right50 = { x = 0.5, y = 0, w = 0.5, h = 0.5 },

        lower50 = { x = 0, y = 0.5, w = 1, h = 0.5 },
        lower50Left50 = { x = 0, y = 0.5, w = 0.5, h = 0.5 },
        lower50Right50 = { x = 0.5, y = 0.5, w = 0.5, h = 0.5 },

        chat = { x = 0.5, y = 0, w = 0.35, h = 0.5 },
    }

    -- Grid
    grid = {
        { key = "h", units = { positions.left50, positions.left66, positions.left34 } },
        { key = "j", units = { positions.lower50 } },
        { key = "k", units = { positions.upper50 } },
        { key = "l", units = { positions.right50, positions.right66, positions.right34 } },

        { key = "u", units = { positions.upper50Left50 } },
        { key = "o", units = { positions.upper50Right50 } },
        { key = "i", units = { positions.centered, positions.center, positions.maximized } },
        { key = ",", units = { positions.lower50Left50 } },
        { key = ".", units = { positions.lower50Right50 } },
        { key = "f", units = { positions.maximized } },
    }

    hs.fnutils.each(grid, function(entry)
        bindKey(entry.key, function()
            local units = entry.units
            local screen = hs.screen.mainScreen()
            local window = hs.window.focusedWindow()
            local windowGeo = window:frame()

            local index = 0
            hs.fnutils.find(units, function(unit)
                index = index + 1

                local geo = hs.geometry.new(unit):fromUnitRect(screen:frame()):floor()
                return windowGeo:equals(geo)
            end)
            if index == #units then
                index = 0
            end

            currentLayout = null
            window:moveToUnit(units[index + 1])
        end)
    end)

    --Layouts
    -- hs.hotkey.bind(mash_shift, 'j', function()
    --   hs.layout.apply({
    --     {"Firefox", nil, screen, positions.right50, nil, nil},
    --     {"iTerm2",   nil, screen, positions.left50, nil, nil}
    --   })
    -- end)
    --
    -- hs.hotkey.bind(mash_shift, 'k', function()
    --   hs.layout.apply({
    --     {"Firefox", nil, screen, positions.center, nil, nil},
    --     {"iTerm2",   nil, screen, positions.center, nil, nil}
    --   })
    -- end)

    local function testing(app, eventType, state)
        -- hs.alert(app)
        if app == "iTerm" then
            local maximized = { x = 0, y = 0, w = 1, h = 1 }
            local windowGeo = window:frame()
            local screen = hs.screen.mainScreen()
            local geo = hs.geometry.new(maximized):fromUnitRect(screen:frame()):floor()
            return windowGeo:equals(geo)
        end
    end

    local setItermSize = hs.application.watcher.new(testing)
    setItermSize:start()
end

return module
Lua
#
local module = { cache = cache }
local hyper = { "cmd", "ctrl", "shift" }

module.start = function()
    ---------------------------------------------------------
    -- App Hotkeys
    ---------------------------------------------------------

    hs.hotkey.bind(hyper, "J", function()
        hs.application.launchOrFocus("Alacritty")
    end)
    hs.hotkey.bind(hyper, "K", function()
        hs.application.launchOrFocus("Firefox")
    end)
    hs.hotkey.bind(hyper, "T", function()
        hs.application.launchOrFocus("Transmit")
    end)
    hs.hotkey.bind(hyper, "M", function()
        hs.application.launchOrFocus("Mail")
    end)
    hs.hotkey.bind(hyper, "G", function()
        hs.application.launchOrFocus("Tower")
    end)

    ---------------------------------------------------------
    -- ON-THE-FLY KEYBIND
    ---------------------------------------------------------

    -- Temporarily bind an application to be toggled by the V key
    -- useful for once-in-a-while applications like Preview
    local boundApplication = nil

    hs.hotkey.bind(hyper, "C", function()
        local appName = hs.window.focusedWindow():application():title()

        if boundApplication then
            boundApplication:disable()
        end

        boundApplication = hs.hotkey.bind(hyper, "V", function()
            hs.application.launchOrFocus(appName)
        end)

        -- https://github.com/Hammerspoon/hammerspoon/issues/184#issuecomment-102835860
        boundApplication:disable()
        boundApplication:enable()

        hs.alert(string.format('Binding: "%s" => hyper + V', appName))
    end)

    ---------------------------------------------------------
    -- Simulate arrow keys with Hyper
    ---------------------------------------------------------
    -- https://stackoverflow.com/questions/41025343/when-remapping-hyper-key-caps-lock-to-f18-with-hammerspoon-is-it-possible-to-us

    arrowKey = function(arrow, modifiers)
        local event = require("hs.eventtap").event
        event.newKeyEvent(modifiers, string.lower(arrow), true):post()
        event.newKeyEvent(modifiers, string.lower(arrow), false):post()
    end

    hs.hotkey.bind(hyper, "N", function()
        arrowKey("DOWN")
    end)
    hs.hotkey.bind(hyper, "P", function()
        arrowKey("UP")
    end)

    ---------------------------------------------------------
    -- Misc
    ---------------------------------------------------------

    hs.hotkey.bind(hyper, "X", function()
        hs.openConsole()
        hs.focus()
    end)

    -- Reload config
    hs.hotkey.bind(hyper, "R", function()
        hs.reload()
    end)
end

return module
Lua
#
local module = {}
local hhtwm = wm.cache.hhtwm

local keys = { "ctrl", "cmd" }
hs.window.animationDuration = 0

module.start = function()
    local bind = function(key, fn)
        hs.hotkey.bind(keys, key, fn, nil, fn)
    end

    -- toggle [f]loat
    bind("w", function()
        local win = hs.window.frontmostWindow()

        if not win then
            return
        end

        hhtwm.toggleFloat(win)

        if hhtwm.isFloating(win) then
            hs.grid.center(win)
        end

        highlightWindow()
    end)

    -- bind('b', function() hhtwm.setLayout('cards') end)
    bind("]", function()
        wm.cycleLayout()
    end)

    -- [r]eset
    bind("r", hhtwm.reset)

    -- re[t]ile
    bind("t", hhtwm.tile)

    -- [e]qualize
    bind("e", hhtwm.equalizeLayout)

    -- toggle [z]oom window
    bind("z", function()
        local win = hs.window.frontmostWindow()

        if not hhtwm.isFloating(win) then
            hhtwm.toggleFloat(win)
            hs.grid.maximizeWindow(win)
        else
            hhtwm.toggleFloat(win)
        end

        highlightWindow()
    end)

    -- throw window to space (and move)
    for n = 0, 9 do
        local idx = tostring(n)

        -- important: use this with onKeyReleased, not onKeyPressed
        hs.hotkey.bind(keys, idx, nil, function()
            local win = hs.window.focusedWindow()

            -- if there's no focused window, just move to that space
            if not win then
                hs.eventtap.keyStroke({ "ctrl" }, idx)
                return
            end

            local isFloating = hhtwm.isFloating(win)
            local success = hhtwm.throwToSpace(win, n)

            -- if window switched space, then follow it (ctrl + 0..9) and focus
            if success then
                hs.eventtap.keyStroke({ "ctrl" }, idx)

                -- retile and re-highlight window after we switch space
                hs.timer.doAfter(0.05, function()
                    if not isFloating then
                        hhtwm.tile()
                    end
                    highlightWindow(win)
                end)
            end
        end)
    end
end

module.stop = function() end

return module

6.2. Wallpapers

Wallpapers are in ./extra/wallpapers/ Some of my favorites: Note: fix later, using links from github

TODO: Attach from github

Author: Shaurya Singh

Created: 2021-12-06 Mon 21:17