├── 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 |
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 |
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 = '' .. tag_name .. '>' .. 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 |
--------------------------------------------------------------------------------