├── config ├── carapace │ ├── bridge │ │ ├── zsh │ │ │ └── .zshrc │ │ ├── bash │ │ │ └── .bashrc │ │ └── fish │ │ │ └── config.fish │ ├── bridges.yaml │ └── .gitignore ├── user-dirs.locale ├── git │ ├── .gitignore │ ├── ignore │ ├── local.example.config │ ├── message │ ├── config │ ├── delta.config │ └── alias.config ├── niri │ ├── niri_tools │ │ ├── __init__.py │ │ ├── daemon │ │ │ └── __init__.py │ │ ├── common.py │ │ └── main.py │ ├── .envrc │ ├── swayidle.conf │ ├── bin │ │ ├── focus-previous │ │ └── inspect │ ├── pyproject.toml │ ├── uv.lock │ ├── CLAUDE.md │ └── .gitignore ├── nushell │ ├── autoload │ │ ├── platform │ │ │ ├── darwin.nu │ │ │ ├── windows.nu │ │ │ ├── linux.nu │ │ │ ├── arch-linux.nu │ │ │ ├── systemd.nu │ │ │ └── arch-linux.mod.nu │ │ ├── platform.nu │ │ ├── aliases.nu │ │ ├── git.nu │ │ ├── plugins.nu │ │ ├── clipboard.nu │ │ ├── python.nu │ │ ├── nushell.nu │ │ ├── js.nu │ │ ├── files.nu │ │ └── nix.nu │ ├── .gitignore │ └── scripts │ │ ├── awtrix │ │ ├── lib.nu │ │ ├── app │ │ │ ├── mod.nu │ │ │ └── gh │ │ │ │ └── mod.nu │ │ └── mod.nu │ │ ├── git │ │ ├── mod.nu │ │ ├── ignore.nu │ │ ├── push.nu │ │ └── clone.nu │ │ ├── completion │ │ └── mod.nu │ │ ├── xtras │ │ └── format.nu │ │ └── rofi │ │ └── mod.nu ├── nvim │ ├── queries │ │ └── .gitignore │ ├── lua │ │ ├── user │ │ │ ├── util │ │ │ │ ├── .gitignore │ │ │ │ ├── path.lua │ │ │ │ ├── workspace │ │ │ │ │ ├── util.lua │ │ │ │ │ └── init.lua │ │ │ │ ├── private.lua │ │ │ │ ├── treesitter.lua │ │ │ │ ├── tabs.lua │ │ │ │ ├── smart-size.lua │ │ │ │ ├── aider.lua │ │ │ │ ├── session.lua │ │ │ │ ├── highlight.lua │ │ │ │ ├── cutbuf.lua │ │ │ │ ├── debounce.lua │ │ │ │ ├── lsp_status.lua │ │ │ │ ├── git.lua │ │ │ │ ├── wrap.lua │ │ │ │ └── recent-wins.lua │ │ │ ├── plugins │ │ │ │ ├── conform │ │ │ │ │ ├── init.lua │ │ │ │ │ └── internal.lua │ │ │ │ ├── lua.lua │ │ │ │ ├── session.lua │ │ │ │ ├── misc.lua │ │ │ │ ├── ai.lua │ │ │ │ ├── wins-bufs.lua │ │ │ │ ├── testing.lua │ │ │ │ ├── nvim-tree │ │ │ │ │ └── decorator-quickfix.lua │ │ │ │ ├── linting.lua │ │ │ │ ├── oil.lua │ │ │ │ ├── fyler.lua │ │ │ │ └── snacks.lua │ │ │ ├── private.example.lua │ │ │ ├── colors.lua │ │ │ ├── keys.lua │ │ │ ├── zen-mode.lua │ │ │ └── settings.lua │ │ ├── window-picker │ │ │ └── hints │ │ │ │ └── data │ │ │ │ ├── stampatello.lua │ │ │ │ └── slant.lua │ │ ├── overseer │ │ │ └── template │ │ │ │ └── mise.lua │ │ └── treesj │ │ │ └── langs │ │ │ └── nu.lua │ ├── after │ │ └── queries │ │ │ ├── lua │ │ │ └── spell.scm │ │ │ ├── markdown │ │ │ ├── highlights.scm │ │ │ └── injections.scm │ │ │ ├── zig │ │ │ └── indents.scm │ │ │ ├── cython │ │ │ └── highlights.scm │ │ │ └── python │ │ │ └── injections.scm │ ├── lsp │ │ ├── systemd-lsp.lua │ │ └── cyright.lua │ ├── ftplugin │ │ └── gitcommit.lua │ ├── stylua.toml │ ├── filetype.lua │ ├── dprint.json │ ├── README.md │ ├── .types.lua │ ├── init.lua │ ├── LICENSE │ └── autoload │ │ └── user │ │ └── fn.vim ├── user-dirs.conf ├── feh │ ├── themes │ └── keys ├── ghostty │ ├── README.md │ └── themes │ │ └── lavi ├── waybar │ ├── dprint.json │ ├── config.jsonc │ ├── mullvad-status-menu.xml │ └── colors.css ├── user-dirs.dirs ├── satty │ ├── overrides.css │ └── config.toml ├── zathura │ └── zathurarc ├── bat │ └── config ├── mako │ └── config ├── htop │ └── htoprc ├── opencode │ └── opencode.jsonc ├── starship.toml └── vivid │ └── themes │ └── lavi.yml ├── nix ├── package-groups │ ├── base.nix │ ├── javascript.nix │ ├── default.nix │ ├── niri.nix │ ├── neovim.nix │ └── shell.nix ├── pkgs │ └── default.nix ├── profiles │ ├── minimal.nix │ └── dev.nix ├── overlays │ └── default.nix ├── hosts │ └── boonix │ │ ├── hardware-configuration.nix │ │ └── default.nix └── lib │ └── zfs.nix ├── justfile ├── .editorconfig ├── dprint.json ├── shell.nix └── flake.nix /config/carapace/bridge/zsh/.zshrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/user-dirs.locale: -------------------------------------------------------------------------------- 1 | en_US 2 | -------------------------------------------------------------------------------- /config/carapace/bridge/bash/.bashrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/git/.gitignore: -------------------------------------------------------------------------------- 1 | local.config 2 | -------------------------------------------------------------------------------- /config/niri/niri_tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/nushell/autoload/platform/darwin.nu: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/nushell/autoload/platform/windows.nu: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/nvim/queries/.gitignore: -------------------------------------------------------------------------------- 1 | cython/ 2 | -------------------------------------------------------------------------------- /config/user-dirs.conf: -------------------------------------------------------------------------------- 1 | enabled=False 2 | -------------------------------------------------------------------------------- /config/feh/themes: -------------------------------------------------------------------------------- 1 | feh -. -B black -j /tmp/ 2 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/.gitignore: -------------------------------------------------------------------------------- 1 | !private.lua 2 | -------------------------------------------------------------------------------- /config/nushell/.gitignore: -------------------------------------------------------------------------------- 1 | history.txt 2 | *.msgpackz 3 | -------------------------------------------------------------------------------- /config/niri/niri_tools/daemon/__init__.py: -------------------------------------------------------------------------------- 1 | """Niri tools daemon package.""" 2 | -------------------------------------------------------------------------------- /config/niri/.envrc: -------------------------------------------------------------------------------- 1 | [ -d .venv ] || uv venv 2 | source_env .venv/bin/activate 3 | -------------------------------------------------------------------------------- /config/nushell/scripts/awtrix/lib.nu: -------------------------------------------------------------------------------- 1 | export use core.nu * 2 | export use app 3 | -------------------------------------------------------------------------------- /config/nvim/after/queries/lua/spell.scm: -------------------------------------------------------------------------------- 1 | (comment) @spell 2 | (string) @spell 3 | -------------------------------------------------------------------------------- /config/git/ignore: -------------------------------------------------------------------------------- 1 | worktree/* 2 | .aider* 3 | 4 | **/.claude/settings.local.json 5 | -------------------------------------------------------------------------------- /config/nushell/scripts/awtrix/app/mod.nu: -------------------------------------------------------------------------------- 1 | export use gh 2 | export alias gh = gh run 3 | -------------------------------------------------------------------------------- /config/carapace/bridges.yaml: -------------------------------------------------------------------------------- 1 | gh: fish 2 | hub: fish 3 | mise: fish 4 | niri: fish 5 | pw: fish 6 | -------------------------------------------------------------------------------- /nix/package-groups/base.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | ... 5 | }: 6 | # with pkgs; 7 | [ 8 | ] 9 | -------------------------------------------------------------------------------- /config/nvim/after/queries/markdown/highlights.scm: -------------------------------------------------------------------------------- 1 | ; ((inline) @_inline (#match? @_inline "^\(import\|export\)")) @nospell 2 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | default: 2 | @just --list 3 | 4 | stow: 5 | stow --verbose --target="$XDG_CONFIG_HOME" --restow config 6 | -------------------------------------------------------------------------------- /config/nushell/autoload/platform/linux.nu: -------------------------------------------------------------------------------- 1 | source (if ("arch" in $nu.os-info.kernel_version) == true { "arch-linux.nu" } else { null }) 2 | -------------------------------------------------------------------------------- /config/niri/swayidle.conf: -------------------------------------------------------------------------------- 1 | timeout 300 'niri msg action power-off-monitors' 2 | timeout 345 'system lock' 3 | before-sleep 'system lock' 4 | -------------------------------------------------------------------------------- /nix/package-groups/javascript.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | ... 5 | }: 6 | with pkgs; [ 7 | # Runtimes 8 | bun 9 | ] 10 | -------------------------------------------------------------------------------- /config/nushell/scripts/git/mod.nu: -------------------------------------------------------------------------------- 1 | export use ./clone.nu * 2 | export use ./ignore.nu * 3 | export use ./push.nu * 4 | export use ./worktree.nu * 5 | -------------------------------------------------------------------------------- /config/ghostty/README.md: -------------------------------------------------------------------------------- 1 | # 👻 Maddison's Ghostty Configuration 2 | 3 | This is my personal configuration for [Ghostty](https://mitchellh.com/ghostty) 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style=space 3 | indent_size=2 4 | end_of_line=lf 5 | charset=utf-8 6 | trim_trailing_whitespace=true 7 | insert_final_newline=true 8 | -------------------------------------------------------------------------------- /config/nvim/lsp/systemd-lsp.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { 'systemd-lsp' }, 3 | filetypes = { 'systemd' }, 4 | root_markers = { 5 | '.git', 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /config/nushell/scripts/awtrix/mod.nu: -------------------------------------------------------------------------------- 1 | # Awtrix Module 2 | # Main entry point - exports everything from lib.nu 3 | 4 | export use lib.nu * 5 | export use core.nu * 6 | -------------------------------------------------------------------------------- /config/waybar/dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | }, 4 | "excludes": [ 5 | "**/*-lock.json" 6 | ], 7 | "plugins": [ 8 | "https://plugins.dprint.dev/json-0.20.0.wasm" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /nix/pkgs/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | }: { 5 | # opentui-b0o, opentui-spinner-b0o, and opencode-b0o are defined in 6 | # nix/overlays/opencode.nix to keep all related packages together 7 | } 8 | -------------------------------------------------------------------------------- /config/nvim/ftplugin/gitcommit.lua: -------------------------------------------------------------------------------- 1 | -- set up folding to hide the text in ~/.gitmessage 2 | -- which is used to prompt Copilot for better commit messages 3 | vim.wo.foldmethod = 'marker' 4 | vim.wo.foldlevel = 0 5 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/path.lua: -------------------------------------------------------------------------------- 1 | -- Re-export Path from plenary.path with fixed type annotations 2 | 3 | ---@class Path 4 | ---@field new fun(self: Path, path: string|Path): Path 5 | local Path = require 'plenary.path' 6 | 7 | return Path 8 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/conform/init.lua: -------------------------------------------------------------------------------- 1 | ---@type LazySpec[] 2 | return { 3 | { 4 | 'stevearc/conform.nvim', 5 | config = function() require('user.plugins.conform.internal').setup() end, 6 | event = 'BufWritePre', 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /config/nvim/after/queries/zig/indents.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | ; TODO: Remove once merged: 3 | ; https://github.com/nvim-treesitter/nvim-treesitter/pull/7199 4 | [ 5 | (opaque_declaration) 6 | (enum_declaration) 7 | (union_declaration) 8 | ] @indent.begin 9 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/workspace/util.lua: -------------------------------------------------------------------------------- 1 | local Path = require 'user.util.path' 2 | 3 | local M = {} 4 | 5 | M.cwd = function() 6 | local cwd = vim.uv.cwd() 7 | assert(cwd, 'Could not get current working directory') 8 | return Path:new(cwd) 9 | end 10 | 11 | return M 12 | -------------------------------------------------------------------------------- /config/nvim/stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 120 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferSingle" 6 | no_call_parentheses = true 7 | collapse_simple_statement = "FunctionOnly" 8 | 9 | [sort_requires] 10 | enabled = true 11 | -------------------------------------------------------------------------------- /config/nvim/after/queries/markdown/injections.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | ((inline) @injection.content 3 | (#lua-match? @injection.content "^%s*import%s") 4 | (#set! injection.language "tsx")) 5 | ((inline) @injection.content 6 | (#lua-match? @injection.content "^%s*export%s") 7 | (#set! injection.language "tsx")) 8 | -------------------------------------------------------------------------------- /config/nushell/autoload/platform.nu: -------------------------------------------------------------------------------- 1 | source ( 2 | if $nu.os-info.name == "linux" { 3 | "platform/linux.nu" 4 | } else if $nu.os-info.name == "darwin" { 5 | "platform/darwin.nu" 6 | } else if $nu.os-info.name == "windows" { 7 | "platform/windows.nu" 8 | } else { 9 | null 10 | } 11 | ) 12 | -------------------------------------------------------------------------------- /config/carapace/bridge/fish/config.fish: -------------------------------------------------------------------------------- 1 | gh completion -s fish | source 2 | mise completion fish | source 3 | pw completion fish | source 4 | 5 | # Add -f to niri completions to avoid file/path suggestions 6 | niri completions fish | string replace -r '^complete -c niri (?!.*-[fF])(.*)' 'complete -c niri -f $1' | source 7 | -------------------------------------------------------------------------------- /config/nvim/filetype.lua: -------------------------------------------------------------------------------- 1 | vim.filetype.add { 2 | extension = { 3 | astro = 'astro', 4 | mdx = 'mdx', 5 | pyx = 'cython', 6 | pxd = 'cython', 7 | }, 8 | pattern = { 9 | ['**/__snapshots__/*.ts.snap'] = { 'jsonc' }, 10 | ['**/__snapshots__/*.js.snap'] = { 'jsonc' }, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/private.lua: -------------------------------------------------------------------------------- 1 | local ok, private = pcall(require, 'user.private') 2 | if not ok then 3 | vim.defer_fn( 4 | function() vim.notify_once('[nvim-conf] Failed to load `lua/user/private.lua`', vim.log.levels.WARN) end, 5 | 100 6 | ) 7 | private = {} 8 | end 9 | 10 | ---@cast private PrivateConfig 11 | return private 12 | -------------------------------------------------------------------------------- /nix/profiles/minimal.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | ... 5 | }: let 6 | packageGroups = import ../package-groups/default.nix { 7 | inherit inputs pkgs; 8 | }; 9 | in 10 | inputs.flakey-profile.lib.mkProfile { 11 | inherit pkgs; 12 | 13 | pinned = {nixpkgs = toString inputs.nixpkgs;}; 14 | 15 | paths = packageGroups.base; 16 | } 17 | -------------------------------------------------------------------------------- /nix/package-groups/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | ... 5 | }: { 6 | base = import ./base.nix {inherit inputs pkgs;}; 7 | javascript = import ./javascript.nix {inherit inputs pkgs;}; 8 | neovim = import ./neovim.nix {inherit inputs pkgs;}; 9 | niri = import ./niri.nix {inherit inputs pkgs;}; 10 | shell = import ./shell.nix {inherit inputs pkgs;}; 11 | } 12 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/lua.lua: -------------------------------------------------------------------------------- 1 | ---@type LazySpec[] 2 | return { 3 | { 4 | 'folke/lazydev.nvim', 5 | ft = 'lua', 6 | opts = { 7 | library = { 8 | { 'lazy.nvim', words = { 'lazy', 'LazySpec' } }, 9 | { path = 'luvit-meta/library', words = { 'vim%.uv' } }, 10 | }, 11 | }, 12 | }, 13 | 'Bilal2453/luvit-meta', -- `vim.uv` typings 14 | } 15 | -------------------------------------------------------------------------------- /config/nushell/autoload/platform/arch-linux.nu: -------------------------------------------------------------------------------- 1 | source ./systemd.nu 2 | 3 | use ./arch-linux.mod.nu * 4 | 5 | alias pss = pacman -Ss 6 | alias psi = pacman -Si 7 | alias pq = pacman -Q 8 | alias pqi = pacman -Qi 9 | alias pqs = pacman -Qs 10 | alias pql = pacman -Ql 11 | alias pqo = pacman -Qo 12 | 13 | alias sp = sudo pacman 14 | alias sps = sudo pacman -S 15 | alias spr = sudo pacman -Rsc 16 | -------------------------------------------------------------------------------- /config/nvim/after/queries/cython/highlights.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | 3 | [ 4 | "(" 5 | ")" 6 | "[" 7 | "]" 8 | "{" 9 | "}" 10 | ] @punctuation.bracket 11 | 12 | (interpolation 13 | "{" @punctuation.special 14 | "}" @punctuation.special) 15 | 16 | (type_conversion) @function.macro 17 | 18 | [ 19 | "," 20 | "." 21 | ":" 22 | ";" 23 | (ellipsis) 24 | ] @punctuation.delimiter 25 | -------------------------------------------------------------------------------- /config/user-dirs.dirs: -------------------------------------------------------------------------------- 1 | XDG_DOWNLOAD_DIR="$HOME/downloads" 2 | XDG_PUBLICSHARE_DIR="$HOME/share" 3 | XDG_DOCUMENTS_DIR="$HOME/documents" 4 | XDG_MUSIC_DIR="$HOME/media/music" 5 | XDG_PICTURES_DIR="$HOME/media/pictures" 6 | XDG_SCREENSHOTS_DIR="$HOME/media/screenshots" 7 | XDG_VIDEOS_DIR="$HOME/media/videos" 8 | XDG_DESKTOP_DIR="$HOME/.local/desktop" 9 | XDG_TEMPLATES_DIR="$HOME/.local/templates" 10 | -------------------------------------------------------------------------------- /config/satty/overrides.css: -------------------------------------------------------------------------------- 1 | /* Make toolbars fully opaque instead of semi-transparent */ 2 | .toolbar { 3 | background: #000000; 4 | } 5 | 6 | /* Add borders to make swatches more visible */ 7 | .toolbar-bottom button { 8 | border: 1px solid #666666; 9 | border-radius: 4px; 10 | margin: 2px; 11 | } 12 | 13 | .toolbar-bottom button:checked { 14 | border: 2px solid #ffffff; 15 | } 16 | -------------------------------------------------------------------------------- /nix/package-groups/niri.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | ... 5 | }: 6 | with pkgs; [ 7 | # Niri Wayland compositor (unstable/nightly) 8 | # niri-unstable 9 | wlr-which-key-b0o # modal keybindings (b0o fork) 10 | # TODO: re-enable once my PR is merged: https://github.com/b0o/wl-clipboard-rs/tree/feat-cli-copy-multi 11 | # wl-clipboard-rs # wl-clipboard replacement (wl-copy/wl-paste), written in Rust 12 | ] 13 | -------------------------------------------------------------------------------- /config/git/local.example.config: -------------------------------------------------------------------------------- 1 | [user] 2 | email = me@example.com 3 | name = My Name 4 | signingkey = XXXXXXXXXXXXXXXX 5 | birthday = mm-dd-yyyy 6 | 7 | [github] 8 | user = username 9 | password = github_pat_XXXXX 10 | 11 | [url "git@github-username:username"] 12 | pushInsteadOf = https://github.com/username 13 | 14 | [url "https://github.com"] 15 | insteadOf = git://github.com 16 | 17 | # vim: ft=gitconfig commentstring=#%s 18 | -------------------------------------------------------------------------------- /config/nvim/dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "trailingCommas": "never" 4 | }, 5 | "markdown": {}, 6 | "toml": {}, 7 | "excludes": [ 8 | "**/*-lock.json" 9 | ], 10 | "plugins": [ 11 | "https://plugins.dprint.dev/json-0.19.3.wasm", 12 | "https://plugins.dprint.dev/markdown-0.17.8.wasm", 13 | "https://plugins.dprint.dev/toml-0.6.2.wasm", 14 | "https://plugins.dprint.dev/typescript-0.91.6.wasm" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /config/nvim/lua/user/private.example.lua: -------------------------------------------------------------------------------- 1 | -- Copy this file to lua/user/private.lua and edit it to add your private configuration. 2 | -- lua/user/private.lua is ignored by git. 3 | 4 | ---@module 'obsidian' 5 | 6 | ---@class PrivateConfig 7 | ---@field obsidian_vault? obsidian.workspace.WorkspaceSpec 8 | local M = { 9 | obsidian_vault = { 10 | name = 'name', 11 | path = '/path/to/obsidian/vault', 12 | }, 13 | } 14 | 15 | return M 16 | -------------------------------------------------------------------------------- /nix/profiles/dev.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | ... 5 | }: let 6 | packageGroups = import ../package-groups/default.nix { 7 | inherit inputs pkgs; 8 | }; 9 | in 10 | inputs.flakey-profile.lib.mkProfile { 11 | inherit pkgs; 12 | 13 | pinned = {nixpkgs = toString inputs.nixpkgs;}; 14 | 15 | paths = 16 | packageGroups.base 17 | ++ packageGroups.shell 18 | ++ packageGroups.neovim 19 | ++ packageGroups.javascript 20 | ++ packageGroups.niri; 21 | } 22 | -------------------------------------------------------------------------------- /config/nvim/README.md: -------------------------------------------------------------------------------- 1 | ## Maddison's Neovim configuration 2 | 3 | This is my Neovim configuration, cobbled together over several 4 | years. There are lots of things in here that are hyper-specific to my setup, but if you see anything interesting, feel free to steal it. 5 | 6 | ## Screenshots 7 | 8 | nvim 9 | 10 | ## License 11 | 12 | © 2015-2025 Maddison Hellstrom 13 | 14 | MIT License 15 | -------------------------------------------------------------------------------- /config/nvim/lsp/cyright.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { 'cyright', '--stdio' }, 3 | filetypes = { 'cython' }, 4 | root_markers = { 5 | 'pyproject.toml', 6 | 'setup.py', 7 | 'setup.cfg', 8 | 'requirements.txt', 9 | 'Pipfile', 10 | 'pyrightconfig.json', 11 | '.git', 12 | }, 13 | settings = { 14 | python = { 15 | analysis = { 16 | autoSearchPaths = true, 17 | useLibraryCodeForTypes = true, 18 | diagnosticMode = 'openFilesOnly', 19 | }, 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /config/niri/niri_tools/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Common utilities and constants for Niri window manager scripts. 4 | """ 5 | 6 | import os 7 | from pathlib import Path 8 | 9 | runtime_dir = os.environ.get("XDG_RUNTIME_DIR", "/tmp") 10 | 11 | # Socket and file paths 12 | SOCKET_PATH = Path(runtime_dir) / "niri-tools.sock" 13 | STATE_FILE = Path(runtime_dir) / "niri-tools-state.json" 14 | 15 | # Scratchpad constants 16 | SCRATCHPAD_WORKSPACE = "󰪷" 17 | CONFIG_DIR = Path.home() / ".config" / "niri" 18 | CONFIG_FILE = CONFIG_DIR / "scratchpads.yaml" 19 | -------------------------------------------------------------------------------- /config/nushell/autoload/platform/systemd.nu: -------------------------------------------------------------------------------- 1 | alias sc = systemctl 2 | alias scs = systemctl status 3 | alias scu = systemctl --user 4 | alias scudr = systemctl --user daemon-reload 5 | alias scus = systemctl --user status 6 | alias ssc = sudo systemctl 7 | alias sscdr = sudo systemctl daemon-reload 8 | 9 | alias jc = journalctl 10 | alias jf = journalctl --follow --unit 11 | alias ju = journalctl --user 12 | alias juf = journalctl --user --follow --unit 13 | alias jcf = journalctl --follow --unit 14 | alias jcu = journalctl --user 15 | alias jcuf = journalctl --user --follow --unit 16 | -------------------------------------------------------------------------------- /config/nvim/.types.lua: -------------------------------------------------------------------------------- 1 | ---@meta 2 | 3 | ---@class CallableTable 4 | ---@operator call:any 5 | 6 | ---@alias Callable fun(...)|CallableTable 7 | 8 | ---@class AutocmdEvent 9 | ---@field id number @the id of the autocommand 10 | ---@field event string @the name of the triggered event 11 | ---@field group number|nil @the autocommand group id, if any 12 | ---@field match string @the expanded value of 13 | ---@field buf number @the expanded value of 14 | ---@field file string @the expanded value of 15 | ---@field data any @arbitrary data passed from `nvim_exec_autocmds()` 16 | -------------------------------------------------------------------------------- /nix/overlays/default.nix: -------------------------------------------------------------------------------- 1 | {inputs, ...}: let 2 | additions = final: _prev: 3 | import ../pkgs { 4 | inherit inputs; 5 | pkgs = final; 6 | }; 7 | 8 | modifications = final: prev: { 9 | wlr-which-key-b0o = inputs.wlr-which-key-b0o.packages.${final.system}.default; 10 | # FIXME: how to properly add bash-env-nushell? 11 | # bash-env-nushell = inputs.bash-env-nushell.flakePkgs.bash-env-nushell.${final.system}.default; 12 | }; 13 | in 14 | inputs.nixpkgs.lib.composeManyExtensions [ 15 | additions 16 | modifications 17 | (import ./opencode.nix {inherit inputs;}) 18 | ] 19 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | }, 4 | "markdown": { 5 | }, 6 | "toml": { 7 | }, 8 | "malva": { 9 | }, 10 | "markup": { 11 | }, 12 | "yaml": { 13 | }, 14 | "excludes": [ 15 | "**/*-lock.json", 16 | "**/node_modules" 17 | ], 18 | "plugins": [ 19 | "https://plugins.dprint.dev/json-0.21.0.wasm", 20 | "https://plugins.dprint.dev/markdown-0.20.0.wasm", 21 | "https://plugins.dprint.dev/toml-0.7.0.wasm", 22 | "https://plugins.dprint.dev/g-plane/malva-v0.15.1.wasm", 23 | "https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.1.wasm", 24 | "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /config/nushell/autoload/aliases.nu: -------------------------------------------------------------------------------- 1 | # shell 2 | alias core-cd = cd 3 | alias cd = core-cd --physical 4 | alias cdp = cd - 5 | 6 | # system 7 | alias s = sudo 8 | alias se = sudo -E 9 | alias sv = sudoedit 10 | alias sev = sudoedit 11 | 12 | # nvim 13 | alias v = nvim 14 | alias vs = nvim +SessionLoad 15 | 16 | # processes 17 | alias pk = pkill --count --echo 18 | alias pkk = pkill --count --echo -KILL 19 | alias pga = pgrep -a 20 | 21 | # misc 22 | alias zj = zellij 23 | 24 | # docs 25 | alias m = man 26 | 27 | # utilities 28 | alias md = gh markdown-preview -p 6419 --disable-auto-open 29 | alias serve = python -m http.server 30 | alias icat = kitty +kitten icat 31 | 32 | # ai 33 | alias oc = opencode 34 | -------------------------------------------------------------------------------- /nix/hosts/boonix/hardware-configuration.nix: -------------------------------------------------------------------------------- 1 | # Do not modify this file! It was generated by 'nixos-generate-config' 2 | # and may be overwritten by future invocations. Please make changes 3 | # to /etc/nixos/configuration.nix instead. 4 | { 5 | config, 6 | lib, 7 | modulesPath, 8 | ... 9 | }: { 10 | imports = [ 11 | (modulesPath + "/installer/scan/not-detected.nix") 12 | ]; 13 | 14 | boot.initrd.availableKernelModules = ["nvme" "xhci_pci" "ahci" "usbhid"]; 15 | boot.initrd.kernelModules = []; 16 | boot.kernelModules = ["kvm-amd"]; 17 | boot.extraModulePackages = []; 18 | 19 | nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 20 | hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 21 | } 22 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | # Shell for bootstrapping flake-enabled nix 2 | { 3 | pkgs ? let 4 | # If pkgs is not defined, instanciate nixpkgs from locked commit 5 | lock = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.nixpkgs.locked; 6 | nixpkgs = fetchTarball { 7 | url = "https://github.com/nixos/nixpkgs/archive/${lock.rev}.tar.gz"; 8 | sha256 = lock.narHash; 9 | }; 10 | system = builtins.currentSystem; 11 | overlays = []; # Explicit blank overlay to avoid interference 12 | in 13 | import nixpkgs {inherit system overlays;}, 14 | ... 15 | }: 16 | pkgs.mkShell { 17 | # Enable experimental features without having to specify the argument 18 | NIX_CONFIG = "experimental-features = nix-command flakes"; 19 | nativeBuildInputs = with pkgs; [nix git]; 20 | } 21 | -------------------------------------------------------------------------------- /config/git/message: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Types: fix, feat, docs, style, refactor, test, chore, deps, perf, ci, build, revert, lint 5 | # {{{ 6 | # 7 | # Examples: 8 | # 9 | # fix: check for null before calling method 10 | # feat: add foobar method to Baz class 11 | # docs: add example to README 12 | # style: remove trailing whitespace 13 | # refactor(foo): extract bar method 14 | # test(Baz): add unit tests for .qux() 15 | # chore: reword landing page copy 16 | # 17 | # Instructions: 18 | # 19 | # After this comment, the rest of this file contains a summary of the changes to be committed. 20 | # The first line of this file should be the subject of the commit describing the changes. 21 | # The next line should be blank, optionally followed by a more detailed description. 22 | # Refer to the rest of this file for the exact changes being made. 23 | # 24 | # }}} 25 | 26 | -------------------------------------------------------------------------------- /nix/package-groups/neovim.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | ... 5 | }: 6 | with pkgs; [ 7 | # Give neovim access to a C compiler for tree-sitter grammars 8 | (neovim.overrideAttrs (old: { 9 | propagatedBuildInputs = 10 | (old.propagatedBuildInputs or []) ++ [stdenv.cc.cc]; 11 | })) 12 | 13 | # Tree Sitter + Node for installing Treesitter Grammars 14 | inputs.neovim-nightly-overlay.packages.${pkgs.system}.tree-sitter 15 | 16 | ### Lanugage Servers / Tools 17 | # Nix 18 | alejandra # black-inspired formatting 19 | nil # language server 20 | statix # linter 21 | 22 | ## LSPs 23 | just-lsp # lsp server for just files 24 | tombi # TOML Formatter / Linter / Language Server 25 | vscode-langservers-extracted # vscode-{css,eslint,html,json,markdown}-langserver 26 | 27 | ## Formatters 28 | dprint # multi-language formatter 29 | stylua # lua formatter 30 | ] 31 | -------------------------------------------------------------------------------- /config/nushell/autoload/git.nu: -------------------------------------------------------------------------------- 1 | alias git = hub # important: set this before the `use` line so that we always use hub 2 | 3 | export use git * 4 | 5 | # see ~/.config/git/alias.config for git alias definitions 6 | alias g = git 7 | alias gl = git ls 8 | alias gla = git la 9 | alias gll = git ll 10 | alias glla = git lla 11 | alias glg = git lg 12 | 13 | alias gst = git st 14 | alias gd = git diff 15 | 16 | alias ga = git add 17 | alias gaa = git add --all 18 | alias gai = git add --interactive 19 | 20 | alias gp = git p 21 | alias gpa = git pa 22 | alias gpao = git pao 23 | 24 | alias gc = git commit --verbose 25 | alias gca = git commit --all --verbose 26 | alias gcA = git commit --amend --verbose 27 | 28 | alias gr = git remote --verbose 29 | 30 | alias gtv = git tag-version 31 | 32 | alias gigg = gig -w 33 | 34 | alias gwc = cd (gw-current) 35 | alias gwco = gw-current 36 | 37 | alias gcclb = gccl --bare 38 | -------------------------------------------------------------------------------- /config/waybar/config.jsonc: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "output": ["DP-2"], 4 | "include": [ 5 | "~/.config/waybar/config.base.jsonc", 6 | ], 7 | "modules-left": [ 8 | "niri/workspaces", 9 | ], 10 | "modules-center": [ 11 | "niri/window", 12 | ], 13 | "modules-right": [ 14 | "custom/linear", 15 | "custom/command-center", 16 | "mpris", 17 | "pulseaudio", 18 | "bluetooth", 19 | "idle_inhibitor", 20 | "systemd-failed-units", 21 | "custom/mullvad", 22 | "clock", 23 | "tray", 24 | ], 25 | }, 26 | { 27 | "output": ["DP-1", "DP-3"], 28 | "include": [ 29 | "~/.config/waybar/config.base.jsonc", 30 | ], 31 | "modules-left": [ 32 | "niri/workspaces", 33 | ], 34 | "modules-center": [ 35 | "niri/window", 36 | ], 37 | "modules-right": [ 38 | "clock", 39 | ], 40 | }, 41 | ] 42 | -------------------------------------------------------------------------------- /config/waybar/mullvad-status-menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Approve All 7 | 8 | 9 | 10 | 11 | Unapprove All 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Check on Web 20 | 21 | 22 | 23 | 24 | Show Menu 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/session.lua: -------------------------------------------------------------------------------- 1 | ---@type LazySpec[] 2 | return { 3 | { 4 | 'mbbill/undotree', 5 | cmd = { 'UndotreeToggle', 'UndotreeHide', 'UndotreeShow', 'UndotreeFocus' }, 6 | keys = { 7 | { 'ut', 'UndotreeToggle', desc = 'Undotree: Toggle' }, 8 | }, 9 | config = function() 10 | vim.g.undotree_SetFocusWhenToggle = 1 11 | end, 12 | }, 13 | { 14 | 'Shatur/neovim-session-manager', 15 | config = function() 16 | require('session_manager').setup { 17 | autoload_mode = require('session_manager.config').AutoloadMode.Disabled, 18 | autosave_last_session = false, 19 | } 20 | end, 21 | }, 22 | { 23 | 'jedrzejboczar/exrc.nvim', 24 | lazy = false, 25 | opts = { 26 | --- IMPORTANT! When you use on_vim_enter=true do not lazy-load 27 | on_vim_enter = true, -- Load exrc from current directory on start 28 | use_telescope = false, 29 | }, 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /config/feh/keys: -------------------------------------------------------------------------------- 1 | menu_parent Left 2 | menu_child Right 3 | menu_down Down 4 | menu_up Up 5 | 6 | scroll_left h a 7 | scroll_right l d 8 | scroll_up k w 9 | scroll_down j s 10 | 11 | scroll_left_page C-h 12 | scroll_right_page C-l 13 | scroll_up_page C-k 14 | scroll_down_page C-j 15 | 16 | toggle_aliasing A 17 | toggle_filenames d 18 | toggle_pointer o 19 | toggle_fullscreen f 20 | 21 | zoom_in J plus 22 | zoom_out K underscore 23 | zoom_fit parenright 24 | zoom_default parenleft 25 | 26 | 27 | prev_img E semicolon C-n 28 | next_img R apostrophe C-p 29 | reload_image r 30 | size_to_image equal 31 | next_dir bracketright 32 | prev_dir bracketleft 33 | orient_3 braceright 34 | orient_1 braceleft 35 | flip greater 36 | mirror less 37 | remove Delete 38 | 39 | close q Q 40 | 41 | # vim: set ft=dosini: 42 | -------------------------------------------------------------------------------- /config/nushell/scripts/git/ignore.nu: -------------------------------------------------------------------------------- 1 | export def gig [ 2 | --write (-w) # write to .gitignore 3 | ...args: string # gitignore templates to fetch 4 | ] { 5 | # Check if in git repo when writing 6 | if $write { 7 | let git_check = (do --ignore-errors { ^git status } | complete) 8 | if $git_check.exit_code != 0 { 9 | error make {msg: "fatal: not a git repository"} 10 | } 11 | } 12 | 13 | # Build query string 14 | let q = ($args | str join ",") 15 | 16 | # Fetch gitignore rules 17 | let result = ( 18 | ^curl -f -L -s $"https://www.toptal.com/developers/gitignore/api/($q)" 19 | | complete 20 | ) 21 | 22 | if $result.exit_code != 0 { 23 | print -e $"Not found: ($q)" 24 | return 25 | } 26 | 27 | let content = $result.stdout 28 | 29 | if $write { 30 | # Append to .gitignore 31 | $content | save --append .gitignore 32 | print -e $"Updated .gitignore with rules for ($args | str join ' ')" 33 | } else { 34 | # Print to stdout 35 | print $content 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /config/git/config: -------------------------------------------------------------------------------- 1 | [diff] 2 | tool = nvim -d 3 | algorithm = histogram 4 | 5 | [merge] 6 | tool = vimdiff 7 | conflictstyle = zdiff3 8 | 9 | [rebase] 10 | autosquash = true 11 | 12 | [core] 13 | pager = delta 14 | 15 | [interactive] 16 | diffFilter = delta 17 | 18 | [commit] 19 | template = ~/.config/git/message 20 | gpgsign = true 21 | verbose = true 22 | 23 | [rerere] 24 | enabled = true 25 | autoUpdate = true 26 | 27 | [pull] 28 | ff = only 29 | 30 | [init] 31 | defaultBranch = main 32 | 33 | [credential] 34 | helper = cache 35 | 36 | [hub] 37 | protocol = https 38 | 39 | [filter "lfs"] 40 | clean = git-lfs clean -- %f 41 | smudge = git-lfs smudge -- %f 42 | process = git-lfs filter-process 43 | required = true 44 | 45 | [status] 46 | showUntrackedFiles = all 47 | 48 | [includeIf "gitdir:$XDG_CACHE_HOME/paru/**"] 49 | path = $XDG_CACHE_HOME/paru/.gitconfig 50 | 51 | [include] 52 | path = ./local.config 53 | path = ./alias.config 54 | path = ./delta.config 55 | 56 | # vim: ft=gitconfig commentstring=#%s 57 | -------------------------------------------------------------------------------- /config/nvim/init.lua: -------------------------------------------------------------------------------- 1 | vim.loader.enable() 2 | 3 | local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim' 4 | if not vim.uv.fs_stat(lazypath) then 5 | vim.fn.system { 6 | 'git', 7 | 'clone', 8 | '--filter=blob:none', 9 | 'https://github.com/folke/lazy.nvim.git', 10 | '--branch=stable', 11 | lazypath, 12 | } 13 | end 14 | vim.opt.rtp:prepend(lazypath) 15 | 16 | local lazyutil = require 'user.util.lazy' 17 | _G.lazy_require = lazyutil.require 18 | _G.very_lazy = lazyutil.very_lazy 19 | 20 | require 'user.settings' 21 | require 'user.commands' 22 | 23 | require('lazy').setup({ 24 | import = 'user.plugins', 25 | }, { 26 | defaults = { lazy = true }, 27 | ui = { border = 'rounded' }, 28 | dev = vim.env.GIT_PROJECTS_DIR and { 29 | path = vim.env.GIT_PROJECTS_DIR .. '/nvim', 30 | fallback = true, 31 | } or nil, 32 | change_detection = { enabled = false }, 33 | }) 34 | 35 | very_lazy(function() 36 | require 'user.mappings' 37 | require 'user.autocmds' 38 | require('user.util.smart-size').enable_autoresize() 39 | end) 40 | -------------------------------------------------------------------------------- /config/carapace/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/zsh,fish 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=zsh,fish 3 | 4 | ### Fish ### 5 | fishd.* 6 | fish_history 7 | fish_variables 8 | config.local.fish 9 | 10 | ### Zsh ### 11 | # Zsh compiled script + zrecompile backup 12 | *.zwc 13 | *.zwc.old 14 | 15 | # Zsh completion-optimization dumpfile 16 | *zcompdump* 17 | 18 | # Zsh history 19 | .zsh_history 20 | 21 | # Zsh sessions 22 | .zsh_sessions 23 | 24 | # Zsh zcalc history 25 | .zcalc_history 26 | 27 | # A popular plugin manager's files 28 | ._zinit 29 | .zinit_lstupd 30 | 31 | # zdharma/zshelldoc tool's files 32 | zsdoc/data 33 | 34 | # robbyrussell/oh-my-zsh/plugins/per-directory-history plugin's files 35 | # (when set-up to store the history in the local directory) 36 | .directory_history 37 | 38 | # MichaelAquilina/zsh-autoswitch-virtualenv plugin's files 39 | # (for Zsh plugins using Python) 40 | .venv 41 | 42 | # Zunit tests' output 43 | /tests/_output/* 44 | !/tests/_output/.gitkeep 45 | 46 | # End of https://www.toptal.com/developers/gitignore/api/zsh,fish 47 | -------------------------------------------------------------------------------- /config/zathura/zathurarc: -------------------------------------------------------------------------------- 1 | set selection-clipboard clipboard 2 | map recolor 3 | 4 | set font "inconsolata 15" 5 | set default-bg "#000000" #00 6 | set default-fg "#F7F7F6" #01 7 | 8 | set statusbar-fg "#B0B0B0" #04 9 | set statusbar-bg "#202020" #01 10 | 11 | set inputbar-bg "#151515" #00 currently not used 12 | set inputbar-fg "#FFFFFF" #02 13 | 14 | set notification-error-bg "#AC4142" #08 15 | set notification-error-fg "#151515" #00 16 | 17 | set notification-warning-bg "#AC4142" #08 18 | set notification-warning-fg "#151515" #00 19 | 20 | set highlight-color "#F4BF75" #0A 21 | set highlight-active-color "#6A9FB5" #0D 22 | 23 | set completion-highlight-fg "#151515" #02 24 | set completion-highlight-bg "#90A959" #0C 25 | 26 | set completion-bg "#303030" #02 27 | set completion-fg "#E0E0E0" #0C 28 | 29 | set notification-bg "#90A959" #0B 30 | set notification-fg "#151515" #00 31 | 32 | set recolor "false" 33 | set recolor-lightcolor "#000000" #00 34 | set recolor-darkcolor "#E0E0E0" #06 35 | set recolor-reverse-video "true" # requires zathura-pdf-poppler 36 | set recolor-keephue "true" 37 | 38 | # Latex support 39 | set synctex true 40 | -------------------------------------------------------------------------------- /config/nvim/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2015-2023 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the “Software”), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/treesitter.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | ---Motion for selecting the best-match named node under the cursor/selection. 4 | ---Via https://www.reddit.com/r/neovim/comments/1ckd1rs/helpful_treesitter_node_motion/ 5 | M.node_motion = function() 6 | local bufnr = vim.api.nvim_win_get_buf(0) 7 | local ok, lang_tree = pcall(vim.treesitter.get_parser, bufnr) 8 | if not ok or not lang_tree then 9 | return 10 | end 11 | local cpos = vim.api.nvim_win_get_cursor(0) 12 | local vpos = vim.fn.getpos 'v' 13 | local node = lang_tree:named_node_for_range( 14 | { vpos[2] - 1, vpos[3] - 1, cpos[1] - 1, cpos[2] + 1 }, 15 | { ignore_injections = false } 16 | ) 17 | if not node then 18 | return 19 | end 20 | local mode = vim.fn.mode() 21 | if mode == 'v' or mode == 'V' or mode == vim.api.nvim_replace_termcodes('', true, true, true) then 22 | vim.cmd('normal! ' .. mode) 23 | end 24 | local start_row0, start_col0, end_row0, end_col0 = node:range() 25 | vim.api.nvim_win_set_cursor(0, { start_row0 + 1, start_col0 }) 26 | vim.cmd 'normal! v' 27 | vim.api.nvim_win_set_cursor(0, { end_row0 + 1, end_col0 - 1 }) 28 | end 29 | 30 | return M 31 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/tabs.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | state = { prev_win = {} }, 3 | } 4 | 5 | local function is_ignored_buf(bufnr) 6 | bufnr = bufnr or 0 7 | if not vim.bo[bufnr].buflisted then 8 | return true 9 | end 10 | if vim.api.nvim_buf_get_name(bufnr) == '' then 11 | return true 12 | end 13 | if vim.bo[bufnr].buftype ~= '' then 14 | return true 15 | end 16 | return false 17 | end 18 | 19 | function M.is_ignored_win(winid) 20 | winid = winid or 0 21 | if is_ignored_buf(vim.api.nvim_win_get_buf(winid)) then 22 | return true 23 | end 24 | if vim.fn.win_gettype(winid) ~= '' then 25 | return true 26 | end 27 | return false 28 | end 29 | 30 | function M.get_most_recent_win(tabpage) 31 | local win = vim.api.nvim_tabpage_get_win(tabpage) 32 | if 33 | M.is_ignored_win(win) 34 | and M.state.prev_win[tabpage] ~= nil 35 | and vim.api.nvim_win_is_valid(M.state.prev_win[tabpage]) 36 | then 37 | win = M.state.prev_win[tabpage] 38 | end 39 | M.state.prev_win[tabpage] = win 40 | return win 41 | end 42 | 43 | function M.get_most_recent_buf(tabpage) 44 | return vim.api.nvim_win_get_buf(M.get_most_recent_win(tabpage)) 45 | end 46 | 47 | return M 48 | -------------------------------------------------------------------------------- /config/bat/config: -------------------------------------------------------------------------------- 1 | # This is `bat`s configuration file. Each line either contains a comment or 2 | # a command-line option that you want to pass to `bat` by default. You can 3 | # run `bat --help` to get a list of all possible configuration options. 4 | 5 | # Specify desired highlighting theme (e.g. "TwoDark"). Run `bat --list-themes` 6 | # for a list of all available themes 7 | --theme="lavi" 8 | 9 | # Enable this to use italic text on the terminal. This is not supported on all 10 | # terminal emulators (like tmux, by default): 11 | #--italic-text=always 12 | 13 | # Uncomment the following line to disable automatic paging: 14 | #--paging=never 15 | 16 | # Uncomment the following line if you are using less version >= 551 and want to 17 | # enable mouse scrolling support in `bat` when running inside tmux. This might 18 | # disable text selection, unless you press shift. 19 | #--pager="less --RAW-CONTROL-CHARS --quit-if-one-screen --mouse" 20 | 21 | # Syntax mappings: map a certain filename pattern to a language. 22 | # Example 1: use the C++ syntax for Arduino .ino files 23 | # Example 2: Use ".gitignore"-style highlighting for ".ignore" files 24 | #--map-syntax "*.ino:C++" 25 | #--map-syntax ".ignore:Git Ignore" 26 | -------------------------------------------------------------------------------- /config/nushell/scripts/git/push.nu: -------------------------------------------------------------------------------- 1 | # Git push with smart default remote and branch 2 | # If no remote is specified, use "origin" or the first remote with "push" access 3 | # If no branch is specified, use the current branch 4 | export def --wrapped gpp [ 5 | ...args: string 6 | ] { 7 | mut opts = [] 8 | mut positional = [] 9 | for item in $args { 10 | if ($item | to text | str starts-with "-") { 11 | $opts = $opts | append $item 12 | } else { 13 | $positional = $positional | append $item 14 | } 15 | } 16 | if ($positional | length) < 2 { 17 | let remotes = (git remote -v 18 | | lines 19 | | each { parse --regex '(?\S+)\s+(?\S+)\s+\((?\w+)\)' } 20 | | flatten 21 | | where kind == 'push') 22 | let default_remote = if ($remotes | any { $in.remote == "origin" }) { 23 | "origin" 24 | } else { 25 | $remotes | first | get remote 26 | } 27 | let default_branch = (^git rev-parse --abbrev-ref HEAD) 28 | if ($positional | length) == 0 { 29 | $positional = [$default_remote $default_branch] 30 | } else if ($positional | length) == 1 { 31 | $positional = ($positional | append $default_branch) 32 | } 33 | } 34 | git push ...$opts ...$positional 35 | } 36 | -------------------------------------------------------------------------------- /config/nushell/autoload/platform/arch-linux.mod.nu: -------------------------------------------------------------------------------- 1 | def complete-pacman-installed-package [spans: list] { 2 | if ($spans | last | str starts-with "-") { 3 | return [] 4 | } 5 | let spans = $spans | where {|s| not ($s | str starts-with "-")} 6 | ^carapace pacman nushell pacman -Q ...$spans | from json 7 | } 8 | 9 | # Like pacman -Ql but only show executable files 10 | @complete complete-pacman-installed-package 11 | export def pqlx [ 12 | --all (-a) # Show all executable files, including those not in PATH 13 | ...pkgs: string 14 | ] { 15 | def is-in-path [file: string] { 16 | for p in $env.PATH { 17 | if ($file | str starts-with $p) { 18 | return true 19 | } 20 | } 21 | false 22 | } 23 | pacman -Ql ...$pkgs 24 | | lines 25 | | par-each { |entry| 26 | let item = $entry | split column -n 2 ' ' package file | first 27 | let target = $item.file | path expand 28 | if ($target | path type) != "file" { 29 | return null 30 | } 31 | let ls = ls -l $target | first 32 | let executable = ($ls.mode | str contains "x") 33 | let in_path = ($all or (is-in-path $item.file)) 34 | if not (($ls.type == "file") and $executable and $in_path) { 35 | return null 36 | } 37 | $item 38 | } 39 | | sort-by package file 40 | } 41 | -------------------------------------------------------------------------------- /config/ghostty/themes/lavi: -------------------------------------------------------------------------------- 1 | # Background color for the window. 2 | background = #211D34 3 | 4 | # Foreground color for the window. 5 | foreground = #FFF1E0 6 | 7 | # The foreground and background color for selection. If this is not set, then 8 | # the selection color is just the inverted window background and foreground 9 | # (note: not to be confused with the cell bg/fg). 10 | selection-foreground = #000000 11 | 12 | selection-background = #FFFFFF 13 | 14 | # Color palette for the 256 color form that many terminal applications use. 15 | # The syntax of this configuration is `N=HEXCODE` where `N` is 0 to 255 (for 16 | # the 256 colors in the terminal color table) and `HEXCODE` is a typical RGB 17 | # color code such as `#AABBCC`. 18 | # 19 | # For definitions on all the codes [see this cheat 20 | # sheet](https://www.ditig.com/256-colors-cheat-sheet). 21 | palette = 0=#282c34 22 | palette = 1=#e06c75 23 | palette = 2=#98c379 24 | palette = 3=#e5c07b 25 | palette = 4=#61afef 26 | palette = 5=#c678dd 27 | palette = 6=#56b6c2 28 | palette = 7=#abb2bf 29 | palette = 8=#5c6370 30 | palette = 9=#e06c75 31 | palette = 10=#98c379 32 | palette = 11=#e5c07b 33 | palette = 12=#61afef 34 | palette = 13=#c678dd 35 | palette = 14=#56b6c2 36 | palette = 15=#ffffff 37 | 38 | # The color of the cursor. If this is not set, a default will be chosen. 39 | cursor-color = #D8DEE9 40 | 41 | # vim: set ft=dosini commentstring=#\ %s: 42 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/smart-size.lua: -------------------------------------------------------------------------------- 1 | ---- Auto-resize 2 | --- Provides window auto-resizing and collapsing functionality 3 | ---@class AutoResize 4 | ---@field enabled boolean Whether auto-resize is currently enabled 5 | ---@field auto_resize_group number|nil Augroup ID for auto-resize events 6 | local M = { 7 | enabled = false, 8 | auto_resize_group = nil, 9 | } 10 | 11 | ---Disables automatic window resizing 12 | ---@return nil 13 | M.disable_autoresize = function() 14 | if M.auto_resize_group then 15 | vim.api.nvim_del_augroup_by_id(M.auto_resize_group) 16 | M.auto_resize_group = nil 17 | end 18 | M.enabled = false 19 | end 20 | 21 | ---Triggers a window resize if auto-resize is enabled 22 | ---@return nil 23 | M.update = function() 24 | if M.enabled then 25 | vim.cmd 'wincmd =' 26 | end 27 | end 28 | 29 | ---Enables automatic window resizing 30 | ---@return nil 31 | M.enable_autoresize = function() 32 | M.auto_resize_group = vim.api.nvim_create_augroup('auto_resize', { clear = true }) 33 | vim.api.nvim_create_autocmd({ 'VimResized', 'WinNew', 'WinClosed' }, { 34 | group = M.auto_resize_group, 35 | callback = vim.schedule_wrap(function() vim.cmd 'wincmd =' end), 36 | }) 37 | vim.cmd 'wincmd =' 38 | M.enabled = true 39 | end 40 | 41 | ---Toggles automatic window resizing on/off 42 | ---@return nil 43 | M.toggle = function() 44 | if M.enabled then 45 | M.disable_autoresize() 46 | else 47 | M.enable_autoresize() 48 | end 49 | end 50 | 51 | return M 52 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/misc.lua: -------------------------------------------------------------------------------- 1 | ---@type LazySpec[] 2 | return { 3 | 'kawre/leetcode.nvim', 4 | config = function() 5 | local maputil = require 'user.util.map' 6 | local map = maputil.map 7 | local xk = require('user.keys').xk 8 | 9 | map('n', 'lc', 'Leet console', 'Leet: Console') 10 | map('n', 'lr', 'Leet run', 'Leet: Run') 11 | map('n', 'ls', 'Leet submit', 'Leet: Submit') 12 | map('n', 'L', 'Leet list', 'Leet: Select question (all)') 13 | map('n', 'l', 'Leet list status=notac', 'Leet: Select question (in progress)') 14 | 15 | ---@diagnostic disable-next-line: missing-fields 16 | require('leetcode').setup { 17 | storage = { 18 | home = (vim.env.GIT_PROJECTS_DIR or vim.fn.stdpath 'data') .. '/leetcode', 19 | cache = vim.fn.stdpath 'cache' .. '/leetcode', 20 | }, 21 | injector = { ---@type table 22 | ['cpp'] = { 23 | before = { '#include ', 'using namespace std;' }, 24 | after = 'int main() {}', 25 | }, 26 | }, 27 | keys = { 28 | toggle = { 'Q', 'q' }, 29 | confirm = xk '', 30 | reset_testcases = xk '', 31 | use_testcase = xk '', 32 | focus_testcases = '', 33 | focus_result = '', 34 | }, 35 | } 36 | end, 37 | cmd = 'Leet', 38 | event = { 'BufRead leetcode.nvim', 'BufNewFile leetcode.nvim' }, 39 | } 40 | -------------------------------------------------------------------------------- /nix/package-groups/shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | ... 5 | }: 6 | with pkgs; [ 7 | # Shells 8 | bash # bourne-again shell 9 | fish # friendly interactive shell 10 | zsh # z-shell 11 | nushell # modern shell 12 | 13 | # Nushell plugins 14 | nushellPlugins.skim 15 | 16 | # Terminal multiplexer 17 | zellij # terminal workspace manager 18 | 19 | # Task automation 20 | just # command runner 21 | 22 | # Dotfiles management 23 | stow # manage symlinks of dotfiles 24 | 25 | # Environment management 26 | direnv # load/unload env vars depending on current directory 27 | nix-direnv # faster implementation of direnv's use_nix and use_flake 28 | # bash-env-nushell # load bash environment variables in nushell 29 | 30 | # Shell enhancements 31 | starship # customizable shell prompt 32 | atuin # shell history manager with sync 33 | carapace # multi-shell completion generator 34 | vivid # themeable LS_COLORS generator 35 | usage # completions for mise 36 | 37 | # Modern CLI utilities 38 | bat # cat clone with syntax highlighting and Git integration 39 | eza # modern replacement for 'ls' 40 | fzf # command-line fuzzy finder 41 | skim # fuzzy finder written in Rust 42 | fd # simple, fast alternative to 'find' 43 | ripgrep # faster grep alternative 44 | 45 | # Git tools 46 | hub # GitHub CLI wrapper for git 47 | gh # official GitHub CLI 48 | lazygit # terminal UI for git repositories 49 | 50 | # AI 51 | opencode # Terminal-based AI coding agent 52 | 53 | # Services 54 | google-cloud-sdk # Google Cloud CLI 55 | ] 56 | -------------------------------------------------------------------------------- /config/git/delta.config: -------------------------------------------------------------------------------- 1 | [delta] 2 | navigate = true # use n and N to move between diff sections 3 | side-by-side = true 4 | syntax-theme = "lavi" # uses bat lavi theme 5 | dark = true 6 | file-style = "#61afef" bold 7 | file-decoration-style = "#4C435C" bold ol ul 8 | file-added-label = " " 9 | file-copied-label = " " 10 | file-modified-label = " " 11 | file-removed-label = " " 12 | file-renamed-label = " " 13 | commit-decoration-style = "#4C435C" bold box 14 | hunk-header-style = file line-number syntax 15 | hunk-header-decoration-style = "#4C435C" bold box ul 16 | hunk-header-file-style = "#61afef" 17 | hunk-header-line-number-style = "#9e8fbd" 18 | line-numbers = true 19 | line-numbers-left-format = "{nm:^4}┃" 20 | line-numbers-right-format = "┃{np:^4}┃" 21 | line-numbers-left-style = "#4C435C" 22 | line-numbers-right-style = "#4C435C" 23 | line-numbers-minus-style = "#ffe5f3" "#63284f" 24 | line-numbers-plus-style = "#d2ffeb" "#35695A" 25 | line-numbers-zero-style = "#8775a6" 26 | minus-style = "syntax" "#451c37" 27 | minus-emph-style = "#ffe5f3" "#993367" bold 28 | plus-style = "syntax" "#243b3e" 29 | plus-emph-style = "#d2ffeb" "#357E5D" bold 30 | whitespace-error-style = "auto" "#e5c07b" 31 | merge-conflict-begin-symbol = ⌃ 32 | merge-conflict-end-symbol = ⌄ 33 | merge-conflict-ours-diff-header-style = "#d2ffeb" bold 34 | merge-conflict-ours-diff-header-decoration-style = "#4C435C" box 35 | merge-conflict-theirs-diff-header-style = "#d2ffeb" bold 36 | merge-conflict-theirs-diff-header-decoration-style = "#4C435C" box 37 | 38 | # vim: ft=gitconfig commentstring=#%s 39 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/ai.lua: -------------------------------------------------------------------------------- 1 | ---@type LazySpec[] 2 | return { 3 | { 4 | 'supermaven-inc/supermaven-nvim', 5 | event = 'VeryLazy', 6 | config = function() 7 | require('supermaven-nvim').setup { 8 | disable_keymaps = true, 9 | ignore_filetypes = { 10 | ['dap-repl'] = true, 11 | dapui_scopes = true, 12 | dapui_breakpoints = true, 13 | dapui_stacks = true, 14 | dapui_watches = true, 15 | dapui_hover = true, 16 | Fyler = true, 17 | }, 18 | } 19 | 20 | local c = require 'supermaven-nvim.completion_preview' 21 | local xk = require('user.keys').xk 22 | local map = require('user.util.map').map 23 | 24 | map('i', { xk [[]], '' }, c.on_accept_suggestion, 'SuperMaven: Accept') 25 | map('i', { [[]] }, c.on_accept_suggestion_word, 'SuperMaven: Accept word') 26 | 27 | map('i', [[]], function() 28 | local smu = require 'supermaven-nvim.util' 29 | local orig_to_next_word = smu.to_next_word 30 | ---@diagnostic disable-next-line: duplicate-set-field 31 | smu.to_next_word = function(str) 32 | local match = str:match '^.' 33 | if match ~= nil then 34 | return match 35 | end 36 | return '' 37 | end 38 | pcall(c.on_accept_suggestion_word) 39 | smu.to_next_word = orig_to_next_word 40 | end, 'SuperMaven: Accept next char') 41 | end, 42 | }, 43 | { 44 | 'NickvanDyke/opencode.nvim', 45 | event = 'VeryLazy', 46 | config = function() vim.g.opencode_opts = {} end, 47 | }, 48 | } 49 | -------------------------------------------------------------------------------- /config/niri/bin/focus-previous: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S nu --stdin 2 | 3 | # Focus the most recently focused window which is still visible on another monitor 4 | def "main monitor" [] { 5 | # Optimization: run niri msg workspaces/windows in parallel 6 | let state = ( 7 | [workspaces windows] 8 | | par-each { { $in: (niri msg -j $in | from json) } } 9 | | into record 10 | ) 11 | let wins = ( 12 | $state.workspaces 13 | | where { $in.is_active and not $in.is_focused } 14 | | get -o active_window_id 15 | ) 16 | if ($wins | is-empty) { 17 | return 18 | } 19 | let recent = ( 20 | $state.windows 21 | | where id in $wins 22 | | sort-by -r focus_timestamp.secs 23 | | first 24 | | get -o id 25 | ) 26 | if ($recent | is-empty) { 27 | return 28 | } 29 | niri msg action focus-window --id $recent 30 | } 31 | 32 | # Focus the most recently focused window in the current workspace 33 | def "main workspace-window" [ 34 | --multi-layer # Consider windows on another layer (floating/tiling) than the focused window as candidates 35 | ] { 36 | let wins = niri msg -j windows | from json 37 | let focused = $wins | where is_focused | first 38 | if ($focused | is-empty) { 39 | return 40 | } 41 | let workspace_wins = ($wins | where { 42 | (not $in.is_focused and $in.workspace_id == $focused.workspace_id and ($multi_layer or $in.is_floating == $focused.is_floating)) 43 | }) 44 | if ($workspace_wins | is-empty) { 45 | return 46 | } 47 | let recent = $workspace_wins | sort-by -r focus_timestamp.secs | first 48 | if ($recent | is-empty) { 49 | return 50 | } 51 | niri msg action focus-window --id $recent.id 52 | } 53 | 54 | def main [] { 55 | ^$env.CURRENT_FILE "--help" 56 | } 57 | -------------------------------------------------------------------------------- /config/mako/config: -------------------------------------------------------------------------------- 1 | max-visible=5 2 | sort=-time 3 | layer=overlay 4 | anchor=top-right 5 | 6 | font=Pragmasevka Nerd Font 15 7 | 8 | background-color=#27223edd 9 | text-color=#eee6ffdd 10 | 11 | width=400 12 | height=400 13 | 14 | outer-margin=0 15 | margin=10,15,15,0 16 | padding=10,0 17 | 18 | border-size=3 19 | border-radius=6 20 | 21 | icons=1 22 | max-icon-size=64 23 | 24 | markup=0 25 | actions=1 26 | 27 | history=1 28 | max-history=10 29 | 30 | default-timeout=5000 31 | ignore-timeout=0 32 | 33 | [group-index=0] 34 | invisible=0 35 | 36 | [urgency=low] 37 | format= %s\n %b 38 | border-color=#574e8edd 39 | on-notify=exec mpv /usr/share/sounds/freedesktop/stereo/audio-volume-change.oga 40 | 41 | [urgency=normal] 42 | format= %s\n %b 43 | border-color=#9074ffdd 44 | on-notify=exec mpv /usr/share/sounds/freedesktop/stereo/audio-volume-change.oga 45 | 46 | [actionable] 47 | format= %s\n %b 48 | on-button-left=exec makoctl menu -- rofi -dmenu -p 'Choose Action: ' 49 | 50 | [urgency=high !actionable] 51 | format= %s\n %b 52 | border-color=#ff87a5dd 53 | on-notify=exec mpv --start=5% /usr/share/sounds/deepin/stereo/dialog-error.wav 54 | 55 | [urgency=high actionable] 56 | format= %s\n %b 57 | border-color=#ff87a5dd 58 | on-notify=exec mpv --start=5% /usr/share/sounds/deepin/stereo/dialog-error.wav 59 | 60 | [app-name=Claude] 61 | on-notify=exec mpv --start=5% /usr/share/sounds/Oxygen-Sys-Question.ogg 62 | 63 | [app-name=audioctl] 64 | group-by=app-name 65 | 66 | [app-name=brightnessd] 67 | group-by=app-name 68 | 69 | # vim: set ft=dosini: 70 | -------------------------------------------------------------------------------- /config/nushell/scripts/completion/mod.nu: -------------------------------------------------------------------------------- 1 | use candidates.nu 2 | use context.nu 3 | 4 | def column-width [candidates: table, column: cell-path, --padding: int = 2]: nothing -> int { 5 | ( 6 | $candidates 7 | | get $column 8 | | each {str length} 9 | | math max 10 | ) + $padding 11 | } 12 | 13 | export def fuzzy-complete-dwim [context: record]: nothing -> string { 14 | # TODO: preview with usage (available in metadata for internals, and tldr for externals) 15 | let candidates = (candidates for-context $context) 16 | if ($candidates | is-empty) { 17 | return "" 18 | } 19 | let name_width = (column-width $candidates name) 20 | $candidates 21 | | (sk 22 | --multi 23 | --prompt " " 24 | --query ($context.token | default {content: ""} | get content) 25 | --height "50%" 26 | --select-1 27 | --exit-0 28 | --format { 29 | $"($in.name | fill -w $name_width)($in.description)" 30 | }) 31 | | default [{ name: "" }] 32 | | get name 33 | | str join " " 34 | } 35 | 36 | def replace-current-token [context: record, replacement: string] { 37 | if ($context.token | is-empty) { 38 | [$context.pipeline, $replacement] | str join "" 39 | } else { 40 | let before = $context.pipeline | str substring ..<$context.token.span.start 41 | let after = $context.pipeline | str substring $context.token.span.end.. 42 | [$before, $replacement, $after] | str join "" 43 | } 44 | } 45 | 46 | export def commandline-fuzzy-complete-dwim [] { 47 | let context = (context current-completion-context) 48 | let selected = (fuzzy-complete-dwim $context) 49 | if ($selected | is-not-empty) { 50 | commandline edit --replace (replace-current-token $context $selected) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/aider.lua: -------------------------------------------------------------------------------- 1 | local apiutil = require 'user.util.api' 2 | 3 | local M = {} 4 | 5 | local state = { 6 | ---@type string[] 7 | cmds = {}, 8 | } 9 | 10 | ---add an aider command to the list 11 | ---@param path string 12 | M.add_cmd = function(path) 13 | if not vim.tbl_contains(state.cmds, path) then 14 | table.insert(state.cmds, path) 15 | end 16 | end 17 | 18 | ---add the current buffer 19 | ---@param bufnr? number 20 | M.add_buf = function(bufnr) 21 | bufnr = apiutil.resolve_bufnr(bufnr) 22 | if not bufnr then 23 | return 24 | end 25 | if vim.bo[bufnr].buftype ~= '' then 26 | return 27 | end 28 | local path = vim.api.nvim_buf_get_name(bufnr) 29 | if not path then 30 | return 31 | end 32 | path = vim.fn.fnamemodify(path, ':.') 33 | M.add_cmd(string.format('/add %s', path)) 34 | end 35 | 36 | ---add all files in a tabpage 37 | ---@param tabnr? number 38 | M.add_tabpage = function(tabnr) 39 | tabnr = apiutil.resolve_tabnr(tabnr) 40 | if not tabnr then 41 | return 42 | end 43 | local wins = vim.api.nvim_tabpage_list_wins(tabnr) 44 | for _, winnr in ipairs(wins) do 45 | if vim.api.nvim_win_is_valid(winnr) then 46 | local bufnr = vim.api.nvim_win_get_buf(winnr) 47 | if vim.api.nvim_buf_is_valid(bufnr) then 48 | M.add_buf(bufnr) 49 | end 50 | end 51 | end 52 | end 53 | 54 | --- copy the current command list to the clipboard 55 | M.copy_cmd = function() 56 | local cmd = table.concat(state.cmds, '\n') 57 | vim.fn.setreg('+', cmd) 58 | vim.notify('Copied: \n' .. cmd, vim.log.levels.INFO) 59 | end 60 | 61 | --- clear the command list 62 | M.clear_cmd = function() 63 | state.cmds = {} 64 | vim.notify('Cleared command list', vim.log.levels.INFO) 65 | end 66 | 67 | return M 68 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/session.lua: -------------------------------------------------------------------------------- 1 | -- Session helpers which persist and load additional state with the session, 2 | -- such as whether nvim-tree is open. 3 | local M = {} 4 | 5 | M.session_save = function() 6 | local meta = { 7 | focused = vim.api.nvim_get_current_win(), 8 | nvimTreeOpen = false, 9 | nvimTreeFocused = false, 10 | } 11 | if package.loaded['nvim-tree'] and require('nvim-tree.api').tree.is_visible() then 12 | meta.nvimTreeOpen = true 13 | meta.nvimTreeFocused = vim.fn.bufname(vim.fn.bufnr()) == 'NvimTree' 14 | vim.cmd 'NvimTreeClose' 15 | end 16 | 17 | vim.g.SessionMeta = vim.json.encode(meta) 18 | require('session_manager').save_current_session() 19 | vim.g.SessionMeta = nil 20 | 21 | if meta.nvimTreeOpen then 22 | vim.cmd 'NvimTreeOpen' 23 | if not meta.nvimTreeFocused and vim.api.nvim_win_is_valid(meta.focused) then 24 | vim.api.nvim_set_current_win(meta.focused) 25 | end 26 | end 27 | end 28 | 29 | M.session_load = function() 30 | vim.api.nvim_create_autocmd('SessionLoadPost', { 31 | once = true, 32 | callback = vim.schedule_wrap(function() 33 | local meta_ok, meta = pcall(vim.json.decode, vim.g.SessionMeta or '{}') 34 | if not meta_ok then 35 | vim.notify('session_load: failed to decode metadata: ' .. meta, vim.log.levels.WARN) 36 | meta = {} 37 | end 38 | vim.g.SessionMeta = nil 39 | if meta.nvimTreeOpen then 40 | vim.cmd 'NvimTreeOpen' 41 | end 42 | if meta.nvimTreeFocused then 43 | vim.cmd 'NvimTreeFocus' 44 | elseif meta.focused and vim.api.nvim_win_is_valid(meta.focused) then 45 | vim.api.nvim_set_current_win(meta.focused) 46 | end 47 | end), 48 | }) 49 | require('session_manager').load_current_dir_session(false) 50 | end 51 | 52 | return M 53 | -------------------------------------------------------------------------------- /config/nushell/scripts/xtras/format.nu: -------------------------------------------------------------------------------- 1 | # Format a duration in human-readable relative time format 2 | # 3 | # Converts a duration into a human-friendly string like "5 minutes ago" or "in 2 hours". 4 | # Positive durations represent the past (uses "ago"), while negative durations represent 5 | # the future (uses "in"). 6 | # 7 | # Examples: 8 | # > 5min | format duration human 9 | # 5 minutes ago 10 | # 11 | # > -2hr | format duration human 12 | # in 2 hours 13 | # 14 | # > (date now) - ('2024-01-01' | into datetime) | format duration human 15 | # 9 months ago 16 | export def "duration human" []: duration -> string { 17 | let dur = $in 18 | let abs_dur = ($dur | math abs) 19 | 20 | let seconds = ($abs_dur / 1sec) 21 | let minutes = ($abs_dur / 1min) 22 | let hours = ($abs_dur / 1hr) 23 | let days = ($abs_dur / 1day) 24 | let weeks = ($abs_dur / 1wk) 25 | let years = ($abs_dur / 365day) 26 | 27 | let result = if $seconds < 60 { 28 | if $seconds == 1 { "1 second" } else { $"($seconds | math round) seconds" } 29 | } else if $minutes < 60 { 30 | if $minutes == 1 { "1 minute" } else { $"($minutes | math round) minutes" } 31 | } else if $hours < 24 { 32 | if $hours == 1 { "1 hour" } else { $"($hours | math round) hours" } 33 | } else if $days < 7 { 34 | if $days == 1 { "1 day" } else { $"($days | math round) days" } 35 | } else if $days < 30 { 36 | if $weeks == 1 { "1 week" } else { $"($weeks | math round) weeks" } 37 | } else if $days < 365 { 38 | let months = ($days / 30 | math round) 39 | if $months == 1 { "1 month" } else { $"($months) months" } 40 | } else { 41 | if $years == 1 { "1 year" } else { $"($years | math round) years" } 42 | } 43 | 44 | # Positive duration = past, negative = future 45 | if $dur < 0sec { 46 | $"in ($result)" 47 | } else { 48 | $"($result) ago" 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/wins-bufs.lua: -------------------------------------------------------------------------------- 1 | local smart_splits = lazy_require 'smart-splits' 2 | local zellij_nav = lazy_require 'zellij-nav' 3 | local wrap = require('user.util.map').wrap 4 | 5 | ---@type LazySpec[] 6 | return { 7 | { 8 | 'sindrets/winshift.nvim', 9 | cmd = 'WinShift', 10 | keys = { 11 | { 'M', 'WinShift', desc = 'WinShift: Start' }, 12 | { 'mm', 'WinShift', desc = 'WinShift: Start' }, 13 | { 'ws', 'WinShift swap', desc = 'WinShift: Swap' }, 14 | }, 15 | opts = { 16 | highlight_moving_win = true, 17 | focused_hl_group = 'Visual', 18 | moving_win_options = { 19 | wrap = false, 20 | cursorline = false, 21 | cursorcolumn = false, 22 | colorcolumn = '', 23 | }, 24 | }, 25 | }, 26 | { 27 | 'mrjones2014/smart-splits.nvim', 28 | cond = function() return vim.env.ZELLIJ == nil end, 29 | event = 'VeryLazy', 30 | keys = { 31 | { '', wrap(smart_splits.move_cursor_left), desc = 'Goto window/pane left' }, 32 | { '', wrap(smart_splits.move_cursor_down), desc = 'Goto window/pane down' }, 33 | { '', wrap(smart_splits.move_cursor_up), desc = 'Goto window/pane up' }, 34 | { '', wrap(smart_splits.move_cursor_right), desc = 'Goto window/pane right' }, 35 | }, 36 | }, 37 | { 38 | 'swaits/zellij-nav.nvim', 39 | cond = function() return vim.env.ZELLIJ ~= nil end, 40 | event = 'VeryLazy', 41 | keys = { 42 | { '', wrap(zellij_nav.left), { desc = 'Goto window/pane left' } }, 43 | { '', wrap(zellij_nav.down), { desc = 'Goto window/pane down' } }, 44 | { '', wrap(zellij_nav.up), { desc = 'Goto window/pane up' } }, 45 | { '', wrap(zellij_nav.right), { desc = 'Goto window/pane right' } }, 46 | }, 47 | opts = {}, 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /config/nvim/lua/user/colors.lua: -------------------------------------------------------------------------------- 1 | -- TODO: remove this file 2 | 3 | local colors = { 4 | bg = '#201C33', 5 | bg_dark = '#1E1A30', 6 | bg_med = '#3F3650', 7 | bg_bright = '#463E57', 8 | 9 | fg = '#FFF1E0', 10 | 11 | black = '#2F2A38', 12 | black_med = '#4C435C', 13 | black_bright = '#8977A8', 14 | 15 | white = '#EEE6FF', 16 | white_bright = '#ffffff', 17 | 18 | skyblue = '#3FC4C4', 19 | cyan = '#2BEDC0', 20 | 21 | green = '#7CF89C', 22 | oceanblue = '#80BDFF', 23 | magenta = '#B98AFF', 24 | orange = '#ff9969', 25 | red = '#F2637E', 26 | red_bright = '#FF87A5', 27 | violet = '#7583FF', 28 | violet_bright = '#B891FF', 29 | violet_med = '#9385F8', 30 | yellow = '#FFD080', 31 | 32 | butter = '#fffacf', 33 | 34 | milk = '#fdf6e3', 35 | cream = '#e6dac3', 36 | cashew = '#CEB999', 37 | almond = '#a6875a', 38 | cocoa = '#3b290e', 39 | 40 | licorice = '#483270', 41 | lavender = '#A872FB', 42 | velvet = '#B29EED', 43 | anise = '#7F7DEE', 44 | anise_dark = '#7E7490', 45 | hydrangea = '#fb72fa', 46 | blush = '#EBBBF9', 47 | powder = '#EAC6F5', 48 | dust = '#EAD2F1', 49 | mistyrose = '#ffe4e1', 50 | rebeccapurple = '#3C2C74', 51 | 52 | evergreen = '#9fdfb4', 53 | 54 | snow = '#e4fffe', 55 | ice = '#a4e2e0', 56 | mint = '#a2e0ca', 57 | 58 | nectar = '#f0f070', 59 | cayenne = '#FF7D90', 60 | yam = '#e86f54', 61 | pumpkin = '#ff9969', -- TODO 62 | rose = '#b32e29', 63 | 64 | grey2 = '#222222', 65 | grey5 = '#777777', 66 | grey6 = '#aaaaaa', 67 | grey7 = '#cccccc', 68 | grey8 = '#dddddd', 69 | 70 | mid_velvet = '#6E6EA3', 71 | 72 | deep_lavender = '#38265A', 73 | deep_licorice = '#201C33', 74 | deep_anise = '#564D82', 75 | deep_velvet = '#8F8FB3', 76 | 77 | light_lavender = '#EAD6FF', 78 | } 79 | 80 | colors.inactive_bg = colors.bg_bright 81 | colors.active_bg = colors.black_bright 82 | colors.bg = colors.active_bg 83 | 84 | return colors 85 | -------------------------------------------------------------------------------- /config/niri/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "niri-tools" 7 | version = "0.1.0" 8 | description = "Utilities for the Niri window manager" 9 | requires-python = ">=3.13" 10 | license = { text = "MIT" } 11 | authors = [] 12 | keywords = ["niri", "wayland", "window-manager", "scratchpad"] 13 | classifiers = [ 14 | "Development Status :: 4 - Beta", 15 | "Environment :: Console", 16 | "Intended Audience :: End Users/Desktop", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: POSIX :: Linux", 19 | "Programming Language :: Python :: 3", 20 | "Programming Language :: Python :: 3.13", 21 | "Topic :: Desktop Environment :: Window Managers", 22 | "Topic :: System :: Monitoring", 23 | "Topic :: Utilities", 24 | ] 25 | 26 | dependencies = ["pyyaml>=6.0.2"] 27 | 28 | [project.urls] 29 | Homepage = "https://github.com/YaLTeR/niri" 30 | Repository = "https://github.com/YaLTeR/niri" 31 | 32 | [project.scripts] 33 | niri-tools = "niri_tools.main:main" 34 | 35 | [tool.hatch.build.targets.wheel] 36 | packages = ["niri_tools"] 37 | 38 | [tool.ruff] 39 | line-length = 88 40 | target-version = "py313" 41 | 42 | [tool.ruff.lint] 43 | select = [ 44 | "E", # pycodestyle errors 45 | "W", # pycodestyle warnings 46 | "F", # Pyflakes 47 | "I", # isort 48 | "B", # flake8-bugbear 49 | "C4", # flake8-comprehensions 50 | "UP", # pyupgrade 51 | ] 52 | ignore = [ 53 | "E501", # line too long, handled by formatter 54 | "B008", # do not perform function calls in argument defaults 55 | ] 56 | 57 | [tool.ruff.format] 58 | quote-style = "double" 59 | indent-style = "space" 60 | skip-magic-trailing-comma = false 61 | line-ending = "auto" 62 | 63 | [tool.basedpyright] 64 | include = ["niri_tools"] 65 | exclude = ["build", "dist"] 66 | reportMissingImports = true 67 | reportMissingTypeStubs = false 68 | pythonVersion = "3.13" 69 | typeCheckingMode = "basic" 70 | -------------------------------------------------------------------------------- /config/nushell/autoload/plugins.nu: -------------------------------------------------------------------------------- 1 | def _plugin_path [plugin: record] { 2 | if ($plugin | get -o path | is-not-empty) { 3 | $plugin.path 4 | } else if ($plugin | get -o cmd | is-not-empty) { 5 | which $plugin.cmd | first | get path 6 | } else { 7 | error make -u {msg: $"Plugin ($plugin.name) has no path or cmd"} 8 | } 9 | } 10 | 11 | def _init_plugins [] { 12 | if ($env | get -o NU_PLUGINS | is-empty) { 13 | return 14 | } 15 | let installed_plugins = plugin list 16 | mut reload = false 17 | for plugin in $env.NU_PLUGINS { 18 | let p = $installed_plugins | where name == $plugin.name | get -o 0 19 | if ($p | is-not-empty) { 20 | continue 21 | } 22 | let path = _plugin_path $plugin 23 | plugin add $path 24 | print -e $"Installed plugin ($plugin.name) from ($path)" 25 | $reload = true 26 | } 27 | 28 | if $reload { 29 | print -e "Reload nushell to load new plugins" 30 | } 31 | } 32 | 33 | def _complete-plugin-name [spans: list] { 34 | $env.NU_PLUGINS | get name 35 | } 36 | 37 | # Update nushell plugins 38 | @complete _complete-plugin-name 39 | def "plugin update" [...plugins: string] { 40 | if ($env | get -o NU_PLUGINS | is-empty) { 41 | print -e "$env.NU_PLUGINS is empty" 42 | } 43 | let plugins = if ($plugins | is-empty) { 44 | $env.NU_PLUGINS 45 | } else { 46 | for plugin in $plugins { 47 | if ($env.NU_PLUGINS | where name == $plugin | is-empty) { 48 | error make -u {msg: $"Plugin ($plugin) not found"} 49 | } 50 | } 51 | $env.NU_PLUGINS | where name in $plugins 52 | } 53 | if ($plugins | is-empty) { 54 | print -e "No plugins to update" 55 | return 56 | } 57 | for plugin in $plugins { 58 | let path = _plugin_path $plugin 59 | plugin rm $plugin.name 60 | plugin add $path 61 | print -e $"Updated plugin ($plugin.name) from ($path)" 62 | } 63 | print -e "Reload nushell to load new plugins" 64 | } 65 | 66 | _init_plugins 67 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/highlight.lua: -------------------------------------------------------------------------------- 1 | local a = vim.api 2 | 3 | local M = { namespace = a.nvim_create_namespace 'user' } 4 | local cache = {} 5 | 6 | M.clear = function() 7 | for hi in pairs(cache) do 8 | vim.cmd('highlight clear ' .. hi) 9 | end 10 | cache = {} 11 | end 12 | 13 | M.register = function(hl, group_name) 14 | hl = type(hl) == 'table' and hl or { group = hl } 15 | group_name = group_name or M.get_pseudonym(hl) 16 | 17 | local cmd = { lhs = { 'highlight' }, rhs = {} } 18 | for key, val in pairs(hl) do 19 | if key == 'default' then 20 | table.insert(cmd.lhs, 2, 'default') 21 | elseif key == 'group' then 22 | table.insert(cmd.lhs, 'link') 23 | table.insert(cmd.rhs, val) 24 | else 25 | table.insert(cmd.rhs, key .. '=' .. val) 26 | end 27 | end 28 | 29 | table.insert(cmd.lhs, group_name) 30 | 31 | local cmd_str = table.concat(cmd.lhs, ' ') .. ' ' .. table.concat(cmd.rhs, ' ') 32 | if not cache[group_name] or cmd_str ~= cache[group_name] then 33 | if not hl.default then 34 | cache[group_name] = cmd_str 35 | end 36 | vim.cmd(cmd_str) 37 | end 38 | 39 | return group_name 40 | end 41 | 42 | M.get_pseudonym = function(hl) 43 | local name = 'incline' 44 | local keys = vim.tbl_keys(hl) 45 | table.sort(keys) 46 | for _, arg in ipairs(keys) do 47 | local val = hl[arg] 48 | name = ('%s__%s_%s'):format(name, arg, tostring(val):gsub('[^%w]', ''):lower()) 49 | end 50 | return name 51 | end 52 | 53 | M.buf_add_highlight = function(buf, ...) 54 | return a.nvim_buf_add_highlight(buf, M.namespace, ...) 55 | end 56 | 57 | M.buf_clear = function(buf) 58 | return a.nvim_buf_clear_namespace(buf, M.namespace, 0, -1) 59 | end 60 | 61 | -- M.setup = function() 62 | -- M.clear() 63 | -- M.namespace = a.nvim_create_namespace 'incline' 64 | -- for hl_group, hl in pairs(config.highlight.groups) do 65 | -- M.register(hl, hl_group) 66 | -- end 67 | -- end 68 | 69 | return M 70 | -------------------------------------------------------------------------------- /config/htop/htoprc: -------------------------------------------------------------------------------- 1 | # Beware! This file is rewritten by htop when settings are changed in the interface. 2 | # The parser is also very primitive, and not human-friendly. 3 | htop_version=3.4.1-3.4.1 4 | config_reader_min_version=3 5 | fields=0 48 17 18 38 39 40 2 46 47 49 1 6 | hide_kernel_threads=1 7 | hide_userland_threads=1 8 | hide_running_in_container=0 9 | shadow_other_users=0 10 | show_thread_names=0 11 | show_program_path=0 12 | highlight_base_name=0 13 | highlight_deleted_exe=1 14 | shadow_distribution_path_prefix=0 15 | highlight_megabytes=1 16 | highlight_threads=1 17 | highlight_changes=0 18 | highlight_changes_delay_secs=5 19 | find_comm_in_cmdline=1 20 | strip_exe_from_cmdline=1 21 | show_merged_command=0 22 | header_margin=1 23 | screen_tabs=0 24 | detailed_cpu_time=0 25 | cpu_count_from_one=1 26 | show_cpu_usage=1 27 | show_cpu_frequency=0 28 | show_cpu_temperature=0 29 | degree_fahrenheit=0 30 | show_cached_memory=1 31 | update_process_names=0 32 | account_guest_in_cpu_meter=0 33 | color_scheme=0 34 | enable_mouse=1 35 | delay=20 36 | hide_function_bar=0 37 | header_layout=two_50_50 38 | column_meters_0=LeftCPUs2 Memory Swap 39 | column_meter_modes_0=1 1 1 40 | column_meters_1=RightCPUs2 Tasks LoadAverage Uptime 41 | column_meter_modes_1=1 2 2 2 42 | tree_view=0 43 | sort_key=46 44 | tree_sort_key=46 45 | sort_direction=-1 46 | tree_sort_direction=-1 47 | tree_view_always_by_pid=0 48 | all_branches_collapsed=0 49 | screen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command 50 | .sort_key=PERCENT_CPU 51 | .tree_sort_key=PERCENT_CPU 52 | .tree_view_always_by_pid=0 53 | .tree_view=0 54 | .sort_direction=-1 55 | .tree_sort_direction=-1 56 | .all_branches_collapsed=0 57 | screen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command 58 | .sort_key=IO_RATE 59 | .tree_sort_key=PID 60 | .tree_view_always_by_pid=0 61 | .tree_view=0 62 | .sort_direction=-1 63 | .tree_sort_direction=1 64 | .all_branches_collapsed=0 65 | -------------------------------------------------------------------------------- /config/nushell/autoload/clipboard.nu: -------------------------------------------------------------------------------- 1 | export def xc [ 2 | --out(-o) # Output/paste mode 3 | ...args # Additional arguments 4 | ] { 5 | if 'WAYLAND_DISPLAY' in $env { 6 | if $out { 7 | ^wl-paste ...$args 8 | } else { 9 | ^wl-copy -t text/plain -n ...$args 10 | } 11 | } else if 'DISPLAY' in $env { 12 | if $out { 13 | ^xclip -o -selection clipboard ...$args 14 | } else { 15 | ^xclip -selection clipboard ...$args 16 | } 17 | } else { 18 | if $out { 19 | print -e 'xc: no graphical session detected' 20 | print -e 'OSC 52: output not supported' 21 | error make {msg: "no graphical session"} 22 | } else { 23 | let encoded = (^base64 | str trim) 24 | print -n $"(\u{001b})]52;;($encoded)(\u{001b})\\" 25 | } 26 | } 27 | } 28 | 29 | # Copy last n command(s) to clipboard 30 | def xcl [count: int = 1] { 31 | let lines = (history | last $count | get command | str join "\n") 32 | echo $lines | xc 33 | print -e $"Copied:\n($lines)" 34 | } 35 | 36 | # Copy command and its output to clipboard 37 | # TODO: custom commands do not work 38 | def xcc [...args] { 39 | let cmd = if ($args | is-not-empty) { 40 | ($args | str join " ") 41 | } else { 42 | $in 43 | } 44 | 45 | let header = $"$ ($cmd)\n" 46 | let result = (do { nu -i -c $cmd } | complete) 47 | let full_str = $header + $result.stdout + $result.stderr 48 | 49 | print -e $full_str 50 | echo $full_str | xc 51 | } 52 | 53 | # Copy last n command(s) and its output to clipboard (command(s) are re-run) 54 | def xccl [count: int = 1] { 55 | let lines = (history | last $count | get command | str join "\n") 56 | print -e $lines 57 | let reply = (input "Run command? [Y/n] " | str trim) 58 | 59 | if $reply not-in ["y", "Y", ""] { 60 | print -e "Aborted" 61 | return 1 62 | } 63 | 64 | print "" 65 | xcc $lines 66 | } 67 | 68 | # Copy a file to clipboard 69 | def xcf [path: path] { 70 | open $path | xc 71 | } 72 | 73 | alias xco = xc -o 74 | -------------------------------------------------------------------------------- /config/niri/niri_tools/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Niri window manager tools - unified CLI entry point. 3 | """ 4 | 5 | import argparse 6 | import asyncio 7 | import sys 8 | 9 | from . import client 10 | from .daemon import server 11 | 12 | 13 | def create_parser() -> argparse.ArgumentParser: 14 | """Create the main argument parser with subcommands.""" 15 | parser = argparse.ArgumentParser( 16 | prog="niri-tools", 17 | description="Niri window manager tools - unified CLI for scratchpad and daemon", 18 | ) 19 | 20 | subparsers = parser.add_subparsers( 21 | dest="command", 22 | help="Available commands", 23 | required=True, 24 | ) 25 | 26 | # Daemon command 27 | subparsers.add_parser( 28 | "daemon", 29 | help="Run the niri-tools daemon", 30 | description="Run the daemon that handles scratchpads and urgency notifications", 31 | ) 32 | 33 | # Scratchpad command (routes to client) 34 | scratchpad_parser = subparsers.add_parser( 35 | "scratchpad", 36 | help="Manage scratchpad windows", 37 | description="Toggle show/hide scratchpad windows (requires daemon)", 38 | ) 39 | client.add_arguments(scratchpad_parser) 40 | 41 | return parser 42 | 43 | 44 | def main(argv: list[str] | None = None) -> int: 45 | """Main entry point.""" 46 | parser = create_parser() 47 | args = parser.parse_args(argv) 48 | 49 | try: 50 | if args.command == "daemon": 51 | return asyncio.run(server.run_daemon()) 52 | elif args.command == "scratchpad": 53 | return client.main(args) 54 | else: 55 | print(f"Unknown command: {args.command}", file=sys.stderr) 56 | parser.print_help() 57 | return 1 58 | except KeyboardInterrupt: 59 | print("\nInterrupted", file=sys.stderr) 60 | return 130 61 | except Exception as e: 62 | print(f"Error: {e}", file=sys.stderr) 63 | return 1 64 | 65 | 66 | if __name__ == "__main__": 67 | sys.exit(main()) 68 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/testing.lua: -------------------------------------------------------------------------------- 1 | ---@type LazySpec[] 2 | local spec = { 3 | { 4 | 'nvim-neotest/neotest', 5 | cmd = { 'Neotest' }, 6 | opts = { 7 | adapters = { 8 | lazy_require 'neotest-vitest', 9 | lazy_require 'rustaceanvim.neotest', 10 | }, 11 | quickfix = { 12 | enabled = false, 13 | open = false, 14 | }, 15 | summary = { 16 | open = [[botright vsplit +set\ nowrap | vertical resize 50]], 17 | }, 18 | icons = { 19 | running_animated = { 20 | '⠋', 21 | '⠙', 22 | '⠹', 23 | '⠸', 24 | '⠼', 25 | '⠴', 26 | '⠦', 27 | '⠧', 28 | '⠇', 29 | '⠏', 30 | }, 31 | }, 32 | }, 33 | }, 34 | 'marilari88/neotest-vitest', 35 | } 36 | 37 | very_lazy(function() 38 | local maputil = require 'user.util.map' 39 | local map = maputil.map 40 | local wrap = maputil.wrap 41 | 42 | local neotest = lazy_require 'neotest' 43 | local neotest_summary = lazy_require 'neotest.consumers.summary' 44 | 45 | map('n', 'nn', neotest.run.run, 'Neotest: Run Nearest Test') 46 | map('n', { 'N', 'nf' }, function() neotest.run.run(vim.fn.expand '%') end, 'Neotest: Run File') 47 | 48 | map('n', '[n', wrap(neotest.jump.prev, { status = 'failed' }), 'Neotest: Jump Prev Failed') 49 | map('n', ']n', wrap(neotest.jump.next, { status = 'failed' }), 'Neotest: Jump Next Failed') 50 | 51 | map('n', '', function() 52 | neotest_summary.open() 53 | if vim.bo.filetype == 'neotest-summary' then 54 | vim.cmd 'wincmd p' 55 | else 56 | for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do 57 | if vim.bo[vim.api.nvim_win_get_buf(win)].filetype == 'neotest-summary' then 58 | vim.api.nvim_set_current_win(win) 59 | return 60 | end 61 | end 62 | end 63 | end, 'Neotest: Open or Focus Summary') 64 | 65 | map('n', '', neotest.summary.toggle, 'Neotest: Toggle Summary') 66 | end) 67 | 68 | return spec 69 | -------------------------------------------------------------------------------- /nix/lib/zfs.nix: -------------------------------------------------------------------------------- 1 | {lib}: let 2 | inherit (lib) optionalAttrs concatStringsSep filter; 3 | in { 4 | # Helper to create a ZFS dataset with common properties 5 | # Optional owner/group/mode generate postMountHook for permissions 6 | mkDataset = { 7 | mountpoint, 8 | options ? {}, 9 | postCreateHook ? null, 10 | postMountHook ? null, 11 | owner ? null, 12 | group ? null, 13 | mode ? null, 14 | }: let 15 | chownArg = 16 | if owner != null && group != null 17 | then "${toString owner}:${toString group}" 18 | else if owner != null 19 | then toString owner 20 | else if group != null 21 | then ":${toString group}" 22 | else null; 23 | 24 | chownCmd = 25 | if chownArg != null 26 | then "chown ${chownArg} /mnt${mountpoint}" 27 | else null; 28 | 29 | chmodCmd = 30 | if mode != null 31 | then "chmod ${mode} /mnt${mountpoint}" 32 | else null; 33 | 34 | permissionHook = concatStringsSep "\n" (filter (x: x != null) [chownCmd chmodCmd]); 35 | 36 | finalPostMountHook = 37 | if permissionHook != "" && postMountHook != null 38 | then permissionHook + "\n" + postMountHook 39 | else if permissionHook != "" 40 | then permissionHook 41 | else postMountHook; 42 | in 43 | { 44 | type = "zfs_fs"; 45 | inherit mountpoint options; 46 | } 47 | // optionalAttrs (postCreateHook != null) {inherit postCreateHook;} 48 | // optionalAttrs (finalPostMountHook != null) {postMountHook = finalPostMountHook;}; 49 | 50 | # Helper to set snapshot retention (all values explicit) 51 | snapshotDataset = { 52 | frequent, 53 | hourly, 54 | daily, 55 | weekly, 56 | monthly, 57 | }: { 58 | "com.sun:auto-snapshot" = "true"; 59 | "com.sun:auto-snapshot:frequent" = toString frequent; 60 | "com.sun:auto-snapshot:hourly" = toString hourly; 61 | "com.sun:auto-snapshot:daily" = toString daily; 62 | "com.sun:auto-snapshot:weekly" = toString weekly; 63 | "com.sun:auto-snapshot:monthly" = toString monthly; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /config/git/alias.config: -------------------------------------------------------------------------------- 1 | [alias] 2 | ci = commit --verbose 3 | pao = push --all origin 4 | co = checkout 5 | st = status 6 | aa = add -A 7 | a = add 8 | cia = commit -a --verbose 9 | ls = !bash -ic 'git --no-pager log --decorate --max-count=10 --format=\"tformat:%C(auto,yellow)%<(8,trunc)%h %C(blue)%<(15,trunc)%cN %<($((COLUMNS - 42)),trunc)%Creset%s %C(yellow)%G?%C(magenta)%>(15,trunc)%cr%Creset\" \"$@\"' -- 10 | la = !bash -ic 'git --no-pager log --decorate --max-count=10 --format=\"tformat:%C(auto,yellow)%<(8,trunc)%h %C(blue)%<(15,trunc)%cN %<($((COLUMNS - 42)),trunc)%Creset%s %C(yellow)%G?%C(magenta)%>(15,trunc)%cr%Creset%+b\" \"$@\"' -- 11 | ll = !bash -ic 'git log --decorate --format=\"tformat:%C(auto,yellow)%<(8,trunc)%h %C(blue)%<(15,trunc)%cN %<($((COLUMNS - 42)),trunc)%Creset%s %C(yellow)%G?%C(magenta)%>(15,trunc)%cr%Creset\" \"$@\"' -- 12 | lla = !bash -ic 'git log --decorate --format=\"tformat:%C(auto,yellow)%<(8,trunc)%h %C(blue)%<(15,trunc)%cN %<($((COLUMNS - 42)),trunc)%Creset%s %C(yellow)%G?%C(magenta)%>(15,trunc)%cr%Creset%+b\" \"$@\"' -- 13 | lg1 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all 14 | lg2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all 15 | lg = !"git lg1" 16 | conflicted = !nvim +Conflicted 17 | c = commit --verbose 18 | pa = push 19 | pab = push --all b0o 20 | p = push 21 | uncommit = reset --soft HEAD^ 22 | # save a 'snapshot' to the stash while maintaining the stashed changes in the working tree 23 | snap = "!git stash push -m \"${1:-snapshot}\" && git stash apply" 24 | # save a 'snapshot' to the stash of the index while maintaining the stashed changes in the working tree 25 | snap-staged = "!git stash push --staged -m \"${1:-snapshot}\" && git stash apply --index" 26 | 27 | # vim: ft=gitconfig commentstring=#%s 28 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/cutbuf.lua: -------------------------------------------------------------------------------- 1 | local lazy = require 'user.util.lazy' 2 | local fn = lazy.require_on_call_rec 'user.fn' 3 | 4 | local MODE_NONE = 1 5 | local MODE_CUT = 2 6 | local MODE_COPY = 3 7 | 8 | local M = { 9 | state = { 10 | buf = nil, 11 | win = nil, 12 | mode = MODE_NONE, 13 | }, 14 | } 15 | 16 | M.cut = function(win) 17 | M.state.win = fn.resolve_winnr(win or 0) 18 | M.state.buf = vim.api.nvim_win_get_buf(M.state.win) 19 | M.state.mode = MODE_CUT 20 | vim.notify('cutbuf: cut buffer ' .. M.state.buf) 21 | end 22 | 23 | M.copy = function(win) 24 | M.state.win = nil 25 | M.state.buf = vim.api.nvim_win_get_buf(fn.resolve_winnr(win or 0)) 26 | M.state.mode = MODE_COPY 27 | vim.notify('cutbuf: copy buffer ' .. M.state.buf) 28 | end 29 | 30 | M.paste = function(win) 31 | if M.state.mode == MODE_NONE or not vim.api.nvim_buf_is_valid(M.state.buf or -1) then 32 | vim.notify 'cutbuf: no buffer to paste' 33 | return 34 | end 35 | 36 | local target_win = fn.resolve_winnr(win or 0) 37 | local target_win_buf = vim.api.nvim_win_get_buf(target_win) 38 | vim.api.nvim_win_set_buf(target_win, M.state.buf) 39 | vim.notify('cutbuf: paste buffer ' .. M.state.buf) 40 | 41 | if M.state.mode == MODE_CUT and vim.api.nvim_win_is_valid(M.state.win or -1) then 42 | vim.api.nvim_win_set_buf(M.state.win, target_win_buf) 43 | M.state.win = nil 44 | M.state.buf = nil 45 | M.state.mode = MODE_NONE 46 | end 47 | end 48 | 49 | M.swap = function(win) 50 | win = fn.resolve_winnr(win or 0) 51 | local buf = vim.api.nvim_win_get_buf(win) 52 | local target_win = require('window-picker').pick_window() 53 | if not target_win or not vim.api.nvim_win_is_valid(target_win) or target_win == win then 54 | vim.notify 'cutbuf: no target window' 55 | return 56 | end 57 | local target_buf = vim.api.nvim_win_get_buf(target_win) 58 | vim.api.nvim_win_set_buf(win, target_buf) 59 | vim.api.nvim_win_set_buf(target_win, buf) 60 | vim.api.nvim_set_current_win(target_win) 61 | vim.notify('cutbuf: swap buffers ' .. buf .. ' and ' .. target_buf) 62 | end 63 | 64 | return M 65 | -------------------------------------------------------------------------------- /config/nushell/autoload/python.nu: -------------------------------------------------------------------------------- 1 | def --env venv [targetdir?: string] { 2 | # Check if already in a virtual environment 3 | if ($env.VIRTUAL_ENV? != null) { 4 | print $"Already in venv ($env.VIRTUAL_ENV)" 5 | return 6 | } 7 | 8 | let startdir = $env.PWD 9 | 10 | # Change to target directory if provided 11 | if $targetdir != null { 12 | if not ($targetdir | path exists) { 13 | print $"error: not a directory: ($targetdir)" --stderr 14 | return 15 | } 16 | try { 17 | cd $targetdir 18 | } catch { 19 | print $"error: failed to change directory to ($targetdir)" --stderr 20 | cd $startdir 21 | return 22 | } 23 | } 24 | 25 | let repo = try { 26 | git rev-parse --show-toplevel 27 | | complete 28 | | if $in.exit_code == 0 { $in.stdout | str trim } else { null } 29 | } catch { 30 | null 31 | } 32 | let project_root = if $repo != null { $repo } else { $env.PWD } 33 | 34 | let venv = if ($env.PWD | path join "pyvenv.cfg" | path exists) { 35 | $env.PWD 36 | } else if ($env.PWD | path join ".venv" | path exists) { 37 | $env.PWD | path join ".venv" 38 | } else if $repo != null and ($repo | path join ".venv" | path exists) { 39 | $repo | path join ".venv" 40 | } else if ($project_root | path join "pyproject.toml" | path exists) { 41 | let response = input "No venv found. Create one with uv? (y/n): " | str trim | str downcase 42 | if not ($response == "y" or response == "") { 43 | return null 44 | } 45 | if (do { 46 | cd $project_root 47 | try { 48 | ^uv venv 49 | } catch { 50 | print "error: failed to create virtual environment" --stderr 51 | return null 52 | } 53 | } | complete).exit_code != 0 { 54 | print "error: failed to create virtual environment" --stderr 55 | return null 56 | } 57 | 58 | $project_root | path join ".venv" 59 | } 60 | if $venv == null { 61 | print "No virtual environment found" 62 | cd $startdir 63 | return 64 | } 65 | 66 | print $"Activating virtual environment ($venv)" 67 | 68 | # Activate the virtual environment 69 | $env.VIRTUAL_ENV = $venv 70 | let venv_bin = $venv | path join "bin" 71 | $env.PATH = ($env.PATH | prepend $venv_bin) 72 | 73 | cd $startdir 74 | } 75 | -------------------------------------------------------------------------------- /config/opencode/opencode.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://opencode.ai/config.json", 3 | "autoupdate": false, 4 | "theme": "lavi", 5 | "keybinds": { 6 | "leader": "ctrl+x,û", // û is ctrl+. 7 | "app_exit": "!", // ctrl+shift+q 8 | "editor_open": "ctrl+e,ctrl+g", 9 | "theme_list": "none", 10 | "sidebar_toggle": "ò", // alt+shift+\ 11 | "username_toggle": "none", 12 | "status_view": "s", 13 | "session_export": "x", 14 | "session_new": "n", 15 | "session_list": "l", 16 | "session_timeline": "g", 17 | "session_share": "none", 18 | "session_unshare": "none", 19 | "session_interrupt": "escape", 20 | "session_compact": "c", 21 | "session_child_cycle": "+right", 22 | "session_child_cycle_reverse": "+left", 23 | "messages_page_up": "pageup", 24 | "messages_page_down": "pagedown", 25 | "messages_half_page_up": "ctrl+alt+u", 26 | "messages_half_page_down": "ctrl+alt+d", 27 | "messages_first": "home", 28 | "messages_last": "ctrl+alt+g,end", 29 | "messages_copy": "y", 30 | "messages_undo": "u", 31 | "messages_redo": "r", 32 | "messages_last_user": "none", 33 | "messages_toggle_conceal": "h", 34 | "model_list": "m", 35 | "model_cycle_recent": "none", 36 | "model_cycle_recent_reverse": "none", 37 | "command_list": "ctrl+o", 38 | "agent_list": "a", 39 | "agent_cycle": "tab", 40 | "agent_cycle_reverse": "shift+tab", 41 | "input_clear": "ctrl+c", 42 | "input_forward_delete": "ctrl+d", 43 | "input_paste": "ctrl+v", 44 | "input_submit": "enter", 45 | "input_newline": "shift+enter,ctrl+j", 46 | "history_previous": "shift+f1", // ctrl+shift+p 47 | "history_next": "shift+f2", // ctrl+shift+n 48 | "tool_details": "d", 49 | "terminal_suspend": "none", 50 | }, 51 | "lsp": { 52 | "pyright": { 53 | "disabled": true, 54 | }, 55 | "basedpyright": { 56 | "command": [ 57 | "basedpyright-langserver", 58 | "--stdio", 59 | ], 60 | "extensions": [".py", ".pyi"], 61 | }, 62 | "nil_ls": { 63 | "command": ["nil"], 64 | "extensions": [".nix"], 65 | }, 66 | }, 67 | } 68 | -------------------------------------------------------------------------------- /config/waybar/colors.css: -------------------------------------------------------------------------------- 1 | /* Lavi Palette */ 2 | 3 | @define-color black #2F2A38; 4 | @define-color black_med #4C435C; 5 | @define-color black_bright #8977A8; 6 | 7 | @define-color white #EEE6FF; 8 | @define-color white_bright #ffffff; 9 | 10 | @define-color bg #1d1834; 11 | @define-color dark_bg #191627; 12 | @define-color med_bg #39335c; 13 | @define-color bright_bg #574e8e; 14 | 15 | @define-color dark_fg #FFF1E0; 16 | @define-color dark_fg_dim #DED4FD; 17 | @define-color dark_fg_nc #A89CCF; 18 | @define-color skyblue #3FC4C4; 19 | @define-color cyan #2BEDC0; 20 | 21 | @define-color red #F2637E; 22 | @define-color orange #ff9969; 23 | @define-color yellow #FFD080; 24 | @define-color green #7CF89C; 25 | @define-color blue #7583FF; 26 | @define-color indigo #9074FF; 27 | @define-color violet #DC91FF; 28 | 29 | @define-color oceanblue #80BDFF; 30 | @define-color magenta #B98AFF; 31 | @define-color red_bright #FF87A5; 32 | @define-color violet_bright #B891FF; 33 | @define-color violet_med #9385F8; 34 | 35 | @define-color straw #fffacf; 36 | 37 | @define-color tofu #fdf6e3; 38 | @define-color cashew #e6dac3; 39 | @define-color walnut #CEB999; 40 | @define-color almond #a6875a; 41 | @define-color cocoa #3b290e; 42 | 43 | @define-color licorice #483270; 44 | @define-color lavender #A872FB; 45 | @define-color velvet #B29EED; 46 | @define-color anise #7F7DEE; 47 | @define-color anise_dark #7E7490; 48 | @define-color hydrangea #fb72fa; 49 | @define-color blush #EBBBF9; 50 | @define-color powder #EAC6F5; 51 | @define-color dust #EAD2F1; 52 | @define-color mistyrose #ffe4e1; 53 | @define-color rebeccapurple #3C2C74; 54 | 55 | @define-color matcha #9fdfb4; 56 | 57 | @define-color snow #e4fffe; 58 | @define-color ice #a4e2e0; 59 | @define-color mint #a2e0ca; 60 | 61 | @define-color nectar #f0f070; 62 | @define-color cayenne #FF7D90; 63 | @define-color yam #e86f54; 64 | @define-color pumpkin #ff9969; 65 | @define-color rose #b32e29; 66 | 67 | @define-color grey2 #222222; 68 | @define-color grey5 #777777; 69 | @define-color grey6 #aaaaaa; 70 | @define-color grey7 #cccccc; 71 | @define-color grey8 #dddddd; 72 | 73 | @define-color mid_velvet #6E6EA3; 74 | 75 | @define-color deep_lavender #38265A; 76 | @define-color deep_licorice #564D82; 77 | @define-color deep_anise #8F8FB3; 78 | @define-color deep_velvet #B7BDF8; 79 | 80 | @define-color light_lavender #EAD6FF; 81 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/nvim-tree/decorator-quickfix.lua: -------------------------------------------------------------------------------- 1 | ---@class (exact) DecoratorQuickfix: nvim_tree.api.decorator.UserDecorator 2 | ---@field private qf_icon nvim_tree.api.HighlightedString 3 | local QuickfixDecorator = require('nvim-tree.api').decorator.UserDecorator:extend() 4 | 5 | local augroup = vim.api.nvim_create_augroup('nvim-tree-decorator-quickfix', { clear = true }) 6 | 7 | local autocmds_setup = false 8 | local function setup_autocmds() 9 | if autocmds_setup then 10 | return 11 | end 12 | autocmds_setup = true 13 | vim.api.nvim_create_autocmd('QuickfixCmdPost', { 14 | group = augroup, 15 | callback = function() require('nvim-tree.api').tree.reload() end, 16 | }) 17 | 18 | vim.api.nvim_create_autocmd('FileType', { 19 | pattern = 'qf', 20 | group = augroup, 21 | callback = function(evt) 22 | vim.api.nvim_create_autocmd('TextChanged', { 23 | buffer = evt.buf, 24 | group = augroup, 25 | callback = function() require('nvim-tree.api').tree.reload() end, 26 | }) 27 | end, 28 | }) 29 | end 30 | 31 | function QuickfixDecorator:new() 32 | self.enabled = true 33 | self.highlight_range = 'none' 34 | self.icon_placement = 'signcolumn' 35 | self.qf_icon = { str = '', hl = { 'QuickFixLine' } } 36 | self:define_sign(self.qf_icon) 37 | setup_autocmds() 38 | end 39 | 40 | ---Helper function to check if a node is in quickfix list 41 | ---@param node nvim_tree.api.Node 42 | ---@return boolean 43 | local function is_qf_item(node) 44 | if node.name == '..' or node.type == 'directory' then 45 | return false 46 | end 47 | local bufnr = vim.fn.bufnr(node.absolute_path) 48 | return bufnr ~= -1 and vim.iter(vim.fn.getqflist()):any(function(qf) return qf.bufnr == bufnr end) 49 | end 50 | 51 | ---Return quickfix icons for the node 52 | ---@param node nvim_tree.api.Node 53 | ---@return nvim_tree.api.HighlightedString[]? icons 54 | function QuickfixDecorator:icons(node) 55 | if is_qf_item(node) then 56 | return { self.qf_icon } 57 | end 58 | return nil 59 | end 60 | 61 | ---Return highlight group for the node 62 | ---@param node nvim_tree.api.Node 63 | ---@return string? highlight_group 64 | function QuickfixDecorator:highlight_group(node) 65 | if is_qf_item(node) then 66 | return 'QuickFixLine' 67 | end 68 | return nil 69 | end 70 | 71 | return QuickfixDecorator 72 | -------------------------------------------------------------------------------- /config/nvim/lua/window-picker/hints/data/stampatello.lua: -------------------------------------------------------------------------------- 1 | -- Font: stampatello 2 | return { 3 | ['a'] = [[ 4 | ,-. 5 | ,-| 6 | `-^ ]], 7 | 8 | ['b'] = [[ 9 | . 10 | |-. 11 | | | 12 | ^-' ]], 13 | 14 | ['c'] = [[ 15 | ,-. 16 | | 17 | `-' ]], 18 | 19 | ['d'] = [[ 20 | . 21 | ,-| 22 | | | 23 | `-^ ]], 24 | 25 | ['e'] = [[ 26 | ,-. 27 | |-' 28 | `-' ]], 29 | 30 | ['f'] = [[ 31 | ," 32 | |- 33 | | 34 | ' ]], 35 | 36 | ['g'] = [[ 37 | ,-. 38 | | | 39 | `-| 40 | ,| 41 | `' ]], 42 | 43 | ['h'] = [[ 44 | . 45 | |-. 46 | | | 47 | ' ' ]], 48 | 49 | ['i'] = [[ 50 | . 51 | | 52 | ' ]], 53 | 54 | ['j'] = [[ 55 | . 56 | | 57 | | 58 | | 59 | `' ]], 60 | 61 | ['k'] = [[ 62 | . 63 | | , 64 | |< 65 | ' ` ]], 66 | 67 | ['l'] = [[ 68 | . 69 | | 70 | | 71 | `' ]], 72 | 73 | ['m'] = [[ 74 | ,-,-. 75 | | | | 76 | ' ' ' ]], 77 | 78 | ['n'] = [[ 79 | ,-. 80 | | | 81 | ' ' ]], 82 | 83 | ['o'] = [[ 84 | ,-. 85 | | | 86 | `-' ]], 87 | 88 | ['p'] = [[ 89 | ,-. 90 | | | 91 | |-' 92 | | 93 | ' ]], 94 | 95 | ['q'] = [[ 96 | ,-. 97 | | | 98 | `-| 99 | | 100 | ` ]], 101 | 102 | ['r'] = [[ 103 | ,-. 104 | | 105 | ' ]], 106 | 107 | ['s'] = [[ 108 | ,-. 109 | `-. 110 | `-' ]], 111 | 112 | ['t'] = [[ 113 | . 114 | |- 115 | | 116 | `' ]], 117 | 118 | ['u'] = [[ 119 | . . 120 | | | 121 | `-^ ]], 122 | 123 | ['v'] = [[ 124 | . , 125 | | / 126 | `' ]], 127 | 128 | ['w'] = [[ 129 | . , , 130 | |/|/ 131 | ' ' ]], 132 | 133 | ['x'] = [[ 134 | . , 135 | X 136 | ' ` ]], 137 | 138 | ['y'] = [[ 139 | . . 140 | | | 141 | `-| 142 | /| 143 | `-' ]], 144 | 145 | ['z'] = [[ 146 | ,_, 147 | / 148 | '"' ]], 149 | 150 | ['0'] = [[ 151 | ,-. 152 | |/| 153 | `-' ]], 154 | 155 | ['1'] = [[ 156 | , 157 | '| 158 | ` ]], 159 | 160 | ['2'] = [[ 161 | ,-, 162 | / 163 | '-` ]], 164 | 165 | ['3'] = [[ 166 | ,-. 167 | -< 168 | `-' ]], 169 | 170 | ['4'] = [[ 171 | ,. 172 | {_| 173 | ' ]], 174 | 175 | ['5'] = [[ 176 | .-- 177 | `-. 178 | `-' ]], 179 | 180 | ['6'] = [[ 181 | ,-. 182 | |-. 183 | `-' ]], 184 | 185 | ['7'] = [[ 186 | --, 187 | / 188 | ' ]], 189 | 190 | ['8'] = [[ 191 | ,-. 192 | >-< 193 | `-' ]], 194 | 195 | ['9'] = [[ 196 | ,-. 197 | `-| 198 | `-' ]], 199 | 200 | [';'] = [[ 201 | :; 202 | :; 203 | ,' ]], 204 | } 205 | -------------------------------------------------------------------------------- /config/nushell/scripts/git/clone.nu: -------------------------------------------------------------------------------- 1 | # Git clone with smart defaults 2 | export def --wrapped gcl [...args] { 3 | mut opts = ["--recurse-submodules"] 4 | mut positional = [] 5 | mut is_bare = false 6 | # Separate options from positional arguments 7 | for item in $args { 8 | if ($item | to text | str starts-with "-") { 9 | $opts = ($opts | append $item) 10 | if $item == "--bare" { 11 | $is_bare = true 12 | } 13 | } else { 14 | $positional = ($positional | append $item) 15 | } 16 | } 17 | mut dest = "" 18 | # Determine destination based on arguments 19 | if ($positional | length) == 1 { 20 | let repo = ($positional | get 0) 21 | let git_dir = ($env.GIT_PROJECTS_DIR? | default ([$env.HOME "git"] | path join)) 22 | $dest = ([$git_dir ($repo | path basename)] | path join) 23 | $positional = [$repo $dest] 24 | } else if ($positional | length) == 2 and ($positional | get 1) == "." { 25 | let repo = ($positional | get 0) 26 | $dest = ($repo | path basename) 27 | $positional = [$repo $dest] 28 | } else if ($positional | length) >= 2 { 29 | $dest = ($positional | get 1 | path expand) 30 | if ($dest | path exists) and ($dest | path type) == "dir" and (ls -a $dest | is-not-empty) { 31 | let repo = ($positional | get 0) 32 | $dest = ([$dest ($repo | path basename)] | path join) 33 | } 34 | $positional = ($positional | update 1 $dest) 35 | } 36 | # Check if we should clone 37 | let should_clone = if ($dest | is-empty) { 38 | true 39 | } else if not ($dest | path exists) { 40 | true 41 | } else if ($dest | path type) != "dir" { 42 | true 43 | } else { 44 | let has_git = ([$dest ".git"] | path join | path exists) 45 | let has_head = ([$dest "HEAD"] | path join | path exists) 46 | not $has_git and not $has_head 47 | } 48 | if $should_clone { 49 | let git_args = ($opts | append $positional) 50 | do { git clone ...$git_args } 51 | # Fix fetch refspec for bare clones 52 | if $is_bare and not ($dest | is-empty) { 53 | git -C $dest config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" 54 | } 55 | } else { 56 | print -e "Skipping clone: Repository already exists" 57 | } 58 | $dest 59 | } 60 | 61 | # Git clone and cd 62 | export def --env --wrapped gccl [...args] { 63 | let dest = (gcl ...$args) 64 | cd $dest 65 | } 66 | -------------------------------------------------------------------------------- /config/starship.toml: -------------------------------------------------------------------------------- 1 | "$schema" = 'https://starship.rs/config-schema.json' 2 | 3 | add_newline = false 4 | 5 | format = """ 6 | ${username}${hostname} [❤](bold purple) ${directory}${custom.git_dirty} 7 | """ 8 | right_format = """ 9 | ${cmd_duration}${status}${nix_shell}${python}${rust}${nodejs}${git_metrics}${git_branch}${git_commit}${git_status}${time} 10 | """ 11 | 12 | [username] 13 | format = '[$user]($style)' 14 | style_user = 'bold green' 15 | detect_env_vars = ['SSH_CONNECTION'] 16 | 17 | [hostname] 18 | ssh_only = true 19 | format = '[@$hostname]($style)' 20 | style = 'bold green' 21 | 22 | [directory] 23 | truncation_length = 3 24 | style = "cyan" 25 | truncate_to_repo = false 26 | 27 | [git_metrics] 28 | disabled = false 29 | format = '([+$added]($added_style) )([-$deleted]($deleted_style) )' 30 | added_style = 'green' 31 | deleted_style = 'red' 32 | ignore_submodules = true 33 | 34 | [git_branch] 35 | format = '[$branch(:$remote_branch)]($style) ' 36 | style = 'purple' 37 | 38 | [git_status] 39 | format = '([$all_status$ahead_behind]($style) )' 40 | style = 'purple' 41 | 42 | [package] 43 | disabled = false 44 | 45 | [status] 46 | disabled = false 47 | symbol = "↪ " 48 | style = "bold yellow" 49 | 50 | [time] 51 | disabled = false 52 | format = '[$time]($style) ' 53 | style = '242' 54 | 55 | [cmd_duration] 56 | disabled = false 57 | min_time = 2000 58 | format = '[$duration]($style) ' 59 | style = 'bold yellow' 60 | 61 | [python] 62 | format = '[$symbol$virtualenv@$version]($style) ' 63 | symbol = ' ' 64 | version_format = '$raw' 65 | 66 | [rust] 67 | format = '[$symbol$version]($style) ' 68 | version_format = '$raw' 69 | symbol = ' ' 70 | style = 'bold #dea584' 71 | 72 | [nodejs] 73 | format = '[$symbol$version]($style) ' 74 | version_format = '$raw' 75 | 76 | [nix_shell] 77 | format = '[$symbol$state( \($name\))]($style) ' 78 | symbol = ' ' 79 | 80 | [custom.git_dirty] 81 | # command = '/usr/bin/git diff --no-ext-diff --quiet HEAD && echo "" || echo "ϟ"' 82 | command = 'if (/usr/bin/git diff --no-ext-diff --quiet HEAD | complete).exit_code == 0 { echo "" } else { echo "ϟ" }' 83 | # This version is 2x slower, but it also detects untracked files: 84 | # command = '/usr/bin/git status --porcelain --ignore-submodules 2>/dev/null | grep -q "." && echo "ϟ" || echo ""' 85 | shell = 'nu' 86 | format = '[$output]($style) ' 87 | style = "blue" 88 | when = true 89 | require_repo = true 90 | -------------------------------------------------------------------------------- /config/nvim/after/queries/python/injections.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | 3 | ; based on https://github.com/nvim-treesitter/nvim-treesitter/blob/9d2acd49976e2a9da72949008df03436f781fd23/queries/ecma/injections.scm 4 | 5 | ;; Support for injecting other syntaxes into Python. 6 | ;; Example: 7 | ;; 8 | ;; class Annotate: 9 | ;; def __getattr__(self, name: str): 10 | ;; def annotate(code: str) -> str: 11 | ;; return code 12 | ;; 13 | ;; return annotate 14 | ;; 15 | ;; annotate = Annotate() 16 | ;; js = annotate.js 17 | ;; 18 | ;; // Normal function call: 19 | ;; js("console.log('hello world')") 20 | ;; 21 | ;; // Method call: 22 | ;; annotate.js("console.log('hello world')") 23 | 24 | ; javascript("..."), typescript("""..."""), etc. 25 | (call 26 | function: (identifier) @injection.language 27 | arguments: (argument_list 28 | (string) @injection.content) 29 | (#match? @injection.language "^[a-zA-Z][a-zA-Z0-9]*$") 30 | (#offset! @injection.content 0 1 0 -1) 31 | (#set! injection.include-children) 32 | ; Languages excluded from auto-injection due to special rules 33 | ; - svg uses the html parser 34 | (#not-any-of? @injection.language "svg")) 35 | 36 | ; obj.lang("..."), obj.lang("""..."""), etc. 37 | (call 38 | function: (attribute 39 | object: (identifier) 40 | attribute: (identifier) @injection.language) 41 | arguments: (argument_list 42 | (string) @injection.content) 43 | (#match? @injection.language "^[a-zA-Z][a-zA-Z0-9]*$") 44 | (#offset! @injection.content 0 1 0 -1) 45 | (#set! injection.include-children) 46 | ; Languages excluded from auto-injection due to special rules 47 | ; - svg uses the html parser 48 | (#not-any-of? @injection.language "svg")) 49 | 50 | ; svg("..."), svg("""..."""), etc. 51 | (call 52 | function: (identifier) @_name 53 | (#eq? @_name "svg") 54 | arguments: (argument_list 55 | (string) @injection.content) 56 | (#offset! @injection.content 0 1 0 -1) 57 | (#set! injection.include-children) 58 | (#set! injection.language "html")) 59 | 60 | ; obj.svg("..."), obj.svg("""..."""), etc. 61 | (call 62 | function: (attribute 63 | object: (identifier) 64 | attribute: (identifier) @_name) 65 | (#eq? @_name "svg") 66 | arguments: (argument_list 67 | (string) @injection.content) 68 | (#offset! @injection.content 0 1 0 -1) 69 | (#set! injection.include-children) 70 | (#set! injection.language "html")) 71 | 72 | -------------------------------------------------------------------------------- /config/niri/bin/inspect: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S nu --stdin 2 | 3 | # Inspection tools for niri 4 | def main [] { 5 | ^$env.CURRENT_FILE "--help" 6 | } 7 | 8 | # Get info about a specific window 9 | # Human-readable output is copied to clipboard as text/plain 10 | # JSON output is copied to clipboard as application/json 11 | def "main window-info" [] { 12 | let info = (niri msg --json pick-window | from json) 13 | 14 | if $info == null { 15 | return 16 | } 17 | 18 | let id = $info.id 19 | let title = $info.title 20 | let app_id = $info.app_id 21 | let pid = $info.pid 22 | let workspace_id = $info.workspace_id 23 | let is_focused = $info.is_focused 24 | let is_floating = $info.is_floating 25 | let is_urgent = $info.is_urgent 26 | 27 | let ws_path = which xwayland-satellite | get -o 0.path 28 | let win_exe = ls -l $"/proc/($pid)/exe" | get -o 0.target 29 | 30 | let shell = if ($win_exe | is-not-empty) and ($ws_path | is-not-empty) { 31 | if ($win_exe == $ws_path) { 32 | "xwayland" 33 | } else { 34 | "wayland" 35 | } 36 | } else "unknown" 37 | 38 | let size = if ($info.layout.window_size? != null) { 39 | $info.layout.window_size | str join "x" 40 | } else { 41 | "" 42 | } 43 | 44 | let msg = $" 45 | id: ($id) 46 | title: ($title | str substring 0..25) 47 | app_id: ($app_id) 48 | pid: ($pid) 49 | workspace_id: ($workspace_id) 50 | is_focused: ($is_focused) 51 | is_floating: ($is_floating) 52 | is_urgent: ($is_urgent) 53 | shell: ($shell) 54 | size: ($size) 55 | " 56 | 57 | let info_json = ($info | to json) 58 | 59 | let res = (notify-send -t 30000 $app_id $msg 60 | -A $"id=Copy ID \(($id)\)" 61 | -A $"title=Copy Title \(($title)\)" 62 | -A $"app_id=Copy App ID \(($app_id)\)" 63 | -A $"pid=Copy PID \(($pid)\)" 64 | -A $"workspace_id=Copy Workspace ID \(($workspace_id)\)" 65 | -A $"size=Copy Size \(($size)\)" 66 | -A $"json=Copy JSON" 67 | | complete) 68 | if $res.exit_code != 0 { 69 | return 70 | } 71 | let action = $res.stdout | str trim 72 | 73 | print $action 74 | 75 | match $action { 76 | "id" => { wl-copy $"($id)" } 77 | "title" => { wl-copy $title } 78 | "app_id" => { wl-copy $app_id } 79 | "pid" => { wl-copy $"($pid)" } 80 | "workspace_id" => { wl-copy $"($workspace_id)" } 81 | "size" => { wl-copy $size } 82 | "json" => { wl-copy $info_json } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/debounce.lua: -------------------------------------------------------------------------------- 1 | local Debounce = {} 2 | 3 | function Debounce:clear_timer() 4 | if self.timer then 5 | self.timer:stop() 6 | self.timer = nil 7 | end 8 | end 9 | 10 | function Debounce:reset() 11 | self:clear_timer() 12 | self.phase = 0 13 | self.waiting = false 14 | end 15 | 16 | function Debounce:call(...) 17 | local args = { ... } 18 | if self.phase == 0 then 19 | self.phase = 1 20 | self.timer = vim.defer_fn(function() -- 1 -> 2 (rising) 21 | if self.opts.mode == 'throttle' then 22 | self:immediate(unpack(args)) 23 | self.phase = 2 24 | end 25 | self:clear_timer() 26 | self.timer = vim.defer_fn(function() -- 2 -> 0 (falling) 27 | if self.opts.mode == 'rolling' then 28 | self:immediate(unpack(args)) 29 | else 30 | if self.waiting then 31 | self:immediate(unpack(args)) 32 | else 33 | self:reset() 34 | end 35 | end 36 | end, type(self.opts.threshold) == 'table' and self.opts.threshold.falling or self.opts.threshold) 37 | end, type(self.opts.threshold) == 'table' and self.opts.threshold.rising or self.opts.threshold) 38 | elseif self.phase == 2 then 39 | self.waiting = true 40 | end 41 | end 42 | 43 | function Debounce:immediate(...) 44 | self:reset() 45 | self.fn(...) 46 | end 47 | 48 | -- ref() returns a normal function which, when called, calls Debounce:call() 49 | -- bound to the original instance. 50 | -- Useful for using Debounce with an API that doesn't accept callable tables. 51 | function Debounce:ref() 52 | return function(...) 53 | self:call(...) 54 | end 55 | end 56 | 57 | local function make(fn, opts) 58 | return setmetatable({ 59 | fn = fn, 60 | timedout = false, 61 | waiting = false, 62 | -- 0: idle 63 | -- 1: trigger 64 | -- 1 -> 2: rising 65 | -- 2: high 66 | -- 2 -> 0: falling 67 | phase = 0, 68 | opts = vim.tbl_extend('force', { 69 | threshold = { 70 | rising = 100, 71 | falling = 100, 72 | }, 73 | -- throttle: fn will be called at least once every (rising + falling) milliseconds 74 | -- rolling: fn will be called only once after the falling edge, new triggers will keep extending the timer 75 | mode = 'throttle', 76 | }, opts or {}), 77 | }, { 78 | __index = Debounce, 79 | __call = Debounce.call, 80 | }) 81 | end 82 | 83 | return make 84 | -------------------------------------------------------------------------------- /config/nvim/autoload/user/fn.vim: -------------------------------------------------------------------------------- 1 | function! s:split_cmdline(cmdline, cmdpos) 2 | let l:res = [ 3 | \ (' ' . a:cmdline)[:a:cmdpos - 1][1:], 4 | \ (' ' . a:cmdline)[a:cmdpos - 1:][1:] 5 | \ ] 6 | return l:res 7 | endfunction 8 | 9 | " emacs-style word-wise movement/deletion in command-line mode 10 | function! user#fn#cmdlineMoveWord(dir, del) 11 | let l:cmdline = getcmdline() 12 | let l:cmdpos = getcmdpos() 13 | let l:pat = '^\s*\W*\S\{-}\($\|\w\@<=\([^a-zA-Z0-9]\|\s\|\>\)\@<=\)' 14 | if a:dir == 1 15 | let l:cl = s:split_cmdline(cmdline, cmdpos) 16 | let l:cmdline_r_post = substitute(l:cl[1], l:pat, '', '') 17 | if a:del == 1 18 | let l:cmdline_new = l:cl[0] . l:cmdline_r_post 19 | return l:cmdline_new 20 | else 21 | let l:cmdlen_r = len(l:cl[1]) 22 | let l:cmdlen_r_post = len(l:cmdline_r_post) 23 | let l:cmdlen_r_diff = l:cmdlen_r - l:cmdlen_r_post 24 | let l:newcmdpos = l:cmdpos + l:cmdlen_r_diff 25 | call setcmdpos(l:newcmdpos) 26 | return l:cmdline 27 | endif 28 | elseif a:dir == -1 29 | let l:cmdline_rev = join(reverse(split(l:cmdline, '.\@=')), '') 30 | let l:cmdpos_rev = len(l:cmdline) - l:cmdpos + 2 31 | let l:cl_rev = s:split_cmdline(cmdline_rev, cmdpos_rev) 32 | let l:cmdline_rev_r_post = substitute(l:cl_rev[1], l:pat, '', '') 33 | let l:cmdlen_rev_r = len(l:cl_rev[1]) 34 | let l:cmdlen_rev_r_post = len(l:cmdline_rev_r_post) 35 | let l:cmdlen_rev_r_diff = l:cmdlen_rev_r - l:cmdlen_rev_r_post 36 | let l:newcmdpos = l:cmdpos - l:cmdlen_rev_r_diff 37 | call setcmdpos(l:newcmdpos) 38 | if a:del == 1 39 | let l:cmdline_rev_new = l:cl_rev[0] . l:cmdline_rev_r_post 40 | let l:cmdline_new = join(reverse(split(l:cmdline_rev_new, '.\@=')), '') 41 | return l:cmdline_new 42 | else 43 | return l:cmdline 44 | endif 45 | endif 46 | throw 'invalid direction ' . a:dir 47 | endfunction 48 | 49 | function! user#fn#manSectionMove(direction, mode, count) 50 | norm! m' 51 | if a:mode ==# 'v' 52 | norm! gv 53 | endif 54 | let i = 0 55 | while i < a:count 56 | let i += 1 57 | " saving current position 58 | let line = line('.') 59 | let col = col('.') 60 | let pos = search('^\a\+', 'W'.a:direction) 61 | " if there are no more matches, return to last position 62 | if pos == 0 63 | call cursor(line, col) 64 | return 65 | endif 66 | endwhile 67 | endfunction 68 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/linting.lua: -------------------------------------------------------------------------------- 1 | ---@class Linter: lint.Linter 2 | ---@field name string 3 | 4 | ---@alias LinterSpec string|Linter 5 | 6 | ---@type { [string]: LinterSpec[] } 7 | local linters_by_ft = { 8 | cpp = { 9 | -- { name = 'cppcheck', args = { '--enable=all', '--inconclusive' } }, 10 | -- 'cpplint', 11 | }, 12 | cmake = { 'cmakelint' }, 13 | nix = { 'nix', 'statix' }, 14 | } 15 | 16 | ---@type LazySpec[] 17 | return { 18 | { 19 | 'mfussenegger/nvim-lint', 20 | ft = vim.tbl_keys(linters_by_ft), 21 | config = function() 22 | local lint = require 'lint' 23 | local by_ft = {} 24 | for ft, linters in pairs(linters_by_ft or {}) do 25 | for _, linter in ipairs(linters) do 26 | local linter_name = type(linter) == 'string' and linter or linter.name 27 | local linter_base = lint.linters[linter_name] 28 | if not linter_base then 29 | error('Unknown linter: ' .. linter_name) 30 | end 31 | ---@cast linter_base lint.Linter 32 | local extended = vim.tbl_deep_extend( -- 33 | 'force', 34 | vim.deepcopy(linter_base), 35 | type(linter) == 'table' and linter or {} 36 | ) 37 | by_ft[ft] = by_ft[ft] or {} 38 | table.insert(by_ft[ft], linter_name) 39 | lint.linters[linter_name] = extended 40 | end 41 | end 42 | lint.linters_by_ft = by_ft 43 | 44 | vim.api.nvim_create_autocmd({ 'BufWritePost', 'BufReadPost', 'TextChanged', 'InsertLeave' }, { 45 | group = vim.api.nvim_create_augroup('user_lint', { clear = true }), 46 | callback = function(event) 47 | local bufnr = event.buf 48 | local ft = vim.bo[bufnr].filetype 49 | local linters = by_ft[ft] 50 | if not linters then 51 | return 52 | end 53 | -- If this is a TextChanged or InsertLeave event, only lint if there 54 | -- are linters for the filetype that can accept stdin 55 | if event.event == 'TextChanged' or event.event == 'InsertLeave' then 56 | local any_stdin = vim.iter(linters):any(function(linter_name) 57 | local linter = lint.linters[linter_name] 58 | return linter.stdin 59 | end) 60 | if not any_stdin then 61 | return 62 | end 63 | end 64 | lint.try_lint() 65 | end, 66 | }) 67 | end, 68 | }, 69 | } 70 | -------------------------------------------------------------------------------- /config/nvim/lua/user/keys.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local utf8 = function(decimal) 4 | if type(decimal) == 'string' then 5 | decimal = vim.fn.char2nr(decimal) 6 | end 7 | if decimal < 128 then 8 | return string.char(decimal) 9 | end 10 | local charbytes = {} 11 | for bytes, vals in ipairs { { 0x7FF, 192 }, { 0xFFFF, 224 }, { 0x1FFFFF, 240 } } do 12 | if decimal <= vals[1] then 13 | for b = bytes + 1, 2, -1 do 14 | local mod = decimal % 64 15 | decimal = (decimal - mod) / 64 16 | charbytes[b] = string.char(128 + mod) 17 | end 18 | charbytes[1] = string.char(vals[2] + decimal) 19 | break 20 | end 21 | end 22 | return table.concat(charbytes) 23 | end 24 | 25 | -- Extra keys 26 | -- Configure your terminal emulator to send the unicode codepoint for each 27 | -- given key sequence 28 | M.xk = setmetatable({ 29 | [ [[]] ] = utf8(0xff01), 30 | [ [[]] ] = utf8(0xff03), 31 | [ [[]] ] = utf8(0xff04), 32 | [ [[]] ] = utf8(0xff05), 33 | [ [[]] ] = utf8(0xff06), 34 | [ [[]] ] = utf8(0x00a7), 35 | [ [[]] ] = utf8(0x00f0), 36 | [ [[]] ] = utf8(0x00f1), 37 | [ [[]] ] = utf8(0x00f2), 38 | [ [[]] ] = utf8(0x00ff), 39 | [ [[]] ] = utf8(0x00f3), 40 | [ [[]] ] = utf8(0x00f4), 41 | [ [[]] ] = utf8(0x00f5), 42 | [ [[]] ] = utf8(0x00f6), 43 | [ [[]] ] = utf8(0x00f7), 44 | [ [[]] ] = utf8(0x00f8), 45 | [ [[]] ] = utf8(0x00fa), 46 | [ [[]] ] = utf8(0x00fb), 47 | [ [[]] ] = utf8(0x00fc), 48 | [ [[]] ] = utf8(0x00fd), 49 | [ [[]] ] = utf8(0x00fe), 50 | [ [[]] ] = utf8(0x00d4), 51 | [ [[]] ] = utf8(0x00d5), 52 | [ [[]] ] = utf8(0x00d6), 53 | [ [[]] ] = utf8(0x00d7), 54 | [ [[]] ] = utf8(0x00d8), 55 | [ [[]] ] = utf8(0x00d9), 56 | [ [[]] ] = utf8(0x00da), 57 | [ [[]] ] = utf8(0x00db), 58 | [ [[]] ] = utf8(0x00dc), 59 | [ [[]] ] = utf8(0x00d0), 60 | [ [[]] ] = utf8(0x00d1), 61 | [ [[]] ] = utf8(0x00d2), 62 | [ [[]] ] = utf8(0x00d3), 63 | [ [[]] ] = utf8(0x00db), 64 | 65 | [ [[]] ] = '', 66 | [ [[]] ] = '', 67 | [ [[]] ] = '', 68 | [ [[]] ] = '', 69 | [ [[]] ] = '', 70 | }, { 71 | __index = function(self, k) 72 | local v = rawget(self, k) 73 | if v == nil then 74 | error('Key ' .. k .. ' not found', 2) 75 | end 76 | return v 77 | end, 78 | __call = function(self, k) return self[k] end, 79 | }) 80 | 81 | return M 82 | -------------------------------------------------------------------------------- /config/nvim/lua/overseer/template/mise.lua: -------------------------------------------------------------------------------- 1 | ---Source: https://github.com/stevearc/overseer.nvim/pull/414 2 | ---TODO: Remove this file when merged 3 | 4 | local log = require 'overseer.log' 5 | local overseer = require 'overseer' 6 | 7 | ---@type overseer.TemplateFileDefinition 8 | local tmpl = { 9 | priority = 60, 10 | params = { 11 | args = { type = 'list', delimiter = ' ' }, 12 | cwd = { optional = true }, 13 | }, 14 | builder = function(params) 15 | local cmd = { 'mise', 'run' } 16 | return { 17 | args = params.args, 18 | cmd = cmd, 19 | cwd = params.cwd, 20 | } 21 | end, 22 | } 23 | 24 | ---@param opts overseer.SearchParams 25 | ---@return nil|string 26 | local function get_mise_file(opts) 27 | local is_misefile = function(name) 28 | name = name:lower() 29 | return name == 'mise.toml' or name == '.mise.toml' 30 | end 31 | return vim.fs.find(is_misefile, { upward = true, path = opts.dir })[1] 32 | end 33 | 34 | ---@type overseer.TemplateFileProvider 35 | local provider = { 36 | cache_key = function(opts) return get_mise_file(opts) end, 37 | condition = { 38 | callback = function(opts) 39 | if vim.fn.executable 'mise' == 0 then 40 | return false, 'Command "mise" not found' 41 | end 42 | if not get_mise_file(opts) then 43 | return false, 'No mise.toml found' 44 | end 45 | return true 46 | end, 47 | }, 48 | generator = function(opts, cb) 49 | local ret = {} 50 | local jid = vim.fn.jobstart({ 'mise', 'tasks', '--json' }, { 51 | stdout_buffered = true, 52 | on_stdout = vim.schedule_wrap(function(_, output) 53 | local ok, data = pcall(vim.json.decode, table.concat(output, ''), { luanil = { object = true } }) 54 | if not ok then 55 | log:error('mise produced invalid json: %s\n%s', data, output) 56 | cb(ret) 57 | return 58 | end 59 | assert(data) 60 | for _, value in pairs(data) do 61 | table.insert( 62 | ret, 63 | overseer.wrap_template(tmpl, { 64 | name = string.format('mise %s', value.name), 65 | desc = value.description ~= '' and value.description or nil, 66 | }, { 67 | args = { value.name }, 68 | cwd = opts.dir, 69 | }) 70 | ) 71 | end 72 | cb(ret) 73 | end), 74 | }) 75 | if jid == 0 then 76 | log:error 'Passed invalid arguments to "mise tasks"' 77 | cb(ret) 78 | elseif jid == -1 then 79 | log:error '"mise" is not executable' 80 | cb(ret) 81 | end 82 | end, 83 | } 84 | 85 | return provider 86 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/lsp_status.lua: -------------------------------------------------------------------------------- 1 | ---@alias user.util.lsp_status.client_id integer 2 | 3 | ---@class user.util.lsp_status.ClientExitResult 4 | ---@field status 'exited' 5 | ---@field name string 6 | ---@field code integer 7 | ---@field signal integer 8 | ---@field attached_buffers integer[] 9 | 10 | local M = { 11 | clients = { 12 | ---@type table 13 | attached = {}, 14 | ---@type table 15 | exited = {}, 16 | }, 17 | } 18 | 19 | function M.on_attach(client, _) 20 | M.clients.attached[client.id] = client 21 | M.clients.exited[client.id] = nil 22 | for id, exitedClient in pairs(M.clients.exited) do 23 | if client.name == exitedClient.name then 24 | M.clients.exited[id] = nil 25 | end 26 | end 27 | end 28 | 29 | function M.on_exit(code, signal, id) 30 | local client = M.clients.attached[id] 31 | M.clients.exited[id] = { 32 | status = 'exited', 33 | name = client.name, 34 | code = code, 35 | signal = signal, 36 | attached_buffers = vim.deepcopy(client.attached_buffers), 37 | } 38 | M.clients.attached[id] = nil 39 | vim.notify( 40 | 'LSP client ' .. client.name .. ' (' .. id .. ') exited with code ' .. code .. ' and signal ' .. signal 41 | ) 42 | end 43 | 44 | ---@param bufnr integer 45 | ---@param clients? table 46 | ---@return (vim.lsp.Client|user.util.lsp_status.ClientExitResult)[] 47 | local function buf_clients(bufnr, clients) 48 | return vim.iter(clients and { clients } or { M.clients.exited, M.clients.attached }) 49 | :flatten() 50 | :filter( 51 | function(client) ---@param client vim.lsp.Client|user.util.lsp_status.ClientExitResult 52 | return client.attached_buffers[bufnr] == true 53 | end) 54 | :totable() 55 | end 56 | 57 | function M.status_clients_count(status, bufnr) 58 | bufnr = bufnr or vim.api.nvim_get_current_buf() 59 | local clients = {} 60 | if status == 'exited' or status == 'exited_ok' or status == 'exited_err' then 61 | clients = buf_clients(bufnr, M.clients.exited) 62 | elseif status == 'running' or status == 'starting' then 63 | clients = buf_clients(bufnr, M.clients.attached) 64 | else 65 | error('Invalid status: ' .. status) 66 | end 67 | local count = 0 68 | for _, c in pairs(clients) do 69 | local skip = false 70 | if c.status == 'exited' then 71 | skip = skip or status == 'exited_ok' and c.signal ~= 0 72 | skip = skip or status == 'exited_err' and c.signal == 0 73 | else 74 | local initialized = c.initialized == true 75 | skip = skip or status == 'starting' and initialized 76 | skip = skip or status == 'running' and not initialized 77 | end 78 | count = skip and count or count + 1 79 | end 80 | return count 81 | end 82 | 83 | return M 84 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/git.lua: -------------------------------------------------------------------------------- 1 | local curl = require 'plenary.curl' 2 | 3 | local M = {} 4 | 5 | ---@return string? 6 | M.gh_repo_name_with_owner = function() 7 | local out = vim.system({ 'gh', 'repo', 'view', '--json', 'nameWithOwner' }):wait() 8 | if out.code ~= 0 or not out.stdout or out.stdout == '' then 9 | return nil 10 | end 11 | local ok, json = pcall(vim.json.decode, out.stdout) 12 | if 13 | not ok 14 | or not json 15 | or not json.nameWithOwner 16 | or type(json.nameWithOwner) ~= 'string' 17 | or json.nameWithOwner == '' 18 | then 19 | return nil 20 | end 21 | return json.nameWithOwner 22 | end 23 | 24 | M.get_gh_token = function() 25 | local out = vim.system({ 'gh', 'auth', 'token' }):wait() 26 | if out.code ~= 0 or not out.stdout or out.stdout == '' then 27 | return nil 28 | end 29 | return out.stdout 30 | end 31 | 32 | ---@param pr_number number 33 | ---@return string[]? 34 | M.gh_pr_range = function(pr_number) 35 | local name_with_owner = M.gh_repo_name_with_owner() 36 | if not name_with_owner then 37 | return nil 38 | end 39 | local gh_token = M.get_gh_token() 40 | if not gh_token then 41 | return nil 42 | end 43 | local res = curl.get { 44 | url = string.format('https://api.github.com/repos/%s/pulls/%s/commits', name_with_owner, pr_number), 45 | headers = { 46 | Authorization = 'Bearer ' .. gh_token, 47 | Accept = 'application/vnd.github.v3+json', 48 | }, 49 | } 50 | if res.status ~= 200 then 51 | return nil 52 | end 53 | if not res.body or res.body == '' then 54 | return nil 55 | end 56 | local ok, body = pcall(vim.json.decode, res.body) 57 | if not ok or not body or not body.base or not body.head then 58 | return nil 59 | end 60 | if not vim.islist(body) then 61 | return nil 62 | end 63 | local head = body[1] -- newest commit 64 | local tail = body[#body] -- oldest commit 65 | -- get parent of oldest commit using the API 66 | local res = curl.get { 67 | url = string.format('https://api.github.com/repos/%s/pulls/%s/commits/%s', name_with_owner, pr_number, tail.sha), 68 | headers = { 69 | Authorization = 'Bearer ' .. gh_token, 70 | Accept = 'application/vnd.github.v3+json', 71 | }, 72 | } 73 | return { base_sha, head_sha } 74 | end 75 | 76 | M.branch_exists = function(branch_name) 77 | local out = vim.system({ 'git', 'branch', '--list', branch_name }):wait() 78 | if out.code ~= 0 or not out.stdout then 79 | return false 80 | end 81 | return out.stdout:match(branch_name) 82 | end 83 | 84 | M.fetch_pr = function(pr_number) 85 | local branch_name = 'pull/' .. pr_number 86 | local remote_head = 'refs/pull/' .. pr_number .. '/head' 87 | local out = vim.system({ 'git', 'fetch', 'origin', remote_head .. ':' .. branch_name }):wait() 88 | if out.code ~= 0 then 89 | return nil 90 | end 91 | if not M.branch_exists(branch_name) then 92 | return nil 93 | end 94 | return branch_name 95 | end 96 | 97 | return M 98 | -------------------------------------------------------------------------------- /config/nushell/scripts/rofi/mod.nu: -------------------------------------------------------------------------------- 1 | # Rofi helper functions for nushell scripts 2 | 3 | # Display a rofi dmenu with optional image preview panel 4 | export def main [ 5 | --input: string # Pre-formatted rofi input (alternative to --options) 6 | --options: list # Simple options list 7 | --icon: string # Icon file for all options (used with --options, requires --preview) 8 | --prompt: string # Prompt text 9 | --mesg: string # Optional message 10 | --placeholder: string # Search field placeholder (null = default) 11 | --lines: int = 6 # Number of visible lines 12 | --preview # Enable image preview panel 13 | --multi-select # Enable multi-select mode 14 | --win-scale: int = 3 # Window is 1/N of screen (3 = 33%, 2 = 50%) 15 | --list-width: string # Custom listview width CSS (e.g., "35%") 16 | --element-icon-size: int # Size for per-element icons (null = disabled) 17 | --extra: list # Extra rofi args (e.g., kb shortcuts) 18 | ] { 19 | let rofi_input = if $input != null { 20 | $input 21 | } else if $preview and $icon != null { 22 | $options | each {|opt| $"($opt)\u{0}icon\u{1f}($icon)" } | str join "\n" 23 | } else { 24 | $options | str join "\n" 25 | } 26 | 27 | let mesg_args = if $mesg != null { [-mesg $mesg] } else { [] } 28 | let placeholder_css = if $placeholder != null { 29 | [-theme-str $"entry { placeholder: \"($placeholder)\"; }"] 30 | } else { 31 | [] 32 | } 33 | 34 | let multi_args = if $multi_select { [-multi-select] } else { [] } 35 | 36 | if $preview { 37 | let mon = niri msg -j focused-output | from json | get logical 38 | let win_width = $mon.width // $win_scale 39 | let win_height = $mon.height // $win_scale 40 | let preview_size = [($win_height - 100), ($win_width * 55 // 100)] | math min 41 | 42 | let list_width_css = if $list_width != null { $" width: ($list_width);" } else { "" } 43 | let element_icon_css = if $element_icon_size != null { 44 | $"element-icon { size: ($element_icon_size)px; }" 45 | } else { 46 | "element-icon { enabled: false; }" 47 | } 48 | 49 | $rofi_input | ^rofi -dmenu -i -p $prompt -show-icons ...$multi_args ...$mesg_args ...[ 50 | -theme-str $"window { width: ($win_width)px; height: ($win_height)px; children: [ mainbox ]; }" 51 | -theme-str "mainbox { children: [ inputbar, message, listview-split ]; }" 52 | -theme-str "listview-split { orientation: horizontal; spacing: 1em; children: [listview, icon-current-entry]; }" 53 | -theme-str $"listview {($list_width_css) columns: 1; lines: ($lines); fixed-columns: true; }" 54 | -theme-str $"icon-current-entry { expand: true; size: ($preview_size)px; }" 55 | -theme-str $element_icon_css 56 | ] ...$placeholder_css ...($extra | default []) | complete 57 | } else { 58 | $rofi_input | ^rofi -dmenu -i -p $prompt ...$multi_args ...$mesg_args ...[ 59 | -theme-str $"listview { lines: ($lines); }" 60 | ] ...$placeholder_css ...($extra | default []) | complete 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Maddison's Dotfiles"; 3 | 4 | inputs = { 5 | # Nix/NixOS 6 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 7 | flakey-profile.url = "github:lf-/flakey-profile"; 8 | disko = { 9 | url = "github:nix-community/disko"; 10 | inputs.nixpkgs.follows = "nixpkgs"; 11 | }; 12 | 13 | # Neovim 14 | neovim-nightly-overlay = { 15 | url = "github:nix-community/neovim-nightly-overlay"; 16 | inputs.nixpkgs.follows = "nixpkgs"; 17 | }; 18 | 19 | # Nushell 20 | nushell-nightly = { 21 | url = "github:JoaquinTrinanes/nushell-nightly-flake"; 22 | inputs.nixpkgs.follows = "nixpkgs"; 23 | }; 24 | bash-env-nushell = { 25 | url = "github:tesujimath/bash-env-nushell"; 26 | inputs.nixpkgs.follows = "nixpkgs"; 27 | }; 28 | 29 | # Niri 30 | niri = { 31 | url = "github:sodiboo/niri-flake"; 32 | inputs.nixpkgs.follows = "nixpkgs"; 33 | }; 34 | wlr-which-key-b0o = { 35 | url = "github:b0o/wlr-which-key/b0o"; 36 | inputs.nixpkgs.follows = "nixpkgs"; 37 | }; 38 | 39 | # Misc 40 | opencode = { 41 | url = "github:b0o/opencode/b0o"; 42 | inputs.nixpkgs.follows = "nixpkgs"; 43 | }; 44 | opentui-src = { 45 | url = "github:b0o/opentui/b0o"; 46 | flake = false; 47 | }; 48 | opentui-spinner-src = { 49 | url = "github:msmps/opentui-spinner"; 50 | flake = false; 51 | }; 52 | }; 53 | 54 | outputs = { 55 | self, 56 | nixpkgs, 57 | flakey-profile, 58 | ... 59 | } @ inputs: let 60 | inherit (nixpkgs) lib; 61 | forAllSystems = lib.genAttrs [ 62 | "x86_64-linux" 63 | # "x86_64-darwin" 64 | ]; 65 | in rec { 66 | overlays = { 67 | default = import ./nix/overlays {inherit inputs;}; 68 | neovim = inputs.neovim-nightly-overlay.overlays.default; 69 | nushell = inputs.nushell-nightly.overlays.default; 70 | inherit (inputs.niri.overlays) niri; 71 | }; 72 | 73 | devShells = forAllSystems (system: { 74 | default = legacyPackages.${system}.callPackage ./shell.nix {}; 75 | }); 76 | 77 | legacyPackages = forAllSystems ( 78 | system: 79 | import inputs.nixpkgs { 80 | inherit system; 81 | overlays = builtins.attrValues overlays; 82 | 83 | # NOTE: Using `nixpkgs.config` in NixOS config won't work 84 | # Instead, set nixpkgs configs here 85 | # (https://nixos.org/manual/nixpkgs/stable/#idm140737322551056) 86 | config.allowUnfree = true; 87 | } 88 | ); 89 | 90 | packages = forAllSystems (system: let 91 | pkgs = legacyPackages."${system}"; 92 | in { 93 | profile.dev = 94 | pkgs.callPackage ./nix/profiles/dev.nix {inherit pkgs inputs;}; 95 | profile.minimal = 96 | pkgs.callPackage ./nix/profiles/minimal.nix {inherit pkgs inputs;}; 97 | }); 98 | 99 | nixosConfigurations = { 100 | boonix = nixpkgs.lib.nixosSystem { 101 | system = "x86_64-linux"; 102 | specialArgs = {inherit inputs;}; 103 | modules = [ 104 | ./nix/hosts/boonix 105 | ]; 106 | }; 107 | }; 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/oil.lua: -------------------------------------------------------------------------------- 1 | ---@type LazySpec[] 2 | local spec = { 3 | { 4 | 'stevearc/oil.nvim', 5 | cmd = 'Oil', 6 | -- If nvim is started with a directory argument, load oil immediately 7 | -- via https://github.com/folke/lazy.nvim/issues/533 8 | init = function() 9 | if vim.fn.argc() == 1 then 10 | local argv0 = vim.fn.argv(0) 11 | ---@cast argv0 string 12 | local stat = vim.uv.fs_stat(argv0) 13 | if stat and stat.type == 'directory' then 14 | require('lazy').load { plugins = { 'oil.nvim' } } 15 | end 16 | end 17 | if not require('lazy.core.config').plugins['oil.nvim']._.loaded then 18 | vim.api.nvim_create_autocmd('BufNew', { 19 | callback = function() 20 | if vim.fn.isdirectory(vim.fn.expand '') == 1 then 21 | require('lazy').load { plugins = { 'oil.nvim' } } 22 | -- Once oil is loaded, we can delete this autocmd 23 | return true 24 | end 25 | end, 26 | }) 27 | end 28 | end, 29 | config = function() 30 | local oil = require 'oil' 31 | oil.setup { 32 | default_file_exporer = true, 33 | view_options = { 34 | show_hidden = true, 35 | }, 36 | float = { 37 | padding = 2, 38 | max_width = 100, 39 | max_height = 40, 40 | override = function(conf) 41 | return vim.tbl_deep_extend('force', conf, { 42 | zindex = 80, 43 | }) 44 | end, 45 | }, 46 | skip_confirm_for_simple_edits = true, 47 | keymaps = { 48 | [''] = 'actions.parent', 49 | [''] = 'actions.select', 50 | [''] = 'actions.select_vsplit', 51 | [''] = 'actions.select_split', 52 | ['v'] = 'actions.select_vsplit', 53 | ['x'] = 'actions.select_split', 54 | [''] = 'actions.refresh', 55 | [''] = { 56 | callback = function() 57 | oil.save() 58 | end, 59 | desc = 'Oil: Save', 60 | mode = { 'n', 'i', 'v' }, 61 | }, 62 | ['Q'] = { 63 | callback = function() 64 | local modified = vim.bo.modified 65 | if modified then 66 | local choice = vim.fn.confirm('Save changes?', '&Save\n&Discard\n&Cancel', 3) 67 | if choice == 1 then 68 | oil.save() 69 | elseif choice == 2 then 70 | oil.discard_all_changes() 71 | else 72 | return 73 | end 74 | end 75 | oil.close() 76 | end, 77 | desc = 'Oil: Close', 78 | mode = { 'n' }, 79 | }, 80 | }, 81 | } 82 | 83 | -- Close the window when oil is closed 84 | -- Also has the effect of quitting vim when the Oil buffer is the last one 85 | vim.api.nvim_create_autocmd('BufUnload', { 86 | pattern = 'oil://*', 87 | callback = function() 88 | if vim.api.nvim_buf_get_name(0) == '' then 89 | vim.cmd 'confirm q' 90 | end 91 | end, 92 | }) 93 | end, 94 | }, 95 | } 96 | 97 | return spec 98 | -------------------------------------------------------------------------------- /config/nvim/lua/window-picker/hints/data/slant.lua: -------------------------------------------------------------------------------- 1 | -- Font: slant 2 | return { 3 | ['a'] = [[ 4 | ____ _ 5 | / __ `/ 6 | / /_/ / 7 | \__,_/ ]], 8 | 9 | ['b'] = [[ 10 | __ 11 | / /_ 12 | / __ \ 13 | / /_/ / 14 | /_.___/ ]], 15 | 16 | ['c'] = [[ 17 | _____ 18 | / ___/ 19 | / /__ 20 | \___/ ]], 21 | 22 | ['d'] = [[ 23 | __ 24 | ____/ / 25 | / __ / 26 | / /_/ / 27 | \__,_/ ]], 28 | 29 | ['e'] = [[ 30 | ___ 31 | / _ \ 32 | / __/ 33 | \___/ ]], 34 | 35 | ['f'] = [[ 36 | ____ 37 | / __/ 38 | / /_ 39 | / __/ 40 | /_/ ]], 41 | 42 | ['g'] = [[ 43 | ____ _ 44 | / __ `/ 45 | / /_/ / 46 | \__, / 47 | /____/ ]], 48 | 49 | ['h'] = [[ 50 | __ 51 | / /_ 52 | / __ \ 53 | / / / / 54 | /_/ /_/ ]], 55 | 56 | ['i'] = [[ 57 | _ 58 | (_) 59 | / / 60 | / / 61 | /_/ ]], 62 | 63 | ['j'] = [[ 64 | _ 65 | (_) 66 | / / 67 | / / 68 | __/ / 69 | /___/ ]], 70 | 71 | ['k'] = [[ 72 | __ 73 | / /__ 74 | / //_/ 75 | / ,< 76 | /_/|_| ]], 77 | 78 | ['l'] = [[ 79 | __ 80 | / / 81 | / / 82 | / / 83 | /_/ ]], 84 | 85 | ['m'] = [[ 86 | ____ ___ 87 | / __ `__ \ 88 | / / / / / / 89 | /_/ /_/ /_/ ]], 90 | 91 | ['n'] = [[ 92 | ____ 93 | / __ \ 94 | / / / / 95 | /_/ /_/ ]], 96 | 97 | ['o'] = [[ 98 | ____ 99 | / __ \ 100 | / /_/ / 101 | \____/ ]], 102 | 103 | ['p'] = [[ 104 | ____ 105 | / __ \ 106 | / /_/ / 107 | / .___/ 108 | /_/ ]], 109 | 110 | ['q'] = [[ 111 | ____ _ 112 | / __ `/ 113 | / /_/ / 114 | \__, / 115 | /_/ ]], 116 | 117 | ['r'] = [[ 118 | _____ 119 | / ___/ 120 | / / 121 | /_/ ]], 122 | 123 | ['s'] = [[ 124 | _____ 125 | / ___/ 126 | (__ ) 127 | /____/ ]], 128 | 129 | ['t'] = [[ 130 | __ 131 | / /_ 132 | / __/ 133 | / /_ 134 | \__/ ]], 135 | 136 | ['u'] = [[ 137 | __ __ 138 | / / / / 139 | / /_/ / 140 | \__,_/ ]], 141 | 142 | ['v'] = [[ 143 | _ __ 144 | | | / / 145 | | |/ / 146 | |___/ ]], 147 | 148 | ['w'] = [[ 149 | _ __ 150 | | | /| / / 151 | | |/ |/ / 152 | |__/|__/ ]], 153 | 154 | ['x'] = [[ 155 | _ __ 156 | | |/_/ 157 | _> < 158 | /_/|_| ]], 159 | 160 | ['y'] = [[ 161 | __ __ 162 | / / / / 163 | / /_/ / 164 | \__, / 165 | /____/ ]], 166 | 167 | ['z'] = [[ 168 | ____ 169 | /_ / 170 | / /_ 171 | /___/]], 172 | 173 | ['0'] = [[ 174 | ____ 175 | / __ \ 176 | / / / / 177 | / /_/ / 178 | \____/ ]], 179 | 180 | ['1'] = [[ 181 | ___ 182 | < / 183 | / / 184 | / / 185 | /_/ ]], 186 | 187 | ['2'] = [[ 188 | ___ 189 | |__ \ 190 | __/ / 191 | / __/ 192 | /____/ ]], 193 | 194 | ['3'] = [[ 195 | _____ 196 | |__ / 197 | /_ < 198 | ___/ / 199 | /____/ ]], 200 | 201 | ['4'] = [[ 202 | __ __ 203 | / // / 204 | / // /_ 205 | /__ __/ 206 | /_/ ]], 207 | 208 | ['5'] = [[ 209 | ______ 210 | / ____/ 211 | /___ \ 212 | ____/ / 213 | /_____/ ]], 214 | 215 | ['6'] = [[ 216 | _____ 217 | / ___/ 218 | / __ \ 219 | / /_/ / 220 | \____/ ]], 221 | 222 | ['7'] = [[ 223 | _____ 224 | /__ / 225 | / / 226 | / / 227 | /_/ ]], 228 | 229 | ['8'] = [[ 230 | ____ 231 | ( __ ) 232 | / __ | 233 | / /_/ / 234 | \____/ ]], 235 | 236 | ['9'] = [[ 237 | ____ 238 | / __ \ 239 | / /_/ / 240 | \__, / 241 | /____/ ]], 242 | 243 | [';'] = [[ 244 | _ 245 | (_) 246 | _ 247 | ( ) 248 | |/ ]], 249 | } 250 | -------------------------------------------------------------------------------- /config/nushell/autoload/nushell.nu: -------------------------------------------------------------------------------- 1 | # Update xtras mod.nu file to import all xtras submodules 2 | def update-xtras-module [] { 3 | let xtras_dir = ($nu.default-config-dir | path join "autoload" "xtras") 4 | let xtras_mod = ($xtras_dir | path join "mod.nu") 5 | mkdir $xtras_dir 6 | if ($xtras_mod | path exists) { 7 | rm $xtras_mod 8 | } 9 | [ 10 | ( 11 | ls ...(glob $"($xtras_dir)/*.nu") 12 | | where type == file 13 | | get name 14 | | path relative-to ($xtras_dir | path expand) 15 | | where { $in != "mod.nu" and $in != "index.nu" } 16 | | each { |f| $"export use ($f)"} 17 | | str join "\n" 18 | ) 19 | (if ($xtras_dir | path join "index.nu" | path exists) { 20 | "export use index.nu *" 21 | }) 22 | ] | compact | str join "\n" | save -a $xtras_mod 23 | } 24 | 25 | # Save a custom command to the autoload directory 26 | # Useful to persist interactively created commands 27 | def save-command [ 28 | name: string, # Name of the custom command 29 | dest?: string # Destination file name (without extension) (default: _($name)) 30 | --append(-a) # Append to existing file without confirmation 31 | --desc(-d): string # Description of the function 32 | --xtras(-x) # Save to xtras module 33 | --export(-e) # Export the command 34 | ] { 35 | let source = view source $name 36 | let dest = ($dest | default $name) 37 | let path = ($nu.default-config-dir | path join "autoload" (if $xtras { "xtras" } else { null }) $"($dest).nu") 38 | if ($path | path exists) { 39 | match ( 40 | if ($append) { 41 | "a" 42 | } else { 43 | input $"Path ($path) exists: [a]ppend, [r]eplace, [C]ancel? " | str trim | str downcase | default -e "c" 44 | } 45 | ) { 46 | "a" => { 47 | print "Appending to ($path)" 48 | } 49 | "r" => { 50 | print "Replacing ($path)" 51 | rm --interactive $path 52 | if ($path | path exists) { 53 | error make {msg: $"Failed to remove ($path)"} 54 | } 55 | } 56 | _ => { 57 | print "Cancelled" 58 | return 59 | } 60 | } 61 | 62 | } 63 | 64 | if ($export and $xtras and $name == $dest) { 65 | error make {msg: "Command and module name cannot be the same"} 66 | } 67 | 68 | print $"Saving ($name) to ($path)" 69 | ([ 70 | (if ($path | path exists) { open $path }) 71 | (if ($desc | is-not-empty) { $desc | split row "\n" | each {|l| $"# ($l)"} | str join "\n" }) 72 | (if $export { $"export ($source)" } else { $source }) 73 | ] 74 | | compact 75 | | str join "\n" 76 | | str replace -ram '\s+$' "\n" 77 | | str trim 78 | | save -f $path 79 | ) 80 | 81 | if $xtras { 82 | update-xtras-module 83 | } 84 | } 85 | 86 | # Like save-command, but saves to xtras module 87 | # If submod is not specified or is "index", saves to xtras/index.nu, 88 | # which will be used like `use xtras/index.nu *`, so that all exports 89 | # are available directly in the `xtras` module scope. 90 | def save-xtra [ 91 | name: string # Name of the custom command 92 | submod?: string = "index" # Submodule name (default: index) 93 | --append(-a) = true # Append to existing file without confirmation (default: true) 94 | --desc(-d): string # Description of the function 95 | --export(-e) = true # Export the command (default: true) 96 | ] { 97 | save-command --desc=$desc --xtras --export=$export --append=$append $name $submod 98 | } 99 | -------------------------------------------------------------------------------- /config/satty/config.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | # Start Satty in fullscreen mode 3 | fullscreen = false 4 | # Exit directly after copy/save action 5 | early-exit = true 6 | # Draw corners of rectangles round if the value is greater than 0 (0 disables rounded corners) 7 | corner-roundness = 0 8 | # Select the tool on startup [possible values: pointer, crop, line, arrow, rectangle, text, marker, blur, brush] 9 | initial-tool = "rectangle" 10 | # Configure the command to be called on copy, for example `wl-copy` 11 | copy-command = "wl-copy" 12 | # Increase or decrease the size of the annotations 13 | annotation-size-factor = 2 14 | # Filename to use for saving action. Omit to disable saving to file. Might contain format specifiers: https://docs.rs/chrono/latest/chrono/format/strftime/index.html 15 | # starting with 0.20.0, can contain leading tilde (~) for home directory 16 | output-filename = "~/Documents/screenshots/%Y-%m-%d_%H-%M-%S_annotated.png" 17 | # After copying the screenshot, save it to a file as well 18 | save-after-copy = false 19 | # Hide toolbars by default 20 | default-hide-toolbars = false 21 | # Experimental (since 0.20.0): whether window focus shows/hides toolbars. This does not affect initial state of toolbars, see default-hide-toolbars. 22 | focus-toggles-toolbars = false 23 | # Fill shapes by default (since 0.20.0) 24 | default-fill-shapes = true 25 | # The primary highlighter to use, the other is accessible by holding CTRL at the start of a highlight [possible values: block, freehand] 26 | primary-highlighter = "block" 27 | # Disable notifications 28 | disable-notifications = false 29 | # Actions to trigger on right click (order is important) 30 | # [possible values: save-to-clipboard, save-to-file, exit] 31 | actions-on-right-click = [] 32 | # Actions to trigger on Enter key (order is important) 33 | # [possible values: save-to-clipboard, save-to-file, exit] 34 | actions-on-enter = ["save-to-clipboard"] 35 | # Actions to trigger on Escape key (order is important) 36 | # [possible values: save-to-clipboard, save-to-file, exit] 37 | actions-on-escape = [] 38 | # request no window decoration. Please note that the compositor has the final say in this. At this point. requires xdg-decoration-unstable-v1. 39 | no-window-decoration = true 40 | # experimental feature: adjust history size for brush input smooting (0: disabled, default: 0, try e.g. 5 or 10) 41 | brush-smooth-history-size = 10 42 | # experimental feature (NEXTRELEASE): The pan step size to use when panning with arrow keys. 43 | # pan-step-size = 50.0 44 | # experimental feature (NEXTRELEASE): The zoom factor to use for the image. 45 | # 1.0 means no zooming. 46 | # zoom-factor = 1.1 47 | 48 | # Tool selection keyboard shortcuts (since 0.20.0) 49 | [keybinds] 50 | pointer = "p" 51 | crop = "c" 52 | brush = "b" 53 | line = "i" 54 | arrow = "z" 55 | rectangle = "r" 56 | ellipse = "e" 57 | text = "t" 58 | marker = "m" 59 | blur = "u" 60 | highlight = "g" 61 | 62 | # Font to use for text annotations 63 | [font] 64 | family = "Pragmasevka Nerd Font" 65 | style = "Regular" 66 | 67 | # Custom colours for the colour palette 68 | [color-palette] 69 | # These will be shown in the toolbar for quick selection 70 | palette = [ 71 | "#000000", 72 | "#ffffff", 73 | "#00ffff", 74 | "#dc143c", 75 | "#ff1493", 76 | "#ffd700", 77 | ] 78 | 79 | # These will be available in the color picker as presets 80 | # Leave empty to use GTK's default 81 | custom = [ 82 | "#00ffff", 83 | "#a52a2a", 84 | "#dc143c", 85 | "#ff1493", 86 | "#ffd700", 87 | "#008000", 88 | ] 89 | -------------------------------------------------------------------------------- /config/niri/uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 3 3 | requires-python = ">=3.13" 4 | 5 | [[package]] 6 | name = "niri-tools" 7 | version = "0.1.0" 8 | source = { editable = "." } 9 | dependencies = [ 10 | { name = "pyyaml" }, 11 | ] 12 | 13 | [package.metadata] 14 | requires-dist = [{ name = "pyyaml", specifier = ">=6.0.2" }] 15 | 16 | [[package]] 17 | name = "pyyaml" 18 | version = "6.0.2" 19 | source = { registry = "https://pypi.org/simple" } 20 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } 21 | wheels = [ 22 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, 23 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, 24 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, 25 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, 26 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, 27 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, 28 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, 29 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, 30 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, 31 | ] 32 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/workspace/init.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local Path = require 'user.util.path' 4 | local util = require 'user.util.workspace.util' 5 | 6 | local cargo = require 'user.util.workspace.cargo' 7 | local pnpm = require 'user.util.workspace.pnpm' 8 | 9 | ---@alias user.util.workspace.WorkspaceType "pnpm"|"cargo" 10 | 11 | ---@alias user.util.workspace.WorkspaceInfo user.util.workspace.pnpm.WorkspaceInfo|user.util.workspace.cargo.WorkspaceInfo 12 | 13 | ---@class user.util.workspace.GetWorkspaceInfoOpts 14 | ---@field workspace_type? user.util.workspace.WorkspaceType 15 | ---@field focused_path? string|Path @the path to use as the focused package 16 | ---@field refresh? boolean @whether to refresh the cache 17 | ---@field only_cached? boolean @whether to only use cached data 18 | 19 | ---@class user.util.workspace.GetWorkspacePackagePathsOpts 20 | ---@field workspace_type? user.util.workspace.WorkspaceType 21 | ---@field only_cached? boolean @whether to only use cached data 22 | ---@field callback? fun(paths: Path[]|nil) @the callback to call when the paths are found (get_workspace_package_paths will run asynchronously) 23 | 24 | ---@param root_dir? Path 25 | ---@return user.util.workspace.WorkspaceType? 26 | M.get_workspace_type = function(root_dir) 27 | root_dir = root_dir or util.cwd() 28 | local cargo_root = cargo.get_root_path(root_dir) 29 | if cargo_root then 30 | return 'cargo' 31 | end 32 | local pnpm_root = pnpm.get_root_path(root_dir) 33 | if pnpm_root then 34 | return 'pnpm' 35 | end 36 | return nil 37 | end 38 | 39 | ---@param opts? user.util.workspace.GetWorkspaceInfoOpts 40 | ---@return user.util.workspace.WorkspaceInfo|nil|false 41 | M.get_workspace_info = function(opts) 42 | opts = opts or {} 43 | local workspace_type = opts.workspace_type 44 | or M.get_workspace_type(opts.focused_path and Path:new(opts.focused_path) or nil) 45 | if not workspace_type then 46 | return 47 | end 48 | local workspace_info = function(info) 49 | return vim.tbl_extend('force', { 50 | type = workspace_type, 51 | }, info) 52 | end 53 | if workspace_type == 'pnpm' then 54 | return workspace_info(pnpm.get_workspace_info(opts)) 55 | end 56 | if workspace_type == 'cargo' then 57 | return workspace_info(cargo.get_workspace_info(opts)) 58 | end 59 | end 60 | 61 | ---@param start_path? Path 62 | ---@param opts? {only_cached?: boolean, workspace_type?: user.util.workspace.WorkspaceType} 63 | ---@return Path|nil|false @the root path, nil if not cached and only_cached is true, false if not found 64 | M.get_root_path = function(start_path, opts) 65 | opts = opts or {} 66 | local workspace_type = opts.workspace_type or M.get_workspace_type(start_path) 67 | if workspace_type == 'pnpm' then 68 | return pnpm.get_root_path(start_path, opts) 69 | end 70 | if workspace_type == 'cargo' then 71 | return cargo.get_root_path(start_path, opts) 72 | end 73 | end 74 | 75 | ---@param root_dir? Path 76 | ---@param opts? user.util.workspace.GetWorkspacePackagePathsOpts 77 | ---@return Path[]|nil 78 | M.get_workspace_package_paths = function(root_dir, opts) 79 | opts = opts or {} 80 | root_dir = root_dir or util.cwd() 81 | local workspace_type = opts.workspace_type or M.get_workspace_type(root_dir) 82 | if not workspace_type then 83 | return 84 | end 85 | if workspace_type == 'pnpm' then 86 | return pnpm.get_workspace_package_paths(root_dir, opts) 87 | end 88 | if workspace_type == 'cargo' then 89 | return cargo.get_workspace_package_paths(root_dir, opts) 90 | end 91 | end 92 | 93 | return M 94 | -------------------------------------------------------------------------------- /config/nvim/lua/user/zen-mode.lua: -------------------------------------------------------------------------------- 1 | local zen_view = require 'zen-mode.view' 2 | 3 | local M = {} 4 | 5 | local last_buf 6 | local augroup 7 | 8 | function M.unset_keymaps(buf) 9 | if not (buf and vim.api.nvim_buf_is_valid(buf)) then 10 | return 11 | end 12 | for _, dir in ipairs { 'h', 'j', 'k', 'l' } do 13 | pcall(vim.keymap.del, 'n', '', { buffer = buf }) 14 | end 15 | pcall(vim.keymap.del, 'n', '', { buffer = buf }) 16 | end 17 | 18 | function M.setup_keymaps(buf) 19 | if not (buf and vim.api.nvim_buf_is_valid(buf)) then 20 | return 21 | end 22 | for _, dir in ipairs { 'h', 'j', 'k', 'l' } do 23 | vim.keymap.set('n', '', function() 24 | M.move(dir) 25 | end, { buffer = buf }) 26 | end 27 | vim.keymap.set('n', '', function() 28 | M.move 'a' 29 | end, { buffer = buf }) 30 | end 31 | 32 | ---@param dir 'h' | 'j' | 'k' | 'l' | 'a' | { win: number } | { buf: number } 33 | function M.move(dir) 34 | if not zen_view.is_open() then 35 | vim.notify_once('Zen: Move: Not in Zen Mode', vim.log.levels.WARN) 36 | return 37 | end 38 | if not zen_view.parent or not vim.api.nvim_win_is_valid(zen_view.parent) then 39 | return 40 | end 41 | 42 | local target_win 43 | if type(dir) == 'table' then 44 | if dir.buf then 45 | vim.api.nvim_win_set_buf(zen_view.parent, dir.buf) 46 | target_win = zen_view.parent 47 | else 48 | target_win = dir.win 49 | end 50 | elseif dir == 'a' then 51 | target_win = require('user.util.recent-wins').get_most_recent_any(zen_view.parent) 52 | else 53 | target_win = vim.api.nvim_win_call(zen_view.parent, function() 54 | return vim.fn.win_getid(vim.fn.winnr(dir)) 55 | end) 56 | end 57 | if not target_win or not vim.api.nvim_win_is_valid(target_win) then 58 | return 59 | end 60 | local target_buf = vim.api.nvim_win_get_buf(target_win) 61 | if not target_buf or not vim.api.nvim_buf_is_valid(target_buf) or not vim.bo[target_buf].buflisted then 62 | return 63 | end 64 | 65 | -- update the cursor position in the parent window, so it's saved for if we return to it 66 | if zen_view.parent and vim.api.nvim_win_is_valid(zen_view.parent) then 67 | vim.api.nvim_win_set_cursor(zen_view.parent, vim.api.nvim_win_get_cursor(zen_view.win)) 68 | end 69 | 70 | -- switch to the target buffer 71 | vim.api.nvim_win_set_buf(zen_view.win, target_buf) 72 | 73 | -- sync the cursor position from the parent window to the floating window 74 | vim.api.nvim_win_set_cursor(zen_view.win, vim.api.nvim_win_get_cursor(target_win)) 75 | 76 | if last_buf and vim.api.nvim_buf_is_valid(last_buf) then 77 | M.unset_keymaps(last_buf) 78 | end 79 | 80 | -- set the previous parent window as the most recent 81 | require('user.util.recent-wins').update(target_win) 82 | 83 | last_buf = target_buf 84 | zen_view.parent = target_win 85 | M.setup_keymaps(target_buf) 86 | end 87 | 88 | function M.on_open() 89 | augroup = vim.api.nvim_create_augroup('user.zen-mode', { clear = true }) 90 | 91 | vim.api.nvim_create_autocmd('WinClosed', { 92 | group = augroup, 93 | pattern = tostring(zen_view.win), 94 | once = true, 95 | callback = function() 96 | local target = zen_view.parent 97 | vim.defer_fn(function() 98 | if target and vim.api.nvim_win_is_valid(target) then 99 | vim.api.nvim_set_current_win(target) 100 | end 101 | end, 0) 102 | M.unset_keymaps(vim.api.nvim_get_current_buf()) 103 | end, 104 | }) 105 | 106 | M.setup_keymaps(vim.api.nvim_get_current_buf()) 107 | end 108 | 109 | return M 110 | -------------------------------------------------------------------------------- /config/niri/CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | This is a personal Niri window manager configuration directory. Niri is a scrollable-tiling Wayland compositor. The configuration includes: 8 | 9 | - Main configuration file (`config.kdl`) written in KDL format 10 | - Custom Python scripts for automation and monitoring 11 | - Dual monitor setup with specific display configurations 12 | 13 | ## Development Commands 14 | 15 | ### Python Scripts 16 | - **Run Python scripts**: Use `uv run python ` (dependencies managed via pyproject.toml) 17 | - **Test configuration**: `niri validate` (validates KDL configuration syntax) 18 | - **Configuration reloading**: Niri automatically reloads config.kdl on file changes 19 | - **Check python code with ruff and basedpyright**: Lint and type-check Python scripts using these tools 20 | 21 | ### Niri Commands 22 | - **Check running instance**: `niri msg version` 23 | - **List windows**: `niri msg -j windows` (JSON output for scripting) 24 | - **Monitor events**: `niri msg -j event-stream` (used by stream_monitor.py) 25 | - **Get workspaces**: `niri msg -j workspaces` 26 | - **Validate config**: `niri validate` 27 | 28 | ## Architecture 29 | 30 | ### Configuration Structure 31 | - `config.kdl`: Main Niri configuration file with window management, keybindings, and layout settings 32 | - `scratchpads.yaml`: Centralized configuration for all scratchpad windows 33 | - `niri_tools/stream_monitor.py`: Event monitoring daemon that handles window urgency notifications 34 | - `niri_tools/scratchpad.py`: Scratchpad management system for floating windows 35 | 36 | ### Key Components 37 | 38 | #### Configuration (`config.kdl`) 39 | - Dual monitor setup (DP-1 and DP-2, both 4K@60Hz) 40 | - Custom keybindings using Mod (Super) key combinations 41 | - Window rules for specific applications (Firefox PiP, password managers) 42 | - Layout configuration with focus rings, borders, and shadows 43 | - Integration with external tools (waybar, swaybg, brightness control) 44 | 45 | #### Event Monitoring (`stream_monitor.py`) 46 | - Extensible event handling system using abstract base classes 47 | - WindowUrgencyHandler: Manages persistent notifications for urgent windows 48 | - Uses `niri msg event-stream` for real-time compositor events 49 | - Notification management via notify-send with proper cleanup 50 | 51 | #### Scratchpad System (`scratchpad.py` and `scratchpads.yaml`) 52 | - YAML-based configuration for all scratchpad windows 53 | - Each scratchpad defines: app_id/title_pattern, command, size, and per-monitor positions 54 | - Commands: `toggle ` (show/hide scratchpad), `hide` (hide focused), `list` (show all configured) 55 | - Automatic window detection and floating window management 56 | - State persistence across restarts 57 | 58 | ### Script Dependencies 59 | Python scripts require: 60 | - Python 3.13+ 61 | - uv package manager for environment management 62 | - Dependencies installed via `uv pip install -e .` (see pyproject.toml) 63 | - Access to `niri msg` command 64 | - notify-send for notifications (stream_monitor.py) 65 | - PyYAML for configuration parsing (scratchpad.py) 66 | 67 | ## Configuration Notes 68 | 69 | - KDL format is used for configuration (not TOML or JSON) 70 | - Scripts are automatically started via `spawn-at-startup` in config.kdl 71 | - Window management uses column-based tiling with scrolling workspaces 72 | - Custom color themes with purple/blue gradients 73 | - Integration with system audio controls and brightness utilities 74 | 75 | ## Documentation Resources 76 | 77 | - Refer to the niri wiki for documentation: https://github.com/YaLTeR/niri/tree/main/wiki -------------------------------------------------------------------------------- /config/nvim/lua/treesj/langs/nu.lua: -------------------------------------------------------------------------------- 1 | -- TODO: Remove once https://github.com/Wansmer/treesj/pull/187 is merged 2 | local lang_utils = require('treesj.langs.utils') 3 | 4 | local DEFAULT_PRESET = require('treesj.langs.default_preset') 5 | 6 | local M = {} 7 | 8 | ---Return function for updating preset (default preset + override) 9 | ---@param preset? table Preset (default preset by default) 10 | ---@return fun(override: table): table 11 | local function set_preset(preset) 12 | preset = preset or {} 13 | preset = lang_utils.merge_preset(DEFAULT_PRESET, preset) 14 | return function(override) 15 | override = override or {} 16 | return lang_utils.merge_preset(preset, override) 17 | end 18 | end 19 | 20 | M.parameter_bracks = lang_utils.set_default_preset({ 21 | split = { 22 | format_resulted_lines = function(lines) 23 | return vim.iter(lines):map(function(line) 24 | line = line:gsub(",$", "") 25 | return line 26 | end):totable() 27 | end, 28 | }, 29 | join = { 30 | space_separator = true, 31 | space_in_brackets = true, 32 | force_insert = ',', 33 | no_insert_if = { lang_utils.helpers.if_penultimate }, 34 | }, 35 | }) 36 | 37 | local set_preset_for_block = set_preset({ 38 | both = { 39 | omit = { "parameter_pipes" }, 40 | }, 41 | join = { 42 | space_separator = true, 43 | space_in_brackets = true, 44 | force_insert = ';', 45 | no_insert_if = { 46 | "parameter_pipes", 47 | lang_utils.helpers.if_penultimate, 48 | }, 49 | }, 50 | }) 51 | 52 | M.expr_parenthesized = set_preset_for_block({}) 53 | M.block = set_preset_for_block({ 54 | split = { 55 | format_tree = function(tsj) 56 | tsj:remove_child(";") 57 | end, 58 | }, 59 | }) 60 | M.val_closure = set_preset_for_block({ 61 | split = { 62 | format_tree = function(tsj) 63 | tsj:remove_child(";") 64 | end, 65 | }, 66 | }) 67 | 68 | local set_preset_for_list = set_preset({ 69 | both = { 70 | non_bracket_node = true, 71 | }, 72 | split = { 73 | last_separator = true, 74 | }, 75 | join = { 76 | last_separator = false, 77 | space_in_brackets = false, 78 | force_insert = ',', 79 | no_insert_if = { lang_utils.helpers.if_penultimate }, 80 | } 81 | }) 82 | 83 | M.val_list = { target_nodes = { "list_body" } } 84 | M.list_body = set_preset_for_list({}) 85 | 86 | M.val_record = { target_nodes = { "record_body" } } 87 | M.record_body = set_preset_for_list({}) 88 | 89 | M.val_table = set_preset_for_list({ 90 | both = { 91 | non_bracket_node = false, 92 | omit = { ";" }, 93 | }, 94 | join = { 95 | no_insert_if = { 96 | lang_utils.helpers.if_second, 97 | ";", 98 | lang_utils.helpers.if_penultimate, 99 | }, 100 | }, 101 | }) 102 | 103 | M.ctrl_match = set_preset_for_list({ 104 | both = { 105 | non_bracket_node = true, 106 | shrink_node = { from = '{', to = '}' }, 107 | }, 108 | join = { 109 | space_in_brackets = true, 110 | } 111 | }) 112 | 113 | M.pipeline = lang_utils.set_default_preset({ 114 | join = { 115 | space_separator = false, 116 | -- 117 | format_tree = function(tsj) 118 | for _, child in ipairs(tsj:children({ "|" })) do 119 | child:update_text(" | ") 120 | end 121 | end, 122 | }, 123 | split = { 124 | inner_indent = "normal", 125 | last_indent = "normal", 126 | ---@param tsj TreeSJ 127 | format_tree = function(tsj) 128 | tsj:remove_child("|") 129 | for idx, child in ipairs(tsj:children()) do 130 | if idx ~= 1 then 131 | child:update_text("| " .. child:text()) 132 | end 133 | end 134 | end, 135 | }, 136 | }) 137 | 138 | return M 139 | -------------------------------------------------------------------------------- /config/nushell/autoload/js.nu: -------------------------------------------------------------------------------- 1 | # find the root of the pnpm workspace 2 | def pnpm-workspace-root [] { 3 | mut current_dir = $env.PWD 4 | 5 | while $current_dir != "/" { 6 | let workspace_file = ($current_dir | path join "pnpm-workspace.yaml") 7 | if ($workspace_file | path exists) { 8 | return $current_dir 9 | } 10 | $current_dir = ($current_dir | path dirname) 11 | } 12 | 13 | error make {msg: "pnpm-workspace.yaml not found."} 14 | } 15 | 16 | # usage: pnpm-pick-workspace [-pn] [query] 17 | # pick and print path to pnpm workspace 18 | # -p: print path (default) 19 | # -n: print name 20 | # -R: hide root 21 | def pnpm-pick-workspace [ 22 | --path (-p) # print path (default) 23 | --name (-n) # print name 24 | --hide-root (-R) # hide root 25 | query?: string # search query 26 | ] { 27 | let mode = if $name { "name" } else { "path" } 28 | let root = (pnpm-workspace-root) 29 | let query_str = ($query | default "") 30 | 31 | # Build jq command - using string concatenation to avoid escaping issues 32 | let jq_cmd = ('jq --arg pwd "$(realpath --relative-to="' + $root + '" "$PWD")" -r "\"\(if \$pwd == \".\" then \"./\" else \"./\" + \$pwd end):\(.name)\"" package.json') 33 | 34 | # Collect workspace info 35 | let workspace_list = ( 36 | if not $hide_root { 37 | do --ignore-errors { ^sh -c $"cd ($root) && ($jq_cmd)" } 38 | } else { 39 | "" 40 | } | lines | append ( 41 | ^pnpm -rc --parallel exec $jq_cmd | lines 42 | ) | where { |line| not ($line | is-empty) } 43 | | str join "\n" 44 | ) 45 | 46 | # Format with column and select with fzf 47 | let sel = ( 48 | echo $workspace_list 49 | | ^column --table --separator=: 50 | | ^fzf -1 --query $query_str --height 10 --preview $"echo 'path: ' {1} && echo 'pkg: ' {2} && eza -la --color=always ($root)/{1}" 51 | | complete 52 | ) 53 | 54 | if $sel.exit_code != 0 or ($sel.stdout | is-empty) { 55 | return 56 | } 57 | 58 | let selected = ($sel.stdout | str trim) 59 | 60 | let res = match $mode { 61 | "path" => { 62 | let path_part = ($selected | split row " " | first) 63 | $"($root)/($path_part)" | path expand 64 | } 65 | "name" => { 66 | $selected | split row " " | get 1 67 | } 68 | _ => { $selected } 69 | } 70 | 71 | return $res 72 | } 73 | 74 | # pnpm 75 | alias pp = pnpm 76 | alias ppi = pnpm install 77 | alias ppr = pnpm run 78 | alias pprf = pnpm run --filter 79 | alias pprff = pnpm run --filter (pnpm-pick-workspace -n -R) 80 | alias pprw = pnpm -w run 81 | alias ppx = pnpm exec 82 | alias ppxf = pnpm --filter (pnpm-pick-workspace -n -R) exec 83 | alias ppxw = pnpm -w exec 84 | alias ppa = pnpm add 85 | alias ppad = pnpm add -D 86 | alias ppaf = pnpm add --filter 87 | alias ppaff = pnpm add --filter (pnpm-pick-workspace -n -R) 88 | alias ppadf = pnpm add -D --filter 89 | alias ppadff = pnpm add -D --filter (pnpm-pick-workspace -n -R) 90 | alias pprm = pnpm remove 91 | alias pprmf = pnpm remove --filter 92 | alias pprmff = pnpm remove --filter (pnpm-pick-workspace -n -R) 93 | alias pwr = cd (pnpm-workspace-root) 94 | alias ppw = cd (pnpm-pick-workspace) 95 | 96 | # bun 97 | # TODO: remove once https://github.com/oven-sh/bun/issues/25391 is fixed 98 | @complete external 99 | def --wrapped bun [...args] { 100 | ^bun --backend=symlink ...$args 101 | } 102 | 103 | alias b = bun 104 | alias br = bun run 105 | alias bi = bun install 106 | alias bb = bun run build 107 | alias brb = bun run build 108 | alias brb = bun run build 109 | alias bu = bun update 110 | alias bx = bun x 111 | alias ba = bun add 112 | alias bad = bun add --dev 113 | alias bt = bun test 114 | alias brt = bun run test 115 | alias brm = bun remove 116 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/wrap.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local emmet_wrap = function(abbreviation) 4 | local bufnr = vim.api.nvim_get_current_buf() 5 | local emmet = vim.lsp.get_clients({ 6 | bufnr = bufnr, 7 | name = 'emmet_language_server', 8 | })[1] 9 | if not emmet then 10 | return 11 | end 12 | local response = emmet:request_sync('emmet/expandAbbreviation', { 13 | abbreviation = abbreviation, 14 | language = vim.bo.filetype, 15 | options = { 16 | text = '${WRAP_TEXT}', 17 | }, 18 | }, 1000, bufnr) 19 | if not response or not response.result then 20 | return 21 | end 22 | local split = vim.split(response.result, '${WRAP_TEXT}') 23 | if split[1] and split[2] then 24 | return split[1], split[2] 25 | end 26 | end 27 | 28 | -- Selection Wrapping 29 | -- TODO: Support function calls, e.g. "foo(" -> ")" 30 | -- 31 | ---@param lhs_in string 32 | local function get_wrap_seq(lhs_in) 33 | local pairings = { 34 | ['('] = ')', 35 | ['{'] = '}', 36 | ['['] = ']', 37 | ['<'] = '>', 38 | ['"'] = '"', 39 | ["'"] = "'", 40 | ['`'] = '`', 41 | ['|'] = '|', 42 | ['/'] = '/', 43 | ['_'] = '_', 44 | [' '] = ' ', 45 | ['*'] = '*', 46 | } 47 | local first = lhs_in:sub(1, 1) 48 | if first == '>' or not pairings[first] then 49 | local lhs, rhs = emmet_wrap(first == '>' and lhs_in:sub(2) or lhs_in) 50 | if lhs and rhs then 51 | return lhs, rhs 52 | end 53 | end 54 | local lhs = '' 55 | local rhs = '' 56 | local valid = true 57 | local i = 1 58 | while i <= #lhs_in do 59 | local tag, tag_name = lhs_in:sub(i):match '^(<([^/> ]*)[^>]*>)' 60 | if tag then 61 | lhs = lhs .. tag 62 | rhs = '' .. rhs 63 | i = i + #tag 64 | else 65 | local c = lhs_in:sub(i, i) 66 | if not pairings[c] then 67 | valid = false 68 | break 69 | end 70 | lhs = lhs .. c 71 | rhs = pairings[c] .. rhs 72 | end 73 | i = i + 1 74 | end 75 | if valid then 76 | return lhs, rhs 77 | end 78 | return lhs_in, lhs_in 79 | end 80 | 81 | ---@param params? { lhs?: string, rhs?: string } 82 | ---@return { lhs: string, rhs: string }|nil 83 | M.wrap_visual_selection = function(params) 84 | params = params or {} 85 | local lhs = params.lhs or vim.fn.input { prompt = 'LHS: ', cancelreturn = -1 } 86 | if lhs == -1 then 87 | return 88 | end 89 | local rhs 90 | if params.rhs then 91 | rhs = params.rhs 92 | else 93 | lhs, rhs = get_wrap_seq(lhs) 94 | rhs = vim.fn.input { prompt = 'RHS: ', default = rhs, cancelreturn = -1 } 95 | end 96 | if rhs == -1 then 97 | return 98 | end 99 | require('user.util.visual').transform_visual_selection(function(text, meta) 100 | local mode = meta.selection.mode 101 | 102 | if mode == '' or mode == '' then 103 | -- TODO: implement block-wise 104 | return text 105 | end 106 | 107 | if mode == 'v' then 108 | return lhs .. text .. rhs 109 | end 110 | 111 | if mode == 'V' then 112 | local indent = require('user.fn').get_indent_info() 113 | local lines = vim.split(text, '\n') 114 | local current_indent = '' 115 | if #lines > 0 then 116 | current_indent = lines[1]:match('^' .. indent.char .. '*') 117 | end 118 | local new_lines = {} 119 | table.insert(new_lines, current_indent .. lhs) 120 | for _, line in ipairs(lines) do 121 | table.insert(new_lines, indent.char:rep(indent.size) .. line) 122 | end 123 | table.insert(new_lines, current_indent .. rhs) 124 | return new_lines 125 | end 126 | end) 127 | return { 128 | lhs = lhs, 129 | rhs = rhs, 130 | } 131 | end 132 | 133 | return M 134 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/fyler.lua: -------------------------------------------------------------------------------- 1 | very_lazy(function() 2 | local fn = require 'user.fn' 3 | local maputil = require 'user.util.map' 4 | local recent_wins = lazy_require 'user.util.recent-wins' 5 | local fyler = lazy_require 'fyler' 6 | local xk = require('user.keys').xk 7 | 8 | local map = maputil.map 9 | local ft = maputil.ft 10 | 11 | map('n', xk '', function() 12 | local cur = require('fyler.views.finder')._current 13 | if cur and cur.win:is_visible() then 14 | fyler.close() 15 | else 16 | fyler.open() 17 | vim.schedule(function() recent_wins.focus_most_recent() end) 18 | end 19 | end, 'Fyler: Toggle') 20 | 21 | map( 22 | 'n', 23 | xk [[]], 24 | fn.if_filetype({ 'fyler', 'DiffviewFiles' }, recent_wins.focus_most_recent, function() 25 | local wins = vim.api.nvim_tabpage_list_wins(0) 26 | local tree_win, diffview_win 27 | for _, win in ipairs(wins) do 28 | local bufnr = vim.api.nvim_win_get_buf(win) 29 | local filetype = vim.bo[bufnr].filetype 30 | if filetype == 'fyler' then 31 | tree_win = win 32 | elseif filetype == 'DiffviewFiles' then 33 | diffview_win = win 34 | end 35 | end 36 | -- prefer diffview 37 | if diffview_win then 38 | vim.api.nvim_set_current_win(diffview_win) 39 | elseif tree_win then 40 | vim.api.nvim_set_current_win(tree_win) 41 | else 42 | fyler.open() 43 | end 44 | end), 45 | 'Fyler: Toggle Focus' 46 | ) 47 | 48 | ft('fyler', function(bufmap) 49 | local parser = require 'fyler.views.finder.parser' 50 | local finder = require('fyler.views.finder')._current 51 | if not finder then 52 | return 53 | end 54 | 55 | local get_selected = function() 56 | local ref_id = parser.parse_ref_id(vim.api.nvim_get_current_line()) 57 | if not ref_id then 58 | return 59 | end 60 | local entry = finder.files:node_entry(ref_id) 61 | if not entry then 62 | return 63 | end 64 | return entry, ref_id 65 | end 66 | 67 | local function toggle_dir(entry, ref_id) 68 | if entry.open then 69 | finder.files:collapse_node(ref_id) 70 | else 71 | finder.files:expand_node(ref_id) 72 | end 73 | finder:dispatch_refresh() 74 | end 75 | 76 | bufmap('n', '', function() 77 | local entry, ref_id = get_selected() 78 | if not entry or not entry:is_directory() then 79 | return 80 | end 81 | toggle_dir(entry, ref_id) 82 | end, 'Fyler: Toggle expanded') 83 | end) 84 | end) 85 | 86 | ---@type LazySpec[] 87 | return { 88 | { 89 | 'A7Lavinraj/fyler.nvim', 90 | -- dev = true, 91 | cmd = 'Fyler', 92 | opts = { 93 | integrations = { 94 | icon = 'nvim_web_devicons', 95 | winpick = 'nvim-window-picker', 96 | }, 97 | views = { 98 | finder = { 99 | close_on_select = false, 100 | win = { 101 | kind = 'split_left_most', 102 | kinds = { split_left_most = { width = '30' } }, 103 | win_opts = { 104 | cursorline = true, 105 | }, 106 | }, 107 | mappings = { 108 | [''] = 'Select', 109 | ['q'] = 'CloseView', 110 | [''] = 'SelectTab', 111 | [''] = 'SelectVSplit', 112 | [''] = 'SelectSplit', 113 | ['^'] = 'GotoParent', 114 | ['='] = 'GotoCwd', 115 | ['.'] = 'GotoNode', 116 | ['#'] = 'CollapseAll', 117 | }, 118 | watcher = { 119 | enabled = true, 120 | }, 121 | }, 122 | }, 123 | }, 124 | }, 125 | } 126 | -------------------------------------------------------------------------------- /nix/hosts/boonix/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: let 6 | username = "boo"; 7 | uid = 1000; 8 | 9 | zfsLib = import ../../lib/zfs.nix {inherit lib;}; 10 | inherit (zfsLib) mkDataset snapshotDataset; 11 | in { 12 | imports = [ 13 | ./hardware-configuration.nix 14 | ../../modules/base.nix 15 | ]; 16 | 17 | custom = { 18 | disk.id = "/dev/disk/by-id/nvme-Samsung_SSD_990_PRO_4TB_S7KGNU0X522072A"; 19 | 20 | secrets.filesToCreate = [ 21 | "${config.custom.secrets.directory}/${username}.password" 22 | ]; 23 | 24 | zfs.extraDatasets = { 25 | "home/${username}" = mkDataset { 26 | mountpoint = "/home/${username}"; 27 | options = snapshotDataset { 28 | frequent = 4; 29 | hourly = 24; 30 | daily = 7; 31 | weekly = 4; 32 | monthly = 6; 33 | }; 34 | owner = uid; 35 | group = 100; 36 | mode = "700"; 37 | }; 38 | 39 | "home/${username}/downloads" = mkDataset { 40 | mountpoint = "/home/${username}/downloads"; 41 | options = snapshotDataset { 42 | frequent = 4; 43 | hourly = 24; 44 | daily = 7; 45 | weekly = 2; 46 | monthly = 0; 47 | }; 48 | owner = uid; 49 | group = 100; 50 | mode = "700"; 51 | }; 52 | 53 | "home/${username}/.cache" = mkDataset { 54 | mountpoint = "/home/${username}/.cache"; 55 | options = snapshotDataset { 56 | frequent = 4; 57 | hourly = 24; 58 | daily = 2; 59 | weekly = 0; 60 | monthly = 0; 61 | }; 62 | owner = uid; 63 | group = 100; 64 | mode = "700"; 65 | }; 66 | 67 | "home/${username}/.local" = mkDataset { 68 | mountpoint = "/home/${username}/.local"; 69 | options = snapshotDataset { 70 | frequent = 4; 71 | hourly = 24; 72 | daily = 7; 73 | weekly = 2; 74 | monthly = 0; 75 | }; 76 | owner = uid; 77 | group = 100; 78 | mode = "700"; 79 | }; 80 | 81 | "home/${username}/git" = mkDataset { 82 | mountpoint = "/home/${username}/git"; 83 | options = snapshotDataset { 84 | frequent = 4; 85 | hourly = 24; 86 | daily = 7; 87 | weekly = 4; 88 | monthly = 3; 89 | }; 90 | owner = uid; 91 | group = 100; 92 | mode = "700"; 93 | }; 94 | 95 | "home/${username}/proj" = mkDataset { 96 | mountpoint = "/home/${username}/proj"; 97 | options = snapshotDataset { 98 | frequent = 4; 99 | hourly = 24; 100 | daily = 7; 101 | weekly = 4; 102 | monthly = 12; 103 | }; 104 | owner = uid; 105 | group = 100; 106 | mode = "700"; 107 | }; 108 | 109 | "home/${username}/work" = mkDataset { 110 | mountpoint = "/home/${username}/work"; 111 | options = snapshotDataset { 112 | frequent = 4; 113 | hourly = 24; 114 | daily = 7; 115 | weekly = 4; 116 | monthly = 24; 117 | }; 118 | owner = uid; 119 | group = 100; 120 | mode = "700"; 121 | }; 122 | }; 123 | }; 124 | 125 | networking.hostName = "boonix"; 126 | networking.hostId = "aab75ae6"; 127 | 128 | hardware.nvidia = { 129 | modesetting.enable = true; 130 | open = true; 131 | }; 132 | services.xserver.videoDrivers = ["nvidia"]; 133 | 134 | users.users.${username} = { 135 | inherit uid; 136 | isNormalUser = true; 137 | home = "/home/${username}"; 138 | extraGroups = ["wheel" "networkmanager" "video" "audio"]; 139 | hashedPasswordFile = "${config.custom.secrets.directory}/${username}.password"; 140 | }; 141 | } 142 | -------------------------------------------------------------------------------- /config/vivid/themes/lavi.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/lavi-project/vivid/main/themes/schema.json 2 | 3 | colors: 4 | # Lavi colorscheme 5 | # Based on the Lavi Neovim theme 6 | # https://github.com/b0o/lavi.nvim 7 | 8 | # Background shades 9 | dark_bg: '25213B' 10 | dark_bg_dark: '1A1729' 11 | dark_bg_med: '2D2846' 12 | black: '2F2A38' 13 | black_med: '4C435C' 14 | black_bright: '8977A8' 15 | 16 | # Foreground shades 17 | dark_fg: 'FFF1E0' 18 | white: 'EEE6FF' 19 | white_bright: 'FFFFFF' 20 | 21 | # Accent colors 22 | cyan: '2BEDC0' 23 | skyblue: '3FC4C4' 24 | green: '7CF89C' 25 | yellow: 'FFD080' 26 | orange: 'FF9969' 27 | pumpkin: 'FF9969' 28 | red: 'F2637E' 29 | red_bright: 'FF87A5' 30 | 31 | # Purples and violets 32 | violet: 'DC91FF' 33 | violet_bright: 'B891FF' 34 | indigo: '9074FF' 35 | blue: '7583FF' 36 | oceanblue: '80BDFF' 37 | lavender: 'A872FB' 38 | velvet: 'B29EED' 39 | anise: '7F7DEE' 40 | anise_dark: '7E7490' 41 | 42 | # Pinks 43 | blush: 'EBBBF9' 44 | powder: 'EAC6F5' 45 | dust: 'EAD2F1' 46 | 47 | # Earth tones 48 | matcha: '9FDFB4' 49 | mint: 'A2E0CA' 50 | 51 | # Adjusted colors 52 | file_normal: 'C5C3F4' # anise.lighten(50).desaturate(10) 53 | dir_normal: 'E0CFF9' # violet_bright.lighten(30).desaturate(10) 54 | config_modified: 'FFE6B3' # yellow.lighten(30) 55 | source_added: 'E4FCE9' # green.lighten(60) 56 | archive_staged: 'A3DCFF' # oceanblue.lighten(10) 57 | file_ignored: '9A9AC0' # anise.lighten(20).desaturate(50) 58 | 59 | core: 60 | normal_text: 61 | foreground: dark_fg 62 | 63 | regular_file: 64 | foreground: file_normal 65 | 66 | reset_to_normal: {} 67 | 68 | directory: 69 | foreground: dir_normal 70 | 71 | symlink: 72 | foreground: cyan 73 | 74 | multi_hard_link: {} 75 | 76 | fifo: 77 | foreground: dark_bg 78 | background: cyan 79 | 80 | socket: 81 | foreground: dark_bg 82 | background: violet 83 | 84 | door: 85 | foreground: dark_bg 86 | background: violet 87 | 88 | block_device: 89 | foreground: oceanblue 90 | background: black 91 | 92 | character_device: 93 | foreground: blush 94 | background: black 95 | 96 | broken_symlink: 97 | foreground: white_bright 98 | background: red 99 | 100 | missing_symlink_target: 101 | foreground: white_bright 102 | background: red 103 | 104 | setuid: 105 | foreground: white_bright 106 | background: red_bright 107 | 108 | setgid: 109 | foreground: white_bright 110 | background: orange 111 | 112 | file_with_capability: 113 | foreground: white_bright 114 | background: red 115 | 116 | sticky_other_writable: 117 | foreground: dark_bg 118 | background: green 119 | 120 | other_writable: 121 | foreground: violet_bright 122 | font-style: bold 123 | 124 | sticky: 125 | foreground: white_bright 126 | background: violet 127 | 128 | executable_file: 129 | foreground: orange 130 | font-style: bold 131 | 132 | text: 133 | special: 134 | foreground: dark_bg 135 | background: yellow 136 | 137 | todo: 138 | foreground: yellow 139 | font-style: bold 140 | 141 | licenses: 142 | foreground: anise_dark 143 | 144 | configuration: 145 | foreground: config_modified 146 | 147 | other: 148 | foreground: file_normal 149 | 150 | markup: 151 | foreground: dust 152 | 153 | programming: 154 | source: 155 | foreground: source_added 156 | 157 | tooling: 158 | foreground: cyan 159 | 160 | continuous-integration: 161 | foreground: matcha 162 | 163 | media: 164 | foreground: blush 165 | 166 | office: 167 | foreground: red_bright 168 | 169 | archives: 170 | foreground: archive_staged 171 | font-style: underline 172 | 173 | executable: 174 | foreground: orange 175 | font-style: bold 176 | 177 | unimportant: 178 | foreground: file_ignored 179 | -------------------------------------------------------------------------------- /config/nushell/autoload/files.nu: -------------------------------------------------------------------------------- 1 | # Smart & pretty file and directory lister using eza and bat 2 | export def l [ 3 | --levels (-L): int = 1 # Levels of depth to display (for directories) 4 | target: path = "." # Target file or directory to list 5 | ] { 6 | if not ($target | path exists) { 7 | error make -u {msg: $"Target does not exist: ($target)"} 8 | } 9 | let expanded = ($target | path expand) 10 | 11 | def default-viewer [ 12 | --text (-t) # Use text viewer 13 | file: string 14 | ] { 15 | let text = $text or ( 16 | if ( 17 | (which isutf8 | is-not-empty) and 18 | (isutf8 $file | complete).exit_code == 0) { 19 | true 20 | } else { 21 | false 22 | } 23 | ) 24 | if ($text) { 25 | if (which bat | is-not-empty) { 26 | ^bat -- $file 27 | } else { 28 | ^cat -- $file 29 | } 30 | } else { 31 | if (which file | is-not-empty) { 32 | ^file -- $file 33 | } 34 | if (which identify | is-not-empty) { 35 | ^identify -- $file 36 | } 37 | } 38 | } 39 | 40 | def exif-viewer [ 41 | file: path 42 | ] { 43 | if (which exiftool | is-not-empty) { 44 | ^exiftool $file 45 | } 46 | } 47 | 48 | match ($expanded | path type) { 49 | "dir" => { 50 | ^eza -algF --git --group-directories-first -TL $levels -- $expanded 51 | } 52 | "file" => { 53 | let mime_type = if (which xdg-mime | is-not-empty) { 54 | ^xdg-mime query filetype $expanded | str trim 55 | } else { 56 | "text/plain" 57 | } | split row '/' 58 | 59 | let type = ($mime_type | get 0) 60 | let subtype = ($mime_type | get 1) 61 | 62 | ^exa -algF --git -T -- $expanded 63 | match $type { 64 | "text" => { 65 | default-viewer --text $expanded 66 | } 67 | "image" => { 68 | exif-viewer $expanded 69 | if (which sips | is-not-empty) { 70 | ^sips -g pixelWidth -g pixelHeight -- $expanded 71 | } else { 72 | default-viewer $expanded 73 | } 74 | } 75 | "video" => { 76 | exif-viewer $expanded 77 | if (which ffprobe | is-not-empty) { 78 | ^ffprobe -hide_banner -loglevel error -- $expanded 79 | } else { 80 | default-viewer $expanded 81 | } 82 | } 83 | "application" => { 84 | if ( 85 | ($expanded | path parse | get stem | str ends-with ".tar") or 86 | (($expanded | path parse | get extension) == "tar") 87 | and (which tar | is-not-empty) 88 | ) { 89 | print "Archive Listing:" 90 | ^tar -tvf $expanded 91 | } else if ($subtype == "zip" and (which unzip | is-not-empty)) { 92 | print "Archive Listing:" 93 | ^unzip -l $expanded 94 | } else if ($subtype == "x-7z-compressed" and (which 7z | is-not-empty)) { 95 | print "Archive Listing:" 96 | ^7z l $expanded 97 | } else if ($subtype == "pdf") { 98 | exif-viewer $expanded 99 | if (which pdfinfo | is-not-empty) { 100 | ^pdfinfo $expanded 101 | } 102 | default-viewer $expanded 103 | } else { 104 | default-viewer $expanded 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | # mkdir and cd 113 | export def --env mcd [ 114 | ...args: string 115 | ] { 116 | let dir = ($args | path join) 117 | mkdir $dir 118 | cd $dir 119 | } 120 | 121 | alias l1 = l -L 1 122 | alias l2 = l -L 2 123 | alias l3 = l -L 3 124 | alias l4 = l -L 4 125 | alias l5 = l -L 5 126 | alias l6 = l -L 6 127 | alias l7 = l -L 7 128 | alias l8 = l -L 8 129 | alias l9 = l -L 9 130 | 131 | alias cx = chmod +x 132 | alias tf = tail -f 133 | alias cat = bat 134 | alias duh = du -h 135 | alias dfh = df -h 136 | alias mcdt = mcd /tmp 137 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/conform/internal.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local format_on_save = true 4 | 5 | function M.set_format_on_save(val) 6 | format_on_save = val 7 | vim.notify('Format on save ' .. (val and 'enabled' or 'disabled')) 8 | end 9 | 10 | function M.toggle_format_on_save() M.set_format_on_save(not format_on_save) end 11 | 12 | ---@type table 13 | M.formatters = {} 14 | 15 | ---@type table 16 | M.formatters_by_ft = { 17 | cmake = { 'gersemi' }, 18 | glsl = { 'clang_format' }, 19 | go = { 'gofmt', 'goimports' }, 20 | lua = { 'stylua' }, 21 | nix = { 'alejandra' }, 22 | 23 | -- python = { 'isort', 'black' }, 24 | 25 | javascript = { 'dprint' }, 26 | javascriptreact = { 'dprint' }, 27 | typescript = { 'dprint' }, 28 | typescriptreact = { 'dprint' }, 29 | svelte = { 'dprint' }, 30 | 31 | dockerfile = { 'dprint' }, 32 | json = { 'dprint' }, 33 | jsonc = { 'dprint' }, 34 | markdown = { 35 | 'dprint' --[[ , 'injected' ]], 36 | }, 37 | mdx = { 'prettierd' }, -- TODO: Use dprint when MDX is supported: https://github.com/dprint/dprint-plugin-markdown/issues/93 38 | toml = { 'dprint' }, 39 | 40 | css = { 'prettierd', 'stylelint' }, 41 | graphql = { 'prettierd' }, 42 | html = { 'prettierd' }, 43 | less = { 'prettierd' }, 44 | scss = { 'prettierd' }, 45 | yaml = { 'prettierd' }, 46 | xml = { 'prettierd' }, 47 | 48 | sh = { 'shfmt', 'shellharden' }, 49 | bash = { 'shfmt', 'shellharden' }, 50 | zsh = { 'shfmt', 'shellharden' }, 51 | } 52 | 53 | ---@param name string 54 | ---@param tbl conform.FormatterConfigOverride|fun(bufnr: integer): nil|conform.FormatterConfigOverride 55 | ---@param setup? boolean 56 | function M.extend_formatter(name, tbl, setup) 57 | if not M.formatters[name] then 58 | M.formatters[name] = require('conform.formatters.' .. name) 59 | end 60 | ---@diagnostic disable-next-line: param-type-mismatch 61 | M.formatters[name] = vim.tbl_deep_extend('force', M.formatters[name], tbl) 62 | if setup == nil or setup == true then 63 | M.setup() 64 | end 65 | end 66 | 67 | ---@param ft string 68 | ---@param formatter conform.FormatterConfigOverride|fun(bufnr: integer): nil|conform.FormatterConfigOverride 69 | ---@param setup? boolean 70 | function M.set_formatter(ft, formatter, setup) 71 | M.formatters[ft] = formatter 72 | if setup == nil or setup == true then 73 | M.setup() 74 | end 75 | end 76 | 77 | ---@param tbl table 78 | ---@param merge? boolean 79 | ---@param setup? boolean 80 | function M.set_formatters_by_ft(tbl, merge, setup) 81 | merge = merge == nil and true or merge 82 | if merge then 83 | M.formatters_by_ft = vim.tbl_deep_extend('force', M.formatters_by_ft, tbl) 84 | else 85 | ---@diagnostic disable-next-line: assign-type-mismatch 86 | M.formatters_by_ft = tbl 87 | end 88 | if setup == nil or setup == true then 89 | M.setup() 90 | end 91 | end 92 | 93 | function M.setup() 94 | require('conform').setup { 95 | log_level = vim.log.levels.DEBUG, 96 | notify_on_error = true, 97 | format_on_save = function(buf) 98 | if format_on_save then 99 | local ft = vim.bo[buf].filetype 100 | local formatter = M.formatters_by_ft[ft] 101 | local opts = { 102 | timeout_ms = 5000, 103 | lsp_format = 'fallback', 104 | } 105 | formatter = type(formatter) == 'function' and formatter(buf) or formatter 106 | if type(formatter) == 'table' and formatter.lsp_format ~= nil then 107 | opts.lsp_format = formatter.lsp_format 108 | end 109 | return opts 110 | end 111 | end, 112 | formatters = M.formatters, 113 | formatters_by_ft = M.formatters_by_ft, 114 | } 115 | end 116 | 117 | return M 118 | -------------------------------------------------------------------------------- /config/nushell/autoload/nix.nu: -------------------------------------------------------------------------------- 1 | # Manage Nix user profiles with flakes. 2 | # See: https://github.com/lf-/flakey-profile 3 | export module fp { 4 | def --env init [ 5 | --profile (-p): string 6 | --dir (-d): string 7 | ] { 8 | let dotfiles_dir = $env | get -o DOTFILES_HOME 9 | let profile_default = $env | get -o NIX_PROFILE | default "dev" 10 | let profile = if ($profile | is-empty) { $profile_default } else { $profile } 11 | let base_dir = if ($dir | is-empty) { $dotfiles_dir } else { $dir | path expand } 12 | if ($base_dir | is-empty) { 13 | error make -u {msg: $"Error: Base directory is not set: set DOTFILES_HOME or specify --dir"} 14 | } 15 | if not ($base_dir | path exists) { 16 | error make -u {msg: $"Error: Base directory does not exist: ($base_dir)"} 17 | } 18 | cd $base_dir 19 | $profile 20 | } 21 | 22 | # Build the flakey profile 23 | export def --wrapped build [ 24 | --profile (-p): string # Specify the profile name 25 | --dir (-d): string # Specify the base directory 26 | ...args: string # Specify additional arguments to pass to nix 27 | ] { 28 | let profile = (init --profile=$profile --dir=$dir) 29 | nix build $".#profile.($profile)" ...$args 30 | } 31 | 32 | # Switch (activate) the flakey profile 33 | export def --wrapped switch [ 34 | --profile (-p): string # Specify the profile name 35 | --dir (-d): string # Specify the base directory 36 | ...args: string # Specify additional arguments to pass to nix 37 | ] { 38 | let profile = (init --profile=$profile --dir=$dir) 39 | nix run $".#profile.($profile).switch" ...$args 40 | } 41 | 42 | # Rollback to the previous flakey profile 43 | export def --wrapped rollback [ 44 | --profile (-p): string # Specify the profile name 45 | --dir (-d): string # Specify the base directory 46 | ...args: string # Specify additional arguments to pass to nix 47 | ] { 48 | let profile = (init --profile=$profile --dir=$dir) 49 | nix run $".#profile.($profile).rollback" ...$args 50 | } 51 | 52 | # Enter a development shell 53 | export def --wrapped develop [ 54 | --profile (-p): string # Specify the profile name 55 | --dir (-d): string # Specify the base directory 56 | ...args: string # Specify additional arguments to pass to nix 57 | ] { 58 | init --profile=$profile --dir=$dir 59 | nix develop ...$args 60 | } 61 | 62 | # Enter a shell with the flakey profile's environment 63 | export def --wrapped shell [ 64 | --profile (-p): string # Specify the profile name 65 | --dir (-d): string # Specify the base directory 66 | ...args: string # Specify additional arguments to pass to nix 67 | ] { 68 | init --profile=$profile --dir=$dir 69 | nix shell ...$args 70 | } 71 | 72 | # Update the flake.lock file 73 | export def --wrapped update [ 74 | --profile (-p): string # Specify the profile name 75 | --dir (-d): string # Specify the base directory 76 | ...inputs: string # Specify the inputs to update (e.g. nixpkgs) 77 | ] { 78 | init --profile=$profile --dir=$dir 79 | nix flake update ...$inputs 80 | switch --profile=$profile --dir=$dir 81 | } 82 | 83 | export alias b = build 84 | export alias sw = switch 85 | export alias rb = rollback 86 | export alias dev = develop 87 | export alias sh = shell 88 | export alias up = update 89 | export alias upd = update 90 | } 91 | 92 | use fp 93 | 94 | def --wrapped fp [cmd, ...args] { 95 | print -e "Usage: fp []" 96 | print -e "Commands:" 97 | print -e " build (b) Build the flakey profile" 98 | print -e " switch (sw) Switch to the flakey profile" 99 | print -e " rollback (rb) Rollback to the previous flakey profile" 100 | print -e " develop (dev) Enter a development shell" 101 | print -e " shell (sh) Enter a shell with the flakey profile's environment" 102 | print -e " update (up) Update the flake.lock file" 103 | } 104 | -------------------------------------------------------------------------------- /config/nvim/lua/user/settings.lua: -------------------------------------------------------------------------------- 1 | vim.o.spell = false 2 | vim.o.spellfile = vim.fn.stdpath 'config' .. '/spellfile.utf-8.add' 3 | 4 | vim.o.undofile = true 5 | vim.o.undodir = vim.fn.stdpath 'cache' .. '/undo' 6 | 7 | vim.o.backup = true 8 | vim.o.backupdir = vim.fn.stdpath 'data' .. '/backup' 9 | 10 | vim.wo.number = true 11 | vim.wo.relativenumber = true 12 | vim.wo.numberwidth = 1 13 | 14 | vim.o.hidden = true 15 | 16 | vim.o.mouse = 'n' 17 | 18 | vim.o.breakindent = true 19 | 20 | vim.o.ignorecase = true 21 | vim.o.smartcase = true 22 | 23 | vim.o.ignorecase = true -- ignore case when searching 24 | vim.o.smartcase = true -- don't ignore case if user types an uppercase letter 25 | vim.o.magic = true -- change set of special search characters 26 | 27 | vim.o.hlsearch = true -- keep matches highlighted after searching 28 | vim.o.incsearch = true -- show matches while typing 29 | vim.opt.inccommand = 'split' 30 | 31 | vim.opt.complete = '' 32 | vim.opt.completeopt = '' 33 | 34 | vim.o.timeout = true 35 | vim.o.timeoutlen = 350 36 | vim.o.matchtime = 2 -- show matching parens/brackets for 200ms 37 | 38 | vim.o.updatetime = 500 39 | 40 | vim.o.signcolumn = 'auto:1-2' 41 | 42 | vim.o.clipboard = 'unnamedplus' -- Enable yanking between vim sessions and system 43 | 44 | vim.o.sessionoptions = 'globals,blank,buffers,curdir,folds,help,tabpages,winsize' 45 | 46 | vim.o.splitright = true -- default vertical splits to open on right 47 | vim.o.splitbelow = true -- default horizontal splits to open on bottom 48 | 49 | vim.o.eadirection = 'hor' 50 | 51 | vim.o.wildchar = 9 -- equivalent to 'set wildchar=' 52 | 53 | vim.o.modeline = true -- always parse modelines when loading files 54 | vim.o.exrc = false -- use jedrzejboczar/exrc.nvim instead 55 | 56 | vim.g.mapleader = ' ' 57 | vim.g.maplocalleader = ',' 58 | 59 | vim.o.splitkeep = 'screen' -- keep the text on the same screen line when splitting 60 | 61 | vim.opt.shortmess:append 'I' -- Disable intro message 62 | 63 | vim.o.title = true 64 | vim.o.titlestring = 'nvim: %f' 65 | 66 | vim.o.showtabline = 0 67 | 68 | vim.o.cursorline = true 69 | 70 | vim.o.showmode = false 71 | vim.o.showcmd = true 72 | vim.o.showcmdloc = 'statusline' 73 | vim.o.ruler = false 74 | vim.o.cmdheight = 0 75 | vim.o.wrap = false 76 | 77 | vim.o.laststatus = 3 78 | vim.o.statusline = '%{""}' 79 | 80 | vim.o.showmatch = true 81 | 82 | vim.o.scrolloff = 5 83 | vim.o.smoothscroll = true -- scroll by screen line rather than by text line when wrap is set 84 | 85 | -- vim.o.winborder = 'rounded' 86 | 87 | vim.opt.list = true 88 | vim.opt.listchars = { 89 | eol = '¬', 90 | tab = 'ᐧᐧᐧ', 91 | trail = '~', 92 | extends = '»', 93 | precedes = '«', 94 | } 95 | vim.opt.fillchars = { 96 | diff = '╱', 97 | } 98 | 99 | vim.o.termguicolors = true 100 | 101 | vim.o.shell = 'nu' 102 | vim.o.shellcmdflag = '-c' 103 | 104 | --- Diagnostics 105 | vim.diagnostic.config { 106 | virtual_text = false, 107 | float = { 108 | border = 'rounded', 109 | prefix = function(diagnostic) 110 | ---@cast diagnostic vim.Diagnostic 111 | local source = diagnostic.source 112 | local replacements = { 113 | ['Lua Diagnostics.'] = 'LuaLS', 114 | } 115 | source = replacements[source] or source 116 | ---@diagnostic disable-next-line: missing-return-value 117 | return source and ('[' .. source .. '] ') or '' 118 | end, 119 | }, 120 | signs = { 121 | text = { 122 | [vim.diagnostic.severity.ERROR] = ' ', 123 | [vim.diagnostic.severity.WARN] = ' ', 124 | [vim.diagnostic.severity.HINT] = ' ', 125 | [vim.diagnostic.severity.INFO] = ' ', 126 | }, 127 | }, 128 | underline = true, 129 | update_in_insert = false, 130 | } 131 | 132 | ---- Providers 133 | vim.g.loaded_perl_provider = 0 134 | 135 | ---- Builtin plugins 136 | -- Disable default matchparen plugin 137 | vim.g.loaded_matchparen = 1 138 | 139 | ---- Filetypes 140 | -- disable default man.vim mappings 141 | vim.g.no_man_maps = 1 142 | 143 | -- Disable netrw 144 | vim.g.loaded_netrw = 1 145 | vim.g.loaded_netrwPlugin = 1 146 | -------------------------------------------------------------------------------- /config/nushell/scripts/awtrix/app/gh/mod.nu: -------------------------------------------------------------------------------- 1 | use ../../builder.nu [init, draw, duration, submit] 2 | use ./palettes.nu [palettes, temporal-palette] 3 | 4 | def get-color [ 5 | kind: string 6 | --palette: record 7 | ]: nothing -> int { 8 | ($palette | default $palettes.default) | get -o $kind | default ($palette.NONE) 9 | } 10 | 11 | # Get the date of the first day of the week for a given date, 12 | # assuming each week starts on Sunday 13 | def week-start [date: datetime]: nothing -> datetime { 14 | $date - ($date | format date "%w" | into duration -u day) 15 | } 16 | 17 | # Fetch GitHub contribution data and process it into pixel array 18 | def contributions-chart [ 19 | --user: string # GitHub username (defaults to authenticated user) 20 | --date: datetime # End date, will fetch 32 weeks ending on this date (defaults to today) 21 | --palette (-p): oneof # Color palette 22 | --width (-w): int = 32 # Width of the heatmap 23 | ]: nothing -> list { 24 | let now = date now 25 | let date = $date | default $now 26 | let end_week_start = week-start $date 27 | let first_week_start = $end_week_start - ($width - 1 | into duration -u wk) 28 | 29 | let palette = if ($palette | is-empty) { 30 | temporal-palette --date=$date 31 | } else if ($palette | describe) == "record" { 32 | $palette 33 | } else if $palette == "random" { 34 | $palettes | values | get (random int 0..(($palettes | columns | length) - 1)) 35 | } else { 36 | $palettes | get ($palette | into string) 37 | } 38 | 39 | let response = (^gh api graphql 40 | -F $"from=($first_week_start | format date "%FT00:00:00%:z")" 41 | -F $"to=($end_week_start + 7day | format date "%FT00:00:00%:z")" 42 | -F $"user=($user)" 43 | -f query=' 44 | query($user: String!, $from: DateTime!, $to: DateTime!) { 45 | user(login: $user) { 46 | contributionsCollection(from: $from, to: $to) { 47 | contributionCalendar { 48 | weeks { 49 | contributionDays { 50 | date 51 | contributionLevel 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | ') | from json 59 | 60 | let gh_data = try { 61 | $response.data.user.contributionsCollection.contributionCalendar.weeks 62 | } catch { 63 | error make -u { msg: "Failed to fetch GitHub contribution data" } 64 | } 65 | 66 | let dates = ($gh_data.contributionDays 67 | | flatten 68 | | reduce -f {} { |day, acc| $acc | insert $day.date $day.contributionLevel }) 69 | 70 | (0..(7 * $width - 1)) | each { |i| 71 | let weeks = $i mod $width | into duration -u wk 72 | let days = $i / $width | math floor | into duration -u day 73 | let date = $first_week_start + $weeks + $days 74 | let date_str = ($date | format date "%Y-%m-%d") 75 | let kind = ($dates | get -o $date_str | default "NONE") 76 | let kind = if ($kind == "NONE" and $now < $date) { "FUTURE" } else { $kind } 77 | get-color --palette=$palette $kind 78 | } 79 | } 80 | 81 | def complete-palette [] { 82 | $palettes | columns | append "random" 83 | } 84 | 85 | # Run the GitHub contribution heatmap app 86 | export def run [ 87 | --user (-u): string # GitHub username (defaults to authenticated user) 88 | --date (-d): datetime # End date, will fetch 32 weeks ending on this date (defaults to today) 89 | --palette (-p): oneof@complete-palette # Color palette 90 | --width (-w): int = 32 # Width of the heatmap 91 | -x: int = 0 # X position of the heatmap 92 | -y: int = 0 # Y position of the heatmap 93 | ] { 94 | let user = if ($user | is-not-empty) { $user } else { 95 | try { 96 | ^git config github.user 97 | } catch { 98 | error make -u { msg: "No GitHub user configured. Pass --user or set github.user in git config." } 99 | } 100 | } 101 | let chart = contributions-chart --date=$date --user=$user --width=$width --palette=$palette 102 | init "gh" 103 | | draw bitmap $x $y $width 8 $chart 104 | | duration 20 105 | | submit 106 | } 107 | -------------------------------------------------------------------------------- /config/nvim/lua/user/util/recent-wins.lua: -------------------------------------------------------------------------------- 1 | ---- Recent wins 2 | -- An extension of the 'wincmd p' concept, but ignoring special windows like 3 | -- popups, sidebars, and quickfix. 4 | 5 | local fn = require 'user.fn' 6 | 7 | ---@alias tabpage_id number 8 | 9 | local M = { 10 | tabpage_wins_normal = {}, -- only normal windows 11 | tabpage_wins_any = {}, -- all windows 12 | } 13 | 14 | M.update = function(current_win) 15 | current_win = current_win or vim.api.nvim_get_current_win() 16 | local tabpage = vim.api.nvim_win_get_tabpage(current_win) 17 | if not M.tabpage_wins_normal then 18 | M.tabpage_wins_normal = {} 19 | end 20 | if not M.tabpage_wins_normal[tabpage] then 21 | M.tabpage_wins_normal[tabpage] = {} 22 | end 23 | if not M.tabpage_wins_any[tabpage] then 24 | M.tabpage_wins_any[tabpage] = {} 25 | end 26 | local tabpage_recents = M.tabpage_wins_normal[tabpage] 27 | local tabpage_recents_any = M.tabpage_wins_any[tabpage] 28 | if current_win ~= tabpage_recents_any[1] then 29 | M.tabpage_wins_any[tabpage] = { 30 | current_win, 31 | tabpage_recents_any[1] or nil, 32 | } 33 | end 34 | if not fn.is_normal_win(current_win) then 35 | return 36 | end 37 | if current_win ~= tabpage_recents[1] then 38 | M.tabpage_wins_normal[tabpage] = { 39 | current_win, 40 | tabpage_recents[1] or nil, 41 | } 42 | end 43 | end 44 | 45 | M.tabpage_get_recents = function(tabpage) 46 | tabpage = tabpage or vim.api.nvim_get_current_tabpage() 47 | return M.tabpage_wins_normal[tabpage] 48 | end 49 | 50 | M.tabpage_get_recents_any = function(tabpage) 51 | tabpage = tabpage or vim.api.nvim_get_current_tabpage() 52 | return M.tabpage_wins_any[tabpage] 53 | end 54 | 55 | M.tabpage_get_recents_smart = function(tabpage) 56 | for _, tp in ipairs { tabpage, M.tabpage_get_recents, M.tabpage_get_recents_any } do 57 | local recents 58 | if tp then 59 | if type(tp) == 'function' then 60 | recents = tp() 61 | else 62 | recents = M.tabpage_wins_normal[tp] 63 | end 64 | end 65 | if recents then 66 | return recents 67 | end 68 | end 69 | end 70 | 71 | M.get_most_recent = function(tabpage_recents, current_win) 72 | current_win = current_win or vim.api.nvim_get_current_win() 73 | tabpage_recents = tabpage_recents or M.tabpage_get_recents() 74 | local winid = tabpage_recents and tabpage_recents[1] 75 | if not winid then 76 | return 77 | end 78 | if current_win == winid then 79 | winid = tabpage_recents[2] 80 | end 81 | if not winid or not vim.api.nvim_win_is_valid(winid) then 82 | return 83 | end 84 | return winid 85 | end 86 | 87 | M.get_most_recent_any = function(current_win) 88 | return M.get_most_recent(M.tabpage_get_recents_any(), current_win) 89 | end 90 | 91 | M.get_most_recent_smart = function(current_win) 92 | local tabpage_recents = M.tabpage_get_recents_any(current_win) or {} 93 | if not vim.api.nvim_win_is_valid(tabpage_recents[1] or -1) then 94 | tabpage_recents = M.tabpage_get_recents() 95 | end 96 | return M.get_most_recent(tabpage_recents, current_win) 97 | end 98 | 99 | M.focus_most_recent = function(winid) 100 | winid = winid or M.get_most_recent() 101 | if winid and vim.api.nvim_win_is_valid(winid) then 102 | vim.api.nvim_set_current_win(winid) 103 | return 104 | end 105 | vim.cmd [[wincmd p]] 106 | end 107 | 108 | M.focus_most_recent_any = function() 109 | return M.focus_most_recent(M.get_most_recent()) 110 | end 111 | 112 | M.focus_most_recent_smart = function() 113 | return M.focus_most_recent(M.get_most_recent_smart()) 114 | end 115 | 116 | M.flip_recents = function(tabpage_recents) 117 | tabpage_recents = tabpage_recents or M.tabpage_get_recents() 118 | local cur_winid = vim.api.nvim_get_current_win() 119 | local last_winid = tabpage_recents and tabpage_recents[1] 120 | if not last_winid or last_winid == cur_winid then 121 | return 122 | end 123 | vim.api.nvim_set_current_win(tabpage_recents[2]) 124 | M.update() 125 | vim.cmd [[wincmd p]] 126 | end 127 | 128 | M.flip_recents_any = function() 129 | return M.flip_recents(M.tabpage_get_recents_any()) 130 | end 131 | 132 | M.flip_recents_smart = function() 133 | return M.flip_recents(M.tabpage_get_recents_smart()) 134 | end 135 | 136 | return M 137 | -------------------------------------------------------------------------------- /config/nvim/lua/user/plugins/snacks.lua: -------------------------------------------------------------------------------- 1 | ---@type LazySpec[] 2 | return { 3 | { 4 | 'folke/snacks.nvim', 5 | priority = 1000, 6 | lazy = false, 7 | config = function() 8 | local Snacks = require 'snacks' 9 | local maputil = require 'user.util.map' 10 | local map = maputil.map 11 | 12 | Snacks.setup { 13 | bigfile = { 14 | enabled = true, 15 | notify = true, 16 | size = 1.5 * 1024 * 1024, -- 1.5MB 17 | -- Enable or disable features when big file detected 18 | ---@param ctx {buf: number, ft:string} 19 | setup = function(ctx) 20 | ---@diagnostic disable-next-line: missing-fields 21 | Snacks.util.wo(0, { 22 | foldmethod = 'manual', 23 | statuscolumn = '', 24 | conceallevel = 0, 25 | }) 26 | vim.schedule(function() vim.bo[ctx.buf].syntax = ctx.ft end) 27 | end, 28 | }, 29 | notifier = { 30 | enabled = true, 31 | margin = { top = 2, right = 1, bottom = 1 }, 32 | style = 'fancy', 33 | filter = function(notif) 34 | local ignores = { 35 | '^No information available$', 36 | '^client.supports_method is deprecated', 37 | '^Error requesting document symbols$', 38 | } 39 | return not vim.iter(ignores):any( 40 | ---@param pat string 41 | function(pat) return string.find(notif.msg, pat) ~= nil end 42 | ) 43 | end, 44 | }, 45 | quickfile = { enabled = true }, 46 | statuscolumn = { enabled = true }, 47 | terminal = { enabled = false }, 48 | words = { enabled = true }, 49 | indent = { 50 | enabled = true, 51 | indent = { enabled = true }, 52 | animate = { enabled = false }, 53 | scope = { enabled = true, only_current = true }, 54 | }, 55 | scratch = { enabled = true }, 56 | scope = { 57 | enabled = true, 58 | keys = { 59 | ---@type table 60 | textobject = { 61 | ii = { 62 | min_size = 2, 63 | edge = false, 64 | cursor = true, 65 | desc = 'inside scope', 66 | }, 67 | ai = { 68 | cursor = true, 69 | edge = true, 70 | min_size = 2, 71 | desc = 'around scope', 72 | }, 73 | }, 74 | ---@type table 75 | jump = { 76 | ['[s'] = { 77 | min_size = 2, 78 | bottom = false, 79 | cursor = true, 80 | edge = true, 81 | desc = 'Scope: start', 82 | }, 83 | [']s'] = { 84 | min_size = 2, 85 | bottom = true, 86 | cursor = true, 87 | edge = true, 88 | desc = 'Scope: end', 89 | }, 90 | }, 91 | }, 92 | }, 93 | } 94 | 95 | vim.api.nvim_create_user_command( 96 | 'Bdelete', 97 | function(opts) Snacks.bufdelete.delete { force = opts.bang == true } end, 98 | { bang = true } 99 | ) 100 | 101 | vim.api.nvim_create_user_command( 102 | 'Gbrowse', 103 | function() Snacks.gitbrowse() end, 104 | {} 105 | ) 106 | 107 | vim.api.nvim_create_user_command( 108 | 'Notifications', 109 | function() Snacks.notifier.show_history() end, 110 | {} 111 | ) 112 | 113 | vim.api.nvim_create_user_command( 114 | 'Scratch', 115 | function() Snacks.scratch() end, 116 | {} 117 | ) 118 | 119 | vim.api.nvim_create_user_command( 120 | 'ScratchSelect', 121 | function() Snacks.scratch.select() end, 122 | {} 123 | ) 124 | 125 | map('n', ')', function() Snacks.words.jump(vim.v.count1, true) end, 'Snacks: Jump to next word') 126 | 127 | map('n', '(', function() Snacks.words.jump(-vim.v.count1, true) end, 'Snacks: Jump to prev word') 128 | end, 129 | }, 130 | } 131 | -------------------------------------------------------------------------------- /config/niri/.gitignore: -------------------------------------------------------------------------------- 1 | private.kdl 2 | scratchpads.private.yaml 3 | 4 | # Created by https://www.toptal.com/developers/gitignore/api/python 5 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 6 | 7 | ### Python ### 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | share/python-wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | MANIFEST 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | *.py,cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | cover/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Django stuff: 66 | *.log 67 | local_settings.py 68 | db.sqlite3 69 | db.sqlite3-journal 70 | 71 | # Flask stuff: 72 | instance/ 73 | .webassets-cache 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | .pybuilder/ 83 | target/ 84 | 85 | # Jupyter Notebook 86 | .ipynb_checkpoints 87 | 88 | # IPython 89 | profile_default/ 90 | ipython_config.py 91 | 92 | # pyenv 93 | # For a library or package, you might want to ignore these files since the code is 94 | # intended to run in multiple environments; otherwise, check them in: 95 | # .python-version 96 | 97 | # pipenv 98 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 99 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 100 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 101 | # install all needed dependencies. 102 | #Pipfile.lock 103 | 104 | # poetry 105 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 106 | # This is especially recommended for binary packages to ensure reproducibility, and is more 107 | # commonly ignored for libraries. 108 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 109 | #poetry.lock 110 | 111 | # pdm 112 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 113 | #pdm.lock 114 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 115 | # in version control. 116 | # https://pdm.fming.dev/#use-with-ide 117 | .pdm.toml 118 | 119 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 120 | __pypackages__/ 121 | 122 | # Celery stuff 123 | celerybeat-schedule 124 | celerybeat.pid 125 | 126 | # SageMath parsed files 127 | *.sage.py 128 | 129 | # Environments 130 | .env 131 | .venv 132 | env/ 133 | venv/ 134 | ENV/ 135 | env.bak/ 136 | venv.bak/ 137 | 138 | # Spyder project settings 139 | .spyderproject 140 | .spyproject 141 | 142 | # Rope project settings 143 | .ropeproject 144 | 145 | # mkdocs documentation 146 | /site 147 | 148 | # mypy 149 | .mypy_cache/ 150 | .dmypy.json 151 | dmypy.json 152 | 153 | # Pyre type checker 154 | .pyre/ 155 | 156 | # pytype static type analyzer 157 | .pytype/ 158 | 159 | # Cython debug symbols 160 | cython_debug/ 161 | 162 | # PyCharm 163 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 164 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 165 | # and can be added to the global gitignore or merged into this file. For a more nuclear 166 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 167 | #.idea/ 168 | 169 | ### Python Patch ### 170 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 171 | poetry.toml 172 | 173 | # ruff 174 | .ruff_cache/ 175 | 176 | # LSP config files 177 | pyrightconfig.json 178 | 179 | # End of https://www.toptal.com/developers/gitignore/api/python 180 | --------------------------------------------------------------------------------