commit 23d4795228cdeff14f18f630dcd4fa828d472c74
Author: Barrett Ruth
Date: Sat Feb 7 00:45:47 2026 -0500
initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4812d58
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+result
+.direnv/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a1229ce
--- /dev/null
+++ b/README.md
@@ -0,0 +1,392 @@
+# NixOS Migration Guide
+
+## Before you start
+
+Things to have ready:
+
+- A USB stick (4GB+)
+- WiFi password
+- This guide on your phone
+
+Your nix-config already has both `homeConfigurations.barrett` (standalone
+home-manager, what you use now on Arch) and `nixosConfigurations.xps15` (full
+NixOS). The flake, configuration.nix, and all home-manager modules are ready.
+The only file you're missing is `hosts/xps15/hardware-configuration.nix`, which
+gets generated during install.
+
+## 1. Prep on Arch (before wiping)
+
+### Back up
+
+```sh
+# Secrets
+cp -r ~/.gnupg /tmp/gnupg-backup
+cp -r ~/.ssh /tmp/ssh-backup
+cp -r ~/.local/share/pass /tmp/pass-backup
+
+# Fonts (not in nixpkgs)
+tar cf /tmp/fonts.tar ~/.local/share/fonts
+
+# Browser profile
+tar cf /tmp/zen.tar ~/.zen
+
+# Any uncommitted work
+cd ~/dev && git status # check each repo
+
+# Wallpapers and lock screen images
+tar cf /tmp/img.tar ~/img
+```
+
+Copy all of `/tmp/*-backup` and `/tmp/*.tar` to the USB stick or another
+machine.
+
+### Push this repo
+
+```sh
+cd ~/nix-config # (as barrett, or sg barrett)
+git push
+```
+
+### Download NixOS ISO
+
+Grab the minimal ISO from https://nixos.org/download — you want the "Minimal
+ISO image" (not graphical), x86_64.
+
+Flash it:
+
+```sh
+sudo dd bs=4M if=nixos-minimal-*.iso of=/dev/sdX status=progress oflag=sync
+```
+
+## 2. Boot the installer
+
+Reboot, mash F12 for boot menu, pick USB.
+
+You'll land in a root shell. Connect to WiFi:
+
+```sh
+systemctl start wpa_supplicant
+wpa_cli
+> add_network 0
+> set_network 0 ssid "YourSSID"
+> set_network 0 psk "YourPassword"
+> enable_network 0
+> quit
+```
+
+Or if you prefer `iwd` (available in the installer too):
+
+```sh
+iwctl
+[iwd]# station wlan0 scan
+[iwd]# station wlan0 get-networks
+[iwd]# station wlan0 connect YourSSID
+```
+
+Verify: `ping nixos.org`
+
+## 3. Partition
+
+Your current disk layout (from fstab):
+
+| Mount | FS | UUID |
+|----------|------|-------------------|
+| `/` | ext4 | `1ac6e3de-...` |
+| `/boot/efi` | vfat | `5646-BF32` |
+| swap | swap | `39cde381-...` |
+
+### Option A: Reuse existing partitions (if dual-boot / keeping data)
+
+```sh
+# Find your partitions
+lsblk -f
+
+# Format root (THIS WIPES ARCH)
+mkfs.ext4 -L nixos /dev/nvme0n1pX
+
+# Mount
+mount /dev/nvme0n1pX /mnt
+mkdir -p /mnt/boot/efi
+mount /dev/nvme0n1pY /mnt/boot/efi
+swapon /dev/nvme0n1pZ
+```
+
+### Option B: Fresh partition table
+
+```sh
+# Wipe and repartition
+fdisk /dev/nvme0n1
+
+# Create:
+# 1. EFI partition (512M, type EFI System)
+# 2. Swap partition (16G or match your RAM)
+# 3. Root partition (rest of disk, type Linux filesystem)
+
+mkfs.fat -F 32 /dev/nvme0n1p1
+mkswap /dev/nvme0n1p2
+mkfs.ext4 -L nixos /dev/nvme0n1p3
+
+mount /dev/nvme0n1p3 /mnt
+mkdir -p /mnt/boot/efi
+mount /dev/nvme0n1p1 /mnt/boot/efi
+swapon /dev/nvme0n1p2
+```
+
+## 4. Generate hardware config
+
+```sh
+nixos-generate-config --root /mnt
+```
+
+This creates:
+
+- `/mnt/etc/nixos/configuration.nix` (ignore this, we have our own)
+- `/mnt/etc/nixos/hardware-configuration.nix` (we need this)
+
+Copy it into your config structure:
+
+```sh
+mkdir -p /mnt/home/barrett/nix-config/hosts/xps15
+cp /mnt/etc/nixos/hardware-configuration.nix /mnt/home/barrett/nix-config/hosts/xps15/
+```
+
+## 5. Get your config onto the new system
+
+### Option A: Clone from git (needs network)
+
+```sh
+nix-shell -p git
+git clone https://github.com/YOUR/nix-config /mnt/home/barrett/nix-config
+cp /mnt/etc/nixos/hardware-configuration.nix /mnt/home/barrett/nix-config/hosts/xps15/
+```
+
+### Option B: Copy from USB
+
+```sh
+mount /dev/sdX1 /mnt2 # your usb
+cp -r /mnt2/nix-config /mnt/home/barrett/nix-config
+cp /mnt/etc/nixos/hardware-configuration.nix /mnt/home/barrett/nix-config/hosts/xps15/
+```
+
+## 6. Install
+
+```sh
+nixos-install --flake /mnt/home/barrett/nix-config#xps15
+```
+
+It will:
+
+- Build the full system closure
+- Install GRUB
+- Ask you to set the root password
+
+This takes a while. Let it run.
+
+## 7. First boot
+
+```sh
+reboot
+```
+
+Remove the USB. GRUB should appear. Boot NixOS.
+
+Log in as root (password you just set), then set barrett's password:
+
+```sh
+passwd barrett
+```
+
+Log out, log in as barrett.
+
+## 8. Post-install setup
+
+### Fix ownership
+
+```sh
+sudo chown -R barrett:users ~/nix-config
+```
+
+### Home-manager (already integrated)
+
+Your flake uses `home-manager.nixosModules.home-manager` so HM is part of the
+system build. No separate `home-manager switch` needed — it all happens during
+`nixos-rebuild switch`.
+
+### Restore secrets
+
+```sh
+# From USB or wherever you backed up
+cp -r /path/to/gnupg-backup ~/.gnupg
+chmod 700 ~/.gnupg
+chmod 600 ~/.gnupg/*
+
+cp -r /path/to/ssh-backup ~/.ssh
+chmod 700 ~/.ssh
+chmod 600 ~/.ssh/*
+
+cp -r /path/to/pass-backup ~/.local/share/pass
+```
+
+### Restore fonts
+
+```sh
+mkdir -p ~/.local/share/fonts
+tar xf /path/to/fonts.tar -C /
+fc-cache -fv
+```
+
+### Restore images
+
+```sh
+tar xf /path/to/img.tar -C /
+```
+
+### Restore browser profile
+
+```sh
+tar xf /path/to/zen.tar -C /
+```
+
+### Rebuild (the main command going forward)
+
+```sh
+sudo nixos-rebuild switch --flake ~/nix-config#xps15
+```
+
+## 9. Gaps — things still needed in configuration.nix
+
+Your `configuration.nix` already covers: GRUB, nvidia, iwd, keyd, pipewire,
+docker, libvirtd, openssh, hyprland, bluetooth, zsh, xdg portals.
+
+Still missing:
+
+### Must add
+
+```nix
+# /bin/sh = dash (you have a pacman hook for this on Arch)
+environment.binsh = "${pkgs.dash}/bin/dash";
+
+# doas (you use this instead of sudo on Arch)
+security.doas = {
+ enable = true;
+ extraRules = [{
+ groups = [ "wheel" ];
+ persist = true;
+ }];
+};
+
+# Auto timezone (replaces your tzupdate timer)
+services.automatic-timezoned.enable = true;
+# (already in your config, just confirming)
+
+# Multilib equivalent — enable 32-bit support for Steam/Wine if needed
+# hardware.graphics.enable32Bit = true;
+```
+
+### Should add
+
+```nix
+# Reflector equivalent — NixOS handles mirrors differently, but you may want
+# to pin a substituter or add cachix
+nix.settings = {
+ experimental-features = [ "nix-command" "flakes" ];
+ # already there, just noting
+};
+
+# geoclue for timezone detection
+services.geoclue2.enable = true;
+
+# X11 session (spectrwm/dwm) — if you want to keep X11 as an option
+services.xserver = {
+ enable = true;
+ videoDrivers = [ "nvidia" ];
+ windowManager.spectrwm.enable = true;
+ # or just use xinit manually
+};
+
+# Fonts — system-wide
+fonts.packages = with pkgs; [
+ jetbrains-mono
+ joypixels
+ font-awesome
+ # Your custom fonts (berkeley-mono etc) aren't in nixpkgs,
+ # keep them in ~/.local/share/fonts
+];
+
+# Yubikey / smartcard
+services.pcscd.enable = true;
+
+# QEMU/KVM
+virtualisation.libvirtd.qemu.package = pkgs.qemu_full;
+
+# Firewall (you have iptables on Arch)
+networking.firewall.enable = true;
+```
+
+### Packages to add to environment.systemPackages or HM
+
+Compare your 207 explicit Arch packages against what's in
+`home/modules/packages.nix`. The big categories not yet covered:
+
+- **TeX**: texlive, biber, typst, quarto — add to HM packages
+- **Languages**: rustup, go, ocaml, opam, uv, luarocks — add to HM packages
+ (or use devShells per-project)
+- **Dev tools**: cmake, ninja, gdb, valgrind, perf — add to HM or devShells
+- **CLI**: fastfetch, socat, rsync, bind (dig), time — add to HM packages
+- **AUR equivalents**: voxtype, pikaur (not needed), sioyek (already in HM),
+ basedpyright, pistol, clipmenu (not needed on Wayland)
+
+### Things you DON'T need on NixOS
+
+- pacman hooks (dash, nvidia) — use NixOS options instead
+- pikaur/AUR — use nixpkgs, overlays, or flake inputs
+- reflector — not applicable
+- mkinitcpio — NixOS uses its own initrd builder
+- GRUB manual config — declarative via `boot.loader.grub`
+
+## 10. Day-to-day workflow
+
+```sh
+# Edit config
+nvim ~/nix-config/hosts/xps15/configuration.nix
+# or any home-manager module
+nvim ~/nix-config/home/modules/shell.nix
+
+# Rebuild
+sudo nixos-rebuild switch --flake ~/nix-config#xps15
+
+# Update all inputs
+nix flake update --flake ~/nix-config
+sudo nixos-rebuild switch --flake ~/nix-config#xps15
+
+# Rollback if something breaks
+sudo nixos-rebuild switch --flake ~/nix-config#xps15 --rollback
+# or pick a previous generation from GRUB
+
+# Garbage collect old generations
+sudo nix-collect-garbage -d
+```
+
+## 11. Checklist
+
+- [ ] Back up gnupg, ssh, pass, fonts, img, zen profile
+- [ ] Push nix-config repo
+- [ ] Flash NixOS minimal ISO to USB
+- [ ] Boot USB, connect WiFi
+- [ ] Partition and mount
+- [ ] Generate hardware-configuration.nix
+- [ ] Get nix-config onto /mnt
+- [ ] `nixos-install --flake /mnt/home/barrett/nix-config#xps15`
+- [ ] Set root password, reboot
+- [ ] Set barrett password, log in
+- [ ] Fix nix-config ownership
+- [ ] Restore secrets (gnupg, ssh, pass)
+- [ ] Restore fonts, images, browser profile
+- [ ] `sudo nixos-rebuild switch --flake ~/nix-config#xps15`
+- [ ] Verify: terminal, editor, browser, WM all working
+- [ ] Add missing packages to configuration.nix / HM modules
+- [ ] Add `environment.binsh = dash`
+- [ ] Add doas config
+- [ ] Add fonts.packages
+- [ ] Add pcscd for Yubikey
+- [ ] Commit hardware-configuration.nix
diff --git a/config/X11/xinitrc b/config/X11/xinitrc
new file mode 100644
index 0000000..ce37f37
--- /dev/null
+++ b/config/X11/xinitrc
@@ -0,0 +1,16 @@
+#!/bin/sh
+if [ -d /etc/X11/xinit/xinitrc.d ]; then
+ for f in /etc/X11/xinit/xinitrc.d/*; do
+ [ -x "$f" ] && . "$f"
+ done
+ unset f
+fi
+
+x setup
+x mon
+x bg ~/img/wp/one/progress.jpg ~/img/wp/one/lilies.jpg
+setxkbmap -layout us -variant colemak &
+pgrep -u "$USER" -f clipmenud >/dev/null || clipmenud &
+# dwmblocks &
+# dwm
+spectrwm
diff --git a/config/X11/xmodmap b/config/X11/xmodmap
new file mode 100644
index 0000000..fc3b30d
--- /dev/null
+++ b/config/X11/xmodmap
@@ -0,0 +1,7 @@
+remove Control = Control_R
+add mod3 = Control_R
+remove mod1 = Alt_R
+add mod2 = Alt_R
+
+remove Shift = Shift_R
+remove Mod4 = Super_L
diff --git a/config/X11/xresources.gruvbox b/config/X11/xresources.gruvbox
new file mode 100644
index 0000000..2f20251
--- /dev/null
+++ b/config/X11/xresources.gruvbox
@@ -0,0 +1,32 @@
+Xcursor.theme: Adwaita
+Xcursor.size: 32
+
+Xft.dpi: 103
+
+*background: #1d2021
+*foreground: #d4be98
+
+! Black + DarkGrey
+*color0: #32302f
+*color8: #32302f
+! DarkRed + Red
+*color1: #ea6962
+*color9: #ea6962
+! DarkGreen + Green
+*color2: #a9b665
+*color10: #a9b665
+! DarkYellow + Yellow
+*color3: #d8a657
+*color11: #d8a657
+! DarkBlue + Blue
+*color4: #7daea3
+*color12: #7daea3
+! DarkMagenta + Magenta
+*color5: #d3869b
+*color13: #d3869b
+! DarkCyan + Cyan
+*color6: #89b482
+*color14: #89b482
+! LightGrey + White
+*color7: #d4be98
+*color15: #d4be98
diff --git a/config/X11/xresources.light b/config/X11/xresources.light
new file mode 100644
index 0000000..555a349
--- /dev/null
+++ b/config/X11/xresources.light
@@ -0,0 +1,26 @@
+Xcursor.theme: Adwaita
+Xcursor.size: 32
+
+Xft.dpi: 103
+
+*background: #ffffff
+*foreground: #000000
+
+*color0: #000000
+*color1: #b22222
+*color2: #228222
+*color3: #b8860b
+*color4: #27408b
+*color5: #8b008b
+*color6: #00bfff
+*color7: #ffffff
+
+! 8 Bright Colors
+*color8: #555555
+*color9: #ff0000
+*color10: #00ff00
+*color11: #ffa500
+*color12: #0000ff
+*color13: #ff00ff
+*color14: #00ffff
+*color15: #ffffff
diff --git a/config/lf/cleaner b/config/lf/cleaner
new file mode 100755
index 0000000..af197ee
--- /dev/null
+++ b/config/lf/cleaner
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec kitten icat --clear --stdin no --transfer-mode memory /dev/tty
diff --git a/config/lf/lf.lua b/config/lf/lf.lua
new file mode 100644
index 0000000..470607b
--- /dev/null
+++ b/config/lf/lf.lua
@@ -0,0 +1,250 @@
+_G.lf = _G.lf or {}
+
+function _G.lf.setup()
+ vim.opt.swapfile = false
+ vim.opt.backup = false
+ vim.opt.writebackup = false
+ vim.opt.termguicolors = true
+
+ vim.pack.add({
+ {
+ src = "https://github.com/nvim-treesitter/nvim-treesitter.git",
+ name = "nvim-treesitter",
+ },
+ {
+ src = "https://github.com/barrettruth/midnight.nvim.git",
+ name = "midnight.nvim",
+ },
+ })
+
+ vim.cmd('silent TSInstall all')
+
+ vim.api.nvim_create_autocmd("OptionSet", {
+ pattern = "background",
+ callback = function()
+ vim.cmd.colorscheme(vim.o.background == "dark" and "midnight" or "daylight")
+ end,
+ group = vim.api.nvim_create_augroup("Midnight", { clear = true }),
+ })
+end
+
+local hl_cache = {}
+local reset = "\27[0m"
+
+---@param name string
+---@return string
+local function get_fg_ansi(name)
+ if hl_cache[name] then
+ return hl_cache[name]
+ end
+
+ local hl = vim.api.nvim_get_hl(0, { name = name, link = false })
+
+ if not hl.fg then
+ hl_cache[name] = ""
+ return ""
+ end
+
+ local r = bit.rshift(bit.band(hl.fg, 0xff0000), 16)
+ local g = bit.rshift(bit.band(hl.fg, 0x00ff00), 8)
+ local b = bit.band(hl.fg, 0x0000ff)
+
+ local ansi = string.format("\27[38;2;%d;%d;%dm", r, g, b)
+ hl_cache[name] = ansi
+ return ansi
+end
+
+---@param lines string[]
+---@return boolean
+local function render_with_vim_syntax(lines)
+ local ok = pcall(vim.cmd.syntax, "enable")
+ if not ok then
+ return false
+ end
+
+ for lnum, text in ipairs(lines) do
+ if #text == 0 then
+ io.write("\n")
+ else
+ local rendered = {}
+ local current_group = nil
+ local chunk_start = 1
+
+ for col = 1, #text do
+ local syn_id = vim.fn.synID(lnum, col, 1)
+ local group = vim.fn.synIDattr(syn_id, "name")
+
+ if group ~= current_group then
+ if current_group then
+ local chunk = text:sub(chunk_start, col - 1)
+ local ansi = get_fg_ansi(current_group)
+ if ansi ~= "" then
+ table.insert(rendered, ansi)
+ table.insert(rendered, chunk)
+ table.insert(rendered, reset)
+ else
+ table.insert(rendered, chunk)
+ end
+ end
+ current_group = group
+ chunk_start = col
+ end
+ end
+
+ if current_group then
+ local chunk = text:sub(chunk_start)
+ local ansi = get_fg_ansi(current_group)
+ if ansi ~= "" then
+ table.insert(rendered, ansi)
+ table.insert(rendered, chunk)
+ table.insert(rendered, reset)
+ else
+ table.insert(rendered, chunk)
+ end
+ end
+
+ local line = table.concat(rendered)
+ io.write(line .. "\n")
+ end
+ end
+
+ io.flush()
+ return true
+end
+
+---@param lines string[]
+local function render_plain(lines)
+ for _, line in ipairs(lines) do
+ io.write(line .. "\n")
+ end
+ io.flush()
+end
+
+---@param buf number
+---@param lines string[]
+---@param filetype string
+---@param preview_line_count number
+---@return table>
+local function collect_treesitter_highlights(buf, lines, filetype, preview_line_count)
+ local parser = vim.treesitter.get_parser(buf)
+ local trees = parser:parse()
+ if not trees or #trees == 0 then
+ return {}
+ end
+
+ local root = trees[1]:root()
+ local query = vim.treesitter.query.get(filetype, "highlights")
+ if not query then
+ return {}
+ end
+
+ ---@type table>
+ local line_highlights = {}
+
+ for id, node in query:iter_captures(root, buf, 0, preview_line_count) do
+ local name = query.captures[id]
+ local start_row, start_col, end_row, end_col = node:range()
+
+ for row = start_row, end_row do
+ line_highlights[row] = line_highlights[row] or {}
+ local s_col = row == start_row and start_col or 0
+ local e_col = row == end_row and end_col or #lines[row + 1]
+ table.insert(line_highlights[row], { s_col, e_col, name })
+ end
+ end
+
+ for _, highlights in pairs(line_highlights) do
+ table.sort(highlights, function(a, b)
+ if a[1] == b[1] then
+ return a[2] > b[2]
+ end
+ return a[1] < b[1]
+ end)
+ end
+
+ return line_highlights
+end
+
+---@param lines string[]
+---@param line_highlights table>
+---@param filetype string
+local function render_with_treesitter(lines, line_highlights, filetype)
+ for i, text in ipairs(lines) do
+ local row = i - 1
+ local highlights = line_highlights[row] or {}
+
+ if #highlights == 0 then
+ io.write(text .. "\n")
+ else
+ local rendered = {}
+ local pos = 0
+ ---@type {[1]: number, [2]: number}[]
+ local used_ranges = {}
+
+ for _, hl in ipairs(highlights) do
+ local s_col, e_col, name = hl[1], hl[2], hl[3]
+
+ local skip = false
+ for _, used in ipairs(used_ranges) do
+ if s_col >= used[1] and s_col < used[2] then
+ skip = true
+ break
+ end
+ end
+
+ if not skip then
+ if pos < s_col then
+ table.insert(rendered, text:sub(pos + 1, s_col))
+ end
+
+ local chunk = text:sub(s_col + 1, e_col)
+ local ansi = get_fg_ansi("@" .. name .. "." .. filetype)
+ if ansi == "" then
+ ansi = get_fg_ansi("@" .. name)
+ end
+
+ if ansi ~= "" then
+ table.insert(rendered, ansi)
+ table.insert(rendered, chunk)
+ table.insert(rendered, reset)
+ else
+ table.insert(rendered, chunk)
+ end
+
+ table.insert(used_ranges, { s_col, e_col })
+ pos = e_col
+ end
+ end
+
+ if pos < #text then
+ table.insert(rendered, text:sub(pos + 1))
+ end
+
+ io.write(table.concat(rendered) .. "\n")
+ end
+ end
+
+ io.flush()
+end
+
+---@param filepath string
+---@param preview_line_count number
+function _G.lf.preview(filepath, preview_line_count)
+ vim.cmd.edit(filepath)
+ vim.cmd.colorscheme(vim.env.THEME)
+
+ local buf = vim.api.nvim_get_current_buf()
+ local lines = vim.api.nvim_buf_get_lines(buf, 0, preview_line_count, false)
+
+ local ft = vim.filetype.match({ buf = buf, filename = filepath })
+
+ local tsok, result = pcall(collect_treesitter_highlights, buf, lines, ft, preview_line_count)
+
+ if tsok and next(result) ~= nil then
+ render_with_treesitter(lines, result, ft)
+ elseif render_with_vim_syntax(lines) then
+ return
+ else
+ render_plain(lines)
+ end
+end
diff --git a/config/lf/previewer b/config/lf/previewer
new file mode 100755
index 0000000..bd47d7e
--- /dev/null
+++ b/config/lf/previewer
@@ -0,0 +1,72 @@
+#!/bin/sh
+FILE_PATH="$1"
+w="$2"
+h="$3"
+x="$4"
+y="$5"
+
+CACHE_DIR="$HOME/.cache/lf/thumbnail"
+mkdir -p "$CACHE_DIR"
+
+CACHE_BASE="$CACHE_DIR/$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$FILE_PATH")" | sha256sum | awk '{print $1}')"
+
+get_cell_size() {
+ size_info=$(kitten @ get-window-size --self)
+ cell_w=$(echo "$size_info" | awk 'BEGIN{RS="[{} ,]"} /"xpixels"/{gsub(/"/,"",$0); split($0,a,":"); x=a[2]} /"cols"/{gsub(/"/,"",$0); split($0,a,":"); c=a[2]} END{if(c) print int(x/c); else print 8}')
+ cell_h=$(echo "$size_info" | awk 'BEGIN{RS="[{} ,]"} /"ypixels"/{gsub(/"/,"",$0); split($0,a,":"); y=a[2]} /"rows"/{gsub(/"/,"",$0); split($0,a,":"); r=a[2]} END{if(r) print int(y/r); else print 16}')
+}
+
+draw() {
+ kitten icat --stdin no --transfer-mode memory --place "${w}x${h}@${x}x${y}" "$1" /dev/tty
+ exit 1
+}
+
+get_cell_size
+preview_px_w=$((w * ${cell_w:-8} - 16))
+preview_px_h=$((h * ${cell_h:-16} - 16))
+PREVIEW_LINE_COUNT=200
+
+mime_type=$(file -Lb --mime-type "$FILE_PATH")
+
+case "$mime_type" in
+image/gif | video/*)
+ if [ "$(stat -c %Y "$FILE_PATH" 2>/dev/null || echo 0)" -gt "$(stat -c %Y "${CACHE_BASE}.jpg" 2>/dev/null || echo 0)" ]; then
+ ffmpeg -y -i "$FILE_PATH" -vf "select=eq(n\,0),scale=${preview_px_w}:-1" -vframes 1 "${CACHE_BASE}.jpg"
+ fi
+ draw "${CACHE_BASE}.jpg"
+ ;;
+image/svg+xml | image/svg)
+ if [ "$(stat -c %Y "$FILE_PATH")" -gt "$(stat -c %Y "${CACHE_BASE}.png" 2>/dev/null || echo 0)" ]; then
+ rsvg-convert --keep-aspect-ratio --width "$preview_px_w" --format=png "$FILE_PATH" -o "${CACHE_BASE}.png"
+ fi
+ draw "${CACHE_BASE}.png"
+ ;;
+image/*)
+ draw "$FILE_PATH"
+ ;;
+application/pdf)
+ if [ "$(stat -c %Y "$FILE_PATH")" -gt "$(stat -c %Y "${CACHE_BASE}.jpg" 2>/dev/null || echo 0)" ]; then
+ pdftoppm -f 1 -l 1 -scale-to-x "$preview_px_w" -scale-to-y -1 -singlefile -jpeg -- "$FILE_PATH" "${CACHE_BASE}"
+ fi
+ draw "${CACHE_BASE}.jpg"
+ ;;
+application/epub+zip | application/x-mobipocket-ebook)
+ if [ "$(stat -c %Y "$FILE_PATH")" -gt "$(stat -c %Y "${CACHE_BASE}.png" 2>/dev/null || echo 0)" ]; then
+ gnome-epub-thumbnailer -s "$preview_px_w" "$FILE_PATH" "${CACHE_BASE}.png" 2>/dev/null || convert -size "${preview_px_w}x${preview_px_w}" xc:gray "${CACHE_BASE}.png"
+ fi
+ [ -f "${CACHE_BASE}.png" ] && draw "${CACHE_BASE}.png"
+ ;;
+audio/*)
+ if [ "$(stat -c %Y "$FILE_PATH")" -gt "$(stat -c %Y "${CACHE_BASE}.jpg" 2>/dev/null || echo 0)" ]; then
+ ffmpeg -y -i "$FILE_PATH" -an -vcodec copy "${CACHE_BASE}.jpg" 2>/dev/null || convert -size "${preview_px_w}x${preview_px_w}" xc:gray "${CACHE_BASE}.jpg"
+ fi
+ draw "${CACHE_BASE}.jpg"
+ ;;
+*)
+ nvim --headless -u NONE \
+ -c "luafile $XDG_CONFIG_HOME/lf/lf.lua" \
+ -c "lua lf.setup()" \
+ -c "lua lf.preview([[${FILE_PATH}]], ${PREVIEW_LINE_COUNT})" \
+ -c "quitall!"
+ ;;
+esac
diff --git a/config/lf/sort.py b/config/lf/sort.py
new file mode 100755
index 0000000..6457ff5
--- /dev/null
+++ b/config/lf/sort.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+import os
+import sys
+
+
+def categorize_and_sort(directory: str) -> list[str]:
+ try:
+ entries = os.listdir(directory)
+ except (PermissionError, FileNotFoundError):
+ return []
+
+ folders: list[str] = []
+ files: list[str] = []
+ dotfolders: list[str] = []
+ dotfiles: list[str] = []
+
+ for entry in entries:
+ full_path = os.path.join(directory, entry)
+ is_hidden = entry.startswith(".")
+ is_dir = os.path.isdir(full_path)
+
+ if not is_hidden and is_dir:
+ folders.append(entry)
+ elif not is_hidden and not is_dir:
+ files.append(entry)
+ elif is_hidden and is_dir:
+ dotfolders.append(entry)
+ else:
+ dotfiles.append(entry)
+
+ folders.sort(key=str.lower)
+ files.sort(key=str.lower)
+ dotfolders.sort(key=str.lower)
+ dotfiles.sort(key=str.lower)
+
+ return folders + files + dotfolders + dotfiles
+
+
+def main() -> None:
+ if len(sys.argv) < 2:
+ directory = os.getcwd()
+ else:
+ directory = sys.argv[1]
+
+ sorted_entries = categorize_and_sort(directory)
+
+ for entry in sorted_entries:
+ print(entry)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/config/nvim/.luacheckrc b/config/nvim/.luacheckrc
new file mode 100644
index 0000000..9e07bfe
--- /dev/null
+++ b/config/nvim/.luacheckrc
@@ -0,0 +1,18 @@
+-- Rerun tests only if their modification time changed.
+cache = true
+
+ignore = {
+ "122", -- Setting a read-only field of a global variable.
+ "212", -- Unused argument, In the case of callback function, _arg_name is easier to understand than _, so this option is set to off.
+ "631", -- max_line_length, vscode pkg URL is too long
+}
+
+-- Global objects defined by the C code
+read_globals = {
+ "vim",
+}
+
+include_files = { "lua", "tests" }
+exclude_files = { ".luacheckrc", "tests/**/*_spec.lua" }
+
+-- vim: ft=lua tw=80
diff --git a/config/nvim/after/ftplugin/c.lua b/config/nvim/after/ftplugin/c.lua
new file mode 100644
index 0000000..aed0337
--- /dev/null
+++ b/config/nvim/after/ftplugin/c.lua
@@ -0,0 +1 @@
+vim.o.shiftwidth = 2
diff --git a/config/nvim/after/ftplugin/cpp.lua b/config/nvim/after/ftplugin/cpp.lua
new file mode 100644
index 0000000..7f85d56
--- /dev/null
+++ b/config/nvim/after/ftplugin/cpp.lua
@@ -0,0 +1 @@
+vim.opt.indentkeys:remove(':')
diff --git a/config/nvim/after/ftplugin/fugitive.lua b/config/nvim/after/ftplugin/fugitive.lua
new file mode 100644
index 0000000..bf9e866
--- /dev/null
+++ b/config/nvim/after/ftplugin/fugitive.lua
@@ -0,0 +1,2 @@
+bmap({ 'n', 'Gh', 'diffget //2' })
+bmap({ 'n', 'Gl', 'diffget //3' })
diff --git a/config/nvim/after/ftplugin/help.lua b/config/nvim/after/ftplugin/help.lua
new file mode 100644
index 0000000..b091af0
--- /dev/null
+++ b/config/nvim/after/ftplugin/help.lua
@@ -0,0 +1,5 @@
+vim.o.number = true
+vim.o.conceallevel = 0
+vim.o.relativenumber = true
+
+bmap({ 'n', 'q', vim.cmd.helpclose })
diff --git a/config/nvim/after/ftplugin/html.lua b/config/nvim/after/ftplugin/html.lua
new file mode 100644
index 0000000..ec47489
--- /dev/null
+++ b/config/nvim/after/ftplugin/html.lua
@@ -0,0 +1,2 @@
+vim.o.shiftwidth = 2
+vim.o.textwidth = 80
diff --git a/config/nvim/after/ftplugin/htmldjango.lua b/config/nvim/after/ftplugin/htmldjango.lua
new file mode 100644
index 0000000..aed0337
--- /dev/null
+++ b/config/nvim/after/ftplugin/htmldjango.lua
@@ -0,0 +1 @@
+vim.o.shiftwidth = 2
diff --git a/config/nvim/after/ftplugin/javascript.lua b/config/nvim/after/ftplugin/javascript.lua
new file mode 100644
index 0000000..80045cc
--- /dev/null
+++ b/config/nvim/after/ftplugin/javascript.lua
@@ -0,0 +1,2 @@
+vim.o.shiftwidth = 2
+vim.o.makeprg = 'node %'
diff --git a/config/nvim/after/ftplugin/man.lua b/config/nvim/after/ftplugin/man.lua
new file mode 100644
index 0000000..b8f7677
--- /dev/null
+++ b/config/nvim/after/ftplugin/man.lua
@@ -0,0 +1,2 @@
+vim.o.number = true
+vim.o.relativenumber = true
diff --git a/config/nvim/after/ftplugin/markdown.lua b/config/nvim/after/ftplugin/markdown.lua
new file mode 100644
index 0000000..570d861
--- /dev/null
+++ b/config/nvim/after/ftplugin/markdown.lua
@@ -0,0 +1,2 @@
+vim.o.conceallevel = 1
+vim.o.textwidth = 80
diff --git a/config/nvim/after/ftplugin/python.lua b/config/nvim/after/ftplugin/python.lua
new file mode 100644
index 0000000..de9ef27
--- /dev/null
+++ b/config/nvim/after/ftplugin/python.lua
@@ -0,0 +1 @@
+vim.o.makeprg = 'python %'
diff --git a/config/nvim/after/ftplugin/rust.lua b/config/nvim/after/ftplugin/rust.lua
new file mode 100644
index 0000000..0d82f09
--- /dev/null
+++ b/config/nvim/after/ftplugin/rust.lua
@@ -0,0 +1 @@
+vim.o.makeprg = 'cargo run'
diff --git a/config/nvim/after/ftplugin/sh.lua b/config/nvim/after/ftplugin/sh.lua
new file mode 100644
index 0000000..598dfa3
--- /dev/null
+++ b/config/nvim/after/ftplugin/sh.lua
@@ -0,0 +1 @@
+vim.o.makeprg = 'sh %'
diff --git a/config/nvim/after/plugin/lsp.lua b/config/nvim/after/plugin/lsp.lua
new file mode 100644
index 0000000..f096c48
--- /dev/null
+++ b/config/nvim/after/plugin/lsp.lua
@@ -0,0 +1,103 @@
+local lsp = require('config.lsp')
+
+vim.diagnostic.config({
+ signs = false,
+ float = {
+ format = function(diagnostic)
+ return ('%s (%s)'):format(diagnostic.message, diagnostic.source)
+ end,
+ header = '',
+ prefix = ' ',
+ },
+ jump = { float = true },
+})
+
+local function prepare_capabilities()
+ local capabilities = vim.lsp.protocol.make_client_capabilities()
+ capabilities.textDocument.completion.completionItem.snippetSupport = false
+
+ local ok, blink = pcall(require, 'blink.cmp')
+
+ return ok and blink.get_lsp_capabilities(capabilities) or capabilities
+end
+
+vim.lsp.config('*', {
+ on_attach = lsp.on_attach,
+ capabilities = prepare_capabilities(),
+ flags = { debounce_text_changes = 0 },
+})
+
+vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(opts)
+ local client = vim.lsp.get_client_by_id(opts.data.client_id)
+
+ if
+ client
+ and (
+ client:supports_method('textDocument/formatting')
+ or client:supports_method('formatting')
+ )
+ then
+ local modes = { 'n' }
+
+ if
+ client:supports_method('textDocument/rangeFormatting')
+ or client:supports_method('rangeFormatting')
+ then
+ table.insert(modes, 'x')
+ end
+
+ bmap({
+ modes,
+ 'gF',
+ lsp.format,
+ }, { buffer = opts.buf, silent = false })
+ end
+ end,
+ group = vim.api.nvim_create_augroup('ALspFormat', { clear = true }),
+})
+
+for _, server in ipairs({
+ 'bashls',
+ 'basedpyright',
+ 'clangd',
+ 'cssls',
+ 'emmet_language_server',
+ 'eslint',
+ 'html',
+ 'mdx_analyzer',
+ 'jsonls',
+ 'vtsls',
+ 'pytest_lsp',
+ 'lua_ls',
+ 'ruff',
+ 'tinymist',
+}) do
+ local ok, config = pcall(require, 'lsp.' .. server)
+ if ok and config then
+ vim.lsp.config(server, config)
+ else
+ vim.lsp.config(server, {})
+ end
+ vim.lsp.enable(server)
+end
+
+-- remove duplicate entries from goto defintion list
+-- example: https://github.com/LuaLS/lua-language-server/issues/2451
+local locations_to_items = vim.lsp.util.locations_to_items
+vim.lsp.util.locations_to_items = function(locations, offset_encoding)
+ local lines = {}
+ local loc_i = 1
+ for _, loc in ipairs(vim.deepcopy(locations)) do
+ local uri = loc.uri or loc.targetUri
+ local range = loc.range or loc.targetSelectionRange
+ if lines[uri .. range.start.line] then
+ table.remove(locations, loc_i)
+ else
+ loc_i = loc_i + 1
+ end
+ lines[uri .. range.start.line] = true
+ end
+
+ return locations_to_items(locations, offset_encoding)
+end
diff --git a/config/nvim/after/queries/html/highlights.scm.disabled b/config/nvim/after/queries/html/highlights.scm.disabled
new file mode 100644
index 0000000..79886a7
--- /dev/null
+++ b/config/nvim/after/queries/html/highlights.scm.disabled
@@ -0,0 +1,5 @@
+;; extends
+(attribute (attribute_name) @att_name (#eq? @att_name "class")
+ (quoted_attribute_value (attribute_value) @att_val (#set! @att_val conceal ".")
+ )
+)
diff --git a/config/nvim/after/queries/javascript/highlights.scm b/config/nvim/after/queries/javascript/highlights.scm
new file mode 100644
index 0000000..e3c448c
--- /dev/null
+++ b/config/nvim/after/queries/javascript/highlights.scm
@@ -0,0 +1,5 @@
+;; extends
+(jsx_attribute (property_identifier) @att_name (#eq? @att_name "className")
+ (string (string_fragment) @att_val (#set! @att_val conceal ".")
+ )
+)
diff --git a/config/nvim/after/queries/jsx/highlights.scm b/config/nvim/after/queries/jsx/highlights.scm
new file mode 100644
index 0000000..3c16815
--- /dev/null
+++ b/config/nvim/after/queries/jsx/highlights.scm
@@ -0,0 +1,2 @@
+;; inherits: jsx
+;; extends
diff --git a/config/nvim/after/queries/tsx/highlights.scm b/config/nvim/after/queries/tsx/highlights.scm
new file mode 100644
index 0000000..3c16815
--- /dev/null
+++ b/config/nvim/after/queries/tsx/highlights.scm
@@ -0,0 +1,2 @@
+;; inherits: jsx
+;; extends
diff --git a/config/nvim/filetype.lua b/config/nvim/filetype.lua
new file mode 100644
index 0000000..167c58b
--- /dev/null
+++ b/config/nvim/filetype.lua
@@ -0,0 +1,10 @@
+vim.filetype.add({
+ extension = {
+ log = 'log',
+ mdx = 'mdx',
+ },
+ filename = {
+ ['requirements.txt'] = 'config',
+ dunstrc = 'dosini'
+ },
+})
diff --git a/config/nvim/init.lua b/config/nvim/init.lua
new file mode 100644
index 0000000..c95997b
--- /dev/null
+++ b/config/nvim/init.lua
@@ -0,0 +1,65 @@
+vim.g.mapleader = ' '
+
+function _G.map(mapping, opts)
+ vim.keymap.set(
+ mapping[1],
+ mapping[2],
+ mapping[3],
+ vim.tbl_extend('keep', opts or {}, { silent = true })
+ )
+end
+
+function _G.bmap(mapping, opts)
+ _G.map(mapping, vim.tbl_extend('force', opts or {}, { buffer = 0 }))
+end
+local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
+if not vim.uv.fs_stat(lazypath) then
+ vim.fn.system({
+ 'git',
+ 'clone',
+ 'https://github.com/folke/lazy.nvim.git',
+ lazypath,
+ })
+end
+
+vim.opt.rtp:prepend(lazypath)
+require('lazy').setup('plugins', {
+ git = { url_format = 'git@github.com:%s.git' },
+ change_detection = { enabled = false },
+ performance = {
+ cache = {
+ enabled = true,
+ },
+ reset_packpath = true,
+ rtp = {
+ reset = true,
+ disabled_plugins = {
+ 'gzip',
+ 'netrwPlugin',
+ 'tarPlugin',
+ 'tohtml',
+ 'tutor',
+ 'zipPlugin',
+ '2html_plugin',
+ 'getscript',
+ 'getscriptPlugin',
+ 'logipat',
+ 'netrw',
+ 'netrwSettings',
+ 'netrwFileHandlers',
+ 'tar',
+ 'tarPlugin',
+ 'rrhelper',
+ 'vimball',
+ 'vimballPlugin',
+ 'zip',
+ 'zipPlugin',
+ 'tutor',
+ 'rplugin',
+ 'synmenu',
+ 'optwin',
+ 'bugreport',
+ },
+ },
+ },
+})
diff --git a/config/nvim/lazy-lock.json b/config/nvim/lazy-lock.json
new file mode 100644
index 0000000..3661418
--- /dev/null
+++ b/config/nvim/lazy-lock.json
@@ -0,0 +1,50 @@
+{
+ "LuaSnip": { "branch": "master", "commit": "dae4f5aaa3574bd0c2b9dd20fb9542a02c10471c" },
+ "SchemaStore.nvim": { "branch": "main", "commit": "cd9e8c22f6ad2012ac395725080cc5737297d840" },
+ "blink.cmp": { "branch": "main", "commit": "b137f63d89d0285ca76eed12e1923220e0aff8c1" },
+ "blink.indent": { "branch": "main", "commit": "9c80820ca77218a8d28e70075d6f44a1609911fe" },
+ "cloak.nvim": { "branch": "main", "commit": "648aca6d33ec011dc3166e7af3b38820d01a71e4" },
+ "dial.nvim": { "branch": "master", "commit": "f2634758455cfa52a8acea6f142dcd6271a1bf57" },
+ "format-ts-errors.nvim": { "branch": "main", "commit": "4b7418d6689bc0fd3c1db0500c67133422522384" },
+ "fzf-lua": { "branch": "main", "commit": "1cd4d8f36789061aa7b7d0718bc13849f92d6d77" },
+ "gitsigns.nvim": { "branch": "main", "commit": "abf82a65f185bd54adc0679f74b7d6e1ada690c9" },
+ "grapple.nvim": { "branch": "main", "commit": "b41ddfc1c39f87f3d1799b99c2f0f1daa524c5f7" },
+ "highlight-undo.nvim": { "branch": "main", "commit": "ee32e12693d70e66f954d09a504a7371d110fc27" },
+ "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
+ "lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" },
+ "live-rename.nvim": { "branch": "main", "commit": "3a3cddf23b89a17992f9ca67afc5858077769462" },
+ "live-server.nvim": { "branch": "main", "commit": "58f2e30e029a57dbda15c5749edd8d642578859f" },
+ "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
+ "mini.bufremove": { "branch": "main", "commit": "10857aa39160c127694151828914df3131ba83b6" },
+ "mini.misc": { "branch": "main", "commit": "b647b64321c34d4868d158282bb89e49f0d6838b" },
+ "mini.pairs": { "branch": "main", "commit": "4089aa6ea6423e02e1a8326a7a7a00159f6f5e04" },
+ "none-ls-extras.nvim": { "branch": "main", "commit": "6ced4fc4072c7b269ba95bb596196cc76e00b280" },
+ "none-ls.nvim": { "branch": "main", "commit": "3c206dfedf5f1385e9d29f85ffaec7874358592a" },
+ "nvim-colorizer.lua": { "branch": "master", "commit": "81e676d3203c9eb6e4c0ccf1eba1679296ef923f" },
+ "nvim-lspconfig": { "branch": "master", "commit": "419b082102fa813739588dd82e19a8b6b2442855" },
+ "nvim-navic": { "branch": "master", "commit": "f5eba192f39b453675d115351808bd51276d9de5" },
+ "nvim-surround": { "branch": "main", "commit": "1098d7b3c34adcfa7feb3289ee434529abd4afd1" },
+ "nvim-treesitter": { "branch": "main", "commit": "568ede7e79172a0fe7c9d631454a97ad968deaf2" },
+ "nvim-treesitter-textobjects": { "branch": "main", "commit": "52bda74e087034408e2d563cb4499c1601038f9d" },
+ "nvim-vtsls": { "branch": "main", "commit": "0b5f73c9e50ce95842ea07bb3f05c7d66d87d14a" },
+ "oil.nvim": { "branch": "master", "commit": "f55b25e493a7df76371cfadd0ded5004cb9cd48a" },
+ "overseer.nvim": { "branch": "master", "commit": "5828bdbd86677497613033c142f0a8624489216f" },
+ "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
+ "rustaceanvim": { "branch": "master", "commit": "796f06742e373012b860dc20f9ecccbfc670dc28" },
+ "snacks.nvim": { "branch": "main", "commit": "fe7cfe9800a182274d0f868a74b7263b8c0c020b" },
+ "telescope.nvim": { "branch": "master", "commit": "ad7d9580338354ccc136e5b8f0aa4f880434dcdc" },
+ "treesj": { "branch": "main", "commit": "186084dee5e9c8eec40f6e39481c723dd567cb05" },
+ "typescript-tools.nvim": { "branch": "master", "commit": "c2f5910074103705661e9651aa841e0d7eea9932" },
+ "typst-preview.nvim": { "branch": "master", "commit": "e123a7ab64e52d836e00dea9251e85b201f38966" },
+ "vim-abolish": { "branch": "master", "commit": "dcbfe065297d31823561ba787f51056c147aa682" },
+ "vim-fugitive": { "branch": "master", "commit": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4" },
+ "vim-jsx-pretty": { "branch": "master", "commit": "6989f1663cc03d7da72b5ef1c03f87e6ddb70b41" },
+ "vim-sleuth": { "branch": "master", "commit": "be69bff86754b1aa5adcbb527d7fcd1635a84080" },
+ "vim-textobj-entire": { "branch": "master", "commit": "64a856c9dff3425ed8a863b9ec0a21dbaee6fb3a" },
+ "vim-textobj-indent": { "branch": "master", "commit": "deb76867c302f933c8f21753806cbf2d8461b548" },
+ "vim-textobj-line": { "branch": "master", "commit": "1a6780d29adcf7e464e8ddbcd0be0a9df1a37339" },
+ "vim-textobj-sentence": { "branch": "master", "commit": "c5dd562aff2c389dfc8cd55e6499854d352a80b8" },
+ "vim-textobj-user": { "branch": "master", "commit": "41a675ddbeefd6a93664a4dc52f302fe3086a933" },
+ "vim-textobj-xmlattr": { "branch": "master", "commit": "694a297f1d75fd527e87da9769f3c6519a87ebb1" },
+ "vimtex": { "branch": "master", "commit": "f707368022cdb851716be0d2970b90599c84a6a6" }
+}
diff --git a/config/nvim/lua/config/fold.lua b/config/nvim/lua/config/fold.lua
new file mode 100644
index 0000000..4fe65d6
--- /dev/null
+++ b/config/nvim/lua/config/fold.lua
@@ -0,0 +1,156 @@
+--- @class Fold
+local M = {}
+
+---@param bufnr number the buffer number
+---@return boolean whether the below foldexpr() is applicable to the buffer
+local function is_foldexpr(bufnr)
+ local ok, parser = pcall(vim.treesitter.get_parser, bufnr)
+ return ok and parser
+end
+
+--- @return string Fold level (as string for foldexpr)
+function M.foldexpr()
+ local line = vim.v.lnum
+ local bufnr = vim.api.nvim_get_current_buf()
+ local foldnestmax = vim.wo.foldnestmax
+ local ok, parser = pcall(vim.treesitter.get_parser, bufnr)
+ if not ok or not parser then
+ return '0'
+ end
+ local trees = parser:parse()
+ if not trees or #trees == 0 then
+ return '0'
+ end
+ local root = trees[1]:root()
+ local line_text = vim.fn.getline(line)
+ local positions = {}
+ local first_col = line_text:match('^%s*()')
+ if first_col and first_col <= #line_text then
+ table.insert(positions, first_col - 1)
+ end
+ local last_col = line_text:find('%S%s*$')
+ if last_col then
+ table.insert(positions, last_col - 1)
+ end
+ if #positions == 0 then
+ table.insert(positions, 0)
+ end
+ local function is_foldable(node_type)
+ return
+ -- functions/methods
+ node_type == 'function_definition'
+ or node_type == 'function_declaration'
+ or node_type == 'method_definition'
+ or node_type == 'method_declaration'
+ or node_type == 'function_item'
+ -- structs/unions
+ or node_type == 'class_definition'
+ or node_type == 'class_declaration'
+ or node_type == 'class_specifier'
+ or node_type == 'struct_item'
+ or node_type == 'struct_specifier'
+ or node_type == 'struct_type'
+ -- interfaces
+ or node_type == 'union_specifier'
+ or node_type == 'interface_declaration'
+ or node_type == 'interface_definition'
+ or node_type == 'interface_type'
+ -- type decls/defs
+ or node_type == 'type_declaration'
+ or node_type == 'type_definition'
+ -- traits
+ or node_type == 'trait_item'
+ -- enums
+ or node_type == 'enum_declaration'
+ or node_type == 'enum_specifier'
+ -- namespace/modules
+ or node_type == 'enum_item'
+ or node_type == 'impl_item'
+ or node_type == 'namespace_definition'
+ or node_type == 'namespace_declaration'
+ or node_type == 'internal_module'
+ or node_type == 'mod_item'
+ end
+ local function should_fold(n)
+ if not n then
+ return false
+ end
+ local srow, _, erow, _ = n:range()
+ return (erow - srow + 1) >= vim.wo.foldminlines
+ end
+ local function nested_fold_level(node)
+ if not node then
+ return 0
+ end
+ local level = 0
+ local temp = node
+ while temp do
+ if is_foldable(temp:type()) and should_fold(temp) then
+ level = level + 1
+ end
+ temp = temp:parent()
+ end
+ return level
+ end
+ local function starts_on_line(n)
+ local srow, _, _, _ = n:range()
+ return srow + 1 == line
+ end
+ local max_level = 0
+ local is_start = false
+ for _, col in ipairs(positions) do
+ local node =
+ root:named_descendant_for_range(line - 1, col, line - 1, col)
+ if node then
+ local raw_level = nested_fold_level(node)
+ max_level = math.max(max_level, math.min(raw_level, foldnestmax))
+ local temp = node
+ while temp do
+ local this_level = nested_fold_level(temp)
+ if
+ is_foldable(temp:type())
+ and should_fold(temp)
+ and starts_on_line(temp)
+ and this_level <= foldnestmax
+ then
+ is_start = true
+ end
+ temp = temp:parent()
+ end
+ end
+ end
+ if max_level == 0 then
+ return '0'
+ end
+ if is_start then
+ return '>' .. max_level
+ end
+ return tostring(max_level)
+end
+
+
+function M.setup()
+ vim.opt.fillchars:append({
+ fold = ' ',
+ foldopen = 'v',
+ foldclose = '>',
+ foldsep = ' ',
+ })
+ vim.o.foldlevel = 1
+ vim.o.foldtext = ''
+ vim.o.foldnestmax = 2
+ vim.o.foldminlines = 5
+ vim.api.nvim_create_autocmd('FileType', {
+ pattern = '*',
+ callback = function(opts)
+ -- do not override fold settings if not applicable
+ if is_foldexpr(opts.bufnr) then
+ vim.wo.foldmethod = 'expr'
+ vim.wo.foldexpr = 'v:lua.require("config.fold").foldexpr()'
+ end
+ end,
+ group = vim.api.nvim_create_augroup('AFold', { clear = true }),
+ })
+end
+
+return M
diff --git a/config/nvim/lua/config/fzf_reload.lua b/config/nvim/lua/config/fzf_reload.lua
new file mode 100644
index 0000000..edf73ca
--- /dev/null
+++ b/config/nvim/lua/config/fzf_reload.lua
@@ -0,0 +1,32 @@
+local M = {}
+M.opts = nil
+
+function M.setup(opts)
+ M.opts = vim.deepcopy(opts)
+ -- allow connections from `theme` script
+ local socket_path = ('/tmp/nvim-%d.sock'):format(vim.fn.getpid())
+ vim.fn.serverstart(socket_path)
+end
+
+---@disable_fzf_lua_reload boolean?
+function M.reload(disable_fzf_lua_reload)
+ local lines = vim.fn.readfile(vim.fn.expand('~/.config/fzf/themes/theme'))
+ if not lines or #lines == 0 then
+ return
+ end
+ local colors = {}
+ for color_spec in table.concat(lines, '\n'):gmatch('--color=([^%s]+)') do
+ for k, v in color_spec:gmatch('([^:,]+):([^,]+)') do
+ colors[k] = v
+ end
+ end
+ if not M.opts then
+ return
+ end
+ M.opts.fzf_colors = colors
+ if not disable_fzf_lua_reload then
+ require('fzf-lua').setup(M.opts)
+ end
+end
+
+return M
diff --git a/config/nvim/lua/config/lines/init.lua b/config/nvim/lua/config/lines/init.lua
new file mode 100644
index 0000000..0277068
--- /dev/null
+++ b/config/nvim/lua/config/lines/init.lua
@@ -0,0 +1,7 @@
+return {
+ setup = function()
+ vim.o.statusline = '%!v:lua.require("config.lines.statusline").statusline()'
+ vim.o.statuscolumn =
+ '%!v:lua.require("config.lines.statuscolumn").statuscolumn()'
+ end,
+}
diff --git a/config/nvim/lua/config/lines/statuscolumn.lua b/config/nvim/lua/config/lines/statuscolumn.lua
new file mode 100644
index 0000000..7cb7e39
--- /dev/null
+++ b/config/nvim/lua/config/lines/statuscolumn.lua
@@ -0,0 +1,25 @@
+return {
+ num = function()
+ if math.abs(vim.v.virtnum) > 0 then
+ return ''
+ elseif vim.v.relnum == 0 then
+ return '%#CursorLineNr#' .. vim.v.lnum
+ end
+
+ return '%#LineNr#' .. vim.v.relnum
+ end,
+ fold = function()
+ local expr = require('config.fold').foldexpr()
+ if expr:sub(1, 1) == '>' then
+ if vim.fn.foldclosed(vim.v.lnum) ~= -1 then
+ return '>'
+ else
+ return 'v'
+ end
+ end
+ return ' '
+ end,
+ statuscolumn = function()
+ return '%{%v:lua.require("config.lines.statuscolumn").fold()%}%s%=%{%v:lua.require("config.lines.statuscolumn").num()%} '
+ end,
+}
diff --git a/config/nvim/lua/config/lines/statusline.lua b/config/nvim/lua/config/lines/statusline.lua
new file mode 100644
index 0000000..d87cf07
--- /dev/null
+++ b/config/nvim/lua/config/lines/statusline.lua
@@ -0,0 +1,139 @@
+local empty = require('config.utils').empty
+
+local M = {}
+
+local mode = {
+ prefix = 'mode',
+ -- highlight = 'white',
+ value = function()
+ local mode_to_text = {
+ n = 'NORMAL',
+ i = 'INSERT',
+ v = 'VISUAL',
+ V = 'V-LINE',
+ ['\22'] = 'V-BLOCK',
+ R = 'REPLACE',
+ c = 'COMMAND',
+ }
+
+ return mode_to_text[vim.fn.mode()]
+ end,
+}
+
+local file = {
+ prefix = 'fp',
+ -- highlight = 'blue',
+ value = function()
+ return vim.fn.expand('%:~')
+ -- return vim.fn.pathshorten(vim.fn.expand('%:~'))
+ -- return vim.fn.fnamemodify(
+ -- vim.fn.bufname(vim.api.nvim_get_current_buf()),
+ -- ':~:.'
+ -- )
+ end,
+}
+
+local git = {
+ prefix = 'git',
+ -- highlight = 'magenta',
+ value = function()
+ return vim.b.gitsigns_head
+ end,
+}
+
+local modified = {
+ value = function()
+ return vim.api.nvim_get_option_value('modified', { buf = 0 }) and '+w'
+ or ''
+ end,
+}
+
+local navic = {
+ prefix = 'loc',
+ value = function()
+ return require('nvim-navic').get_location()
+ end,
+ condition = function()
+ local ok, _ = pcall(require, 'nvim-navic')
+ return ok
+ end,
+}
+
+local search = {
+ prefix = '/',
+ value = function()
+ local count = vim.fn.searchcount({ maxcount = 999 })
+
+ return string.format(
+ '%s (%s/%d)',
+ vim.fn.getreg('/'),
+ count.current,
+ count.total
+ )
+ end,
+ condition = function()
+ local status, searchcount = pcall(vim.fn.searchcount)
+
+ if not status or not searchcount or vim.tbl_isempty(searchcount) then
+ return false
+ end
+
+ return searchcount.total > 0
+ end,
+}
+
+local filetype = {
+ prefix = function()
+ return empty(
+ vim.api.nvim_get_option_value(
+ 'filetype',
+ { buf = vim.api.nvim_get_current_buf() }
+ )
+ ) and 'bt' or 'ft'
+ end,
+ -- highlight = 'green',
+ value = function()
+ local ft = vim.api.nvim_get_option_value(
+ 'filetype',
+ { buf = vim.api.nvim_get_current_buf() }
+ )
+
+ if empty(ft) then
+ ft = vim.bo.buftype
+ end
+
+ return ft
+ end,
+}
+
+local lineinfo = {
+ prefix = 'lnnr',
+ -- highlight = 'yellow',
+ value = '%c:%l/%L',
+}
+
+M.components = {
+ left = {
+ [1] = mode,
+ [2] = git,
+ [3] = file,
+ [4] = navic,
+ [5] = modified,
+ },
+ right = {
+ [1] = search,
+ [2] = lineinfo,
+ [3] = filetype,
+ },
+}
+
+local format_components = require('config.lines.utils').format_components
+
+M.statusline = function()
+ return ('%s%%=%s'):format(
+ format_components(M.components.left),
+ format_components(M.components.right)
+ )
+end
+
+return M
diff --git a/config/nvim/lua/config/lines/utils.lua b/config/nvim/lua/config/lines/utils.lua
new file mode 100644
index 0000000..3101dfc
--- /dev/null
+++ b/config/nvim/lua/config/lines/utils.lua
@@ -0,0 +1,41 @@
+local utils = require('config.utils')
+
+local M = {}
+
+local function vorfn(val_or_fn)
+ if type(val_or_fn) == 'function' then
+ return val_or_fn()
+ end
+
+ return val_or_fn
+end
+
+function M.format_components(components)
+ local side = {}
+
+ for i = 1, #components do
+ local component = components[i]
+
+ local highlight = vim.env.THEME == 'midnight' and 'Normal'
+ or component.highlight
+
+ if
+ vorfn(component.condition) ~= false
+ and not utils.empty(vorfn(component.value))
+ then
+ side[#side + 1] = ('%%#%s#%s%%#%s#'):format(
+ highlight,
+ vorfn(component.value),
+ component.highlight or 'Normal'
+ )
+ end
+ end
+
+ if #side > 0 then
+ return (' %s '):format(table.concat(side, ' │ '))
+ end
+
+ return ''
+end
+
+return M
diff --git a/config/nvim/lua/config/lsp.lua b/config/nvim/lua/config/lsp.lua
new file mode 100644
index 0000000..4a60340
--- /dev/null
+++ b/config/nvim/lua/config/lsp.lua
@@ -0,0 +1,97 @@
+local M = {}
+
+local Methods = vim.lsp.protocol.Methods
+
+function M.on_attach(client, bufnr)
+ if client:supports_method(Methods.textDocument_hover) then
+ bmap({ 'n', 'K', vim.lsp.buf.hover })
+ end
+
+ if client:supports_method(Methods.textDocument_documentSymbol) then
+ local ok, navic = pcall(require, 'nvim-navic')
+ if ok then
+ navic.attach(client, bufnr)
+ end
+ end
+
+ local ok, _ = pcall(require, 'fzf-lua')
+
+ local mappings = {
+ {
+ Methods.textDocument_codeAction,
+ 'gra',
+ ok and 'FzfLua lsp_code_actions'
+ or vim.lsp.buf.code_action,
+ },
+ {
+ Methods.textDocument_declaration,
+ 'gD',
+ ok and 'FzfLua lsp_declarations'
+ or vim.lsp.buf.declaration,
+ },
+ {
+ Methods.textDocument_definition,
+ 'gd',
+ ok and 'FzfLua lsp_definitions' or vim.lsp.buf.definition,
+ },
+ {
+ Methods.textDocument_implementation,
+ 'gri',
+ ok and 'FzfLua lsp_implementations'
+ or vim.lsp.buf.implementation,
+ },
+ {
+ Methods.textDocument_references,
+ 'grr',
+ ok and 'FzfLua lsp_references' or vim.lsp.buf.references,
+ },
+ {
+ Methods.textDocument_typeDefinition,
+ 'grt',
+ ok and 'FzfLua lsp_typedefs'
+ or vim.lsp.buf.type_definition,
+ },
+ {
+ Methods.textDocument_documentSymbol,
+ 'gs',
+ ok and 'FzfLua lsp_document_symbols'
+ or vim.lsp.buf.document_symbol,
+ },
+ {
+ Methods.workspace_diagnostic,
+ 'gw',
+ ok and 'FzfLua lsp_workspace_diagnostics'
+ or vim.diagnostic.setqflist,
+ },
+ {
+ Methods.workspace_symbol,
+ 'gS',
+ ok and 'FzfLua lsp_workspace_symbols'
+ or vim.lsp.buf.workspace_symbol,
+ },
+ }
+
+ for _, m in ipairs(mappings) do
+ local method, key, cmd = unpack(m)
+ if client:supports_method(method) then
+ bmap({ 'n', key, cmd })
+ end
+ end
+end
+
+local FORMAT_LSPS = { 'null-ls', 'clangd', 'tinymist', 'ruff' }
+
+function M.format(opts)
+ local format_opts = vim.tbl_extend('force', opts or {}, {
+ filter = function(c)
+ if c.name == 'typescript-tools' then
+ vim.cmd.TSToolsOrganizeImports()
+ end
+ return vim.tbl_contains(FORMAT_LSPS, c.name)
+ end,
+ })
+ vim.lsp.buf.format(format_opts)
+ vim.cmd.w()
+end
+
+return M
diff --git a/config/nvim/lua/config/projects.lua b/config/nvim/lua/config/projects.lua
new file mode 100644
index 0000000..aa588ad
--- /dev/null
+++ b/config/nvim/lua/config/projects.lua
@@ -0,0 +1,15 @@
+local project_configs = {
+ cavauto = {
+ lsp = {
+ clangd = {
+ cmd = { 'socat', '-', 'TCP:localhost:12345' },
+ },
+ },
+ },
+}
+
+local function project_name()
+ return vim.fn.fnamemodify(vim.fn.getcwd(), ':t')
+end
+
+return project_configs[project_name()] or {}
diff --git a/config/nvim/lua/config/tmux.lua b/config/nvim/lua/config/tmux.lua
new file mode 100644
index 0000000..53eca6f
--- /dev/null
+++ b/config/nvim/lua/config/tmux.lua
@@ -0,0 +1,101 @@
+local M = {}
+
+local projects = {
+ {
+ name = 'bmath',
+ paths = { vim.env.HOME .. '/dev/bmath' },
+ cmd = 'cmake -B build -DCMAKE_BUILD_TYPE=Debug && cmake --build build && ctest --test-dir build --output-on-failure',
+ },
+ {
+ name = 'neovim',
+ paths = { vim.env.HOME .. '/dev/neovim' },
+ cmd = 'make',
+ },
+ {
+ name = 'barrettruth.com',
+ paths = { vim.env.HOME .. '/dev/barrettruth.com' },
+ cmd = 'pnpm dev',
+ },
+ {
+ name = 'philipmruth.com',
+ paths = { vim.env.HOME .. '/dev/philipmruth.com' },
+ cmd = 'pnpm dev',
+ },
+}
+
+---@type overseer.Task|nil
+local current_task = nil
+
+local actions = {
+ nvim = function()
+ local ok, oil = pcall(require, 'oil')
+
+ if not ok then
+ return
+ end
+
+ oil.open()
+ end,
+ git = function()
+ vim.cmd.Git()
+ vim.cmd.only()
+ end,
+ run = function()
+ local ok, overseer = pcall(require, 'overseer')
+
+ if not ok then
+ return
+ end
+
+ local cwd = vim.fn.getcwd()
+ local match = nil
+ for _, p in ipairs(projects) do
+ if vim.tbl_contains(p.paths, cwd) then
+ match = p
+ break
+ end
+ end
+ if not match then
+ vim.notify_once(
+ 'No task defined for this project',
+ vim.log.levels.WARN
+ )
+ vim.cmd('q!')
+ return
+ end
+ if
+ current_task
+ and (current_task.cwd ~= cwd or current_task.name ~= match.name)
+ then
+ if current_task:is_running() then
+ current_task:stop()
+ end
+ current_task:dispose(true)
+ current_task = nil
+ end
+ if not current_task or not current_task:is_running() then
+ current_task = overseer.new_task({
+ name = match.name,
+ cmd = match.cmd,
+ cwd = cwd,
+ env = match.env,
+ })
+ current_task:start()
+ end
+ current_task:open_output()
+ end,
+}
+
+function M.run(subcmd)
+ if not subcmd then
+ error('No subcommand provided')
+ end
+
+ if not vim.tbl_contains(vim.tbl_keys(actions), subcmd) then
+ error('Invalid subcommand: ' .. subcmd)
+ end
+
+ actions[subcmd]()
+end
+
+return M
diff --git a/config/nvim/lua/config/utils.lua b/config/nvim/lua/config/utils.lua
new file mode 100644
index 0000000..55cb5ef
--- /dev/null
+++ b/config/nvim/lua/config/utils.lua
@@ -0,0 +1,7 @@
+local M = {}
+
+function M.empty(s)
+ return s == '' or s == nil
+end
+
+return M
diff --git a/config/nvim/lua/lsp/bashls.lua b/config/nvim/lua/lsp/bashls.lua
new file mode 100644
index 0000000..7322fc5
--- /dev/null
+++ b/config/nvim/lua/lsp/bashls.lua
@@ -0,0 +1,9 @@
+return {
+ on_attach = function(_, bufnr)
+ require('config.lsp').on_attach(_, bufnr)
+ local bufname = vim.api.nvim_buf_get_name(bufnr)
+ if string.match(bufname, '%.env') then
+ vim.diagnostic.enable(false, { bufnr = bufnr })
+ end
+ end,
+}
diff --git a/config/nvim/lua/lsp/clangd.lua b/config/nvim/lua/lsp/clangd.lua
new file mode 100644
index 0000000..dc66564
--- /dev/null
+++ b/config/nvim/lua/lsp/clangd.lua
@@ -0,0 +1,39 @@
+local config = require('config.projects')
+
+local clangd_settings = {
+ filetypes = { 'c', 'cpp', 'objc', 'objcpp', 'cuda' },
+ cmd = {
+ 'clangd',
+ '--clang-tidy',
+ '-j=4',
+ '--background-index',
+ '--completion-style=bundled',
+ '--header-insertion=iwyu',
+ '--header-insertion-decorators=false',
+ },
+ capabilities = {
+ textDocument = {
+ completion = {
+ editsNearCursor = true,
+ },
+ },
+ },
+}
+
+local project_settings = (config.lsp and config.lsp.clangd)
+ and config.lsp.clangd
+
+vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(args)
+ local client = vim.lsp.get_client_by_id(args.data.client_id)
+ if client and client.name == 'clangd' then
+ bmap(
+ { 'n', 'gh', vim.cmd.ClangdSwitchSourceHeader },
+ { buffer = args.buf }
+ )
+ end
+ end,
+ group = vim.api.nvim_creat_augroup('AClangdKeymap', { clear = true }),
+})
+
+return vim.tbl_extend('force', clangd_settings, project_settings or {})
diff --git a/config/nvim/lua/lsp/cssls.lua b/config/nvim/lua/lsp/cssls.lua
new file mode 100644
index 0000000..bad5dce
--- /dev/null
+++ b/config/nvim/lua/lsp/cssls.lua
@@ -0,0 +1,18 @@
+return {
+ cmd = { 'vscode-css-language-server', '--stdio' },
+ filetypes = { 'css', 'scss', 'less' },
+ settings = {
+ css = {
+ lint = {
+ unknownAtRules = 'ignore',
+ },
+ },
+ },
+ capabilities = {
+ textDocument = {
+ completion = {
+ completionItem = { snippetSupport = true },
+ },
+ },
+ },
+}
diff --git a/config/nvim/lua/lsp/emmet_language_server.lua b/config/nvim/lua/lsp/emmet_language_server.lua
new file mode 100644
index 0000000..bda333c
--- /dev/null
+++ b/config/nvim/lua/lsp/emmet_language_server.lua
@@ -0,0 +1,30 @@
+return {
+ cmd = { 'emmet-language-server', '--stdio' },
+ filetypes = {
+ 'astro',
+ 'css',
+ 'eruby',
+ 'html',
+ 'htmlangular',
+ 'htmldjango',
+ 'javascriptreact',
+ 'less',
+ 'pug',
+ 'sass',
+ 'scss',
+ 'svelte',
+ 'templ',
+ 'typescriptreact',
+ 'vue',
+ },
+ init_options = {
+ showSuggestionsAsSnippets = true,
+ },
+ capabilities = {
+ textDocument = {
+ completion = {
+ completionItem = { snippetSupport = true },
+ },
+ },
+ },
+}
diff --git a/config/nvim/lua/lsp/eslint.lua b/config/nvim/lua/lsp/eslint.lua
new file mode 100644
index 0000000..3a9ddc5
--- /dev/null
+++ b/config/nvim/lua/lsp/eslint.lua
@@ -0,0 +1,5 @@
+return {
+ settings = {
+ format = false,
+ },
+}
diff --git a/config/nvim/lua/lsp/html.lua b/config/nvim/lua/lsp/html.lua
new file mode 100644
index 0000000..ee57dd9
--- /dev/null
+++ b/config/nvim/lua/lsp/html.lua
@@ -0,0 +1,9 @@
+return {
+ capabilities = {
+ textDocument = {
+ completion = {
+ completionItem = { snippetSupport = true },
+ },
+ },
+ },
+}
diff --git a/config/nvim/lua/lsp/jsonls.lua b/config/nvim/lua/lsp/jsonls.lua
new file mode 100644
index 0000000..b229c84
--- /dev/null
+++ b/config/nvim/lua/lsp/jsonls.lua
@@ -0,0 +1,15 @@
+return {
+ capabilities = {
+ textDocument = {
+ completion = {
+ completionItem = { snippetSupport = true },
+ },
+ },
+ },
+ settings = {
+ json = {
+ schemas = require('schemastore').json.schemas(),
+ validate = { enable = true },
+ },
+ },
+}
diff --git a/config/nvim/lua/lsp/lua_ls.lua b/config/nvim/lua/lsp/lua_ls.lua
new file mode 100644
index 0000000..aaff467
--- /dev/null
+++ b/config/nvim/lua/lsp/lua_ls.lua
@@ -0,0 +1,19 @@
+return {
+ settings = {
+ Lua = {
+ codeLens = { enable = true },
+ completion = { keywordSnippet = 'Disable' },
+ diagnostics = { globals = { 'vim' } },
+ hint = {
+ enable = true,
+ arrayIndex = 'Disable',
+ semicolon = 'Disable',
+ },
+ runtime = { version = 'LuaJIT' },
+ telemetry = { enable = false },
+ workspace = {
+ checkThirdParty = false,
+ },
+ },
+ },
+}
diff --git a/config/nvim/lua/lsp/mdx_analyzer.lua b/config/nvim/lua/lsp/mdx_analyzer.lua
new file mode 100644
index 0000000..552c59a
--- /dev/null
+++ b/config/nvim/lua/lsp/mdx_analyzer.lua
@@ -0,0 +1,5 @@
+return {
+ cmd = { 'mdx-language-server', '--stdio' },
+ filetypes = { 'mdx' },
+ root_markers = { 'package.json' },
+}
diff --git a/config/nvim/lua/lsp/pytest_lsp.lua b/config/nvim/lua/lsp/pytest_lsp.lua
new file mode 100644
index 0000000..086029e
--- /dev/null
+++ b/config/nvim/lua/lsp/pytest_lsp.lua
@@ -0,0 +1,11 @@
+return {
+ cmd = { 'pytest-language-server' },
+ filetypes = { 'python' },
+ root_markers = {
+ 'pyproject.toml',
+ 'setup.py',
+ 'setup.cfg',
+ 'pytest.ini',
+ '.git',
+ },
+}
diff --git a/config/nvim/lua/lsp/rust-analyzer.lua b/config/nvim/lua/lsp/rust-analyzer.lua
new file mode 100644
index 0000000..729f3a2
--- /dev/null
+++ b/config/nvim/lua/lsp/rust-analyzer.lua
@@ -0,0 +1,28 @@
+return {
+ standalone = false,
+ capabilities = { general = { positionEncodings = { 'utf-16' } } },
+ settings = {
+ ['rust-analyzer'] = {
+ checkOnSave = {
+ overrideCommand = {
+ 'cargo',
+ 'clippy',
+ '--message-format=json',
+ '--',
+ '-W',
+ 'clippy::expect_used',
+ '-W',
+ 'clippy::pedantic',
+ '-W',
+ 'clippy::unwrap_used',
+ },
+ },
+ },
+ },
+ on_attach = function(...)
+ require('config.lsp').on_attach(...)
+ bmap({ 'n', '\\Rc', 'RustLsp codeAction' })
+ bmap({ 'n', '\\Rm', 'RustLsp expandMacro' })
+ bmap({ 'n', '\\Ro', 'RustLsp openCargo' })
+ end,
+}
diff --git a/config/nvim/lua/lsp/tailwindcss.lua b/config/nvim/lua/lsp/tailwindcss.lua
new file mode 100644
index 0000000..aaef569
--- /dev/null
+++ b/config/nvim/lua/lsp/tailwindcss.lua
@@ -0,0 +1,14 @@
+return {
+ filetypes = {
+ 'javascript',
+ 'javascriptreact',
+ 'typescript',
+ 'typescriptreact',
+ },
+ root_markers = {
+ 'tailwind.config.js',
+ 'tailwind.config.ts',
+ 'postcss.config.js',
+ 'postcss.config.ts',
+ },
+}
diff --git a/config/nvim/lua/lsp/tinymist.lua b/config/nvim/lua/lsp/tinymist.lua
new file mode 100644
index 0000000..32ce5ca
--- /dev/null
+++ b/config/nvim/lua/lsp/tinymist.lua
@@ -0,0 +1,12 @@
+return {
+ filetypes = { 'typst' },
+ settings = {
+ formatterMode = 'typstyle',
+ semanticTokens = 'disable',
+ lint = {
+ enabled = true,
+ when = 'onType',
+ -- when = 'onSave'
+ },
+ },
+}
diff --git a/config/nvim/lua/plugins/cp.lua b/config/nvim/lua/plugins/cp.lua
new file mode 100644
index 0000000..176af41
--- /dev/null
+++ b/config/nvim/lua/plugins/cp.lua
@@ -0,0 +1,169 @@
+local default_cpp_lang = {
+ extension = 'cc',
+ commands = {
+ build = {
+ 'g++',
+ '-std=c++23',
+ '-O2',
+ '-Wall',
+ '-Wextra',
+ '-Wpedantic',
+ '-Wshadow',
+ '-Wconversion',
+ '-Wformat=2',
+ '-Wfloat-equal',
+ '-Wundef',
+ '-fdiagnostics-color=always',
+ '-DLOCAL',
+ '{source}',
+ '-o',
+ '{binary}',
+ },
+ run = { '{binary}' },
+ debug = {
+ 'g++',
+ '-std=c++23',
+ '-g3',
+ '-fsanitize=address,undefined',
+ '-fno-omit-frame-pointer',
+ '-fstack-protector-all',
+ '-D_GLIBCXX_DEBUG',
+ '-DLOCAL',
+ '{source}',
+ '-o',
+ '{binary}',
+ },
+ },
+}
+
+local default_python_lang = {
+ extension = 'py',
+ commands = {
+ run = { 'python', '{source}' },
+ debug = { 'python', '{source}' },
+ },
+}
+
+local clang_format_content = [[BasedOnStyle: LLVM
+IndentWidth: 2
+UseTab: Never
+
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortLambdasOnASingleLine: None
+AllowShortBlocksOnASingleLine: Never
+AllowShortEnumsOnASingleLine: false
+AllowShortCaseExpressionOnASingleLine: false
+
+BreakBeforeBraces: Attach
+ColumnLimit: 100
+AlignAfterOpenBracket: Align
+BinPackArguments: false
+BinPackParameters: false]]
+
+return {
+ 'barrettruth/cp.nvim',
+ dir = '~/dev/cp.nvim',
+ cmd = 'CP',
+ keys = {
+ { 'ce', 'CP edit' },
+ { 'cp', 'CP panel' },
+ { 'cP', 'CP pick' },
+ { 'cr', 'CP run' },
+ { 'cd', 'CP run --debug' },
+ { 'cc', 'CP cache read' },
+ { ']c', 'CP next' },
+ { '[c', 'CP prev' },
+ },
+ dependencies = {
+ 'L3MON4D3/LuaSnip',
+ 'nvim-telescope/telescope.nvim',
+ },
+ config = function()
+ require('cp').setup({
+ languages = {
+ cpp = default_cpp_lang,
+ python = default_python_lang,
+ },
+ platforms = {
+ codeforces = {
+ enabled_languages = { 'cpp', 'python' },
+ default_language = 'cpp',
+ },
+ atcoder = {
+ enabled_languages = { 'cpp', 'python' },
+ default_language = 'cpp',
+ },
+ cses = {},
+ },
+ ui = { picker = 'fzf-lua' },
+ hooks = {
+ setup_io_input = function(buf)
+ require('cp.helpers').clearcol(buf)
+ end,
+ setup_io_output = function(buf)
+ require('cp.helpers').clearcol(buf)
+ end,
+ before_run = function(_)
+ require('config.lsp').format({ async = true })
+ end,
+ before_debug = function(_)
+ require('config.lsp').format({ async = true })
+ end,
+ setup_code = function(state)
+ vim.opt_local.winbar = ''
+ vim.opt_local.foldlevel = 0
+ vim.opt_local.foldmethod = 'marker'
+ vim.opt_local.foldmarker = '{{{,}}}'
+ vim.opt_local.foldtext = ''
+ vim.diagnostic.enable(false)
+
+ local buf = vim.api.nvim_get_current_buf()
+ local lines = vim.api.nvim_buf_get_lines(buf, 0, 1, true)
+ if #lines > 1 or (#lines == 1 and lines[1] ~= '') then
+ local pos = vim.api.nvim_win_get_cursor(0)
+ vim.cmd('normal! zx')
+ vim.api.nvim_win_set_cursor(0, pos)
+ return
+ end
+
+ local trigger = state.get_platform() or ''
+ vim.api.nvim_buf_set_lines(buf, 0, -1, false, { trigger })
+ vim.api.nvim_win_set_cursor(0, { 1, #trigger })
+ vim.cmd.startinsert({ bang = true })
+ vim.schedule(function()
+ local ls = require('luasnip')
+ if ls.expandable() then
+ vim.api.nvim_create_autocmd('TextChanged', {
+ buffer = buf,
+ once = true,
+ callback = function()
+ vim.schedule(function()
+ local pos =
+ vim.api.nvim_win_get_cursor(0)
+ vim.cmd('normal! zx')
+ vim.api.nvim_win_set_cursor(0, pos)
+ end)
+ end,
+ })
+ ls.expand()
+ end
+ vim.cmd.stopinsert()
+ end)
+ local clang_format_path = vim.fn.getcwd()
+ .. '/.clang-format'
+ if vim.fn.filereadable(clang_format_path) == 0 then
+ vim.fn.writefile(
+ vim.split(clang_format_content, '\n'),
+ clang_format_path
+ )
+ end
+ end,
+ },
+ filename = function(_, _, problem_id)
+ return problem_id
+ end,
+ })
+ end,
+}
diff --git a/config/nvim/lua/plugins/fzf.lua b/config/nvim/lua/plugins/fzf.lua
new file mode 100644
index 0000000..848105e
--- /dev/null
+++ b/config/nvim/lua/plugins/fzf.lua
@@ -0,0 +1,149 @@
+return {
+ 'ibhagwan/fzf-lua',
+ config = function(_, opts)
+ require('fzf-lua').setup(opts)
+
+ vim.api.nvim_create_autocmd('FileType', {
+ pattern = 'fzf',
+ callback = function()
+ vim.opt_local.number = false
+ vim.opt_local.relativenumber = false
+ end,
+ group = vim.api.nvim_create_augroup(
+ 'AFzfHighlights',
+ { clear = true }
+ ),
+ })
+
+ local ok, fzf_reload = pcall(require, 'config.fzf_reload')
+ if ok then
+ fzf_reload.setup(opts)
+ fzf_reload.reload(true)
+ end
+ end,
+ keys = {
+ {
+ '',
+ function()
+ local fzf = require('fzf-lua')
+ local git_dir = vim.fn
+ .system('git rev-parse --git-dir 2>/dev/null')
+ :gsub('\n', '')
+ if vim.v.shell_error == 0 and git_dir ~= '' then
+ fzf.git_files({ cwd_prompt = false })
+ else
+ fzf.files()
+ end
+ end,
+ },
+ { '', 'FzfLua live_grep' },
+ { 'f/', 'FzfLua search_history' },
+ { 'f:', 'FzfLua command_history' },
+ { 'fa', 'FzfLua autocmds' },
+ { 'fB', 'FzfLua builtin' },
+ { 'fb', 'FzfLua buffers' },
+ { 'fc', 'FzfLua commands' },
+ {
+ 'fe',
+ 'FzfLua files cwd=~/.config',
+ },
+ {
+ 'ff',
+ function()
+ require('fzf-lua').files({ cwd = vim.fn.expand('%:h') })
+ end,
+ },
+ {
+ 'fg',
+ function()
+ require('fzf-lua').live_grep({ cwd = vim.fn.expand('%:h') })
+ end,
+ },
+ { 'fH', 'FzfLua highlights' },
+ { 'fh', 'FzfLua help_tags' },
+ { 'fl', 'FzfLua loclist' },
+ { 'fm', 'FzfLua man_pages' },
+ { 'fq', 'FzfLua quickfix' },
+ { 'fr', 'FzfLua resume' },
+ {
+ 'fs',
+ 'FzfLua files cwd=~/.local/bin/scripts',
+ },
+ { 'GB', 'FzfLua git_branches' },
+ { 'Gb', 'FzfLua git_worktrees' },
+ { 'gq', 'FzfLua quickfix' },
+ { 'gl', 'FzfLua loclist' },
+ },
+ opts = {
+ files = {
+ cmd = vim.env.FZF_CTRL_T_COMMAND,
+ file_icons = false,
+ no_header_i = true,
+ },
+ fzf_args = (vim.env.FZF_DEFAULT_OPTS or ''):gsub(
+ '%-%-color=[^%s]+',
+ ''
+ ),
+ grep = {
+ file_icons = false,
+ no_header_i = true,
+ RIPGREP_CONFIG_PATH = vim.env.RIPGREP_CONFIG_PATH,
+ },
+ lsp = {
+ includeDeclaration = false,
+ jump1 = true,
+ symbols = {
+ symbol_hl_prefix = '@',
+ symbol_style = 3,
+ },
+ },
+ winopts = {
+ border = 'single',
+ preview = {
+ hidden = 'hidden',
+ },
+ },
+ actions = {
+ files = {
+ default = function(...)
+ require('fzf-lua.actions').file_edit(...)
+ end,
+ ['ctrl-l'] = function(...)
+ local a = require('fzf-lua.actions')
+ a.file_sel_to_ll(...)
+ vim.cmd.lclose()
+ end,
+ ['ctrl-q'] = function(...)
+ local a = require('fzf-lua.actions')
+ a.file_sel_to_qf(...)
+ vim.cmd.cclose()
+ end,
+ ['ctrl-h'] = function(...)
+ require('fzf-lua.actions').toggle_hidden(...)
+ end,
+ ['ctrl-v'] = function(...)
+ require('fzf-lua.actions').file_vsplit(...)
+ end,
+ ['ctrl-x'] = function(...)
+ require('fzf-lua.actions').file_split(...)
+ end,
+ },
+ },
+ border = 'single',
+ git = {
+ files = {
+ cmd = 'git ls-files --cached --others --exclude-standard',
+ },
+ worktrees = {
+ fzf_args = ((vim.env.FZF_DEFAULT_OPTS or '')
+ :gsub('%-%-bind=ctrl%-a:select%-all', '')
+ :gsub('--color=[^%s]+', '')),
+ },
+ branches = {
+ fzf_args = ((vim.env.FZF_DEFAULT_OPTS or '')
+ :gsub('%-%-bind=ctrl%-a:select%-all', '')
+ :gsub('--color=[^%s]+', '')),
+ },
+ },
+ },
+}
diff --git a/config/nvim/lua/plugins/git.lua b/config/nvim/lua/plugins/git.lua
new file mode 100644
index 0000000..da3ea6f
--- /dev/null
+++ b/config/nvim/lua/plugins/git.lua
@@ -0,0 +1,57 @@
+---@type number|nil
+local git_tab = nil
+
+---@type string|nil
+local prev = nil
+
+return {
+ {
+ 'tpope/vim-fugitive',
+ cmd = 'Git',
+ },
+ {
+ 'folke/snacks.nvim',
+ ---@type snacks.Config
+ opts = { gitbrowse = {} },
+ keys = {
+ { 'Go', 'lua Snacks.gitbrowse()' },
+ { 'Gi', 'lua Snacks.picker.gh_issue()' },
+ { 'Gp', 'lua Snacks.picker.gh_pr()' },
+ },
+ },
+ {
+ 'lewis6991/gitsigns.nvim',
+ keys = {
+ { '[g', 'Gitsigns next_hunk' },
+ { ']g', 'Gitsigns prev_hunk' },
+ { 'Gb', 'Gitsigns toggle_current_line_blame' },
+ {
+ 'Gs',
+ function()
+ if vim.opt.signcolumn:get() == 'no' then
+ prev = vim.opt.signcolumn:get()
+ vim.opt.signcolumn = 'yes'
+ else
+ vim.opt.signcolumn = prev
+ end
+ vim.cmd.Gitsigns('toggle_signs')
+ end,
+ },
+ },
+ event = 'VeryLazy',
+ opts = {
+ current_line_blame_formatter_nc = function()
+ return {}
+ end,
+ signs = {
+ -- use boxdraw chars
+ add = { text = '│' },
+ change = { text = '│' },
+ delete = { text = '_' },
+ topdelete = { text = '‾' },
+ changedelete = { text = '│' },
+ },
+ signcolumn = false,
+ },
+ },
+}
diff --git a/config/nvim/lua/plugins/lsp.lua b/config/nvim/lua/plugins/lsp.lua
new file mode 100644
index 0000000..7dbda41
--- /dev/null
+++ b/config/nvim/lua/plugins/lsp.lua
@@ -0,0 +1,374 @@
+return {
+ 'neovim/nvim-lspconfig',
+ {
+ 'folke/lazydev.nvim',
+ ft = 'lua',
+ opts = {
+ library = {
+ { path = '${3rd}/luv/library' },
+ },
+ },
+ },
+ {
+ 'saghen/blink.cmp',
+ build = 'cargo build --release',
+ dependencies = 'folke/lazydev.nvim',
+ ---@module 'blink.cmp'
+ ---@type blink.cmp.Config
+ event = { 'InsertEnter', 'CmdlineEnter' },
+ config = function(_, opts)
+ vim.o.pumheight = 15
+ opts.completion.menu.max_height = vim.o.pumheight
+
+ require('blink.cmp').setup(opts)
+ end,
+ opts = {
+ keymap = {
+ [''] = { 'select_prev' },
+ [''] = { 'show', 'select_next' },
+ [''] = {},
+ [''] = {
+ function(cmp)
+ return cmp.snippet_active() and cmp.accept()
+ or cmp.select_and_accept()
+ end,
+ 'snippet_forward',
+ },
+ },
+ completion = {
+ menu = {
+ auto_show = false,
+ scrollbar = false,
+ draw = {
+ columns = function(ctx)
+ if ctx.mode == 'cmdline' then
+ return {
+ { 'label', 'label_description', gap = 1 },
+ }
+ else
+ return {
+ { 'label', 'label_description' },
+ { 'kind' },
+ }
+ end
+ end,
+ },
+ },
+ },
+ cmdline = {
+ completion = {
+ menu = {
+ auto_show = true,
+ },
+ },
+ keymap = {
+ [''] = false,
+ [''] = false,
+ },
+ },
+ sources = {
+ default = { 'lsp', 'path', 'snippets', 'buffer' },
+ providers = {
+ lazydev = {
+ name = 'LazyDev',
+ module = 'lazydev.integrations.blink',
+ score_offset = 100,
+ },
+ },
+ },
+ },
+ keys = { { '', mode = 'i' } },
+ opts_extend = { 'sources.default' },
+ },
+ {
+ 'nvimtools/none-ls.nvim',
+ config = function()
+ local null_ls = require('null-ls')
+ local builtins = null_ls.builtins
+ local code_actions, diagnostics, formatting, hover =
+ builtins.code_actions,
+ builtins.diagnostics,
+ builtins.formatting,
+ builtins.hover
+
+ null_ls.setup({
+ border = 'single',
+ sources = {
+ require('none-ls.code_actions.eslint_d'),
+ code_actions.gitrebase,
+
+ diagnostics.buf,
+ diagnostics.checkmake,
+ require('none-ls.diagnostics.cpplint').with({
+ extra_args = {
+ '--filter',
+ '-legal/copyright',
+ '-whitespace/indent',
+ },
+ prepend_extra_args = true,
+ }),
+ require('none-ls.diagnostics.eslint_d'),
+ diagnostics.hadolint,
+ diagnostics.mypy.with({
+ extra_args = { '--check-untyped-defs' },
+ runtime_condition = function(params)
+ return vim.fn.executable('mypy') == 1
+ and require('null-ls.utils').path.exists(
+ params.bufname
+ )
+ end,
+ }),
+ diagnostics.selene,
+ diagnostics.vacuum,
+ diagnostics.zsh,
+
+ formatting.black,
+ formatting.isort.with({
+ extra_args = { '--profile', 'black' },
+ }),
+ formatting.buf,
+ formatting.cbfmt,
+ formatting.cmake_format,
+ require('none-ls.formatting.latexindent'),
+ formatting.prettierd.with({
+ env = {
+ XDG_RUNTIME_DIR = vim.env.XDG_RUNTIME_DIR
+ or (
+ (
+ vim.env.XDG_DATA_HOME
+ or (vim.env.HOME .. '/.local/share')
+ )
+ .. '/prettierd'
+ ),
+ },
+ extra_args = function(params)
+ if params.ft == 'jsonc' then
+ return { '--trailing-comma', 'none' }
+ end
+ return {}
+ end,
+ filetypes = {
+ 'css',
+ 'graphql',
+ 'html',
+ 'javascript',
+ 'javascriptreact',
+ 'json',
+ 'jsonc',
+ 'markdown',
+ 'mdx',
+ 'typescript',
+ 'typescriptreact',
+ 'yaml',
+ },
+ }),
+ formatting.shfmt.with({
+ extra_args = { '-i', '2' },
+ }),
+ formatting.stylua.with({
+ condition = function(utils)
+ return utils.root_has_file({
+ 'stylua.toml',
+ '.stylua.toml',
+ })
+ end,
+ }),
+
+ hover.dictionary,
+ hover.printenv,
+ },
+ on_attach = require('config.lsp').on_attach,
+ debounce = 0,
+ })
+ end,
+ dependencies = 'nvimtools/none-ls-extras.nvim',
+ },
+ {
+ 'b0o/SchemaStore.nvim',
+ },
+ {
+ 'saecki/live-rename.nvim',
+ event = 'LspAttach',
+ config = function(_, opts)
+ local live_rename = require('live-rename')
+
+ live_rename.setup(opts)
+
+ vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(o)
+ local clients = vim.lsp.get_clients({ buffer = o.buf })
+ for _, client in ipairs(clients) do
+ if client:supports_method('textDocument/rename') then
+ bmap(
+ { 'n', 'grn', live_rename.rename },
+ { buffer = o.buf }
+ )
+ end
+ end
+ end,
+ group = vim.api.nvim_create_augroup(
+ 'ALiveRename',
+ { clear = true }
+ ),
+ })
+ end,
+ keys = { 'grn' },
+ },
+ {
+ 'yioneko/nvim-vtsls',
+ config = function(_, opts)
+ require('vtsls').config(opts)
+ end,
+ dependencies = {
+ {
+ 'davidosomething/format-ts-errors.nvim',
+ ft = {
+ 'javascript',
+ 'javascriptreact',
+ 'typescript',
+ 'typescriptreact',
+ },
+ },
+ },
+ ft = {
+ 'javascript',
+ 'javascriptreact',
+ 'typescript',
+ 'typescriptreact',
+ },
+ opts = {
+ on_attach = function(_, bufnr)
+ bmap(
+ { 'n', 'gD', vim.cmd.VtsExec('goto_source_definition') },
+ { buffer = bufnr }
+ )
+ end,
+ settings = {
+ typescript = {
+ inlayHints = {
+ parameterNames = { enabled = 'literals' },
+ parameterTypes = { enabled = true },
+ variableTypes = { enabled = true },
+ propertyDeclarationTypes = { enabled = true },
+ functionLikeReturnTypes = { enabled = true },
+ enumMemberValues = { enabled = true },
+ },
+ },
+ },
+ handlers = {
+ ['textDocument/publishDiagnostics'] = function(_, result, ctx)
+ if not result.diagnostics then
+ return
+ end
+
+ local idx = 1
+ while idx <= #result.diagnostics do
+ local entry = result.diagnostics[idx]
+
+ local formatter =
+ require('format-ts-errors')[entry.code]
+ entry.message = formatter and formatter(entry.message)
+ or entry.message
+
+ if vim.tbl_contains({ 80001, 80006 }, entry.code) then
+ table.remove(result.diagnostics, idx)
+ else
+ idx = idx + 1
+ end
+ end
+
+ vim.lsp.diagnostic.on_publish_diagnostics(_, result, ctx)
+ end,
+ },
+ },
+ },
+ {
+ 'pmizio/typescript-tools.nvim',
+ opts = {
+ on_attach = function(_, bufnr)
+ bmap(
+ { 'n', 'gD', vim.cmd.TSToolsGoToSourceDefinition },
+ { buffer = bufnr }
+ )
+ end,
+ handlers = {
+ ['textDocument/publishDiagnostics'] = function(_, result, ctx)
+ if not result.diagnostics then
+ return
+ end
+
+ local idx = 1
+ while idx <= #result.diagnostics do
+ local entry = result.diagnostics[idx]
+
+ local formatter =
+ require('format-ts-errors')[entry.code]
+ entry.message = formatter and formatter(entry.message)
+ or entry.message
+
+ if vim.tbl_contains({ 80001, 80006 }, entry.code) then
+ table.remove(result.diagnostics, idx)
+ else
+ idx = idx + 1
+ end
+ end
+
+ vim.lsp.diagnostic.on_publish_diagnostics(_, result, ctx)
+ end,
+ },
+
+ settings = {
+ expose_as_code_action = 'all',
+ -- tsserver_path = vim.env.XDG_DATA_HOME .. '/pnpm/tsserver',
+ tsserver_file_preferences = {
+ includeInlayarameterNameHints = 'all',
+ includeInlayarameterNameHintsWhenArgumentMatchesName = false,
+ includeInlayFunctionParameterTypeHints = true,
+ includeInlayVariableTypeHints = true,
+ includeInlayVariableTypeHintsWhenTypeMatchesName = false,
+ includeInlayPropertyDeclarationTypeHints = true,
+ includeInlayFunctionLikeReturnTypeHints = true,
+ includeInlayEnumMemberValueHints = true,
+ },
+ },
+ },
+ dependencies = {
+ 'nvim-lua/plenary.nvim',
+ },
+ ft = {
+ 'javascript',
+ 'javascriptreact',
+ 'typescript',
+ 'typescriptreact',
+ },
+ },
+ {
+ 'mrcjkb/rustaceanvim',
+ ft = { 'rust' },
+ },
+ {
+ 'SmiteshP/nvim-navic',
+ opts = {
+ depth_limit = 3,
+ depth_limit_indicator = '…',
+ icons = {
+ enabled = false,
+ },
+ },
+ event = 'LspAttach',
+ },
+ {
+ 'chomosuke/typst-preview.nvim',
+ ft = 'typst',
+ version = '1.*',
+ opts = {
+ open_cmd = ('%s %%s --new-window'):format(vim.env.BROWSER),
+ invert_colors = 'auto',
+ dependencies_bin = {
+ tinymist = vim.fn.exepath('tinymist'),
+ websocat = vim.fn.exepath('websocat'),
+ },
+ },
+ keys = { { 't', 'TypstPreviewToggle' } },
+ },
+}
diff --git a/config/nvim/lua/plugins/nvim.lua b/config/nvim/lua/plugins/nvim.lua
new file mode 100644
index 0000000..29104be
--- /dev/null
+++ b/config/nvim/lua/plugins/nvim.lua
@@ -0,0 +1,430 @@
+return {
+ {
+ 'barrettruth/live-server.nvim',
+ build = 'pnpm add -g live-server',
+ cmd = { 'LiveServerStart', 'LiveServerStart' },
+ config = true,
+ keys = { { 'L', 'LiveServerToggle' } },
+ },
+ {
+ 'echasnovski/mini.pairs',
+ config = true,
+ event = 'InsertEnter',
+ },
+ {
+ 'iamcco/markdown-preview.nvim',
+ build = 'pnpm up && cd app && pnpm install',
+ ft = { 'markdown' },
+ config = function()
+ vim.cmd([[
+ function OpenMarkdownPreview(url)
+ exec "silent !$BROWSER -n --args " . a:url
+ endfunction
+ ]])
+ vim.g.mkdp_auto_close = 0
+ vim.g.mkdp_browserfunc = 'OpenMarkdownPreview'
+ vim.api.nvim_create_autocmd('FileType', {
+ pattern = 'markdown',
+ callback = function(opts)
+ bmap(
+ { 'n', 'm', vim.cmd.MarkdownPreviewToggle },
+ { buffer = opts.buf }
+ )
+ end,
+ group = vim.api.nvim_create_augroup(
+ 'AMarkdownKeybind',
+ { clear = true }
+ ),
+ })
+ end,
+ },
+ {
+ 'lervag/vimtex',
+ init = function()
+ vim.g.vimtex_view_method = 'general'
+ vim.g.vimtex_compiler_method = 'latexmk'
+ vim.g.vimtex_callback_progpath = '/usr/bin/nvim'
+ vim.g.vimtex_quickfix_mode = 0
+ end,
+ ft = { 'plaintext', 'tex' },
+ },
+ {
+ 'L3MON4D3/LuaSnip',
+ build = 'make install_jsregexp',
+ config = function()
+ local ls = require('luasnip')
+
+ ls.filetype_extend('htmldjango', { 'html' })
+ ls.filetype_extend('markdown', { 'html' })
+ ls.filetype_extend('javascriptreact', { 'javascript', 'html' })
+ ls.filetype_extend('typescript', { 'javascript' })
+ ls.filetype_extend(
+ 'typescriptreact',
+ { 'javascriptreact', 'javascript', 'html' }
+ )
+
+ require('luasnip.loaders.from_lua').lazy_load()
+ end,
+ keys = {
+ -- restore digraph mapping
+ { '', '', mode = 'i' },
+ {
+ '',
+ 'lua require("luasnip").expand()',
+ mode = 'i',
+ },
+ {
+ '',
+ 'lua if require("luasnip").jumpable(-1) then require("luasnip").jump(-1) end',
+ mode = { 'i', 's' },
+ },
+ {
+ '',
+ 'lua if require("luasnip").jumpable(1) then require("luasnip").jump(1) end',
+ mode = { 'i', 's' },
+ },
+ {
+ '',
+ 'lua if require("luasnip").choice_active() then require("luasnip").change_choice(-1) end',
+ mode = 'i',
+ },
+ {
+ '',
+ 'lua if require("luasnip").choice_active() then require("luasnip").change_choice(1) end',
+ mode = 'i',
+ },
+ },
+ opts = {
+ region_check_events = 'InsertEnter',
+ delete_check_events = {
+ 'TextChanged',
+ 'TextChangedI',
+ 'InsertLeave',
+ },
+ ext_opts = {
+ [require('luasnip.util.types').choiceNode] = {
+ active = {
+ virt_text = {
+ {
+ ' <- ',
+ vim.wo.cursorline and 'CursorLine' or 'Normal',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ 'laytan/cloak.nvim',
+ config = true,
+ keys = { { 'Ct', 'CloakToggle' } },
+ event = 'BufReadPre .env*',
+ },
+ {
+ 'maxmellon/vim-jsx-pretty',
+ ft = {
+ 'javascript',
+ 'javascriptreact',
+ 'typescript',
+ 'typescriptreact',
+ },
+ },
+ {
+ 'monaqa/dial.nvim',
+ config = function(_)
+ local augend = require('dial.augend')
+ require('dial.config').augends:register_group({
+ default = {
+ augend.integer.alias.decimal_int,
+ augend.integer.alias.hex,
+ augend.integer.alias.octal,
+ augend.integer.alias.binary,
+ augend.constant.alias.bool,
+ augend.constant.alias.alpha,
+ augend.constant.alias.Alpha,
+ augend.semver.alias.semver,
+ },
+ })
+ end,
+ keys = {
+ {
+ '',
+ function()
+ require('dial.map').manipulate('increment', 'normal')
+ end,
+ mode = 'n',
+ },
+ {
+ '',
+ function()
+ require('dial.map').manipulate('decrement', 'normal')
+ end,
+ mode = 'n',
+ },
+ {
+ 'g',
+ function()
+ require('dial.map').manipulate('increment', 'gnormal')
+ end,
+ mode = 'n',
+ },
+ {
+ 'g',
+ function()
+ require('dial.map').manipulate('decrement', 'gnormal')
+ end,
+ mode = 'n',
+ },
+
+ {
+ '',
+ function()
+ require('dial.map').manipulate('increment', 'visual')
+ end,
+ mode = 'v',
+ },
+ {
+ '',
+ function()
+ require('dial.map').manipulate('decrement', 'visual')
+ end,
+ mode = 'v',
+ },
+ {
+ 'g',
+ function()
+ require('dial.map').manipulate('increment', 'gvisual')
+ end,
+ mode = 'v',
+ },
+ {
+ 'g',
+ function()
+ require('dial.map').manipulate('decrement', 'gvisual')
+ end,
+ mode = 'v',
+ },
+ },
+ },
+ {
+ 'cbochs/grapple.nvim',
+ opts = {
+ scope = 'git_branch',
+ icons = false,
+ status = false,
+ win_opts = {
+ title = '',
+ footer = '',
+ },
+ },
+ keys = {
+ { 'ha', 'Grapple toggle' },
+ { 'hd', 'Grapple untag' },
+ { 'hq', 'Grapple toggle_tags' },
+
+ { '', 'Grapple select index=1' },
+ { '', 'Grapple select index=2' },
+ { '', 'Grapple select index=3' },
+ { '', 'Grapple select index=4' },
+
+ { ']h', 'Grapple cycle_tags next' },
+ { '[h', 'Grapple cycle_tags prev' },
+ },
+ },
+ {
+ 'catgoose/nvim-colorizer.lua',
+ opts = {
+ user_default_options = {
+ names = false,
+ rrggbbaa = true,
+ css = true,
+ css_fn = true,
+ rgb_fn = true,
+ hsl_fn = true,
+ },
+ },
+ event = 'VeryLazy',
+ },
+ {
+ 'stevearc/oil.nvim',
+ config = function(_, opts)
+ require('oil').setup(opts)
+ vim.api.nvim_create_autocmd('BufEnter', {
+ callback = function()
+ local ft = vim.bo.filetype
+ if ft == '' then
+ local path = vim.fn.expand('%:p')
+ if vim.fn.isdirectory(path) == 1 then
+ vim.cmd('Oil ' .. path)
+ end
+ end
+ end,
+ group = vim.api.nvim_create_augroup('AOil', { clear = true }),
+ })
+ end,
+ event = 'VeryLazy',
+ keys = {
+ { '-', 'e .' },
+ { '_', vim.cmd.Oil },
+ },
+ opts = {
+ skip_confirm_for_simple_edits = true,
+ prompt_save_on_select_new_entry = false,
+ float = { border = 'single' },
+ view_options = {
+ is_hidden_file = function(name, bufnr)
+ local dir = require('oil').get_current_dir(bufnr)
+ if not dir then
+ return false
+ end
+ if vim.startswith(name, '.') then
+ return false
+ end
+ local git_dir = vim.fn.finddir('.git', dir .. ';')
+ if git_dir == '' then
+ return false
+ end
+ local fullpath = dir .. '/' .. name
+ local result =
+ vim.fn.systemlist({ 'git', 'check-ignore', fullpath })
+ return #result > 0
+ end,
+ },
+ keymaps = {
+ [''] = false,
+ [''] = false,
+ [''] = false,
+ [''] = 'actions.refresh',
+ [''] = { 'actions.select', opts = { vertical = true } },
+ [''] = { 'actions.select', opts = { horizontal = true } },
+ ['q'] = function()
+ local ok, bufremove = pcall(require, 'mini.bufremove')
+ if ok then
+ bufremove.delete()
+ else
+ vim.cmd.bd()
+ end
+ end,
+ },
+ },
+ },
+ {
+ 'echasnovski/mini.misc',
+ config = true,
+ keys = {
+ {
+ 'm',
+ "lua MiniMisc.zoom(0, { title = '', border = 'none' })",
+ },
+ },
+ },
+ {
+ 'nvim-mini/mini.bufremove',
+ config = true,
+ keys = {
+ {
+ 'bd',
+ 'lua MiniBufremove.delete()',
+ },
+ {
+ 'bw',
+ 'lua MiniBufremove.wipeout()',
+ },
+ },
+ },
+ { 'tpope/vim-abolish', event = 'VeryLazy' },
+ { 'tpope/vim-sleuth', event = 'BufReadPost' },
+ {
+ 'kylechui/nvim-surround',
+ config = true,
+ keys = {
+ { 'cs', mode = 'n' },
+ { 'ds', mode = 'n' },
+ { 'ys', mode = 'n' },
+ { 'yS', mode = 'n' },
+ { 'yss', mode = 'n' },
+ { 'ySs', mode = 'n' },
+ },
+ },
+ {
+ 'tzachar/highlight-undo.nvim',
+ config = true,
+ keys = { 'u', 'U' },
+ },
+ {
+ 'kana/vim-textobj-user',
+ dependencies = {
+ {
+ 'kana/vim-textobj-entire',
+ keys = {
+ { 'ae', mode = { 'o', 'x' } },
+ { 'ie', mode = { 'o', 'x' } },
+ },
+ },
+ {
+ 'kana/vim-textobj-line',
+ keys = {
+ { 'al', mode = { 'o', 'x' } },
+ { 'il', mode = { 'o', 'x' } },
+ },
+ },
+ {
+ 'kana/vim-textobj-indent',
+ keys = {
+ { 'ai', mode = { 'o', 'x' } },
+ { 'ii', mode = { 'o', 'x' } },
+ },
+ },
+ {
+ 'preservim/vim-textobj-sentence',
+ keys = {
+ { 'as', mode = { 'o', 'x' } },
+ { 'is', mode = { 'o', 'x' } },
+ },
+ },
+ {
+ 'whatyouhide/vim-textobj-xmlattr',
+ keys = {
+ { 'ax', mode = { 'o', 'x' } },
+ { 'ix', mode = { 'o', 'x' } },
+ },
+ },
+ },
+ },
+ {
+ 'saghen/blink.indent',
+ opts = {
+ blocked = {
+ filetypes = {
+ include_defaults = true,
+ 'fugitive',
+ 'markdown',
+ 'typst',
+ },
+ },
+ static = {
+ char = '│',
+ },
+ scope = { enabled = false },
+ },
+ },
+ {
+ 'barrettruth/midnight.nvim',
+ dir = '~/dev/midnight.nvim',
+ init = function()
+ vim.api.nvim_create_autocmd({ 'OptionSet' }, {
+ pattern = 'background',
+ callback = function()
+ vim.cmd.colorscheme(
+ vim.o.background == 'dark' and 'midnight' or 'daylight'
+ )
+ end,
+ group = vim.api.nvim_create_augroup(
+ 'AColorScheme',
+ { clear = true }
+ ),
+ })
+ end,
+ },
+}
diff --git a/config/nvim/lua/plugins/overseer.lua b/config/nvim/lua/plugins/overseer.lua
new file mode 100644
index 0000000..f5743ca
--- /dev/null
+++ b/config/nvim/lua/plugins/overseer.lua
@@ -0,0 +1,30 @@
+return {
+ 'stevearc/overseer.nvim',
+ init = function()
+ vim.api.nvim_create_autocmd('VimLeavePre', {
+ callback = function()
+ local overseer = require('overseer')
+ for _, task in ipairs(overseer.list_tasks()) do
+ if task:is_running() then
+ task:stop()
+ end
+ task:dispose(true)
+ end
+ current_task = nil
+ end,
+ group = vim.api.nvim_create_augroup('AOverseer', { clear = true }),
+ })
+ end,
+ opts = {
+ strategy = 'terminal',
+ task_list = {
+ bindings = {
+ q = 'OverseerClose',
+ },
+ },
+ },
+ keys = {
+ { 'Oa', 'OverseerTaskAction' },
+ { 'Ob', 'OverseerBuild' },
+ },
+}
diff --git a/config/nvim/lua/plugins/treesitter.lua b/config/nvim/lua/plugins/treesitter.lua
new file mode 100644
index 0000000..41ae1a2
--- /dev/null
+++ b/config/nvim/lua/plugins/treesitter.lua
@@ -0,0 +1,156 @@
+return {
+ {
+ 'nvim-treesitter/nvim-treesitter',
+ branch = 'main',
+ build = ':TSUpdate',
+ lazy = false,
+ init = function()
+ vim.api.nvim_create_autocmd('FileType', {
+ pattern = '*',
+ callback = function()
+ local bufnr = vim.api.nvim_get_current_buf()
+ local lines = vim.api.nvim_buf_line_count(bufnr)
+
+ if lines < 5000 then
+ pcall(vim.treesitter.start)
+ else
+ vim.notify_once(
+ ('Skipping tree-sitter for bufnr %s; file too large (%s >= 5000 lines)'):format(
+ bufnr,
+ lines
+ )
+ )
+ end
+ end,
+ group = vim.api.nvim_create_augroup(
+ 'ATreeSitter',
+ { clear = true }
+ ),
+ })
+ end,
+ keys = {
+ {
+ 'T',
+ function()
+ local lang_map = { htmldjango = 'html' }
+ local bufnr = vim.api.nvim_get_current_buf()
+ local parser = vim.treesitter.get_parser(bufnr)
+ local lang = parser:lang()
+ local path = (
+ vim.env.NVIM_APPNAME or vim.fn.stdpath('config')
+ )
+ .. ('/after/queries/%s/highlights.scm'):format(
+ lang_map[lang] or lang
+ )
+
+ if vim.loop.fs_stat(path) then
+ vim.fn.rename(path, path .. '.disabled')
+ elseif vim.loop.fs_stat(path .. '.disabled') then
+ vim.fn.rename(path .. '.disabled', path)
+ end
+ vim.cmd.TSBufToggle('highlight')
+ vim.cmd.TSBufToggle('highlight')
+ end,
+ },
+ },
+ },
+ {
+ 'nvim-treesitter/nvim-treesitter-textobjects',
+ branch = 'main',
+ dependencies = 'nvim-treesitter/nvim-treesitter',
+ init = function()
+ vim.g.no_plugin_maps = true
+ end,
+ opts = {
+ select = {
+ enable = true,
+ lookahead = true,
+ },
+ move = {
+ enable = true,
+ set_jumps = true,
+ },
+ },
+ config = function(_, opts)
+ require('nvim-treesitter-textobjects').setup(opts)
+
+ local select = require('nvim-treesitter-textobjects.select')
+ local select_maps = {
+ { 'aa', '@parameter.outer' },
+ { 'ia', '@parameter.inner' },
+ { 'ab', '@block.outer' },
+ { 'ib', '@block.inner' },
+ { 'as', '@class.outer' },
+ { 'is', '@class.inner' },
+ { 'aC', '@call.outer' },
+ { 'iC', '@call.inner' },
+ { 'af', '@function.outer' },
+ { 'if', '@function.inner' },
+ { 'ai', '@conditional.outer' },
+ { 'ii', '@conditional.inner' },
+ { 'aL', '@loop.outer' },
+ { 'iL', '@loop.inner' },
+ }
+ for _, m in ipairs({ 'x', 'o' }) do
+ for _, t in ipairs(select_maps) do
+ map({
+ m,
+ t[1],
+ function()
+ select.select_textobject(t[2], 'textobjects', m)
+ end,
+ })
+ end
+ end
+
+ local move = require('nvim-treesitter-textobjects.move')
+ local move_maps = {
+ { ']a', 'goto_next_start', '@parameter.inner' },
+ { ']s', 'goto_next_start', '@class.outer' },
+ { ']f', 'goto_next_start', '@function.outer' },
+ { ']i', 'goto_next_start', '@conditional.outer' },
+ { ']/', 'goto_next_start', '@comment.outer' },
+ { ']A', 'goto_next_end', '@parameter.inner' },
+ { ']F', 'goto_next_end', '@function.outer' },
+ { ']I', 'goto_next_end', '@conditional.outer' },
+ { '[a', 'goto_previous_start', '@parameter.inner' },
+ { '[s', 'goto_previous_start', '@class.outer' },
+ { '[f', 'goto_previous_start', '@function.outer' },
+ { '[i', 'goto_previous_start', '@conditional.outer' },
+ { '[/', 'goto_previous_start', '@comment.outer' },
+ { '[A', 'goto_previous_end', '@parameter.inner' },
+ { '[F', 'goto_previous_end', '@function.outer' },
+ { '[I', 'goto_previous_end', '@conditional.outer' },
+ }
+ for _, m in ipairs({ 'n', 'x', 'o' }) do
+ for _, t in ipairs(move_maps) do
+ map({
+ m,
+ t[1],
+ function()
+ move[t[2]](t[3], 'textobjects')
+ end,
+ })
+ end
+ end
+
+ local ts_repeat =
+ require('nvim-treesitter-textobjects.repeatable_move')
+ for _, m in ipairs({ 'n', 'x', 'o' }) do
+ map({ m, ';', ts_repeat.repeat_last_move_next })
+ map({ m, ',', ts_repeat.repeat_last_move_previous })
+ map({ m, 'f', ts_repeat.builtin_f_expr }, { expr = true })
+ map({ m, 'F', ts_repeat.builtin_F_expr }, { expr = true })
+ map({ m, 't', ts_repeat.builtin_t_expr }, { expr = true })
+ map({ m, 'T', ts_repeat.builtin_T_expr }, { expr = true })
+ end
+ end,
+ },
+ {
+ 'Wansmer/treesj',
+ config = true,
+ keys = {
+ { 'gt', 'lua require("treesj").toggle()' },
+ },
+ },
+}
diff --git a/config/nvim/luasnippets/c.lua b/config/nvim/luasnippets/c.lua
new file mode 100644
index 0000000..f72aa53
--- /dev/null
+++ b/config/nvim/luasnippets/c.lua
@@ -0,0 +1,19 @@
+return {
+ s('/* ', fmt('/* {} */', { i(1) })),
+ s('pr', fmt('printf("{}", {});', { i(1), i(2) })),
+ s(
+ 'main',
+ fmt(
+ [[
+ #include
+
+ int main(void) {{
+ {}
+
+ return 0;
+ }}
+ ]],
+ { i(1) }
+ )
+ ),
+}
diff --git a/config/nvim/luasnippets/cpp.lua b/config/nvim/luasnippets/cpp.lua
new file mode 100644
index 0000000..26193da
--- /dev/null
+++ b/config/nvim/luasnippets/cpp.lua
@@ -0,0 +1,179 @@
+local ls = require('luasnip')
+local s, i, fmt = ls.snippet, ls.insert_node, require('luasnip.extras.fmt').fmt
+
+local cppsnippets = {}
+
+local template = [=[#include // {{{{{{
+
+#include
+#ifdef __cpp_lib_ranges_enumerate
+#include
+namespace rv = std::views;
+namespace rs = std::ranges;
+#endif
+
+#pragma GCC optimize("O2,unroll-loops")
+#pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")
+
+using namespace std;
+
+using i32 = int32_t;
+using u32 = uint32_t;
+using i64 = int64_t;
+using u64 = uint64_t;
+using f64 = double;
+using f128 = long double;
+
+#if __cplusplus >= 202002L
+template
+constexpr T MIN = std::numeric_limits::min();
+
+template
+constexpr T MAX = std::numeric_limits::max();
+#endif
+
+#ifdef LOCAL
+#define db(...) std::print(__VA_ARGS__)
+#define dbln(...) std::println(__VA_ARGS__)
+#else
+#define db(...)
+#define dbln(...)
+#endif
+// }}}}}}]=]
+
+-- utility snippets
+for _, snippet in ipairs({
+ s('in', fmt('#include {}', { i(1) })),
+ s(
+ 'main',
+ fmt(
+ [[#include
+
+int main() {{
+ {}
+
+ return 0;
+}}]],
+ { i(1) }
+ )
+ ),
+ s('pr', fmt('std::cout << {}', { i(1) })),
+ s('s', fmt('std::{}', { i(1) })),
+ s(
+ 'pbds',
+ fmt(
+ [[
+#include
+#include
+
+namespace pbds = __gnu_pbds;
+
+template
+using hashset = pbds::gp_hash_table;
+
+template
+using hashmap = pbds::gp_hash_table;
+
+template
+using multitreemap =
+ pbds::tree, pbds::rb_tree_tag,
+ pbds::tree_order_statistics_node_update>;
+
+template
+using treeset =
+ pbds::tree, pbds::rb_tree_tag,
+ pbds::tree_order_statistics_node_update>;
+
+template
+using treemap =
+ pbds::tree, pbds::rb_tree_tag,
+ pbds::tree_order_statistics_node_update>;
+
+template
+using treemultiset =
+ pbds::tree, pbds::rb_tree_tag,
+ pbds::tree_order_statistics_node_update>;
+ ]],
+ {}
+ )
+ ),
+}) do
+ table.insert(cppsnippets, snippet)
+end
+
+for _, entry in ipairs({
+ {
+ trig = 'codeforces',
+ body = template .. [[
+
+
+void solve() {{
+ {}
+}}
+
+int main() {{ // {{{{{{
+ std::cin.exceptions(std::cin.failbit);
+#ifdef LOCAL
+ std::cerr.rdbuf(std::cout.rdbuf());
+ std::cout.setf(std::ios::unitbuf);
+ std::cerr.setf(std::ios::unitbuf);
+#else
+ std::cin.tie(nullptr)->sync_with_stdio(false);
+#endif
+ u32 tc = 1;
+ std::cin >> tc;
+ for (u32 t = 0; t < tc; ++t) {{
+ solve();
+ }}
+ return 0;
+}} // vim: set foldmethod=marker foldmarker={{{{{{,}}}}}}]],
+ },
+ {
+ trig = 'atcoder',
+ body = template .. [[
+
+
+void solve() {{
+ {}
+}}
+
+int main() {{ // {{{{{{
+ std::cin.exceptions(std::cin.failbit);
+#ifdef LOCAL
+ std::cerr.rdbuf(std::cout.rdbuf());
+ std::cout.setf(std::ios::unitbuf);
+ std::cerr.setf(std::ios::unitbuf);
+#else
+ std::cin.tie(nullptr)->sync_with_stdio(false);
+#endif
+ solve();
+ return 0;
+}} // vim: set foldmethod=marker foldmarker={{{{{{,}}}}}}]],
+ },
+ {
+ trig = 'cses',
+ body = template .. [[
+
+
+void solve() {{
+ {}
+}}
+
+int main() {{ // {{{{{{
+ std::cin.exceptions(std::cin.failbit);
+#ifdef LOCAL
+ std::cerr.rdbuf(std::cout.rdbuf());
+ std::cout.setf(std::ios::unitbuf);
+ std::cerr.setf(std::ios::unitbuf);
+#else
+ std::cin.tie(nullptr)->sync_with_stdio(false);
+#endif
+ solve();
+ return 0;
+}} // vim: set foldmethod=marker foldmarker={{{{{{,}}}}}}]],
+ },
+}) do
+ table.insert(cppsnippets, s(entry.trig, fmt(entry.body, { i(1) })))
+end
+
+return cppsnippets
diff --git a/config/nvim/luasnippets/html.lua b/config/nvim/luasnippets/html.lua
new file mode 100644
index 0000000..0e4d411
--- /dev/null
+++ b/config/nvim/luasnippets/html.lua
@@ -0,0 +1,11 @@
+local word = function(index)
+ return f(function(name)
+ return name[1][1]:match('([^ ]*)')
+ end, { index })
+end
+
+return {
+ s('<', fmt('<{}>\n\t{}\n{}>', { i(1), i(2), word(1) })),
+ s('>', fmt('<{}>{}{}>', { i(1), i(2), word(1) })),
+ s('/', fmt('<{} />', { i(1) })),
+}
diff --git a/config/nvim/luasnippets/python.lua b/config/nvim/luasnippets/python.lua
new file mode 100644
index 0000000..18d49a4
--- /dev/null
+++ b/config/nvim/luasnippets/python.lua
@@ -0,0 +1,14 @@
+return {
+ s(
+ 'main',
+ fmt(
+ [[def main() -> None:
+ {}
+
+if __name__ == '__main__':
+ main()
+]],
+ { i(1) }
+ )
+ ),
+}
diff --git a/config/nvim/nvim-pack-lock.json b/config/nvim/nvim-pack-lock.json
new file mode 100644
index 0000000..5a07ddc
--- /dev/null
+++ b/config/nvim/nvim-pack-lock.json
@@ -0,0 +1,12 @@
+{
+ "plugins": {
+ "midnight.nvim": {
+ "rev": "45c447a1902e7bd5b8e0de99bb5068de9ac1efba",
+ "src": "https://github.com/barrettruth/midnight.nvim.git"
+ },
+ "nvim-treesitter": {
+ "rev": "6e42d823ce0a5a76180c473c119c7677738a09d1",
+ "src": "https://github.com/nvim-treesitter/nvim-treesitter.git"
+ }
+ }
+}
\ No newline at end of file
diff --git a/config/nvim/plugin/autocmds.lua b/config/nvim/plugin/autocmds.lua
new file mode 100644
index 0000000..b8b8b03
--- /dev/null
+++ b/config/nvim/plugin/autocmds.lua
@@ -0,0 +1,75 @@
+local api, au = vim.api, vim.api.nvim_create_autocmd
+
+local aug = api.nvim_create_augroup('AAugs', { clear = true })
+
+au('BufEnter', {
+ command = 'setl formatoptions-=cro spelloptions=camel,noplainbuffer',
+ group = aug,
+})
+
+au({ 'TermOpen', 'BufWinEnter' }, {
+ callback = function(args)
+ if vim.bo[args.buf].buftype == 'terminal' then
+ vim.opt_local.number = true
+ vim.opt_local.relativenumber = true
+ vim.cmd.startinsert()
+ end
+ end,
+ group = aug,
+})
+
+au('BufWritePost', {
+ pattern = (
+ os.getenv('XDG_CONFIG_HOME') or (os.getenv('HOME') .. '/.config')
+ ) .. '/dunst/dunstrc',
+ callback = function()
+ vim.fn.system('killall dunst && nohup dunst &; disown')
+ end,
+ group = aug,
+})
+
+au('BufReadPost', {
+ command = 'sil! normal g`"',
+ group = aug,
+})
+
+au({ 'BufRead', 'BufNewFile' }, {
+ pattern = '*/templates/*.html',
+ callback = function(opts)
+ vim.api.nvim_set_option_value(
+ 'filetype',
+ 'htmldjango',
+ { buf = opts.buf }
+ )
+ end,
+ group = aug,
+})
+
+au('TextYankPost', {
+ callback = function()
+ vim.highlight.on_yank({ higroup = 'Visual', timeout = 300 })
+ end,
+ group = aug,
+})
+
+au({ 'FocusLost', 'BufLeave', 'VimLeave' }, {
+ pattern = '*',
+ callback = function()
+ vim.cmd('silent! wall')
+ end,
+ group = aug,
+})
+
+au({ 'VimEnter', 'BufWinEnter', 'BufEnter' }, {
+ callback = function()
+ vim.api.nvim_set_option_value('cursorline', true, { scope = 'local' })
+ end,
+ group = aug,
+})
+
+au('WinLeave', {
+ callback = function()
+ vim.api.nvim_set_option_value('cursorline', false, { scope = 'local' })
+ end,
+ group = aug,
+})
diff --git a/config/nvim/plugin/fold.lua b/config/nvim/plugin/fold.lua
new file mode 100644
index 0000000..b95f0d0
--- /dev/null
+++ b/config/nvim/plugin/fold.lua
@@ -0,0 +1 @@
+require('config.fold').setup()
diff --git a/config/nvim/plugin/keymaps.lua b/config/nvim/plugin/keymaps.lua
new file mode 100644
index 0000000..afd3a21
--- /dev/null
+++ b/config/nvim/plugin/keymaps.lua
@@ -0,0 +1,68 @@
+map({
+ 'n',
+ 'gx',
+ function()
+ local url = vim.fn.expand('', nil)
+
+ if not url:match('https') and url:match('/') then
+ url = 'https://github.com/' .. url
+ end
+
+ vim.fn.jobstart({ vim.env.BROWSER, url })
+ end,
+})
+
+map({ { 'i', 'c' }, '', '' })
+
+for key, cmd in pairs({
+ left = 'vertical resize -10',
+ right = 'vertical resize +10',
+ down = 'resize +10',
+ up = 'resize -10',
+}) do
+ map({
+ 'n',
+ ('<%s>'):format(key),
+ function()
+ vim.cmd(cmd)
+ end,
+ })
+end
+
+map({ 'n', 'J', 'mzJ`z' })
+
+map({ 'x', 'p', '"_dp' })
+map({ 'x', 'P', '"_dP' })
+map({ 't', '', '' })
+
+map({ 'n', '