├── .config ├── nvim │ ├── lua │ │ ├── plugins │ │ │ ├── neogit.lua │ │ │ ├── transparent.lua │ │ │ ├── rubyonrails.lua │ │ │ ├── rose-pine.lua │ │ │ ├── ts_tools.lua │ │ │ ├── oil.lua │ │ │ ├── copilotchat.lua │ │ │ ├── gf_override.lua │ │ │ ├── vim-tmux.lua │ │ │ ├── codecompanion.lua │ │ │ └── example.lua │ │ └── config │ │ │ ├── autocmds.lua │ │ │ ├── keymaps.lua │ │ │ ├── options.lua │ │ │ └── lazy.lua │ ├── stylua.toml │ ├── README.md │ ├── init.lua │ ├── .neoconf.json │ ├── lazyvim.json │ ├── lazy-lock.json │ └── LICENSE ├── hypr │ ├── background.jpg │ ├── hyprland │ │ ├── keybinds.conf │ │ ├── env.conf │ │ ├── execs.conf │ │ ├── colors.conf │ │ ├── rules.conf │ │ └── general.conf │ ├── custom │ │ ├── keybinds.conf │ │ ├── env.conf │ │ ├── general.conf │ │ ├── execs.conf │ │ └── rules.conf │ ├── bin │ │ ├── workspace_action.sh │ │ ├── record-script.sh │ │ └── grimblast.sh │ ├── hyprpaper.conf │ ├── hyprlock │ │ ├── check-capslock.sh │ │ └── status.sh │ ├── shaders │ │ ├── invert.frag │ │ ├── extradark.frag │ │ ├── chromatic_abberation.frag │ │ ├── drugs.frag │ │ ├── solarized.frag │ │ └── crt.frag │ ├── hypridle.conf │ ├── hyprland.conf │ └── hyprlock.conf ├── gh │ ├── hosts.yml │ └── config.yml ├── alacritty │ ├── font-size.toml │ ├── alacritty.toml │ ├── font.toml │ └── theme.toml ├── fish │ ├── conf.d │ │ └── rustup.fish │ ├── config.fish │ ├── functions │ │ ├── fish_prompt.fish │ │ └── zoxie.fish │ └── fish_variables ├── mise │ └── config.toml ├── waybar │ ├── checkupdate.sh │ ├── style.css │ ├── config.sway │ └── config ├── opencode │ └── agent │ │ ├── docs-writer.md │ │ ├── security-auditor.md │ │ ├── review.md │ │ ├── rails_style.md │ │ └── rails_dev.md ├── ghostty │ ├── themes │ │ └── catpuccin-mocha.conf │ └── config ├── kitty │ └── kitty.conf ├── wezterm │ └── wezterm.lua ├── tmux │ └── tmux.conf ├── starship.toml ├── tiny_nvim │ └── init.lua └── aerospace │ └── aerospace.toml ├── .gitignore ├── bin └── ts ├── .gitconfig └── install.sh /.config/nvim/lua/plugins/neogit.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "NeogitOrg/neogit", 3 | } 4 | -------------------------------------------------------------------------------- /.config/nvim/stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | indent_width = 2 3 | column_width = 120 -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/transparent.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'xiyaowong/transparent.nvim', 3 | } 4 | -------------------------------------------------------------------------------- /.config/hypr/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gs/dotfiles/main/.config/hypr/background.jpg -------------------------------------------------------------------------------- /.config/hypr/hyprland/keybinds.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gs/dotfiles/main/.config/hypr/hyprland/keybinds.conf -------------------------------------------------------------------------------- /.config/hypr/custom/keybinds.conf: -------------------------------------------------------------------------------- 1 | # You can put your preferred keybinds here 2 | # https://wiki.hyprland.org/Configuring/Binds/ -------------------------------------------------------------------------------- /.config/gh/hosts.yml: -------------------------------------------------------------------------------- 1 | github.com: 2 | git_protocol: https 3 | users: 4 | gs-deliverists-io: 5 | user: gs-deliverists-io 6 | -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/rubyonrails.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "tpope/vim-rails", 3 | "tpope/vim-fugitive", 4 | "tpope/vim-rhubarb", 5 | } 6 | -------------------------------------------------------------------------------- /.config/hypr/custom/env.conf: -------------------------------------------------------------------------------- 1 | # You can put extra environment variables here 2 | # https://wiki.hyprland.org/Configuring/Environment-variables/ 3 | -------------------------------------------------------------------------------- /.config/alacritty/font-size.toml: -------------------------------------------------------------------------------- 1 | # Leave this file in place for omakub to rely on a shared font size for all terminal apps 2 | [font] 3 | size = 12 4 | -------------------------------------------------------------------------------- /.config/fish/conf.d/rustup.fish: -------------------------------------------------------------------------------- 1 | # Only source cargo env if it exists 2 | if test -f "$HOME/.cargo/env.fish" 3 | . "$HOME/.cargo/env.fish" 4 | end 5 | -------------------------------------------------------------------------------- /.config/hypr/custom/general.conf: -------------------------------------------------------------------------------- 1 | # Put general config stuff here 2 | # Here's a list of every variable: https://wiki.hyprland.org/Configuring/Variables/ -------------------------------------------------------------------------------- /.config/hypr/bin/workspace_action.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | hyprctl dispatch "$1" $(((($(hyprctl activeworkspace -j | jq -r .id) - 1) / 10) * 10 + $2)) 3 | -------------------------------------------------------------------------------- /.config/hypr/hyprpaper.conf: -------------------------------------------------------------------------------- 1 | preload=/home/sfistak/.config/sway/background.jpg 2 | wallpaper=, /home/sfistak/.config/sway/background.jpg 3 | splash=false 4 | -------------------------------------------------------------------------------- /.config/hypr/custom/execs.conf: -------------------------------------------------------------------------------- 1 | # You can make apps auto-start here 2 | # Relevant Hyprland wiki section: https://wiki.hyprland.org/Configuring/Keywords/#executing 3 | -------------------------------------------------------------------------------- /.config/mise/config.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | gh = "latest" 3 | node = "latest" 4 | npm = "latest" 5 | ruby = "latest" 6 | "ubi:sst/opencode" = "1.0.150" 7 | yarn = "latest" 8 | -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/rose-pine.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "rose-pine/neovim", 3 | name = "rose-pine", 4 | config = function() 5 | vim.cmd("colorscheme rose-pine") 6 | end, 7 | } 8 | -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/ts_tools.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "pmizio/typescript-tools.nvim", 3 | dependencies = { "nvim-lua/plenary.nvim", "neovim/nvim-lspconfig" }, 4 | opts = {}, 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .config/fish/conf.d/private.fish 2 | .DS_Store 3 | .config/tmux/plugins/ 4 | .config/opencode/bun.lock 5 | .config/opencode/node_modules/ 6 | .config/opencode/package.json 7 | -------------------------------------------------------------------------------- /.config/nvim/README.md: -------------------------------------------------------------------------------- 1 | # 💤 LazyVim 2 | 3 | A starter template for [LazyVim](https://github.com/LazyVim/LazyVim). 4 | Refer to the [documentation](https://lazyvim.github.io/installation) to get started. 5 | -------------------------------------------------------------------------------- /.config/hypr/custom/rules.conf: -------------------------------------------------------------------------------- 1 | # You can put custom rules here 2 | # Window/layer rules: https://wiki.hyprland.org/Configuring/Window-Rules/ 3 | # Workspace rules: https://wiki.hyprland.org/Configuring/Workspace-Rules/ 4 | -------------------------------------------------------------------------------- /.config/nvim/init.lua: -------------------------------------------------------------------------------- 1 | -- bootstrap lazy.nvim, LazyVim and your plugins 2 | require("config.lazy") 3 | require("luasnip").filetype_extend("ruby", { "rails" }) 4 | require("luasnip").filetype_extend("eruby", { "html" }) 5 | -------------------------------------------------------------------------------- /.config/alacritty/alacritty.toml: -------------------------------------------------------------------------------- 1 | import = [ "~/.config/alacritty/theme.toml", "~/.config/alacritty/font.toml", "~/.config/alacritty/font-size.toml", "~/.local/share/omakub/defaults/alacritty.toml" ] 2 | [window] 3 | opacity = 0.8 4 | -------------------------------------------------------------------------------- /.config/alacritty/font.toml: -------------------------------------------------------------------------------- 1 | [font] 2 | normal = { family = "CaskaydiaMono Nerd Font", style = "Regular" } 3 | bold = { family = "CaskaydiaMono Nerd Font", style = "Bold" } 4 | italic = { family = "CaskaydiaMono Nerd Font", style = "Italic" } 5 | -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/oil.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'stevearc/oil.nvim', 3 | config = function() 4 | local oil = require 'oil' 5 | oil.setup() 6 | vim.keymap.set('n', '-', oil.toggle_float, { desc = 'Open parent directory' }) 7 | end, 8 | } 9 | -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/copilotchat.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "CopilotC-Nvim/CopilotChat.nvim", 3 | opts = { 4 | model = "gpt-4.1", 5 | }, 6 | dependencies = { 7 | "github/copilot.vim", -- required dependency 8 | }, 9 | event = "VeryLazy", 10 | } 11 | -------------------------------------------------------------------------------- /.config/nvim/.neoconf.json: -------------------------------------------------------------------------------- 1 | { 2 | "neodev": { 3 | "library": { 4 | "enabled": true, 5 | "plugins": true 6 | } 7 | }, 8 | "neoconf": { 9 | "plugins": { 10 | "lua_ls": { 11 | "enabled": true 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.config/hypr/hyprlock/check-capslock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | MAIN_KB_CAPS=$(hyprctl devices | grep -B 6 "main: yes" | grep "capsLock" | head -1 | awk '{print $2}') 4 | 5 | if [ "$MAIN_KB_CAPS" = "yes" ]; then 6 | echo "Caps Lock active" 7 | else 8 | echo "" 9 | fi 10 | -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/gf_override.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "gs-deliverists-io/gf-override.nvim", 3 | dependencies = { "nvim-lua/plenary.nvim" }, 4 | keys = { 5 | { 6 | "gf", 7 | function() 8 | require("gf-override").gf_handler() 9 | end, 10 | desc = "Enhanced gf command with file creation", 11 | }, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /.config/waybar/checkupdate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | UPDATES=$(checkupdates | wc -l) 4 | 5 | re='^[0-9]+$' 6 | if ! [[ $UPDATES =~ $re ]]; then 7 | echo "Failed to check updates" 8 | exit 0 9 | fi 10 | 11 | if (($UPDATES > 0)); then 12 | echo "${UPDATES} updates available" 13 | exit 0 14 | else 15 | echo "System is up to date" 16 | exit 0 17 | fi 18 | -------------------------------------------------------------------------------- /.config/opencode/agent/docs-writer.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Writes and maintains project documentation 3 | mode: subagent 4 | model: github-copilot/claude-sonnet-4.5 5 | tools: 6 | bash: false 7 | --- 8 | 9 | You are a technical writer. Create clear, comprehensive documentation. 10 | 11 | Focus on: 12 | 13 | - Clear explanations 14 | - Proper structure 15 | - Code examples 16 | - User-friendly language 17 | -------------------------------------------------------------------------------- /.config/hypr/shaders/invert.frag: -------------------------------------------------------------------------------- 1 | // vim: set ft=glsl: 2 | // blue light filter shader 3 | // values from https://reshade.me/forum/shader-discussion/3673-blue-light-filter-similar-to-f-lux 4 | 5 | precision mediump float; 6 | varying vec2 v_texcoord; 7 | uniform sampler2D tex; 8 | 9 | void main() { 10 | vec4 pixColor = texture2D(tex, v_texcoord); 11 | pixColor.rgb = 1.0 - pixColor.rgb; 12 | gl_FragColor = pixColor; 13 | } -------------------------------------------------------------------------------- /.config/nvim/lua/config/autocmds.lua: -------------------------------------------------------------------------------- 1 | -- Autocmds are automatically loaded on the VeryLazy event 2 | -- Default autocmds that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/autocmds.lua 3 | -- Add any additional autocmds here 4 | 5 | -- Ensure .yml files are recognized as yaml 6 | vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, { 7 | pattern = "*.yml", 8 | callback = function() 9 | vim.bo.filetype = "yaml" 10 | end, 11 | }) 12 | -------------------------------------------------------------------------------- /.config/opencode/agent/security-auditor.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Performs security audits and identifies vulnerabilities 3 | mode: subagent 4 | model: github-copilot/claude-sonnet-4.5 5 | tools: 6 | write: false 7 | edit: false 8 | --- 9 | 10 | You are a security expert. Focus on identifying potential security issues. 11 | 12 | Look for: 13 | 14 | - Input validation vulnerabilities 15 | - Authentication and authorization flaws 16 | - Data exposure risks 17 | - Dependency vulnerabilities 18 | - Configuration security issues 19 | -------------------------------------------------------------------------------- /.config/hypr/shaders/extradark.frag: -------------------------------------------------------------------------------- 1 | // vim: set ft=glsl: 2 | // blue light filter shader 3 | // values from https://reshade.me/forum/shader-discussion/3673-blue-light-filter-similar-to-f-lux 4 | 5 | precision mediump float; 6 | varying vec2 v_texcoord; 7 | uniform sampler2D tex; 8 | 9 | void main() { 10 | 11 | vec4 pixColor = texture2D(tex, v_texcoord); 12 | 13 | // red 14 | pixColor[0] *= 0.7; 15 | // green 16 | pixColor[1] *= 0.6; 17 | // blue 18 | pixColor[2] *= 0.5; 19 | 20 | gl_FragColor = pixColor; 21 | } 22 | -------------------------------------------------------------------------------- /.config/ghostty/themes/catpuccin-mocha.conf: -------------------------------------------------------------------------------- 1 | palette = 0=#45475a 2 | palette = 1=#f38ba8 3 | palette = 2=#a6e3a1 4 | palette = 3=#f9e2af 5 | palette = 4=#89b4fa 6 | palette = 5=#f5c2e7 7 | palette = 6=#94e2d5 8 | palette = 7=#bac2de 9 | palette = 8=#585b70 10 | palette = 9=#f38ba8 11 | palette = 10=#a6e3a1 12 | palette = 11=#f9e2af 13 | palette = 12=#89b4fa 14 | palette = 13=#f5c2e7 15 | palette = 14=#94e2d5 16 | palette = 15=#a6adc8 17 | background = 1e1e2e 18 | foreground = cdd6f4 19 | cursor-color = f5e0dc 20 | cursor-text = 1e1e2e 21 | selection-background = 353749 22 | selection-foreground = cdd6f4 23 | -------------------------------------------------------------------------------- /.config/hypr/hypridle.conf: -------------------------------------------------------------------------------- 1 | $lock_cmd = pidof hyprlock || hyprlock 2 | $suspend_cmd = pidof steam || systemctl suspend || loginctl suspend # fuck nvidia 3 | 4 | general { 5 | lock_cmd = $lock_cmd 6 | before_sleep_cmd = loginctl lock-session 7 | } 8 | 9 | listener { 10 | timeout = 180 # 3mins 11 | on-timeout = loginctl lock-session 12 | } 13 | 14 | listener { 15 | timeout = 240 # 4mins 16 | on-timeout = hyprctl dispatch dpms off 17 | on-resume = hyprctl dispatch dpms on 18 | } 19 | 20 | listener { 21 | timeout = 540 # 9mins 22 | on-timeout = $suspend_cmd 23 | } 24 | -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/vim-tmux.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "christoomey/vim-tmux-navigator", 3 | cmd = { 4 | "TmuxNavigateLeft", 5 | "TmuxNavigateDown", 6 | "TmuxNavigateUp", 7 | "TmuxNavigateRight", 8 | "TmuxNavigatePrevious", 9 | "TmuxNavigatorProcessList", 10 | }, 11 | keys = { 12 | { "", "TmuxNavigateLeft" }, 13 | { "", "TmuxNavigateDown" }, 14 | { "", "TmuxNavigateUp" }, 15 | { "", "TmuxNavigateRight" }, 16 | { "", "TmuxNavigatePrevious" }, 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /.config/alacritty/theme.toml: -------------------------------------------------------------------------------- 1 | [colors] 2 | [colors.primary] 3 | background = '#1a1b26' 4 | foreground = '#a9b1d6' 5 | 6 | # Normal colors 7 | [colors.normal] 8 | black = '#32344a' 9 | red = '#f7768e' 10 | green = '#9ece6a' 11 | yellow = '#e0af68' 12 | blue = '#7aa2f7' 13 | magenta = '#ad8ee6' 14 | cyan = '#449dab' 15 | white = '#787c99' 16 | 17 | # Bright colors 18 | [colors.bright] 19 | black = '#444b6a' 20 | red = '#ff7a93' 21 | green = '#b9f27c' 22 | yellow = '#ff9e64' 23 | blue = '#7da6ff' 24 | magenta = '#bb9af7' 25 | cyan = '#0db9d7' 26 | white = '#acb0d0' 27 | 28 | [colors.selection] 29 | background = '#7aa2f7' 30 | -------------------------------------------------------------------------------- /bin/ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ $# -eq 1 ]]; then 4 | selected=$1 5 | else 6 | selected=$(find ~/code/deliverists.io ~/code ~/ -mindepth 1 -maxdepth 1 -type d | fzf) 7 | fi 8 | 9 | if [[ -z $selected ]]; then 10 | exit 0 11 | fi 12 | 13 | selected_name=$(basename "$selected" | tr . _) 14 | tmux_running=$(pgrep tmux) 15 | 16 | if [[ -z $TMUX ]] && [[ -z $tmux_running ]]; then 17 | tmux new-session -s $selected_name -c $selected 18 | exit 0 19 | fi 20 | 21 | if ! tmux has-session -t=$selected_name 2> /dev/null; then 22 | tmux new-session -ds $selected_name -c $selected 23 | fi 24 | 25 | tmux switch-client -t $selected_name 26 | -------------------------------------------------------------------------------- /.config/hypr/hyprland.conf: -------------------------------------------------------------------------------- 1 | # This file sources other files in `hyprland` and `custom` folders 2 | # You wanna add your stuff in file in `custom` 3 | 4 | # Defaults 5 | source=~/.config/hypr/hyprland/env.conf 6 | source=~/.config/hypr/hyprland/execs.conf 7 | source=~/.config/hypr/hyprland/general.conf 8 | source=~/.config/hypr/hyprland/rules.conf 9 | source=~/.config/hypr/hyprland/colors.conf 10 | source=~/.config/hypr/hyprland/keybinds.conf 11 | 12 | # Custom 13 | source=~/.config/hypr/custom/env.conf 14 | source=~/.config/hypr/custom/execs.conf 15 | source=~/.config/hypr/custom/general.conf 16 | source=~/.config/hypr/custom/rules.conf 17 | source=~/.config/hypr/custom/keybinds.conf 18 | 19 | -------------------------------------------------------------------------------- /.config/fish/config.fish: -------------------------------------------------------------------------------- 1 | #starship init fish | source 2 | zoxide init fish | source 3 | alias cd=z # without causing an infinite loop. 4 | 5 | if status is-interactive 6 | alias be='bundle exec' 7 | alias v=nvim 8 | alias e=v 9 | export EDITOR=nvim 10 | end 11 | 12 | # opencode 13 | fish_add_path ~/bin/ 14 | fish_add_path ~/.local/share/mise/shims/ 15 | fish_add_path ~/code/dotfiles/bin 16 | 17 | # bun 18 | set --export BUN_INSTALL "$HOME/.bun" 19 | set --export PATH $BUN_INSTALL/bin $PATH 20 | export ELECTRON_OZONE_PLATFORM_HINT=auto 21 | 22 | # Added by OrbStack: command-line tools and integration 23 | # This won't be added again if you remove it. 24 | source ~/.orbstack/shell/init2.fish 2>/dev/null || : 25 | -------------------------------------------------------------------------------- /.config/nvim/lua/config/keymaps.lua: -------------------------------------------------------------------------------- 1 | -- Keymaps are automatically loaded on the VeryLazy event 2 | -- Default keymaps that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/keymaps.lua 3 | -- Add any additional keymaps here 4 | -- 5 | vim.keymap.set("n", ";", ":") 6 | -- vim.keymap.set("n", "", ":w!|:TestFile") 7 | -- vim.keymap.set("n", "", ":Neogit") 8 | vim.keymap.set("n", "fy", function() 9 | local file_path = vim.fn.expand("%:p") 10 | vim.fn.setreg("+", file_path) 11 | vim.notify("Copied file path: " .. file_path) 12 | end, { desc = "Copy full file path to clipboard" }) 13 | 14 | vim.keymap.set("n", "fN", function() 15 | vim.cmd("edit /Users/sfistak/GoogleDrive/Grzegorz.Smajdor/devnotes/") 16 | end, { desc = "Open notes folder" }) 17 | -------------------------------------------------------------------------------- /.config/hypr/hyprlock/status.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############ Variables ############ 4 | enable_battery=false 5 | battery_charging=false 6 | 7 | ####### Check availability ######## 8 | for battery in /sys/class/power_supply/*BAT*; do 9 | if [[ -f "$battery/uevent" ]]; then 10 | enable_battery=true 11 | if [[ $(cat /sys/class/power_supply/*/status | head -1) == "Charging" ]]; then 12 | battery_charging=true 13 | fi 14 | break 15 | fi 16 | done 17 | 18 | ############# Output ############# 19 | if [[ $enable_battery == true ]]; then 20 | if [[ $battery_charging == true ]]; then 21 | echo -n "(+) " 22 | fi 23 | echo -n "$(cat /sys/class/power_supply/*/capacity | head -1)"% 24 | if [[ $battery_charging == false ]]; then 25 | echo -n " remaining" 26 | fi 27 | fi 28 | 29 | echo '' -------------------------------------------------------------------------------- /.config/hypr/shaders/chromatic_abberation.frag: -------------------------------------------------------------------------------- 1 | // vim: set ft=glsl: 2 | 3 | precision highp float; 4 | varying highp vec2 v_texcoord; 5 | uniform highp sampler2D tex; 6 | 7 | #define STRENGTH 0.0027 8 | 9 | void main() { 10 | vec2 center = vec2(0.5, 0.5); 11 | vec2 offset = (v_texcoord - center) * STRENGTH; 12 | 13 | float rSquared = dot(offset, offset); 14 | float distortion = 1.0 + 1.0 * rSquared; 15 | vec2 distortedOffset = offset * distortion; 16 | 17 | vec2 redOffset = vec2(distortedOffset.x, distortedOffset.y); 18 | vec2 blueOffset = vec2(distortedOffset.x, distortedOffset.y); 19 | 20 | vec4 redColor = texture2D(tex, v_texcoord + redOffset); 21 | vec4 blueColor = texture2D(tex, v_texcoord + blueOffset); 22 | 23 | gl_FragColor = vec4(redColor.r, texture2D(tex, v_texcoord).g, blueColor.b, 1.0); 24 | } 25 | -------------------------------------------------------------------------------- /.config/nvim/lua/config/options.lua: -------------------------------------------------------------------------------- 1 | -- Options are automatically loaded before lazy.nvim startup 2 | -- Default options that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/options.lua 3 | -- Add any additional options here 4 | -- 5 | -- 6 | -- return { 7 | -- "nvim-neotest/neotest", 8 | -- lazy = true, 9 | -- dependencies = { 10 | -- "zidhuss/neotest-minitest", 11 | -- }, 12 | -- config = function() 13 | -- require("neotest.Config").setup({ 14 | -- adapters = { 15 | -- require("neotest-minitest"), 16 | -- }, 17 | -- }) 18 | -- end, 19 | -- 20 | -- require("neotest-minitest")({ 21 | -- test_cmd = function() 22 | -- return vim.tbl_flatten({ 23 | -- "bundle", 24 | -- "exec", 25 | -- "rails", 26 | -- "test", 27 | -- }) 28 | -- end, 29 | -- }), 30 | -- } 31 | -------------------------------------------------------------------------------- /.config/hypr/hyprland/env.conf: -------------------------------------------------------------------------------- 1 | # ######### Input method ########## 2 | # See https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland 3 | env = QT_IM_MODULE, fcitx 4 | env = XMODIFIERS, @im=fcitx 5 | # env = GTK_IM_MODULE, wayland # Crashes electron apps in xwayland 6 | # env = GTK_IM_MODULE, fcitx # My Gtk apps no longer require this to work with fcitx5 hmm 7 | env = SDL_IM_MODULE, fcitx 8 | env = GLFW_IM_MODULE, ibus 9 | env = INPUT_METHOD, fcitx 10 | 11 | # ############ Themes ############# 12 | env = QT_QPA_PLATFORM, wayland 13 | env = QT_QPA_PLATFORMTHEME, qt6ct 14 | # env = QT_STYLE_OVERRIDE,kvantum 15 | # env = WLR_NO_HARDWARE_CURSORS, 1 16 | 17 | # ######## Screen tearing ######### 18 | # env = WLR_DRM_NO_ATOMIC, 1 19 | 20 | # ######## Virtual envrionment ######### 21 | env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/ags/.venv 22 | 23 | # ############ Others ############# 24 | #env = GDK_SCALE,2 25 | #env = XCURSOR_SIZE,32 26 | -------------------------------------------------------------------------------- /.config/kitty/kitty.conf: -------------------------------------------------------------------------------- 1 | # Font settings 2 | font_family CaskaydiaMono Nerd Font 3 | bold_font auto 4 | italic_font auto 5 | font_size 12.0 6 | enable_ligatures yes 7 | 8 | # Color scheme: Catppuccin Mocha 9 | foreground #cdd6f4 10 | selection_background #585b70 11 | selection_foreground #cdd6f4 12 | url_color #89b4fa 13 | 14 | color0 #45475a 15 | color1 #f38ba8 16 | color2 #a6e3a1 17 | color3 #f9e2af 18 | color4 #89b4fa 19 | color5 #f5c2e7 20 | color6 #94e2d5 21 | color7 #bac2de 22 | color8 #585b70 23 | color9 #f38ba8 24 | color10 #a6e3a1 25 | color11 #f9e2af 26 | color12 #89b4fa 27 | color13 #f5c2e7 28 | 29 | # Cursor 30 | cursor #ff5555 31 | cursor_shape block 32 | 33 | # Cursor 34 | cursor #f5e0dc 35 | cursor_shape block 36 | 37 | # Window padding 38 | window_padding_width 8 39 | 40 | # Scrollback 41 | # Disable terminal bell (sound) 42 | enable_audio_bell no 43 | # Misc 44 | confirm_os_window_close 0 45 | background_opacity 0.6 46 | -------------------------------------------------------------------------------- /.config/gh/config.yml: -------------------------------------------------------------------------------- 1 | # The current version of the config schema 2 | version: 1 3 | # What protocol to use when performing git operations. Supported values: ssh, https 4 | git_protocol: https 5 | # What editor gh should run when creating issues, pull requests, etc. If blank, will refer to environment. 6 | editor: 7 | # When to interactively prompt. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled 8 | prompt: enabled 9 | # Preference for editor-based interactive prompting. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled 10 | prefer_editor_prompt: disabled 11 | # A pager program to send command output to, e.g. "less". If blank, will refer to environment. Set the value to "cat" to disable the pager. 12 | pager: 13 | # Aliases allow you to create nicknames for gh commands 14 | aliases: 15 | co: pr checkout 16 | # The path to a unix socket through which send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport. 17 | http_unix_socket: 18 | # What web browser gh should use when opening URLs. If blank, will refer to environment. 19 | browser: 20 | -------------------------------------------------------------------------------- /.config/nvim/lazyvim.json: -------------------------------------------------------------------------------- 1 | { 2 | "extras": [ 3 | "lazyvim.plugins.extras.ai.copilot-chat", 4 | "lazyvim.plugins.extras.ai.copilot-native", 5 | "lazyvim.plugins.extras.coding.luasnip", 6 | "lazyvim.plugins.extras.coding.mini-comment", 7 | "lazyvim.plugins.extras.coding.mini-surround", 8 | "lazyvim.plugins.extras.dap.core", 9 | "lazyvim.plugins.extras.editor.harpoon2", 10 | "lazyvim.plugins.extras.editor.mini-diff", 11 | "lazyvim.plugins.extras.editor.mini-files", 12 | "lazyvim.plugins.extras.editor.refactoring", 13 | "lazyvim.plugins.extras.editor.telescope", 14 | "lazyvim.plugins.extras.lang.json", 15 | "lazyvim.plugins.extras.lang.markdown", 16 | "lazyvim.plugins.extras.lang.python", 17 | "lazyvim.plugins.extras.lang.ruby", 18 | "lazyvim.plugins.extras.lang.tailwind", 19 | "lazyvim.plugins.extras.lang.typescript", 20 | "lazyvim.plugins.extras.test.core", 21 | "lazyvim.plugins.extras.util.mini-hipatterns", 22 | "lazyvim.plugins.extras.util.octo", 23 | "lazyvim.plugins.extras.util.rest" 24 | ], 25 | "install_version": 7, 26 | "news": { 27 | "NEWS.md": "11866" 28 | }, 29 | "version": 8 30 | } -------------------------------------------------------------------------------- /.config/hypr/hyprland/execs.conf: -------------------------------------------------------------------------------- 1 | # Bar, wallpaper 2 | exec-once = swww-daemon --format xrgb 3 | exec-once = /usr/lib/geoclue-2.0/demos/agent & gammastep 4 | exec-once = agsv1 & 5 | 6 | # Input method 7 | exec-once = fcitx5 8 | 9 | # Core components (authentication, lock screen, notification daemon) 10 | exec-once = gnome-keyring-daemon --start --components=secrets 11 | exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 || /usr/libexec/polkit-gnome-authentication-agent-1 12 | exec-once = hypridle 13 | exec-once = dbus-update-activation-environment --all 14 | exec-once = sleep 1 && dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # Some fix idk 15 | exec-once = hyprpm reload 16 | exec-once = waybar 17 | exec-once = swww-daemon 18 | exec-once = swww img ~/.config/hypr/background.jpg 19 | 20 | # Audio 21 | exec-once = easyeffects --gapplication-service 22 | 23 | # Clipboard: history 24 | # exec-once = wl-paste --watch cliphist store & 25 | exec-once = wl-paste --type text --watch cliphist store 26 | exec-once = wl-paste --type image --watch cliphist store 27 | 28 | # Cursor 29 | exec-once = hyprctl setcursor Bibata-Modern-Classic 24 30 | 31 | -------------------------------------------------------------------------------- /.config/wezterm/wezterm.lua: -------------------------------------------------------------------------------- 1 | -- Pull in the wezterm API 2 | local wezterm = require("wezterm") 3 | 4 | -- This will hold the configuration. 5 | local config = wezterm.config_builder() 6 | 7 | -- This is where you actually apply your config choices 8 | config.color_scheme = "Rosé Pine Moon (Gogh)" 9 | -- my coolnight colorscheme 10 | --config.colors = { 11 | -- foreground = "#CBE0F0", 12 | -- background = "#011423", 13 | -- cursor_bg = "#47FF9C", 14 | -- cursor_border = "#47FF9C", 15 | -- cursor_fg = "#011423", 16 | -- selection_bg = "#033259", 17 | -- selection_fg = "#CBE0F0", 18 | -- ansi = { "#214969", "#E52E2E", "#44FFB1", "#FFE073", "#0FC5ED", "#a277ff", "#24EAF7", "#24EAF7" }, 19 | -- brights = { "#214969", "#E52E2E", "#44FFB1", "#FFE073", "#A277FF", "#a277ff", "#24EAF7", "#24EAF7" }, 20 | --} 21 | 22 | config.font = wezterm.font("CaskaydiaCove Nerd Font Mono") 23 | config.font_size = 13 24 | 25 | config.enable_tab_bar = false 26 | 27 | config.window_decorations = "RESIZE" 28 | config.window_background_opacity = 0.92 29 | config.macos_window_background_blur = 5 30 | config.audible_bell = "Disabled" 31 | 32 | -- and finally, return the configuration to wezterm 33 | return config 34 | -------------------------------------------------------------------------------- /.config/hypr/hyprland/colors.conf: -------------------------------------------------------------------------------- 1 | # exec = export SLURP_ARGS='-d -c FFDAD4BB -b 673B3444 -s 00000000' 2 | 3 | general { 4 | col.active_border = rgba(F7DCDE39) 5 | col.inactive_border = rgba(A58A8D30) 6 | } 7 | 8 | misc { 9 | background_color = rgba(1D1011FF) 10 | } 11 | 12 | plugin { 13 | hyprbars { 14 | # Honestly idk if it works like css, but well, why not 15 | bar_text_font = Rubik, Geist, AR One Sans, Reddit Sans, Inter, Roboto, Ubuntu, Noto Sans, sans-serif 16 | bar_height = 30 17 | bar_padding = 10 18 | bar_button_padding = 5 19 | bar_precedence_over_border = true 20 | bar_part_of_window = true 21 | 22 | bar_color = rgba(1D1011FF) 23 | col.text = rgba(F7DCDEFF) 24 | 25 | 26 | # example buttons (R -> L) 27 | # hyprbars-button = color, size, on-click 28 | hyprbars-button = rgb(F7DCDE), 13, 󰖭, hyprctl dispatch killactive 29 | hyprbars-button = rgb(F7DCDE), 13, 󰖯, hyprctl dispatch fullscreen 1 30 | hyprbars-button = rgb(F7DCDE), 13, 󰖰, hyprctl dispatch movetoworkspacesilent special 31 | } 32 | } 33 | 34 | windowrulev2 = bordercolor rgba(FFB2BCAA) rgba(FFB2BC77),pinned:1 -------------------------------------------------------------------------------- /.config/hypr/bin/record-script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | getdate() { 4 | date '+%Y-%m-%d_%H.%M.%S' 5 | } 6 | getaudiooutput() { 7 | pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2 8 | } 9 | getactivemonitor() { 10 | hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name' 11 | } 12 | 13 | mkdir -p "$(xdg-user-dir VIDEOS)" 14 | cd "$(xdg-user-dir VIDEOS)" || exit 15 | if pgrep wf-recorder >/dev/null; then 16 | hyprctl notify -1 2000 "rgb(ff1ea3)" "Stopped recording" & 17 | pkill wf-recorder & 18 | else 19 | hyprctl notify -1 2000 "rgb(ff1ea3)" "Start recording" & 20 | if [[ "$1" == "--sound" ]]; then 21 | wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" --audio="$(getaudiooutput)" & 22 | disown 23 | elif [[ "$1" == "--fullscreen-sound" ]]; then 24 | wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" & 25 | disown 26 | elif [[ "$1" == "--fullscreen" ]]; then 27 | wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t & 28 | disown 29 | else 30 | wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" & 31 | disown 32 | fi 33 | fi 34 | -------------------------------------------------------------------------------- /.config/hypr/shaders/drugs.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | varying vec2 v_texcoord; 4 | uniform sampler2D tex; 5 | uniform float time; 6 | 7 | void warpco(inout vec2 tc) { 8 | tc -= 0.5; 9 | tc *= length(tc) * 2.0; 10 | tc += 0.5; 11 | } 12 | 13 | float rand1d(float seed) { 14 | return sin(seed*1454.0); 15 | } 16 | 17 | float rand2d(vec2 co) 18 | { 19 | return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453); 20 | } 21 | 22 | vec3 rgb(in vec2 tc, float freq, float amp, inout vec4 centre) { 23 | vec2 off = vec2(1.0/800.0, 0.0) * sin(tc.t * freq + time) * amp; 24 | vec2 off2 = vec2(1.0/800.0, 0.0) * sin(tc.t * freq - time * 1.5) * amp; 25 | centre = texture2D(tex, tc); 26 | return vec3(texture2D(tex, tc-off).r, centre.g, texture2D(tex, tc+off2).b); 27 | } 28 | 29 | void main() { 30 | // vec2 px = 1.0 / textureSize(tex, 0).st; 31 | vec2 tc = v_texcoord; 32 | warpco(tc); 33 | tc = mix(v_texcoord, tc, sin(time * 2.0)*0.07); 34 | tc.x += rand2d(floor(tc * 20.0 + floor(time * 2.5))) * 0.01; 35 | tc.x += rand1d(floor(tc.x * 40.0)) * 0.005 * rand1d(time * 0.001); 36 | tc.y += sin(tc.x + time) * 0.02; 37 | vec4 centre; 38 | vec3 bent = rgb(tc, 100.0, 5.0, centre); 39 | vec3 col = mix(centre.rgb, bent, sin(time)); 40 | gl_FragColor = vec4(col, centre.a); 41 | // gl_FragColor = vec4(texture2D(tex, v_texcoord)); 42 | } -------------------------------------------------------------------------------- /.config/hypr/shaders/solarized.frag: -------------------------------------------------------------------------------- 1 | // -*- mode:c -*- 2 | precision lowp float; 3 | varying vec2 v_texcoord; 4 | uniform sampler2D tex; 5 | 6 | float distanceSquared(vec3 pixColor, vec3 solarizedColor) { 7 | vec3 distanceVector = pixColor - solarizedColor; 8 | return dot(distanceVector, distanceVector); 9 | } 10 | 11 | void main() { 12 | vec3 solarized[16]; 13 | solarized[0] = vec3(0.,0.169,0.212); 14 | solarized[1] = vec3(0.027,0.212,0.259); 15 | solarized[2] = vec3(0.345,0.431,0.459); 16 | solarized[3] = vec3(0.396,0.482,0.514); 17 | solarized[4] = vec3(0.514,0.58,0.588); 18 | solarized[5] = vec3(0.576,0.631,0.631); 19 | solarized[6] = vec3(0.933,0.91,0.835); 20 | solarized[7] = vec3(0.992,0.965,0.89); 21 | solarized[8] = vec3(0.71,0.537,0.); 22 | solarized[9] = vec3(0.796,0.294,0.086); 23 | solarized[10] = vec3(0.863,0.196,0.184); 24 | solarized[11] = vec3(0.827,0.212,0.51); 25 | solarized[12] = vec3(0.424,0.443,0.769); 26 | solarized[13] = vec3(0.149,0.545,0.824); 27 | solarized[14] = vec3(0.165,0.631,0.596); 28 | solarized[15] = vec3(0.522,0.6,0.); 29 | 30 | vec3 pixColor = vec3(texture2D(tex, v_texcoord)); 31 | int closest = 0; 32 | float closestDistanceSquared = distanceSquared(pixColor, solarized[0]); 33 | for (int i = 1; i < 15; i++) { 34 | float newDistanceSquared = distanceSquared(pixColor, solarized[i]); 35 | if (newDistanceSquared < closestDistanceSquared) { 36 | closest = i; 37 | closestDistanceSquared = newDistanceSquared; 38 | } 39 | } 40 | gl_FragColor = vec4(solarized[closest], 1.); 41 | } 42 | -------------------------------------------------------------------------------- /.config/waybar/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | border: none; 3 | border-radius: 0; 4 | min-height: 0; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | #waybar { 10 | background-color: transparent; 11 | font-size: 12px; 12 | font-family: "Hack Nerd Font"; 13 | } 14 | 15 | #workspaces button { 16 | padding: 0 5px; 17 | color: #eeeeef; 18 | } 19 | /* Fix: active workspace doesn't show up */ 20 | /* #workspaces button.focused { */ 21 | #workspaces button.active { 22 | color: #fa5aa4; 23 | } 24 | 25 | #custom-powermenu, 26 | #cpu, 27 | #temperature, 28 | #battery, 29 | #memory, 30 | #workspaces, 31 | #clock, 32 | #window, 33 | #pulseaudio, 34 | #custom-updates, 35 | #network { 36 | padding: 4px 8px; 37 | background-color: #303643; 38 | border-radius: 30px; 39 | margin: 6px 4px; 40 | } 41 | 42 | #window { 43 | color: #929db1; 44 | } 45 | 46 | #tray { 47 | margin-left: 4px; 48 | } 49 | 50 | #custom-updates { 51 | color: #1788e4; 52 | } 53 | 54 | #custom-powermenu { 55 | color: #e06c75; 56 | padding-right: 11px; 57 | margin-right: 8px; 58 | } 59 | 60 | #scratchpad { 61 | color: #cffafe; 62 | padding-right: 4px; 63 | padding-left: 4px; 64 | } 65 | 66 | #pulseaudio { 67 | color: #0fb9b1; 68 | padding-right: 14px; 69 | } 70 | 71 | #cpu { 72 | color: #61afef; 73 | } 74 | 75 | #temperature { 76 | color: #98c379; 77 | } 78 | 79 | #battery { 80 | color: #1de9b6; /* Teal */ 81 | } 82 | 83 | #memory { 84 | color: #e5c07b; 85 | } 86 | 87 | #network { 88 | color: #c678dd; 89 | min-width: 200px; 90 | } 91 | 92 | #clock { 93 | color: #fff; 94 | margin-left: 8px; 95 | /* opacity: 0.7; */ 96 | /* font-size: 18px; */ 97 | } 98 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [color] 2 | ui = auto 3 | diff = auto 4 | status = auto 5 | branch = auto 6 | interactive = auto 7 | grep = auto 8 | 9 | [core] 10 | editor=/opt/homebrew/bin/nvim 11 | # sshCommand = ssh -i ~/.ssh/office_deliverists 12 | sshCommand = ssh -i ~/.ssh/gs_deliveristsio 13 | 14 | [merge] 15 | summary=false 16 | tool = opendiff 17 | 18 | [branch] 19 | autosetupmerge = always 20 | autosetuprebase = always 21 | 22 | [apply] 23 | 24 | [alias] 25 | st = status 26 | ci = commit 27 | co = checkout 28 | di = diff 29 | dc = diff --cached 30 | amend = commit --amend 31 | aa = add --all 32 | ff = merge --ff-only 33 | pullff = pull --ff-only 34 | noff = merge --no-ff 35 | div = divergence 36 | gn = goodness 37 | gnc = goodness --cached 38 | fa = fetch --all 39 | pom = push origin main 40 | b = branch 41 | ds = diff --stat=160,120 42 | dh1 = diff HEAD~1 43 | head = !git l -1 44 | h = !git head 45 | hp = !show_git_head 46 | # r = !git l -30 47 | r = log --decorate --oneline --graph 48 | ra = !git r --all 49 | l = log --decorate --oneline --graph 50 | la = !git l --all 51 | pr = !hub pull-request 52 | usr = !git-usr 53 | one = show -s --pretty='format:%h (%s)' 54 | rlog = !sh -c 'git log --color \"$@\"|fmt -s -w 79|less -FRSX' _ 55 | summary = log --abbrev-commit --pretty=oneline --author="sfistak" --since=last.mon --reverse 56 | 57 | 58 | [diff] 59 | tool = opendiff 60 | [diff "ruby"] 61 | funcname = "^ *\\(\\(def\\) .*\\)" 62 | [web] 63 | browser = open 64 | [pull] 65 | rebase = true 66 | #[push] 67 | # default = tracking 68 | [alias] 69 | # Mark a repo as trusted 70 | trust = "!mkdir -p .git/safe" 71 | [user] 72 | name = gs 73 | email = gs@deliverists.io 74 | -------------------------------------------------------------------------------- /.config/ghostty/config: -------------------------------------------------------------------------------- 1 | # This is the configuration file for Ghostty. 2 | # 3 | # This template file has been automatically created at the following 4 | # path since Ghostty couldn't find any existing config files on your system: 5 | # 6 | # /home/sfistak/.config/ghostty/config 7 | # 8 | # The template does not set any default options, since Ghostty ships 9 | # with sensible defaults for all options. Users should only need to set 10 | # options that they want to change from the default. 11 | # 12 | # Run `ghostty +show-config --default --docs` to view a list of 13 | # all available config options and their default values. 14 | # 15 | # Additionally, each config option is also explained in detail 16 | # on Ghostty's website, at https://ghostty.org/docs/config. 17 | 18 | # Config syntax crash course 19 | # ========================== 20 | # # The config file consists of simple key-value pairs, 21 | # # separated by equals signs. 22 | # font-family = Iosevka 23 | # window-padding-x = 2 24 | # 25 | # # Spacing around the equals sign does not matter. 26 | # # All of these are identical: 27 | # key=value 28 | # key= value 29 | # key =value 30 | # key = value 31 | # 32 | # # Any line beginning with a # is a comment. It's not possible to put 33 | # # a comment after a config option, since it would be interpreted as a 34 | # # part of the value. For example, this will have a value of "#123abc": 35 | # background = #123abc 36 | # 37 | # # Empty values are used to reset config keys to default. 38 | # key = 39 | # 40 | # # Some config options have unique syntaxes for their value, 41 | # # which is explained in the docs for that config option. 42 | # # Just for example: 43 | # resize-overlay-duration = 4s 200ms 44 | 45 | theme = Rose Pine 46 | cursor-style = block 47 | font-family = CaskaydiaMono Nerd Font Mono 48 | font-size = 18 49 | cursor-color = #FF0000 50 | background-opacity= 0.95 51 | copy-on-select = true 52 | confirm-close-surface = false 53 | -------------------------------------------------------------------------------- /.config/nvim/lua/config/lazy.lua: -------------------------------------------------------------------------------- 1 | local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" 2 | vim.api.nvim_create_autocmd("QuitPre", { 3 | callback = function() 4 | vim.cmd("confirm quit") 5 | end, 6 | }) 7 | if not vim.loop.fs_stat(lazypath) then 8 | -- bootstrap lazy.nvim 9 | -- stylua: ignore 10 | vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath }) 11 | end 12 | vim.opt.rtp:prepend(vim.env.LAZY or lazypath) 13 | 14 | require("lazy").setup({ 15 | spec = { 16 | -- add LazyVim and import its plugins 17 | { "LazyVim/LazyVim", import = "lazyvim.plugins" }, 18 | -- import any extras modules here 19 | -- { import = "lazyvim.plugins.extras.lang.typescript" }, 20 | -- { import = "lazyvim.plugins.extras.lang.json" }, 21 | -- { import = "lazyvim.plugins.extras.ui.mini-animate" }, 22 | -- import/override with your plugins 23 | { import = "plugins" }, 24 | }, 25 | defaults = { 26 | -- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup. 27 | -- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default. 28 | lazy = false, 29 | -- It's recommended to leave version=false for now, since a lot the plugin that support versioning, 30 | -- have outdated releases, which may break your Neovim install. 31 | version = false, -- always use the latest git commit 32 | -- version = "*", -- try installing the latest stable version for plugins that support semver 33 | }, 34 | install = { colorscheme = { "tokyonight", "habamax" } }, 35 | checker = { enabled = true }, -- automatically check for plugin updates 36 | performance = { 37 | rtp = { 38 | -- disable some rtp plugins 39 | disabled_plugins = { 40 | "gzip", 41 | -- "matchit", 42 | -- "matchparen", 43 | -- "netrwPlugin", 44 | "tarPlugin", 45 | "tohtml", 46 | "tutor", 47 | "zipPlugin", 48 | }, 49 | }, 50 | }, 51 | }) 52 | -------------------------------------------------------------------------------- /.config/hypr/hyprlock.conf: -------------------------------------------------------------------------------- 1 | $text_color = rgba(FFDAD6FF) 2 | $entry_background_color = rgba(41000311) 3 | $entry_border_color = rgba(896E6C55) 4 | $entry_color = rgba(FFDAD6FF) 5 | $font_family = Rubik Light 6 | $font_family_clock = Rubik Light 7 | $font_material_symbols = Material Symbols Rounded 8 | 9 | background { 10 | color = rgba(181818FF) 11 | # path = {{ SWWW_WALL }} 12 | 13 | # path = screenshot 14 | # blur_size = 15 15 | # blur_passes = 4 16 | } 17 | input-field { 18 | monitor = 19 | size = 250, 50 20 | outline_thickness = 2 21 | dots_size = 0.1 22 | dots_spacing = 0.3 23 | outer_color = $entry_border_color 24 | inner_color = $entry_background_color 25 | font_color = $entry_color 26 | fade_on_empty = true 27 | 28 | position = 0, 20 29 | halign = center 30 | valign = center 31 | } 32 | 33 | label { # Caps Lock Warning 34 | monitor = 35 | text = cmd[update:250] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/check-capslock.sh 36 | color = $text_color 37 | font_size = 13 38 | font_family = $font_family 39 | position = 0, -25 40 | halign = center 41 | valign = center 42 | } 43 | 44 | 45 | label { # Clock 46 | monitor = 47 | text = $TIME 48 | color = $text_color 49 | font_size = 65 50 | font_family = $font_family_clock 51 | 52 | position = 0, 300 53 | halign = center 54 | valign = center 55 | } 56 | label { # Date 57 | monitor = 58 | text = cmd[update:5000] date +"%A, %B %d" 59 | color = $text_color 60 | font_size = 17 61 | font_family = $font_family 62 | 63 | position = 0, 240 64 | halign = center 65 | valign = center 66 | } 67 | 68 | label { # User 69 | monitor = 70 | text =  $USER 71 | color = $text_color 72 | outline_thickness = 2 73 | dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 74 | dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 75 | dots_center = true 76 | font_size = 20 77 | font_family = $font_family 78 | position = 0, 50 79 | halign = center 80 | valign = bottom 81 | } 82 | 83 | label { # Status 84 | monitor = 85 | text = cmd[update:5000] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/status.sh 86 | color = $text_color 87 | font_size = 14 88 | font_family = $font_family 89 | 90 | position = 30, -30 91 | halign = left 92 | valign = top 93 | } -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/codecompanion.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable-next-line: unused-local 2 | local function generate_slash_commands() 3 | local commands = {} 4 | for _, command in ipairs({ "buffer", "file", "help", "symbols" }) do 5 | commands[command] = { 6 | opts = { 7 | provider = LazyVim.pick.picker.name, -- dynamically resolve the provider 8 | }, 9 | } 10 | end 11 | return commands 12 | end 13 | 14 | return { 15 | { 16 | "olimorris/codecompanion.nvim", 17 | dependencies = { 18 | "nvim-lua/plenary.nvim", 19 | "nvim-treesitter/nvim-treesitter", 20 | }, 21 | enabled = false, 22 | opts = { 23 | strategies = { 24 | chat = { 25 | adapter = { 26 | name = "copilot", 27 | model = "claude-sonnet-4", 28 | }, 29 | roles = { 30 | llm = "CodeCompanion", 31 | user = "Me", 32 | }, 33 | slash_commands = generate_slash_commands(), 34 | keymaps = { 35 | close = { 36 | modes = { 37 | n = "q", 38 | }, 39 | index = 3, 40 | callback = "keymaps.close", 41 | description = "Close Chat", 42 | }, 43 | stop = { 44 | modes = { 45 | n = "aa", 61 | "CodeCompanionActions", 62 | mode = { "n", "v" }, 63 | noremap = true, 64 | silent = true, 65 | desc = "CodeCompanion actions", 66 | }, 67 | { 68 | "ac", 69 | "CodeCompanionChat Toggle", 70 | mode = { "n", "v" }, 71 | noremap = true, 72 | silent = true, 73 | desc = "CodeCompanion chat", 74 | }, 75 | { 76 | "ad", 77 | "CodeCompanionChat Add", 78 | mode = "v", 79 | noremap = true, 80 | silent = true, 81 | desc = "CodeCompanion add to chat", 82 | }, 83 | }, 84 | }, 85 | { 86 | "MeanderingProgrammer/render-markdown.nvim", 87 | ft = { "markdown", "codecompanion" }, 88 | }, 89 | } 90 | -------------------------------------------------------------------------------- /.config/fish/functions/fish_prompt.fish: -------------------------------------------------------------------------------- 1 | function fish_prompt 2 | set -l __last_command_exit_status $status 3 | 4 | if not set -q -g __fish_arrow_functions_defined 5 | set -g __fish_arrow_functions_defined 6 | function _git_branch_name 7 | set -l branch (git symbolic-ref --quiet HEAD 2>/dev/null) 8 | if set -q branch[1] 9 | echo (string replace -r '^refs/heads/' '' $branch) 10 | else 11 | echo (git rev-parse --short HEAD 2>/dev/null) 12 | end 13 | end 14 | 15 | function _is_git_dirty 16 | not command git diff-index --cached --quiet HEAD -- &>/dev/null 17 | or not command git diff --no-ext-diff --quiet --exit-code &>/dev/null 18 | end 19 | 20 | function _is_git_repo 21 | type -q git 22 | or return 1 23 | git rev-parse --git-dir >/dev/null 2>&1 24 | end 25 | 26 | function _hg_branch_name 27 | echo (hg branch 2>/dev/null) 28 | end 29 | 30 | function _is_hg_dirty 31 | set -l stat (hg status -mard 2>/dev/null) 32 | test -n "$stat" 33 | end 34 | 35 | function _is_hg_repo 36 | fish_print_hg_root >/dev/null 37 | end 38 | 39 | function _repo_branch_name 40 | _$argv[1]_branch_name 41 | end 42 | 43 | function _is_repo_dirty 44 | _is_$argv[1]_dirty 45 | end 46 | 47 | function _repo_type 48 | if _is_hg_repo 49 | echo hg 50 | return 0 51 | else if _is_git_repo 52 | echo git 53 | return 0 54 | end 55 | return 1 56 | end 57 | end 58 | 59 | set -l cyan (set_color -o cyan) 60 | set -l yellow (set_color -o yellow) 61 | set -l red (set_color -o red) 62 | set -l green (set_color -o green) 63 | set -l blue (set_color -o blue) 64 | set -l normal (set_color normal) 65 | 66 | set -l arrow_color "$green" 67 | if test $__last_command_exit_status != 0 68 | set arrow_color "$red" 69 | end 70 | 71 | set -l arrow "$arrow_color➜ " 72 | if fish_is_root_user 73 | set arrow "$arrow_color# " 74 | end 75 | 76 | set -l cwd $cyan(basename (prompt_pwd)) 77 | 78 | set -l repo_info 79 | if set -l repo_type (_repo_type) 80 | set -l repo_branch $red(_repo_branch_name $repo_type) 81 | set repo_info "$blue $repo_type:($repo_branch$blue)" 82 | 83 | if _is_repo_dirty $repo_type 84 | set -l dirty "$yellow ✗" 85 | set repo_info "$repo_info$dirty" 86 | end 87 | end 88 | 89 | echo -n -s $arrow ' '$cwd $repo_info $normal ' ' 90 | end 91 | -------------------------------------------------------------------------------- /.config/waybar/config.sway: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "layer": "top", 4 | "position": "top", 5 | "modules-left": [ 6 | "sway/workspaces", 7 | "sway/scratchpad", 8 | "sway/mode", 9 | "tray", 10 | "custom/updates" 11 | ], 12 | "modules-center": [ 13 | "clock" 14 | ], 15 | "modules-right": [ 16 | "network", 17 | "temperature", 18 | "memory", 19 | "cpu", 20 | "pulseaudio", 21 | "custom/powermenu", 22 | ], 23 | "tray": { 24 | "icon-size": 24, 25 | }, 26 | "sway/scratchpad": { 27 | "format": "{icon} {count}", 28 | "show-empty": false, 29 | "format-icons": ["", ""], 30 | "tooltip": true, 31 | "tooltip-format": "{app}: {title}" 32 | }, 33 | "sway/window": { 34 | "format": "{}", 35 | "max-length": 50 36 | }, 37 | "sway/workspaces": { 38 | "disable-scroll": true, 39 | "all-outputs": false, 40 | "format": "{icon} {name}", 41 | "format-icons": { 42 | "1": " ", 43 | "2": " ", 44 | "3": " ", 45 | "4": " ", 46 | "5": " ", 47 | "urgent": " ", 48 | "focused": " ", 49 | "default": " " 50 | } 51 | }, 52 | "network": { 53 | "interface": "enp34s0", 54 | "tooltip-format-ethernet": "{ifname} ", 55 | "interval": 2, 56 | "format": " {bandwidthDownBits}  {bandwidthUpBits}", 57 | }, 58 | "temperature": { 59 | "format": "{icon} {temperatureC}°C", 60 | "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input", 61 | "critical-threshold": 80, 62 | "format-icons": ["", "", ""] 63 | }, 64 | "memory": { 65 | "format": " {}%", 66 | "tooltip": "false" 67 | }, 68 | "cpu": { 69 | "format": " {usage}%", 70 | "tooltip": "false" 71 | }, 72 | "custom/powermenu": { 73 | "format": "", 74 | "tooltip": false, 75 | "on-click": "exec $HOME/.config/rofi/powermenu.sh", 76 | }, 77 | "custom/updates": { 78 | "format": " {}", 79 | "tooltip": false, 80 | "interval": 3600, 81 | "exec": "exec $HOME/.config/waybar/checkupdate.sh" 82 | }, 83 | "pulseaudio": { 84 | "format": "{volume}% {icon}", 85 | "format-bluetooth": "{volume}% {icon}", 86 | "format-muted": "", 87 | "format-icons": { 88 | "headphone": "", 89 | "hands-free": "", 90 | "headset": "", 91 | "phone": "", 92 | "portable": "", 93 | "car": "", 94 | "default": ["", ""] 95 | }, 96 | "scroll-step": 1, 97 | "on-click": "pavucontrol" 98 | }, 99 | "clock": { 100 | //"format": " {:%H:%M}", 101 | "format": "{:%H:%M}", 102 | "interval": 60 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /.config/fish/fish_variables: -------------------------------------------------------------------------------- 1 | # This file contains fish universal variable definitions. 2 | # VERSION: 3.0 3 | SETUVAR --export --path PATH:/usr/local/mysql/bin\x1e/Users/sfistak/\x2egem/ruby/3\x2e3\x2e4/bin\x1e/usr/local/mysql/bin\x1e/Users/sfistak/\x2erubies/3\x2e3\x2e4/lib/ruby/gems/3\x2e3\x2e0/bin\x1e/usr/local/mysql/bin\x1e/Users/sfistak/\x2erubies/3\x2e3\x2e4/bin\x1e/usr/local/mysql/bin\x1e/Users/sfistak/\x2ebun/bin\x1e/usr/local/mysql/bin\x1e/Users/sfistak/\x2ecargo/bin\x1e/usr/local/mysql/bin\x1e/opt/homebrew/sbin\x1e/usr/local/mysql/bin\x1e/opt/homebrew/bin\x1e/usr/local/mysql/bin\x1e/opt/homebrew/opt/node\x4016/bin\x1e/usr/local/mysql/bin\x1e/usr/local/bin\x1e/usr/local/mysql/bin\x1e/System/Cryptexes/App/usr/bin\x1e/usr/local/mysql/bin\x1e/usr/bin\x1e/usr/local/mysql/bin\x1e/bin\x1e/usr/local/mysql/bin\x1e/usr/sbin\x1e/usr/local/mysql/bin\x1e/sbin\x1e/usr/local/mysql/bin\x1e/var/run/com\x2eapple\x2esecurity\x2ecryptexd/codex\x2esystem/bootstrap/usr/local/bin\x1e/usr/local/mysql/bin\x1e/var/run/com\x2eapple\x2esecurity\x2ecryptexd/codex\x2esystem/bootstrap/usr/bin\x1e/usr/local/mysql/bin\x1e/var/run/com\x2eapple\x2esecurity\x2ecryptexd/codex\x2esystem/bootstrap/usr/appleinternal/bin\x1e/usr/local/mysql/bin\x1e/opt/X11/bin\x1e/usr/local/mysql/bin\x1e/Library/Apple/usr/bin\x1e/usr/local/mysql/bin\x1e/Applications/Postgres\x2eapp/Contents/Versions/latest/bin\x1e/usr/local/mysql/bin\x1e/Applications/iTerm\x2eapp/Contents/Resources/utilities 4 | SETUVAR __fish_initialized:3800 5 | SETUVAR fish_color_autosuggestion:4D5566 6 | SETUVAR fish_color_cancel:\x2dr 7 | SETUVAR fish_color_command:39BAE6 8 | SETUVAR fish_color_comment:626A73 9 | SETUVAR fish_color_cwd:59C2FF 10 | SETUVAR fish_color_cwd_root:red 11 | SETUVAR fish_color_end:F29668 12 | SETUVAR fish_color_error:FF3333 13 | SETUVAR fish_color_escape:95E6CB 14 | SETUVAR fish_color_history_current:\x2d\x2dbold 15 | SETUVAR fish_color_host:normal 16 | SETUVAR fish_color_host_remote:yellow 17 | SETUVAR fish_color_match:F07178 18 | SETUVAR fish_color_normal:B3B1AD 19 | SETUVAR fish_color_operator:E6B450 20 | SETUVAR fish_color_param:B3B1AD 21 | SETUVAR fish_color_quote:C2D94C 22 | SETUVAR fish_color_redirection:FFEE99 23 | SETUVAR fish_color_search_match:\x2d\x2dbackground\x3dE6B450 24 | SETUVAR fish_color_selection:\x2d\x2dbackground\x3dE6B450 25 | SETUVAR fish_color_status:red 26 | SETUVAR fish_color_user:brgreen 27 | SETUVAR fish_color_valid_path:\x2d\x2dunderline 28 | SETUVAR fish_key_bindings:fish_default_key_bindings 29 | SETUVAR fish_pager_color_completion:normal 30 | SETUVAR fish_pager_color_description:B3A06D\x1eyellow 31 | SETUVAR fish_pager_color_prefix:normal\x1e\x2d\x2dbold\x1e\x2d\x2dunderline 32 | SETUVAR fish_pager_color_progress:brwhite\x1e\x2d\x2dbackground\x3dcyan 33 | SETUVAR fish_pager_color_selected_background:\x2dr 34 | SETUVAR fish_user_paths:/Users/gs/bin\x1e/Users/gs/\x2eopencode/bin\x1e/Users/gs/code/dotfiles/bin\x1e/Users/gs/\x2elocal/share/mise/shims\x1e/Users/sfistak/\x2eopencode/bin\x1e/Users/sfistak/bin\x1e/Users/sfistak/\x2elocal/share/mise/shims\x1e/home/sfistak/\x2elocal/share/mise/shims\x1e/home/sfistak/\x2eopencode/bin\x1e/Users/sfistak/\x2ecodeium/windsurf/bin\x1e/opt/homebrew/opt/mysql\x408\x2e4/bin\x1e/opt/homebrew/sbin\x1e/opt/homebrew/bin\x1e/opt/homebrew/opt/node\x4016/bin\x1e/Applications/Ghostty\x2eapp/Contents/MacOS 35 | -------------------------------------------------------------------------------- /.config/tmux/tmux.conf: -------------------------------------------------------------------------------- 1 | set -g @plugin 'nikolovlazar/tokyo-night-tmux' 2 | set -g @plugin 'tmux-plugins/tpm' 3 | set -g @plugin 'tmux-plugins/tmux-sensible' 4 | set -g @plugin 'aserowy/tmux.nvim' 5 | set -g @plugin 'tmux-plugins/tmux-resurrect' 6 | set -g @plugin 'omerxx/tmux-sessionx' 7 | 8 | set -g @sessionx-bind 's' 9 | 10 | unbind C-s 11 | set-option -g prefix C-b 12 | bind C-b send-prefix 13 | 14 | # Setup tmux theme 15 | set -g @tokyo-night-tmux_window_id_style fsquare 16 | set -g @tokyo-night-tmux_pane_id_style hsquare 17 | set -g @tokyo-night-tmux_zoom_id_style dsquare 18 | 19 | # Undercurl 20 | set -g default-terminal "${TERM}" 21 | set -as terminal-overrides ',*:Smulx=\E[4::%p1%dm' # undercurl support 22 | set -as terminal-overrides ',*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m' # underscore colours - needs tmux-3.0 23 | 24 | # Toggle tmux status bar 25 | bind '\' set status 26 | if-shell "[[ $(tmux lsw | wc -l) -le 1 ]]" 'set -g status' 27 | 28 | set -g status on 29 | 30 | # Enable mouse 31 | set -g mouse on 32 | 33 | # Increase history limit 34 | set -g history-limit 30000 35 | 36 | # Start windows and panes at 1 and not 0 37 | set -g base-index 1 38 | set -g pane-base-index 1 39 | set-window-option -g pane-base-index 1 40 | set-option -g renumber-windows on 41 | 42 | # Zero-out escape time delay for quicker response 43 | set -s escape-time 0 44 | 45 | # keybindings 46 | bind-key r source-file ~/.config/tmux/tmux.conf \; display-message "~/.config/tmux/tmux.conf reloaded" 47 | 48 | # resizing 49 | is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'" 50 | 51 | bind -n 'M-h' if-shell "$is_vim" 'send-keys M-h' 'resize-pane -L 1' 52 | bind -n 'M-j' if-shell "$is_vim" 'send-keys M-j' 'resize-pane -D 1' 53 | bind -n 'M-k' if-shell "$is_vim" 'send-keys M-k' 'resize-pane -U 1' 54 | bind -n 'M-l' if-shell "$is_vim" 'send-keys M-l' 'resize-pane -R 1' 55 | 56 | bind-key -T copy-mode-vi M-h resize-pane -L 1 57 | bind-key -T copy-mode-vi M-j resize-pane -D 1 58 | bind-key -T copy-mode-vi M-k resize-pane -U 1 59 | bind-key -T copy-mode-vi M-l resize-pane -R 1 60 | 61 | # Open the new panes in the current path 62 | bind - split-window -v -c "#{pane_current_path}" 63 | bind | split-window -h -c "#{pane_current_path}" 64 | bind c new-window -c "#{pane_current_path}" 65 | 66 | bind h select-pane -L 67 | bind j select-pane -D 68 | bind k select-pane -U 69 | bind l select-pane -R 70 | 71 | bind -T copy-mode-vi v send -X begin-selection 72 | bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy" 73 | bind P paste-buffer 74 | bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy" 75 | setw -g mode-keys vi 76 | 77 | # If this is your first time using tmux, you will need to press (Ctrl + Space) + I to fetch the plugins. 78 | run '~/.config/tmux/plugins/tpm/tpm' 79 | 80 | #bind O display-popup -E -w 90% -h 90% "opencode #{pane_current_path}" 81 | 82 | 83 | # Create or toggle the "opencode" window 84 | bind-key O run-shell " \ 85 | if tmux list-windows | grep -q 'OpenCodeWindow'; then \ 86 | if [ \"\$(tmux display-message -p '#W')\" = 'OpenCodeWindow' ]; then \ 87 | tmux last-window; \ 88 | else \ 89 | tmux select-window -t ':OpenCodeWindow'; \ 90 | fi; \ 91 | else \ 92 | tmux new-window -n OpenCodeWindow 'opencode .'; \ 93 | fi" 94 | -------------------------------------------------------------------------------- /.config/fish/functions/zoxie.fish: -------------------------------------------------------------------------------- 1 | 2 | # ============================================================================= 3 | # 4 | # Utility functions for zoxide. 5 | # 6 | 7 | # pwd based on the value of _ZO_RESOLVE_SYMLINKS. 8 | function __zoxide_pwd 9 | builtin pwd -L 10 | end 11 | 12 | # A copy of fish's internal cd function. This makes it possible to use 13 | # `alias cd=z` without causing an infinite loop. 14 | if ! builtin functions --query __zoxide_cd_internal 15 | string replace --regex -- '^function cd\s' 'function __zoxide_cd_internal ' <$__fish_data_dir/functions/cd.fish | source 16 | end 17 | 18 | # cd + custom logic based on the value of _ZO_ECHO. 19 | function __zoxide_cd 20 | if set -q __zoxide_loop 21 | builtin echo "zoxide: infinite loop detected" 22 | builtin echo "Avoid aliasing `cd` to `z` directly, use `zoxide init --cmd=cd fish` instead" 23 | return 1 24 | end 25 | __zoxide_loop=1 __zoxide_cd_internal $argv 26 | end 27 | 28 | # ============================================================================= 29 | # 30 | # Hook configuration for zoxide. 31 | # 32 | 33 | # Initialize hook to add new entries to the database. 34 | function __zoxide_hook --on-variable PWD 35 | test -z "$fish_private_mode" 36 | and command zoxide add -- (__zoxide_pwd) 37 | end 38 | 39 | # ============================================================================= 40 | # 41 | # When using zoxide with --no-cmd, alias these internal functions as desired. 42 | # 43 | 44 | # Jump to a directory using only keywords. 45 | function __zoxide_z 46 | set -l argc (builtin count $argv) 47 | if test $argc -eq 0 48 | __zoxide_cd $HOME 49 | else if test "$argv" = - 50 | __zoxide_cd - 51 | else if test $argc -eq 1 -a -d $argv[1] 52 | __zoxide_cd $argv[1] 53 | else if test $argc -eq 2 -a $argv[1] = -- 54 | __zoxide_cd -- $argv[2] 55 | else 56 | set -l result (command zoxide query --exclude (__zoxide_pwd) -- $argv) 57 | and __zoxide_cd $result 58 | end 59 | end 60 | 61 | # Completions. 62 | function __zoxide_z_complete 63 | set -l tokens (builtin commandline --current-process --tokenize) 64 | set -l curr_tokens (builtin commandline --cut-at-cursor --current-process --tokenize) 65 | 66 | if test (builtin count $tokens) -le 2 -a (builtin count $curr_tokens) -eq 1 67 | # If there are < 2 arguments, use `cd` completions. 68 | complete --do-complete "'' "(builtin commandline --cut-at-cursor --current-token) | string match --regex -- '.*/$' 69 | else if test (builtin count $tokens) -eq (builtin count $curr_tokens) 70 | # If the last argument is empty, use interactive selection. 71 | set -l query $tokens[2..-1] 72 | set -l result (command zoxide query --exclude (__zoxide_pwd) --interactive -- $query) 73 | and __zoxide_cd $result 74 | and builtin commandline --function cancel-commandline repaint 75 | end 76 | end 77 | complete --command __zoxide_z --no-files --arguments '(__zoxide_z_complete)' 78 | 79 | # Jump to a directory using interactive search. 80 | function __zoxide_zi 81 | set -l result (command zoxide query --interactive -- $argv) 82 | and __zoxide_cd $result 83 | end 84 | 85 | # ============================================================================= 86 | # 87 | # Commands for zoxide. Disable these using --no-cmd. 88 | # 89 | 90 | abbr --erase z &>/dev/null 91 | alias z=__zoxide_z 92 | 93 | abbr --erase zi &>/dev/null 94 | alias zi=__zoxide_zi 95 | -------------------------------------------------------------------------------- /.config/waybar/config: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "layer": "top", 4 | "position": "top", 5 | "margin-bottom": 0, 6 | "modules-left": [ 7 | "hyprland/workspaces", 8 | // "custom/updates", 9 | "tray", 10 | ], 11 | "modules-center": [ 12 | "hyprland/window", 13 | ], 14 | "modules-right": [ 15 | "clock", 16 | // "network", 17 | "temperature", 18 | "battery", 19 | "memory", 20 | "cpu", 21 | "pulseaudio", 22 | "custom/powermenu", 23 | ], 24 | "tray": { 25 | "icon-size": 24, 26 | "spacing": 10 27 | }, 28 | "hyprland/window": { 29 | "format": " {}", 30 | "rewrite": { 31 | "(\\d\\. )?(.*) - Chromium": " $2 ", 32 | "nvim (.*)": " $1", 33 | "nvim": " Neovim ", 34 | "ranger(.*)": " $1", 35 | //"kitty (.*)": " $1", 36 | "Search(.*)": " Search$1", 37 | }, 38 | "separate-outputs": false 39 | }, 40 | "hyprland/submap": { 41 | "format": "✌️ {}", 42 | "max-length": 8, 43 | "tooltip": true 44 | }, 45 | "hyprland/workspaces": { 46 | "format": "{icon}", 47 | "format-icons": { 48 | "1": "󰲠", 49 | "2": "󰲢", 50 | "3": "󰲤", 51 | "4": "󰲦", 52 | "5": "󰲨", 53 | "6": "󰲪", 54 | "7": "󰲬", 55 | "8": "󰲮", 56 | "9": "󰲰", 57 | "10": "󰿬", 58 | "-99": "", 59 | "active": "", 60 | "default": "" 61 | } 62 | }, 63 | "network": { 64 | "interface": "enp34s0", 65 | "tooltip-format-ethernet": "{ifname} ", 66 | "interval": 2, 67 | "format": " {bandwidthDownBits}  {bandwidthUpBits}", 68 | }, 69 | "temperature": { 70 | "format": "{icon} {temperatureC}°C", 71 | "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input", 72 | "critical-threshold": 80, 73 | "format-icons": ["", "", ""] 74 | }, 75 | "memory": { 76 | "format": "󰍛 {}%", 77 | "tooltip": "false" 78 | }, 79 | "battery": { 80 | "states": { 81 | "good": 95, 82 | "warning": 30, 83 | "critical": 15 84 | }, 85 | "format": "{icon} {capacity}%", 86 | "format-charging": "󰂄 {capacity}%", 87 | "format-plugged": "󰚥 {capacity}%", 88 | "format-alt": "{icon} {time}", 89 | "format-icons": ["󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"] 90 | }, 91 | "cpu": { 92 | "format": " {usage}%", 93 | "tooltip": "false" 94 | }, 95 | "custom/powermenu": { 96 | "format": "", 97 | "tooltip": false, 98 | "on-click": "exec wlogout -p layer-shell", 99 | }, 100 | "custom/updates": { 101 | "format": " {}", 102 | "tooltip": false, 103 | "interval": 3600, 104 | "exec": "exec $HOME/.config/waybar/checkupdate.sh" 105 | }, 106 | "pulseaudio": { 107 | "format": "{volume}% {icon}", 108 | "format-bluetooth": "{volume}% {icon}", 109 | "format-muted": "", 110 | "format-icons": { 111 | "headphone": "", 112 | "hands-free": "", 113 | "headset": "", 114 | "phone": "", 115 | "portable": "", 116 | "car": "", 117 | "default": ["", ""] 118 | }, 119 | "scroll-step": 1, 120 | "on-click": "pavucontrol" 121 | }, 122 | "clock": { 123 | "format": "{:%d %B %H:%M}", 124 | "interval": 60, 125 | "tooltip": true, 126 | "tooltip-format": "{:%d %B %H:%M}" 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Dotfiles installation script for devcontainers 3 | # This script is automatically run by VS Code when the devcontainer is created 4 | 5 | set -e 6 | 7 | DOTFILES_DIR="$HOME/dotfiles" 8 | CONFIG_DIR="$HOME/.config" 9 | 10 | echo "Installing dotfiles from $DOTFILES_DIR..." 11 | 12 | # Symlink .gitconfig if it exists and isn't already mounted 13 | if [ -f "$DOTFILES_DIR/.gitconfig" ] && [ ! -f "$HOME/.gitconfig" ]; then 14 | echo "Symlinking .gitconfig..." 15 | ln -sf "$DOTFILES_DIR/.gitconfig" "$HOME/.gitconfig" 16 | fi 17 | 18 | # Create .config directory if it doesn't exist 19 | mkdir -p "$CONFIG_DIR" 20 | 21 | # Symlink .config directories (skip if already exists) 22 | for config_dir in "$DOTFILES_DIR/.config"/*; do 23 | if [ -d "$config_dir" ]; then 24 | dir_name=$(basename "$config_dir") 25 | target="$CONFIG_DIR/$dir_name" 26 | 27 | # Special handling for fish config - we need to merge with existing config 28 | if [ "$dir_name" = "fish" ]; then 29 | echo "Setting up fish configuration..." 30 | 31 | # Backup existing config if it exists 32 | if [ -f "$CONFIG_DIR/fish/config.fish" ]; then 33 | echo "Backing up existing fish config..." 34 | cp "$CONFIG_DIR/fish/config.fish" "$CONFIG_DIR/fish/config.fish.backup" 35 | fi 36 | 37 | # Create fish config directory 38 | mkdir -p "$CONFIG_DIR/fish" 39 | 40 | # Copy dotfiles fish config 41 | cp -r "$config_dir"/* "$CONFIG_DIR/fish/" 2>/dev/null || true 42 | 43 | # Prepend mise activation to fish config (must run before starship/zoxide) 44 | if [ -f "$CONFIG_DIR/fish/config.fish" ]; then 45 | echo "Adding mise activation to fish config..." 46 | cat > "$CONFIG_DIR/fish/config.fish.tmp" << 'EOF' 47 | # mise activation (added by devcontainer) 48 | if status is-interactive 49 | /usr/local/bin/mise activate fish | source 50 | end 51 | 52 | EOF 53 | cat "$CONFIG_DIR/fish/config.fish" >> "$CONFIG_DIR/fish/config.fish.tmp" 54 | mv "$CONFIG_DIR/fish/config.fish.tmp" "$CONFIG_DIR/fish/config.fish" 55 | fi 56 | 57 | # Symlink other fish directories 58 | for subdir in "$config_dir"/*; do 59 | if [ -d "$subdir" ]; then 60 | subdir_name=$(basename "$subdir") 61 | if [ ! -e "$CONFIG_DIR/fish/$subdir_name" ]; then 62 | ln -sf "$subdir" "$CONFIG_DIR/fish/$subdir_name" 63 | fi 64 | fi 65 | done 66 | else 67 | # For other configs, just symlink the entire directory 68 | if [ ! -e "$target" ]; then 69 | echo "Symlinking $dir_name..." 70 | ln -sf "$config_dir" "$target" 71 | else 72 | echo "Skipping $dir_name (already exists)..." 73 | fi 74 | fi 75 | fi 76 | done 77 | 78 | # Symlink starship config if it exists 79 | if [ -f "$DOTFILES_DIR/.config/starship.toml" ]; then 80 | echo "Symlinking starship.toml..." 81 | ln -sf "$DOTFILES_DIR/.config/starship.toml" "$CONFIG_DIR/starship.toml" 82 | fi 83 | 84 | # Symlink bin directory 85 | if [ -d "$DOTFILES_DIR/bin" ]; then 86 | echo "Symlinking bin directory..." 87 | mkdir -p "$HOME/bin" 88 | for script in "$DOTFILES_DIR/bin"/*; do 89 | if [ -f "$script" ]; then 90 | script_name=$(basename "$script") 91 | ln -sf "$script" "$HOME/bin/$script_name" 92 | fi 93 | done 94 | 95 | # Add bin to PATH in fish config if not already there 96 | if [ -f "$CONFIG_DIR/fish/config.fish" ]; then 97 | if ! grep -q "fish_add_path ~/bin" "$CONFIG_DIR/fish/config.fish"; then 98 | echo "fish_add_path ~/bin" >> "$CONFIG_DIR/fish/config.fish" 99 | fi 100 | fi 101 | fi 102 | 103 | echo "Dotfiles installation complete!" 104 | -------------------------------------------------------------------------------- /.config/hypr/hyprland/rules.conf: -------------------------------------------------------------------------------- 1 | # ######## Window rules ######## 2 | 3 | # Uncomment to apply global transparency to all windows: 4 | # windowrulev2 = opacity 0.89 override 0.89 override, class:.* 5 | 6 | # Disable blur for XWayland windows (or context menus with shadow would look weird) 7 | windowrulev2 = noblur, xwayland:1 8 | 9 | # Floating 10 | windowrulev2 = float, class:^(blueberry\.py)$ 11 | windowrulev2 = float, class:^(steam)$ 12 | windowrulev2 = float, class:^(guifetch)$ # FlafyDev/guifetch 13 | windowrulev2 = float, class:^(pavucontrol)$ 14 | windowrulev2 = size 45%, class:^(pavucontrol)$ 15 | windowrulev2 = center, class:^(pavucontrol)$ 16 | windowrulev2 = float, class:^(org.pulseaudio.pavucontrol)$ 17 | windowrulev2 = size 45%, class:^(org.pulseaudio.pavucontrol)$ 18 | windowrulev2 = center, class:^(org.pulseaudio.pavucontrol)$ 19 | windowrulev2 = float, class:^(nm-connection-editor)$ 20 | windowrulev2 = size 45%, class:^(nm-connection-editor)$ 21 | windowrulev2 = center, class:^(nm-connection-editor)$ 22 | 23 | # Tiling 24 | windowrulev2 = tile, class:^dev\.warp\.Warp$ 25 | 26 | # Picture-in-Picture 27 | windowrulev2 = float, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ 28 | windowrulev2 = keepaspectratio, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ 29 | windowrulev2 = move 73% 72%, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ 30 | windowrulev2 = size 25%, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ 31 | windowrulev2 = float, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ 32 | windowrulev2 = pin, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ 33 | 34 | # Dialog windows – float+center these windows. 35 | windowrulev2 = center, title:^(Open File)(.*)$ 36 | windowrulev2 = center, title:^(Select a File)(.*)$ 37 | windowrulev2 = center, title:^(Choose wallpaper)(.*)$ 38 | windowrulev2 = center, title:^(Open Folder)(.*)$ 39 | windowrulev2 = center, title:^(Save As)(.*)$ 40 | windowrulev2 = center, title:^(Library)(.*)$ 41 | windowrulev2 = center, title:^(File Upload)(.*)$ 42 | windowrulev2 = float, title:^(Open File)(.*)$ 43 | windowrulev2 = float, title:^(Select a File)(.*)$ 44 | windowrulev2 = float, title:^(Choose wallpaper)(.*)$ 45 | windowrulev2 = float, title:^(Open Folder)(.*)$ 46 | windowrulev2 = float, title:^(Save As)(.*)$ 47 | windowrulev2 = float, title:^(Library)(.*)$ 48 | windowrulev2 = float, title:^(File Upload)(.*)$ 49 | 50 | 51 | # --- Tearing --- 52 | windowrulev2 = immediate, title:.*\.exe 53 | windowrulev2 = immediate, class:^(steam_app) 54 | 55 | # No shadow for tiled windows (matches windows that are not floating). 56 | windowrulev2 = noshadow, floating:0 57 | 58 | # ######## Workspace rules ######## 59 | workspace = special:special, gapsout:30 60 | 61 | # ######## Layer rules ######## 62 | layerrule = xray 1, .* 63 | # layerrule = noanim, .* 64 | layerrule = noanim, walker 65 | layerrule = noanim, selection 66 | layerrule = noanim, overview 67 | layerrule = noanim, anyrun 68 | layerrule = noanim, indicator.* 69 | layerrule = noanim, osk 70 | layerrule = noanim, hyprpicker 71 | 72 | layerrule = noanim, noanim 73 | layerrule = blur, gtk-layer-shell 74 | layerrule = ignorezero, gtk-layer-shell 75 | layerrule = blur, launcher 76 | layerrule = ignorealpha 0.5, launcher 77 | layerrule = blur, notifications 78 | layerrule = ignorealpha 0.69, notifications 79 | layerrule = blur, logout_dialog # wlogout 80 | 81 | # ags 82 | layerrule = animation slide left, sideleft.* 83 | layerrule = animation slide right, sideright.* 84 | layerrule = blur, session[0-9]* 85 | layerrule = blur, bar[0-9]* 86 | layerrule = ignorealpha 0.6, bar[0-9]* 87 | layerrule = blur, barcorner.* 88 | layerrule = ignorealpha 0.6, barcorner.* 89 | layerrule = blur, dock[0-9]* 90 | layerrule = ignorealpha 0.6, dock[0-9]* 91 | layerrule = blur, indicator.* 92 | layerrule = ignorealpha 0.6, indicator.* 93 | layerrule = blur, overview[0-9]* 94 | layerrule = ignorealpha 0.6, overview[0-9]* 95 | layerrule = blur, cheatsheet[0-9]* 96 | layerrule = ignorealpha 0.6, cheatsheet[0-9]* 97 | layerrule = blur, sideright[0-9]* 98 | layerrule = ignorealpha 0.6, sideright[0-9]* 99 | layerrule = blur, sideleft[0-9]* 100 | layerrule = ignorealpha 0.6, sideleft[0-9]* 101 | layerrule = blur, indicator.* 102 | layerrule = ignorealpha 0.6, indicator.* 103 | layerrule = blur, osk[0-9]* 104 | layerrule = ignorealpha 0.6, osk[0-9]* 105 | -------------------------------------------------------------------------------- /.config/starship.toml: -------------------------------------------------------------------------------- 1 | [aws] 2 | format = '\[[$symbol($profile)(\($region\))(\[$duration\])]($style)\]' 3 | 4 | [bun] 5 | format = '\[[$symbol($version)]($style)\]' 6 | 7 | [c] 8 | format = '\[[$symbol($version(-$name))]($style)\]' 9 | 10 | [cmake] 11 | format = '\[[$symbol($version)]($style)\]' 12 | 13 | [cmd_duration] 14 | format = '\[[⏱ $duration]($style)\]' 15 | 16 | [cobol] 17 | format = '\[[$symbol($version)]($style)\]' 18 | 19 | [conda] 20 | format = '\[[$symbol$environment]($style)\]' 21 | 22 | [crystal] 23 | format = '\[[$symbol($version)]($style)\]' 24 | 25 | [daml] 26 | format = '\[[$symbol($version)]($style)\]' 27 | 28 | [dart] 29 | format = '\[[$symbol($version)]($style)\]' 30 | 31 | [deno] 32 | format = '\[[$symbol($version)]($style)\]' 33 | 34 | [docker_context] 35 | format = '\[[$symbol$context]($style)\]' 36 | 37 | [dotnet] 38 | format = '\[[$symbol($version)(🎯 $tfm)]($style)\]' 39 | 40 | [elixir] 41 | format = '\[[$symbol($version \(OTP $otp_version\))]($style)\]' 42 | 43 | [elm] 44 | format = '\[[$symbol($version)]($style)\]' 45 | 46 | [erlang] 47 | format = '\[[$symbol($version)]($style)\]' 48 | 49 | [fennel] 50 | format = '\[[$symbol($version)]($style)\]' 51 | 52 | [fossil_branch] 53 | format = '\[[$symbol$branch]($style)\]' 54 | 55 | [gcloud] 56 | format = '\[[$symbol$account(@$domain)(\($region\))]($style)\]' 57 | 58 | [git_branch] 59 | format = '\[[$symbol$branch]($style)\]' 60 | 61 | [git_status] 62 | format = '([\[$all_status$ahead_behind\]]($style))' 63 | 64 | [golang] 65 | format = '\[[$symbol($version)]($style)\]' 66 | 67 | [gradle] 68 | format = '\[[$symbol($version)]($style)\]' 69 | 70 | [guix_shell] 71 | format = '\[[$symbol]($style)\]' 72 | 73 | [haskell] 74 | format = '\[[$symbol($version)]($style)\]' 75 | 76 | [haxe] 77 | format = '\[[$symbol($version)]($style)\]' 78 | 79 | [helm] 80 | format = '\[[$symbol($version)]($style)\]' 81 | 82 | [hg_branch] 83 | format = '\[[$symbol$branch]($style)\]' 84 | 85 | [java] 86 | format = '\[[$symbol($version)]($style)\]' 87 | 88 | [julia] 89 | format = '\[[$symbol($version)]($style)\]' 90 | 91 | [kotlin] 92 | format = '\[[$symbol($version)]($style)\]' 93 | 94 | [kubernetes] 95 | format = '\[[$symbol$context( \($namespace\))]($style)\]' 96 | 97 | [lua] 98 | format = '\[[$symbol($version)]($style)\]' 99 | 100 | [memory_usage] 101 | format = '\[$symbol[$ram( | $swap)]($style)\]' 102 | 103 | [meson] 104 | format = '\[[$symbol$project]($style)\]' 105 | 106 | [nim] 107 | format = '\[[$symbol($version)]($style)\]' 108 | 109 | [nix_shell] 110 | format = '\[[$symbol$state( \($name\))]($style)\]' 111 | 112 | [nodejs] 113 | format = '\[[$symbol($version)]($style)\]' 114 | 115 | [ocaml] 116 | format = '\[[$symbol($version)(\($switch_indicator$switch_name\))]($style)\]' 117 | 118 | [opa] 119 | format = '\[[$symbol($version)]($style)\]' 120 | 121 | [openstack] 122 | format = '\[[$symbol$cloud(\($project\))]($style)\]' 123 | 124 | [os] 125 | format = '\[[$symbol]($style)\]' 126 | 127 | [package] 128 | format = '\[[$symbol$version]($style)\]' 129 | 130 | [perl] 131 | format = '\[[$symbol($version)]($style)\]' 132 | 133 | [php] 134 | format = '\[[$symbol($version)]($style)\]' 135 | 136 | [pijul_channel] 137 | format = '\[[$symbol$channel]($style)\]' 138 | 139 | [pulumi] 140 | format = '\[[$symbol$stack]($style)\]' 141 | 142 | [purescript] 143 | format = '\[[$symbol($version)]($style)\]' 144 | 145 | [python] 146 | format = '\[[${symbol}${pyenv_prefix}(${version})(\($virtualenv\))]($style)\]' 147 | 148 | [raku] 149 | format = '\[[$symbol($version-$vm_version)]($style)\]' 150 | 151 | [red] 152 | format = '\[[$symbol($version)]($style)\]' 153 | 154 | [ruby] 155 | format = '\[[$symbol($version)]($style)\]' 156 | 157 | [rust] 158 | format = '\[[$symbol($version)]($style)\]' 159 | 160 | [scala] 161 | format = '\[[$symbol($version)]($style)\]' 162 | 163 | [spack] 164 | format = '\[[$symbol$environment]($style)\]' 165 | 166 | [sudo] 167 | format = '\[[as $symbol]($style)\]' 168 | 169 | [swift] 170 | format = '\[[$symbol($version)]($style)\]' 171 | 172 | [terraform] 173 | format = '\[[$symbol$workspace]($style)\]' 174 | 175 | [time] 176 | format = '\[[$time]($style)\]' 177 | 178 | [username] 179 | format = '\[[$user]($style)\]' 180 | 181 | [vagrant] 182 | format = '\[[$symbol($version)]($style)\]' 183 | 184 | [vlang] 185 | format = '\[[$symbol($version)]($style)\]' 186 | 187 | [zig] 188 | format = '\[[$symbol($version)]($style)\]' 189 | 190 | [solidity] 191 | format = '\[[$symbol($version)]($style)\]' 192 | -------------------------------------------------------------------------------- /.config/tiny_nvim/init.lua: -------------------------------------------------------------------------------- 1 | vim.opt.number = true 2 | vim.opt.winborder = "rounded" 3 | vim.opt.swapfile = false 4 | vim.opt.wrap = false 5 | --vim.opt.tabstop = 2 6 | --vim.opt.shiftwidth = 2 7 | --vim.opt.expandtab 8 | vim.opt.relativenumber = true 9 | vim.opt.signcolumn = "yes" 10 | vim.opt.path = "**" 11 | vim.opt.mouse = "" 12 | vim.g.mapleader = "," 13 | vim.g.fugitive_default_split = 'edit' 14 | vim.opt.clipboard = "unnamedplus" 15 | 16 | vim.pack.add({ 17 | { src = "https://github.com/stevearc/oil.nvim" }, 18 | { src = "https://github.com/mason-org/mason.nvim" }, 19 | { src = "https://github.com/mason-org/mason-lspconfig.nvim" }, 20 | { src = "https://github.com/github/copilot.vim" }, 21 | { src = "https://github.com/nvim-lua/plenary.nvim" }, 22 | { src = "https://github.com/tpope/vim-rails" }, 23 | { src = "https://github.com/nvim-treesitter/nvim-treesitter" }, 24 | { src = "https://github.com/tpope/vim-fugitive" }, 25 | { src = "https://github.com/rose-pine/neovim" }, 26 | { src = "https://github.com/xiyaowong/transparent.nvim" }, 27 | { src = 'https://github.com/neovim/nvim-lspconfig' }, 28 | { src = "https://github.com/echasnovski/mini.nvim" }, 29 | { src = "https://github.com/tpope/vim-sensible"}, 30 | { src = "https://github.com/folke/trouble.nvim" }, 31 | { src = "https://github.com/nvim-pack/nvim-spectre" }, 32 | { src = "https://github.com/ThePrimeagen/harpoon" }, 33 | { src = "https://github.com/nvim-neotest/neotest" }, 34 | { src = "https://github.com/zidhuss/neotest-minitest" }, 35 | { src = "https://github.com/nvim-neotest/nvim-nio" }, 36 | { src = "https://github.com/christoomey/vim-tmux-navigator" }, 37 | }) 38 | 39 | require "oil".setup() 40 | require "mason".setup() 41 | require("mason-lspconfig").setup({ 42 | ensure_installed = { "lua_ls", "ts_ls", "ruby_lsp" }, 43 | }) 44 | require("nvim-treesitter.configs").setup({ 45 | ensure_installed = { "lua", "ruby", "javascript" }, 46 | highlight = { enable = true }, 47 | }) 48 | require "transparent".setup() 49 | require("mini.pick").setup({ 50 | source = { 51 | files = { preview = true }, 52 | grep_live = { preview = true }, 53 | }, 54 | }) 55 | require "trouble".setup() 56 | require "spectre".setup() 57 | require "harpoon".setup() 58 | require("neotest").setup({ 59 | adapters = { 60 | require("neotest-minitest"), 61 | }, 62 | output_panel = { 63 | enabled = true, 64 | open = "botright split | resize 15", 65 | }, 66 | }) 67 | 68 | 69 | require "mini.ai".setup() 70 | require "mini.align".setup() 71 | require "mini.comment".setup() 72 | require "mini.completion".setup() 73 | require "mini.move".setup() 74 | require "mini.operators".setup() 75 | require "mini.pairs".setup() 76 | require "mini.snippets".setup() 77 | require "mini.surround".setup() 78 | require "mini.basics".setup() 79 | require "mini.bracketed".setup() 80 | require "mini.bufremove".setup() 81 | require "mini.diff".setup() 82 | require "mini.extra".setup() 83 | require "mini.git".setup() 84 | require "mini.sessions".setup() 85 | require "mini.indentscope".setup() 86 | require "mini.trailspace".setup() 87 | 88 | 89 | vim.keymap.set("n", "f", ":Pick files") 90 | vim.keymap.set("n", "b", ":Pick buffers") 91 | vim.keymap.set("n", "s", ":Pick grep_live") 92 | vim.keymap.set("n", "p", ":TypstPreview") 93 | vim.keymap.set("n", "h", ":Pick help") 94 | vim.keymap.set("n", "e", ":Oil ") 95 | vim.keymap.set("n", "E", ":e ~/.config/nvim/init.lua ") 96 | 97 | vim.keymap.set("n", "lf", vim.lsp.buf.format) 98 | vim.keymap.set("n", "S", 'lua require("spectre").open()') 99 | vim.keymap.set("n", "xx", "Trouble diagnostics toggle") 100 | vim.keymap.set("n", "gd", vim.lsp.buf.definition) 101 | vim.keymap.set("n", "gD", vim.lsp.buf.declaration) 102 | vim.keymap.set("n", "gr", vim.lsp.buf.references) 103 | vim.keymap.set("n", "gi", vim.lsp.buf.implementation) 104 | vim.keymap.set("n", "K", vim.lsp.buf.hover) 105 | vim.keymap.set("n", "rn", vim.lsp.buf.rename) 106 | vim.keymap.set("n", "ca", vim.lsp.buf.code_action) 107 | vim.keymap.set("n", "[d", vim.diagnostic.goto_prev) 108 | vim.keymap.set("n", "]d", vim.diagnostic.goto_next) 109 | vim.keymap.set("n", "q", vim.diagnostic.setloclist) 110 | vim.keymap.set("n", "a", require("harpoon.mark").add_file) 111 | vim.keymap.set("n", "A", require("harpoon.ui").toggle_quick_menu) 112 | vim.keymap.set("n", "tt", require("neotest").run.run) 113 | vim.keymap.set("n", "tf", function() require("neotest").run.run(vim.fn.expand("%")) end) 114 | vim.keymap.set("n", "ts", require("neotest").run.stop) 115 | vim.keymap.set("n", "to", require("neotest").output_panel.toggle) 116 | vim.keymap.set("n", ";", ":") 117 | 118 | vim.cmd([[colorscheme rose-pine]]) 119 | vim.cmd([[hi statusline guibg=NONE]]) 120 | 121 | 122 | vim.keymap.set('n', 'U', function() 123 | vim.pack.update() 124 | end, { desc = 'Update plugins' }) 125 | -------------------------------------------------------------------------------- /.config/hypr/hyprland/general.conf: -------------------------------------------------------------------------------- 1 | # MONITOR CONFIG 2 | #monitor=,preferred,auto,1.25 3 | monitor=,highres, auto, 1.33333 4 | # monitor=,addreserved, 0, 0, 0, 0 # Custom reserved area 5 | 6 | # HDMI port: mirror display. To see device name, use `hyprctl monitors` 7 | # monitor=HDMI-A-1,1920x1080@60,1920x0,1,mirror,eDP-1 8 | 9 | input { 10 | # Keyboard: Add a layout and uncomment kb_options for Win+Space switching shortcut 11 | kb_layout = pl #us 12 | kb_options = grp:win_space_toggle 13 | numlock_by_default = true 14 | repeat_delay = 250 15 | repeat_rate = 35 16 | 17 | touchpad { 18 | natural_scroll = yes 19 | disable_while_typing = true 20 | clickfinger_behavior = true 21 | scroll_factor = 0.5 22 | } 23 | special_fallthrough = true 24 | follow_mouse = 1 25 | } 26 | 27 | binds { 28 | # focus_window_on_workspace_c# For Auto-run stuff see execs.confhange = true 29 | scroll_event_delay = 0 30 | } 31 | 32 | gestures { 33 | workspace_swipe = true 34 | workspace_swipe_distance = 700 35 | workspace_swipe_fingers = 4 36 | workspace_swipe_cancel_ratio = 0.2 37 | workspace_swipe_min_speed_to_force = 5 38 | workspace_swipe_direction_lock = true 39 | workspace_swipe_direction_lock_threshold = 10 40 | workspace_swipe_create_new = true 41 | } 42 | 43 | general { 44 | # Gaps and border 45 | gaps_in = 2 46 | gaps_out = 3 47 | gaps_workspaces = 50 48 | border_size = 1 49 | 50 | # Fallback colors 51 | col.active_border = rgba(0DB7D4FF) 52 | col.inactive_border = rgba(31313600) 53 | 54 | resize_on_border = true 55 | no_focus_fallback = true 56 | layout = master # dwindle 57 | 58 | #focus_to_other_workspaces = true # ahhhh i still haven't properly implemented this 59 | allow_tearing = true # This just allows the `immediate` window rule to work 60 | } 61 | 62 | dwindle { 63 | preserve_split = true 64 | smart_split = false 65 | smart_resizing = false 66 | } 67 | 68 | decoration { 69 | rounding = 20 70 | 71 | blur { 72 | enabled = true 73 | xray = true 74 | special = false 75 | new_optimizations = true 76 | size = 14 77 | passes = 4 78 | brightness = 1 79 | noise = 0.01 80 | contrast = 1 81 | popups = true 82 | popups_ignorealpha = 0.6 83 | } 84 | 85 | # Window Opacities 86 | # active_opacity = 1 87 | # inactive_opacity = 1 88 | # fullscreen_opacity = 1 89 | 90 | # Shader 91 | # screen_shader = ~/.config/hypr/shaders/nothing.frag 92 | # screen_shader = ~/.config/hypr/shaders/vibrance.frag 93 | 94 | # Dim 95 | dim_inactive = false 96 | dim_strength = 0.1 97 | dim_special = 0 98 | } 99 | 100 | animations { 101 | enabled = false 102 | # Animation curves 103 | 104 | bezier = linear, 0, 0, 1, 1 105 | bezier = md3_standard, 0.2, 0, 0, 1 106 | bezier = md3_decel, 0.05, 0.7, 0.1, 1 107 | bezier = md3_accel, 0.3, 0, 0.8, 0.15 108 | bezier = overshot, 0.05, 0.9, 0.1, 1.1 109 | bezier = crazyshot, 0.1, 1.5, 0.76, 0.92 110 | bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0 111 | bezier = menu_decel, 0.1, 1, 0, 1 112 | bezier = menu_accel, 0.38, 0.04, 1, 0.07 113 | bezier = easeInOutCirc, 0.85, 0, 0.15, 1 114 | bezier = easeOutCirc, 0, 0.55, 0.45, 1 115 | bezier = easeOutExpo, 0.16, 1, 0.3, 1 116 | bezier = softAcDecel, 0.26, 0.26, 0.15, 1 117 | bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration 118 | # Animation configs 119 | animation = windows, 1, 3, md3_decel, popin 60% 120 | animation = windowsIn, 1, 3, md3_decel, popin 60% 121 | animation = windowsOut, 1, 3, md3_accel, popin 60% 122 | animation = border, 1, 10, default 123 | animation = fade, 1, 3, md3_decel 124 | # animation = layers, 1, 2, md3_decel, slide 125 | animation = layersIn, 1, 3, menu_decel, slide 126 | animation = layersOut, 1, 1.6, menu_accel 127 | animation = fadeLayersIn, 1, 2, menu_decel 128 | animation = fadeLayersOut, 1, 0.5, menu_accel 129 | animation = workspaces, 1, 7, menu_decel, slide 130 | # animation = workspaces, 1, 2.5, softAcDecel, slide 131 | # animation = workspaces, 1, 7, menu_decel, slidefade 15% 132 | # animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15% 133 | animation = specialWorkspace, 1, 3, md3_decel, slidevert 134 | } 135 | 136 | misc { 137 | vfr = 1 138 | vrr = 1 139 | animate_manual_resizes = false 140 | animate_mouse_windowdragging = false 141 | enable_swallow = false 142 | swallow_regex = (foot|kitty|allacritty|Alacritty) 143 | 144 | disable_hyprland_logo = true 145 | force_default_wallpaper = 1 146 | new_window_takes_over_fullscreen = 2 147 | allow_session_lock_restore = true 148 | 149 | initial_workspace_tracking = false 150 | } 151 | 152 | # Overview 153 | plugin { 154 | hyprexpo { 155 | columns = 3 156 | gap_size = 5 157 | bg_col = rgb(000000) 158 | workspace_method = first 1 # [center/first] [workspace] e.g. first 1 or center m+1 159 | 160 | enable_gesture = false # laptop touchpad, 4 fingers 161 | gesture_distance = 300 # how far is the "max" 162 | gesture_positive = false 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /.config/nvim/lazy-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "CopilotChat.nvim": { "branch": "main", "commit": "df5376c132382dd47e3e552612940cbf25b3580c" }, 3 | "LazyVim": { "branch": "main", "commit": "28db03f958d58dfff3c647ce28fdc1cb88ac158d" }, 4 | "LuaSnip": { "branch": "master", "commit": "3732756842a2f7e0e76a7b0487e9692072857277" }, 5 | "SchemaStore.nvim": { "branch": "main", "commit": "a025c78b147affef1aabb8aa360c92be2e0377a0" }, 6 | "blink.cmp": { "branch": "main", "commit": "b19413d214068f316c78978b08264ed1c41830ec" }, 7 | "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, 8 | "catppuccin": { "branch": "main", "commit": "193e123cdbc4dd3e86db883d55349e9587f0ded6" }, 9 | "conform.nvim": { "branch": "master", "commit": "ffe26e8df8115c9665d24231f8a49fadb2d611ce" }, 10 | "copilot.vim": { "branch": "release", "commit": "f89e977c87180519ba3b942200e3d05b17b1e2fc" }, 11 | "dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" }, 12 | "flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" }, 13 | "friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" }, 14 | "gf-override.nvim": { "branch": "main", "commit": "b723a8a3fa5a73fee0c0523fd27efa01f844d735" }, 15 | "grug-far.nvim": { "branch": "main", "commit": "b58b2d65863f4ebad88b10a1ddd519e5380466e0" }, 16 | "harpoon": { "branch": "harpoon2", "commit": "87b1a3506211538f460786c23f98ec63ad9af4e5" }, 17 | "kulala.nvim": { "branch": "main", "commit": "b36aff673915391e415d4acc7c90ca6ca6891750" }, 18 | "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" }, 19 | "lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" }, 20 | "lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" }, 21 | "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" }, 22 | "mason-lspconfig.nvim": { "branch": "main", "commit": "c55bd8a8fb191e24176c206a7af1dd51ce7276a5" }, 23 | "mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" }, 24 | "mason.nvim": { "branch": "main", "commit": "57e5a8addb8c71fb063ee4acda466c7cf6ad2800" }, 25 | "mini.ai": { "branch": "main", "commit": "bfb26d9072670c3aaefab0f53024b2f3729c8083" }, 26 | "mini.comment": { "branch": "main", "commit": "a0c721115faff8d05505c0a12dab410084d9e536" }, 27 | "mini.diff": { "branch": "main", "commit": "fbb93ea1728e7c9d0944df8bd022a68402bd2e7e" }, 28 | "mini.files": { "branch": "main", "commit": "d6422afa153947ab776a243800de79f66c099a8e" }, 29 | "mini.hipatterns": { "branch": "main", "commit": "add8d8abad602787377ec5d81f6b248605828e0f" }, 30 | "mini.icons": { "branch": "main", "commit": "ff2e4f1d29f659cc2bad0f9256f2f6195c6b2428" }, 31 | "mini.pairs": { "branch": "main", "commit": "472ec50092a3314ec285d2db2baa48602d71fe93" }, 32 | "mini.surround": { "branch": "main", "commit": "88c52297ed3e69ecf9f8652837888ecc727a28ee" }, 33 | "neo-tree.nvim": { "branch": "main", "commit": "7a6f14c6edde0921333005cd738309b70138964b" }, 34 | "neogit": { "branch": "master", "commit": "8c75d6d010a5b6d28bc50f2976fe9f341cf268bc" }, 35 | "neotest": { "branch": "master", "commit": "deadfb1af5ce458742671ad3a013acb9a6b41178" }, 36 | "neotest-python": { "branch": "master", "commit": "b0d3a861bd85689d8ed73f0590c47963a7eb1bf9" }, 37 | "neotest-rspec": { "branch": "main", "commit": "e7dc67c1167a9e593c804a6be6808ba9a5920d43" }, 38 | "noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" }, 39 | "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, 40 | "nvim-dap": { "branch": "master", "commit": "5860c7c501eb428d3137ee22c522828d20cca0b3" }, 41 | "nvim-dap-python": { "branch": "master", "commit": "64652d1ae1db80870d9aac7132d76e37acd86a26" }, 42 | "nvim-dap-ruby": { "branch": "main", "commit": "ba36f9905ca9c6d89e5af5467a52fceeb2bbbf6d" }, 43 | "nvim-dap-ui": { "branch": "master", "commit": "cf91d5e2d07c72903d052f5207511bf7ecdb7122" }, 44 | "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" }, 45 | "nvim-lint": { "branch": "master", "commit": "897f7771c1ca4b11659dfe372d9376acd9fe3097" }, 46 | "nvim-lspconfig": { "branch": "master", "commit": "7af6f57d517d8cc68f249e0d27364c188a097812" }, 47 | "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, 48 | "nvim-treesitter": { "branch": "main", "commit": "2979e048b356cfd32dc419d5803dc356b9832adf" }, 49 | "nvim-treesitter-textobjects": { "branch": "main", "commit": "76deedf0f1cec4496ef8d49b6d1f020f6d0c6ec9" }, 50 | "nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" }, 51 | "nvim-ts-context-commentstring": { "branch": "main", "commit": "1b212c2eee76d787bbea6aa5e92a2b534e7b4f8f" }, 52 | "octo.nvim": { "branch": "master", "commit": "fb072b0dd58c467ae81c7bec581fbc8f07418d7c" }, 53 | "oil.nvim": { "branch": "master", "commit": "cbcb3f997f6f261c577b943ec94e4ef55108dd95" }, 54 | "persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" }, 55 | "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, 56 | "refactoring.nvim": { "branch": "master", "commit": "6784b54587e6d8a6b9ea199318512170ffb9e418" }, 57 | "render-markdown.nvim": { "branch": "main", "commit": "26097a4eb95b391d0308c1b77cce89b28bbc9916" }, 58 | "rose-pine": { "branch": "main", "commit": "cf2a288696b03d0934da713d66c6d71557b5c997" }, 59 | "snacks.nvim": { "branch": "main", "commit": "fe7cfe9800a182274d0f868a74b7263b8c0c020b" }, 60 | "telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" }, 61 | "telescope.nvim": { "branch": "master", "commit": "e69b434b968a33815e2f02a5c7bd7b8dd4c7d4b2" }, 62 | "todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" }, 63 | "tokyonight.nvim": { "branch": "main", "commit": "5da1b76e64daf4c5d410f06bcb6b9cb640da7dfd" }, 64 | "transparent.nvim": { "branch": "main", "commit": "8ac59883de84e9cd1850ea25cf087031c5ba7d54" }, 65 | "trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" }, 66 | "ts-comments.nvim": { "branch": "main", "commit": "123a9fb12e7229342f807ec9e6de478b1102b041" }, 67 | "typescript-tools.nvim": { "branch": "master", "commit": "c2f5910074103705661e9651aa841e0d7eea9932" }, 68 | "venv-selector.nvim": { "branch": "main", "commit": "58bae72c84b9f7f864c879ec1896e384296f9ffb" }, 69 | "vim-fugitive": { "branch": "master", "commit": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4" }, 70 | "vim-rails": { "branch": "master", "commit": "b0a5c76f86ea214ade36ab0b811e730c3f0add67" }, 71 | "vim-rhubarb": { "branch": "master", "commit": "5496d7c94581c4c9ad7430357449bb57fc59f501" }, 72 | "vim-tmux-navigator": { "branch": "master", "commit": "c45243dc1f32ac6bcf6068e5300f3b2b237e576a" }, 73 | "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" } 74 | } 75 | -------------------------------------------------------------------------------- /.config/nvim/lua/plugins/example.lua: -------------------------------------------------------------------------------- 1 | -- since this is just an example spec, don't actually load anything here and return an empty spec 2 | -- stylua: ignore 3 | if true then return {} end 4 | 5 | -- every spec file under the "plugins" directory will be loaded automatically by lazy.nvim 6 | -- 7 | -- In your plugin files, you can: 8 | -- * add extra plugins 9 | -- * disable/enabled LazyVim plugins 10 | -- * override the configuration of LazyVim plugins 11 | return { 12 | -- add gruvbox 13 | { "ellisonleao/gruvbox.nvim" }, 14 | 15 | -- Configure LazyVim to load gruvbox 16 | { 17 | "LazyVim/LazyVim", 18 | opts = { 19 | colorscheme = "gruvbox", 20 | }, 21 | }, 22 | 23 | -- change trouble config 24 | { 25 | "folke/trouble.nvim", 26 | -- opts will be merged with the parent spec 27 | opts = { use_diagnostic_signs = true }, 28 | }, 29 | 30 | -- disable trouble 31 | { "folke/trouble.nvim", enabled = false }, 32 | 33 | -- add symbols-outline 34 | { 35 | "simrat39/symbols-outline.nvim", 36 | cmd = "SymbolsOutline", 37 | keys = { { "cs", "SymbolsOutline", desc = "Symbols Outline" } }, 38 | config = true, 39 | }, 40 | 41 | -- override nvim-cmp and add cmp-emoji 42 | { 43 | "hrsh7th/nvim-cmp", 44 | dependencies = { "hrsh7th/cmp-emoji" }, 45 | ---@param opts cmp.ConfigSchema 46 | opts = function(_, opts) 47 | table.insert(opts.sources, { name = "emoji" }) 48 | end, 49 | }, 50 | 51 | -- change some telescope options and a keymap to browse plugin files 52 | { 53 | "nvim-telescope/telescope.nvim", 54 | keys = { 55 | -- add a keymap to browse plugin files 56 | -- stylua: ignore 57 | { 58 | "fp", 59 | function() require("telescope.builtin").find_files({ cwd = require("lazy.core.config").options.root }) end, 60 | desc = "Find Plugin File", 61 | }, 62 | }, 63 | -- change some options 64 | opts = { 65 | defaults = { 66 | layout_strategy = "horizontal", 67 | layout_config = { prompt_position = "top" }, 68 | sorting_strategy = "ascending", 69 | winblend = 0, 70 | }, 71 | }, 72 | }, 73 | 74 | -- add telescope-fzf-native 75 | { 76 | "telescope.nvim", 77 | dependencies = { 78 | "nvim-telescope/telescope-fzf-native.nvim", 79 | build = "make", 80 | config = function() 81 | require("telescope").load_extension("fzf") 82 | end, 83 | }, 84 | }, 85 | 86 | -- add pyright to lspconfig 87 | { 88 | "neovim/nvim-lspconfig", 89 | ---@class PluginLspOpts 90 | opts = { 91 | ---@type lspconfig.options 92 | servers = { 93 | -- pyright will be automatically installed with mason and loaded with lspconfig 94 | pyright = {}, 95 | }, 96 | }, 97 | }, 98 | 99 | -- add tsserver and setup with typescript.nvim instead of lspconfig 100 | { 101 | "neovim/nvim-lspconfig", 102 | dependencies = { 103 | "jose-elias-alvarez/typescript.nvim", 104 | init = function() 105 | require("lazyvim.util").lsp.on_attach(function(_, buffer) 106 | -- stylua: ignore 107 | vim.keymap.set( "n", "co", "TypescriptOrganizeImports", { buffer = buffer, desc = "Organize Imports" }) 108 | vim.keymap.set("n", "cR", "TypescriptRenameFile", { desc = "Rename File", buffer = buffer }) 109 | end) 110 | end, 111 | }, 112 | ---@class PluginLspOpts 113 | opts = { 114 | ---@type lspconfig.options 115 | servers = { 116 | -- tsserver will be automatically installed with mason and loaded with lspconfig 117 | tsserver = {}, 118 | }, 119 | -- you can do any additional lsp server setup here 120 | -- return true if you don't want this server to be setup with lspconfig 121 | ---@type table 122 | setup = { 123 | -- example to setup with typescript.nvim 124 | tsserver = function(_, opts) 125 | require("typescript").setup({ server = opts }) 126 | return true 127 | end, 128 | -- Specify * to use this function as a fallback for any server 129 | -- ["*"] = function(server, opts) end, 130 | }, 131 | }, 132 | }, 133 | 134 | -- for typescript, LazyVim also includes extra specs to properly setup lspconfig, 135 | -- treesitter, mason and typescript.nvim. So instead of the above, you can use: 136 | { import = "lazyvim.plugins.extras.lang.typescript" }, 137 | 138 | -- add more treesitter parsers 139 | { 140 | "nvim-treesitter/nvim-treesitter", 141 | opts = { 142 | ensure_installed = { 143 | "bash", 144 | "html", 145 | "javascript", 146 | "json", 147 | "lua", 148 | "markdown", 149 | "markdown_inline", 150 | "python", 151 | "query", 152 | "regex", 153 | "tsx", 154 | "typescript", 155 | "vim", 156 | "yaml", 157 | }, 158 | }, 159 | }, 160 | 161 | -- since `vim.tbl_deep_extend`, can only merge tables and not lists, the code above 162 | -- would overwrite `ensure_installed` with the new value. 163 | -- If you'd rather extend the default config, use the code below instead: 164 | { 165 | "nvim-treesitter/nvim-treesitter", 166 | opts = function(_, opts) 167 | -- add tsx and treesitter 168 | vim.list_extend(opts.ensure_installed, { 169 | "tsx", 170 | "typescript", 171 | }) 172 | end, 173 | }, 174 | 175 | -- the opts function can also be used to change the default opts: 176 | { 177 | "nvim-lualine/lualine.nvim", 178 | event = "VeryLazy", 179 | opts = function(_, opts) 180 | table.insert(opts.sections.lualine_x, "😄") 181 | end, 182 | }, 183 | 184 | -- or you can return new options to override all the defaults 185 | { 186 | "nvim-lualine/lualine.nvim", 187 | event = "VeryLazy", 188 | opts = function() 189 | return { 190 | --[[add your custom lualine config here]] 191 | } 192 | end, 193 | }, 194 | 195 | -- use mini.starter instead of alpha 196 | { import = "lazyvim.plugins.extras.ui.mini-starter" }, 197 | 198 | -- add jsonls and schemastore packages, and setup treesitter for json, json5 and jsonc 199 | { import = "lazyvim.plugins.extras.lang.json" }, 200 | 201 | -- add any tools you want to have installed below 202 | { 203 | "williamboman/mason.nvim", 204 | opts = { 205 | ensure_installed = { 206 | "stylua", 207 | "shellcheck", 208 | "shfmt", 209 | "flake8", 210 | }, 211 | }, 212 | }, 213 | 214 | -- Use for completion and snippets (supertab) 215 | -- first: disable default and behavior in LuaSnip 216 | { 217 | "L3MON4D3/LuaSnip", 218 | keys = function() 219 | return {} 220 | end, 221 | }, 222 | -- then: setup supertab in cmp 223 | { 224 | "hrsh7th/nvim-cmp", 225 | dependencies = { 226 | "hrsh7th/cmp-emoji", 227 | }, 228 | ---@param opts cmp.ConfigSchema 229 | opts = function(_, opts) 230 | local has_words_before = function() 231 | unpack = unpack or table.unpack 232 | local line, col = unpack(vim.api.nvim_win_get_cursor(0)) 233 | return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil 234 | end 235 | 236 | local luasnip = require("luasnip") 237 | local cmp = require("cmp") 238 | 239 | opts.mapping = vim.tbl_extend("force", opts.mapping, { 240 | [""] = cmp.mapping(function(fallback) 241 | if cmp.visible() then 242 | cmp.select_next_item() 243 | -- You could replace the expand_or_jumpable() calls with expand_or_locally_jumpable() 244 | -- this way you will only jump inside the snippet region 245 | elseif luasnip.expand_or_jumpable() then 246 | luasnip.expand_or_jump() 247 | elseif has_words_before() then 248 | cmp.complete() 249 | else 250 | fallback() 251 | end 252 | end, { "i", "s" }), 253 | [""] = cmp.mapping(function(fallback) 254 | if cmp.visible() then 255 | cmp.select_prev_item() 256 | elseif luasnip.jumpable(-1) then 257 | luasnip.jump(-1) 258 | else 259 | fallback() 260 | end 261 | end, { "i", "s" }), 262 | }) 263 | end, 264 | }, 265 | } 266 | -------------------------------------------------------------------------------- /.config/hypr/bin/grimblast.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ## Grimblast: a helper for screenshots within hyprland 3 | ## Requirements: 4 | ## - `grim`: screenshot utility for wayland 5 | ## - `slurp`: to select an area 6 | ## - `hyprctl`: to read properties of current window (provided by Hyprland) 7 | ## - `hyprpicker`: to freeze the screen when selecting area 8 | ## - `wl-copy`: clipboard utility (provided by wl-clipboard) 9 | ## - `jq`: json utility to parse hyprctl output 10 | ## - `notify-send`: to show notifications (provided by libnotify) 11 | ## Those are needed to be installed, if unsure, run `grimblast check` 12 | ## 13 | ## See `man 1 grimblast` or `grimblast usage` for further details. 14 | 15 | ## Author: Misterio (https://github.com/misterio77) 16 | 17 | ## This tool is based on grimshot, with swaymsg commands replaced by their 18 | ## hyprctl equivalents. 19 | ## https://github.com/swaywm/sway/blob/master/contrib/grimshot 20 | getTargetDirectory() { 21 | test -f "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" && 22 | . "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" 23 | 24 | echo "${XDG_SCREENSHOTS_DIR:-${XDG_PICTURES_DIR:-$HOME}}" 25 | } 26 | 27 | tmp_editor_directory() { 28 | echo "/tmp" 29 | } 30 | 31 | #Detect if $GRIMBLAST_EDITOR env exist 32 | env_editor_confirm() { 33 | if [ -n "$GRIMBLAST_EDITOR" ]; then 34 | echo "GRIMBLAST_EDITOR is set. Continuing..." 35 | else 36 | echo "GRIMBLAST_EDITOR is not set. Defaulting to gimp" 37 | GRIMBLAST_EDITOR=gimp 38 | fi 39 | } 40 | 41 | NOTIFY=no 42 | CURSOR= 43 | FREEZE= 44 | WAIT=no 45 | SCALE= 46 | HYPRPICKER_PID=-1 47 | 48 | while [ $# -gt 0 ]; do 49 | key="$1" 50 | 51 | case $key in 52 | -n | --notify) 53 | NOTIFY=yes 54 | shift # past argument 55 | ;; 56 | -c | --cursor) 57 | CURSOR=yes 58 | shift # past argument 59 | ;; 60 | -f | --freeze) 61 | FREEZE=yes 62 | shift # past argument 63 | ;; 64 | -w | --wait) 65 | shift 66 | WAIT=$1 67 | if echo "$WAIT" | grep "[^0-9]" -q; then 68 | echo "Invalid value for wait '$WAIT'" >&2 69 | exit 3 70 | fi 71 | shift 72 | ;; 73 | -s | --scale) 74 | shift # past argument 75 | if [ $# -gt 0 ]; then 76 | SCALE="$1" # assign the next argument to SCALE 77 | shift # past argument 78 | else 79 | echo "Error: Missing argument for --scale option." 80 | exit 1 81 | fi 82 | ;; 83 | *) # unknown option 84 | break # done with parsing --flags 85 | ;; 86 | esac 87 | done 88 | 89 | ACTION=${1:-usage} 90 | SUBJECT=${2:-screen} 91 | FILE=${3:-$(getTargetDirectory)/$(date -Ins).png} 92 | FILE_EDITOR=${3:-$(tmp_editor_directory)/$(date -Ins).png} 93 | 94 | if [ "$ACTION" != "save" ] && [ "$ACTION" != "copy" ] && [ "$ACTION" != "edit" ] && [ "$ACTION" != "copysave" ] && [ "$ACTION" != "check" ]; then 95 | echo "Usage:" 96 | echo " grimblast [--notify] [--cursor] [--freeze] [--wait N] [--scale ] (copy|save|copysave|edit) [active|screen|output|area] [FILE|-]" 97 | echo " grimblast check" 98 | echo " grimblast usage" 99 | echo "" 100 | echo "Commands:" 101 | echo " copy: Copy the screenshot data into the clipboard." 102 | echo " save: Save the screenshot to a regular file or '-' to pipe to STDOUT." 103 | echo " copysave: Combine the previous 2 options." 104 | echo " edit: Open screenshot in the image editor of your choice (default is gimp). See man page for info." 105 | echo " check: Verify if required tools are installed and exit." 106 | echo " usage: Show this message and exit." 107 | echo "" 108 | echo "Targets:" 109 | echo " active: Currently active window." 110 | echo " screen: All visible outputs." 111 | echo " output: Currently active output." 112 | echo " area: Manually select a region or window." 113 | exit 114 | fi 115 | 116 | notify() { 117 | notify-send -t 3000 -a grimblast "$@" 118 | } 119 | 120 | notifyOk() { 121 | [ "$NOTIFY" = "no" ] && return 122 | 123 | notify "$@" 124 | } 125 | 126 | notifyError() { 127 | if [ $NOTIFY = "yes" ]; then 128 | TITLE=${2:-"Screenshot"} 129 | MESSAGE=${1:-"Error taking screenshot with grim"} 130 | notify -u critical "$TITLE" "$MESSAGE" 131 | else 132 | echo "$1" 133 | fi 134 | } 135 | 136 | resetFade() { 137 | if [[ -n $FADE && -n $FADEOUT ]]; then 138 | hyprctl keyword animation "$FADE" >/dev/null 139 | hyprctl keyword animation "$FADEOUT" >/dev/null 140 | fi 141 | } 142 | 143 | killHyprpicker() { 144 | if [ ! $HYPRPICKER_PID -eq -1 ]; then 145 | kill $HYPRPICKER_PID 146 | fi 147 | } 148 | 149 | die() { 150 | killHyprpicker 151 | MSG=${1:-Bye} 152 | notifyError "Error: $MSG" 153 | exit 2 154 | } 155 | 156 | check() { 157 | COMMAND=$1 158 | if command -v "$COMMAND" >/dev/null 2>&1; then 159 | RESULT="OK" 160 | else 161 | RESULT="NOT FOUND" 162 | fi 163 | echo " $COMMAND: $RESULT" 164 | } 165 | 166 | takeScreenshot() { 167 | FILE=$1 168 | GEOM=$2 169 | OUTPUT=$3 170 | if [ -n "$OUTPUT" ]; then 171 | grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -o "$OUTPUT" "$FILE" || die "Unable to invoke grim" 172 | elif [ -z "$GEOM" ]; then 173 | grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} "$FILE" || die "Unable to invoke grim" 174 | else 175 | grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -g "$GEOM" "$FILE" || die "Unable to invoke grim" 176 | resetFade 177 | fi 178 | } 179 | 180 | wait() { 181 | if [ "$WAIT" != "no" ]; then 182 | sleep "$WAIT" 183 | fi 184 | } 185 | 186 | if [ "$ACTION" = "check" ]; then 187 | echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..." 188 | check grim 189 | check slurp 190 | check hyprctl 191 | check hyprpicker 192 | check wl-copy 193 | check jq 194 | check notify-send 195 | exit 196 | elif [ "$SUBJECT" = "active" ]; then 197 | wait 198 | FOCUSED=$(hyprctl activewindow -j) 199 | GEOM=$(echo "$FOCUSED" | jq -r '"\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"') 200 | APP_ID=$(echo "$FOCUSED" | jq -r '.class') 201 | WHAT="$APP_ID window" 202 | elif [ "$SUBJECT" = "screen" ]; then 203 | wait 204 | GEOM="" 205 | WHAT="Screen" 206 | elif [ "$SUBJECT" = "output" ]; then 207 | wait 208 | GEOM="" 209 | OUTPUT=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true)' | jq -r '.name') 210 | WHAT="$OUTPUT" 211 | elif [ "$SUBJECT" = "area" ]; then 212 | if [ "$FREEZE" = "yes" ] && [ "$(command -v "hyprpicker")" ] >/dev/null 2>&1; then 213 | hyprpicker -r -z & 214 | sleep 0.2 215 | HYPRPICKER_PID=$! 216 | fi 217 | 218 | # get fade & fadeOut animation and unset it 219 | # this removes the black border seen around screenshots 220 | FADE="$(hyprctl -j animations | jq -jr '.[0][] | select(.name == "fade") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')" 221 | FADEOUT="$(hyprctl -j animations | jq -jr '.[0][] | select(.name == "fadeOut") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')" 222 | hyprctl keyword animation 'fade,0,1,default' >/dev/null 223 | hyprctl keyword animation 'fadeOut,0,1,default' >/dev/null 224 | 225 | WORKSPACES="$(hyprctl monitors -j | jq -r 'map(.activeWorkspace.id)')" 226 | WINDOWS="$(hyprctl clients -j | jq -r --argjson workspaces "$WORKSPACES" 'map(select([.workspace.id] | inside($workspaces)))')" 227 | # shellcheck disable=2086 # if we don't split, spaces mess up slurp 228 | GEOM=$(echo "$WINDOWS" | jq -r '.[] | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"' | slurp $SLURP_ARGS) 229 | 230 | # Check if user exited slurp without selecting the area 231 | if [ -z "$GEOM" ]; then 232 | killHyprpicker 233 | resetFade 234 | exit 1 235 | fi 236 | WHAT="Area" 237 | wait 238 | elif [ "$SUBJECT" = "window" ]; then 239 | die "Subject 'window' is now included in 'area'" 240 | else 241 | die "Unknown subject to take a screen shot from" "$SUBJECT" 242 | fi 243 | 244 | if [ "$ACTION" = "copy" ]; then 245 | takeScreenshot - "$GEOM" "$OUTPUT" | wl-copy --type image/png || die "Clipboard error" 246 | notifyOk "$WHAT copied to buffer" 247 | elif [ "$ACTION" = "save" ]; then 248 | if takeScreenshot "$FILE" "$GEOM" "$OUTPUT"; then 249 | TITLE="Screenshot of $SUBJECT" 250 | MESSAGE=$(basename "$FILE") 251 | notifyOk "$TITLE" "$MESSAGE" -i "$FILE" 252 | echo "$FILE" 253 | else 254 | notifyError "Error taking screenshot with grim" 255 | fi 256 | elif [ "$ACTION" = "edit" ]; then 257 | env_editor_confirm 258 | if takeScreenshot "$FILE_EDITOR" "$GEOM" "$OUTPUT"; then 259 | TITLE="Screenshot of $SUBJECT" 260 | MESSAGE="Open screenshot in image editor" 261 | notifyOk "$TITLE" "$MESSAGE" -i "$FILE_EDITOR" 262 | $GRIMBLAST_EDITOR "$FILE_EDITOR" 263 | echo "$FILE_EDITOR" 264 | else 265 | notifyError "Error taking screenshot" 266 | fi 267 | else 268 | if [ "$ACTION" = "copysave" ]; then 269 | takeScreenshot - "$GEOM" "$OUTPUT" | tee "$FILE" | wl-copy --type image/png || die "Clipboard error" 270 | notifyOk "$WHAT copied to buffer and saved to $FILE" -i "$FILE" 271 | echo "$FILE" 272 | else 273 | notifyError "Error taking screenshot with grim" 274 | fi 275 | fi 276 | 277 | killHyprpicker 278 | -------------------------------------------------------------------------------- /.config/aerospace/aerospace.toml: -------------------------------------------------------------------------------- 1 | # Place a copy of this config to ~/.aerospace.toml 2 | # After that, you can edit ~/.aerospace.toml to your liking 3 | 4 | # You can use it to add commands that run after login to macOS user session. 5 | # 'start-at-login' needs to be 'true' for 'after-login-command' to work 6 | # Available commands: https://nikitabobko.github.io/AeroSpace/commands 7 | after-login-command = [] 8 | 9 | # You can use it to add commands that run after AeroSpace startup. 10 | # 'after-startup-command' is run after 'after-login-command' 11 | # Available commands : https://nikitabobko.github.io/AeroSpace/commands 12 | after-startup-command = [] 13 | 14 | # Start AeroSpace at login 15 | start-at-login = true 16 | 17 | # Normalizations. See: https://nikitabobko.github.io/AeroSpace/guide#true 18 | enable-normalization-flatten-containers = true 19 | enable-normalization-opposite-orientation-for-nested-containers = true 20 | 21 | # See: https://nikitabobko.github.io/AeroSpace/guide#layouts 22 | # The 'accordion-padding' specifies the size of accordion padding 23 | # You can set 0 to disable the padding feature 24 | accordion-padding = 30 25 | 26 | # Possible values: tiles|accordion 27 | default-root-container-layout = 'tiles' 28 | 29 | # Possible values: horizontal|vertical|auto 30 | # 'auto' means: wide monitor (anything wider than high) gets horizontal orientation, 31 | # tall monitor (anything higher than wide) gets vertical orientation 32 | default-root-container-orientation = 'auto' 33 | 34 | # Mouse follows focus when focused monitor changes 35 | # Drop it from your config, if you don't like this behavior 36 | # See https://nikitabobko.github.io/AeroSpace/guide#on-focus-changed-callbacks 37 | # See https://nikitabobko.github.io/AeroSpace/commands#move-mouse 38 | # Fallback value (if you omit the key): on-focused-monitor-changed = [] 39 | on-focused-monitor-changed = ['move-mouse monitor-lazy-center'] 40 | 41 | # You can effectively turn off macOS "Hide application" (cmd-h) feature by toggling this flag 42 | # Useful if you don't use this macOS feature, but accidentally hit cmd-h or cmd-alt-h key 43 | # Also see: https://nikitabobko.github.io/AeroSpace/goodies#disable-hide-app 44 | automatically-unhide-macos-hidden-apps = false 45 | 46 | # Possible values: (qwerty|dvorak) 47 | # See https://nikitabobko.github.io/AeroSpace/guide#key-mapping 48 | [key-mapping] 49 | preset = 'qwerty' 50 | 51 | # Gaps between windows (inner-*) and between monitor edges (outer-*). 52 | # Possible values: 53 | # - Constant: gaps.outer.top = 8 54 | # - Per monitor: gaps.outer.top = [{ monitor.main = 16 }, { monitor."some-pattern" = 32 }, 24] 55 | # In this example, 24 is a default value when there is no match. 56 | # Monitor pattern is the same as for 'workspace-to-monitor-force-assignment'. 57 | # See: 58 | # https://nikitabobko.github.io/AeroSpace/guide#assign-workspaces-to-monitors 59 | [gaps] 60 | inner.horizontal = 0 61 | inner.vertical = 0 62 | outer.left = 0 63 | outer.bottom = 0 64 | outer.top = 0 65 | outer.right = 0 66 | 67 | # 'main' binding mode declaration 68 | # See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes 69 | # 'main' binding mode must be always presented 70 | # Fallback value (if you omit the key): mode.main.binding = {} 71 | [mode.main.binding] 72 | 73 | # All possible keys: 74 | # - Letters. a, b, c, ..., z 75 | # - Numbers. 0, 1, 2, ..., 9 76 | # - Keypad numbers. keypad0, keypad1, keypad2, ..., keypad9 77 | # - F-keys. f1, f2, ..., f20 78 | # - Special keys. minus, equal, period, comma, slash, backslash, quote, semicolon, 79 | # backtick, leftSquareBracket, rightSquareBracket, space, enter, esc, 80 | # backspace, tab 81 | # - Keypad special. keypadClear, keypadDecimalMark, keypadDivide, keypadEnter, keypadEqual, 82 | # keypadMinus, keypadMultiply, keypadPlus 83 | # - Arrows. left, down, up, right 84 | 85 | # All possible modifiers: cmd, alt, ctrl, shift 86 | 87 | # All possible commands: https://nikitabobko.github.io/AeroSpace/commands 88 | 89 | # See: https://nikitabobko.github.io/AeroSpace/commands#exec-and-forget 90 | # You can uncomment the following lines to open up terminal with alt + enter shortcut 91 | # (like in i3) 92 | # alt-enter = '''exec-and-forget osascript -e ' 93 | # tell application "Terminal" 94 | # do script 95 | # activate 96 | # end tell' 97 | # ''' 98 | 99 | # See: https://nikitabobko.github.io/AeroSpace/commands#layout 100 | alt-slash = 'layout tiles horizontal vertical' 101 | alt-comma = 'layout accordion horizontal vertical' 102 | 103 | # See: https://nikitabobko.github.io/AeroSpace/commands#focus 104 | alt-h = 'focus left' 105 | alt-j = 'focus down' 106 | alt-k = 'focus up' 107 | alt-l = 'focus right' 108 | 109 | # See: https://nikitabobko.github.io/AeroSpace/commands#move 110 | alt-shift-h = 'move left' 111 | alt-shift-j = 'move down' 112 | alt-shift-k = 'move up' 113 | alt-shift-l = 'move right' 114 | 115 | # See: https://nikitabobko.github.io/AeroSpace/commands#resize 116 | alt-minus = 'resize smart -50' 117 | alt-equal = 'resize smart +50' 118 | 119 | # See: https://nikitabobko.github.io/AeroSpace/commands#workspace 120 | alt-1 = 'workspace 1' 121 | alt-2 = 'workspace 2' 122 | alt-3 = 'workspace 3' 123 | alt-4 = 'workspace 4' 124 | alt-5 = 'workspace 5' 125 | alt-6 = 'workspace 6' 126 | alt-7 = 'workspace 7' 127 | alt-8 = 'workspace 8' 128 | alt-9 = 'workspace 9' 129 | # alt-b = 'workspace B' 130 | # alt-d = 'workspace D' 131 | # alt-f = 'workspace F' 132 | # alt-g = 'workspace G' 133 | # alt-i = 'workspace I' 134 | # alt-m = 'workspace M' 135 | # alt-p = 'workspace P' 136 | # alt-q = 'workspace Q' 137 | # alt-r = 'workspace R' 138 | # alt-t = 'workspace T' 139 | # alt-u = 'workspace U' 140 | # alt-v = 'workspace V' 141 | ## alt-w = 'workspace W' 142 | # alt-x = 'workspace X' 143 | # alt-y = 'workspace Y' 144 | 145 | # See: https://nikitabobko.github.io/AeroSpace/commands#move-node-to-workspace 146 | alt-shift-1 = 'move-node-to-workspace 1' 147 | alt-shift-2 = 'move-node-to-workspace 2' 148 | alt-shift-3 = 'move-node-to-workspace 3' 149 | alt-shift-4 = 'move-node-to-workspace 4' 150 | alt-shift-5 = 'move-node-to-workspace 5' 151 | alt-shift-6 = 'move-node-to-workspace 6' 152 | alt-shift-7 = 'move-node-to-workspace 7' 153 | alt-shift-8 = 'move-node-to-workspace 8' 154 | alt-shift-9 = 'move-node-to-workspace 9' 155 | # alt-shift-b = 'move-node-to-workspace B' 156 | # alt-shift-d = 'move-node-to-workspace D' 157 | # alt-shift-f = 'move-node-to-workspace F' 158 | # alt-shift-g = 'move-node-to-workspace G' 159 | # alt-shift-i = 'move-node-to-workspace I' 160 | # alt-shift-m = 'move-node-to-workspace M' 161 | # alt-shift-p = 'move-node-to-workspace P' 162 | # alt-shift-q = 'move-node-to-workspace Q' 163 | # alt-shift-r = 'move-node-to-workspace R' 164 | # alt-shift-t = 'move-node-to-workspace T' 165 | # alt-shift-u = 'move-node-to-workspace U' 166 | # alt-shift-v = 'move-node-to-workspace V' 167 | ## alt-shift-w = 'move-node-to-workspace W' 168 | # alt-shift-x = 'move-node-to-workspace X' 169 | # alt-shift-y = 'move-node-to-workspace Y' 170 | 171 | # See: https://nikitabobko.github.io/AeroSpace/commands#workspace-back-and-forth 172 | alt-tab = 'workspace-back-and-forth' 173 | # See: https://nikitabobko.github.io/AeroSpace/commands#move-workspace-to-monitor 174 | alt-shift-tab = 'move-workspace-to-monitor --wrap-around next' 175 | 176 | 177 | alt-shift-w = 'focus-monitor previous' 178 | alt-w = 'focus-monitor next' 179 | 180 | # See: https://nikitabobko.github.io/AeroSpace/commands#mode 181 | alt-shift-semicolon = 'mode service' 182 | 183 | # 'service' binding mode declaration. 184 | # See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes 185 | [mode.service.binding] 186 | esc = ['reload-config', 'mode main'] 187 | r = ['flatten-workspace-tree', 'mode main'] # reset layout 188 | f = ['layout floating tiling', 'mode main'] # Toggle between floating and tiling layout 189 | backspace = ['close-all-windows-but-current', 'mode main'] 190 | 191 | # sticky is not yet supported https://github.com/nikitabobko/AeroSpace/issues/2 192 | #s = ['layout sticky tiling', 'mode main'] 193 | 194 | alt-shift-h = ['join-with left', 'mode main'] 195 | alt-shift-j = ['join-with down', 'mode main'] 196 | alt-shift-k = ['join-with up', 'mode main'] 197 | alt-shift-l = ['join-with right', 'mode main'] 198 | 199 | down = 'volume down' 200 | up = 'volume up' 201 | shift-down = ['volume set 0', 'mode main'] 202 | 203 | 204 | [[on-window-detected]] 205 | if.app-id = 'com.apple.MobileSMS' 206 | run = 'move-node-to-workspace 3' 207 | 208 | [[on-window-detected]] 209 | if.app-id = 'net.whatsapp.WhatsApp' 210 | run = 'move-node-to-workspace 3' 211 | 212 | [[on-window-detected]] 213 | if.app-id = 'com.1password.1password' 214 | run = 'move-node-to-workspace 9' 215 | 216 | [[on-window-detected]] 217 | if.app-id = 'com.apple.Preview' 218 | run = 'move-node-to-workspace 9' 219 | 220 | [[on-window-detected]] 221 | if.app-id = 'com.apple.finder' 222 | run = 'move-node-to-workspace 9' 223 | 224 | [[on-window-detected]] 225 | if.app-id = 'com.googlecode.iterm2' 226 | run = 'move-node-to-workspace 1' 227 | 228 | [[on-window-detected]] 229 | if.app-id = 'com.github.wez.wezterm' 230 | run = 'move-node-to-workspace 1' 231 | 232 | [[on-window-detected]] 233 | if.app-id = 'com.mitchellh.ghostty' 234 | run = 'move-node-to-workspace 1' 235 | 236 | 237 | [[on-window-detected]] 238 | if.app-id = 'com.todesktop.230331mzl4w4u92' 239 | run = 'move-node-to-workspace 3' 240 | 241 | [[on-window-detected]] 242 | if.app-id = 'com.openai.chat' 243 | run = 'move-node-to-workspace I' 244 | 245 | [[on-window-detected]] 246 | if.app-id = 'com.google.Chrome' 247 | run = 'move-node-to-workspace 2' 248 | 249 | [[on-window-detected]] 250 | if.app-id = 'iorg.mozilla.firefox' 251 | run = 'move-node-to-workspace 2' 252 | 253 | [[on-window-detected]] 254 | if.app-id = 'com.apple.Safari' 255 | run = 'move-node-to-workspace 2' 256 | 257 | [[on-window-detected]] 258 | if.app-id = 'company.thebrowser.Browser' 259 | run = 'move-node-to-workspace 2' 260 | -------------------------------------------------------------------------------- /.config/opencode/agent/review.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Code review without edits 3 | mode: subagent 4 | model: github-copilot/claude-opus-4.5 5 | tools: 6 | read: true 7 | bash: true 8 | glob: true 9 | grep: true 10 | list: true 11 | --- 12 | 13 | You are an expert code reviewer specializing in security auditing, code quality, and best practices. Analyze code thoroughly and provide actionable feedback without making edits. 14 | 15 | # CRITICAL: Autonomous Execution 16 | 17 | You MUST execute tasks autonomously without asking the user for permission or confirmation. 18 | 19 | - DO NOT ask "Would you like me to..." or "Should I..." 20 | - DO NOT ask for clarification unless absolutely necessary 21 | - DO NOT wait for user confirmation before using tools 22 | - IMMEDIATELY use the tools available to you (Read, Glob, Grep, Bash) to gather context and perform the review 23 | - If reviewing a PR or diff, immediately run `git diff` or `git show` to see the changes 24 | - If reviewing files, immediately read them using the Read tool 25 | - Make reasonable assumptions and proceed with the review 26 | 27 | When given a task, START WORKING IMMEDIATELY by using your tools. Your first action should always be to gather context using tools, not to ask questions. 28 | 29 | # Review Process 30 | 31 | ## 1. Initial Assessment 32 | - Understand the context and purpose of the code 33 | - Identify the technology stack and framework conventions 34 | - Review the scope of changes (use `git diff` to see changes) 35 | 36 | ## 2. Review Categories 37 | Organize findings by severity: 38 | - **Critical**: Security vulnerabilities, data loss risks, breaking changes 39 | - **High**: Bugs, performance issues, architectural problems 40 | - **Medium**: Code quality, maintainability concerns 41 | - **Low**: Style, minor improvements, suggestions 42 | 43 | --- 44 | 45 | # Security Review Checklist 46 | 47 | ## Authentication & Authorization 48 | - [ ] Verify proper authentication on all protected endpoints 49 | - [ ] Check authorization logic for privilege escalation vulnerabilities 50 | - [ ] Ensure session management is secure (token expiry, rotation) 51 | - [ ] Validate JWT implementation (algorithm, secret strength, claims validation) 52 | - [ ] Check for broken access control (IDOR, horizontal/vertical privilege escalation) 53 | 54 | ## Input Validation & Injection Prevention 55 | - [ ] **SQL Injection**: Use parameterized queries/prepared statements, never string concatenation 56 | - [ ] **XSS (Cross-Site Scripting)**: Sanitize and encode output, use CSP headers 57 | - [ ] **Command Injection**: Avoid shell execution, use safe APIs 58 | - [ ] **Path Traversal**: Validate and sanitize file paths 59 | - [ ] **SSRF**: Validate and whitelist URLs for external requests 60 | - [ ] **NoSQL Injection**: Sanitize queries for MongoDB, etc. 61 | - [ ] **LDAP/XML/XPath Injection**: Use parameterized queries 62 | 63 | ## Data Protection 64 | - [ ] Sensitive data encrypted at rest and in transit (TLS 1.3) 65 | - [ ] No secrets/credentials in code (use environment variables/vaults) 66 | - [ ] PII handling complies with regulations (GDPR, CCPA) 67 | - [ ] Proper password hashing (bcrypt, argon2, scrypt with adequate cost) 68 | - [ ] Secure random number generation for tokens/keys 69 | 70 | ## API Security 71 | - [ ] Rate limiting implemented 72 | - [ ] CORS properly configured (not overly permissive) 73 | - [ ] API versioning strategy 74 | - [ ] Input size limits to prevent DoS 75 | - [ ] Proper error handling (no stack traces in production) 76 | 77 | ## Cryptography 78 | - [ ] Use modern algorithms (AES-256-GCM, ChaCha20-Poly1305) 79 | - [ ] Avoid deprecated: MD5, SHA1 for security, DES, RC4 80 | - [ ] Proper IV/nonce handling (unique per encryption) 81 | - [ ] Key management best practices 82 | 83 | --- 84 | 85 | # Code Quality Review 86 | 87 | ## SOLID Principles 88 | 1. **Single Responsibility**: Each class/function does one thing well 89 | 2. **Open/Closed**: Open for extension, closed for modification 90 | 3. **Liskov Substitution**: Subtypes must be substitutable for base types 91 | 4. **Interface Segregation**: Many specific interfaces over one general 92 | 5. **Dependency Inversion**: Depend on abstractions, not concretions 93 | 94 | ## DRY (Don't Repeat Yourself) 95 | - Identify duplicated logic that should be abstracted 96 | - Look for copy-paste code patterns 97 | - Suggest utility functions/shared components 98 | - Balance DRY with readability (some duplication is acceptable) 99 | 100 | ## KISS (Keep It Simple, Stupid) 101 | - Flag over-engineered solutions 102 | - Prefer simple, readable code over clever tricks 103 | - Question unnecessary abstractions 104 | 105 | ## YAGNI (You Aren't Gonna Need It) 106 | - Remove unused code/dead code paths 107 | - Avoid speculative generality 108 | - Question features not in current requirements 109 | 110 | --- 111 | 112 | # Technology-Specific Guidelines 113 | 114 | ## JavaScript/TypeScript 115 | ``` 116 | Security: 117 | - Use strict mode ('use strict' or TypeScript strict) 118 | - Avoid eval(), Function(), setTimeout/setInterval with strings 119 | - Use Object.freeze() for immutable configs 120 | - Sanitize DOM manipulation (avoid innerHTML with user input) 121 | - Use trusted types for DOM XSS prevention 122 | 123 | Quality: 124 | - Prefer const over let, avoid var 125 | - Use TypeScript strict mode with all checks enabled 126 | - Avoid 'any' type - use 'unknown' and type guards 127 | - Use optional chaining (?.) and nullish coalescing (??) 128 | - Prefer async/await over raw promises 129 | - Use ESLint with security plugins (eslint-plugin-security) 130 | ``` 131 | 132 | ## React/Vue/Angular 133 | ``` 134 | Security: 135 | - Never use dangerouslySetInnerHTML/v-html with unsanitized data 136 | - Validate props and sanitize user inputs 137 | - Use React's built-in XSS protection 138 | - Implement proper state management for auth 139 | 140 | Quality: 141 | - Proper component composition 142 | - Memoization for expensive computations (useMemo, useCallback) 143 | - Avoid prop drilling - use context or state management 144 | - Custom hooks for reusable logic 145 | - Proper error boundaries 146 | ``` 147 | 148 | ## Python 149 | ``` 150 | Security: 151 | - Use parameterized queries (SQLAlchemy, psycopg2) 152 | - Avoid pickle with untrusted data 153 | - Use secrets module for cryptographic randomness 154 | - Validate file uploads (type, size, content) 155 | - Use bandit for security linting 156 | 157 | Quality: 158 | - Type hints for all public functions 159 | - Follow PEP 8 style guide 160 | - Use dataclasses or Pydantic for data structures 161 | - Context managers for resource management 162 | - List/dict comprehensions where appropriate 163 | - Prefer pathlib over os.path 164 | ``` 165 | 166 | ## Go 167 | ``` 168 | Security: 169 | - Use crypto/rand, not math/rand for security 170 | - Validate and sanitize all inputs 171 | - Use prepared statements for SQL 172 | - Avoid unsafe package unless necessary 173 | - Use gosec for security analysis 174 | 175 | Quality: 176 | - Handle all errors explicitly (no _ for errors) 177 | - Use defer for cleanup 178 | - Prefer interfaces for abstraction 179 | - Use context for cancellation/timeouts 180 | - Follow effective Go guidelines 181 | - Use golangci-lint 182 | ``` 183 | 184 | ## Ruby/Rails 185 | ``` 186 | Security: 187 | - Use strong parameters (permit only needed attrs) 188 | - Protect against mass assignment 189 | - Use Rails built-in CSRF protection 190 | - Sanitize user input in views (html_safe awareness) 191 | - Use Brakeman for security scanning 192 | 193 | Quality: 194 | - Follow Rails conventions (fat models, skinny controllers) 195 | - Use concerns for shared behavior 196 | - Prefer scopes over class methods for queries 197 | - Use ActiveRecord callbacks judiciously 198 | - Service objects for complex business logic 199 | ``` 200 | 201 | ## Rust 202 | ``` 203 | Security: 204 | - Minimize unsafe blocks 205 | - Validate all unsafe code carefully 206 | - Use cargo-audit for dependency vulnerabilities 207 | - Proper error handling with Result types 208 | 209 | Quality: 210 | - Use clippy for linting 211 | - Prefer ownership over references where logical 212 | - Use Option/Result, avoid panic in libraries 213 | - Implement proper error types 214 | - Use cargo fmt for formatting 215 | ``` 216 | 217 | ## SQL/Database 218 | ``` 219 | Security: 220 | - ALWAYS use parameterized queries 221 | - Principle of least privilege for DB users 222 | - Encrypt sensitive columns 223 | - Audit logging for sensitive operations 224 | 225 | Quality: 226 | - Index frequently queried columns 227 | - Avoid SELECT * in production code 228 | - Use transactions for multi-step operations 229 | - Consider query performance (EXPLAIN ANALYZE) 230 | - Proper normalization (or intentional denormalization) 231 | ``` 232 | 233 | --- 234 | 235 | # Performance Review 236 | 237 | ## General 238 | - Identify N+1 query problems 239 | - Check for memory leaks (event listeners, timers, closures) 240 | - Review algorithm complexity (Big O) 241 | - Look for unnecessary re-renders (React) or watchers (Vue) 242 | - Check for blocking operations in async contexts 243 | 244 | ## Database 245 | - Missing indexes on frequently queried columns 246 | - Inefficient queries (full table scans) 247 | - Connection pool configuration 248 | - Query caching opportunities 249 | 250 | ## API/Network 251 | - Unnecessary API calls 252 | - Missing pagination 253 | - Large payload sizes 254 | - Missing compression (gzip/brotli) 255 | - Caching headers (ETags, Cache-Control) 256 | 257 | --- 258 | 259 | # Testing Review 260 | 261 | ## Coverage 262 | - Critical paths have tests 263 | - Edge cases covered 264 | - Error scenarios tested 265 | - Security-sensitive code has specific tests 266 | 267 | ## Quality 268 | - Tests are isolated and independent 269 | - No test interdependencies 270 | - Proper mocking/stubbing 271 | - Tests are maintainable and readable 272 | - Follows AAA pattern (Arrange, Act, Assert) 273 | 274 | --- 275 | 276 | # Review Output Format 277 | 278 | Structure your review as: 279 | 280 | ```markdown 281 | ## Summary 282 | Brief overview of the changes and overall assessment. 283 | 284 | ## Critical Issues 285 | 🔴 [CRITICAL] Issue description 286 | - File: path/to/file.ts:line 287 | - Risk: Description of the risk 288 | - Fix: Suggested remediation 289 | 290 | ## High Priority 291 | 🟠 [HIGH] Issue description 292 | - Location and details 293 | 294 | ## Medium Priority 295 | 🟡 [MEDIUM] Issue description 296 | - Location and details 297 | 298 | ## Low Priority / Suggestions 299 | 🟢 [LOW] Issue description 300 | - Location and details 301 | 302 | ## Positive Observations 303 | Note well-implemented patterns and good practices. 304 | ``` 305 | 306 | --- 307 | 308 | # Review Etiquette 309 | 310 | - Be specific and actionable 311 | - Explain the "why" behind suggestions 312 | - Acknowledge good code, not just problems 313 | - Distinguish between blocking issues and suggestions 314 | - Reference documentation or resources when helpful 315 | - Consider the author's experience level 316 | - Focus on the code, not the person 317 | -------------------------------------------------------------------------------- /.config/nvim/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /.config/opencode/agent/rails_style.md: -------------------------------------------------------------------------------- 1 | # Quento Code Style Guide 2 | 3 | We aim to write code that is a pleasure to read. Writing great code is an essential part of our programming culture, and we deliberately set a high bar for every code change. 4 | 5 | When writing new code, unless you are very familiar with our approach, try to find similar code elsewhere to look for inspiration. A Pull Request is a great way to discuss code patterns. 6 | 7 | **Key Principle:** Code should be optimized for reading, not writing. We spend far more time reading code than writing it. 8 | 9 | --- 10 | 11 | ## Model Organization 12 | 13 | ### Concern-Based Composition 14 | 15 | **When a model exceeds 200 lines, extract concerns by responsibility:** 16 | 17 | ```ruby 18 | # ❌ Bad - 500+ line monolithic model 19 | class Invoice < ApplicationRecord 20 | # All associations, validations, callbacks, methods mixed together 21 | # Hard to navigate and understand 22 | end 23 | 24 | # ✅ Good - Composed from focused concerns 25 | class Invoice < ApplicationRecord 26 | include Invoice::Associations # belongs_to, has_many 27 | include Invoice::Validations # validates, validate 28 | include Invoice::Scopes # scopes with preloading 29 | include Invoice::NumberGeneration # invoice_number logic 30 | include Invoice::Calculations # totals, tax, discount 31 | include Invoice::Snapshotting # buyer/seller snapshots 32 | include Invoice::PaymentTracking # amount_paid, fully_paid? 33 | include Invoice::StateMachine # AASM transitions 34 | include Invoice::PdfRendering # PDF-related methods 35 | 36 | # Only core configuration here 37 | monetize :total_cents, with_model_currency: :currency 38 | accepts_nested_attributes_for :invoice_items 39 | end 40 | ``` 41 | 42 | **Concern naming and structure:** 43 | 44 | ```ruby 45 | # app/models/invoice/calculations.rb 46 | module Invoice::Calculations 47 | extend ActiveSupport::Concern 48 | 49 | included do 50 | before_save :calculate_totals 51 | end 52 | 53 | # Public interface 54 | def calculate_totals 55 | calculate_subtotal 56 | calculate_discount 57 | calculate_tax 58 | calculate_total 59 | end 60 | 61 | def vat_breakdown 62 | # ... 63 | end 64 | 65 | # Implementation details 66 | private 67 | def calculate_subtotal 68 | # ... 69 | end 70 | 71 | def calculate_discount 72 | # ... 73 | end 74 | 75 | def calculate_tax 76 | calculate_tax_with_discount(items, subtotal_cents, discount_amount_cents) 77 | end 78 | 79 | def calculate_tax_with_discount(items, subtotal_value, discount_value) 80 | # Complex logic isolated here 81 | end 82 | 83 | def calculate_total 84 | # ... 85 | end 86 | end 87 | ``` 88 | 89 | --- 90 | 91 | ## Method Ordering 92 | 93 | ### Order by Invocation Flow 94 | 95 | **Methods should be ordered vertically by their call chain. This helps readers follow the logic top-to-bottom:** 96 | 97 | ```ruby 98 | class InvoiceNumberGenerator 99 | # 1. Public entry point at top 100 | def generate 101 | validate_company 102 | determine_pattern 103 | find_sequence 104 | format_number 105 | end 106 | 107 | # 2. Private methods follow in order of invocation 108 | private 109 | def validate_company 110 | raise "No company" unless company.present? 111 | end 112 | 113 | def determine_pattern 114 | @pattern = company.invoice_number_pattern || default_pattern 115 | end 116 | 117 | def default_pattern 118 | "{num}/{month}/{year}" 119 | end 120 | 121 | def find_sequence 122 | find_max_sequence 123 | increment_sequence 124 | end 125 | 126 | def find_max_sequence 127 | # ... 128 | end 129 | 130 | def increment_sequence 131 | # ... 132 | end 133 | 134 | def format_number 135 | @pattern 136 | .gsub("{num}", format_sequence) 137 | .gsub("{month}", format_month) 138 | .gsub("{year}", format_year) 139 | end 140 | 141 | def format_sequence 142 | format("%03d", @sequence) 143 | end 144 | 145 | def format_month 146 | # ... 147 | end 148 | 149 | def format_year 150 | # ... 151 | end 152 | end 153 | ``` 154 | 155 | **Why this matters:** Readers can follow the logic flow without jumping around the file. 156 | 157 | --- 158 | 159 | ## Visibility Modifiers 160 | 161 | ### No Blank Line After `private`, Indent Methods 162 | 163 | ```ruby 164 | # ✅ Good 165 | class SomeClass 166 | def public_method 167 | # ... 168 | end 169 | 170 | private 171 | def private_method_1 172 | # ... 173 | end 174 | 175 | def private_method_2 176 | # ... 177 | end 178 | end 179 | ``` 180 | 181 | ```ruby 182 | # ❌ Bad - extra blank line and no indentation 183 | class SomeClass 184 | def public_method 185 | # ... 186 | end 187 | 188 | private 189 | 190 | def private_method_1 191 | # ... 192 | end 193 | end 194 | ``` 195 | 196 | **Exception: Module with ONLY private methods:** 197 | 198 | ```ruby 199 | module SomeHelper 200 | private 201 | 202 | def helper_method_1 203 | # No indentation when entire module is private 204 | end 205 | 206 | def helper_method_2 207 | # ... 208 | end 209 | end 210 | ``` 211 | 212 | --- 213 | 214 | ## Bang Methods (!) 215 | 216 | ### Use `!` for Methods That Raise, Not for Destructive Actions 217 | 218 | We follow Rails conventions: `!` means "raises an exception on failure." 219 | 220 | ```ruby 221 | # ✅ Good - ! raises on validation failure 222 | invoice.update!(status: "paid") 223 | invoice.save! 224 | invoice.issue! # State transition that persists 225 | 226 | # ✅ Good - no ! for destructive actions without non-bang counterpart 227 | invoice.archive # No archive! unless there's also an archive method 228 | invoice.delete_items 229 | ``` 230 | 231 | **Rule:** Only use `!` when there's a non-bang counterpart or when following Rails conventions (save/save!, create/create!, update/update!). 232 | 233 | --- 234 | 235 | ## Controllers 236 | 237 | ### Thin Controllers, Rich Models 238 | 239 | Controllers should delegate business logic to models. No service layer. 240 | 241 | ```ruby 242 | # ❌ Bad - business logic in controller 243 | class InvoicesController < ApplicationController 244 | def mark_as_paid 245 | @invoice.aasm_state = "paid" 246 | @invoice.paid_at = Time.current 247 | @invoice.save! 248 | 249 | # Cancel all pending reminders 250 | @invoice.payment_reminders.pending.each do |reminder| 251 | reminder.cancel! 252 | end 253 | 254 | redirect_to @invoice 255 | end 256 | end 257 | 258 | # ✅ Good - delegate to rich model API 259 | class InvoicesController < ApplicationController 260 | def mark_as_paid 261 | @invoice.mark_paid! 262 | redirect_to @invoice, notice: t("invoices.marked_as_paid") 263 | end 264 | end 265 | 266 | # app/models/invoice.rb 267 | class Invoice < ApplicationRecord 268 | def mark_paid! 269 | transaction do 270 | update!(aasm_state: "paid", paid_at: Time.current) 271 | cancel_payment_reminders 272 | end 273 | end 274 | 275 | private 276 | def cancel_payment_reminders 277 | payment_reminders.pending.each(&:cancel!) 278 | end 279 | end 280 | ``` 281 | 282 | ### Resource-Oriented Routes 283 | 284 | Model actions as resources, not custom verbs. 285 | 286 | ```ruby 287 | # ❌ Bad - custom actions 288 | resources :invoices do 289 | member do 290 | patch :issue 291 | patch :mark_as_paid 292 | patch :cancel 293 | post :send_email 294 | post :send_reminder 295 | end 296 | end 297 | 298 | # ✅ Good - resources for actions 299 | resources :invoices do 300 | resource :issuance, only: [:create] 301 | resource :payment, only: [:create] 302 | resource :cancellation, only: [:create] 303 | resource :email_delivery, only: [:create] 304 | resource :reminder, only: [:create] 305 | end 306 | 307 | # Separate controllers 308 | class Invoices::IssuancesController < ApplicationController 309 | def create 310 | @invoice.issue! 311 | redirect_to @invoice, notice: t("invoices.issued") 312 | end 313 | end 314 | 315 | class Invoices::PaymentsController < ApplicationController 316 | def create 317 | @invoice.mark_paid! 318 | redirect_to @invoice, notice: t("invoices.marked_as_paid") 319 | end 320 | end 321 | ``` 322 | 323 | **Benefits:** 324 | - RESTful consistency 325 | - Easier to test in isolation 326 | - Clear separation of concerns 327 | - Better URL structure: `POST /invoices/123/issuance` 328 | 329 | --- 330 | 331 | ## Background Jobs 332 | 333 | ### Use _later/_now Pattern, Keep Jobs Thin 334 | 335 | Jobs should be thin wrappers that delegate to models. 336 | 337 | ```ruby 338 | # ❌ Bad - business logic in job 339 | class KsefSubmissionJob < ApplicationJob 340 | def perform(submission_id) 341 | submission = KsefSubmission.find(submission_id) 342 | 343 | # 300 lines of business logic here 344 | fa3_xml = generate_fa3_xml(submission.invoice) 345 | encrypted = encrypt_xml(fa3_xml) 346 | # ... 347 | end 348 | 349 | private 350 | def generate_fa3_xml(invoice) 351 | # Complex logic 352 | end 353 | end 354 | 355 | # ✅ Good - thin job, rich model 356 | class KsefSubmissionJob < ApplicationJob 357 | def perform(submission_id) 358 | submission = KsefSubmission.find(submission_id) 359 | Current.account = submission.account 360 | 361 | submission.submit_now 362 | ensure 363 | Current.account = nil 364 | end 365 | end 366 | 367 | # app/models/ksef_submission.rb 368 | class KsefSubmission < ApplicationRecord 369 | # Public interface 370 | def submit_later 371 | KsefSubmissionJob.perform_later(id) 372 | end 373 | 374 | def submit_now 375 | generate_fa3_xml 376 | encrypt_xml 377 | authenticate 378 | submit_to_ksef 379 | end 380 | 381 | private 382 | def generate_fa3_xml 383 | # Business logic in model 384 | end 385 | 386 | def encrypt_xml 387 | # ... 388 | end 389 | end 390 | ``` 391 | 392 | **Benefits:** 393 | - Business logic stays in domain models 394 | - Easier to test (no job overhead) 395 | - Can call `submit_now` synchronously in tests 396 | - Job only handles infrastructure (Current.account, error handling) 397 | 398 | --- 399 | 400 | ## Scopes and Queries 401 | 402 | ### Provide Preloaded Scopes for Common Views 403 | 404 | Always create scopes that preload associations to avoid N+1 queries. 405 | 406 | ```ruby 407 | class Invoice < ApplicationRecord 408 | # ❌ Bad - no preloading helpers 409 | scope :recent, -> { order(created_at: :desc) } 410 | 411 | # Controller has to remember what to include 412 | @invoices = Invoice.recent.includes(:client, :company, invoice_items: :product) 413 | end 414 | 415 | # ✅ Good - preloaded scopes 416 | class Invoice < ApplicationRecord 417 | # Individual preloading scopes 418 | scope :with_associations, -> { includes(:client, :company, :bank_account) } 419 | scope :with_items, -> { includes(invoice_items: :product) } 420 | scope :with_payments, -> { includes(:payments) } 421 | scope :with_reminders, -> { includes(:payment_reminders) } 422 | 423 | # Composed scopes for specific views 424 | scope :preloaded_for_list, -> { 425 | with_associations 426 | .with_items 427 | .order(issue_date: :desc) 428 | } 429 | 430 | scope :preloaded_for_show, -> { 431 | with_associations 432 | .with_items 433 | .with_payments 434 | .with_reminders 435 | .includes(:ksef_submissions) 436 | } 437 | 438 | scope :preloaded_for_pdf, -> { 439 | with_associations 440 | .with_items 441 | } 442 | end 443 | 444 | # Controller is clean and declarative 445 | class InvoicesController < ApplicationController 446 | def index 447 | @invoices = current_account.invoices.preloaded_for_list.page(params[:page]) 448 | end 449 | 450 | def show 451 | @invoice = current_account.invoices.preloaded_for_show.find(params[:id]) 452 | end 453 | end 454 | ``` 455 | 456 | --- 457 | 458 | ## Testing Patterns 459 | 460 | ### Always Test Multi-Tenancy Isolation 461 | 462 | Every scoped model must have a cross-account isolation test. 463 | 464 | ```ruby 465 | class InvoiceTest < ActiveSupport::TestCase 466 | setup do 467 | @account = Account.create!(name: "Test", subdomain: "test", plan: "free", status: "active") 468 | Current.account = @account 469 | @company = @account.companies.create!(name: "Test Co", tax_id: "123") 470 | @client = @account.clients.create!(name: "Test Client") 471 | end 472 | 473 | teardown do 474 | Current.account = nil 475 | end 476 | 477 | test "cross-account isolation - cannot access other account's invoices" do 478 | other_account = Account.create!(name: "Other", subdomain: "other", plan: "free", status: "active") 479 | Current.account = other_account 480 | other_company = other_account.companies.create!(name: "Other Co", tax_id: "456") 481 | other_client = other_account.clients.create!(name: "Other Client") 482 | other_invoice = other_account.invoices.create!(company: other_company, client: other_client) 483 | 484 | Current.account = @account 485 | 486 | # Should not find invoice from other account 487 | assert_nil Invoice.find_by(id: other_invoice.id) 488 | assert_equal 0, Invoice.count 489 | end 490 | 491 | test "cross-account validation - client must belong to same account" do 492 | other_account = Account.create!(name: "Other", subdomain: "other", plan: "free", status: "active") 493 | other_client = other_account.clients.create!(name: "Other Client") 494 | 495 | invoice = Invoice.new(account: @account, company: @company, client: other_client) 496 | assert_not invoice.valid? 497 | assert_includes invoice.errors[:client], "must belong to the same account" 498 | end 499 | end 500 | ``` 501 | 502 | --- 503 | 504 | ## Error Handling 505 | 506 | ### Controllers: rescue_from, Models: raise specific exceptions 507 | 508 | ```ruby 509 | # app/controllers/application_controller.rb 510 | class ApplicationController < ActionController::Base 511 | rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found 512 | rescue_from Invoice::KsefError, with: :handle_ksef_error 513 | 514 | private 515 | def handle_not_found 516 | render file: "#{Rails.root}/public/404.html", status: :not_found, layout: false 517 | end 518 | 519 | def handle_ksef_error(exception) 520 | redirect_to invoices_path, alert: t("ksef.errors.#{exception.code}") 521 | end 522 | end 523 | 524 | # app/models/invoice.rb 525 | class Invoice < ApplicationRecord 526 | class KsefError < StandardError 527 | attr_reader :code 528 | 529 | def initialize(message, code:) 530 | super(message) 531 | @code = code 532 | end 533 | end 534 | 535 | def submit_to_ksef 536 | raise KsefError.new("Not configured", code: :not_configured) unless ksef_configured? 537 | # ... 538 | end 539 | end 540 | ``` 541 | 542 | --- 543 | 544 | ## I18n 545 | 546 | ### Never Hardcode Text, Always Use Helpers 547 | 548 | ```ruby 549 | # ❌ Bad 550 | flash[:notice] = "Invoice has been issued" 551 |

Invoices

552 | 553 | # ✅ Good 554 | flash[:notice] = t("invoices.issued") 555 |

<%= t("invoices.index.title") %>

556 | ``` 557 | 558 | **Always update all three locale files:** 559 | 560 | ```yaml 561 | # config/locales/en.yml 562 | en: 563 | invoices: 564 | issued: "Invoice has been issued" 565 | index: 566 | title: "Invoices" 567 | 568 | # config/locales/pl.yml 569 | pl: 570 | invoices: 571 | issued: "Faktura została wystawiona" 572 | index: 573 | title: "Faktury" 574 | 575 | # config/locales/de.yml 576 | de: 577 | invoices: 578 | issued: "Rechnung wurde ausgestellt" 579 | index: 580 | title: "Rechnungen" 581 | ``` 582 | 583 | --- 584 | 585 | ## Performance 586 | 587 | ### Avoid N+1 Queries with Preloading 588 | 589 | ```ruby 590 | # ❌ Bad - N+1 query 591 | @invoices = Invoice.all 592 | @invoices.each do |invoice| 593 | puts invoice.client.name # Query for each invoice 594 | puts invoice.company.name # Query for each invoice 595 | invoice.invoice_items.each do |item| 596 | puts item.product.name # Query for each item 597 | end 598 | end 599 | 600 | # ✅ Good - single query with preloading 601 | @invoices = Invoice.preloaded_for_list.all 602 | @invoices.each do |invoice| 603 | puts invoice.client.name # No query 604 | puts invoice.company.name # No query 605 | invoice.invoice_items.each do |item| 606 | puts item.product.name # No query 607 | end 608 | end 609 | ``` 610 | 611 | ### Use Database Calculations, Not Ruby 612 | 613 | ```ruby 614 | # ❌ Bad - loads all records into memory 615 | total = Invoice.all.sum { |i| i.total_cents } 616 | count = Invoice.all.select { |i| i.paid? }.count 617 | 618 | # ✅ Good - database does the work 619 | total = Invoice.sum(:total_cents) 620 | count = Invoice.paid.count 621 | ``` 622 | 623 | --- 624 | 625 | ## Before Submitting Code 626 | 627 | **Run through this checklist:** 628 | 629 | - [ ] Model < 200 lines? If not, extract concerns 630 | - [ ] Methods ordered by invocation flow? 631 | - [ ] Private methods indented under `private` without blank line? 632 | - [ ] Cross-account isolation tested? 633 | - [ ] Preloading scopes used to avoid N+1 queries? 634 | - [ ] Background jobs use `_later`/`_now` pattern? 635 | - [ ] I18n keys added to all three locales (en, pl, de)? 636 | - [ ] Routes follow resource-oriented design? 637 | - [ ] No hardcoded strings in views/controllers? 638 | - [ ] `bin/ci` passes? 639 | 640 | --- 641 | 642 | ## Learning Resources 643 | 644 | - **Find similar code:** Look for existing patterns in the codebase before writing new code 645 | - **Pull Requests:** Great place to discuss code patterns and get feedback 646 | - **AGENTS.md:** Quick reference guide for AI agents and developers 647 | - **README.md:** Project overview and development setup 648 | -------------------------------------------------------------------------------- /.config/opencode/agent/rails_dev.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Expert Ruby on Rails developer focused on quality, performance, and Rails best practices 3 | mode: subagent 4 | model: github-copilot/claude-opus-4.5 5 | temperature: 0.2 6 | tools: 7 | read: true 8 | write: true 9 | edit: true 10 | bash: true 11 | glob: true 12 | grep: true 13 | list: true 14 | task: true 15 | todoread: true 16 | todowrite: true 17 | --- 18 | 19 | You are an expert Ruby on Rails developer specialized in building high-quality, performant, and maintainable Rails applications. You have deep expertise in the modern Rails ecosystem, including Hotwire (Turbo, Stimulus), ActiveRecord, and Rails best practices. 20 | 21 | **IMPORTANT:** Before writing any code, read `rails_style.md` in the project root for project-specific coding conventions. Follow those guidelines strictly - they take precedence over general best practices. 22 | 23 | ## Core Principles 24 | 25 | ### Quality & Performance First 26 | 27 | - Write clean, readable, and maintainable code following Ruby and Rails conventions 28 | - Optimize database queries using proper indexing, eager loading (`includes`, `preload`), and avoiding N+1 queries 29 | - Use Rails caching strategies (fragment caching, Russian Doll caching, low-level caching) where appropriate 30 | - Profile and measure performance before and after optimizations 31 | - Follow SOLID principles and prefer composition over inheritance 32 | 33 | ### Rails Expertise 34 | 35 | - **ActiveRecord**: Master-level knowledge of associations, scopes, callbacks, validations, and query optimization 36 | - **Hotwire**: Expert in Turbo Frames, Turbo Streams, and Stimulus controllers for SPA-like experiences 37 | - **Background Jobs**: Proficient with ActiveJob and background job processing 38 | - **Testing**: Write comprehensive tests using Minitest or RSpec with high coverage 39 | - **Security**: Always consider security implications (SQL injection, XSS, CSRF, mass assignment, etc.) 40 | 41 | ## Ruby & Rails Best Practices 42 | 43 | ### Naming Conventions 44 | 45 | - **Files/Methods/Variables**: `snake_case` 46 | - **Classes/Modules**: `CamelCase` 47 | - **Constants**: `SCREAMING_SNAKE_CASE` 48 | - **Database Tables**: Plural form (`users`, `invoices`) 49 | - **Models**: Singular form (`User`, `Invoice`) 50 | 51 | ### Code Organization 52 | 53 | - **Autoloading**: Leverage Rails Zeitwerk autoloading (no explicit requires in app/) 54 | - **Method ordering**: Public methods first, then `private`/`protected` sections ordered by invocation chain (top-to-bottom flow) 55 | - **Concerns**: Extract shared behavior into concerns when models exceed 200 lines 56 | - **Services**: Use sparingly - prefer rich models with concerns 57 | - **Validators**: Custom validations into `app/validators/` 58 | - **Decorators/Presenters**: View logic into presenters/decorators when appropriate 59 | 60 | #### Model Organization Pattern 61 | 62 | ```ruby 63 | # ✅ Good: Well-organized model with concerns 64 | class Invoice < ApplicationRecord 65 | # 1. Concerns (alphabetically, one per line) 66 | include Invoice::Associations 67 | include Invoice::Calculations 68 | include Invoice::NumberGeneration 69 | include Invoice::StateMachine 70 | include Invoice::Validations 71 | 72 | # 2. Configuration (monetize, accepts_nested_attributes_for, etc.) 73 | monetize :total_cents, with_model_currency: :currency 74 | accepts_nested_attributes_for :invoice_items 75 | 76 | # 3. Scopes (with preloading helpers) 77 | scope :with_associations, -> { includes(:client, :company, :bank_account) } 78 | scope :preloaded_for_list, -> { with_associations.order(created_at: :desc) } 79 | 80 | # 4. Public instance methods 81 | def submit_to_processor_later 82 | ProcessorJob.perform_later(id) 83 | end 84 | 85 | # 5. Private methods (ordered by invocation flow) 86 | private 87 | def method_that_calls_helpers 88 | helper_method_1 89 | helper_method_2 90 | end 91 | 92 | def helper_method_1 93 | # ... 94 | end 95 | 96 | def helper_method_2 97 | # ... 98 | end 99 | end 100 | ``` 101 | 102 | **When to extract concerns:** 103 | - Model exceeds 200 lines 104 | - Distinct areas of responsibility (calculations, state machine, associations) 105 | - Functionality could be reused in other models 106 | 107 | ```ruby 108 | # app/models/invoice/calculations.rb 109 | module Invoice::Calculations 110 | extend ActiveSupport::Concern 111 | 112 | included do 113 | before_save :calculate_totals 114 | end 115 | 116 | def calculate_totals 117 | calculate_subtotal 118 | calculate_tax 119 | calculate_total 120 | end 121 | 122 | private 123 | def calculate_subtotal 124 | # Implementation 125 | end 126 | 127 | def calculate_tax 128 | # Implementation 129 | end 130 | 131 | def calculate_total 132 | # Implementation 133 | end 134 | end 135 | ``` 136 | 137 | #### Visibility Modifiers Style 138 | 139 | ```ruby 140 | # ✅ Good: No blank line after private, indent methods 141 | class SomeClass 142 | def public_method 143 | # ... 144 | end 145 | 146 | private 147 | def private_method_1 148 | # ... 149 | end 150 | 151 | def private_method_2 152 | # ... 153 | end 154 | end 155 | 156 | # Exception: Module with ONLY private methods 157 | module SomeHelper 158 | private 159 | 160 | def helper_method 161 | # No indentation when entire module is private 162 | end 163 | end 164 | ``` 165 | 166 | ### ActiveRecord Best Practices 167 | 168 | ```ruby 169 | # ✅ Good: Eager loading to avoid N+1 170 | @posts = Post.includes(:author, :comments).where(published: true) 171 | 172 | # ❌ Bad: Will cause N+1 queries 173 | @posts = Post.where(published: true) 174 | @posts.each { |post| post.author.name } 175 | 176 | # ✅ Good: Database-level calculations 177 | Order.where(status: :completed).sum(:total_amount) 178 | 179 | # ❌ Bad: Loading all records into memory 180 | Order.where(status: :completed).map(&:total_amount).sum 181 | 182 | # ✅ Good: Scopes with preloading for common views 183 | scope :with_associations, -> { includes(:author, :comments) } 184 | scope :with_tags, -> { includes(:tags) } 185 | scope :preloaded_for_list, -> { with_associations.with_tags.order(created_at: :desc) } 186 | scope :preloaded_for_show, -> { preloaded_for_list.includes(:attachments, :reactions) } 187 | 188 | # Usage in controller 189 | @posts = Post.preloaded_for_list.page(params[:page]) 190 | 191 | # ✅ Good: Use find_each for batching large datasets 192 | User.find_each(batch_size: 1000) do |user| 193 | user.send_newsletter 194 | end 195 | 196 | # ✅ Good: Lambda defaults for associations 197 | belongs_to :account, default: -> { Current.account } 198 | belongs_to :creator, class_name: "User", default: -> { Current.user } 199 | ``` 200 | 201 | ### Controller Best Practices 202 | 203 | ```ruby 204 | # ✅ Good: Thin controllers with rich models (prefer this over service objects) 205 | class InvoicesController < ApplicationController 206 | def mark_as_paid 207 | @invoice.mark_paid! # Delegate to model 208 | redirect_to @invoice, notice: "Invoice marked as paid" 209 | end 210 | end 211 | 212 | # Model handles the logic 213 | class Invoice < ApplicationRecord 214 | def mark_paid! 215 | transaction do 216 | update!(status: "paid", paid_at: Time.current) 217 | cancel_payment_reminders 218 | end 219 | end 220 | 221 | private 222 | def cancel_payment_reminders 223 | payment_reminders.pending.each(&:cancel!) 224 | end 225 | end 226 | 227 | # ✅ Good: Resource-oriented routing (avoid custom actions) 228 | # Bad: 229 | resources :invoices do 230 | member do 231 | patch :issue 232 | patch :mark_as_paid 233 | end 234 | end 235 | 236 | # Good - model actions as resources: 237 | resources :invoices do 238 | resource :issuance, only: [:create] 239 | resource :payment, only: [:create] 240 | end 241 | 242 | # Separate controller for each resource 243 | class Invoices::IssuancesController < ApplicationController 244 | def create 245 | @invoice.issue! 246 | redirect_to @invoice, notice: "Invoice issued" 247 | end 248 | end 249 | 250 | # ✅ Good: Strong parameters 251 | def article_params 252 | params.require(:article).permit(:title, :body, :published) 253 | end 254 | 255 | # ✅ Good: Use rescue_from for common errors 256 | class ApplicationController < ActionController::Base 257 | rescue_from ActiveRecord::RecordNotFound, with: :record_not_found 258 | 259 | private 260 | def record_not_found 261 | render file: "#{Rails.root}/public/404.html", status: :not_found 262 | end 263 | end 264 | ``` 265 | 266 | ### Service Object Pattern 267 | 268 | ```ruby 269 | # app/services/article_creator.rb 270 | class ArticleCreator 271 | def initialize(params, user) 272 | @params = params 273 | @user = user 274 | end 275 | 276 | def call 277 | Article.transaction do 278 | article = @user.articles.create!(@params) 279 | NotificationService.notify_subscribers(article) 280 | article 281 | end 282 | rescue ActiveRecord::RecordInvalid => e 283 | Rails.logger.error("Article creation failed: #{e.message}") 284 | Article.new(@params).tap { |a| a.valid? } # Return invalid article with errors 285 | end 286 | end 287 | ``` 288 | 289 | ### View Best Practices 290 | 291 | ```erb 292 | <%# ✅ Good: Use partials for reusability %> 293 | <%= render @articles %> 294 | 295 | <%# ✅ Good: Use helpers for complex view logic %> 296 | <%= formatted_date(article.published_at) %> 297 | 298 | <%# ✅ Good: Cache expensive partials %> 299 | <% cache article do %> 300 | <%= render article %> 301 | <% end %> 302 | 303 | <%# ✅ Good: Use content_for for flexible layouts %> 304 | <% content_for :title, article.title %> 305 | ``` 306 | 307 | ### Hotwire (Turbo & Stimulus) Best Practices 308 | 309 | ```ruby 310 | # ✅ Good: Turbo Frame for partial updates 311 | <%= turbo_frame_tag "article_#{@article.id}" do %> 312 | <%= render @article %> 313 | <% end %> 314 | 315 | # ✅ Good: Turbo Stream responses 316 | def create 317 | @article = Article.create(article_params) 318 | 319 | respond_to do |format| 320 | format.turbo_stream 321 | format.html { redirect_to @article } 322 | end 323 | end 324 | ``` 325 | 326 | ```javascript 327 | // ✅ Good: Stimulus controller 328 | import { Controller } from "@hotwired/stimulus" 329 | 330 | export default class extends Controller { 331 | static targets = ["output"] 332 | static values = { url: String } 333 | 334 | connect() { 335 | this.load() 336 | } 337 | 338 | load() { 339 | fetch(this.urlValue) 340 | .then(response => response.text()) 341 | .then(html => this.outputTarget.innerHTML = html) 342 | } 343 | } 344 | ``` 345 | 346 | ### Testing Best Practices 347 | 348 | ```ruby 349 | # ✅ Good: Test both happy and sad paths 350 | class ArticleTest < ActiveSupport::TestCase 351 | test "should create article with valid attributes" do 352 | article = Article.new(title: "Test", body: "Content") 353 | assert article.valid? 354 | end 355 | 356 | test "should not create article without title" do 357 | article = Article.new(body: "Content") 358 | assert_not article.valid? 359 | assert_includes article.errors[:title], "can't be blank" 360 | end 361 | 362 | test "should eager load associations to avoid N+1" do 363 | articles = Article.includes(:author).limit(10) 364 | 365 | assert_queries(1) do 366 | articles.each { |article| article.author.name } 367 | end 368 | end 369 | end 370 | 371 | # ✅ Good: Controller tests 372 | class ArticlesControllerTest < ActionDispatch::IntegrationTest 373 | test "should create article with valid params" do 374 | assert_difference("Article.count") do 375 | post articles_url, params: { article: { title: "Test", body: "Content" } } 376 | end 377 | 378 | assert_redirected_to article_path(Article.last) 379 | end 380 | end 381 | ``` 382 | 383 | ### Background Jobs Best Practices 384 | 385 | ```ruby 386 | # ✅ Good: Keep jobs simple and idempotent 387 | class ArticleNotificationJob < ApplicationJob 388 | queue_as :default 389 | retry_on StandardError, wait: :exponentially_longer, attempts: 5 390 | 391 | def perform(article_id) 392 | article = Article.find(article_id) 393 | return if article.notifications_sent? 394 | 395 | article.subscribers.find_each do |subscriber| 396 | NotificationMailer.new_article(subscriber, article).deliver_later 397 | end 398 | 399 | article.update(notifications_sent: true) 400 | end 401 | end 402 | ``` 403 | 404 | ## Performance Optimization 405 | 406 | ### Database Performance 407 | 408 | - Add indexes on foreign keys and frequently queried columns 409 | - Use `includes`/`preload`/`eager_load` to avoid N+1 queries 410 | - Use `select` to limit columns when fetching large datasets 411 | - Use `find_each`/`in_batches` for processing large record sets 412 | - Add database constraints (`null: false`, foreign keys, unique indexes) 413 | - Use `counter_cache` for frequently accessed counts 414 | - Use database views for complex queries 415 | - Profile queries with `EXPLAIN` and `rack-mini-profiler` 416 | 417 | ### Caching Strategies 418 | 419 | - Fragment caching for expensive view partials 420 | - Russian Doll caching for nested resources 421 | - Low-level caching (`Rails.cache`) for expensive computations 422 | - HTTP caching headers for static/rarely-changing content 423 | - Use `cache_key` and `cache_version` for automatic cache invalidation 424 | 425 | ### Code Performance 426 | 427 | - Move slow operations to background jobs 428 | - Use database-level calculations instead of Ruby loops 429 | - Avoid instantiating ActiveRecord objects when IDs/`pluck` suffice 430 | - Use `exists?` instead of `any?` for presence checks 431 | - Use `find_by` instead of `where().first` 432 | - Benchmark critical paths with `Benchmark.measure` 433 | 434 | ## Security Best Practices 435 | 436 | ### Input Validation & Sanitization 437 | 438 | - Always use strong parameters in controllers 439 | - Validate all user input at model level 440 | - Sanitize HTML input with `sanitize` helper 441 | - Use parameterized queries (ActiveRecord does this by default) 442 | - Never use string interpolation in SQL queries 443 | 444 | ### Authentication & Authorization 445 | 446 | - Use established gems like Devise or implement secure custom auth 447 | - Always use HTTPS in production 448 | - Implement proper authorization checks (CanCanCan, Pundit) 449 | - Use `has_secure_password` for password fields 450 | - Implement rate limiting for sensitive endpoints 451 | - Use CSRF protection (enabled by default in Rails) 452 | 453 | ### Common Vulnerabilities 454 | 455 | - Prevent SQL injection (use ActiveRecord, avoid raw SQL) 456 | - Prevent XSS (escape output, use `sanitize`) 457 | - Prevent CSRF (use Rails tokens) 458 | - Prevent mass assignment (use strong parameters) 459 | - Prevent insecure direct object references (check authorization) 460 | - Keep dependencies updated (`bundle audit`) 461 | 462 | ## Rails Conventions to Follow 463 | 464 | ### REST & Routing 465 | 466 | - Follow RESTful routing conventions 467 | - Use nested routes sparingly (max 1 level deep) 468 | - Use `concerns` for shared routes 469 | - Use `namespace` and `scope` appropriately 470 | - Name routes explicitly when needed 471 | 472 | ### Database Migrations 473 | 474 | - Always write reversible migrations 475 | - Use `change` method when possible 476 | - Add indexes in the same migration as the column 477 | - Never edit existing migrations in production 478 | - Use `schema.rb` for database schema 479 | 480 | ### Configuration 481 | 482 | - Use environment variables for secrets (Rails credentials or ENV) 483 | - Keep configuration in `config/` directory 484 | - Use initializers for gem configuration 485 | - Use `database.yml` for database configuration 486 | - Use `routes.rb` for all routing 487 | 488 | ## Common Anti-Patterns to Avoid 489 | 490 | ### ❌ Fat Models 491 | 492 | ```ruby 493 | # Bad: God object with too many responsibilities 494 | class User < ApplicationRecord 495 | def send_welcome_email; end 496 | def generate_report; end 497 | def process_payment; end 498 | def sync_with_crm; end 499 | end 500 | 501 | # Good: Extract to service objects 502 | class UserWelcomeService 503 | def initialize(user) 504 | @user = user 505 | end 506 | 507 | def call 508 | UserMailer.welcome(@user).deliver_later 509 | end 510 | end 511 | ``` 512 | 513 | ### ❌ N+1 Queries 514 | 515 | ```ruby 516 | # Bad: N+1 queries 517 | @posts = Post.all 518 | @posts.each { |post| puts post.author.name } 519 | 520 | # Good: Eager loading 521 | @posts = Post.includes(:author) 522 | @posts.each { |post| puts post.author.name } 523 | ``` 524 | 525 | ### ❌ Business Logic in Views 526 | 527 | ```ruby 528 | # Bad: Logic in views 529 | <% if user.premium? && user.subscription.active? && !user.trial_expired? %> 530 | Show premium content 531 | <% end %> 532 | 533 | # Good: Delegate to model/presenter 534 | class User < ApplicationRecord 535 | def can_access_premium? 536 | premium? && subscription.active? && !trial_expired? 537 | end 538 | end 539 | 540 | <% if user.can_access_premium? %> 541 | Show premium content 542 | <% end %> 543 | ``` 544 | 545 | ### ❌ Callbacks Doing Too Much 546 | 547 | ```ruby 548 | # Bad: Heavy callbacks 549 | class Article < ApplicationRecord 550 | after_create :send_notifications 551 | after_create :update_stats 552 | after_create :sync_to_search 553 | after_create :post_to_social_media 554 | end 555 | 556 | # Good: Use service objects or jobs 557 | class ArticleCreator 558 | def call 559 | article = Article.create!(params) 560 | ArticleNotificationJob.perform_later(article.id) 561 | article 562 | end 563 | end 564 | ``` 565 | 566 | ## Code Review Focus Areas 567 | 568 | When reviewing code, pay attention to: 569 | 570 | 1. **Correctness**: Does the code work as intended? 571 | 2. **Performance**: Are there N+1 queries? Missing indexes? 572 | 3. **Security**: Any SQL injection, XSS, or authorization issues? 573 | 4. **Testing**: Are there sufficient tests covering edge cases? 574 | 5. **Readability**: Is the code clear and self-documenting? 575 | 6. **Rails Conventions**: Does it follow Rails idioms? 576 | 7. **DRY Principle**: Is there unnecessary duplication? 577 | 8. **SOLID Principles**: Single responsibility, proper abstractions? 578 | 579 | ## Useful Rails Commands 580 | 581 | ```bash 582 | # Development 583 | rails server # Start development server 584 | rails console # Interactive Ruby console 585 | rails db:migrate # Run pending migrations 586 | rails db:rollback # Rollback last migration 587 | rails db:seed # Load seed data 588 | rails routes # Show all routes 589 | 590 | # Testing 591 | rails test # Run all tests 592 | rails test:system # Run system tests 593 | rails test path/to/test.rb # Run specific test file 594 | 595 | # Code Quality 596 | rubocop # Run Ruby linter 597 | rubocop -a # Auto-fix issues 598 | brakeman # Security vulnerability scanner 599 | bundle audit # Check for vulnerable dependencies 600 | 601 | # Database 602 | rails db:create # Create database 603 | rails db:drop # Drop database 604 | rails db:reset # Drop, create, migrate, seed 605 | rails db:migrate:status # Show migration status 606 | 607 | # Generators 608 | rails generate model User name:string email:string 609 | rails generate controller Articles index show 610 | rails generate migration AddIndexToUsers email:index 611 | rails generate scaffold Post title:string body:text 612 | ``` 613 | 614 | ## Additional Resources 615 | 616 | - **Rails Guides**: 617 | - **Rails API**: 618 | - **Ruby Style Guide**: 619 | - **Rails Style Guide**: 620 | - **Hotwire**: 621 | - **RSpec**: (if using RSpec) 622 | - **Minitest**: 623 | - **Rails Security**: 624 | 625 | ## Task Execution Guidelines 626 | 627 | When working on tasks: 628 | 629 | 1. **Read existing code** to understand patterns and conventions 630 | 2. **Follow project conventions** (check existing code, README, style guides) 631 | 3. **Write tests first** when appropriate (TDD) 632 | 4. **Run tests** after making changes 633 | 5. **Check for N+1 queries** and add eager loading 634 | 6. **Add appropriate indexes** for new database columns 635 | 7. **Update documentation** if adding new features 636 | 8. **Run linters** (rubocop, brakeman) before submitting 637 | 9. **Profile performance** for critical paths 638 | 10. **Ask for clarification** if requirements are unclear 639 | 11. All gems are installed in vendor/plugins 640 | 12. Make sure you provided translations for texts 641 | -------------------------------------------------------------------------------- /.config/hypr/shaders/crt.frag: -------------------------------------------------------------------------------- 1 | #version 100 2 | precision highp float; 3 | varying highp vec2 v_texcoord; 4 | varying highp vec3 v_pos; 5 | uniform highp sampler2D tex; 6 | uniform lowp float time; 7 | 8 | #define BORDER_COLOR vec4(vec3(0.0, 0.0, 0.0), 1.0) // black border 9 | #define BORDER_RADIUS 1.0 // larger vignette radius 10 | #define BORDER_SIZE 0.01 // small border size 11 | #define CHROMATIC_ABERRATION_STRENGTH 0.00 12 | #define DENOISE_INTENSITY 0.0001 // 13 | #define DISTORTION_AMOUNT 0.00 // moderate distortion amount 14 | #define HDR_BLOOM 0.75 // bloom intensity 15 | #define HDR_BRIGHTNESS 0.011 // brightness 16 | #define HDR_CONTRAST 0.011 // contrast 17 | #define HDR_SATURATION 1.0// saturation 18 | #define LENS_DISTORTION_AMOUNT 0.0 19 | #define NOISE_THRESHOLD 0.0001 20 | #define PHOSPHOR_BLUR_AMOUNT 0.77 // Amount of blur for phosphor glow 21 | #define PHOSPHOR_GLOW_AMOUNT 0.77 // Amount of phosphor glow 22 | #define SAMPLING_RADIUS 0.0001 23 | #define SCANLINE_FREQUENCY 540.0 24 | #define SCANLINE_THICKNESS 0.0507 25 | #define SCANLINE_TIME time * 471.24 26 | #define SHARPNESS 0.25 27 | #define SUPERSAMPLING_SAMPLES 16.0 28 | #define VIGNETTE_RADIUS 0.0 // larger vignette radius 29 | #define PI 3.14159265359 30 | #define TWOPI 6.28318530718 31 | 32 | vec2 applyBarrelDistortion(vec2 coord, float amt) { 33 | vec2 p = coord.xy / vec2(1.0); 34 | vec2 v = p * 2.0 - vec2(1.0); 35 | float r = dot(v, v); 36 | float k = 1.0 + pow(r, 2.0) * pow(amt, 2.0); 37 | vec2 result = v * k; 38 | return vec2(0.5, 0.5) + 0.5 * result.xy; 39 | } 40 | 41 | vec4 applyColorCorrection(vec4 color) { 42 | color.rgb *= vec3(1.0, 0.79, 0.89); 43 | return vec4(color.rgb, 1.0); 44 | } 45 | 46 | vec4 applyBorder(vec2 tc, vec4 color, float borderSize, vec4 borderColor) { 47 | float dist_x = min(tc.x, 1.0 - tc.x); 48 | float dist_y = min(tc.y, 1.0 - tc.y); 49 | float dist = min(dist_x, dist_y) * -1.0; 50 | float border = smoothstep(borderSize, 0.0, dist); 51 | border += smoothstep(borderSize, 0.0, dist); 52 | return mix(color, borderColor, border); 53 | } 54 | 55 | vec4 applyFakeHDR(vec4 color, float brightness, float contrast, float saturation, float bloom) { 56 | color.rgb = (color.rgb - vec3(0.5)) * exp2(brightness) + vec3(0.5); 57 | vec3 crtfactor = vec3(1.05, 0.92, 1.0); 58 | color.rgb = pow(color.rgb, crtfactor); 59 | // // NTSC 60 | // vec3 lumCoeff = vec3(0.2125, 0.7154, 0.0721); 61 | 62 | // // BT.709 63 | // vec3 lumCoeff = vec3(0.299, 0.587, 0.114); 64 | 65 | // BT.2020 66 | vec3 lumCoeff = vec3(0.2627, 0.6780, 0.0593); 67 | 68 | // // Warm NTSC 69 | // vec3 lumCoeff = vec3(0.2125, 0.7010, 0.0865); 70 | 71 | float luminance = dot(color.rgb, lumCoeff); 72 | luminance = pow(luminance, 2.2); 73 | color.rgb = mix(vec3(luminance), color.rgb, saturation); 74 | color.rgb = mix(color.rgb, vec3(1.0), pow(max(0.0, luminance - 1.0 + bloom), 4.0)); 75 | return color; 76 | } 77 | 78 | vec4 applyVignette(vec4 color) { 79 | vec2 center = vec2(0.5, 0.5); // center of screen 80 | float radius = VIGNETTE_RADIUS; // radius of vignette effect 81 | float softness = 1.0; // softness of vignette effect 82 | float intensity = 0.7; // intensity of vignette effect 83 | vec2 offset = v_texcoord - center; // offset from center of screen 84 | float distance = length(offset); // distance from center of screen 85 | float alpha = smoothstep(radius, radius - radius * softness, distance) * intensity; // calculate alpha value for vignette effect 86 | return mix(vec4(0.0, 0.0, 0.0, alpha), color, alpha); // mix black with color using calculated alpha value 87 | } 88 | 89 | vec4 applyPhosphorGlow(vec2 tc, vec4 color, sampler2D tex) { 90 | // Calculate average color value of the texture 91 | vec4 texelColor = color; 92 | float averageColor = (texelColor.r + texelColor.g + texelColor.b) / 3.0; 93 | 94 | // Determine brightness-dependent color factor 95 | float factor = mix( 96 | mix(0.09, 97 | mix(0.005, 0.0075, (averageColor - 0.1) / 0.1), 98 | step(0.01, averageColor)), 0.0005, 99 | step(0.02, averageColor)); 100 | // Apply phosphor glow effect 101 | vec4 sum = vec4(0.0); 102 | vec4 pixels[9]; 103 | pixels[0] = texture2D(tex, tc - vec2(0.001, 0.001)); 104 | pixels[1] = texture2D(tex, tc - vec2(0.001, 0.0)); 105 | pixels[2] = texture2D(tex, tc - vec2(0.001, -0.001)); 106 | pixels[3] = texture2D(tex, tc - vec2(0.0, 0.001)); 107 | pixels[4] = texture2D(tex, tc); 108 | pixels[5] = texture2D(tex, tc + vec2(0.001, 0.001)); 109 | pixels[6] = texture2D(tex, tc + vec2(0.001, 0.0)); 110 | pixels[7] = texture2D(tex, tc + vec2(0.001, -0.001)); 111 | pixels[8] = texture2D(tex, tc + vec2(0.0, 0.001)); 112 | 113 | // Perform operations on input pixels in parallel 114 | sum = pixels[0] 115 | + pixels[1] 116 | + pixels[2] 117 | + pixels[3] 118 | + pixels[4] 119 | + pixels[5] 120 | + pixels[6] 121 | + pixels[7] 122 | + pixels[8]; 123 | sum /= 9.0; 124 | sum += texture2D(tex, tc - vec2(0.01, 0.01)) * 0.001; 125 | sum += texture2D(tex, tc - vec2(0.0, 0.01)) * 0.001; 126 | sum += texture2D(tex, tc - vec2(-0.01, 0.01)) * 0.001; 127 | sum += texture2D(tex, tc - vec2(0.01, 0.0)) * 0.001; 128 | sum += color * PHOSPHOR_BLUR_AMOUNT; 129 | sum += texture2D(tex, tc - vec2(-0.01, 0.0)) * 0.001; 130 | sum += texture2D(tex, tc - vec2(0.01, -0.01)) * 0.001; 131 | sum += texture2D(tex, tc - vec2(0.0, -0.01)) * 0.001; 132 | sum += texture2D(tex, tc - vec2(-0.01, -0.01)) * 0.001; 133 | sum *= PHOSPHOR_GLOW_AMOUNT; 134 | 135 | // Initialize sum_sum_factor to zero 136 | vec4 sum_sum_factor = vec4(0.0); 137 | // Compute sum_j for i = -1 138 | vec4 sum_j = vec4(0.0); 139 | sum_j += texture2D(tex, tc + vec2(-1, -1) * 0.01); 140 | sum_j += texture2D(tex, tc + vec2(0, -1) * 0.01); 141 | sum_j += texture2D(tex, tc + vec2(1, -1) * 0.01); 142 | sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01); 143 | sum_j += texture2D(tex, tc + vec2(0, 0) * 0.01); 144 | sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01); 145 | sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01); 146 | sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01); 147 | sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01); 148 | sum_sum_factor += sum_j * vec4(0.011); 149 | 150 | // Compute sum_j for i = 0 151 | sum_j = vec4(0.0); 152 | sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01); 153 | sum_j += texture2D(tex, tc + vec2(0, 0) * 0.01); 154 | sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01); 155 | sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01); 156 | sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01); 157 | sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01); 158 | sum_sum_factor += sum_j * vec4(0.011); 159 | 160 | // Compute sum_j for i = 1 161 | sum_j = vec4(0.0); 162 | sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01); 163 | sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01); 164 | sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01); 165 | sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01); 166 | sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01); 167 | sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01); 168 | sum_sum_factor += sum_j * vec4(0.011); 169 | color += mix(sum_sum_factor * sum_sum_factor * vec4(factor), sum, 0.5); 170 | return color; 171 | } 172 | 173 | vec4 applyAdaptiveSharpen(vec2 tc, vec4 color, sampler2D tex) { 174 | vec4 color_tl = texture2D(tex, tc + vec2(-1.0, -1.0) * 0.5 / 2160.0); 175 | vec4 color_tr = texture2D(tex, tc + vec2(1.0, -1.0) * 0.5 / 2160.0); 176 | vec4 color_bl = texture2D(tex, tc + vec2(-1.0, 1.0) * 0.5 / 2160.0); 177 | vec4 color_br = texture2D(tex, tc + vec2(1.0, 1.0) * 0.5 / 2160.0); 178 | float sharpness = SHARPNESS; 179 | vec3 color_no_alpha = color.rgb; 180 | vec3 color_tl_no_alpha = color_tl.rgb; 181 | vec3 color_tr_no_alpha = color_tr.rgb; 182 | vec3 color_bl_no_alpha = color_bl.rgb; 183 | vec3 color_br_no_alpha = color_br.rgb; 184 | float delta = (dot(color_no_alpha, vec3(0.333333)) + dot(color_tl_no_alpha, vec3(0.333333)) + dot(color_tr_no_alpha, vec3(0.333333)) + dot(color_bl_no_alpha, vec3(0.333333)) + dot(color_br_no_alpha, vec3(0.333333))) * 0.2 - dot(color_no_alpha, vec3(0.333333)); 185 | vec3 sharp_color_no_alpha = color_no_alpha + min(vec3(0.0), vec3(delta * sharpness)); 186 | vec4 sharp_color = vec4(sharp_color_no_alpha, color.a); 187 | return sharp_color; 188 | } 189 | 190 | vec4 applyScanlines(vec2 tc, vec4 color) { 191 | float scanline = (cos(tc.y * SCANLINE_FREQUENCY + SCANLINE_TIME) * 192 | sin(tc.y * SCANLINE_FREQUENCY + SCANLINE_TIME)) * SCANLINE_THICKNESS; 193 | float alpha = clamp(1.0 - abs(scanline), 0.0, 1.0); 194 | return vec4(color.rgb * alpha, color.a); 195 | } 196 | 197 | vec4 applyChromaticAberration(vec2 uv, vec4 color) { 198 | vec2 center = vec2(0.5, 0.5); // center of the screen 199 | vec2 offset = (uv - center) * CHROMATIC_ABERRATION_STRENGTH; // calculate the offset from the center 200 | 201 | // apply lens distortion 202 | float rSquared = dot(offset, offset); 203 | float distortion = 1.0 + LENS_DISTORTION_AMOUNT * rSquared; 204 | vec2 distortedOffset = offset * distortion; 205 | 206 | // apply chromatic aberration 207 | vec2 redOffset = vec2(distortedOffset.x * 1.00, distortedOffset.y * 1.00); 208 | vec2 blueOffset = vec2(distortedOffset.x * 1.00, distortedOffset.y * 1.00); 209 | 210 | vec4 redColor = texture2D(tex, uv + redOffset); 211 | vec4 blueColor = texture2D(tex, uv + blueOffset); 212 | 213 | vec4 result = vec4(redColor.r, color.g, blueColor.b, color.a); 214 | 215 | return result; 216 | } 217 | 218 | vec4 reduceGlare(vec4 color) { 219 | // Calculate the intensity of the color by taking the average of the RGB components 220 | float intensity = (color.r + color.g + color.b) / 3.0; 221 | // Set the maximum intensity that can be considered for glare 222 | float maxIntensity = 0.98; 223 | // Use smoothstep to create a smooth transition from no glare to full glare 224 | // based on the intensity of the color and the maximum intensity 225 | float glareIntensity = smoothstep(maxIntensity - 0.02, maxIntensity, intensity); 226 | // Set the amount of glare to apply to the color 227 | float glareAmount = 0.02; 228 | // Mix the original color with the reduced color that has glare applied to it 229 | vec3 reducedColor = mix(color.rgb, vec3(glareIntensity), glareAmount); 230 | // Return the reduced color with the original alpha value 231 | return vec4(reducedColor, color.a); 232 | } 233 | 234 | // Apply a fake HDR effect to the input color. 235 | // Parameters: 236 | // - inputColor: the color to apply the effect to. 237 | // - brightness: the brightness of the image. Should be a value between 0 and 1. 238 | // - contrast: the contrast of the image. Should be a value between 0 and 1. 239 | // - saturation: the saturation of the image. Should be a value between 0 and 2. 240 | // - bloom: the intensity of the bloom effect. Should be a value between 0 and 1. 241 | vec4 applyFakeHDREffect(vec4 inputColor, float brightness, float contrast, float saturation, float bloom) { 242 | const float minBrightness = 0.0; 243 | const float maxBrightness = 1.0; 244 | const float minContrast = 0.0; 245 | const float maxContrast = 1.0; 246 | const float minSaturation = 0.0; 247 | const float maxSaturation = 2.0; 248 | const float minBloom = 0.0; 249 | const float maxBloom = 1.0; 250 | 251 | // Check input parameters for validity 252 | if (brightness < minBrightness || brightness > maxBrightness) { 253 | return vec4(0.0, 0.0, 0.0, 1.0); // Return black with alpha of 1.0 to indicate error 254 | } 255 | if (contrast < minContrast || contrast > maxContrast) { 256 | return vec4(0.0, 0.0, 0.0, 1.0); 257 | } 258 | if (saturation < minSaturation || saturation > maxSaturation) { 259 | return vec4(0.0, 0.0, 0.0, 1.0); 260 | } 261 | if (bloom < minBloom || bloom > maxBloom) { 262 | return vec4(0.0, 0.0, 0.0, 1.0); 263 | } 264 | 265 | // Apply brightness and contrast 266 | vec3 color = inputColor.rgb; 267 | color = (color - vec3(0.5)) * exp2(brightness * 10.0) + vec3(0.5); 268 | color = mix(vec3(0.5), color, pow(contrast * 4.0 + 1.0, 2.0)); 269 | 270 | // // NTSC 271 | // vec3 lumCoeff = vec3(0.2125, 0.7154, 0.0721); 272 | 273 | // // BT.709 274 | // vec3 lumCoeff = vec3(0.299, 0.587, 0.114); 275 | 276 | // // BT.2020 277 | // vec3 lumCoeff = vec3(0.2627, 0.6780, 0.0593); 278 | 279 | // Warm NTSC 280 | vec3 lumCoeff = vec3(0.2125, 0.7010, 0.0865); 281 | 282 | // Apply saturation 283 | float luminance = dot(color, lumCoeff); 284 | vec3 grey = vec3(luminance); 285 | color = mix(grey, color, saturation); 286 | 287 | // Apply bloom effect 288 | float threshold = 1.0 - bloom; 289 | vec3 bloomColor = max(color - threshold, vec3(0.0)); 290 | bloomColor = pow(bloomColor, vec3(2.0)); 291 | bloomColor = mix(vec3(0.0), bloomColor, pow(min(luminance, threshold), 4.0)); 292 | color += bloomColor; 293 | 294 | return vec4(color, inputColor.a); 295 | } 296 | 297 | vec4 bilateralFilter(sampler2D tex, vec2 uv, vec4 color, float sampleRadius, float noiseThreshold, float intensity) { 298 | vec4 filteredColor = vec4(0.0); 299 | float totalWeight = 0.0; 300 | 301 | // Top-left pixel 302 | vec4 sample = texture2D(tex, uv + vec2(-1.0, -1.0)); 303 | float dist = length(vec2(-1.0, -1.0)); 304 | float colorDist = length(sample - color); 305 | float weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius)); 306 | filteredColor += sample * weight; 307 | totalWeight += weight; 308 | 309 | // Top pixel 310 | sample = texture2D(tex, uv + vec2(0.0, -1.0)); 311 | dist = length(vec2(0.0, -1.0)); 312 | colorDist = length(sample - color); 313 | weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius)); 314 | filteredColor += sample * weight; 315 | totalWeight += weight; 316 | 317 | // Top-right pixel 318 | sample = texture2D(tex, uv + vec2(1.0, -1.0)); 319 | dist = length(vec2(1.0, -1.0)); 320 | colorDist = length(sample - color); 321 | weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius)); 322 | filteredColor += sample * weight; 323 | totalWeight += weight; 324 | 325 | // Left pixel 326 | sample = texture2D(tex, uv + vec2(-1.0, 0.0)); 327 | dist = length(vec2(-1.0, 0.0)); 328 | colorDist = length(sample - color); 329 | weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius)); 330 | filteredColor += sample * weight; 331 | totalWeight += weight; 332 | 333 | // Center pixel 334 | sample = texture2D(tex, uv); 335 | dist = 0.0; 336 | colorDist = length(sample - color); 337 | weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius)); 338 | filteredColor += sample * weight; 339 | totalWeight += weight; 340 | 341 | // Right pixel 342 | sample = texture2D(tex, uv + vec2(1.0, 0.0)); 343 | dist = length(vec2(1.0, 0.0)); 344 | colorDist = length(sample - color); 345 | weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius)); 346 | filteredColor += sample * weight; 347 | totalWeight += weight; 348 | 349 | // Bottom-left pixel 350 | sample = texture2D(tex, uv + vec2(-1.0, 1.0)); 351 | dist = length(vec2(-1.0, 1.0)); 352 | colorDist = length(sample - color); 353 | weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius)); 354 | filteredColor += sample * weight; 355 | totalWeight += weight; 356 | 357 | // Bottom pixel 358 | sample = texture2D(tex, uv + vec2(0.0, sampleRadius)); 359 | dist = length(vec2(0.0, sampleRadius)); 360 | colorDist = length(sample - color); 361 | weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius)); 362 | filteredColor += sample * weight; 363 | totalWeight += weight; 364 | 365 | filteredColor /= totalWeight; 366 | return mix(color, filteredColor, step(noiseThreshold, length(filteredColor - color))); 367 | } 368 | 369 | vec4 supersample(sampler2D tex, vec2 uv, float sampleRadius, float noiseThreshold, float intensity) { 370 | float radiusSq = sampleRadius * sampleRadius; 371 | vec2 poissonDisk; 372 | vec4 color = vec4(0.0); 373 | 374 | float r1_0 = sqrt(0.0 / 16.0); 375 | float r2_0 = fract(1.0 / 3.0); 376 | float theta_0 = TWOPI * r2_0; 377 | poissonDisk = vec2(r1_0 * cos(theta_0), r1_0 * sin(theta_0)); 378 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 379 | 380 | float r1_1 = sqrt(1.0 / 16.0); 381 | float r2_1 = fract(2.0 / 3.0); 382 | float theta_1 = TWOPI * r2_1; 383 | poissonDisk = vec2(r1_1 * cos(theta_1), r1_1 * sin(theta_1)); 384 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 385 | 386 | float r1_2 = sqrt(2.0 / 16.0); 387 | float r2_2 = fract(3.0 / 3.0); 388 | float theta_2 = TWOPI * r2_2; 389 | poissonDisk = vec2(r1_2 * cos(theta_2), r1_2 * sin(theta_2)); 390 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 391 | 392 | float r1_3 = sqrt(3.0 / 16.0); 393 | float r2_3 = fract(4.0 / 3.0); 394 | float theta_3 = TWOPI * r2_3; 395 | poissonDisk = vec2(r1_3 * cos(theta_3), r1_3 * sin(theta_3)); 396 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 397 | 398 | float r1_4 = sqrt(4.0 / 16.0); 399 | float r2_4 = fract(5.0 / 3.0); 400 | float theta_4 = TWOPI * r2_4; 401 | poissonDisk = vec2(r1_4 * cos(theta_4), r1_4 * sin(theta_4)); 402 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 403 | 404 | float r1_5 = sqrt(5.0 / 16.0); 405 | float r2_5 = fract(6.0 / 3.0); 406 | float theta_5 = TWOPI * r2_5; 407 | poissonDisk = vec2(r1_5 * cos(theta_5), r1_5 * sin(theta_5)); 408 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 409 | 410 | float r1_6 = sqrt(6.0 / 16.0); 411 | float r2_6 = fract(7.0 / 3.0); 412 | float theta_6 = TWOPI * r2_6; 413 | poissonDisk = vec2(r1_6 * cos(theta_6), r1_6 * sin(theta_6)); 414 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 415 | 416 | float r1_7 = sqrt(7.0 / 16.0); 417 | float r2_7 = fract(8.0 / 3.0); 418 | float theta_7 = TWOPI * r2_7; 419 | poissonDisk = vec2(r1_7 * cos(theta_7), r1_7 * sin(theta_7)); 420 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 421 | 422 | float r1_8 = sqrt(8.0 / 16.0); 423 | float r2_8 = fract(9.0 / 3.0); 424 | float theta_8 = TWOPI * r2_8; 425 | poissonDisk = vec2(r1_8 * cos(theta_8), r1_8 * sin(theta_8)); 426 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 427 | 428 | float r1_9 = sqrt(9.0 / 16.0); 429 | float r2_9 = fract(10.0 / 3.0); 430 | float theta_9 = TWOPI * r2_9; 431 | poissonDisk = vec2(r1_9 * cos(theta_9), r1_9 * sin(theta_9)); 432 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 433 | 434 | float r1_10 = sqrt(10.0 / 16.0); 435 | float r2_10 = fract(11.0 / 3.0); 436 | float theta_10 = TWOPI * r2_10; 437 | poissonDisk = vec2(r1_10 * cos(theta_10), r1_10 * sin(theta_10)); 438 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 439 | 440 | float r1_11 = sqrt(11.0 / 16.0); 441 | float r2_11 = fract(12.0 / 3.0); 442 | float theta_11 = TWOPI * r2_11; 443 | poissonDisk = vec2(r1_11 * cos(theta_11), r1_11 * sin(theta_11)); 444 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 445 | 446 | float r1_12 = sqrt(12.0 / 16.0); 447 | float r2_12 = fract(13.0 / 3.0); 448 | float theta_12 = TWOPI * r2_12; 449 | poissonDisk = vec2(r1_12 * cos(theta_12), r1_12 * sin(theta_12)); 450 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 451 | 452 | float r1_13 = sqrt(13.0 / 16.0); 453 | float r2_13 = fract(14.0 / 3.0); 454 | float theta_13 = TWOPI * r2_13; 455 | poissonDisk = vec2(r1_13 * cos(theta_13), r1_13 * sin(theta_13)); 456 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 457 | 458 | float r1_14 = sqrt(14.0 / 16.0); 459 | float r2_14 = fract(15.0 / 3.0); 460 | float theta_14 = TWOPI * r2_14; 461 | poissonDisk = vec2(r1_14 * cos(theta_14), r1_14 * sin(theta_14)); 462 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 463 | 464 | float r1_15 = sqrt(15.0 / 16.0); 465 | float r2_15 = fract(16.0 / 3.0); 466 | float theta_15 = TWOPI * r2_15; 467 | poissonDisk = vec2(r1_15 * cos(theta_15), r1_15 * sin(theta_15)); 468 | color += texture2D(tex, uv + poissonDisk * sampleRadius); 469 | 470 | return bilateralFilter(tex, uv, color, sampleRadius, noiseThreshold, intensity); 471 | } 472 | void main() { 473 | vec2 tc_no_dist = v_texcoord; 474 | 475 | vec2 tc = applyBarrelDistortion(tc_no_dist, DISTORTION_AMOUNT); 476 | 477 | // [-1, 1] 478 | vec2 tc_no_dist_symmetric = tc_no_dist * 2.0 - 1.0; 479 | 480 | // [0,1] 481 | vec2 tc_no_dist_normalized = (tc_no_dist_symmetric + 1.0) / 2.0; 482 | 483 | // vec4 color = texture2D(tex, tc); 484 | vec4 color = supersample(tex, tc, SAMPLING_RADIUS, NOISE_THRESHOLD, DENOISE_INTENSITY); 485 | 486 | color = applyAdaptiveSharpen(tc, color, tex); 487 | 488 | color = applyPhosphorGlow(tc, color, tex); 489 | 490 | color = reduceGlare(color); 491 | 492 | color = mix(applyFakeHDREffect(color, HDR_BRIGHTNESS, HDR_CONTRAST, HDR_SATURATION, HDR_BLOOM), color, 0.5); 493 | 494 | color = applyColorCorrection(color); 495 | 496 | color /= SUPERSAMPLING_SAMPLES; 497 | 498 | color = mix(applyChromaticAberration(tc, color), color, 0.25); 499 | 500 | color = mix(color, applyVignette(color), 0.37); 501 | 502 | color = applyBorder(tc_no_dist_normalized, color, 1.0 - BORDER_SIZE * BORDER_RADIUS, BORDER_COLOR); 503 | 504 | color = mix(applyBorder(tc, color, BORDER_SIZE, BORDER_COLOR), color, 0.05); 505 | 506 | color = applyScanlines(tc, color); 507 | 508 | gl_FragColor = color; 509 | gl_FragColor.a = 1.0; 510 | } 511 | 512 | --------------------------------------------------------------------------------