From 23d4795228cdeff14f18f630dcd4fa828d472c74 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sat, 7 Feb 2026 00:45:47 -0500 Subject: [PATCH] initial commit --- .gitignore | 2 + README.md | 392 ++++++++++++++ config/X11/xinitrc | 16 + config/X11/xmodmap | 7 + config/X11/xresources.gruvbox | 32 ++ config/X11/xresources.light | 26 + config/lf/cleaner | 2 + config/lf/lf.lua | 250 +++++++++ config/lf/previewer | 72 +++ config/lf/sort.py | 53 ++ config/nvim/.luacheckrc | 18 + config/nvim/after/ftplugin/c.lua | 1 + config/nvim/after/ftplugin/cpp.lua | 1 + config/nvim/after/ftplugin/fugitive.lua | 2 + config/nvim/after/ftplugin/help.lua | 5 + config/nvim/after/ftplugin/html.lua | 2 + config/nvim/after/ftplugin/htmldjango.lua | 1 + config/nvim/after/ftplugin/javascript.lua | 2 + config/nvim/after/ftplugin/man.lua | 2 + config/nvim/after/ftplugin/markdown.lua | 2 + config/nvim/after/ftplugin/python.lua | 1 + config/nvim/after/ftplugin/rust.lua | 1 + config/nvim/after/ftplugin/sh.lua | 1 + config/nvim/after/plugin/lsp.lua | 103 ++++ .../queries/html/highlights.scm.disabled | 5 + .../after/queries/javascript/highlights.scm | 5 + config/nvim/after/queries/jsx/highlights.scm | 2 + config/nvim/after/queries/tsx/highlights.scm | 2 + config/nvim/filetype.lua | 10 + config/nvim/init.lua | 65 +++ config/nvim/lazy-lock.json | 50 ++ config/nvim/lua/config/fold.lua | 156 ++++++ config/nvim/lua/config/fzf_reload.lua | 32 ++ config/nvim/lua/config/lines/init.lua | 7 + config/nvim/lua/config/lines/statuscolumn.lua | 25 + config/nvim/lua/config/lines/statusline.lua | 139 +++++ config/nvim/lua/config/lines/utils.lua | 41 ++ config/nvim/lua/config/lsp.lua | 97 ++++ config/nvim/lua/config/projects.lua | 15 + config/nvim/lua/config/tmux.lua | 101 ++++ config/nvim/lua/config/utils.lua | 7 + config/nvim/lua/lsp/bashls.lua | 9 + config/nvim/lua/lsp/clangd.lua | 39 ++ config/nvim/lua/lsp/cssls.lua | 18 + config/nvim/lua/lsp/emmet_language_server.lua | 30 ++ config/nvim/lua/lsp/eslint.lua | 5 + config/nvim/lua/lsp/html.lua | 9 + config/nvim/lua/lsp/jsonls.lua | 15 + config/nvim/lua/lsp/lua_ls.lua | 19 + config/nvim/lua/lsp/mdx_analyzer.lua | 5 + config/nvim/lua/lsp/pytest_lsp.lua | 11 + config/nvim/lua/lsp/rust-analyzer.lua | 28 + config/nvim/lua/lsp/tailwindcss.lua | 14 + config/nvim/lua/lsp/tinymist.lua | 12 + config/nvim/lua/plugins/cp.lua | 169 ++++++ config/nvim/lua/plugins/fzf.lua | 149 ++++++ config/nvim/lua/plugins/git.lua | 57 ++ config/nvim/lua/plugins/lsp.lua | 374 +++++++++++++ config/nvim/lua/plugins/nvim.lua | 430 +++++++++++++++ config/nvim/lua/plugins/overseer.lua | 30 ++ config/nvim/lua/plugins/treesitter.lua | 156 ++++++ config/nvim/luasnippets/c.lua | 19 + config/nvim/luasnippets/cpp.lua | 179 +++++++ config/nvim/luasnippets/html.lua | 11 + config/nvim/luasnippets/python.lua | 14 + config/nvim/nvim-pack-lock.json | 12 + config/nvim/plugin/autocmds.lua | 75 +++ config/nvim/plugin/fold.lua | 1 + config/nvim/plugin/keymaps.lua | 68 +++ config/nvim/plugin/lines.lua | 1 + config/nvim/plugin/options.lua | 76 +++ config/nvim/selene.toml | 6 + config/nvim/stylua.toml | 4 + config/nvim/vim.toml | 12 + config/zen/containers.json | 1 + config/zen/extensions.txt | 4 + config/zen/handlers.json | 1 + config/zen/user.js | 11 + config/zen/userChrome.css | 50 ++ config/zen/zen-keyboard-shortcuts.json | 1 + flake.lock | 195 +++++++ flake.nix | 57 ++ home/home.nix | 33 ++ home/modules/editor.nix | 17 + home/modules/git.nix | 95 ++++ home/modules/packages.nix | 92 ++++ home/modules/shell.nix | 504 ++++++++++++++++++ home/modules/terminal.nix | 95 ++++ home/modules/theme.nix | 50 ++ home/modules/ui.nix | 504 ++++++++++++++++++ hosts/xps15/configuration.nix | 104 ++++ scripts/ctl | 94 ++++ scripts/doc | 23 + scripts/hypr | 283 ++++++++++ scripts/mux | 118 ++++ scripts/pipes | 227 ++++++++ scripts/theme | 122 +++++ scripts/wp | 144 +++++ scripts/x | 56 ++ 99 files changed, 6691 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config/X11/xinitrc create mode 100644 config/X11/xmodmap create mode 100644 config/X11/xresources.gruvbox create mode 100644 config/X11/xresources.light create mode 100755 config/lf/cleaner create mode 100644 config/lf/lf.lua create mode 100755 config/lf/previewer create mode 100755 config/lf/sort.py create mode 100644 config/nvim/.luacheckrc create mode 100644 config/nvim/after/ftplugin/c.lua create mode 100644 config/nvim/after/ftplugin/cpp.lua create mode 100644 config/nvim/after/ftplugin/fugitive.lua create mode 100644 config/nvim/after/ftplugin/help.lua create mode 100644 config/nvim/after/ftplugin/html.lua create mode 100644 config/nvim/after/ftplugin/htmldjango.lua create mode 100644 config/nvim/after/ftplugin/javascript.lua create mode 100644 config/nvim/after/ftplugin/man.lua create mode 100644 config/nvim/after/ftplugin/markdown.lua create mode 100644 config/nvim/after/ftplugin/python.lua create mode 100644 config/nvim/after/ftplugin/rust.lua create mode 100644 config/nvim/after/ftplugin/sh.lua create mode 100644 config/nvim/after/plugin/lsp.lua create mode 100644 config/nvim/after/queries/html/highlights.scm.disabled create mode 100644 config/nvim/after/queries/javascript/highlights.scm create mode 100644 config/nvim/after/queries/jsx/highlights.scm create mode 100644 config/nvim/after/queries/tsx/highlights.scm create mode 100644 config/nvim/filetype.lua create mode 100644 config/nvim/init.lua create mode 100644 config/nvim/lazy-lock.json create mode 100644 config/nvim/lua/config/fold.lua create mode 100644 config/nvim/lua/config/fzf_reload.lua create mode 100644 config/nvim/lua/config/lines/init.lua create mode 100644 config/nvim/lua/config/lines/statuscolumn.lua create mode 100644 config/nvim/lua/config/lines/statusline.lua create mode 100644 config/nvim/lua/config/lines/utils.lua create mode 100644 config/nvim/lua/config/lsp.lua create mode 100644 config/nvim/lua/config/projects.lua create mode 100644 config/nvim/lua/config/tmux.lua create mode 100644 config/nvim/lua/config/utils.lua create mode 100644 config/nvim/lua/lsp/bashls.lua create mode 100644 config/nvim/lua/lsp/clangd.lua create mode 100644 config/nvim/lua/lsp/cssls.lua create mode 100644 config/nvim/lua/lsp/emmet_language_server.lua create mode 100644 config/nvim/lua/lsp/eslint.lua create mode 100644 config/nvim/lua/lsp/html.lua create mode 100644 config/nvim/lua/lsp/jsonls.lua create mode 100644 config/nvim/lua/lsp/lua_ls.lua create mode 100644 config/nvim/lua/lsp/mdx_analyzer.lua create mode 100644 config/nvim/lua/lsp/pytest_lsp.lua create mode 100644 config/nvim/lua/lsp/rust-analyzer.lua create mode 100644 config/nvim/lua/lsp/tailwindcss.lua create mode 100644 config/nvim/lua/lsp/tinymist.lua create mode 100644 config/nvim/lua/plugins/cp.lua create mode 100644 config/nvim/lua/plugins/fzf.lua create mode 100644 config/nvim/lua/plugins/git.lua create mode 100644 config/nvim/lua/plugins/lsp.lua create mode 100644 config/nvim/lua/plugins/nvim.lua create mode 100644 config/nvim/lua/plugins/overseer.lua create mode 100644 config/nvim/lua/plugins/treesitter.lua create mode 100644 config/nvim/luasnippets/c.lua create mode 100644 config/nvim/luasnippets/cpp.lua create mode 100644 config/nvim/luasnippets/html.lua create mode 100644 config/nvim/luasnippets/python.lua create mode 100644 config/nvim/nvim-pack-lock.json create mode 100644 config/nvim/plugin/autocmds.lua create mode 100644 config/nvim/plugin/fold.lua create mode 100644 config/nvim/plugin/keymaps.lua create mode 100644 config/nvim/plugin/lines.lua create mode 100644 config/nvim/plugin/options.lua create mode 100644 config/nvim/selene.toml create mode 100644 config/nvim/stylua.toml create mode 100644 config/nvim/vim.toml create mode 100644 config/zen/containers.json create mode 100644 config/zen/extensions.txt create mode 100644 config/zen/handlers.json create mode 100644 config/zen/user.js create mode 100644 config/zen/userChrome.css create mode 100644 config/zen/zen-keyboard-shortcuts.json create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 home/home.nix create mode 100644 home/modules/editor.nix create mode 100644 home/modules/git.nix create mode 100644 home/modules/packages.nix create mode 100644 home/modules/shell.nix create mode 100644 home/modules/terminal.nix create mode 100644 home/modules/theme.nix create mode 100644 home/modules/ui.nix create mode 100644 hosts/xps15/configuration.nix create mode 100755 scripts/ctl create mode 100755 scripts/doc create mode 100755 scripts/hypr create mode 100755 scripts/mux create mode 100755 scripts/pipes create mode 100755 scripts/theme create mode 100755 scripts/wp create mode 100755 scripts/x 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', 'iw', 'setlocal wrap!' }) +map({ 'n', 'is', 'setlocal spell!' }) +local state = nil + +map({ + 'n', + 'iz', + function() + if state then + for k, v in pairs(state) do + vim.opt_local[k] = v + end + state = nil + else + state = { + number = vim.opt_local.number:get(), + relativenumber = vim.opt_local.relativenumber:get(), + signcolumn = vim.opt_local.signcolumn:get(), + statuscolumn = vim.opt_local.statuscolumn:get(), + laststatus = vim.opt_local.laststatus:get(), + cmdheight = vim.opt_local.cmdheight:get(), + } + vim.opt_local.number = false + vim.opt_local.relativenumber = false + vim.opt_local.signcolumn = 'no' + vim.opt_local.statuscolumn = '' + vim.opt_local.laststatus = 0 + vim.opt_local.cmdheight = 0 + end + end, +}) diff --git a/config/nvim/plugin/lines.lua b/config/nvim/plugin/lines.lua new file mode 100644 index 0000000..70aa836 --- /dev/null +++ b/config/nvim/plugin/lines.lua @@ -0,0 +1 @@ +require('config.lines').setup() diff --git a/config/nvim/plugin/options.lua b/config/nvim/plugin/options.lua new file mode 100644 index 0000000..831748f --- /dev/null +++ b/config/nvim/plugin/options.lua @@ -0,0 +1,76 @@ +local o, opt = vim.o, vim.opt + +o.autowrite = true + +o.breakindent = true + +o.conceallevel = 0 + +opt.diffopt:append('linematch:60') + +o.expandtab = true + +o.exrc = true +o.secure = true + +opt.foldcolumn = 'auto:1' +opt.signcolumn = 'no' + +opt.fillchars = { + eob = ' ', + vert = '│', + diff = '╱', +} + +opt.iskeyword:append('-') + +o.laststatus = 3 + +o.linebreak = true + +o.list = true +opt.listchars = { + space = ' ', + trail = '·', + tab = ' ', +} + +opt.matchpairs:append('<:>') + +o.number = true +o.relativenumber = true + +opt.path:append('**') + +o.scrolloff = 8 + +o.shiftwidth = 2 + +opt.shortmess:append('acCIs') + +o.showmode = false + +o.showtabline = 0 + +o.spellfile = (vim.env.XDG_DATA_HOME or (vim.env.HOME .. '/.local/share')) + .. '/nvim/spell.encoding.add' + +o.splitkeep = 'screen' + +o.splitbelow = true +o.splitright = true + +o.swapfile = false + +o.termguicolors = true + +o.undodir = (vim.env.XDG_DATA_HOME or (vim.env.HOME .. '/.local/share')) + .. '/nvim/undo' +o.undofile = true + +o.updatetime = 50 + +o.winborder = 'single' +o.winbar = '' + +o.wrap = false diff --git a/config/nvim/selene.toml b/config/nvim/selene.toml new file mode 100644 index 0000000..a286987 --- /dev/null +++ b/config/nvim/selene.toml @@ -0,0 +1,6 @@ +std = "vim" + +exclude = [".luacheckrc"] + +[rules] +mixed_table = "allow" diff --git a/config/nvim/stylua.toml b/config/nvim/stylua.toml new file mode 100644 index 0000000..dda733b --- /dev/null +++ b/config/nvim/stylua.toml @@ -0,0 +1,4 @@ +quote_style = "AutoPreferSingle" +indent_type = "Spaces" +column_width = 80 +collapse_simple_statement = "Never" diff --git a/config/nvim/vim.toml b/config/nvim/vim.toml new file mode 100644 index 0000000..eac769a --- /dev/null +++ b/config/nvim/vim.toml @@ -0,0 +1,12 @@ +[selene] +base = "lua52" +name = "vim" + +[vim] +any = true + +[map] +any = true + +[bmap] +any = true diff --git a/config/zen/containers.json b/config/zen/containers.json new file mode 100644 index 0000000..8114fdf --- /dev/null +++ b/config/zen/containers.json @@ -0,0 +1 @@ +{"version":5,"lastUserContextId":5,"identities":[{"icon":"fingerprint","color":"blue","l10nId":"user-context-personal","public":true,"userContextId":1},{"icon":"briefcase","color":"orange","l10nId":"user-context-work","public":true,"userContextId":2},{"icon":"dollar","color":"green","l10nId":"user-context-banking","public":true,"userContextId":3},{"icon":"cart","color":"pink","l10nId":"user-context-shopping","public":true,"userContextId":4},{"public":false,"icon":"","color":"","name":"userContextIdInternal.thumbnail","accessKey":"","userContextId":5},{"userContextId":4294967295,"public":false,"icon":"","color":"","name":"userContextIdInternal.webextStorageLocal","accessKey":""}]} \ No newline at end of file diff --git a/config/zen/extensions.txt b/config/zen/extensions.txt new file mode 100644 index 0000000..f55fd05 --- /dev/null +++ b/config/zen/extensions.txt @@ -0,0 +1,4 @@ +{446900e4-71c2-419f-a6a7-df9c091e268b}: Bitwarden Password Manager +addon@darkreader.org: Dark Reader +jid1-D7momAzRw417Ag@jetpack: Wikiwand - knowledge, with context +uBlock0@raymondhill.net: uBlock Origin diff --git a/config/zen/handlers.json b/config/zen/handlers.json new file mode 100644 index 0000000..3d96b89 --- /dev/null +++ b/config/zen/handlers.json @@ -0,0 +1 @@ +{"defaultHandlersVersion":{},"mimeTypes":{"application/pdf":{"action":3,"extensions":["pdf"]},"image/webp":{"action":3,"extensions":["webp"]},"image/avif":{"action":3,"extensions":["avif"]},"image/jxl":{"action":3,"extensions":["jxl"]}},"schemes":{"mailto":{"stubEntry":true,"handlers":[null,{"name":"Gmail","uriTemplate":"https://mail.google.com/mail/?extsrc=mailto&url=%s"}]},"io.element.desktop":{"action":4},"element":{"action":4}},"isDownloadsImprovementsAlreadyMigrated":false} \ No newline at end of file diff --git a/config/zen/user.js b/config/zen/user.js new file mode 100644 index 0000000..29feb05 --- /dev/null +++ b/config/zen/user.js @@ -0,0 +1,11 @@ +user_pref("browser.urlbar.shortcuts.bookmarks", false); +user_pref("browser.urlbar.shortcuts.history", false); +user_pref("browser.urlbar.shortcuts.tabs", false); +user_pref("zen.tabs.show-newtab-vertical", false); +user_pref("zen.theme.border-radius", 0); +user_pref("zen.theme.content-element-separation", 0); +user_pref("zen.view.compact.enable-at-startup", false); +user_pref("zen.view.compact.hide-toolbar", true); +user_pref("zen.view.compact.toolbar-hide-after-hover.duration", 0); +user_pref("zen.view.experimental-no-window-controls", true); +user_pref("zen.workspaces.continue-where-left-off", true); diff --git a/config/zen/userChrome.css b/config/zen/userChrome.css new file mode 100644 index 0000000..b201494 --- /dev/null +++ b/config/zen/userChrome.css @@ -0,0 +1,50 @@ +#vertical-pinned-tabs-container > .zen-workspace-tabs-section[hidden="true"] { + display: none !important; +} + +tabs { + counter-reset: tab-counter; +} + +hbox.zen-essentials-container tab:not([zen-glance-tab="true"]), +zen-workspace[active] tab:not([zen-glance-tab="true"]) { + counter-increment: tab-counter; +} + +@media (-moz-bool-pref: "zen.view.sidebar-expanded") { + tab:not([zen-glance-tab="true"]) > .tab-stack > .tab-content::before { + content: counter(tab-counter); + font-weight: normal; + font-size: 100%; + color: inherit; + z-index: -100; + display: inline-block; + text-align: center; + width: 20px; + height: 20px; + line-height: 20px; + margin-left: 3px; + margin-right: 5px; + } +} + +@media not (-moz-bool-pref: "zen.view.sidebar-expanded") { + tab:not([zen-glance-tab="true"]) > .tab-stack > .tab-content::before { + content: counter(tab-counter) ""; + position: absolute; + top: 4px; + left: 1px; + z-index: -100; + font-weight: normal; + font-size: 100%; + color: inherit; + } +} + +.zen-current-workspace-indicator { + display: none !important; +} + +#zen-media-controls-toolbar { + display: none !important; +} diff --git a/config/zen/zen-keyboard-shortcuts.json b/config/zen/zen-keyboard-shortcuts.json new file mode 100644 index 0000000..014b65e --- /dev/null +++ b/config/zen/zen-keyboard-shortcuts.json @@ -0,0 +1 @@ +{"shortcuts":[{"id":"key_wrToggleCaptureSequenceCmd","key":"^","keycode":null,"group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"wrToggleCaptureSequenceCmd","disabled":false,"reserved":false,"internal":false},{"id":"key_wrCaptureCmd","key":"#","keycode":null,"group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"wrCaptureCmd","disabled":false,"reserved":false,"internal":false},{"id":"key_selectLastTab","key":"9","keycode":"","group":"windowAndTabManagement","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_selectTab8","key":"8","keycode":"","group":"windowAndTabManagement","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_selectTab7","key":"7","keycode":"","group":"windowAndTabManagement","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_selectTab6","key":"6","keycode":"","group":"windowAndTabManagement","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_selectTab5","key":"5","keycode":"","group":"windowAndTabManagement","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_selectTab4","key":"4","keycode":"","group":"windowAndTabManagement","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_selectTab3","key":"3","keycode":"","group":"windowAndTabManagement","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_selectTab2","key":"2","keycode":"","group":"windowAndTabManagement","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_selectTab1","key":"1","keycode":"","group":"windowAndTabManagement","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_undoCloseWindow","key":"","keycode":"","group":"windowAndTabManagement","l10nId":"zen-window-new-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":"History:UndoCloseWindow","disabled":true,"reserved":false,"internal":false},{"id":"key_restoreLastClosedTabOrWindowOrSession","key":"t","keycode":null,"group":"windowAndTabManagement","l10nId":"zen-restore-last-closed-tab-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"History:RestoreLastClosedTabOrWindowOrSession","disabled":false,"reserved":false,"internal":false},{"id":"key_quitApplication","key":"q","keycode":null,"group":"windowAndTabManagement","l10nId":"zen-quit-app-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_quitApplication","disabled":false,"reserved":true,"internal":false},{"id":"key_sanitize","keycode":"VK_DELETE","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"Tools:Sanitize","disabled":false,"reserved":false,"internal":false},{"id":"key_screenshot","key":"","keycode":"","group":"mediaAndDisplay","l10nId":"zen-screenshot-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":"Browser:Screenshot","disabled":false,"reserved":false,"internal":false},{"id":"key_privatebrowsing","key":"p","keycode":null,"group":"navigation","l10nId":"zen-private-browsing-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"Tools:PrivateBrowsing","disabled":false,"reserved":true,"internal":false},{"id":"key_switchTextDirection","key":"x","keycode":null,"group":"mediaAndDisplay","l10nId":"zen-bidi-switch-direction-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_switchTextDirection","disabled":false,"reserved":false,"internal":false},{"id":"key_showAllTabs","keycode":"VK_TAB","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":null,"key":"","keycode":null,"group":"other","l10nId":"zen-full-zoom-reset-shortcut-alt","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_fullZoomReset","disabled":false,"reserved":false,"internal":false},{"id":"key_fullZoomReset","key":"0","keycode":null,"group":"mediaAndDisplay","l10nId":"zen-full-zoom-reset-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_fullZoomReset","disabled":false,"reserved":false,"internal":false},{"id":null,"key":"","keycode":null,"group":"other","l10nId":"zen-full-zoom-enlarge-shortcut-alt2","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_fullZoomEnlarge","disabled":false,"reserved":false,"internal":false},{"id":null,"key":"=","keycode":null,"group":"other","l10nId":"zen-full-zoom-enlarge-shortcut-alt","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_fullZoomEnlarge","disabled":false,"reserved":false,"internal":false},{"id":"key_fullZoomEnlarge","key":"+","keycode":null,"group":"mediaAndDisplay","l10nId":"zen-full-zoom-enlarge-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_fullZoomEnlarge","disabled":false,"reserved":false,"internal":false},{"id":null,"key":"","keycode":null,"group":"other","l10nId":"zen-full-zoom-reduce-shortcut-alt-b","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_fullZoomReduce","disabled":false,"reserved":false,"internal":false},{"id":null,"key":"_","keycode":null,"group":"other","l10nId":"zen-full-zoom-reduce-shortcut-alt-a","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_fullZoomReduce","disabled":false,"reserved":false,"internal":false},{"id":"key_fullZoomReduce","key":"-","keycode":null,"group":"mediaAndDisplay","l10nId":"zen-full-zoom-reduce-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_fullZoomReduce","disabled":false,"reserved":false,"internal":false},{"id":"key_gotoHistory","key":"h","keycode":null,"group":"navigation","l10nId":"zen-history-sidebar-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"toggleSidebarKb","key":"z","keycode":null,"group":"other","l10nId":"zen-toggle-sidebar-shortcut","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"viewGenaiChatSidebarKb","key":"x","keycode":null,"group":"other","l10nId":"zen-ai-chatbot-sidebar-shortcut","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_stop","key":"","keycode":"","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":"Browser:Stop","disabled":false,"reserved":false,"internal":false},{"id":"viewBookmarksToolbarKb","key":"","keycode":"","group":"other","l10nId":"zen-bookmark-show-toolbar-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"viewBookmarksSidebarKb","key":"b","keycode":null,"group":"other","l10nId":"zen-bookmark-show-sidebar-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"manBookmarkKb","key":"o","keycode":null,"group":"historyAndBookmarks","l10nId":"zen-bookmark-show-library-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"Browser:ShowAllBookmarks","disabled":false,"reserved":false,"internal":false},{"id":"bookmarkAllTabsKb","key":"","keycode":"","group":"historyAndBookmarks","l10nId":"zen-bookmark-this-page-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"addBookmarkAsKb","key":"d","keycode":null,"group":"historyAndBookmarks","l10nId":"zen-bookmark-this-page-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"Browser:AddBookmarkAs","disabled":false,"reserved":false,"internal":false},{"id":null,"keycode":"VK_F3","group":"other","l10nId":"zen-search-find-again-shortcut-prev","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":false},"action":"cmd_findPrevious","disabled":false,"reserved":false,"internal":false},{"id":null,"keycode":"VK_F3","group":"other","l10nId":"zen-search-find-again-shortcut-alt","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_findAgain","disabled":false,"reserved":false,"internal":false},{"id":"key_findPrevious","key":"g","keycode":null,"group":"searchAndFind","l10nId":"zen-search-find-again-shortcut-prev","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_findPrevious","disabled":false,"reserved":false,"internal":false},{"id":"key_findAgain","key":"g","keycode":null,"group":"searchAndFind","l10nId":"zen-search-find-again-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_findAgain","disabled":false,"reserved":false,"internal":false},{"id":"key_find","key":"f","keycode":null,"group":"searchAndFind","l10nId":"zen-find-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_find","disabled":false,"reserved":false,"internal":false},{"id":"key_viewInfo","key":"i","keycode":null,"group":"pageOperations","l10nId":"zen-page-info-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"View:PageInfo","disabled":false,"reserved":false,"internal":false},{"id":"key_viewSource","key":"u","keycode":null,"group":"pageOperations","l10nId":"zen-page-source-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"View:PageSource","disabled":false,"reserved":false,"internal":false},{"id":"key_aboutProcesses","keycode":"VK_ESCAPE","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":false},"action":"View:AboutProcesses","disabled":false,"reserved":false,"internal":false},{"id":"key_reload_skip_cache","key":"r","keycode":null,"group":"navigation","l10nId":"zen-nav-reload-shortcut-skip-cache","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"Browser:ReloadSkipCache","disabled":false,"reserved":false,"internal":false},{"id":"key_reload","key":"r","keycode":null,"group":"navigation","l10nId":"zen-nav-reload-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"Browser:Reload","disabled":false,"reserved":false,"internal":false},{"id":null,"key":"}","keycode":null,"group":"other","l10nId":"zen-picture-in-picture-toggle-shortcut-alt","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"View:PictureInPicture","disabled":false,"reserved":false,"internal":false},{"id":"key_togglePictureInPicture","key":"]","keycode":null,"group":"pageOperations","l10nId":"zen-picture-in-picture-toggle-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"View:PictureInPicture","disabled":false,"reserved":false,"internal":false},{"id":"key_toggleReaderMode","key":"r","keycode":null,"group":"pageOperations","l10nId":"zen-reader-mode-toggle-shortcut-other","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":"View:ReaderView","disabled":true,"reserved":false,"internal":false},{"id":"key_exitFullScreen","keycode":"VK_F11","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"View:FullScreen","disabled":true,"reserved":true,"internal":false},{"id":"key_enterFullScreen","keycode":"VK_F11","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"View:FullScreen","disabled":false,"reserved":false,"internal":false},{"id":"key_reload_skip_cache2","keycode":"VK_F5","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"Browser:ReloadSkipCache","disabled":false,"reserved":false,"internal":false},{"id":"showAllHistoryKb","key":"","keycode":"","group":"historyAndBookmarks","l10nId":"zen-history-show-all-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":"Browser:ShowAllHistory","disabled":false,"reserved":false,"internal":false},{"id":"key_reload2","keycode":"VK_F5","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"Browser:Reload","disabled":false,"reserved":false,"internal":false},{"id":"goHome","keycode":"VK_HOME","group":"navigation","l10nId":null,"modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"goForwardKb2","key":"]","keycode":null,"group":"navigation","l10nId":"zen-nav-fwd-shortcut-alt","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"Browser:Forward","disabled":false,"reserved":false,"internal":false},{"id":"goBackKb2","key":"[","keycode":null,"group":"navigation","l10nId":"zen-nav-back-shortcut-alt","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"Browser:Back","disabled":false,"reserved":false,"internal":false},{"id":"goForwardKb","keycode":"VK_RIGHT","group":"navigation","l10nId":null,"modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":false},"action":"Browser:Forward","disabled":false,"reserved":false,"internal":false},{"id":"goBackKb","keycode":"VK_LEFT","group":"navigation","l10nId":null,"modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":false},"action":"Browser:Back","disabled":false,"reserved":false,"internal":false},{"id":null,"keycode":"VK_BACK","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":false},"action":"cmd_handleShiftBackspace","disabled":false,"reserved":false,"internal":false},{"id":null,"keycode":"VK_BACK","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_handleBackspace","disabled":false,"reserved":false,"internal":false},{"id":"key_selectAll","key":"a","keycode":null,"group":"other","l10nId":"zen-text-action-select-all-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":true},{"id":"key_delete","keycode":"VK_DELETE","group":"other","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_delete","disabled":false,"reserved":false,"internal":false},{"id":"key_paste","key":"v","keycode":null,"group":"other","l10nId":"zen-text-action-paste-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":true},{"id":"key_copy","key":"c","keycode":null,"group":"other","l10nId":"zen-text-action-copy-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":true},{"id":"key_cut","key":"x","keycode":null,"group":"other","l10nId":"zen-text-action-cut-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":true},{"id":"key_redo","key":"z","keycode":null,"group":"other","l10nId":"zen-text-action-undo-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":true},{"id":"key_undo","key":"z","keycode":null,"group":"other","l10nId":"zen-text-action-undo-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":true},{"id":"key_toggleMute","key":"m","keycode":null,"group":"mediaAndDisplay","l10nId":"zen-mute-toggle-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_toggleMute","disabled":false,"reserved":false,"internal":false},{"id":"key_closeWindow","key":"w","keycode":null,"group":"windowAndTabManagement","l10nId":"zen-close-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_closeWindow","disabled":false,"reserved":true,"internal":false},{"id":"key_close","key":"w","keycode":null,"group":"windowAndTabManagement","l10nId":"zen-close-tab-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_close","disabled":false,"reserved":true,"internal":false},{"id":"printKb","key":"p","keycode":null,"group":"pageOperations","l10nId":"zen-print-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_print","disabled":false,"reserved":false,"internal":false},{"id":"key_savePage","key":"s","keycode":null,"group":"pageOperations","l10nId":"zen-save-page-shortcut","modifiers":{"control":false,"alt":true,"shift":true,"meta":false,"accel":true},"action":"Browser:SavePage","disabled":false,"reserved":false,"internal":false},{"id":"openFileKb","key":"","keycode":"","group":"other","l10nId":"zen-file-open-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":"Browser:OpenFile","disabled":false,"reserved":false,"internal":false},{"id":"key_openAddons","key":"a","keycode":null,"group":"other","l10nId":"zen-addons-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"Tools:Addons","disabled":false,"reserved":false,"internal":false},{"id":"key_openDownloads","key":"y","keycode":null,"group":"other","l10nId":"zen-downloads-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"Tools:Downloads","disabled":false,"reserved":false,"internal":false},{"id":"key_search2","key":"j","keycode":null,"group":"searchAndFind","l10nId":"zen-search-focus-shortcut-alt","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"Tools:Search","disabled":false,"reserved":false,"internal":false},{"id":"key_search","key":"k","keycode":null,"group":"searchAndFind","l10nId":"zen-search-focus-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"Tools:Search","disabled":false,"reserved":false,"internal":false},{"id":"focusURLBar2","key":"d","keycode":null,"group":"pageOperations","l10nId":"zen-location-open-shortcut-alt","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":false},"action":"Browser:OpenLocation","disabled":false,"reserved":false,"internal":false},{"id":"focusURLBar","key":"l","keycode":null,"group":"pageOperations","l10nId":"zen-location-open-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"Browser:OpenLocation","disabled":false,"reserved":false,"internal":false},{"id":"key_newNavigatorTab","key":"t","keycode":null,"group":"windowAndTabManagement","l10nId":"zen-tab-new-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_newNavigatorTabNoEvent","disabled":false,"reserved":true,"internal":false},{"id":"key_newNavigator","key":"n","keycode":null,"group":"windowAndTabManagement","l10nId":"zen-window-new-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_newNavigator","disabled":false,"reserved":true,"internal":false},{"id":"zen-compact-mode-toggle","key":"b","keycode":"","group":"zen-compact-mode","l10nId":"zen-compact-mode-shortcut-toggle","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_zenCompactModeToggle","disabled":false,"reserved":false,"internal":false},{"id":"zen-compact-mode-show-sidebar","key":"s","keycode":"","group":"zen-compact-mode","l10nId":"zen-compact-mode-shortcut-show-sidebar","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":"cmd_zenCompactModeShowSidebar","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-10","key":"","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-10","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_zenWorkspaceSwitch10","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-9","key":"","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-9","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_zenWorkspaceSwitch9","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-8","key":"","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-8","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_zenWorkspaceSwitch8","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-7","key":"","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-7","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_zenWorkspaceSwitch7","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-6","key":"","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-6","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_zenWorkspaceSwitch6","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-5","key":"","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-5","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_zenWorkspaceSwitch5","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-4","key":"l","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-4","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_zenWorkspaceSwitch4","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-3","key":"k","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-3","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_zenWorkspaceSwitch3","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-2","key":"j","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-2","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_zenWorkspaceSwitch2","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-switch-1","key":"h","keycode":"","group":"zen-workspace","l10nId":"zen-workspace-shortcut-switch-1","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_zenWorkspaceSwitch1","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-forward","key":"","keycode":"VK_RIGHT","group":"zen-workspace","l10nId":"zen-workspace-shortcut-forward","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":"cmd_zenWorkspaceForward","disabled":false,"reserved":false,"internal":false},{"id":"zen-workspace-backward","key":"","keycode":"VK_LEFT","group":"zen-workspace","l10nId":"zen-workspace-shortcut-backward","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":"cmd_zenWorkspaceBackward","disabled":false,"reserved":false,"internal":false},{"id":"zen-split-view-grid","key":"g","keycode":"","group":"zen-split-view","l10nId":"zen-split-view-shortcut-grid","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":"cmd_zenSplitViewGrid","disabled":false,"reserved":false,"internal":false},{"id":"zen-split-view-vertical","key":"v","keycode":"","group":"zen-split-view","l10nId":"zen-split-view-shortcut-vertical","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":"cmd_zenSplitViewVertical","disabled":false,"reserved":false,"internal":false},{"id":"zen-split-view-horizontal","key":"h","keycode":"","group":"zen-split-view","l10nId":"zen-split-view-shortcut-horizontal","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":"cmd_zenSplitViewHorizontal","disabled":false,"reserved":false,"internal":false},{"id":"zen-split-view-unsplit","key":"u","keycode":"","group":"zen-split-view","l10nId":"zen-split-view-shortcut-unsplit","modifiers":{"control":false,"alt":true,"shift":false,"meta":false,"accel":true},"action":"cmd_zenSplitViewUnsplit","disabled":false,"reserved":false,"internal":false},{"id":"zen-pinned-tab-reset-shortcut","key":"","keycode":"","group":"zen-other","l10nId":"zen-pinned-tab-shortcut-reset","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_zenPinnedTabReset","disabled":false,"reserved":false,"internal":false},{"id":"zen-toggle-sidebar","key":"","keycode":"","group":"zen-other","l10nId":"zen-sidebar-shortcut-toggle","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":false},"action":"cmd_zenToggleSidebar","disabled":false,"reserved":false,"internal":false},{"id":"zen-copy-url","key":"c","keycode":"","group":"zen-other","l10nId":"zen-text-action-copy-url-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_zenCopyCurrentURL","disabled":false,"reserved":false,"internal":false},{"id":"zen-copy-url-markdown","key":"c","keycode":"","group":"zen-other","l10nId":"zen-text-action-copy-url-markdown-shortcut","modifiers":{"control":false,"alt":true,"shift":true,"meta":false,"accel":true},"action":"cmd_zenCopyCurrentURLMarkdown","disabled":false,"reserved":false,"internal":false},{"id":"zen-toggle-pin-tab","key":"","keycode":"","group":"zen-other","l10nId":"zen-toggle-pin-tab-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":"cmd_zenTogglePinTab","disabled":false,"reserved":false,"internal":false},{"id":"zen-glance-expand","key":"o","keycode":"","group":"zen-other","l10nId":"","modifiers":{"control":false,"alt":false,"shift":false,"meta":false,"accel":true},"action":"cmd_zenGlanceExpand","disabled":false,"reserved":false,"internal":false},{"id":"zen-new-empty-split-view","key":"*","keycode":"","group":"zen-split-view","l10nId":"zen-new-empty-split-view-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_zenNewEmptySplit","disabled":false,"reserved":false,"internal":false},{"id":"zen-close-all-unpinned-tabs","key":"","keycode":"","group":"zen-workspace","l10nId":"zen-close-all-unpinned-tabs-shortcut","modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":"cmd_zenCloseUnpinnedTabs","disabled":false,"reserved":false,"internal":false},{"id":"zen-new-unsynced-window","key":"n","keycode":"","group":"zen-other","l10nId":"zen-new-unsynced-window-shortcut","modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":"cmd_zenNewNavigatorUnsynced","disabled":false,"reserved":false,"internal":false},{"id":"key_accessibility","keycode":"VK_F12","group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_dom","key":"w","keycode":null,"group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_storage","keycode":"VK_F9","group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_performance","keycode":"VK_F5","group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_styleeditor","keycode":"VK_F7","group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_netmonitor","key":"e","keycode":null,"group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_jsdebugger","key":"z","keycode":null,"group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_webconsole","key":"","keycode":"","group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_inspector","key":"","keycode":"","group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_responsiveDesignMode","key":"m","keycode":null,"group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_browserConsole","key":"","keycode":"","group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":false,"meta":false},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_browserToolbox","key":"i","keycode":null,"group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":true,"shift":true,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false},{"id":"key_toggleToolbox","key":"i","keycode":null,"group":"devTools","l10nId":null,"modifiers":{"control":false,"alt":false,"shift":true,"meta":false,"accel":true},"action":null,"disabled":false,"reserved":false,"internal":false}]} \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..cbd096e --- /dev/null +++ b/flake.lock @@ -0,0 +1,195 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "neovim-nightly", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769397130, + "narHash": "sha256-TTM4KV9IHwa181X7afBRbhLJIrgynpDjAXJFMUOWfyU=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "c37679d37bdbecf11bbe3c5eb238d89ca4f60641", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "home-manager_2": { + "inputs": { + "nixpkgs": [ + "zen-browser", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769872935, + "narHash": "sha256-07HMIGQ/WJeAQJooA7Kkg1SDKxhAiV6eodvOwTX6WKI=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "f4ad5068ee8e89e4a7c2e963e10dd35cd77b37b7", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "neovim-nightly": { + "inputs": { + "flake-parts": "flake-parts", + "neovim-src": "neovim-src", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1770336287, + "narHash": "sha256-czvrg8uyf2VWRmbobsthTAIJCg1GH4mEekyW01AvHco=", + "owner": "nix-community", + "repo": "neovim-nightly-overlay", + "rev": "1cd999cdf20536ac6a6d1aa17ba0242eefd2312b", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "neovim-nightly-overlay", + "type": "github" + } + }, + "neovim-src": { + "flake": false, + "locked": { + "lastModified": 1770334851, + "narHash": "sha256-FvT3T0l8eNr1Hv+D1Sj1jM/2vLkonLxpadTk6gdYHAo=", + "owner": "neovim", + "repo": "neovim", + "rev": "db133879b2a115cdf982b2899f154f1851d59a60", + "type": "github" + }, + "original": { + "owner": "neovim", + "repo": "neovim", + "type": "github" + } + }, + "nixos-hardware": { + "locked": { + "lastModified": 1769302137, + "narHash": "sha256-QEDtctEkOsbx8nlFh4yqPEOtr4tif6KTqWwJ37IM2ds=", + "owner": "NixOS", + "repo": "nixos-hardware", + "rev": "a351494b0e35fd7c0b7a1aae82f0afddf4907aa8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixos-hardware", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1770169770, + "narHash": "sha256-awR8qIwJxJJiOmcEGgP2KUqYmHG4v/z8XpL9z8FnT1A=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "aa290c9891fa4ebe88f8889e59633d20cc06a5f2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1770169770, + "narHash": "sha256-awR8qIwJxJJiOmcEGgP2KUqYmHG4v/z8XpL9z8FnT1A=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "aa290c9891fa4ebe88f8889e59633d20cc06a5f2", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1769461804, + "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "home-manager": "home-manager", + "neovim-nightly": "neovim-nightly", + "nixos-hardware": "nixos-hardware", + "nixpkgs": "nixpkgs_2", + "zen-browser": "zen-browser" + } + }, + "zen-browser": { + "inputs": { + "home-manager": "home-manager_2", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1770382887, + "narHash": "sha256-on4vg7ctpMPzKWcvXPtV095aal6KUPDSKV9+I8HhQtY=", + "owner": "0xc000022070", + "repo": "zen-browser-flake", + "rev": "58aa8fb418e2853382d52453a6a7739125f2b8e0", + "type": "github" + }, + "original": { + "owner": "0xc000022070", + "repo": "zen-browser-flake", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..32b1a2c --- /dev/null +++ b/flake.nix @@ -0,0 +1,57 @@ +{ + description = "Barrett Ruth's Nix Configuration"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + nixos-hardware.url = "github:NixOS/nixos-hardware"; + neovim-nightly.url = "github:nix-community/neovim-nightly-overlay"; + zen-browser.url = "github:0xc000022070/zen-browser-flake"; + }; + + outputs = { nixpkgs, home-manager, nixos-hardware, neovim-nightly, zen-browser, ... }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { + inherit system; + config.allowUnfreePredicate = pkg: builtins.elem (nixpkgs.lib.getName pkg) [ + "slack" + "claude-code" + "nvidia-x11" + "nvidia-settings" + ]; + overlays = [ neovim-nightly.overlays.default ]; + }; + in { + nixosConfigurations.xps15 = nixpkgs.lib.nixosSystem { + inherit system; + modules = [ + nixos-hardware.nixosModules.dell-xps-15-9500-nvidia + ./hosts/xps15/configuration.nix + home-manager.nixosModules.home-manager + { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.users.barrett = import ./home/home.nix; + home-manager.extraSpecialArgs = { + inherit zen-browser system; + }; + } + ]; + specialArgs = { + inherit nixpkgs; + }; + }; + + homeConfigurations.barrett = home-manager.lib.homeManagerConfiguration { + inherit pkgs; + extraSpecialArgs = { + inherit zen-browser system; + }; + modules = [ ./home/home.nix ]; + }; + }; +} diff --git a/home/home.nix b/home/home.nix new file mode 100644 index 0000000..314e005 --- /dev/null +++ b/home/home.nix @@ -0,0 +1,33 @@ +{ lib, config, pkgs, ... }: + +let + isNixOS = builtins.pathExists /etc/NIXOS; +in { + imports = [ + ./modules/theme.nix + ./modules/shell.nix + ./modules/terminal.nix + ./modules/git.nix + ./modules/editor.nix + ./modules/ui.nix + ./modules/packages.nix + ]; + + config = { + theme = "midnight"; + + home.username = "barrett"; + home.homeDirectory = "/home/${config.home.username}"; + home.stateVersion = "24.11"; + + xdg.enable = true; + targets.genericLinux.enable = !isNixOS; + news.display = "silent"; + + home.file.".local/bin/scripts" = { + source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nix-config/scripts"; + }; + + programs.home-manager.enable = true; + }; +} diff --git a/home/modules/editor.nix b/home/modules/editor.nix new file mode 100644 index 0000000..baf442f --- /dev/null +++ b/home/modules/editor.nix @@ -0,0 +1,17 @@ +{ pkgs, config, ... }: + +{ + home.sessionVariables = { + EDITOR = "nvim"; + MANPAGER = "nvim +Man!"; + }; + + programs.neovim = { + enable = true; + defaultEditor = true; + viAlias = true; + vimAlias = true; + }; + + xdg.configFile."nvim".source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nix-config/config/nvim"; +} diff --git a/home/modules/git.nix b/home/modules/git.nix new file mode 100644 index 0000000..9385038 --- /dev/null +++ b/home/modules/git.nix @@ -0,0 +1,95 @@ +{ pkgs, config, ... }: + +{ + programs.git = { + enable = true; + lfs.enable = true; + + ignores = [ + "*.swp" + "*.swo" + "*~" + ".vscode/" + ".idea/" + ".DS_Store" + "Thumbs.db" + "*.o" + "*.a" + "*.so" + "*.pyc" + "__pycache__/" + "node_modules/" + "target/" + "dist/" + "build/" + "out/" + "*.class" + "*.log" + ".env" + ".env.local" + ".envrc" + "venv/" + ".mypy_cache/" + "result" + "result-*" + ".claude/settings.local.json" + ]; + + settings = { + user = { + name = "Barrett Ruth"; + email = "br@barrettruth.com"; + }; + alias = { + a = "add"; + b = "branch"; + c = "commit"; + acp = "!acp() { git add . && git commit -m \"$*\" && git push; }; acp"; + cane = "commit --amend --no-edit"; + cf = "config"; + ch = "checkout"; + cl = "clone"; + cp = "cherry-pick"; + d = "diff"; + dt = "difftool"; + f = "fetch"; + i = "init"; + lg = "log --oneline --graph --decorate"; + m = "merge"; + p = "pull"; + pu = "push"; + r = "remote"; + rb = "rebase"; + rs = "restore"; + rt = "reset"; + s = "status"; + sm = "submodule"; + st = "stash"; + sw = "switch"; + wt = "worktree"; + }; + init.defaultBranch = "main"; + core = { + editor = "nvim"; + whitespace = "fix,-indent-with-non-tab,trailing-space,cr-at-eol"; + }; + color.ui = "auto"; + diff.tool = "codediff"; + difftool.prompt = false; + difftool.codediff.cmd = "nvim -c 'CodeDiff' $LOCAL $REMOTE"; + merge.tool = "codediff"; + mergetool.prompt = false; + mergetool.codediff.cmd = "nvim -c 'CodeDiff' $LOCAL $REMOTE $MERGED"; + push.autoSetupRemote = true; + credential.helper = "cache"; + }; + }; + + programs.gh = { + enable = true; + settings = { + git_protocol = "ssh"; + prompt = "enabled"; + }; + }; +} diff --git a/home/modules/packages.nix b/home/modules/packages.nix new file mode 100644 index 0000000..050437b --- /dev/null +++ b/home/modules/packages.nix @@ -0,0 +1,92 @@ +{ pkgs, lib, config, zen-browser, system, ... }: + +let + zen = true; + sioyek = true; + vesktop = true; + neovim = config.programs.neovim.enable; +in { + home.sessionVariables = lib.optionalAttrs zen { + BROWSER = "zen-browser"; + }; + + programs.mpv.enable = true; + + home.packages = with pkgs; [ + signal-desktop + slack + bitwarden-desktop + claude-code + ] + ++ lib.optionals zen [ zen-browser.packages.${system}.default ] + ++ lib.optionals sioyek [ pkgs.sioyek ] + ++ lib.optionals vesktop [ pkgs.vesktop ]; + + home.activation.linkZenProfile = lib.mkIf zen ( + lib.hm.dag.entryAfter [ "writeBoundary" ] '' + zen_config="$HOME/.zen" + repo_zen="${config.home.homeDirectory}/nix-config/config/zen" + + if [ ! -d "$zen_config" ]; then + exit 0 + fi + + profile="" + for d in "$zen_config"/*.Default\ \(release\); do + [ -d "$d" ] && profile="$d" && break + done + + if [ -z "$profile" ]; then + exit 0 + fi + + mkdir -p "$profile/chrome" + + for f in userChrome.css user.js containers.json handlers.json zen-keyboard-shortcuts.json; do + src="$repo_zen/$f" + if [ "$f" = "userChrome.css" ]; then + dest="$profile/chrome/$f" + else + dest="$profile/$f" + fi + + [ -f "$src" ] || continue + + if [ -L "$dest" ]; then + continue + fi + + if [ -f "$dest" ]; then + rm "$dest" + fi + + ln -s "$src" "$dest" + done + '' + ); + + xdg.configFile."electron-flags.conf".text = '' + --enable-features=WaylandWindowDecorations + --ozone-platform-hint=auto + ''; + + xdg.mimeApps = { + enable = true; + defaultApplications = {} + // lib.optionalAttrs zen { + "x-scheme-handler/http" = "zen.desktop"; + "x-scheme-handler/https" = "zen.desktop"; + "text/html" = "zen.desktop"; + } + // lib.optionalAttrs neovim { + "text/plain" = "nvim.desktop"; + } + // lib.optionalAttrs sioyek { + "application/pdf" = "sioyek.desktop"; + "application/epub" = "sioyek.desktop"; + } + // lib.optionalAttrs vesktop { + "x-scheme-handler/discord" = "vesktop.desktop"; + }; + }; +} diff --git a/home/modules/shell.nix b/home/modules/shell.nix new file mode 100644 index 0000000..cc70578 --- /dev/null +++ b/home/modules/shell.nix @@ -0,0 +1,504 @@ +{ pkgs, lib, config, ... }: + +let + c = config.colors; + isNixOS = builtins.pathExists /etc/NIXOS; + + ripgrep = config.programs.ripgrep.enable; + + rust = true; + go = true; + node = true; + python = true; + ocaml = true; + docker = true; + aws = true; + psql = true; + sqlite = true; +in { + home.packages = with pkgs; [ + pure-prompt + xclip + tree + jq + curl + wget + unzip + tesseract + gnumake + gcc + file + ffmpeg + poppler-utils + librsvg + imagemagick + ]; + + home.sessionVariables = { + LESSHISTFILE = "-"; + } + // lib.optionalAttrs ripgrep { + RIPGREP_CONFIG_PATH = "${config.xdg.configHome}/rg/config"; + } + // lib.optionalAttrs rust { + CARGO_HOME = "${config.xdg.dataHome}/cargo"; + RUSTUP_HOME = "${config.xdg.dataHome}/rustup"; + } + // lib.optionalAttrs go { + GOPATH = "${config.xdg.dataHome}/go"; + GOMODCACHE = "${config.xdg.cacheHome}/go/mod"; + } + // lib.optionalAttrs node { + NPM_CONFIG_USERCONFIG = "${config.xdg.configHome}/npm/npmrc"; + NODE_REPL_HISTORY = "${config.xdg.stateHome}/node_repl_history"; + PNPM_HOME = "${config.xdg.dataHome}/pnpm"; + } + // lib.optionalAttrs python { + PYTHONSTARTUP = "${config.xdg.configHome}/python/pythonrc"; + PYTHON_HISTORY = "${config.xdg.stateHome}/python_history"; + PYTHONPYCACHEPREFIX = "${config.xdg.cacheHome}/python"; + PYTHONUSERBASE = "${config.xdg.dataHome}/python"; + } + // lib.optionalAttrs ocaml { + OPAMROOT = "${config.xdg.dataHome}/opam"; + } + // lib.optionalAttrs docker { + DOCKER_CONFIG = "${config.xdg.configHome}/docker"; + } + // lib.optionalAttrs aws { + AWS_SHARED_CREDENTIALS_FILE = "${config.xdg.configHome}/aws/credentials"; + AWS_CONFIG_FILE = "${config.xdg.configHome}/aws/config"; + } + // lib.optionalAttrs psql { + PSQL_HISTORY = "${config.xdg.stateHome}/psql_history"; + } + // lib.optionalAttrs sqlite { + SQLITE_HISTORY = "${config.xdg.stateHome}/sqlite_history"; + }; + + home.sessionPath = [ + "${config.home.homeDirectory}/.local/bin" + "${config.home.homeDirectory}/.local/bin/scripts" + ] + ++ lib.optionals rust [ "${config.xdg.dataHome}/cargo/bin" ] + ++ lib.optionals go [ "${config.xdg.dataHome}/go/bin" ] + ++ lib.optionals node [ "${config.xdg.dataHome}/pnpm" ]; + + xdg.configFile."npm/npmrc" = lib.mkIf node { + text = '' + prefix=''${XDG_DATA_HOME}/npm + cache=''${XDG_CACHE_HOME}/npm + init-module=''${XDG_CONFIG_HOME}/npm/config/npm-init.js + ''; + }; + + xdg.configFile."python/pythonrc" = lib.mkIf python { + text = '' + import atexit + import os + import readline + + history = os.path.join(os.environ.get('XDG_STATE_HOME', os.path.expanduser('~/.local/state')), 'python_history') + + try: + readline.read_history_file(history) + except OSError: + pass + + def write_history(): + try: + readline.write_history_file(history) + except OSError: + pass + + atexit.register(write_history) + ''; + }; + + xdg.configFile."rg/config" = lib.mkIf ripgrep { + text = '' + --column + --no-heading + --smart-case + --no-follow + --glob=!pnpm-lock.yaml + --glob=!*.json + --glob=!venv/ + --glob=!pyenv/ + --ignore-file=${config.xdg.configHome}/git/ignore + --no-messages + --color=auto + --colors=line:style:nobold + --colors=line:fg:242 + --colors=match:fg:green + --colors=match:style:bold + --colors=path:fg:blue + ''; + }; + + programs.zsh = { + enable = true; + dotDir = "${config.xdg.configHome}/zsh"; + + history = { + path = "${config.xdg.stateHome}/zsh_history"; + size = 2000; + save = 2000; + ignoreDups = true; + ignoreAllDups = true; + ignoreSpace = true; + extended = true; + append = true; + }; + + shellAliases = { + ls = "eza"; + l = "ls --color=auto --group-directories-first"; + ll = "l -alF"; + la = "ll -R"; + g = "git"; + nv = "nvim"; + pe = "printenv"; + }; + + completionInit = '' + autoload -U compinit && compinit -d "$XDG_STATE_HOME/zcompdump" -u + zmodload zsh/complist + zstyle ':completion:*' list-colors ''${(s.:.)LS_COLORS} + zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-za-z}' + ''; + + initContent = '' + export THEME="''${THEME:-${config.theme}}" + + setopt auto_cd + unsetopt beep notify + unset completealiases + + bindkey -v + bindkey '^[[3~' delete-char + bindkey '^P' up-line-or-history + bindkey '^N' down-line-or-history + bindkey '^J' backward-char + bindkey '^K' forward-char + + export PURE_PROMPT_SYMBOL=">" + export PURE_PROMPT_VICMD_SYMBOL="<" + export PURE_GIT_UP_ARROW="^" + export PURE_GIT_DOWN_ARROW="v" + export PURE_GIT_STASH_SYMBOL="=" + export PURE_CMD_MAX_EXEC_TIME=5 + export PURE_GIT_PULL=0 + export PURE_GIT_UNTRACKED_DIRTY=1 + zstyle ':prompt:pure:git:stash' show yes + + fpath+=("${pkgs.pure-prompt}/share/zsh/site-functions") + autoload -Uz promptinit && promptinit + prompt pure + + autoload -Uz add-zle-hook-widget + function _cursor_shape() { + case $KEYMAP in + vicmd) echo -ne '\e[2 q' ;; + viins|main) echo -ne '\e[6 q' ;; + esac + } + function _cursor_init() { echo -ne '\e[6 q'; } + add-zle-hook-widget zle-keymap-select _cursor_shape + add-zle-hook-widget zle-line-init _cursor_init + + export FZF_COMPLETION_TRIGGER=\; + export FZF_TMUX=1 + + fzf-config-widget() { + file="$(fd --type file --hidden . ~/.config | sed "s|$HOME|~|g" | fzf)" + [ -n "$file" ] || { zle reset-prompt; return; } + file="${file/#\~/$HOME}" + BUFFER="nvim $file" + zle accept-line + } + zle -N fzf-config-widget + bindkey '^E' fzf-config-widget + + '' + lib.optionalString ocaml '' + [[ ! -r "$OPAMROOT/opam-init/init.zsh" ]] || source "$OPAMROOT/opam-init/init.zsh" > /dev/null 2> /dev/null + ''; + }; + + programs.fzf = { + enable = true; + enableZshIntegration = true; + defaultCommand = "rg --files --hidden"; + defaultOptions = [ + "--bind=ctrl-a:select-all" + "--bind=ctrl-f:half-page-down" + "--bind=ctrl-b:half-page-up" + "--no-scrollbar" + "--no-info" + "--color=fg:${c.fg},bg:${c.bg},hl:${c.accent}" + "--color=fg+:${c.fg},bg+:${c.bgAlt},hl+:${c.accent}" + "--color=info:${c.green},prompt:${c.accent},pointer:${c.fg},marker:${c.green},spinner:${c.fg}" + ]; + changeDirWidgetCommand = "fd --type d --hidden"; + fileWidgetCommand = "rg --files --hidden"; + historyWidgetOptions = [ "--reverse" ]; + }; + + programs.eza = { + enable = true; + enableZshIntegration = false; + git = true; + }; + + programs.zoxide = { + enable = true; + enableZshIntegration = true; + }; + + programs.direnv = { + enable = true; + enableZshIntegration = true; + nix-direnv.enable = true; + stdlib = '' + layout_uv() { + if [[ ! -d .venv ]]; then + uv venv + fi + source .venv/bin/activate + } + ''; + }; + + programs.ripgrep.enable = true; + + programs.fd = { + enable = true; + hidden = true; + ignores = [ + ".git/" + "node_modules/" + "target/" + "venv/" + ]; + }; + + programs.tmux = { + enable = true; + shortcut = "x"; + keyMode = "vi"; + mouse = true; + escapeTime = 0; + historyLimit = 50000; + baseIndex = 1; + aggressiveResize = true; + focusEvents = true; + sensibleOnTop = false; + + plugins = with pkgs.tmuxPlugins; [ + resurrect + { + plugin = continuum; + extraConfig = '' + set -g @continuum-restore 'on' + set -g @continuum-save-interval '10' + ''; + } + ]; + + extraConfig = '' + set -g prefix M-x + unbind C-b + bind M-x send + + set -g default-terminal "$TERM" + set -g default-shell "$SHELL" + + set -g renumber-windows on + set -g pane-base-index 1 + + set -g status-position bottom + set -g status-interval 5 + set -g status-left ' ' + set -g status-right '' + set-hook -g session-created 'run "mux bar #S"' + set-hook -g session-closed 'run "mux bar #S"' + set-hook -g client-session-changed 'run "mux bar #S"' + + set -g status-bg '${c.bg}' + set -g status-fg '${c.fg}' + set -g window-status-style fg='${c.fg}' + set -g window-status-current-style fg='${c.fg}' + set -g window-status-bell-style fg='${c.bellFg}',bg='${c.bg}',bold + set -g window-status-activity-style fg='${c.activityFg}',bg='${c.bg}',bold + set -g pane-border-style fg='${c.border}' + set -g pane-active-border-style fg='${c.fg}' + + set -as terminal-features ",$TERM:RGB" + set -as terminal-overrides ",*:U8=1" + set -as terminal-overrides ',*:Smulx=\E[4::%p1%dm' + set -as terminal-overrides ',*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m' + + unbind Left; bind h selectp -L + unbind Down; bind j selectp -D + unbind Up; bind k selectp -U + unbind Right; bind l selectp -R + + unbind m; bind m choose-tree -Z "join-pane -t '%%'" + unbind n; bind n break-pane + unbind p; bind p choose-tree -Z "join-pane -s '%%'" + + bind -r Left resizep -L 5 + bind -r Right resizep -R 5 + bind -r Up resizep -U 5 + bind -r Down resizep -D 5 + + unbind c; bind c neww -c '#{pane_current_path}' + unbind \'; bind \' splitw -hc '#{pane_current_path}' + unbind \-; bind \- splitw -vc '#{pane_current_path}' + + unbind y; bind y copy-mode + + bind -T copy-mode-vi v send -X begin-selection + bind -T copy-mode-vi y send -X copy-pipe-and-cancel 'xclip -in -sel c' + + unbind C-b; bind C-b set status + unbind C-m; bind C-m set mouse\; run 'mux bar #S' + + unbind e; bind e neww -n 'tmux.conf' "sh -c 'nvim $XDG_CONFIG_HOME/tmux/tmux.conf && tmux source $XDG_CONFIG_HOME/tmux/tmux.conf'" + + unbind H; bind H run 'mux switch 0'\; run 'mux bar #S' + unbind J; bind J run 'mux switch 1'\; run 'mux bar #S' + unbind K; bind K run 'mux switch 2'\; run 'mux bar #S' + unbind L; bind L run 'mux switch 3'\; run 'mux bar #S' + unbind \$; bind \$ run 'mux switch 4'\; run 'mux bar #S' + + unbind Tab; bind Tab switchc -l + + set-hook -g client-light-theme 'source ${config.xdg.configHome}/tmux/themes/daylight.conf' + set-hook -g client-dark-theme 'source ${config.xdg.configHome}/tmux/themes/midnight.conf' + + unbind N; bind N run 'mux nvim' + unbind C; bind C run 'mux claude' + unbind R; bind R run 'mux run' + unbind T; bind T run 'mux term' + unbind G; bind G run 'mux git' + + set -g lock-after-time 300 + set -g lock-command "pipes -p 2" + + set -g @resurrect-capture-pane-contents on + ''; + }; + + xdg.configFile."tmux/themes/midnight.conf".text = '' + set -g status-bg '#121212' + set -g status-fg '#e0e0e0' + set -g window-status-style fg='#e0e0e0' + set -g window-status-current-style fg='#e0e0e0' + set -g window-status-bell-style fg='#ff6b6b',bg='#121212',bold + set -g window-status-activity-style fg='#7aa2f7',bg='#121212',bold + set -g pane-border-style fg='#3d3d3d' + set -g pane-active-border-style fg='#e0e0e0' + ''; + + xdg.configFile."tmux/themes/daylight.conf".text = '' + set -g status-bg '#f5f5f5' + set -g status-fg '#1a1a1a' + set -g window-status-style fg='#1a1a1a' + set -g window-status-current-style fg='#1a1a1a' + set -g window-status-bell-style fg='#c7254e',bg='#f5f5f5',bold + set -g window-status-activity-style fg='#3b5bdb',bg='#f5f5f5',bold + set -g pane-border-style fg='#e8e8e8' + set -g pane-active-border-style fg='#1a1a1a' + ''; + + programs.lf = { + enable = true; + settings = { + drawbox = true; + number = true; + relativenumber = true; + hidden = true; + shell = "zsh"; + icons = false; + incsearch = true; + scrolloff = 4; + tabstop = 2; + smartcase = true; + dircounts = true; + info = "size"; + ratios = "1:2:3"; + timefmt = "2006-01-02 15:04:05 -0700"; + previewer = "~/.config/lf/previewer"; + cleaner = "~/.config/lf/cleaner"; + }; + + commands = { + open = ''$${{ + setsid -f xdg-open "$f" 2>/dev/null 2>&1 & + }}''; + sopen = ''$${{ + for f in $fx; do + setsid -f xdg-open "$f" >/dev/null 2>&1 & + done + }}''; + rmd = ''$${{ + set -f + while IFS= read -r dir; do + rmdir -v -- "$dir" + done <<< "$fx" + }}''; + rmf = ''$${{ + set -f + while IFS= read -r file; do + rm -v -- "$file" + done <<< "$fx" + }}''; + resize = ''%{{ + w=$(tmux display-message -p '#{pane_width}' || tput cols) + if [ $w -le 62 ]; then + lf -remote "send $id set ratios 1:4" + lf -remote "send $id set nopreview" + elif [ $w -le 80 ]; then + lf -remote "send $id set ratios 1:2:2" + elif [ $w -le 100 ]; then + lf -remote "send $id set ratios 1:2:3" + else + lf -remote "send $id set ratios 2:4:5" + fi + }}''; + on-init = ''%{{ + lf -remote "send $id resize" + }}''; + }; + + keybindings = { + "" = ":sopen; quit"; + "." = "set hidden!"; + "ad" = "push $mkdir"; + "af" = "push $touch"; + "xd" = "rmd"; + "xf" = "rmf"; + "H" = "jump-prev"; + "L" = "jump-next"; + "" = ''$lf -remote "send $id select $(fzf)"''; + "zz" = "push :z"; + }; + + extraConfig = '' + set shellopts '-eu' + set ifs "\n" + ''; + }; + + xdg.configFile."lf/previewer" = { + source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nix-config/config/lf/previewer"; + }; + + xdg.configFile."lf/cleaner" = { + source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nix-config/config/lf/cleaner"; + }; + + xdg.configFile."lf/lf.lua".source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nix-config/config/lf/lf.lua"; + xdg.configFile."lf/sort.py".source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nix-config/config/lf/sort.py"; +} diff --git a/home/modules/terminal.nix b/home/modules/terminal.nix new file mode 100644 index 0000000..45dae67 --- /dev/null +++ b/home/modules/terminal.nix @@ -0,0 +1,95 @@ +{ pkgs, config, ... }: + +let + c = config.colors; +in { + home.sessionVariables = { + TERMINAL = "ghostty"; + TERM = "xterm-ghostty"; + }; + + programs.ghostty = { + enable = true; + settings = { + font-family = "Berkeley Mono"; + font-feature = "-calt"; + font-size = 20; + adjust-cell-height = "10%"; + + theme = "dark:midnight,light:daylight"; + + cursor-style-blink = false; + shell-integration-features = "no-cursor"; + + window-decoration = false; + window-padding-x = 0; + window-padding-y = 0; + window-padding-color = "background"; + app-notifications = "no-clipboard-copy,no-config-reload"; + resize-overlay = "never"; + mouse-scroll-multiplier = 0.5; + quit-after-last-window-closed = true; + confirm-close-surface = false; + + keybind = [ + "clear" + "alt+r=reload_config" + "alt+y=copy_to_clipboard" + "alt+p=paste_from_clipboard" + "alt+shift+h=decrease_font_size:1" + "alt+shift+l=increase_font_size:1" + "shift+enter=text:\\n" + ]; + }; + }; + + xdg.configFile."ghostty/themes/midnight".text = '' + palette = 0=#121212 + palette = 1=#ff6b6b + palette = 2=#98c379 + palette = 3=#e5c07b + palette = 4=#7aa2f7 + palette = 5=#c678dd + palette = 6=#56b6c2 + palette = 7=#e0e0e0 + palette = 8=#666666 + palette = 9=#f48771 + palette = 10=#b5e890 + palette = 11=#f0d197 + palette = 12=#9db8f7 + palette = 13=#e298ff + palette = 14=#7dd6e0 + palette = 15=#ffffff + background = #121212 + foreground = #e0e0e0 + cursor-color = #ff6b6b + cursor-text = #121212 + selection-background = #2d2d2d + selection-foreground = #e0e0e0 + ''; + + xdg.configFile."ghostty/themes/daylight".text = '' + palette = 0=#f5f5f5 + palette = 1=#c7254e + palette = 2=#2d7f3e + palette = 3=#996800 + palette = 4=#3b5bdb + palette = 5=#ae3ec9 + palette = 6=#1098ad + palette = 7=#1a1a1a + palette = 8=#999999 + palette = 9=#e03e52 + palette = 10=#37b24d + palette = 11=#f59f00 + palette = 12=#4c6ef5 + palette = 13=#da77f2 + palette = 14=#15aabf + palette = 15=#000000 + background = #f5f5f5 + foreground = #1a1a1a + cursor-color = #e03e52 + cursor-text = #f5f5f5 + selection-background = #ebebeb + selection-foreground = #1a1a1a + ''; +} diff --git a/home/modules/theme.nix b/home/modules/theme.nix new file mode 100644 index 0000000..e933bda --- /dev/null +++ b/home/modules/theme.nix @@ -0,0 +1,50 @@ +{ lib, config, ... }: + +let + palettes = { + midnight = { + bg = "#121212"; + fg = "#e0e0e0"; + bgAlt = "#2d2d2d"; + fgAlt = "#666666"; + border = "#3d3d3d"; + accent = "#7aa2f7"; + green = "#98c379"; + red = "#ff6b6b"; + yellow = "#e5c07b"; + blue = "#7aa2f7"; + magenta = "#c678dd"; + cyan = "#56b6c2"; + bellFg = "#ff6b6b"; + activityFg = "#7aa2f7"; + }; + daylight = { + bg = "#f5f5f5"; + fg = "#1a1a1a"; + bgAlt = "#ebebeb"; + fgAlt = "#999999"; + border = "#e8e8e8"; + accent = "#3b5bdb"; + green = "#2d7f3e"; + red = "#c7254e"; + yellow = "#996800"; + blue = "#3b5bdb"; + magenta = "#ae3ec9"; + cyan = "#1098ad"; + bellFg = "#c7254e"; + activityFg = "#3b5bdb"; + }; + }; +in { + options.theme = lib.mkOption { + type = lib.types.enum [ "midnight" "daylight" ]; + default = "midnight"; + }; + + options.colors = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + readOnly = true; + }; + + config.colors = palettes.${config.theme}; +} diff --git a/home/modules/ui.nix b/home/modules/ui.nix new file mode 100644 index 0000000..73d9c82 --- /dev/null +++ b/home/modules/ui.nix @@ -0,0 +1,504 @@ +{ pkgs, lib, config, ... }: + +let + isNixOS = builtins.pathExists /etc/NIXOS; + c = config.colors; + + nvidia = true; + backlightDevice = "intel_backlight"; +in { + home.packages = with pkgs; [ + wl-clipboard + cliphist + grim + slurp + libnotify + brightnessctl + pamixer + xorg.xinit + xorg.xmodmap + xorg.xrdb + ]; + + wayland.windowManager.hyprland = { + enable = true; + package = lib.mkIf (!isNixOS) null; + portalPackage = lib.mkIf (!isNixOS) null; + systemd.enable = isNixOS; + + extraConfig = '' + exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=Hyprland THEME + exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=Hyprland THEME + + monitor=,preferred,auto,1 + + env = XDG_CURRENT_DESKTOP,Hyprland + env = XDG_SESSION_TYPE,wayland + env = ELECTRON_OZONE_PLATFORM_HINT,wayland + env = GTK_USE_PORTAL,1 + env = OZONE_PLATFORM,wayland + env = QT_QPA_PLATFORM,wayland + env = GDK_BACKEND,wayland,x11 + env = SDL_VIDEODRIVER,wayland + '' + lib.optionalString nvidia '' + env = LIBVA_DRIVER_NAME,nvidia + env = __GLX_VENDOR_LIBRARY_NAME,nvidia + env = NVD_BACKEND,direct + env = GBM_BACKEND,nvidia-drm + env = GSK_RENDERER,ngl + env = __NV_PRIME_RENDER_OFFLOAD,1 + env = __VK_LAYER_NV_optimus,NVIDIA_only + '' + '' + + env = XCURSOR_SIZE,24 + env = HYPRCURSOR_SIZE,24 + '' + lib.optionalString nvidia '' + cursor { + no_hardware_cursors = true + } + '' + '' + + general { + gaps_in = 0 + gaps_out = 0 + border_size = 5 + col.active_border = rgb(${builtins.substring 1 6 c.fg}) + col.inactive_border = rgb(${builtins.substring 1 6 c.bg}) + layout = master + resize_on_border = true + } + + master { + new_status = slave + new_on_top = false + mfact = 0.50 + } + + decoration { + rounding = 0 + active_opacity = 1.0 + inactive_opacity = 1.0 + blur { + enabled = false + } + } + + animations { + enabled = false + } + + input { + kb_layout = us,us + kb_variant = ,colemak + follow_mouse = 1 + sensitivity = 0 + touchpad { + tap-to-click = false + } + repeat_delay = 300 + repeat_rate = 50 + } + + exec-once = dunst + exec-once = wl-paste --watch cliphist store + exec-once = hyprpaper + exec-once = hypridle + exec-once = hypr spawnfocus --ws 1 $TERMINAL -e mux + exec-once = hypr spawnfocus --ws 2 $BROWSER + + bindul = , XF86AudioRaiseVolume, exec, hypr volume up + bindul = , XF86AudioLowerVolume, exec, hypr volume down + bindul = , XF86AudioMute, exec, hypr volume toggle + + bindul = , XF86MonBrightnessUp, exec, hypr brightness up + bindul = , XF86MonBrightnessDown, exec, hypr brightness down + + bindu = ALT, SPACE, exec, rofi -show run + bindu = ALT, TAB, workspace, previous + bindu = ALT, A, cyclenext + bindu = ALT, B, exec, pkill -USR1 waybar || waybar + bindu = ALT, D, layoutmsg, swapprev + bindu = ALT, F, cyclenext, prev + bindu = ALT, H, resizeactive, -15 0 + bindu = ALT, J, resizeactive, 0 15 + bindu = ALT, K, resizeactive, 0 -15 + bindu = ALT, L, resizeactive, 15 0 + bindu = ALT, Q, killactive, + bindu = ALT, U, layoutmsg, swapnext + + bindu = ALT CTRL, B, exec, hypr pull bitwarden-desktop + bindu = ALT CTRL, C, exec, hypr pull $BROWSER + bindu = ALT CTRL, D, exec, hypr pull discord + bindu = ALT CTRL, S, exec, hypr pull signal-desktop + bindu = ALT CTRL, T, exec, hypr pull Telegram + bindu = ALT CTRL, V, exec, hypr pull vesktop + bindu = ALT CTRL, Y, exec, hypr pull sioyek + + bindu = ALT SHIFT, RETURN, exec, hypr spawnfocus --ws 1 $TERMINAL + bindu = ALT SHIFT, B, exec, hypr spawnfocus --ws 9 bitwarden-desktop + bindu = ALT SHIFT, C, exec, hypr spawnfocus --ws 2 $BROWSER --ozone-platform=wayland + bindu = ALT SHIFT, D, exec, hypr spawnfocus --ws 5 discord + bindu = ALT SHIFT, F, togglefloating + bindu = ALT SHIFT, G, exec, hypr pull $TERMINAL + bindu = ALT SHIFT, Q, exec, hypr exit + bindu = ALT SHIFT, R, exec, hyprctl reload && notify-send -u low 'hyprland reloaded' + bindu = ALT SHIFT, S, exec, hypr spawnfocus --ws 6 signal-desktop + bindu = ALT SHIFT, T, exec, hypr spawnfocus --ws 6 Telegram + bindu = ALT SHIFT, V, exec, hypr spawnfocus --ws 5 vesktop + bindu = ALT SHIFT, Y, exec, hypr spawnfocus --ws 3 sioyek + + bind = , XF86Tools, submap, scripts + submap = scripts + + bind = , A, exec, ctl audio out + bind = , C, exec, bash -lc 'cliphist list | rofi -dmenu -p "copy to clipboard" --lines 15 | cliphist decode | wl-copy' + bind = , F, exec, [float; fullscreen] ghostty -e lf + bind = , K, exec, ctl keyboard toggle + bind = , O, exec, ctl ocr + bind = , P, exec, hypr pull + bind = , S, exec, ctl screenshot + bind = , T, exec, theme + + bind = , catchall, submap, reset + submap = reset + + misc { + force_default_wallpaper = 0 + disable_hyprland_logo = true + } + + bindu = ALT, 1, workspace, 1 + bindu = ALT, 2, workspace, 2 + bindu = ALT, 3, workspace, 3 + bindu = ALT, 4, workspace, 4 + bindu = ALT, 5, workspace, 5 + bindu = ALT, 6, workspace, 6 + bindu = ALT, 7, workspace, 7 + bindu = ALT, 8, workspace, 8 + bindu = ALT, 9, workspace, 9 + + bindu = ALT SHIFT, 1, movetoworkspace, 1 + bindu = ALT SHIFT, 2, movetoworkspace, 2 + bindu = ALT SHIFT, 3, movetoworkspace, 3 + bindu = ALT SHIFT, 4, movetoworkspace, 4 + bindu = ALT SHIFT, 5, movetoworkspace, 5 + bindu = ALT SHIFT, 6, movetoworkspace, 6 + bindu = ALT SHIFT, 7, movetoworkspace, 7 + bindu = ALT SHIFT, 8, movetoworkspace, 8 + bindu = ALT SHIFT, 9, movetoworkspace, 9 + + bindu = ALT CTRL, 1, movetoworkspacesilent, 1 + bindu = ALT CTRL, 2, movetoworkspacesilent, 2 + bindu = ALT CTRL, 3, movetoworkspacesilent, 3 + bindu = ALT CTRL, 4, movetoworkspacesilent, 4 + bindu = ALT CTRL, 5, movetoworkspacesilent, 5 + bindu = ALT CTRL, 6, movetoworkspacesilent, 6 + bindu = ALT CTRL, 7, movetoworkspacesilent, 7 + bindu = ALT CTRL, 8, movetoworkspacesilent, 8 + bindu = ALT CTRL, 9, movetoworkspacesilent, 9 + + workspace = w[tv1], gapsout:0, gapsin:0 + workspace = f[1], gapsout:0, gapsin:0 + windowrule = match:float 0, match:workspace w[tv1], border_size 0 + windowrule = match:float 0, match:workspace w[tv1], rounding 0 + windowrule = match:float 0, match:workspace f[1], border_size 0 + windowrule = match:float 0, match:workspace f[1], rounding 0 + + windowrule = match:class ^(xdg-desktop-portal-gtk)$, float on + windowrule = match:class ^(xdg-desktop-portal-gtk)$, size monitor_w * 0.5 monitor_h * 0.6 + windowrule = match:class ^(xdg-desktop-portal-kde)$, float on + windowrule = match:class ^(xdg-desktop-portal-kde)$, size monitor_w * 0.5 monitor_h * 0.6 + windowrule = match:class ^(xdg-desktop-portal-hyprland)$, float on + windowrule = match:class ^(xdg-desktop-portal-hyprland)$, size monitor_w * 0.5 monitor_h * 0.6 + ''; + }; + + services.hypridle = { + enable = true; + package = lib.mkIf (!isNixOS) null; + settings = { + general = { + lock_cmd = "wp lock && hyprlock"; + after_sleep_cmd = "hyprctl dispatch dpms on"; + }; + listener = [ + { + timeout = 300; + on-timeout = "wp lock && hyprlock"; + } + { + timeout = 600; + on-timeout = "hyprctl dispatch dpms off"; + on-resume = "hyprctl dispatch dpms on"; + } + { + timeout = 1800; + on-timeout = "systemctl suspend"; + } + ]; + }; + }; + + programs.hyprlock = { + enable = true; + package = lib.mkIf (!isNixOS) null; + settings = { + general = { + hide_cursor = true; + grace = 0; + }; + background = [{ + monitor = ""; + path = "~/img/screen/lock.jpg"; + }]; + animations.enabled = false; + }; + }; + + services.hyprpaper = { + enable = true; + package = lib.mkIf (!isNixOS) null; + settings = { + wallpaper = [ ",~/img/screen/wallpaper.jpg" ]; + splash = false; + }; + }; + + programs.waybar = { + enable = true; + settings.mainBar = { + reload_style_on_change = true; + layer = "top"; + position = "top"; + exclusive = true; + height = 34; + + modules-left = [ "hyprland/workspaces" "hyprland/window" ]; + modules-center = []; + modules-right = [ "backlight" "pulseaudio" "network" "battery" "clock" ]; + + "hyprland/workspaces" = { + disable-scroll = true; + all-outputs = true; + format = "{icon}"; + format-icons = { + "1" = "i"; + "2" = "ii"; + "3" = "iii"; + "4" = "iv"; + "5" = "v"; + "6" = "vi"; + "7" = "vii"; + "8" = "viii"; + "9" = "ix"; + }; + }; + + "hyprland/window" = { + format = " {} [{}]"; + separate-outputs = true; + max-length = 80; + rewrite = {}; + }; + + pulseaudio = { + format = "volume:{volume}% │ "; + format-muted = "volume:{volume}% (muted) │ "; + interval = 2; + signal = 1; + tooltip-format = "Audio Output: {desc}"; + }; + + network = { + format-wifi = "wifi:{essid} │ "; + format-ethernet = "eth:{interface} │ "; + format-disconnected = "wifi:off │ "; + format-disabled = "network:disabled"; + interval = 10; + signal = 1; + tooltip-format-wifi = "Signal: {signalStrength}%\nIP: {ipaddr}/{cidr}"; + tooltip-format-ethernet = "IP: {ipaddr}/{cidr}\nGateway: {gwaddr}"; + tooltip-format-disconnected = "Network: disconnected"; + }; + + backlight = { + device = backlightDevice; + format = "brightness:{percent}% │ "; + signal = 1; + tooltip = false; + }; + + battery = { + format = "battery:-{capacity}% │ "; + format-charging = "battery:+{capacity}% │ "; + format-full = "battery:{capacity}% │ "; + states = { + hi = 30; + mid = 20; + lo = 10; + ultralo = 5; + }; + events = { + on-discharging-hi = "notify-send -u low 'battery 30%'"; + on-discharging-mid = "notify-send -u normal 'battery 20%'"; + on-discharging-lo = "notify-send -u critical 'battery 10%'"; + on-discharging-ultralo = "notify-send -u critical 'battery 5%'"; + on-charging-100 = "notify-send -u low 'battery 100%'"; + }; + interval = 30; + signal = 1; + }; + + clock = { + format = "{:%H:%M:%S %d/%m/%Y} "; + interval = 1; + tooltip-format = "{:%A, %d %B %Y\nTimezone: %Z}"; + }; + }; + + style = '' + * { + font-family: "Berkeley Mono", monospace; + font-size: 15px; + color: ${c.fg}; + } + + button { + border: none; + border-radius: 0; + } + + #waybar { + background: ${c.bg}; + } + + #workspaces button { + padding: 0 10px; + color: ${c.fg}; + } + + #workspaces button.focused, + #workspaces button.active { + background: ${c.bgAlt}; + color: ${c.fg}; + } + + tooltip { + color: ${c.fg}; + background-color: ${c.bgAlt}; + text-shadow: none; + } + + tooltip * { + color: ${c.fg}; + text-shadow: none; + } + ''; + }; + + programs.rofi = { + enable = true; + package = pkgs.rofi; + font = "Berkeley Mono 15"; + extraConfig = { + show-icons = false; + }; + theme = let + inherit (config.lib.formats.rasi) mkLiteral; + in { + "*" = { + selected-normal-foreground = mkLiteral "${c.fg}"; + foreground = mkLiteral "${c.fg}"; + normal-foreground = mkLiteral "@foreground"; + alternate-normal-background = mkLiteral "${c.bg}"; + background = mkLiteral "${c.bgAlt}"; + alternate-normal-foreground = mkLiteral "@foreground"; + normal-background = mkLiteral "@background"; + selected-normal-background = mkLiteral "${c.accent}"; + border-color = mkLiteral "${c.fgAlt}"; + spacing = 2; + separatorcolor = mkLiteral "@foreground"; + background-color = mkLiteral "rgba ( 0, 0, 0, 0 % )"; + }; + window = { + background-color = mkLiteral "@background"; + border = 1; + padding = 5; + }; + mainbox = { + border = 0; + padding = 0; + }; + listview = { + fixed-height = 0; + border = mkLiteral "2px 0px 0px"; + border-color = mkLiteral "@separatorcolor"; + spacing = mkLiteral "2px"; + scrollbar = false; + padding = mkLiteral "2px 0px 0px"; + }; + element = { + border = 0; + padding = mkLiteral "1px"; + }; + element-text = { + background-color = mkLiteral "inherit"; + text-color = mkLiteral "inherit"; + }; + "element.normal.normal" = { + background-color = mkLiteral "@normal-background"; + text-color = mkLiteral "@normal-foreground"; + }; + "element.selected.normal" = { + background-color = mkLiteral "@selected-normal-background"; + text-color = mkLiteral "@selected-normal-foreground"; + }; + "element.alternate.normal" = { + background-color = mkLiteral "@alternate-normal-background"; + text-color = mkLiteral "@alternate-normal-foreground"; + }; + inputbar = { + spacing = 0; + text-color = mkLiteral "@normal-foreground"; + padding = mkLiteral "1px"; + children = map mkLiteral [ "prompt" "textbox-prompt-colon" "entry" "case-indicator" ]; + }; + textbox-prompt-colon = { + expand = false; + str = ":"; + margin = mkLiteral "0px 0.3em 0em 0em"; + text-color = mkLiteral "@normal-foreground"; + }; + }; + }; + + services.dunst = { + enable = true; + settings = { + global = { + font = "Berkeley Mono 15"; + frame_color = c.fgAlt; + separator_color = "frame"; + background = c.bg; + foreground = c.fg; + }; + urgency_low = { + background = c.bg; + foreground = c.fg; + }; + urgency_normal = { + background = c.bg; + foreground = c.fg; + }; + urgency_critical = { + background = c.bg; + foreground = c.red; + frame_color = c.red; + }; + experimental = { + per_monitor_dpi = true; + }; + }; + }; + xdg.configFile."X11".source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nix-config/config/X11"; +} diff --git a/hosts/xps15/configuration.nix b/hosts/xps15/configuration.nix new file mode 100644 index 0000000..9f51d6e --- /dev/null +++ b/hosts/xps15/configuration.nix @@ -0,0 +1,104 @@ +{ pkgs, ... }: + +{ + imports = [ + ./hardware-configuration.nix + ]; + + boot.loader.grub = { + enable = true; + device = "nodev"; + efiSupport = true; + useOSProber = true; + gfxmodeEfi = "1920x1080x32"; + gfxpayloadEfi = "keep"; + }; + boot.loader.efi.canTouchEfiVariables = true; + boot.loader.efi.efiSysMountPoint = "/boot/efi"; + boot.kernelParams = [ "nvidia-drm.modeset=1" "ibt=off" "loglevel=3" "quiet" ]; + + networking.hostName = "xps15"; + networking.wireless.iwd = { + enable = true; + settings = { + General.EnableNetworkConfiguration = true; + Settings.AutoConnect = true; + }; + }; + + services.automatic-timezoned.enable = true; + i18n.defaultLocale = "en_US.UTF-8"; + + security.pam.services.hyprlock = {}; + + users.users.barrett = { + isNormalUser = true; + extraGroups = [ "wheel" "docker" "libvirt" "storage" "power" ]; + shell = pkgs.zsh; + }; + + programs.zsh.enable = true; + programs.hyprland.enable = true; + + hardware.nvidia = { + open = true; + modesetting.enable = true; + prime = { + offload.enable = true; + intelBusId = "PCI:0:2:0"; + nvidiaBusId = "PCI:1:0:0"; + }; + }; + hardware.graphics.enable = true; + hardware.bluetooth.enable = true; + + services.keyd = { + enable = true; + keyboards.default = { + ids = [ "*" ]; + settings = { + main = { + capslock = "overload(control, esc)"; + leftcontrol = "capslock"; + leftmeta = "A-x"; + rightalt = "f13"; + }; + }; + }; + }; + + services.pipewire = { + enable = true; + alsa.enable = true; + jack.enable = true; + pulse.enable = true; + wireplumber.enable = true; + }; + + services.openssh.enable = true; + + virtualisation.docker.enable = true; + virtualisation.libvirtd.enable = true; + programs.virt-manager.enable = true; + + xdg.portal = { + enable = true; + extraPortals = with pkgs; [ + xdg-desktop-portal-gtk + xdg-desktop-portal-hyprland + ]; + }; + + environment.systemPackages = with pkgs; [ + vim + wget + git + ntfs3g + efibootmgr + dmidecode + ]; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + + system.stateVersion = "24.11"; +} diff --git a/scripts/ctl b/scripts/ctl new file mode 100755 index 0000000..096da85 --- /dev/null +++ b/scripts/ctl @@ -0,0 +1,94 @@ +#!/bin/sh + +case "$1" in +screenshot) + dir="$HOME/img/ss" + mkdir -p "$dir" + file="$dir/$(openssl rand -hex 10)-$(date +'%Y-%m-%d_%H-%M-%S').png" + if [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then + grim -g "$(slurp)" "$file" && wl-copy < "$file" + else + maim -s "$file" && xclip -selection clipboard -t image/png -i "$file" & + fi + ;; +ocr) + dir="$HOME/img/ss" + mkdir -p "$dir" + file="$dir/$(openssl rand -hex 10)-$(date +'%Y-%m-%d_%H-%M-%S').png" + if [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then + ( + region="$(slurp)" + [ -n "$region" ] || exit 0 + grim -g "$region" "$file" && + tesseract -l eng "$file" - 2>/dev/null | wl-copy + ) /dev/null 2>&1 & + else + ( + maim -s "$file" && + tesseract -l eng "$file" - 2>/dev/null | xclip -selection clipboard -in + ) /dev/null 2>&1 & + fi + ;; +keyboard) + case "$2" in + toggle) + if [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then + if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then + hyprctl switchxkblayout current next + elif [ "$XDG_CURRENT_DESKTOP" = "sway" ]; then + if swaymsg -t get_inputs | grep -qi "Colemak"; then + swaymsg input type:keyboard xkb_variant "''" + else + swaymsg input type:keyboard xkb_variant colemak + fi + fi + else + current="$(setxkbmap -query | awk '/variant/{print $2}')" + if [ "$current" = "colemak" ]; then + setxkbmap -layout us + else + setxkbmap -layout us -variant colemak + fi + fi + ;; + *) + echo "Usage: ctl keyboard {toggle}" >&2 + exit 1 + ;; + esac + ;; +audio) + case "$2" in + out) + sinks="$(pactl list short sinks | awk '{print $1": "$2}')" + [ -z "$sinks" ] && exit 0 + count="$(printf "%s\n" "$sinks" | wc -l)" + if [ "$XDG_SESSION_TYPE" = x11 ]; then + choice="$(printf "%s\n" "$sinks" | dmenu -i -l "$count" -p "select sink:" | cut -d: -f1)" + else + choice="$(printf "%s\n" "$sinks" | rofi -dmenu -i -lines "$count" -p "select sink" | cut -d: -f1)" + fi + [ "$choice" ] && pactl set-default-sink "$choice" + ;; + in) + sources="$(pactl list short sources | awk '{print $1": "$2}')" + [ -z "$sources" ] && exit 0 + count="$(printf "%s\n" "$sources" | wc -l)" + if [ "$XDG_SESSION_TYPE" = x11 ]; then + choice="$(printf "%s\n" "$sources" | dmenu -i -l "$count" -p "select source:" | cut -d: -f1)" + else + choice="$(printf "%s\n" "$sources" | rofi -dmenu -i -lines "$count" -p "select source" | cut -d: -f1)" + fi + [ "$choice" ] && pactl set-default-source "$choice" + ;; + *) + echo "Usage: ctl audio {in|out}" >&2 + exit 1 + ;; + esac + ;; +*) + echo "Usage: ctl {screenshot|ocr|keyboard|audio}" >&2 + exit 1 + ;; +esac diff --git a/scripts/doc b/scripts/doc new file mode 100755 index 0000000..eec2b6a --- /dev/null +++ b/scripts/doc @@ -0,0 +1,23 @@ +#!/bin/sh + +dir="$HOME/doc" +test -d "$dir" || exit + +if [ "$XDG_SESSION_TYPE" = x11 ]; then + picker() { dmenu -i -l 10 -p "Select file or folder: "; } +else + picker() { rofi -dmenu -i -l 10 -p "Select file or folder"; } +fi + +while :; do + choice="$(find "$dir" -not -path "$dir/.*" -mindepth 1 -maxdepth 1 \( -type d -printf "%f/\n" -o -type f -printf "%f\n" \) | picker)" + + [ -n "$choice" ] || break + + if [ -d "$dir/${choice%/}" ]; then + dir="$dir/${choice%/}" + elif [ -f "$dir/$choice" ]; then + sioyek "$dir/$choice" & + break + fi +done diff --git a/scripts/hypr b/scripts/hypr new file mode 100755 index 0000000..cb0392f --- /dev/null +++ b/scripts/hypr @@ -0,0 +1,283 @@ +#!/bin/sh + +usage() { + cat < [app] [args...] + +Commands: + volume {up,down,toggle} Adjust volume accordingly and notify + brightness {up,down} Adjust brightness accordingly and notify + spawnfocus [args...] Focus existing window or spawn app with args + pull [app] Pull window to current workspace (picker if no app) + borders Initialize dynamic borders + exit Safely exit hyprland + +Options: + -h, --help Show this help message +EOF +} + +[ -z "$1" ] && { + usage + exit 1 +} + +cmd="$1" +shift + +case "$cmd" in +-h | --help) + usage + exit 0 + ;; +exit) + pkill hypridle + pkill hyprpaper + hyprctl dispatch exit + exit 0 + ;; +brightness) + BRIGHT_STEP=5 + max_brightness="$(brightnessctl max)" + case "$1" in + up) + brightnessctl set "$BRIGHT_STEP"%+ + notify-send -u low -t 2500 -r 5555 "brightness $(brightnessctl get | awk -v max="$max_brightness" '{printf "%d%%", $1*100/max}')" + ;; + down) + brightnessctl set "$BRIGHT_STEP"%- + notify-send -u low -t 2500 -r 5555 "brightness $(brightnessctl get | awk -v max="$max_brightness" '{printf "%d%%", $1*100/max}')" + ;; + *) + echo "Invalid subcommand: $1" >&2 + exit 1 + ;; + esac + ;; +volume) + SINK="@DEFAULT_SINK@" + VOL_STEP=5 + get_vol() { pactl get-sink-volume "$SINK" | awk 'NR==1{print $5+0}'; } + + case "$1" in + up) + vol=$(get_vol) + [ "$vol" -lt 100 ] && vol=$((vol + VOL_STEP)) + [ "$vol" -gt 100 ] && vol=100 + pactl set-sink-volume "$SINK" "${vol}%" + ;; + down) + vol=$(get_vol) + vol=$((vol - VOL_STEP)) + [ "$vol" -lt 0 ] && vol=0 + pactl set-sink-volume "$SINK" "${vol}%" + ;; + toggle) + pactl set-sink-mute "$SINK" toggle + ;; + *) + echo "Invalid subcommand: $1" >&2 + exit 1 + ;; + esac + + muted=$(pactl get-sink-mute "$SINK" | awk '{print $2}') + vol=$(get_vol) + [ "$vol" -gt 100 ] && vol=100 + + if [ "$muted" = "yes" ]; then + notify-send -u low -t 2500 -r 5556 "volume: ${vol}% (muted)" + else + notify-send -u low -t 2500 -r 5556 "volume: ${vol}%" + fi + ;; +pull) + APP="$1" + if [ -n "$APP" ]; then + case "$APP" in + google-chrome | google-chrome-stable) CLASS="google-chrome" ;; + chromium | ungoogled-chromium) CLASS="Chromium" ;; + firefox) CLASS="firefox" ;; + alacritty) CLASS="Alacritty" ;; + code | vscodium) CLASS="Code" ;; + signal-desktop | signal) CLASS="signal" ;; + telegram-desktop | telegram) CLASS="TelegramDesktop" ;; + ghostty) CLASS="com.mitchellh.ghostty" ;; + bitwarden-desktop | bitwarden) CLASS="Bitwarden" ;; + slack) CLASS="Slack" ;; + discord) CLASS="discord" ;; + vesktop) CLASS="vesktop" ;; + *) CLASS="$APP" ;; + esac + CUR_ADDR=$(hyprctl -j activewindow | jq -r '.address') + WIN_ADDRS=$( + hyprctl -j clients 2>/dev/null | jq -r --arg class "$CLASS" ' + .[]? | select( + ((.xdgTag // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.initialClass // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.class // "") | ascii_downcase | contains($class | ascii_downcase)) + ) | "\(.class)\t\(.title)\t\(.address)"' + ) + WIN_COUNT=$(echo "$WIN_ADDRS" | grep -c .) + if [ "$WIN_COUNT" -eq 1 ]; then + WIN_ADDR=$(echo "$WIN_ADDRS" | awk -F'\t' '{print $3}') + elif [ "$WIN_COUNT" -gt 1 ]; then + SELECTED=$(echo "$WIN_ADDRS" | + awk -F'\t' -v cur="$CUR_ADDR" '{if($3!=cur) print $1 ": " $2 "\t" $3}' | + rofi -dmenu -i -p "pull window") + WIN_ADDR=$(echo "$SELECTED" | awk -F'\t' '{print $2}') + fi + fi + if [ -z "$SELECTED" ]; then + exit 0 + fi + if [ -z "$WIN_ADDR" ]; then + CUR_ADDR=$(hyprctl -j activewindow | jq -r '.address') + WIN_ADDRS=$(hyprctl -j clients 2>/dev/null | jq -r '.[]? | "\(.class)\t\(.title)\t\(.address)"') + SELECTED=$(echo "$WIN_ADDRS" | + awk -F'\t' -v cur="$CUR_ADDR" '{if($3!=cur) print $1 ": " $2 "\t" $3}' | + rofi -dmenu -i -p "pull window") + WIN_ADDR=$(echo "$SELECTED" | awk -F'\t' '{print $2}') + fi + if [ -n "$WIN_ADDR" ]; then + CURRENT_WS="$(hyprctl activeworkspace | head -n1 | awk -F'[()]' '{print $2}')" + hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$WIN_ADDR" + hyprctl dispatch focuswindow "address:$WIN_ADDR" + fi + ;; +spawnfocus) + WS="" + while [ $# -gt 0 ]; do + case "$1" in + --ws) + if [ $# -lt 2 ]; then + echo "Missing workspace number after --ws" + exit 1 + fi + WS="$2" + shift 2 + ;; + -*) + echo "Unknown option $1" + exit 1 + ;; + *) + break + ;; + esac + done + + APP="$1" + [ -z "$APP" ] && { + echo 'Specify an app' + exit 1 + } + shift + + case "$APP" in + google-chrome | google-chrome-stable) CLASS="google-chrome" ;; + chromium | ungoogled-chromium) CLASS="Chromium" ;; + firefox) CLASS="firefox" ;; + alacritty) CLASS="Alacritty" ;; + code | vscodium) CLASS="Code" ;; + signal-desktop | signal) CLASS="signal" ;; + telegram-desktop | telegram) CLASS="TelegramDesktop" ;; + ghostty) CLASS="com.mitchellh.ghostty" ;; + bitwarden-desktop | bitwarden) CLASS="Bitwarden" ;; + slack) CLASS="Slack" ;; + discord) CLASS="discord" ;; + vesktop) CLASS="vesktop" ;; + *) CLASS="$APP" ;; + esac + + WIN_ADDRS=$(hyprctl -j clients 2>/dev/null | jq -r --arg class "$CLASS" ' + .[]? | select( + ((.xdgTag // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.initialClass // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.class // "") | ascii_downcase | contains($class | ascii_downcase)) + ) | "\(.class)\t\(.title)\t\(.address)" + ') + + WIN_COUNT=$(echo "$WIN_ADDRS" | grep -c .) + + if [ "$WIN_COUNT" -eq 0 ]; then + WIN_ADDR="" + elif [ "$WIN_COUNT" -eq 1 ]; then + WIN_ADDR=$(echo "$WIN_ADDRS" | awk -F'\t' '{print $3}') + elif [ "$WIN_COUNT" -gt 1 ]; then + SELECTED=$(echo "$WIN_ADDRS" | awk -F'\t' '{print $1 ": " $2 "\t" $3}' | rofi -dmenu -i -p "select window") + + if [ -z "$SELECTED" ]; then + exit 0 + fi + + WIN_ADDR=$(echo "$SELECTED" | awk -F'\t' '{print $2}') + fi + if [ -n "$WIN_ADDR" ]; then + if [ -n "$WS" ]; then + WIN_WS=$(hyprctl clients -j | jq -r --arg addr "$WIN_ADDR" '.[] | select(.address == $addr) | .workspace.id') + + if [ "$WIN_WS" != "$WS" ]; then + hyprctl dispatch movetoworkspacesilent "$WS,address:$WIN_ADDR" + fi + + hyprctl dispatch workspace "$WS" + else + WIN_WS=$(hyprctl clients -j | jq -r --arg addr "$WIN_ADDR" '.[] | select(.address == $addr) | .workspace.id') + + hyprctl dispatch workspace "$WIN_WS" + fi + + hyprctl dispatch focuswindow "address:$WIN_ADDR" + else + EXISTING_HEX=$(hyprctl -j clients 2>/dev/null | jq -r --arg class "$CLASS" ' + .[]? | select( + ((.xdgTag // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.initialClass // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.class // "") | ascii_downcase | contains($class | ascii_downcase)) + ) | .address | ltrimstr("0x") + ' | tr '\n' ' ') + + if [ -n "$WS" ]; then + hyprctl dispatch workspace "$WS" + fi + + if [ $# -eq 0 ]; then + "$APP" & + else + "$APP" "$@" & + fi + + socat -u UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" - | while read -r line; do + case "$line" in + openwindow*) + window_id=$(echo "$line" | cut -d'>' -f3 | cut -d',' -f1) + event_class=$(echo "$line" | cut -d'>' -f3 | cut -d',' -f3) + class_lower=$(echo "$event_class" | tr '[:upper:]' '[:lower:]') + target_lower=$(echo "$CLASS" | tr '[:upper:]' '[:lower:]') + case "$class_lower" in + *"$target_lower"*) + case " $EXISTING_HEX " in + *" $window_id "*) ;; + *) + addr="0x$window_id" + if [ -n "$WS" ]; then + hyprctl dispatch movetoworkspacesilent "$WS,address:$addr" + fi + hyprctl dispatch focuswindow "address:$addr" + break + ;; + esac + ;; + esac + ;; + esac + done + fi + ;; +*) + echo "Unknown subcommand: $cmd" + usage + exit 1 + ;; +esac diff --git a/scripts/mux b/scripts/mux new file mode 100755 index 0000000..3c6a8a4 --- /dev/null +++ b/scripts/mux @@ -0,0 +1,118 @@ +#!/bin/sh + +spawn_or_focus() { + name="$1" + cmd="$2" + + if tmux list-windows -F '#{window_name}' | grep -Fx "$name" >/dev/null; then + tmux select-window -t "${name}" + else + if [ -n "$cmd" ]; then + tmux new-window -c '#{pane_current_path}' -n "${name}" "$cmd" + else + tmux new-window -c '#{pane_current_path}' -n "${name}" + fi + fi +} + +case "$1" in +bar) + [ "$2" ] || exit + mouse="" + if [ "$(tmux show-options | grep mouse | awk '{ print $NF }')" = "on" ]; then + mouse='[m]' + fi + set -f + keys="H J K L" + bar_content="" + i=0 + total=$(tmux ls -F x | wc -l) + for line in $(tmux ls -F '#{session_id}:#{session_name}'); do + sid="${line%%:*}" + sname="${line#*:}" + if [ $i -lt 4 ]; then + key=$(echo "$keys" | cut -d' ' -f $((i + 1))) + elif [ $i -eq $((total - 1)) ]; then + key='$' + else + key='?' + fi + star="" + [ "$sname" = "$2" ] && star="*" + bar_content="$bar_content#[range=session|${sid}]$key:$sname$star#[norange] " + i=$((i + 1)) + done + set +f + left='#[align=left list=on] #{W:#[range=window|#{window_index}]#{window_index}:#{window_name}#{window_flags}#[norange] }#[nolist]' + right="#[align=right]$mouse $bar_content" + tmux set -g 'status-format[0]' "$left$right" + ;; +switch) + session="$(tmux ls -F '#S' | tail -n "+$(($2 + 1))" | head -1)" + tmux switch -t "$session" + ;; +exec) + name="$(basename "$PWD")" + project="$(basename "$(dirname "$PWD")")/$name" + + case "$project" in + */bmath) + cmd='cmake -B build -DCMAKE_BUILD_TYPE=Debug && cmake --build build && ctest --test-dir build --output-on-failure' + ;; + */ag) + cmd='cp -f autograder.py example && cd example && ./run_autograder' + ;; + */project-a-10) + cmd='. venv/bin/activate && python manage.py runserver' + ;; + */theCourseForum2) + cmd='docker compose up' + ;; + */atlas | */tinyground) + cmd='pnpm run dev' + ;; + */interview-prep) + cmd='pnpm run dev' + ;; + */neovim) + cmd='make' + ;; + */TestCppClient) + cmd='rm -f TestCppClientStatic && cmake -S . -B build/ && make && ./TestCppClientStatic' + ;; + sl/*) + cmd='make clean install && make clean' + [ "$name" = 'slock' ] && cmd="doas $cmd" + ;; + */barrettruth.com) + cmd='pnpm dev' + ;; + esac + + echo " > $cmd" | sed "s|$HOME|~|g" + eval "$cmd" + ;; +claude) + spawn_or_focus claude 'claude --chrome' + ;; +nvim) + spawn_or_focus nvim 'nvim -c "lua require([[config.tmux]]).run([[nvim]])"' + ;; +git) + pane_path=$(tmux display-message -p '#{pane_current_path}') + if ! git -C "$pane_path" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + tmux display-message "Not a git repository" + else + spawn_or_focus git 'nvim -c "lua require([[config.tmux]]).run([[git]])"' + fi + ;; +run) + spawn_or_focus run 'nvim -c "lua require([[config.tmux]]).run([[run]])"' + ;; +term) + spawn_or_focus term + ;; +*) + tmux attach + ;; +esac diff --git a/scripts/pipes b/scripts/pipes new file mode 100755 index 0000000..5ee0b51 --- /dev/null +++ b/scripts/pipes @@ -0,0 +1,227 @@ +#!/usr/bin/env bash +# pipes.sh: Animated pipes terminal screensaver. +# https://github.com/pipeseroni/pipes.sh +# +# Copyright (c) 2015-2018 Pipeseroni/pipes.sh contributors +# Copyright (c) 2013-2015 Yu-Jie Lin +# Copyright (c) 2010 Matthew Simpson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +VERSION=1.3.0 + +M=32768 # Bash RANDOM maximum + 1 +p=1 # number of pipes +f=75 # frame rate +s=13 # probability of straight fitting +r=2000 # characters limit +t=0 # iteration counter for -r character limit +w=80 # terminal size +h=24 + +# ab -> sets[][idx] = a*4 + b +# 0: up, 1: right, 2: down, 3: left +# 00 means going up , then going up -> ┃ +# 12 means going right, then going down -> ┓ +sets=( + "┃┏ ┓┛━┓ ┗┃┛┗ ┏━" + "│╭ ╮╯─╮ ╰│╯╰ ╭─" + "│┌ ┐┘─┐ └│┘└ ┌─" + "║╔ ╗╝═╗ ╚║╝╚ ╔═" + "|+ ++-+ +|++ +-" + "|/ \/-\ \|/\ /-" + ".. .... .... .." + ".o oo.o o.oo o." + "-\ /\|/ /-\/ \|" + "╿┍ ┑┚╼┒ ┕╽┙┖ ┎╾" + "████▀▀███▀█▀▀██▀" +) + +# pipes' +x=() # current position +y=() +l=() # current directions + # 0: up, 1: right, 2: down, 3: left +n=() # new directions +v=() # current types +c=() # current color + +# selected pipes' +V=() # types (indexes to sets[]) +C=() # colors +VN=0 # number of selected types +CN=0 # number of selected colors + +# switches +RNDSTART=0 # randomize starting position and direction +BOLD=1 +NOCOLOR=0 +KEEPCT=0 # keep pipe color and type + + +parse() { + OPTIND=1 + while getopts "p:t:c:f:s:r:RBCKhv" arg; do + case $arg in + p) ((p = (OPTARG > 0) ? OPTARG : p));; + t) + if [[ "$OPTARG" = c???????????????? ]]; then + V+=(${#sets[@]}) + sets+=("${OPTARG:1}") + else + ((OPTARG >= 0 && OPTARG < ${#sets[@]})) && V+=($OPTARG) + fi + ;; + c) [[ $OPTARG =~ ^[0-7]$ ]] && C+=($OPTARG);; + f) ((f = (OPTARG > 19 && OPTARG < 101) ? OPTARG : f));; + s) ((s = (OPTARG > 4 && OPTARG < 16) ? OPTARG : s));; + r) ((r = (OPTARG >= 0) ? OPTARG : r));; + R) RNDSTART=1;; + B) BOLD=0;; + C) NOCOLOR=1;; + K) KEEPCT=1;; + h) echo -e "Usage: $(basename $0) [OPTION]..." + echo -e "Animated pipes terminal screensaver.\n" + echo -e " -p [1-]\tnumber of pipes (D=1)." + echo -e " -t [0-$((${#sets[@]} - 1))]\ttype of pipes, can be used more than once (D=0)." + echo -e " -c [0-7]\tcolor of pipes, can be used more than once (D=1 2 3 4 5 6 7 0)." + echo -e " -t c[16 chars]\tcustom type of pipes." + echo -e " -f [20-100]\tframerate (D=75)." + echo -e " -s [5-15]\tprobability of a straight fitting (D=13)." + echo -e " -r LIMIT\treset after x characters, 0 if no limit (D=2000)." + echo -e " -R \t\trandomize starting position and direction." + echo -e " -B \t\tno bold effect." + echo -e " -C \t\tno color." + echo -e " -K \t\tpipes keep their color and type when hitting the screen edge." + echo -e " -h\t\thelp (this screen)." + echo -e " -v\t\tprint version number.\n" + exit 0;; + v) echo "$(basename -- "$0") $VERSION" + exit 0 + esac + done + + # set default values if not by options + ((${#V[@]})) || V=(0) + VN=${#V[@]} + ((${#C[@]})) || C=(1 2 3 4 5 6 7 0) + CN=${#C[@]} +} + + +cleanup() { + # clear out standard input + read -t 0.001 && cat /dev/null + + # terminal has no smcup and rmcup capabilities + ((FORCE_RESET)) && reset && exit 0 + + tput reset # fix for konsole, see pipeseroni/pipes.sh#43 + tput rmcup + tput cnorm + # stty echo + ((NOCOLOR)) && echo -ne '\e[0m' + exit 0 +} + + +resize() { + w=$(tput cols) h=$(tput lines) +} + + +init() { + local i + + resize + trap resize SIGWINCH + ci=$((KEEPCT ? 0 : CN * RANDOM / M)) + vi=$((KEEPCT ? 0 : VN * RANDOM / M)) + for ((i = 0; i < p; i++)); {(( + n[i] = 0, + l[i] = RNDSTART ? RANDOM % 4 : 0, + x[i] = RNDSTART ? w * RANDOM / M : w / 2, + y[i] = RNDSTART ? h * RANDOM / M : h / 2, + c[i] = C[ci], + v[i] = V[vi], + ci = (ci + 1) % CN, + vi = (vi + 1) % VN + ));} + + # stty -echo + tput smcup || FORCE_RESET=1 + tput civis + tput clear + trap cleanup HUP TERM +} + + +main() { + local i + + parse "$@" + init "$@" + + # any key press exits the loop and this script + trap 'break 2' INT + while REPLY=; do + read -t 0.0$((1000 / f)) -n 1 2>/dev/null + case "$REPLY" in + P) ((s = s < 15 ? s + 1 : s));; + O) ((s = s > 3 ? s - 1 : s));; + F) ((f = f < 100 ? f + 1 : f));; + D) ((f = f > 20 ? f - 1 : f));; + B) ((BOLD = (BOLD + 1) % 2));; + C) ((NOCOLOR = (NOCOLOR + 1) % 2));; + K) ((KEEPCT = (KEEPCT + 1) % 2));; + ?) break;; + esac + for ((i = 0; i < p; i++)); do + # New position: + # l[] direction = 0: up, 1: right, 2: down, 3: left + ((l[i] % 2)) && ((x[i] += -l[i] + 2, 1)) || ((y[i] += l[i] - 1)) + + # Loop on edges (change color on loop): + ((!KEEPCT && (x[i] >= w || x[i] < 0 || y[i] >= h || y[i] < 0))) \ + && ((c[i] = C[CN * RANDOM / M], v[i] = V[VN * RANDOM / M])) + ((x[i] = (x[i] + w) % w)) + ((y[i] = (y[i] + h) % h)) + + # New random direction: + ((n[i] = s * RANDOM / M - 1)) + ((n[i] = (n[i] > 1 || n[i] == 0) ? l[i] : l[i] + n[i])) + ((n[i] = (n[i] < 0) ? 3 : n[i] % 4)) + + # Print: + tput cup ${y[i]} ${x[i]} + echo -ne "\e[${BOLD}m" + ((NOCOLOR)) && echo -ne "\e[0m" || echo -ne "\e[3${c[i]}m" + echo -n "${sets[v[i]]:l[i]*4+n[i]:1}" + l[i]=${n[i]} + done + ((r > 0 && t * p >= r)) && tput reset && tput civis && t=0 || ((t++)) + done + + cleanup +} + + +main "$@" + diff --git a/scripts/theme b/scripts/theme new file mode 100755 index 0000000..828911e --- /dev/null +++ b/scripts/theme @@ -0,0 +1,122 @@ +#!/bin/sh + +themes="daylight +midnight" + +as_list="$(printf "%s\n" "$themes" | awk 'NF{printf "\"%s\",", $0}' | sed 's/,$//')" + +case "$(uname)" in +Linux) + if [ -n "$1" ]; then + theme="$1" + else + if [ "$XDG_SESSION_TYPE" = "wayland" ]; then + theme="$(printf "%s\n" "$themes" | rofi -dmenu -p 'theme')" + else + theme="$(printf "%s\n" "$themes" | dmenu -p 'select theme: ')" + fi + fi + ;; +Darwin) + if [ -n "$1" ]; then + theme="$1" + else + theme="$( + osascript </dev/null 2>&1; then + case "$theme" in + midnight) + gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' + ;; + *) + gsettings set org.gnome.desktop.interface color-scheme 'prefer-light' + ;; + esac + fi + + [ "$XDG_SESSION_TYPE" = "wayland" ] && { + if [ "$XDG_CURRENT_DESKTOP" = 'Hyprland' ]; then + current_theme=$(readlink "$XDG_CONFIG_HOME/hypr/theme.conf" 2>/dev/null | awk -F/ '{print $NF}' | sed 's/\.conf$//') + if [ "$current_theme" != "$theme" ]; then + ln -sf "$XDG_CONFIG_HOME/hypr/themes/$theme.conf" "$XDG_CONFIG_HOME/hypr/theme.conf" + ln -sf "$XDG_CONFIG_HOME/waybar/themes/$theme.css" "$XDG_CONFIG_HOME/waybar/themes/theme.css" + hyprctl reload + pkill -USR1 waybar + pkill -USR1 waybar + fi + elif [ "$XDG_CURRENT_DESKTOP" = 'sway' ]; then + current_theme=$(readlink "$XDG_CONFIG_HOME/sway/themes/theme" 2>/dev/null | sed 's|.*/||') + if [ "$current_theme" != "$theme" ]; then + test -f "$XDG_CONFIG_HOME/sway/themes/theme" && ln -sf "$XDG_CONFIG_HOME/sway/themes/$theme" "$XDG_CONFIG_HOME/sway/themes/theme" + swaymsg reload + fi + fi + } + + test -d "$XDG_CONFIG_HOME/rofi/themes" && ln -sf "$XDG_CONFIG_HOME/rofi/themes/$theme.rasi" "$XDG_CONFIG_HOME/rofi/themes/theme.rasi" + ;; +Darwin) + case "$theme" in + daylight) + osascript -e 'tell app "System Events" to tell appearance preferences to set dark mode to false' 2>/dev/null || true + ;; + midnight) + osascript -e 'tell app "System Events" to tell appearance preferences to set dark mode to true' 2>/dev/null || true + ;; + esac + ;; +esac + +if tmux list-sessions >/dev/null 2>&1; then + test -f "$XDG_CONFIG_HOME/tmux/tmux.conf" && tmux source-file "$XDG_CONFIG_HOME/tmux/tmux.conf" + [ "$TMUX" ] && tmux refresh-client -S +fi + +test -d "$XDG_CONFIG_HOME/fzf/themes" && ln -sf "$XDG_CONFIG_HOME/fzf/themes/$theme" "$XDG_CONFIG_HOME/fzf/themes/theme" +test -d "$XDG_CONFIG_HOME/rg/themes" && ln -sf "$XDG_CONFIG_HOME/rg/themes/$theme" "$XDG_CONFIG_HOME/rg/themes/theme" +test -d "$XDG_CONFIG_HOME/task/themes" && ln -sf "$XDG_CONFIG_HOME/task/themes/$theme.theme" "$XDG_CONFIG_HOME/task/themes/theme.theme" + +if command -v claude >/dev/null 2>&1; then + case "$theme" in + daylight) + claude config set theme light + ;; + midnight) + claude config set theme dark + ;; + esac +fi + +test -f ~/.zshenv && sed -i "s|^\(export THEME=\).*|\1$theme|" ~/.zshenv + +for socket in /tmp/nvim-*.sock; do + test -S "$socket" && nvim --server "$socket" --remote-expr "luaeval('require(\"config.fzf_reload\").reload()')" 2>/dev/null || true +done diff --git a/scripts/wp b/scripts/wp new file mode 100755 index 0000000..7581fe6 --- /dev/null +++ b/scripts/wp @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +import os +import random +import subprocess +import sys + +from PIL import Image, ImageDraw + +HOME = os.environ["HOME"] +DIR = f"{HOME}/img/screen" +os.makedirs(DIR, exist_ok=True) + + +def get_resolution(): + output = "1920x1080" + if "WAYLAND_DISPLAY" in os.environ: + desktop = os.environ.get("XDG_CURRENT_DESKTOP", "") + if desktop == "Hyprland": + output = ( + subprocess.check_output( + "hyprctl monitors -j | jq -r '.[] | select(.focused==true) | .width, .height' | paste -sd x -", + shell=True, + ) + .decode() + .strip() + ) + elif desktop == "sway": + output = ( + subprocess.check_output( + "swaymsg -t get_outputs -r | jq -r '.[] | select(.active==true) | .current_mode.width,.current_mode.height' | paste -sd x -", + shell=True, + ) + .decode() + .strip() + ) + else: + output = ( + subprocess.check_output( + "xrandr | grep '*' | awk '{print $1}'", shell=True + ) + .decode() + .strip() + ) + return map(int, output.split("x")) + + +def lock(): + W, H = get_resolution() + UNIT = 16 + grid_w = W // UNIT + grid_h = H // UNIT + + bg_color = tuple(random.randint(0, 255) for _ in range(3)) + img = Image.new("RGB", (W, H), bg_color) + draw = ImageDraw.Draw(img) + + MU = 0.8 + SIGMA = 0.7 + SCALE_FACTOR = 1.8 + + S = {(x, y) for x in range(grid_w) for y in range(grid_h)} + N = len(S) + + while S: + if random.random() < 1 / N: + break + gx, gy = S.pop() + w_units = max( + 1, + min( + grid_w - gx, + int(round(random.lognormvariate(MU, SIGMA) * SCALE_FACTOR)), + ), + ) + h_units = max( + 1, + min( + grid_h - gy, + int(round(random.lognormvariate(MU, SIGMA) * SCALE_FACTOR)), + ), + ) + color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + + dx = random.choice([1, -1]) + dy = random.choice([1, -1]) + + gx_end = gx + dx * (w_units - 1) + gy_end = gy + dy * (h_units - 1) + + x0, x1 = sorted([gx, gx_end]) + y0, y1 = sorted([gy, gy_end]) + + draw.rectangle( + [x0 * UNIT, y0 * UNIT, (x1 + 1) * UNIT - 1, (y1 + 1) * UNIT - 1], + fill=color, + ) + + img.save(f"{DIR}/lock.jpg", quality=95) + + +def wall(): + W, H = get_resolution() + + colors = [ + "#aa0000", + "#ff3333", + "#ff7777", + "#ffbb55", + "#ffcc88", + "#ff88ff", + "#aaaaff", + "#77ddbb", + "#77ff77", + "#cccccc", + ] + + colors.reverse() + + img = Image.new("RGB", (W, H), "white") + draw = ImageDraw.Draw(img) + + num_bars = len(colors) + + for i, color in enumerate(reversed(colors)): + top = round(i * H / num_bars) + bottom = round((i + 1) * H / num_bars) + draw.rectangle([0, top, W, bottom], fill=color) + + img.save(f"{DIR}/wallpaper.jpg", quality=95) + + +if len(sys.argv) < 2: + print("Usage: wp {lock|wall}", file=sys.stderr) + sys.exit(1) + +cmd = sys.argv[1] +if cmd == "lock": + lock() +elif cmd == "wall": + wall() +else: + print("Usage: wp {lock|wall}", file=sys.stderr) + sys.exit(1) diff --git a/scripts/x b/scripts/x new file mode 100755 index 0000000..9ab902c --- /dev/null +++ b/scripts/x @@ -0,0 +1,56 @@ +#!/bin/sh + +cmd="$1"; shift + +case "$cmd" in +setup) + xrdb -merge "$XDG_CONFIG_HOME"/X11/xresources."$THEME" + xset b off + xset m 0 + xset r rate 300 50 + xset s 300 + xset dpms 420 540 720 + xmodmap "$XDG_CONFIG_HOME"/X11/xmodmap + xss-lock -- slock & + ;; +bg) + randr="$(xrandr | rg ' connected ')" + mons="$(echo "$randr" | wc -l)" + wpdir="$HOME"/img/wp + + [ "$1" ] && bgone="$1" || bgone="$wpdir"/one/cliff.jpg + cmd="feh --no-fehbg --bg-fill $bgone" + + if [ "$mons" = 2 ]; then + [ "$2" ] && bgtwo="$2" || bgtwo="$wpdir"/two/lilies.jpg + cmd="$cmd --bg-fill $bgtwo" + else + cmd="feh --no-fehbg --bg-fill $bgone" + fi + + eval "$cmd" + ;; +mon) + mons="$(xrandr | rg --no-config ' connected ' | awk '{ print $1 }')" + + one="$(echo "$mons" | head -n 1)" + two="$(echo "$mons" | tail -n 1)" + case "$two" in + *None* | "$one") + unset two + ;; + esac + + xrandr --auto + + xrandr --output "$one" --primary --mode 1920x1200 --scale 1x1 --set TearFree on --dpi 161.73 + + [ -z "$two" ] && exit + + xrandr --output "$one" --pos 960x2160 --output "$two" --scale 1.5x1.5 --pos 0x0 + ;; +*) + echo "Usage: x {setup|bg|mon}" >&2 + exit 1 + ;; +esac