├── .gitignore ├── hypr ├── xdph.conf ├── exit.sh ├── resolution.sh ├── terminal.sh ├── toggle-input.sh ├── hypridle.conf ├── screen-record.sh ├── launch-discourse.sh ├── toggle-master-layout.sh ├── edit-discourse.sh ├── countdown ├── hyprlock.conf └── hyprland.conf ├── vim ├── update.bat ├── setup └── vimrc ├── nvim ├── data │ └── plenary │ │ └── filetypes │ │ └── gjs.lua ├── init.lua ├── lua │ ├── config │ │ ├── lazy.lua │ │ ├── options.lua │ │ ├── keymaps.lua │ │ ├── autocmds.lua │ │ └── test_runner.lua │ └── plugins │ │ ├── tools.lua │ │ ├── git.lua │ │ ├── ui.lua │ │ ├── ai.lua │ │ └── coding.lua ├── AGENTS.md └── lazy-lock.json ├── i3 ├── edit_discourse ├── picom ├── i3-resize ├── status.toml ├── lock-screen ├── countdown ├── config_laptop ├── config └── i3-plus ├── swappy └── config ├── systemd └── lockscreen.service ├── niri ├── scripts │ ├── power-menu.sh │ ├── toggle-magic-workspace.sh │ ├── send-to-magic-workspace.sh │ ├── terminal.sh │ └── screen-record.sh └── config.kdl ├── fuzzel └── fuzzel.ini ├── xinitrc_desktop ├── Xresources ├── kitty └── kitty.conf ├── Xresources_laptop ├── waybar ├── style.css └── config ├── satty └── config.toml └── zshrc_laptop /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | .aider* 4 | -------------------------------------------------------------------------------- /hypr/xdph.conf: -------------------------------------------------------------------------------- 1 | screencopy { 2 | allow_token_by_default = true 3 | } 4 | -------------------------------------------------------------------------------- /vim/update.bat: -------------------------------------------------------------------------------- 1 | copy .vimrc %UserProfile% 2 | robocopy vimfiles %UserProfile%\vimfiles /s -------------------------------------------------------------------------------- /nvim/data/plenary/filetypes/gjs.lua: -------------------------------------------------------------------------------- 1 | return { 2 | extension = { 3 | ["gjs"] = "glimmer.js", 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /i3/edit_discourse: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | cd /home/sam/Source/discourse 4 | exec nvim -c ':NERDTree|:wincmd w|:vsplit' 5 | -------------------------------------------------------------------------------- /i3/picom: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | while [ 1 ]; do 4 | picom && break 5 | echo "re spawn picom due to fault" 6 | sleep 5 7 | done 8 | -------------------------------------------------------------------------------- /swappy/config: -------------------------------------------------------------------------------- 1 | [Default] 2 | save_dir=$HOME/Desktop 3 | save_filename_format=swappy-%Y%m%d-%H%M%S.png 4 | show_panel=false 5 | line_size=5 6 | text_size=20 7 | text_font=sans-serif 8 | paint_mode=arrow 9 | early_exit=true 10 | fill_shape=false 11 | -------------------------------------------------------------------------------- /nvim/init.lua: -------------------------------------------------------------------------------- 1 | _G.dd = function(...) 2 | Snacks.debug.inspect(...) 3 | end 4 | _G.bt = function() 5 | Snacks.debug.backtrace() 6 | end 7 | vim.print = _G.dd 8 | -- see: https://github.com/neovim/neovim/issues/31675 9 | vim.hl = vim.highlight 10 | 11 | require("config.lazy") 12 | -------------------------------------------------------------------------------- /hypr/exit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | options="󰜉 Reboot\n󰈆 Exit\n󰅖 Cancel" 4 | 5 | chosen=$(echo -e "$options" | fuzzel --placeholder 'Power Menu:' --dmenu) 6 | 7 | case "$chosen" in 8 | "󰜉 Reboot") 9 | systemctl reboot 10 | ;; 11 | "󰈆 Exit") 12 | hyprctl dispatch exit 13 | ;; 14 | "󰅖 Cancel") 15 | exit 0 16 | ;; 17 | esac 18 | -------------------------------------------------------------------------------- /systemd/lockscreen.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description = Lock screen when going to sleep/suspend 3 | Before=sleep.target 4 | Before=suspend.target 5 | 6 | [Service] 7 | User=sam 8 | Type=forking 9 | Environment=DISPLAY=:0 10 | ExecStart=/home/sam/.config/i3/lock-screen 11 | ExecStartPre= 12 | 13 | [Install] 14 | WantedBy=sleep.target 15 | WantedBy=suspend.target 16 | -------------------------------------------------------------------------------- /niri/scripts/power-menu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | options=$'󰜉 Reboot\n󰈆 Exit\n󰅖 Cancel' 5 | 6 | choice=$(printf '%s' "$options" | fuzzel --placeholder 'Power Menu:' --dmenu || true) 7 | 8 | case "$choice" in 9 | "󰜉 Reboot") 10 | systemctl reboot 11 | ;; 12 | "󰈆 Exit") 13 | niri msg action quit --skip-confirmation >/dev/null 2>&1 || true 14 | ;; 15 | *) 16 | ;; 17 | esac 18 | -------------------------------------------------------------------------------- /hypr/resolution.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | current_scale=$(hyprctl monitors -j | jq -r '.[] | select(.name=="DP-1") | .scale') 4 | 5 | if (($(echo "$current_scale == 1.6" | bc -l))); then 6 | hyprctl keyword monitor DP-1,highrr,auto,1.0 7 | notify-send "Display Scaling" "Switched to 100%" -t 2000 8 | else 9 | hyprctl keyword monitor DP-1,highrr,auto,1.6 10 | notify-send "Display Scaling" "Switched to 160%" -t 2000 11 | fi 12 | -------------------------------------------------------------------------------- /niri/scripts/toggle-magic-workspace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | command -v niri >/dev/null 2>&1 || exit 0 5 | 6 | current=$(niri msg --json workspaces 2>/dev/null | jq -r '.[] | select(.is_focused) | (.name // ("#" + (.idx|tostring)))' 2>/dev/null || true) 7 | 8 | if [[ "$current" == "magic" ]]; then 9 | niri msg action focus-workspace-previous >/dev/null 2>&1 || true 10 | else 11 | niri msg action focus-workspace magic >/dev/null 2>&1 || true 12 | fi 13 | -------------------------------------------------------------------------------- /vim/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -fLo curl ~/.local/share/nvim/site/autoload/plug.vim --create-dirs \ 4 | https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim 5 | 6 | curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ 7 | https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim 8 | 9 | mkdir -p ~/.config/nvim/ 10 | 11 | rm -f ~/.vimrc 12 | ln -s `pwd`/vimrc ~/.vimrc 13 | rm -fr ~/.config/nvim/init.vim 14 | ln -s `pwd`/vimrc ~/.config/nvim/init.vim 15 | -------------------------------------------------------------------------------- /fuzzel/fuzzel.ini: -------------------------------------------------------------------------------- 1 | dpi-aware=yes 2 | icon-theme=Papirus-Dark 3 | width=25 4 | #font=Hack:weight=bold:size=36 5 | line-height=20 6 | fields=name,generic,comment,categories,filename,keywords 7 | terminal=kitty -e 8 | prompt="❯ " 9 | show-actions=yes 10 | exit-on-keyboard-focus-loss=no 11 | border-width=2 12 | layer=overlay 13 | 14 | [colors] 15 | background=282a36fa 16 | selection=3d4474fa 17 | border=fffffffa 18 | match=e6e6e6fa 19 | selection-match=e6e6e6fa 20 | text=9e9e9efa 21 | selection-text=e6e6e6fa 22 | 23 | 24 | [border] 25 | radius=10 26 | 27 | #[dmenu] 28 | #exit-immediately-if-empty=y 29 | -------------------------------------------------------------------------------- /i3/i3-resize: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # This script was made by `goferito` on Github. 4 | # Some cleanup by Luke. 5 | 6 | [ -z "$1" ] && echo "No direction provided" && exit 1 7 | distanceStr="20 px or 2 ppt" 8 | 9 | moveChoice() { 10 | i3-msg resize "$1" "$2" "$distanceStr" | grep '"success":true' || \ 11 | i3-msg resize "$3" "$4" "$distanceStr" 12 | } 13 | 14 | case $1 in 15 | up) 16 | moveChoice grow up shrink down 17 | ;; 18 | down) 19 | moveChoice shrink up grow down 20 | ;; 21 | left) 22 | moveChoice shrink right grow left 23 | ;; 24 | right) 25 | moveChoice grow right shrink left 26 | ;; 27 | esac 28 | -------------------------------------------------------------------------------- /xinitrc_desktop: -------------------------------------------------------------------------------- 1 | eval $(dbus-launch -sh-syntax --exit-with-session) 2 | dbus-update-activation-environment --systemd DISPLAY 3 | 4 | xrdb -merge ~/.Xresources 5 | 6 | xrandr --output DVI-D-0 --off --output HDMI-1 --off --output HDMI-0 --mode 3840x2160 --pos 0x0 --rotate normal --output DP-3 --off --output DP-2 --primary --mode 3840x2160 --pos 3840x0 --rotate normal --output DP-1 --off --output DP-0 --mode 3840x2160 --pos 7680x0 --rotate normal 7 | 8 | sleep 2 9 | 10 | eval $(/usr/bin/gnome-keyring-daemon --start --components=gpg,pkcs11,secrets,ssh) 11 | export GNOME_KEYRING_CONTROL GNOME_KEYRING_PID GPG_AGENT_INFO SSH_AUTH_SOCK 12 | 13 | exec i3 14 | exec xmonad 15 | # exec /home/sam/Source/dwm/dwm 16 | -------------------------------------------------------------------------------- /niri/scripts/send-to-magic-workspace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | command -v niri >/dev/null 2>&1 || exit 0 5 | 6 | current_idx=$(niri msg --json workspaces 2>/dev/null | jq -r '.[] | select(.is_focused) | .idx' 2>/dev/null || true) 7 | 8 | if niri msg action move-window-to-workspace magic --focus false >/dev/null 2>&1; then 9 | exit 0 10 | fi 11 | 12 | # If the workspace does not exist yet, create it and retry. 13 | niri msg action focus-workspace magic >/dev/null 2>&1 || true 14 | niri msg action set-workspace-name magic >/dev/null 2>&1 || true 15 | 16 | if [[ -n "$current_idx" ]]; then 17 | niri msg action focus-workspace "$current_idx" >/dev/null 2>&1 || true 18 | fi 19 | 20 | niri msg action move-window-to-workspace magic --focus false >/dev/null 2>&1 || true 21 | -------------------------------------------------------------------------------- /hypr/terminal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | active_pid=$(hyprctl activewindow | grep -E "^\s*pid:" | awk '{print $2}') 4 | 5 | dir="$HOME" 6 | 7 | if [[ -n "$active_pid" ]] && [[ "$active_pid" =~ ^[0-9]+$ ]] && [[ "$active_pid" -gt 0 ]]; then 8 | # Find shell processes in the process tree 9 | shell_pid=$(pstree -aApT "$active_pid" 2>/dev/null | grep -E 'zsh,|bash,' | head -1 | awk -F',' '{print $NF}') 10 | 11 | # If we found a shell PID, get its working directory 12 | if [[ -n "$shell_pid" ]] && [[ "$shell_pid" =~ ^[0-9]+$ ]]; then 13 | cwd=$(readlink "/proc/$shell_pid/cwd" 2>/dev/null) 14 | 15 | # Use the directory if it exists and doesn't contain single quotes 16 | if [[ -n "$cwd" ]] && [[ -d "$cwd" ]] && [[ ! "$cwd" =~ \' ]]; then 17 | dir="$cwd" 18 | fi 19 | fi 20 | fi 21 | 22 | cd "$dir" && kitty 23 | -------------------------------------------------------------------------------- /niri/scripts/terminal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | dir="$HOME" 5 | 6 | if command -v niri >/dev/null 2>&1; then 7 | focused_json=$(niri msg --json focused-window 2>/dev/null || true) 8 | if [[ -n "$focused_json" && "$focused_json" != "null" ]]; then 9 | pid=$(printf '%s' "$focused_json" | jq -r '.pid // empty' 2>/dev/null || true) 10 | if [[ -n "$pid" && "$pid" =~ ^[0-9]+$ && "$pid" -gt 1 ]]; then 11 | shell_pid=$(pstree -aApT "$pid" 2>/dev/null | grep -E 'zsh,|bash,' | head -1 | awk -F',' '{print $NF}') 12 | if [[ -n "$shell_pid" && "$shell_pid" =~ ^[0-9]+$ ]]; then 13 | cwd=$(readlink "/proc/$shell_pid/cwd" 2>/dev/null || true) 14 | if [[ -n "$cwd" && -d "$cwd" && "$cwd" != *"'"* ]]; then 15 | dir="$cwd" 16 | fi 17 | fi 18 | fi 19 | fi 20 | fi 21 | 22 | cd "$dir" 23 | exec kitty 24 | -------------------------------------------------------------------------------- /Xresources: -------------------------------------------------------------------------------- 1 | Xft.dpi: 120 2 | Xft.autohint: 0 3 | Xft.lcdfilter: lcddefault 4 | Xft.hintstyle: hintfull 5 | Xft.hinting: 1 6 | Xft.antialias: 1 7 | Xft.rgba: rgb 8 | 9 | *TkTheme: clearlooks 10 | 11 | ! ! hard contrast: *background: #1d2021 12 | ! *background: #282828 13 | ! ! soft contrast: *background: #32302f 14 | ! *foreground: #ebdbb2 15 | ! ! Black + DarkGrey 16 | ! *color0: #282828 17 | ! *color8: #928374 18 | ! ! DarkRed + Red 19 | ! *color1: #cc241d 20 | ! *color9: #fb4934 21 | ! ! DarkGreen + Green 22 | ! *color2: #98971a 23 | ! *color10: #b8bb26 24 | ! ! DarkYellow + Yellow 25 | ! *color3: #d79921 26 | ! *color11: #fabd2f 27 | ! ! DarkBlue + Blue 28 | ! *color4: #458588 29 | ! *color12: #83a598 30 | ! ! DarkMagenta + Magenta 31 | ! *color5: #b16286 32 | ! *color13: #d3869b 33 | ! ! DarkCyan + Cyan 34 | ! *color6: #689d6a 35 | ! *color14: #8ec07c 36 | ! ! LightGrey + White 37 | ! *color7: #a89984 38 | ! *color15: #ebdbb2 39 | -------------------------------------------------------------------------------- /i3/status.toml: -------------------------------------------------------------------------------- 1 | icons = "awesome5" 2 | 3 | [theme] 4 | name = "solarized-dark" 5 | pen 6 | 7 | [theme.overrides] 8 | separator = "" 9 | 10 | [[block]] 11 | block = "pacman" 12 | 13 | [[block]] 14 | block = "disk_space" 15 | path = "/" 16 | alias = "/" 17 | info_type = "available" 18 | unit = "GB" 19 | interval = 20 20 | warning = 20.0 21 | alert = 10.0 22 | 23 | [[block]] 24 | block = "memory" 25 | display_type = "memory" 26 | format_mem = "{mem_used}/{mem_total}" 27 | format_swap = "{swap_used}/{swap_total}" 28 | 29 | [[block]] 30 | block = "cpu" 31 | interval = 1 32 | 33 | [[block]] 34 | block= "speedtest" 35 | interval = 1800 36 | 37 | [[block]] 38 | block = "weather" 39 | format = "{weather} ({location}) {temp}" 40 | service = { name = "openweathermap", api_key = "TOKEN", city_id = "2207748", units = "metric" } 41 | 42 | [[block]] 43 | block = "sound" 44 | 45 | [[block]] 46 | block = "time" 47 | interval = 60 48 | format = "%a %d/%m %R" 49 | -------------------------------------------------------------------------------- /i3/lock-screen: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | B='#00000000' # blank 4 | C='#ffffff22' # clear ish 5 | D='#00000000' # default 6 | T='#f0ffffff' # text 7 | W='#ee0000bb' # wrong 8 | V='#bb00bbbb' # verifying 9 | 10 | BG="#000000ff" 11 | 12 | # -e to ignore empty password 13 | 14 | i3lock \ 15 | --greeter-text="" \ 16 | -c $BG \ 17 | --insidever-color=$C \ 18 | --ringver-color=$V \ 19 | \ 20 | --insidewrong-color=$C \ 21 | --ringwrong-color=$W \ 22 | \ 23 | --inside-color=$B \ 24 | --ring-color=$D \ 25 | --line-color=$B \ 26 | --separator-color=$D \ 27 | \ 28 | --verif-color=$T \ 29 | --wrong-color=$T \ 30 | --time-color=$T \ 31 | --date-color=$T \ 32 | --layout-color=$T \ 33 | --keyhl-color=$W \ 34 | --bshl-color=$W \ 35 | \ 36 | --screen 1 \ 37 | --clock \ 38 | --indicator \ 39 | --time-str="%H:%M:%S" \ 40 | --date-str="%A, %m %Y" \ 41 | --keylayout 1 \ 42 | -------------------------------------------------------------------------------- /hypr/toggle-input.sh: -------------------------------------------------------------------------------- 1 | set -euo pipefail 2 | 3 | VCP=60 4 | A=0x0f 5 | B=0x19 6 | DDC=${DDCUTIL_BIN:-ddcutil} 7 | 8 | err() { printf "toggle-input: %s\n" "$*" >&2; } 9 | 10 | command -v "$DDC" >/dev/null 2>&1 || { err "ddcutil not found"; exit 1; } 11 | 12 | # Read current value (short value after sl=) 13 | read_current() { 14 | "$DDC" getvcp "$VCP" 2>/dev/null | sed -n 's/.*sl=\(0x[0-9a-fA-F]\+\).*/\1/p' | head -n1 15 | } 16 | 17 | CURRENT=$(read_current || true) 18 | 19 | if [[ -z "${CURRENT}" ]]; then 20 | err "could not read current input" 21 | exit 1 22 | fi 23 | 24 | case "$CURRENT" in 25 | "$A") NEXT=$B ;; 26 | "$B") NEXT=$A ;; 27 | *) 28 | err "unknown current value: $CURRENT (expected $A or $B)" 29 | exit 1 30 | ;; 31 | esac 32 | 33 | echo "Current: $CURRENT -> Switching to: $NEXT" 34 | "$DDC" setvcp "$VCP" "$NEXT" 35 | 36 | # Wait a bit; some monitors need time to apply & respond 37 | for i in 1 2 3; do 38 | sleep 1 39 | NEW=$(read_current || true) 40 | if [[ "$NEW" == "$NEXT" ]]; then 41 | echo "Switched successfully: $NEW" 42 | exit 0 43 | fi 44 | done 45 | 46 | err "failed to confirm switch (last read: ${NEW:-none})" 47 | exit 1 48 | -------------------------------------------------------------------------------- /hypr/hypridle.conf: -------------------------------------------------------------------------------- 1 | general { 2 | lock_cmd = pidof hyprlock || hyprlock # avoid starting multiple hyprlock instances. 3 | before_sleep_cmd = loginctl lock-session # lock before suspend. 4 | after_sleep_cmd = hyprctl dispatch dpms on # to avoid having to press a key twice to turn on the display. 5 | } 6 | 7 | listener { 8 | timeout = 150 # 2.5min. 9 | on-timeout = brightnessctl -s set 10 # set monitor backlight to minimum, avoid 0 on OLED monitor. 10 | on-resume = brightnessctl -r # monitor backlight restore. 11 | } 12 | 13 | listener { 14 | timeout = 600 # 10 min 15 | on-timeout = loginctl lock-session # lock screen when timeout has passed 16 | } 17 | 18 | listener { 19 | timeout = 630 # 10.5min 20 | on-timeout = hyprctl dispatch dpms off # screen off when timeout has passed 21 | on-resume = hyprctl dispatch dpms on && brightnessctl -r # screen on when activity is detected after timeout has fired. 22 | } 23 | 24 | listener { 25 | timeout = 1800 # 30min 26 | on-timeout = systemctl suspend # suspend pc 27 | } 28 | -------------------------------------------------------------------------------- /kitty/kitty.conf: -------------------------------------------------------------------------------- 1 | # font_family SourceCodePro 2 | # font_family Consolas 3 | enable_audio_bell no 4 | font_size 12.0 5 | 6 | cursor_shape block 7 | shell_integration no-cursor 8 | 9 | background #282828 10 | foreground #ebdbb2 11 | 12 | cursor #928374 13 | 14 | selection_foreground #928374 15 | selection_background #3c3836 16 | 17 | color0 #282828 18 | color8 #928374 19 | 20 | # red 21 | color1 #cc241d 22 | # light red 23 | color9 #fb4934 24 | 25 | # green 26 | color2 #98971a 27 | # light green 28 | color10 #b8bb26 29 | 30 | # yellow 31 | color3 #d79921 32 | # light yellow 33 | color11 #fabd2d 34 | 35 | # blue 36 | color4 #458588 37 | # light blue 38 | color12 #83a598 39 | 40 | # magenta 41 | color5 #b16286 42 | # light magenta 43 | color13 #d3869b 44 | 45 | # cyan 46 | color6 #689d6a 47 | # lighy cyan 48 | color14 #8ec07c 49 | 50 | # light gray 51 | color7 #a89984 52 | # dark gray 53 | color15 #928374 54 | 55 | # BEGIN_KITTY_FONTS 56 | font_family family="SFMono Nerd Font Mono" 57 | bold_font auto 58 | italic_font auto 59 | bold_italic_font auto 60 | 61 | window_padding_width 4 62 | # END_KITTY_FONTS 63 | -------------------------------------------------------------------------------- /nvim/lua/config/lazy.lua: -------------------------------------------------------------------------------- 1 | local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" 2 | if not vim.uv.fs_stat(lazypath) then 3 | local lazyrepo = "https://github.com/folke/lazy.nvim.git" 4 | local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) 5 | if vim.v.shell_error ~= 0 then 6 | vim.api.nvim_echo({ 7 | { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, 8 | { out, "WarningMsg" }, 9 | { "\nPress any key to exit..." }, 10 | }, true, {}) 11 | vim.fn.getchar() 12 | os.exit(1) 13 | end 14 | end 15 | vim.opt.rtp:prepend(lazypath) 16 | 17 | -- Make sure to setup `mapleader` and `maplocalleader` before 18 | -- loading lazy.nvim so that mappings are correct. 19 | -- This is also a good place to setup other settings (vim.opt) 20 | vim.g.mapleader = " " 21 | vim.g.maplocalleader = "\\" 22 | 23 | -- Setup lazy.nvim 24 | require("lazy").setup({ 25 | spec = { 26 | -- import your plugins 27 | { import = "plugins" }, 28 | }, 29 | -- Configure any other settings here. See the documentation for more details. 30 | -- colorscheme that will be used when installing plugins. 31 | install = { colorscheme = { "gruvbox" } }, 32 | -- automatically check for plugin updates 33 | checker = { 34 | enabled = true, 35 | frequency = 3600 * 24 * 7, 36 | }, 37 | }) 38 | 39 | require("config.options") 40 | require("config.keymaps") 41 | require("config.autocmds") 42 | require("config.test_runner").setup() 43 | -------------------------------------------------------------------------------- /hypr/screen-record.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PIDFILE="/tmp/wf-recorder.pid" 4 | RECORDING_FILE_PATH="/tmp/wf-recorder-current-file" 5 | SCREENCAST_DIR="$HOME/Videos/Screencasts" 6 | 7 | mkdir -p "$SCREENCAST_DIR" 8 | 9 | if [ -f "$PIDFILE" ]; then 10 | PID=$(cat "$PIDFILE") 11 | kill -SIGINT "$PID" 12 | wait "$PID" 2>/dev/null 13 | 14 | if [ -f "$RECORDING_FILE_PATH" ]; then 15 | RECORDED_FILE=$(cat "$RECORDING_FILE_PATH") 16 | rm "$RECORDING_FILE_PATH" 17 | fi 18 | 19 | rm "$PIDFILE" 20 | 21 | if [ -f "$RECORDED_FILE" ]; then 22 | notify-send "Screen Recording" "Recording saved to $RECORDED_FILE" -i video-x-generic 23 | echo "file://$RECORDED_FILE" | wl-copy -t text/uri-list 24 | else 25 | notify-send "Screen Recording" "Failed to find recording file" -i dialog-error 26 | fi 27 | else 28 | notify-send "Screen Recording" "Click to select window or drag to select region" -i media-record 29 | RECORDING_FILE="$SCREENCAST_DIR/screencast-$(date +%Y%m%d-%H%M%S).mp4" 30 | echo "$RECORDING_FILE" >"$RECORDING_FILE_PATH" 31 | 32 | GEOMETRY=$(slurp 2>/dev/null) 33 | 34 | if [ -z "$GEOMETRY" ] || [[ "$GEOMETRY" =~ ^0,0\ 0x0$ ]]; then 35 | GEOMETRY=$(hyprctl activewindow -j | jq -r '"\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"') 36 | fi 37 | 38 | echo GEOMETRY: "$GEOMETRY" 39 | 40 | wf-recorder -g "$GEOMETRY" -c h264_nvenc -p preset=fast -p rc=vbr -p cq=35 -p b:v=1M -r 30 -f "$RECORDING_FILE" & 41 | echo $! >"$PIDFILE" 42 | fi 43 | -------------------------------------------------------------------------------- /nvim/lua/config/options.lua: -------------------------------------------------------------------------------- 1 | local opt = vim.opt 2 | 3 | -- General 4 | opt.mouse = "a" 5 | opt.clipboard = "unnamedplus" 6 | opt.swapfile = true 7 | opt.dir = vim.fn.expand("$HOME/.vim/swapfiles//") 8 | opt.backup = true 9 | opt.backupdir = vim.fn.expand("$HOME/.vim/backupdir//") 10 | opt.undofile = true 11 | opt.history = 1000 12 | opt.hidden = true 13 | 14 | -- UI 15 | opt.number = false 16 | opt.termguicolors = true 17 | opt.background = "dark" 18 | opt.showmode = false 19 | opt.showcmd = true 20 | -- cause lualine will not be at the bottom 21 | opt.cmdheight = 0 22 | opt.guicursor = "" 23 | opt.splitright = true 24 | 25 | -- Indenting 26 | opt.expandtab = true 27 | opt.shiftwidth = 2 28 | opt.tabstop = 2 29 | opt.softtabstop = 2 30 | opt.autoindent = true 31 | opt.smartindent = true 32 | opt.cindent = true 33 | 34 | -- Search 35 | opt.hlsearch = false 36 | opt.incsearch = true 37 | opt.ignorecase = true 38 | opt.smartcase = true 39 | 40 | -- Appearance 41 | -- this inherits from kitty, no need to set anything here 42 | -- opt.guifont = "Consolas:h14" 43 | -- keep lualine at the bottom where it is not annoying 44 | opt.laststatus = 3 45 | 46 | opt.shortmess:append("c") -- Avoid showing extra messages when using completion 47 | opt.shortmess:append("I") -- No intro message when starting Vim 48 | opt.shortmess:append("s") -- Don't show "search hit BOTTOM" type messages 49 | opt.shortmess:append("W") -- Don't show "written" message when saving 50 | opt.shortmess:append("F") -- Don't show file info when editing 51 | -------------------------------------------------------------------------------- /hypr/launch-discourse.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Function to check if workspace 2 is empty 4 | is_workspace_empty() { 5 | local workspace_id=2 6 | local window_count=$(hyprctl workspaces -j | jq ".[] | select(.id == $workspace_id) | .windows" 2>/dev/null) 7 | 8 | # If workspace doesn't exist or has 0 windows, it's empty 9 | if [[ -z "$window_count" ]] || [[ "$window_count" -eq 0 ]]; then 10 | return 0 # empty 11 | else 12 | return 1 # not empty 13 | fi 14 | } 15 | 16 | # Check if workspace 2 is empty 17 | if is_workspace_empty; then 18 | echo "Workspace 2 is empty. Setting up Discourse development environment..." 19 | 20 | # Switch to workspace 2 first 21 | hyprctl dispatch workspace 2 22 | 23 | # Launch first kitty terminal for unicorn 24 | hyprctl dispatch exec "kitty --hold -e bash -c 'cd ~/Source/discourse && bin/unicorn'" 25 | 26 | # Small delay to ensure first terminal is launched 27 | sleep 0.5 28 | 29 | # Launch second kitty terminal for ember-cli 30 | hyprctl dispatch exec "kitty --hold -e bash -c 'cd ~/Source/discourse && bin/ember-cli'" 31 | 32 | # Notify success 33 | notify-send "Discourse Dev Setup" "Launched unicorn and ember-cli terminals on workspace 2" -t 3000 34 | 35 | else 36 | echo "Workspace 2 is not empty. Taking you there..." 37 | 38 | # Notify that workspace is not empty 39 | notify-send "Workspace 2 Not Empty" "Workspace 2 already has applications running" -t 3000 40 | 41 | # Switch to workspace 2 so user can see what's there 42 | hyprctl dispatch workspace 2 43 | fi 44 | -------------------------------------------------------------------------------- /hypr/toggle-master-layout.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get current workspace 4 | workspace=$(hyprctl activeworkspace -j | jq -r '.id') 5 | 6 | # Get current workspace layout 7 | workspace_layout=$(hyprctl activeworkspace -j | jq -r '.lastwindow' | xargs -I {} hyprctl clients -j | jq -r ".[] | select(.address == \"{}\") | .workspace.name") 8 | current_layout=$(hyprctl activeworkspace -j | jq -r '.name') 9 | 10 | # Check if workspace has custom layout by trying to get layout messages 11 | # We'll track state in a temporary file per workspace 12 | state_file="/tmp/hypr-layout-state-ws${workspace}" 13 | 14 | if [ ! -f "$state_file" ]; then 15 | echo "master-center" > "$state_file" 16 | fi 17 | 18 | current_state=$(cat "$state_file") 19 | 20 | if [ "$current_state" = "dwindle" ]; then 21 | # Switch to master left 22 | hyprctl dispatch layoutmsg orientationleft 23 | hyprctl dispatch layoutmsg mfact exact 0.6 24 | echo "master-left" > "$state_file" 25 | echo "Switched to 2-window layout (left orientation)" 26 | notify-send -t 1000 "Layout: Master Left" "2-window layout" 27 | elif [ "$current_state" = "master-left" ]; then 28 | # Switch to master center 29 | hyprctl dispatch layoutmsg orientationcenter 30 | hyprctl dispatch layoutmsg mfact exact 0.4 31 | echo "master-center" > "$state_file" 32 | echo "Switched to 3-window layout (center orientation)" 33 | notify-send -t 1000 "Layout: Master Center" "3-window layout" 34 | else 35 | # Switch back to dwindle (reset to default) 36 | hyprctl dispatch layoutmsg orientationleft 37 | hyprctl dispatch layoutmsg mfact exact 0.5 38 | echo "dwindle" > "$state_file" 39 | echo "Switched to dwindle layout" 40 | notify-send -t 1000 "Layout: Dwindle" "Tiling layout" 41 | fi 42 | -------------------------------------------------------------------------------- /hypr/edit-discourse.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Function to check if workspace 3 is empty 4 | is_workspace_empty() { 5 | local workspace_id=3 6 | local window_count=$(hyprctl workspaces -j | jq ".[] | select(.id == $workspace_id) | .windows" 2>/dev/null) 7 | 8 | # If workspace doesn't exist or has 0 windows, it's empty 9 | if [[ -z "$window_count" ]] || [[ "$window_count" -eq 0 ]]; then 10 | return 0 # empty 11 | else 12 | return 1 # not empty 13 | fi 14 | } 15 | 16 | # Check if workspace 3 is empty 17 | if is_workspace_empty; then 18 | echo "Workspace 3 is empty. Setting up editing environment..." 19 | 20 | # Switch to workspace 3 first 21 | hyprctl dispatch workspace 3 22 | 23 | # Configure master layout 24 | hyprctl keyword general:layout master 25 | hyprctl keyword master:mfact 0.7 26 | hyprctl keyword master:slave_count_for_center_master -1 27 | hyprctl keyword master:orientation left 28 | hyprctl dispatch layoutmsg orientationleft 29 | hyprctl dispatch layoutmsg mfact exact 0.7 30 | 31 | # Small delay to ensure layout is set 32 | sleep 0.3 33 | 34 | # Launch kitty terminal with vim in discourse directory 35 | hyprctl dispatch exec "kitty --hold -d '/home/sam/Source/discourse' -e bash -c 'vim'" 36 | 37 | # Small delay before launching chromium 38 | sleep 0.5 39 | 40 | # Launch chromium 41 | hyprctl dispatch exec "chromium" 42 | 43 | # Notify success 44 | notify-send "Editing Environment" "Set up master layout with vim and chromium on workspace 3" -t 3000 45 | 46 | else 47 | echo "Workspace 3 is already set up. Taking you there..." 48 | 49 | # Notify that workspace is already set up 50 | notify-send "Workspace 3 Ready" "Editing environment is already set up" -t 3000 51 | 52 | # Switch to workspace 3 53 | hyprctl dispatch workspace 3 54 | fi 55 | -------------------------------------------------------------------------------- /i3/countdown: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # taken from https://askubuntu.com/questions/761505/can-i-have-a-countdown-window-show-the-time-until-next-suspend 4 | 5 | import gi 6 | gi.require_version('Gtk', '3.0') 7 | from gi.repository import Gtk, Gdk, GObject, Pango 8 | from threading import Thread 9 | import subprocess 10 | import time 11 | import signal 12 | import sys 13 | import os 14 | 15 | # --- set the color (index) below (1 is the first) 16 | color = 1 17 | # --- 18 | textcolors = ["grey", "orange", "green", "blue", "white"] 19 | # --- don't change anything below 20 | txtcolor = textcolors[color-1] 21 | 22 | countdown = int(sys.argv[1]) 23 | 24 | class CountDown(Gtk.Window): 25 | 26 | def __init__(self): 27 | Gtk.Window.__init__(self) 28 | maingrid = Gtk.Grid() 29 | self.add(maingrid) 30 | maingrid.set_border_width(20) 31 | # set initial text for the spash window 32 | self.label = Gtk.Label(countdown) 33 | self.label.modify_font(Pango.FontDescription('Consolas 30')) 34 | self.label.set_width_chars(5) 35 | maingrid.attach(self.label, 0, 0, 1, 1) 36 | self.update = Thread(target=self.start_countdown, args=[countdown]) 37 | # daemonize the thread 38 | self.update.setDaemon(True) 39 | self.update.start() 40 | 41 | def start_countdown(self, countdown): 42 | t = countdown 43 | while t > 0: 44 | time.sleep(1) 45 | t -= 1 46 | GObject.idle_add(self.label.set_text, str(t), 47 | priority=GObject.PRIORITY_DEFAULT) 48 | self.stop() 49 | 50 | def stop(self): 51 | Gtk.main_quit() 52 | if not self.emit("delete-event", Gtk.gdk.Event(Gtk.gdk.DELETE)): 53 | self.destroy() 54 | 55 | def get_screen(): 56 | scr = [s.split("x") for s in subprocess.check_output([ 57 | "xrandr"]).decode("utf-8").split() if "+0+0" in s][0] 58 | return int(scr[0]) - 400 59 | 60 | def splashwindow(): 61 | window = CountDown() 62 | window.set_decorated(False) 63 | window.set_resizable(False) 64 | window.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(0,0,0,1)) 65 | window.modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse(txtcolor)) 66 | window.set_opacity(0.8) 67 | window.move(get_screen(), 80) 68 | window.set_keep_above(True) 69 | window.show_all() 70 | Gtk.main() 71 | 72 | 73 | splashwindow() 74 | -------------------------------------------------------------------------------- /hypr/countdown: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # taken from https://askubuntu.com/questions/761505/can-i-have-a-countdown-window-show-the-time-until-next-suspend 4 | 5 | import gi 6 | gi.require_version('Gtk', '3.0') 7 | from gi.repository import Gtk, Gdk, GObject, Pango 8 | from threading import Thread 9 | import subprocess 10 | import time 11 | import signal 12 | import sys 13 | import os 14 | 15 | # --- set the color (index) below (1 is the first) 16 | color = 1 17 | # --- 18 | textcolors = ["grey", "orange", "green", "blue", "white"] 19 | # --- don't change anything below 20 | txtcolor = textcolors[color-1] 21 | 22 | countdown = int(sys.argv[1]) 23 | 24 | class CountDown(Gtk.Window): 25 | 26 | def __init__(self): 27 | Gtk.Window.__init__(self) 28 | maingrid = Gtk.Grid() 29 | self.add(maingrid) 30 | maingrid.set_border_width(20) 31 | # set initial text for the spash window 32 | self.label = Gtk.Label(countdown) 33 | self.label.modify_font(Pango.FontDescription('Consolas 30')) 34 | self.label.set_width_chars(5) 35 | maingrid.attach(self.label, 0, 0, 1, 1) 36 | self.update = Thread(target=self.start_countdown, args=[countdown]) 37 | # daemonize the thread 38 | self.update.setDaemon(True) 39 | self.update.start() 40 | 41 | def start_countdown(self, countdown): 42 | t = countdown 43 | while t > 0: 44 | time.sleep(1) 45 | t -= 1 46 | GObject.idle_add(self.label.set_text, str(t), 47 | priority=GObject.PRIORITY_DEFAULT) 48 | self.stop() 49 | 50 | def stop(self): 51 | Gtk.main_quit() 52 | if not self.emit("delete-event", Gtk.gdk.Event(Gtk.gdk.DELETE)): 53 | self.destroy() 54 | 55 | def get_screen(): 56 | scr = [s.split("x") for s in subprocess.check_output([ 57 | "xrandr"]).decode("utf-8").split() if "+0+0" in s][0] 58 | return int(scr[0]) - 400 59 | 60 | def splashwindow(): 61 | window = CountDown() 62 | window.set_decorated(False) 63 | window.set_resizable(False) 64 | window.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(0,0,0,1)) 65 | window.modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse(txtcolor)) 66 | window.set_opacity(0.8) 67 | window.move(get_screen(), 80) 68 | window.set_keep_above(True) 69 | window.show_all() 70 | Gtk.main() 71 | 72 | 73 | splashwindow() 74 | -------------------------------------------------------------------------------- /Xresources_laptop: -------------------------------------------------------------------------------- 1 | Xft.dpi: 192 2 | Xft.autohint: 0 3 | Xft.lcdfilter: lcddefault 4 | Xft.hintstyle: hintfull 5 | Xft.hinting: 1 6 | Xft.antialias: 1 7 | Xft.rgba: rgb 8 | 9 | URxvt.font: xft:Consolas:pixelsize=34 10 | URxvt.boldFont: xft:Consolas:bold:pixelsize=34 11 | URxvt.italicFont: xft:Consolas:italic:pixelsize=34 12 | URxvt.boldItalicFont: xft:Consolas:bold:italic:pixelsize=34 13 | URxvt.scrollBar: false 14 | 15 | ! stop stealing ctrl-shift, to enter magic unicodes 16 | URxvt.iso14755: false 17 | 18 | URxvt*scrollTtyOutput: false 19 | URxvt*scrollWithBuffer: true 20 | URxvt*scrollTtyKeypress: true 21 | URxvt*saveLines: 20000 22 | 23 | URxvt.keysym.M-u: perl:url-select:select_next 24 | URxvt.perl-ext-common:matcher,selection,-readline,-selection-popup,-option-popup,-option-popup,-tabbed,-searchable-scrollback,resize-font 25 | URxvt.cutchars:"()*,<>[]{}|'" 26 | URxvt.matcher.button:1 27 | URxvt.url-select.autocopy: true 28 | URxvt.perl-ext: clipboard,url-select,keyboard-select 29 | URxvt.url-select.launcher: firefox 30 | 31 | ! hard contrast: *background: #1d2021 32 | *background: #282828 33 | ! soft contrast: *background: #32302f 34 | *foreground: #ebdbb2 35 | ! Black + DarkGrey 36 | *color0: #282828 37 | *color8: #928374 38 | ! DarkRed + Red 39 | *color1: #cc241d 40 | *color9: #fb4934 41 | ! DarkGreen + Green 42 | *color2: #98971a 43 | *color10: #b8bb26 44 | ! DarkYellow + Yellow 45 | *color3: #d79921 46 | *color11: #fabd2f 47 | ! DarkBlue + Blue 48 | *color4: #458588 49 | *color12: #83a598 50 | ! DarkMagenta + Magenta 51 | *color5: #b16286 52 | *color13: #d3869b 53 | ! DarkCyan + Cyan 54 | *color6: #689d6a 55 | *color14: #8ec07c 56 | ! LightGrey + White 57 | *color7: #a89984 58 | *color15: #ebdbb2 59 | 60 | ! gruvbox 61 | URxvt.color24: #076678 62 | URxvt.color66: #427b58 63 | URxvt.color88: #9d0006 64 | URxvt.color96: #8f3f71 65 | URxvt.color100: #79740e 66 | URxvt.color108: #8ec07c 67 | URxvt.color109: #83a598 68 | URxvt.color130: #af3a03 69 | URxvt.color136: #b57614 70 | URxvt.color142: #b8bb26 71 | URxvt.color167: #fb4934 72 | URxvt.color175: #d3869b 73 | URxvt.color208: #fe8019 74 | URxvt.color214: #fabd2f 75 | URxvt.color223: #ebdbb2 76 | URxvt.color228: #f2e5bc 77 | URxvt.color229: #fbf1c7 78 | URxvt.color230: #f9f5d7 79 | URxvt.color234: #1d2021 80 | URxvt.color235: #282828 81 | URxvt.color236: #32302f 82 | URxvt.color237: #3c3836 83 | URxvt.color239: #504945 84 | URxvt.color241: #665c54 85 | URxvt.color243: #7c6f64 86 | URxvt.color244: #928374 87 | URxvt.color245: #928374 88 | URxvt.color246: #a89984 89 | URxvt.color248: #bdae93 90 | URxvt.color250: #d5c4a1 91 | -------------------------------------------------------------------------------- /niri/scripts/screen-record.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | PIDFILE="/tmp/wf-recorder.pid" 5 | RECORDING_FILE_PATH="/tmp/wf-recorder-current-file" 6 | SCREENCAST_DIR="${HOME}/Videos/Screencasts" 7 | 8 | mkdir -p "$SCREENCAST_DIR" 9 | 10 | stop_recording() { 11 | local pid recorded_file 12 | pid=$(<"$PIDFILE") 13 | kill -SIGINT "$pid" 2>/dev/null || true 14 | wait "$pid" 2>/dev/null || true 15 | rm -f "$PIDFILE" 16 | 17 | if [[ -f "$RECORDING_FILE_PATH" ]]; then 18 | recorded_file=$(<"$RECORDING_FILE_PATH") 19 | rm -f "$RECORDING_FILE_PATH" 20 | if [[ -f "$recorded_file" ]]; then 21 | notify-send "Screen Recording" "Recording saved to $recorded_file" -i video-x-generic 22 | printf 'file://%s' "$recorded_file" | wl-copy -t text/uri-list 23 | return 24 | fi 25 | fi 26 | 27 | notify-send "Screen Recording" "Failed to find recording file" -i dialog-error 28 | } 29 | 30 | focused_geometry() { 31 | command -v niri >/dev/null 2>&1 || return 1 32 | local json 33 | json=$(niri msg --json focused-window 2>/dev/null || true) 34 | if [[ -z "$json" || "$json" == "null" ]]; then 35 | return 1 36 | fi 37 | 38 | printf '%s' "$json" | jq -r ' 39 | .layout as $layout 40 | | ($layout.window_size // empty) as $size 41 | | if ($size | length) == 2 then 42 | ($layout.window_offset_in_tile // [0.0, 0.0]) as $offset 43 | | ($layout.tile_pos_in_workspace_view // null) as $tile 44 | | if $tile != null then 45 | ($tile[0] + $offset[0]) as $x 46 | | ($tile[1] + $offset[1]) as $y 47 | else 48 | $offset[0] as $x 49 | | $offset[1] as $y 50 | end 51 | | ($x | floor) as $xf 52 | | ($y | floor) as $yf 53 | | ($size[0]) as $width 54 | | ($size[1]) as $height 55 | | ($xf | tostring) + "," + ($yf | tostring) + " " + ($width | tostring) + "x" + ($height | tostring) 56 | else empty end 57 | ' 2>/dev/null 58 | } 59 | 60 | if [[ -f "$PIDFILE" ]]; then 61 | stop_recording 62 | exit 0 63 | fi 64 | 65 | notify-send "Screen Recording" "Click to select window or drag to select region" -i media-record 66 | 67 | recording_file="$SCREENCAST_DIR/screencast-$(date +%Y%m%d-%H%M%S).mp4" 68 | printf '%s\n' "$recording_file" >"$RECORDING_FILE_PATH" 69 | 70 | geometry=$(slurp 2>/dev/null || true) 71 | if [[ -z "$geometry" || "$geometry" =~ ^0,0\ 0x0$ ]]; then 72 | geometry=$(focused_geometry || true) 73 | fi 74 | 75 | if [[ -z "$geometry" || "$geometry" =~ ^0,0\ 0x0$ ]]; then 76 | notify-send "Screen Recording" "No selection made" -i dialog-information 77 | rm -f "$RECORDING_FILE_PATH" 78 | exit 1 79 | fi 80 | 81 | wf-recorder -g "$geometry" -c h264_nvenc \ 82 | -p preset=fast -p rc=vbr -p cq=35 -p b:v=1M -r 30 -f "$recording_file" & 83 | printf '%s\n' "$!" >"$PIDFILE" 84 | -------------------------------------------------------------------------------- /waybar/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-weight: bold; 3 | font-size: 16px; 4 | font-family: 5 | "JetBrains Mono Nerd Font", "Fira Code", "Source Code Pro", "Ubuntu Mono", 6 | monospace; 7 | color: #dcdfe1; 8 | } 9 | 10 | #waybar { 11 | background-color: rgba(0, 0, 0, 0); 12 | border: none; 13 | box-shadow: none; 14 | } 15 | 16 | #workspaces, 17 | #window, 18 | #tray { 19 | background-color: rgba(15, 27, 53, 0.9); 20 | padding: 4px 6px; 21 | margin-top: 6px; 22 | margin-left: 0px; 23 | margin-right: 6px; 24 | border-width: 0px; 25 | } 26 | 27 | #clock, 28 | #custom-power { 29 | background-color: rgba(15, 27, 53, 0.9); 30 | margin-top: 6px; 31 | margin-right: 6px; 32 | padding: 4px 2px; 33 | border-radius: 0 10px 10px 0; 34 | border-width: 0px; 35 | } 36 | 37 | #network, 38 | #custom-lock { 39 | background-color: rgba(15, 27, 53, 0.9); 40 | margin-top: 6px; 41 | margin-left: 6px; 42 | padding: 4px 2px; 43 | border-radius: 10px 0 0 10px; 44 | border-width: 0px; 45 | } 46 | 47 | #custom-reboot, 48 | #bluetooth, 49 | #battery, 50 | #pulseaudio, 51 | #backlight, 52 | #custom-temperature, 53 | #custom-weather, 54 | #custom-updates, 55 | #memory, 56 | #wireplumber, 57 | #weather, 58 | #disk, 59 | #cpu { 60 | background-color: rgba(15, 27, 53, 0.9); 61 | margin-top: 6px; 62 | padding: 4px 15px; 63 | border-width: 0px; 64 | margin-left: 0px; 65 | } 66 | 67 | #custpm-temperature.critical, 68 | #pulseaudio.muted { 69 | color: #ff0000; 70 | padding-top: 0; 71 | } 72 | 73 | #bluetooth:hover, 74 | #network:hover, 75 | #custom-weather:hover, 76 | #custom-updates:hover, 77 | /*#tray:hover,*/ 78 | #backlight:hover, 79 | #battery:hover, 80 | #pulseaudio:hover, 81 | #custom-temperature:hover, 82 | #memory:hover, 83 | #cpu:hover, 84 | #clock:hover, 85 | #custom-lock:hover, 86 | #custom-reboot:hover, 87 | #custom-power:hover, 88 | #wireplumber:hover, 89 | #disk:hover, 90 | #window:hover { 91 | background-color: rgba(70, 75, 90, 0.9); 92 | } 93 | 94 | #workspaces button:hover { 95 | background-color: rgba(97, 175, 239, 0.2); 96 | padding: 2px 8px; 97 | margin: 0 2px; 98 | border-radius: 10px; 99 | } 100 | 101 | #workspaces button.active { 102 | background-color: #61afef; 103 | color: #ffffff; 104 | padding: 2px 8px; 105 | margin: 0 2px; 106 | border-radius: 10px; 107 | } 108 | 109 | #workspaces button { 110 | background: transparent; 111 | border: none; 112 | color: #888888; 113 | padding: 2px 8px; 114 | margin: 0 2px; 115 | font-weight: bold; 116 | } 117 | 118 | #window { 119 | font-weight: 500; 120 | } 121 | 122 | #tray menu { 123 | background-color: #202124; 124 | color: #e0e0e0; 125 | } 126 | #tray menu *:hover, 127 | #tray menu *:selected { 128 | background-color: #3a3b3c; 129 | color: #ffffff; 130 | } 131 | -------------------------------------------------------------------------------- /satty/config.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | # Start Satty in fullscreen mode 3 | # fullscreen = true 4 | # Exit directly after copy/save action 5 | early-exit = true 6 | # Draw corners of rectangles round if the value is greater than 0 (0 disables rounded corners) 7 | corner-roundness = 12 8 | # Select the tool on startup [possible values: pointer, crop, line, arrow, rectangle, text, marker, blur, brush] 9 | initial-tool = "brush" 10 | # Configure the command to be called on copy, for example `wl-copy` 11 | copy-command = "wl-copy" 12 | # Increase or decrease the size of the annotations 13 | annotation-size-factor = 2 14 | # Filename to use for saving action. Omit to disable saving to file. Might contain format specifiers: https://docs.rs/chrono/latest/chrono/format/strftime/index.html 15 | output-filename = "/tmp/test-%Y-%m-%d_%H:%M:%S.png" 16 | # After copying the screenshot, save it to a file as well 17 | save-after-copy = false 18 | # Hide toolbars by default 19 | default-hide-toolbars = false 20 | # Experimental: whether window focus shows/hides toolbars. This does not affect initial state of toolbars, see default-hide-toolbars. 21 | #focus-toggles-toolbars = false 22 | # The primary highlighter to use, the other is accessible by holding CTRL at the start of a highlight [possible values: block, freehand] 23 | primary-highlighter = "block" 24 | # Disable notifications 25 | disable-notifications = false 26 | # Actions to trigger on right click (order is important) 27 | # [possible values: save-to-clipboard, save-to-file, exit] 28 | actions-on-right-click = [] 29 | # Actions to trigger on Enter key (order is important) 30 | # [possible values: save-to-clipboard, save-to-file, exit] 31 | actions-on-enter = ["save-to-clipboard"] 32 | # Actions to trigger on Escape key (order is important) 33 | # [possible values: save-to-clipboard, save-to-file, exit] 34 | actions-on-escape = ["exit"] 35 | # Action to perform when the Enter key is pressed [possible values: save-to-clipboard, save-to-file] 36 | # Deprecated: use actions-on-enter instead 37 | action-on-enter = "save-to-clipboard" 38 | # Right click to copy 39 | # Deprecated: use actions-on-right-click instead 40 | right-click-copy = false 41 | # request no window decoration. Please note that the compositor has the final say in this. At this point. requires xdg-decoration-unstable-v1. 42 | no-window-decoration = true 43 | # experimental feature: adjust history size for brush input smooting (0: disabled, default: 0, try e.g. 5 or 10) 44 | # brush-smooth-history-size = 10 45 | 46 | # Font to use for text annotations 47 | [font] 48 | family = "Roboto" 49 | style = "Bold" 50 | 51 | # Custom colours for the colour palette 52 | [color-palette] 53 | # These will be shown in the toolbar for quick selection 54 | palette = ["#00ffff", "#a52a2a", "#dc143c", "#ff1493", "#ffd700", "#008000"] 55 | 56 | # These will be available in the color picker as presets 57 | # Leave empty to use GTK's default 58 | custom = ["#00ffff", "#a52a2a", "#dc143c", "#ff1493", "#ffd700", "#008000"] 59 | -------------------------------------------------------------------------------- /hypr/hyprlock.conf: -------------------------------------------------------------------------------- 1 | # sample hyprlock.conf 2 | # for more configuration options, refer https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock 3 | # 4 | # rendered text in all widgets supports pango markup (e.g. or tags) 5 | # ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#general-remarks 6 | # 7 | # shortcuts to clear password buffer: ESC, Ctrl+U, Ctrl+Backspace 8 | # 9 | # you can get started by copying this config to ~/.config/hypr/hyprlock.conf 10 | # 11 | 12 | $font = Monospace 13 | 14 | general { 15 | hide_cursor = false 16 | } 17 | 18 | # uncomment to enable fingerprint authentication 19 | # auth { 20 | # fingerprint { 21 | # enabled = true 22 | # ready_message = Scan fingerprint to unlock 23 | # present_message = Scanning... 24 | # retry_delay = 250 # in milliseconds 25 | # } 26 | # } 27 | 28 | animations { 29 | enabled = true 30 | bezier = linear, 1, 1, 0, 0 31 | animation = fadeIn, 1, 5, linear 32 | animation = fadeOut, 1, 5, linear 33 | animation = inputFieldDots, 1, 2, linear 34 | } 35 | 36 | background { 37 | monitor = 38 | path = screenshot 39 | blur_passes = 3 40 | } 41 | 42 | input-field { 43 | monitor = 44 | size = 20%, 5% 45 | outline_thickness = 3 46 | inner_color = rgba(0, 0, 0, 0.0) # no fill 47 | 48 | outer_color = rgba(33ccffee) rgba(00ff99ee) 45deg 49 | check_color = rgba(00ff99ee) rgba(ff6633ee) 120deg 50 | fail_color = rgba(ff6633ee) rgba(ff0066ee) 40deg 51 | 52 | font_color = rgb(143, 143, 143) 53 | fade_on_empty = false 54 | rounding = 15 55 | 56 | font_family = $font 57 | placeholder_text = Input password... 58 | fail_text = $PAMFAIL 59 | 60 | # uncomment to use a letter instead of a dot to indicate the typed password 61 | # dots_text_format = * 62 | # dots_size = 0.4 63 | dots_spacing = 0.3 64 | 65 | # uncomment to use an input indicator that does not show the password length (similar to swaylock's input indicator) 66 | # hide_input = true 67 | 68 | position = 0, -20 69 | halign = center 70 | valign = center 71 | } 72 | 73 | # TIME 74 | label { 75 | monitor = 76 | text = $TIME # ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#variable-substitution 77 | font_size = 90 78 | font_family = $font 79 | 80 | position = -30, 0 81 | halign = right 82 | valign = top 83 | } 84 | 85 | # DATE 86 | label { 87 | monitor = 88 | text = cmd[update:60000] date +"%A, %d %B %Y" # update every 60 seconds 89 | font_size = 25 90 | font_family = $font 91 | 92 | position = -30, -150 93 | halign = right 94 | valign = top 95 | } 96 | 97 | label { 98 | monitor = 99 | text = $LAYOUT[en,ru] 100 | font_size = 24 101 | onclick = hyprctl switchxkblayout all next 102 | 103 | position = 250, -20 104 | halign = center 105 | valign = center 106 | } 107 | 108 | -------------------------------------------------------------------------------- /nvim/AGENTS.md: -------------------------------------------------------------------------------- 1 | # Repository Guidelines 2 | 3 | ## Project Structure & Module Organization 4 | - `init.lua`: Entry point; sets globals and loads `config.lazy` (bootstraps lazy.nvim). 5 | - `lua/config/`: Core setup — `options.lua`, `keymaps.lua`, `autocmds.lua`, and `lazy.lua` (plugin manager config). 6 | - `lua/plugins/`: Topical plugin specs returning tables (e.g., `ui.lua`, `tools.lua`, `coding.lua`, `ai.lua`). 7 | - `data/`: Extra runtime data and filetype tweaks (e.g., `plenary/filetypes/gjs.lua`). 8 | - `lazy-lock.json`: Pinned plugin versions — commit intentional updates only. 9 | 10 | ## Build, Test, and Development Commands 11 | - Install/sync plugins: `:Lazy sync` (uses `lazy-lock.json`). 12 | - Check plugin health: `:Lazy check` and `:checkhealth`. 13 | - Headless sanity check: `nvim --headless -u ./init.lua '+qa'` (should exit cleanly). 14 | - Format current buffer: `:lua require("conform").format({ async = true })`. 15 | 16 | ## Coding Style & Naming Conventions 17 | - Lua: 2‑space indent, no tabs; prefer `vim.opt`, `vim.api`, and table‑scoped locals over globals. 18 | - New config belongs in `lua/config/*.lua`; keep files focused and small. 19 | - New plugins: add a spec in `lua/plugins/.lua` returning a list of plugin tables. 20 | - Keymaps: use `desc` for discoverability (works with which‑key); avoid surprising global side effects. 21 | - Keep names descriptive; group related plugins (e.g., UI, tools, coding, AI). 22 | 23 | ## Testing Guidelines 24 | - Startup: `nvim --clean -u ./init.lua` loads without errors. 25 | - After plugin edits: run `:Lazy sync` and `:checkhealth`. 26 | - LSP/formatting: open a representative file (Lua, Ruby, JS) and verify diagnostics and formatting via Conform. 27 | 28 | ## Commit & Pull Request Guidelines 29 | - Commits: imperative, concise; prefix scope when useful. 30 | - Examples: `plugins: add telescope fzf`, `config: tweak keymaps`, `ui: adjust lualine`. 31 | - PRs: include a short summary, before/after notes (screenshots if UI‑adjacent), and call out any machine‑specific assumptions. 32 | - Update `lazy-lock.json` only when intentionally bumping or pinning versions. 33 | - Stage‑only workflow: do not create commits in this repo. Stage changes with `git add -A` and place a draft commit message (prefixed `DRAFT:`) in the PR description or comment. Do not run `git commit` unless explicitly requested. 34 | - Examples: `DRAFT: plugins: add telescope fzf`, `DRAFT: config: adjust keymaps`. 35 | 36 | ## Pull Request Checklist 37 | - Staging only: changes are staged (`git add -A`); no local commits created. 38 | - Draft message: PR description starts with `DRAFT: : `. 39 | - Linked issues: include `Fixes #` or `Refs #` when relevant. 40 | - Validation: ran `:Lazy sync`, `:checkhealth`, and `nvim --headless -u ./init.lua '+qa'`. 41 | - UI changes: include before/after screenshots or GIFs. 42 | - Machine‑specific notes: call out any paths, env vars, or external tools. 43 | 44 | ## Security & Configuration Tips 45 | - Avoid hardcoded absolute paths. Guard machine‑specific bits (e.g., `/home/sam/Source/discourse`) behind checks or make them configurable. 46 | - Prefer lazy‑loading for experimental plugins; keep defaults stable and fast. 47 | -------------------------------------------------------------------------------- /nvim/lua/plugins/tools.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | "nvim-telescope/telescope.nvim", 4 | dependencies = { 5 | "nvim-lua/plenary.nvim", 6 | { "nvim-telescope/telescope-fzf-native.nvim", build = "make" }, 7 | "nvim-telescope/telescope-ui-select.nvim", 8 | }, 9 | keys = { 10 | { "", "Telescope find_files theme=ivy disable_devicons=true", desc = "Find files (ivy)" }, 11 | { "ff", "Telescope find_files", desc = "Find files" }, 12 | { "fg", "Telescope live_grep", desc = "Live grep" }, 13 | { "fb", "Telescope buffers", desc = "Buffers" }, 14 | { "fh", "Telescope help_tags", desc = "Help tags" }, 15 | { "fo", "Telescope oldfiles", desc = "Recent files" }, 16 | { 17 | "fm", 18 | function() 19 | local previewers = require("telescope.previewers") 20 | local pickers = require("telescope.pickers") 21 | local sorters = require("telescope.sorters") 22 | local finders = require("telescope.finders") 23 | 24 | local branch = "main" 25 | if vim.fn.system("git rev-parse --verify main 2>/dev/null") == "" then 26 | branch = "master" 27 | end 28 | 29 | local merge_base = vim.fn.system("git merge-base " .. branch .. " HEAD"):gsub("\n", "") 30 | if merge_base == "" then 31 | merge_base = branch 32 | end 33 | 34 | pickers.new({}, { 35 | prompt_title = "Modified (vs " .. branch .. " base)", 36 | -- Use separate command calls and combine, avoiding && which stops on empty 37 | finder = finders.new_oneshot_job({ 38 | "sh", "-c", 39 | "(git diff --name-only --relative " .. 40 | merge_base .. "; git ls-files --others --exclude-standard) | sort -u" 41 | }, {}), 42 | sorter = sorters.get_fuzzy_file(), 43 | previewer = previewers.new_termopen_previewer({ 44 | get_command = function(entry) 45 | if vim.fn.system("git ls-files --error-unmatch " .. vim.fn.shellescape(entry.value) .. " 2>/dev/null") ~= "" then 46 | return { "git", "diff", merge_base, "--", entry.value } 47 | else 48 | return { "git", "diff", "--no-index", "/dev/null", entry.value } 49 | end 50 | end 51 | }) 52 | }):find() 53 | end, 54 | desc = "Changed files (main)" 55 | }, 56 | { "fw", "Telescope git_status", desc = "Git changed files" }, 57 | }, 58 | config = function() 59 | local telescope = require("telescope") 60 | telescope.setup({ 61 | defaults = { 62 | file_ignore_patterns = { "node_modules/", "tmp/", "log/" }, 63 | -- path_display = { "smart" }, 64 | }, 65 | }) 66 | 67 | telescope.load_extension("fzf") 68 | telescope.load_extension("ui-select") 69 | end, 70 | }, 71 | } 72 | -------------------------------------------------------------------------------- /nvim/lua/config/keymaps.lua: -------------------------------------------------------------------------------- 1 | local map = vim.keymap.set 2 | 3 | -- General mappings 4 | map("n", "", ":set number!", { desc = "Toggle line numbers" }) 5 | map("n", "", ":tabnext", { desc = "Go to next tab" }) 6 | map("n", "", ":tabprevious", { desc = "Go to previous tab" }) 7 | map("n", "", ":previous", { desc = "Go to previous buffer" }) 8 | map("n", "", ":next", { desc = "Go to next buffer" }) 9 | 10 | -- Leader mappings 11 | map("n", "s", ":!touch tmp/refresh_browser", { desc = "Touch browser refresh file" }) 12 | map("n", "g", ":silent Git gui", { desc = "Open Git GUI" }) 13 | map("n", "l", function() 14 | if vim.o.hlsearch and vim.v.hlsearch == 1 then 15 | return ":nohls" 16 | else 17 | return ":set hls" 18 | end 19 | end, { expr = true, silent = true, desc = "Toggle search highlighting" }) 20 | 21 | -- Window navigation (Alt + Arrow keys) 22 | map("n", "", ":wincmd k", { desc = "Move to window above" }) 23 | map("n", "", ":wincmd j", { desc = "Move to window below" }) 24 | map("n", "", ":wincmd h", { desc = "Move to window left" }) 25 | map("n", "", ":wincmd l", { desc = "Move to window right" }) 26 | 27 | -- Window resizing 28 | map("n", "=", ':exe "resize " . (winheight(0) * 3/2)', { desc = "Increase window height" }) 29 | map("n", "-", ':exe "resize " . (winheight(0) * 2/3)', { desc = "Decrease window height" }) 30 | 31 | -- GitHub link in visual mode (converted from original vimfunc) 32 | local function github_link() 33 | local start_pos = vim.fn.getpos("v") 34 | local start_line = start_pos[2] 35 | local end_line = vim.api.nvim_win_get_cursor(0)[1] 36 | 37 | local path = vim.fn.resolve(vim.fn.expand("%:p")) 38 | local dir = vim.fn.shellescape(vim.fn.fnamemodify(path, ":h")) 39 | 40 | local repo = vim.fn 41 | .system( 42 | string.format( 43 | "cd %s && git remote -v | awk '{ tmp = match($2, /github/); if (tmp) { split($2,a,/github.com[:\\.]/); c = a[2]; split(c,b,/[.]/); print b[1]; exit; }}'", 44 | dir 45 | ) 46 | ) 47 | :gsub("%s+$", "") 48 | 49 | local root = vim.fn.system(string.format("cd %s && git rev-parse --show-toplevel", dir)):gsub("%s+$", "") 50 | local relative = string.sub(path, string.len(root) + 2) 51 | local repo_sha = vim.fn.system(string.format("cd %s && git rev-parse HEAD", dir)):gsub("%s+$", "") 52 | 53 | local link = 54 | string.format("https://github.com/%s/blob/%s/%s#L%d-L%d", repo, repo_sha, relative, start_line, end_line) 55 | 56 | vim.fn.setreg("+", link) 57 | vim.fn.setreg("*", link) 58 | print(link) 59 | end 60 | 61 | map("v", "g", github_link, { desc = "Copy GitHub permalink to clipboard" }) 62 | 63 | local function restart_discourse() 64 | local pid_file = "/home/sam/Source/discourse/tmp/pids/unicorn.pid" 65 | 66 | -- Check if pid file exists 67 | local f = io.open(pid_file, "r") 68 | if f == nil then 69 | vim.notify("ERROR: unicorn not started") 70 | return 71 | end 72 | f:close() 73 | 74 | -- Execute the HUP signal 75 | vim.fn.system(string.format("kill -HUP $(cat %s)", pid_file)) 76 | vim.notify("Restarting Discourse...") 77 | end 78 | 79 | map("n", "d", restart_discourse, { desc = "Restart Discourse server" }) 80 | -------------------------------------------------------------------------------- /waybar/config: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "layer": "top", 4 | "position": "bottom", 5 | 6 | // If height property would be not present, it'd be calculated dynamically 7 | "height": 30, 8 | 9 | "modules-left": [ 10 | "hyprland/workspaces", 11 | "niri/workspaces" 12 | ], 13 | "modules-center": [ 14 | "hyprland/window" 15 | ], 16 | "modules-right": [ 17 | "custom/updates", 18 | "disk", 19 | "memory", 20 | "cpu", 21 | "wireplumber", 22 | "custom/weather", 23 | "tray", 24 | "clock#date", 25 | "clock#time" 26 | ], 27 | 28 | "disk": { 29 | "interval": 30, 30 | "format": "󰋊 {percentage_used}%", 31 | "path": "/" 32 | }, 33 | 34 | "niri/workspaces": { 35 | "format": "{index}", 36 | "all-outputs": true 37 | }, 38 | 39 | 40 | "clock#time": { 41 | "interval": 1, 42 | "format": "{:%H:%M}", 43 | "tooltip": true 44 | }, 45 | 46 | "clock#date": { 47 | "interval": 10, 48 | "format": " {:%e %b %Y}", // Icon: calendar-alt 49 | "tooltip-format": "{:%e %B %Y}" 50 | }, 51 | 52 | "cpu": { 53 | "ybainterval": 5, 54 | "format": " {usage}% ({load})", // Icon: microchip 55 | "states": { 56 | "warning": 70, 57 | "critical": 90 58 | } 59 | }, 60 | 61 | "wireplumber": { 62 | "format": "{volume}% {icon}", 63 | "format-muted": "", 64 | "on-click": "helvum", 65 | "format-icons": ["", "", ""] 66 | }, 67 | 68 | "memory": { 69 | "interval": 5, 70 | "format": "󰍛 {}%", // Icon: memory 71 | "states": { 72 | "warning": 70, 73 | "critical": 90 74 | } 75 | }, 76 | 77 | "custom/weather": { 78 | "format": "{}°", 79 | "tooltip": true, 80 | "interval": 3600, 81 | "exec": "wttrbar --location Sydney", 82 | "return-type": "json" 83 | }, 84 | 85 | "network": { 86 | "interval": 5, 87 | "format-wifi": " {essid} ({signalStrength}%)", // Icon: wifi 88 | "format-ethernet": "󰈀 {ifname}: {ipaddr}/{cidr}", // Icon: ethernet 89 | "format-disconnected": "⚠ Disconnected", 90 | "tooltip-format": "{ifname}: {ipaddr}" 91 | }, 92 | "custom/updates": { 93 | "format": "{icon} {text}", 94 | "return-type": "json", 95 | "format-icons": { 96 | "has-updates": "󱍷", 97 | "updated": "󰂪" 98 | }, 99 | "exec-if": "which waybar-module-pacman-updates", 100 | "exec": "waybar-module-pacman-updates --interval-seconds 600 --network-interval-seconds 1800" 101 | }, 102 | 103 | "temperature": { 104 | "critical-threshold": 80, 105 | "interval": 5, 106 | "format": "{icon} {temperatureC}°C", 107 | "format-icons": [ 108 | "", // Icon: temperature-empty 109 | "", // Icon: temperature-quarter 110 | "", // Icon: temperature-half 111 | "", // Icon: temperature-three-quarters 112 | "" // Icon: temperature-full 113 | ], 114 | "tooltip": true 115 | }, 116 | 117 | "tray": { 118 | "icon-size": 21, 119 | "spacing": 10 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /nvim/lua/config/autocmds.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | 3 | -- Create autogroup 4 | local group = api.nvim_create_augroup("VimrcGroup", { clear = true }) 5 | 6 | api.nvim_create_autocmd({ "BufWinEnter" }, { 7 | group = group, 8 | pattern = "*", 9 | command = "match ExtraWhitespace /\\s\\+$/", 10 | }) 11 | 12 | api.nvim_create_autocmd({ "InsertEnter" }, { 13 | group = group, 14 | pattern = "*", 15 | command = "match ExtraWhitespace /\\s\\+\\%#\\@&/dev/null; then 31 | # We have color support; assume it's compliant with Ecma-48 32 | # (ISO/IEC-6429). (Lack of such support is extremely rare, and such 33 | # a case would tend to support setf rather than setaf.) 34 | color_prompt=yes 35 | else 36 | color_prompt= 37 | fi 38 | fi 39 | 40 | if [ "$color_prompt" = yes ]; then 41 | PROMPT="%{$fg[red]%}%n%{$reset_color%}@%{$fg[blue]%}%m %{$fg_no_bold[yellow]%}%1~ %{$reset_color%}%# " 42 | else 43 | PROMPT="%{$fg[red]%}%n%{$reset_color%}@%{$fg[blue]%}%m %{$fg_no_bold[yellow]%}%1~ %{$reset_color%}%# " 44 | fi 45 | unset color_prompt force_color_prompt 46 | 47 | # enable color support of ls and also add handy aliases 48 | if [ -x /usr/bin/dircolors ]; then 49 | test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" 50 | alias ls='ls --color=auto' 51 | #alias dir='dir --color=auto' 52 | #alias vdir='vdir --color=auto' 53 | 54 | alias grep='grep --color=auto' 55 | alias fgrep='fgrep --color=auto' 56 | alias egrep='egrep --color=auto' 57 | fi 58 | 59 | # some more ls aliases 60 | alias ll='ls -alF' 61 | alias la='ls -A' 62 | alias l='ls -CF' 63 | alias m='/home/sam/Source/mothership/cli/mothership' 64 | 65 | alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"' 66 | 67 | # Alias definitions. 68 | # You may want to put all your additions into a separate file like 69 | # ~/.bash_aliases, instead of adding them here directly. 70 | # See /usr/share/doc/bash-doc/examples in the bash-doc package. 71 | 72 | export RBENV_ROOT="${HOME}/.rbenv" 73 | if [ -d "${RBENV_ROOT}" ]; then 74 | export PATH="${RBENV_ROOT}/bin:${PATH}" 75 | eval "$(rbenv init -)" 76 | fi 77 | 78 | alias ts='sudo ntpdate -s time.nist.gov' 79 | 80 | export PATH="$PATH:$HOME/go/bin" 81 | 82 | export NVM_DIR="/home/sam/.nvm" 83 | source /usr/share/nvm/nvm.sh 84 | source /usr/share/nvm/bash_completion 85 | source /usr/share/nvm/install-nvm-exec 86 | 87 | [[ -s "/home/sam/.gvm/scripts/gvm" ]] && source "/home/sam/.gvm/scripts/gvm" 88 | 89 | alias mc='mailcatcher --ip 0.0.0.0 -f' 90 | 91 | eval $(/usr/bin/gnome-keyring-daemon --start --components=gpg,pkcs11,secrets,ssh) 92 | export GNOME_KEYRING_CONTROL GNOME_KEYRING_PID GPG_AGENT_INFO SSH_AUTH_SOCK 93 | 94 | if [ -S $SSH_AUTH_SOCK ]; then 95 | ssh-add -l | grep "The agent has no identities" && ssh-add 96 | fi 97 | 98 | stty -ixon 99 | 100 | [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh 101 | 102 | alias vim=nvim 103 | alias spotify=spotify --force-device-scale-factor=2 104 | 105 | precmd() { 106 | CMD_PREFIX="sam" 107 | eval 'echo -ne "\033]0;${CMD_PREFIX}${PWD}\007"' 108 | } 109 | 110 | alias bb='sudo /usr/bin/bbswitch-load' 111 | export DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE=1 112 | 113 | export GPG_TTY=$(tty) 114 | -------------------------------------------------------------------------------- /nvim/lua/plugins/git.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | "lewis6991/gitsigns.nvim", 4 | event = { "BufReadPre", "BufNewFile" }, 5 | opts = { 6 | signs = { 7 | add = { text = "│" }, 8 | change = { text = "│" }, 9 | delete = { text = "_" }, 10 | topdelete = { text = "‾" }, 11 | changedelete = { text = "~" }, 12 | untracked = { text = "┆" }, 13 | }, 14 | on_attach = function(bufnr) 15 | local gs = package.loaded.gitsigns 16 | 17 | local function get_main_branch() 18 | -- Try to get default branch from remote 19 | local result = vim.fn.systemlist("git rev-parse --abbrev-ref origin/HEAD 2>/dev/null")[1] 20 | if result and not result:match("^fatal") then 21 | return result:gsub("origin/", "") 22 | end 23 | -- Check if main branch exists 24 | vim.fn.system("git show-ref --verify --quiet refs/heads/main") 25 | if vim.v.shell_error == 0 then 26 | return "main" 27 | end 28 | return "master" 29 | end 30 | 31 | local function map(mode, l, r, opts) 32 | opts = opts or {} 33 | opts.buffer = bufnr 34 | vim.keymap.set(mode, l, r, opts) 35 | end 36 | 37 | -- Navigation 38 | map("n", "]c", function() 39 | if vim.wo.diff then return "]c" end 40 | vim.schedule(function() gs.next_hunk() end) 41 | return "" 42 | end, { expr = true, desc = "Next hunk" }) 43 | 44 | map("n", "[c", function() 45 | if vim.wo.diff then return "[c" end 46 | vim.schedule(function() gs.prev_hunk() end) 47 | return "" 48 | end, { expr = true, desc = "Previous hunk" }) 49 | 50 | -- Actions 51 | map("n", "hs", gs.stage_hunk, { desc = "Stage hunk" }) 52 | map("n", "hr", gs.reset_hunk, { desc = "Reset hunk" }) 53 | map("v", "hs", function() gs.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, 54 | { desc = "Stage hunk" }) 55 | map("v", "hr", function() gs.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, 56 | { desc = "Reset hunk" }) 57 | map("n", "hS", gs.stage_buffer, { desc = "Stage buffer" }) 58 | map("n", "hu", gs.undo_stage_hunk, { desc = "Undo stage hunk" }) 59 | map("n", "hR", gs.reset_buffer, { desc = "Reset buffer" }) 60 | map("n", "hp", gs.preview_hunk, { desc = "Preview hunk" }) 61 | map("n", "hb", function() gs.blame_line({ full = true }) end, { desc = "Blame line" }) 62 | map("n", "tb", gs.toggle_current_line_blame, { desc = "Toggle line blame" }) 63 | map("n", "hd", gs.diffthis, { desc = "Diff this" }) 64 | map("n", "hD", function() gs.diffthis("~") end, { desc = "Diff this ~" }) 65 | map("n", "td", gs.toggle_deleted, { desc = "Toggle deleted" }) 66 | map("n", "hm", function() gs.change_base(get_main_branch(), true) end, { desc = "Show changes from main branch" }) 67 | map("n", "hH", function() gs.reset_base(true) end, { desc = "Reset base to HEAD" }) 68 | 69 | -- Text object 70 | map({ "o", "x" }, "ih", ":Gitsigns select_hunk", { desc = "Select hunk" }) 71 | end, 72 | }, 73 | }, 74 | { 75 | "tpope/vim-fugitive", 76 | cmd = { "Git", "Gwrite", "Gcommit", "Gread" }, 77 | keys = { 78 | { "g", "Git gui", desc = "Git GUI" }, 79 | }, 80 | }, 81 | { 82 | "tpope/vim-rhubarb", 83 | dependencies = { "tpope/vim-fugitive" }, 84 | }, 85 | { 86 | "rhysd/git-messenger.vim", 87 | keys = { 88 | { "m", "(git-messenger)", desc = "Git Messenger" }, 89 | }, 90 | init = function() 91 | vim.g.git_messenger_no_default_mappings = true 92 | vim.g.git_messenger_always_into_popup = true 93 | end, 94 | }, 95 | { 96 | "kdheepak/lazygit.nvim", 97 | lazy = true, 98 | cmd = { 99 | "LazyGit", 100 | "LazyGitConfig", 101 | "LazyGitCurrentFile", 102 | "LazyGitFilter", 103 | "LazyGitFilterCurrentFile", 104 | }, 105 | -- optional for floating window border decoration 106 | dependencies = { 107 | "nvim-lua/plenary.nvim", 108 | }, 109 | keys = { 110 | { "lg", "LazyGit", desc = "LazyGit" }, 111 | { "lf", "LazyGitFilterCurrentFile", desc = "LazyGit" } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /niri/config.kdl: -------------------------------------------------------------------------------- 1 | // Niri configuration derived from the existing Hyprland setup. 2 | 3 | input { 4 | keyboard { 5 | xkb { 6 | layout "us" 7 | options "caps:swapescape" 8 | } 9 | numlock 10 | } 11 | 12 | touchpad { 13 | tap 14 | // natural-scroll 15 | } 16 | 17 | focus-follows-mouse max-scroll-amount="0%" 18 | } 19 | 20 | output "DP-1" { 21 | mode "5120x2160@120.000" 22 | scale 1.6 23 | } 24 | 25 | layout { 26 | gaps 4 27 | 28 | focus-ring { 29 | width 1 30 | active-color "#505050aa" 31 | inactive-color "#595959aa" 32 | } 33 | } 34 | 35 | // Force Git GUI windows (Tk/XWayland) to open floating for easier use. 36 | window-rule { 37 | match app-id=r#"(?i)^git-gui$"# 38 | open-floating true 39 | open-focused true 40 | } 41 | 42 | spawn-sh-at-startup "systemctl --user start hyprpolkitagent" 43 | spawn-sh-at-startup "1password --silent" 44 | spawn-sh-at-startup "env QT_QPA_PLATFORM=xcb copyq" 45 | spawn-sh-at-startup "telegram-desktop -startintray" 46 | spawn-sh-at-startup "slack -u" 47 | spawn-sh-at-startup "signal-desktop --start-in-tray" 48 | spawn-sh-at-startup "blueman-applet" 49 | spawn-sh-at-startup "swww-daemon" 50 | spawn-sh-at-startup "hypridle" 51 | spawn-sh-at-startup "waybar" 52 | spawn-sh-at-startup "dunst" 53 | spawn-sh-at-startup "opendeck --hide" 54 | 55 | binds { 56 | Super+Return { spawn "~/.config/niri/scripts/terminal.sh"; } 57 | Super+Q repeat=false { close-window; } 58 | Super+F { spawn "dolphin"; } 59 | Super+V { toggle-window-floating; } 60 | Super+D { spawn "fuzzel"; } 61 | Super+J { toggle-column-tabbed-display; } 62 | Super+X { spawn "~/.config/niri/scripts/power-menu.sh"; } 63 | Super+T { switch-preset-column-width; } 64 | 65 | Super+Left { focus-column-left; } 66 | Super+Right { focus-column-right; } 67 | Super+Up { focus-window-up; } 68 | Super+Down { focus-window-down; } 69 | 70 | Super+Shift+Right { swap-window-right; } 71 | Super+Shift+Left { swap-window-left; } 72 | 73 | Super+1 { focus-workspace 1; } 74 | Super+2 { focus-workspace 2; } 75 | Super+3 { focus-workspace 3; } 76 | Super+4 { focus-workspace 4; } 77 | Super+5 { focus-workspace 5; } 78 | Super+6 { focus-workspace 6; } 79 | Super+7 { focus-workspace 7; } 80 | Super+8 { focus-workspace 8; } 81 | Super+9 { focus-workspace 9; } 82 | Super+0 { focus-workspace 10; } 83 | 84 | Super+Shift+1 { move-window-to-workspace 1 focus=false; } 85 | Super+Shift+2 { move-window-to-workspace 2 focus=false; } 86 | Super+Shift+3 { move-window-to-workspace 3 focus=false; } 87 | Super+Shift+4 { move-window-to-workspace 4 focus=false; } 88 | Super+Shift+5 { move-window-to-workspace 5 focus=false; } 89 | Super+Shift+6 { move-window-to-workspace 6 focus=false; } 90 | Super+Shift+7 { move-window-to-workspace 7 focus=false; } 91 | Super+Shift+8 { move-window-to-workspace 8 focus=false; } 92 | Super+Shift+9 { move-window-to-workspace 9 focus=false; } 93 | Super+Shift+0 { move-window-to-workspace 10 focus=false; } 94 | 95 | Super+S { spawn "~/.config/niri/scripts/toggle-magic-workspace.sh"; } 96 | Super+Shift+S { spawn "~/.config/niri/scripts/send-to-magic-workspace.sh"; } 97 | 98 | Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; } 99 | Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; } 100 | Mod+WheelScrollRight { focus-column-right; } 101 | Mod+WheelScrollLeft { focus-column-left; } 102 | 103 | Print { spawn "hyprshot" "-m" "region" "--clipboard-only"; } 104 | Shift+Print { spawn-sh "~/.config/hypr/countdown 3 && hyprshot -z -m region --clipboard-only"; } 105 | Ctrl+Print { spawn "hyprshot" "-z" "-m" "output" "-m" "DP-1" "--clipboard-only"; } 106 | Super+E { spawn-sh "wl-paste --type image/png --no-newline | swappy -f -"; } 107 | Super+R { spawn "~/.config/niri/scripts/screen-record.sh"; } 108 | 109 | XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"; } 110 | XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"; } 111 | XF86AudioMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; } 112 | XF86AudioMicMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; } 113 | 114 | XF86MonBrightnessUp allow-when-locked=true { spawn-sh "brightnessctl -e4 -n2 set 5%+"; } 115 | XF86MonBrightnessDown allow-when-locked=true { spawn-sh "brightnessctl -e4 -n2 set 5%-"; } 116 | 117 | XF86AudioNext { spawn "playerctl" "next"; } 118 | XF86AudioPause { spawn "playerctl" "play-pause"; } 119 | XF86AudioPlay { spawn "playerctl" "play-pause"; } 120 | XF86AudioPrev { spawn "playerctl" "previous"; } 121 | } 122 | -------------------------------------------------------------------------------- /nvim/lazy-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "FixCursorHold.nvim": { "branch": "master", "commit": "1900f89dc17c603eec29960f57c00bd9ae696495" }, 3 | "ccc.nvim": { "branch": "main", "commit": "9d1a256e006decc574789dfc7d628ca11644d4c2" }, 4 | "cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" }, 5 | "cmp-cmdline": { "branch": "main", "commit": "d126061b624e0af6c3a556428712dd4d4194ec6d" }, 6 | "cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" }, 7 | "cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" }, 8 | "conform.nvim": { "branch": "master", "commit": "ffe26e8df8115c9665d24231f8a49fadb2d611ce" }, 9 | "copilot.vim": { "branch": "release", "commit": "f89e977c87180519ba3b942200e3d05b17b1e2fc" }, 10 | "gist.nvim": { "branch": "main", "commit": "fabafb01dc6a710bec3417c83980cfdefd72d9e6" }, 11 | "git-messenger.vim": { "branch": "master", "commit": "fd124457378a295a5d1036af4954b35d6b807385" }, 12 | "gitsigns.nvim": { "branch": "main", "commit": "5813e4878748805f1518cee7abb50fd7205a3a48" }, 13 | "go.nvim": { "branch": "master", "commit": "41a18f0c05534c375bafec7ed05cdb409c4abcc6" }, 14 | "gruvbox.nvim": { "branch": "main", "commit": "5e0a460d8e0f7f669c158dedd5f9ae2bcac31437" }, 15 | "guihua.lua": { "branch": "master", "commit": "ef44ba40f12e56c1c9fa45967f2b4d142e4b97a0" }, 16 | "incline.nvim": { "branch": "main", "commit": "6a3b0635bcd2490dbb1fa124217d41bf69ca0fa2" }, 17 | "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" }, 18 | "lazygit.nvim": { "branch": "main", "commit": "2305deed25bc61b866d5d39189e9105a45cf1cfb" }, 19 | "lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" }, 20 | "mason-lspconfig.nvim": { "branch": "main", "commit": "c55bd8a8fb191e24176c206a7af1dd51ce7276a5" }, 21 | "mason.nvim": { "branch": "main", "commit": "57e5a8addb8c71fb063ee4acda466c7cf6ad2800" }, 22 | "neo-tree.nvim": { "branch": "v3.x", "commit": "f3df514fff2bdd4318127c40470984137f87b62e" }, 23 | "neotest": { "branch": "master", "commit": "deadfb1af5ce458742671ad3a013acb9a6b41178" }, 24 | "neotest-rspec": { "branch": "main", "commit": "e7dc67c1167a9e593c804a6be6808ba9a5920d43" }, 25 | "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, 26 | "nvim-cmp": { "branch": "main", "commit": "d97d85e01339f01b842e6ec1502f639b080cb0fc" }, 27 | "nvim-dap": { "branch": "master", "commit": "5860c7c501eb428d3137ee22c522828d20cca0b3" }, 28 | "nvim-dap-ruby": { "branch": "main", "commit": "ba36f9905ca9c6d89e5af5467a52fceeb2bbbf6d" }, 29 | "nvim-lspconfig": { "branch": "master", "commit": "a2bd1cf7b0446a7414aaf373cea5e4ca804c9c69" }, 30 | "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, 31 | "nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, 32 | "nvim-treesitter-textobjects": { "branch": "main", "commit": "76deedf0f1cec4496ef8d49b6d1f020f6d0c6ec9" }, 33 | "nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" }, 34 | "nvim-unception": { "branch": "main", "commit": "df0e505f0f1371c49c2bcf993985962edb5a279d" }, 35 | "nvim-web-devicons": { "branch": "master", "commit": "8dcb311b0c92d460fac00eac706abd43d94d68af" }, 36 | "playground": { "branch": "master", "commit": "ba48c6a62a280eefb7c85725b0915e021a1a0749" }, 37 | "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, 38 | "render-markdown.nvim": { "branch": "main", "commit": "b2b135347e299ffbf7f4123fb7811899b0c9f4b8" }, 39 | "sidekick.nvim": { "branch": "main", "commit": "c2bdf8cfcd87a6be5f8b84322c1b5052e78e302e" }, 40 | "snacks.nvim": { "branch": "main", "commit": "fe7cfe9800a182274d0f868a74b7263b8c0c020b" }, 41 | "telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" }, 42 | "telescope-ui-select.nvim": { "branch": "master", "commit": "6e51d7da30bd139a6950adf2a47fda6df9fa06d2" }, 43 | "telescope.nvim": { "branch": "master", "commit": "e69b434b968a33815e2f02a5c7bd7b8dd4c7d4b2" }, 44 | "vim-commentary": { "branch": "master", "commit": "64a654ef4a20db1727938338310209b6a63f60c9" }, 45 | "vim-endwise": { "branch": "master", "commit": "4994afb0cdf956d9a665a14b9c834869e602c396" }, 46 | "vim-fugitive": { "branch": "master", "commit": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4" }, 47 | "vim-rails": { "branch": "master", "commit": "b0a5c76f86ea214ade36ab0b811e730c3f0add67" }, 48 | "vim-rhubarb": { "branch": "master", "commit": "5496d7c94581c4c9ad7430357449bb57fc59f501" }, 49 | "vim-ruby": { "branch": "master", "commit": "bf3a5994ce63796db7b1b04aea92772271f387aa" }, 50 | "vim-surround": { "branch": "master", "commit": "3d188ed2113431cf8dac77be61b842acb64433d9" }, 51 | "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }, 52 | "yaml.nvim": { "branch": "main", "commit": "e70ee49f7aefc79dce020d3ffc3c9447b0c52236" } 53 | } 54 | -------------------------------------------------------------------------------- /nvim/lua/config/test_runner.lua: -------------------------------------------------------------------------------- 1 | -- Simple test runner: opens a right panel and runs tests 2 | -- Supports: bin/rspec (Ruby), with dv container detection 3 | 4 | local M = {} 5 | 6 | -- dv path constants 7 | local DV_LOCAL_PREFIX = "/home/sam/.local/share/dv/discourse_src" 8 | local DV_CONTAINER_PREFIX = "/var/www/discourse" 9 | 10 | -- Check if current file is in a dv-managed directory 11 | local function is_dv_path(filepath) 12 | return filepath:match("/%.local/share/dv/") ~= nil 13 | end 14 | 15 | -- Convert local dv path to container-relative path 16 | local function to_container_path(filepath) 17 | -- /home/sam/.local/share/dv/discourse_src/plugins/foo/spec/bar.rb 18 | -- becomes: ./plugins/foo/spec/bar.rb 19 | local relative = filepath:gsub("^" .. DV_LOCAL_PREFIX, ".") 20 | return relative 21 | end 22 | 23 | -- Build the rspec command based on file location 24 | local function build_rspec_cmd(filepath, line) 25 | local target 26 | local use_dv = is_dv_path(filepath) 27 | 28 | if use_dv then 29 | target = to_container_path(filepath) 30 | else 31 | target = filepath 32 | end 33 | 34 | if line then 35 | target = target .. ":" .. line 36 | end 37 | 38 | if use_dv then 39 | return string.format("dv run -- bin/rspec %s", target) 40 | else 41 | return string.format("bin/rspec %s", target) 42 | end 43 | end 44 | 45 | -- Track the test runner buffer and window 46 | local test_buf = nil 47 | local test_win = nil 48 | 49 | -- Open a vertical split on the right and run the command 50 | local function run_in_panel(cmd) 51 | local current_win = vim.api.nvim_get_current_win() 52 | 53 | -- Kill old buffer first if it exists 54 | if test_buf and vim.api.nvim_buf_is_valid(test_buf) then 55 | pcall(function() 56 | local job_id = vim.b[test_buf].terminal_job_id 57 | if job_id then vim.fn.jobstop(job_id) end 58 | end) 59 | vim.api.nvim_buf_delete(test_buf, { force = true }) 60 | test_buf = nil 61 | end 62 | 63 | -- Check if test window still exists (buffer delete may have closed it) 64 | local win_valid = test_win and vim.api.nvim_win_is_valid(test_win) 65 | 66 | if win_valid then 67 | vim.api.nvim_set_current_win(test_win) 68 | else 69 | vim.cmd("botright vnew") 70 | vim.cmd("vertical resize 80") 71 | test_win = vim.api.nvim_get_current_win() 72 | end 73 | 74 | -- Create fresh buffer for terminal 75 | test_buf = vim.api.nvim_create_buf(false, true) 76 | vim.api.nvim_win_set_buf(test_win, test_buf) 77 | 78 | -- Run the command 79 | vim.fn.termopen(cmd, { 80 | on_exit = function(_, _, _) 81 | -- Delay to let nvim add the "[Process exited]" message 82 | vim.defer_fn(function() 83 | if test_buf and vim.api.nvim_buf_is_valid(test_buf) then 84 | vim.bo[test_buf].modifiable = true 85 | -- Find and delete the line containing "[Process exited" 86 | local lines = vim.api.nvim_buf_get_lines(test_buf, 0, -1, false) 87 | for i = #lines, 1, -1 do 88 | if lines[i]:match("%[Process exited") then 89 | vim.api.nvim_buf_set_lines(test_buf, i - 1, i, false, {}) 90 | break 91 | end 92 | end 93 | vim.bo[test_buf].modifiable = false 94 | vim.bo[test_buf].modified = false 95 | end 96 | end, 10) 97 | end, 98 | }) 99 | 100 | -- Return focus to original window 101 | vim.api.nvim_set_current_win(current_win) 102 | end 103 | 104 | -- Build the qunit command for javascript 105 | local function build_qunit_cmd(filepath) 106 | local target 107 | if is_dv_path(filepath) then 108 | target = to_container_path(filepath) 109 | return string.format("dv run -- bin/qunit %s", target) 110 | else 111 | return string.format("bin/qunit %s", filepath) 112 | end 113 | end 114 | 115 | -- Run all tests in current file 116 | function M.run_file() 117 | local filepath = vim.fn.expand("%:p") 118 | local ft = vim.bo.filetype 119 | 120 | if ft == "ruby" then 121 | run_in_panel(build_rspec_cmd(filepath, nil)) 122 | elseif ft == "javascript" then 123 | run_in_panel(build_qunit_cmd(filepath)) 124 | else 125 | vim.notify("No test runner for filetype: " .. ft, vim.log.levels.WARN) 126 | end 127 | end 128 | 129 | -- Run test nearest to current line 130 | function M.run_nearest() 131 | local filepath = vim.fn.expand("%:p") 132 | local line = vim.fn.line(".") 133 | local ft = vim.bo.filetype 134 | 135 | if ft == "ruby" then 136 | run_in_panel(build_rspec_cmd(filepath, line)) 137 | elseif ft == "javascript" then 138 | -- qunit doesn't support line numbers, just run file 139 | run_in_panel(build_qunit_cmd(filepath)) 140 | else 141 | vim.notify("No test runner for filetype: " .. ft, vim.log.levels.WARN) 142 | end 143 | end 144 | 145 | -- Close the test buffer and window 146 | function M.close() 147 | if test_buf and vim.api.nvim_buf_is_valid(test_buf) then 148 | pcall(function() 149 | local job_id = vim.b[test_buf].terminal_job_id 150 | if job_id then vim.fn.jobstop(job_id) end 151 | end) 152 | vim.api.nvim_buf_delete(test_buf, { force = true }) 153 | test_buf = nil 154 | test_win = nil 155 | end 156 | end 157 | 158 | -- Setup keymaps 159 | function M.setup() 160 | vim.keymap.set("n", "tt", M.run_file, { desc = "Run tests in file" }) 161 | vim.keymap.set("n", "tr", M.run_nearest, { desc = "Run test near cursor" }) 162 | vim.keymap.set("n", "tx", M.close, { desc = "Close test buffer" }) 163 | end 164 | 165 | return M 166 | -------------------------------------------------------------------------------- /nvim/lua/plugins/ui.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | "nvim-lua/plenary.nvim", 4 | config = function() 5 | -- very annoying but copilot will not find gjs files without this 6 | require("plenary.filetype").add_file("gjs") 7 | 8 | local log = require("plenary.log") 9 | local original_new = log.new 10 | 11 | log.new = function(config, standalone) 12 | local merged_config = vim.tbl_deep_extend("force", { use_console = false }, config or {}) 13 | return original_new(merged_config, standalone) 14 | end 15 | end, 16 | }, 17 | { 18 | "folke/snacks.nvim", 19 | priority = 1000, 20 | lazy = false, 21 | ---@type snacks.Config 22 | opts = { 23 | words = { enabled = true }, 24 | notifier = { enabled = true }, 25 | bigfile = { enabled = true }, 26 | debug = { enabled = true }, 27 | picker = { 28 | enabled = true, 29 | sources = { 30 | grep = { 31 | cmd = "rg", -- Explicitly use ripgrep 32 | args = { 33 | "--color=never", 34 | "--no-heading", 35 | "--with-filename", 36 | "--line-number", 37 | "--column", 38 | "--smart-case", 39 | "--hidden", -- Include hidden files 40 | "--glob=!.git/", -- Exclude .git directory 41 | -- Add any other ripgrep flags you prefer 42 | }, 43 | live = true, -- Disable live search for the quickfix workflow 44 | }, 45 | }, 46 | layouts = { 47 | quickfix_modal = { 48 | -- preview = false, -- No preview 49 | layout = { 50 | backdrop = false, 51 | width = 0.5, -- Half screen width 52 | height = 0.1, -- Just enough for input 53 | border = "rounded", 54 | box = "vertical", 55 | { win = "input", height = 1, border = "none" }, 56 | }, 57 | }, 58 | }, 59 | }, 60 | }, 61 | -- stylua: ignore 62 | keys = { 63 | { "n", function() Snacks.notifier.show_history() end, desc = "Notification History" }, 64 | { "un", function() Snacks.notifier.hide() end, desc = "Dismiss All Notifications" }, 65 | { 66 | "fj", 67 | function() 68 | Snacks.picker.pick({ 69 | source = "grep", 70 | live = true, 71 | focus = "input", 72 | layout = "quickfix_modal", -- Use our custom minimal layout 73 | confirm = function(picker, item) 74 | require("snacks.picker.actions").qflist(picker) 75 | picker:close() 76 | end, 77 | }) 78 | end, 79 | desc = "Grep to Quickfix" 80 | }, 81 | }, 82 | }, 83 | { 84 | "ellisonleao/gruvbox.nvim", 85 | priority = 1000, 86 | config = function() 87 | vim.o.background = "dark" 88 | require("gruvbox").setup({ 89 | contrast = "hard", 90 | italic = { 91 | strings = false, 92 | comments = false, 93 | emphasis = false, 94 | }, 95 | }) 96 | vim.cmd.colorscheme("gruvbox") 97 | end, 98 | }, 99 | { 100 | "uga-rosa/ccc.nvim", 101 | config = function() 102 | require("ccc").setup({ 103 | highlighter = { 104 | auto_enable = true, 105 | lsp = true, 106 | }, 107 | }) 108 | end, 109 | }, 110 | { 111 | "nvim-tree/nvim-web-devicons", 112 | lazy = false, 113 | config = function() 114 | require("nvim-web-devicons").setup({ 115 | color_icons = true, 116 | default = true, 117 | strict = true, 118 | variant = "dark", 119 | override = (function() 120 | -- technically we need to also fix the name, so 121 | -- rake is now white, but Rb is red 122 | local files = { "rb", "rakefile", "Gemfile", "Brewfile" } 123 | local result = {} 124 | for _, ext in ipairs(files) do 125 | result[ext] = { 126 | icon = "", 127 | color = "#bb2222", 128 | cterm_color = "52", 129 | name = "Rb", 130 | } 131 | end 132 | return result 133 | end)(), 134 | }) 135 | end, 136 | }, 137 | { 138 | "nvim-neo-tree/neo-tree.nvim", 139 | lazy = false, 140 | branch = "v3.x", 141 | dependencies = { 142 | "nvim-lua/plenary.nvim", 143 | "nvim-tree/nvim-web-devicons", 144 | "MunifTanjim/nui.nvim", 145 | -- "3rd/image.nvim", -- Optional image support in preview window: See `# Preview Mode` for more information 146 | }, 147 | opts = { 148 | filesystem = { 149 | filtered_items = { 150 | hide_gitignored = true, 151 | always_show_by_pattern = { 152 | "*plugins*", 153 | }, 154 | }, 155 | }, 156 | }, 157 | keys = { 158 | { 159 | "e", 160 | function() 161 | require("neo-tree.command").execute({ 162 | action = "show", 163 | reveal = true, 164 | }) 165 | end, 166 | desc = "Find current file", 167 | }, 168 | }, 169 | }, 170 | { 171 | "folke/which-key.nvim", 172 | event = "VeryLazy", 173 | opts = { 174 | preset = "helix", 175 | }, 176 | keys = { 177 | { 178 | "?", 179 | function() 180 | require("which-key").show({ global = false }) 181 | end, 182 | desc = "Buffer Local Keymaps (which-key)", 183 | }, 184 | }, 185 | }, 186 | { 187 | "cuducos/yaml.nvim", 188 | dependencies = { 189 | "nvim-treesitter/nvim-treesitter", 190 | "nvim-telescope/telescope.nvim", -- optional 191 | "folke/snacks.nvim", -- optional 192 | }, 193 | keys = { 194 | { 195 | "fy", 196 | function() 197 | require("yaml_nvim").snacks() 198 | end, 199 | desc = "Find YAML Key", 200 | }, 201 | }, 202 | }, 203 | { 204 | "nvim-lualine/lualine.nvim", 205 | dependencies = { "nvim-tree/nvim-web-devicons" }, 206 | config = function() 207 | local old_position 208 | local old_result 209 | 210 | local function get_yaml_key() 211 | if vim.bo.filetype ~= "yaml" then 212 | return "" 213 | end 214 | 215 | local position = vim.api.nvim_win_get_cursor(0) 216 | 217 | if old_position and old_position[1] == position[1] and old_position[2] == position[2] then 218 | return old_result 219 | end 220 | 221 | local result = require("yaml_nvim").get_yaml_key() 222 | 223 | old_position = position 224 | old_result = result 225 | 226 | return result or "" 227 | end 228 | require("lualine").setup({ 229 | sections = { 230 | lualine_x = { get_yaml_key, "encoding", "fileformat", "filetype" }, 231 | }, 232 | }) 233 | end, 234 | }, 235 | { 236 | "b0o/incline.nvim", 237 | config = function() 238 | require("incline").setup() 239 | end, 240 | -- Optional: Lazy load Incline 241 | event = "VeryLazy", 242 | }, 243 | } 244 | -------------------------------------------------------------------------------- /i3/config_laptop: -------------------------------------------------------------------------------- 1 | # This file has been auto-generated by i3-config-wizard(1). 2 | # It will not be overwritten, so edit it as you like. 3 | set $mod Mod4 4 | font pango:SF Pro Display 10 5 | 6 | exec --no-startup-id /home/sam/.config/i3/picom 7 | exec --no-startup-id hsetroot 8 | exec --no-startup-id flameshot 9 | exec --no-startup-id skypeforlinux 10 | exec --no-startup-id copyq 11 | exec --no-startup-id mattermost 12 | exec --no-startup-id urxvt -name scratch-term 13 | exec --no-startup-id yubioath-desktop 14 | exec --no-startup-id nm-applet 15 | exec --no-startup-id xfce4-power-manager 16 | exec --no-startup-id xbanish 17 | exec --no-startup-id dunst 18 | exec --no-startup-id firefox 19 | 20 | exec_always --no-startup-id xmodmap -e "clear lock" 21 | exec_always --no-startup-id xmodmap -e "keycode 9 = Caps_Lock NoSymbol Caps_Lock" 22 | exec_always --no-startup-id xmodmap -e "keycode 66 = Escape NoSymbol Escape" 23 | exec_always --no-startup-id xmodmap -e "keysym Menu = Super_R" 24 | 25 | for_window [class="Skype"] floating enable border normal 26 | for_window [class="Git-gui" instance="git-gui"] floating enable 27 | for_window [title="Terminator Preferences"] floating enable 28 | for_window [class="mattermost-nativefier"] floating enable, move to workspace 3 29 | for_window [class="simplescreenrecorder"] floating enable 30 | for_window [instance="scratch-term"] floating enable, move to scratchpad 31 | for_window [class="Firefox"] floating enable, move to scratchpad, scratchpad show 32 | for_window [class="Yubico Authenticator"] floating enable, move to scratchpad 33 | for_window [class="mattermost-nativefier"] floating enable, move to scratchpad 34 | 35 | # Use Mouse+$mod to drag floating windows to their wanted position 36 | floating_modifier $mod 37 | 38 | # start a terminal 39 | bindsym $mod+Shift+Return exec i3-sensible-terminal 40 | bindsym $mod+Return exec /home/sam/.config/i3/i3-plus layout_exec i3-sensible-terminal 41 | 42 | # kill focused window (usually shift q) 43 | bindsym $mod+q kill 44 | 45 | bindsym $mod+d exec --no-startup-id rofi -combi-modi window#run -show combi -modi combi -font "Consolas 25" 46 | 47 | bindsym $mod+h split h 48 | bindsym $mod+v split v 49 | bindsym $mod+f fullscreen 50 | 51 | # center a floating window 52 | # bindsym $mod+c exec "/home/sam/.i3/i3-plus smart_center 1830x2100,2030x2100,2230x2100" 53 | 54 | # change container layout (stacked, tabbed, toggle split) 55 | bindsym $mod+s layout stacking 56 | bindsym $mod+w layout tabbed 57 | bindsym $mod+e layout toggle split 58 | 59 | # change focus 60 | bindsym $mod+k focus down 61 | bindsym $mod+l focus up 62 | bindsym $mod+semicolon focus right 63 | bindsym $mod+j focus left 64 | 65 | # alternatively, you can use the cursor keys: 66 | bindsym $mod+Left focus left 67 | bindsym $mod+Right focus right 68 | bindsym $mod+Down focus down 69 | bindsym $mod+Up focus up 70 | 71 | # move focused window 72 | # bindsym $mod+Shift+j exec /home/sam/.i3/i3-plus move left 73 | # bindsym $mod+Shift+semicolon exec /home/sam/.i3/i3-plus move right 74 | # bindsym $mod+Shift+k move down 75 | # bindsym $mod+Shift+l move up 76 | 77 | #bindsym $mod+Shift+Left exec /home/sam/.i3/i3-plus move left 78 | #bindsym $mod+Shift+Right exec /home/sam/.i3/i3-plus move right 79 | bindsym $mod+Shift+Left [con_id="__focused__" tiling] move left; [con_id="__focused__" floating] move to output left, focus 80 | bindsym $mod+Shift+Right [con_id="__focused__" tiling] move right; [con_id="__focused__" floating] move to output right, focus 81 | bindsym $mod+Shift+Down move down 82 | bindsym $mod+Shift+Up move up 83 | 84 | # toggle tiling / floating 85 | bindsym $mod+Shift+space floating toggle 86 | 87 | # change focus between tiling / floating windows 88 | bindsym $mod+space focus mode_toggle 89 | 90 | # focus the parent container 91 | bindsym $mod+a focus parent 92 | 93 | # switch to workspace 94 | bindsym $mod+1 workspace 1 95 | bindsym $mod+2 workspace 2 96 | bindsym $mod+3 workspace 3 97 | bindsym $mod+4 workspace 4 98 | bindsym $mod+5 workspace 5 99 | bindsym $mod+6 workspace 6 100 | bindsym $mod+7 workspace 7 101 | bindsym $mod+8 workspace 8 102 | bindsym $mod+9 workspace 9 103 | bindsym $mod+0 workspace 10 104 | 105 | # move focused container to workspace 106 | bindsym $mod+Shift+1 move container to workspace 1 107 | bindsym $mod+Shift+2 move container to workspace 2 108 | bindsym $mod+Shift+3 move container to workspace 3 109 | bindsym $mod+Shift+4 move container to workspace 4 110 | bindsym $mod+Shift+5 move container to workspace 5 111 | bindsym $mod+Shift+6 move container to workspace 6 112 | bindsym $mod+Shift+7 move container to workspace 7 113 | bindsym $mod+Shift+8 move container to workspace 8 114 | bindsym $mod+Shift+9 move container to workspace 9 115 | bindsym $mod+Shift+0 move container to workspace 10 116 | 117 | # reload the configuration file 118 | bindsym $mod+Shift+c reload 119 | # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) 120 | bindsym $mod+Shift+r restart 121 | 122 | bindsym $mod+Ctrl+Right "move workspace to output right" 123 | bindsym $mod+Ctrl+Left "move workspace to output left" 124 | 125 | # Resizing windows by 10 in i3 using keyboard only 126 | bindsym $mod+Mod1+Right exec --no-startup-id /home/sam/.config/i3/i3-resize right 127 | bindsym $mod+Mod1+Left exec --no-startup-id /home/sam/.config/i3/i3-resize left 128 | bindsym $mod+Mod1+Up exec --no-startup-id /home/sam/.config/i3/i3-resize up 129 | bindsym $mod+Mod1+Down exec --no-startup-id /home/sam/.config/i3/i3-resize down 130 | 131 | # Start i3bar to display a workspace bar (plus the system information i3status 132 | # finds out, if available) 133 | bar { 134 | font pango:DejaVu Sans Mono, FontAwesome 12 135 | position bottom 136 | status_command /usr/bin/i3status-rs /home/sam/.config/i3/status.toml 137 | colors { 138 | separator #666666 139 | background #222222 140 | statusline #dddddd 141 | focused_workspace #0088CC #0088CC #ffffff 142 | active_workspace #333333 #333333 #ffffff 143 | inactive_workspace #333333 #333333 #888888 144 | urgent_workspace #2f343a #900000 #ffffff 145 | } 146 | } 147 | 148 | #bindsym $mod+Shift+d exec "i3-sensible-terminal -e '/home/sam/.i3/edit_discourse'" 149 | bindsym $mod+Shift+d exec cd /home/sam/Source/discourse && i3-sensible-terminal -e nvim -c ':NERDTree|:wincmd w|:vsplit' 150 | 151 | # screenshot apps 152 | bindsym Print exec "flameshot gui" 153 | bindsym $mod+Print exec "/bin/bash -c '/home/sam/.config/.i3/countdown 3 && sleep 0.2 && flameshot gui'" 154 | # full desktop screenshot 155 | bindsym $mod+Shift+Print exec "/home/sam/.config/i3/i3-plus screenshot" 156 | 157 | # scratchpad apps 158 | bindsym $mod+p [instance="scratch-term"] scratchpad show 159 | bindsym $mod+b [class="Firefox"] scratchpad show 160 | bindsym $mod+y [class="Yubico Authenticator"] scratchpad show 161 | bindsym $mod+m [class="mattermost-nativefier"] scratchpad show 162 | 163 | bindsym $mod+x mode "exit: [l]ogout, [r]eboot, [s]hutdown" 164 | mode "exit: [l]ogout, [r]eboot, [s]hutdown" { 165 | bindsym l exec i3-msg exit 166 | bindsym r exec systemctl reboot 167 | bindsym s exec systemctl shutdown 168 | bindsym Escape mode "default" 169 | bindsym Return mode "default" 170 | } 171 | 172 | bindsym XF86AudioRaiseVolume exec "amixer -q set Master 1%+ unmute" 173 | bindsym XF86AudioLowerVolume exec "amixer -q set Master 1%- unmute" 174 | bindsym XF86AudioMute exec "amixer -q set Master toggle" 175 | 176 | # Sreen brightness controls 177 | bindsym XF86MonBrightnessUp exec xbacklight -inc 5 178 | bindsym XF86MonBrightnessDown exec xbacklight -dec 5 179 | 180 | focus_follows_mouse no 181 | -------------------------------------------------------------------------------- /i3/config: -------------------------------------------------------------------------------- 1 | # This file has been auto-generated by i3-config-wizard(1). 2 | # It will not be overwritten, so edit it as you like. 3 | set $mod Mod4 4 | font pango:SF Pro Display 10 5 | 6 | exec_always --no-startup-id /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 7 | exec --no-startup-id picom --backend glx --vsync 8 | exec --no-startup-id hsetroot 9 | exec --no-startup-id flameshot 10 | # skype is retired 11 | #exec --no-startup-id skypeforlinux 12 | exec --no-startup-id 1password --silent 13 | exec --no-startup-id copyq 14 | exec --no-startup-id kitty --name scratch-term 15 | exec --no-startup-id yubioath-desktop 16 | exec --no-startup-id telegram-desktop -startintray 17 | exec --no-startup-id slack -u 18 | exec --no-startup-id xss-lock "/home/sam/.config/i3/lock-screen" 19 | exec --no-startup-id pasystray 20 | exec --no-startup-id blueman-applet 21 | exec --no-startup-id signal-desktop --start-in-tray 22 | 23 | # trying setxkbmap -option caps:escape in .xinitrc 24 | # exec_always --no-startup-id xmodmap -e "clear lock" 25 | # exec_always --no-startup-id xmodmap -e "keycode 9 = Caps_Lock NoSymbol Caps_Lock" 26 | # exec_always --no-startup-id xmodmap -e "keycode 66 = Escape NoSymbol Escape" 27 | # exec_always --no-startup-id xmodmap -e "keysym Menu = Super_R" 28 | 29 | for_window [class="discourse-status" window_role="discourse-status-dialog"] floating enable 30 | for_window [class="kruler"] floating enable 31 | for_window [class="Slack"] floating enable border normal 32 | for_window [class="Skype"] floating enable border normal 33 | for_window [class="Git-gui" instance="git-gui"] floating enable 34 | for_window [title="Terminator Preferences"] floating enable 35 | for_window [class="mattermost-nativefier"] floating enable, move to workspace 3 36 | for_window [class="simplescreenrecorder"] floating enable 37 | for_window [instance="scratch-term"] floating enable, move to scratchpad 38 | for_window [class="Yubico Authenticator" window_type="normal"] floating enable, move to scratchpad 39 | for_window [class="mattermost-nativefier"] floating enable, move to scratchpad 40 | for_window [class="Telegram"] floating enable 41 | for_window [class="1Password"] floating enable 42 | 43 | # Use Mouse+$mod to drag floating windows to their wanted position 44 | floating_modifier $mod 45 | 46 | # start a terminal 47 | bindsym $mod+Shift+Ctrl+Return split h; exec i3-sensible-terminal 48 | bindsym $mod+Shift+Return split v; exec i3-sensible-terminal 49 | bindsym $mod+Return exec /home/sam/.config/i3/i3-plus layout_exec i3-sensible-terminal 50 | 51 | # kill focused window (usually shift q) 52 | bindsym $mod+q kill 53 | 54 | bindsym $mod+d exec rofi -combi-modi window#run -show combi -modi combi 55 | bindsym $mod+e exec rofimoji 56 | 57 | bindsym $mod+h split h 58 | bindsym $mod+v split v 59 | bindsym $mod+f fullscreen 60 | 61 | 62 | bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +1% 63 | bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -1% 64 | bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle 65 | 66 | # center a floating window 67 | bindsym $mod+c exec "/home/sam/.config/i3/i3-plus smart_center 1500x1500,1700x1500,1900x1500,2100x1500" 68 | 69 | # change container layout (stacked, tabbed, toggle split) 70 | bindsym $mod+s layout stacking 71 | bindsym $mod+w layout tabbed 72 | bindsym $mod+Shift+e layout toggle split 73 | 74 | # change focus 75 | #bindsym $mod+k focus down 76 | #bindsym $mod+l focus up 77 | #bindsym $mod+semicolon exec /home/sam/.config/i3/i3-plus focus right 78 | #bindsym $mod+j exec /home/sam/.config/i3/i3-plus focus left 79 | # 80 | bindsym $mod+l exec /home/sam/.config/i3/layout_manager.sh 81 | 82 | # alternatively, you can use the cursor keys: 83 | bindsym $mod+Left exec /home/sam/.config/i3/i3-plus focus left 84 | bindsym $mod+Right exec /home/sam/.config/i3/i3-plus focus right 85 | bindsym $mod+Down focus down 86 | bindsym $mod+Up focus up 87 | 88 | bindsym $mod+Shift+Left [con_id="__focused__" tiling] move left; [con_id="__focused__" floating] move to output left, focus 89 | bindsym $mod+Shift+Right [con_id="__focused__" tiling] move right; [con_id="__focused__" floating] move to output right, focus 90 | bindsym $mod+Shift+Down move down 91 | bindsym $mod+Shift+Up move up 92 | 93 | # toggle tiling / floating 94 | bindsym $mod+Shift+space floating toggle 95 | 96 | # change focus between tiling / floating windows 97 | bindsym $mod+space focus mode_toggle 98 | 99 | # focus the parent container 100 | bindsym $mod+a focus parent 101 | 102 | bindsym $mod+comma exec pkill -USR2 -f 'ruby bin/unicorn' 103 | 104 | # switch to workspace 105 | bindsym $mod+1 workspace 1 106 | bindsym $mod+2 workspace 2 107 | bindsym $mod+3 workspace 3 108 | bindsym $mod+4 workspace 4 109 | bindsym $mod+5 workspace 5 110 | bindsym $mod+6 workspace 6 111 | bindsym $mod+7 workspace 7 112 | bindsym $mod+8 workspace 8 113 | bindsym $mod+9 workspace 9 114 | bindsym $mod+0 workspace 10 115 | 116 | # move focused container to workspace 117 | bindsym $mod+Shift+1 move container to workspace 1 118 | bindsym $mod+Shift+2 move container to workspace 2 119 | bindsym $mod+Shift+3 move container to workspace 3 120 | bindsym $mod+Shift+4 move container to workspace 4 121 | bindsym $mod+Shift+5 move container to workspace 5 122 | bindsym $mod+Shift+6 move container to workspace 6 123 | bindsym $mod+Shift+7 move container to workspace 7 124 | bindsym $mod+Shift+8 move container to workspace 8 125 | bindsym $mod+Shift+9 move container to workspace 9 126 | bindsym $mod+Shift+0 move container to workspace 10 127 | 128 | # reload the configuration file 129 | bindsym $mod+Shift+c reload 130 | # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) 131 | bindsym $mod+Shift+r restart 132 | 133 | #bindsym $mod+Ctrl+Right "move workspace to output right" 134 | #bindsym $mod+Ctrl+Left "move workspace to output left" 135 | 136 | # resize commands 137 | bindsym $mod+Mod1+Right exec --no-startup-id /home/sam/.config/i3/i3-resize right 138 | bindsym $mod+Mod1+Left exec --no-startup-id /home/sam/.config/i3/i3-resize left 139 | bindsym $mod+Mod1+Up exec --no-startup-id /home/sam/.config/i3/i3-resize up 140 | bindsym $mod+Mod1+Down exec --no-startup-id /home/sam/.config/i3/i3-resize down 141 | 142 | bindsym $mod+Shift+b resize shrink width 5 px or 3 ppt 143 | bindsym $mod+b resize grow width 5 px or 3 ppt 144 | 145 | # Start i3bar to display a workspace bar (plus the system information i3status 146 | # finds out, if available) 147 | bar { 148 | tray_output DP-0 149 | font pango:DejaVu Sans Mono, Font Awesome 5 Free 150 | position bottom 151 | status_command /bin/i3status-rs /home/sam/.config/i3/status.toml 152 | colors { 153 | separator #666666 154 | background #222222 155 | statusline #dddddd 156 | focused_workspace #0088CC #0088CC #ffffff 157 | active_workspace #333333 #333333 #ffffff 158 | inactive_workspace #333333 #333333 #888888 159 | urgent_workspace #2f343a #900000 #ffffff 160 | } 161 | } 162 | 163 | bindsym $mod+Shift+d exec cd /home/sam/Source/discourse && i3-sensible-terminal -e nvim -c ':NERDTree|:wincmd w|:vsplit' 164 | 165 | # screenshot apps 166 | bindsym Print exec "flameshot gui" 167 | bindsym $mod+Print exec "/bin/bash -c '/home/sam/.config/i3/countdown 3 && sleep 0.2 && flameshot gui'" 168 | # full desktop screenshot 169 | bindsym $mod+Shift+Print exec "/home/sam/.config/i3/i3-plus screenshot" 170 | 171 | # scratchpad apps 172 | bindsym $mod+p [instance="scratch-term"] scratchpad show 173 | bindsym $mod+y [class="Yubico Authenticator"] scratchpad show 174 | 175 | bindsym $mod+x mode "exit: [l]ogout, [r]eboot, [s]hutdown" 176 | mode "exit: [l]ogout, [r]eboot, [s]hutdown" { 177 | bindsym l exec i3-msg exit 178 | bindsym r exec systemctl reboot 179 | bindsym s exec systemctl shutdown 180 | bindsym Escape mode "default" 181 | bindsym Return mode "default" 182 | } 183 | 184 | focus_follows_mouse no 185 | -------------------------------------------------------------------------------- /i3/i3-plus: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "i3ipc" 4 | 5 | class I3Helper 6 | def initialize 7 | @i3 = I3Ipc::Connection.new 8 | end 9 | 10 | def tree 11 | @tree ||= @i3.tree 12 | end 13 | 14 | def recurse(nodes = nil, focused: false, floating: false, depth: 0, &blk) 15 | nodes ||= tree.nodes 16 | nodes.each do |n| 17 | blk.call n, 18 | focused: n.focused || focused, 19 | depth: depth, 20 | floating: floating 21 | depth += 1 22 | recurse(n.nodes, depth: depth, floating: floating, &blk) 23 | recurse(n.floating_nodes, depth: depth, floating: true, &blk) 24 | end 25 | end 26 | 27 | def is_floating? 28 | recurse(tree.nodes) do |n, focused:, depth:, floating:| 29 | return true if floating && focused 30 | end 31 | false 32 | end 33 | 34 | def layout_exec(command, cols: 3, rows: 3, balance: :auto) 35 | windows = [] 36 | 37 | focused_window = nil 38 | focused_workspace = nil 39 | 40 | workspace = nil 41 | 42 | recurse do |n, depth:, focused:, floating:| 43 | # dim = n.type == "con" ? " #{n.rect.width}x#{n.rect.height}" : "" 44 | # puts "#{" " * depth} #{n.name} #{n.type}#{dim} #{n.layout} #{focused ? "*" : ""} #{floating ? "f" : ""}" 45 | workspace = n.name if n.type == "workspace" 46 | 47 | if n.respond_to?(:window_properties) && !floating 48 | windows << { window: n, workspace: workspace } 49 | if focused 50 | focused_window = n if focused 51 | focused_workspace = workspace 52 | end 53 | end 54 | end 55 | 56 | if !focused_window 57 | @i3.command("exec #{command}") 58 | return 59 | end 60 | 61 | windows = 62 | windows 63 | .select do |hash| 64 | window = hash[:window] 65 | workspace = hash[:workspace] 66 | workspace == focused_workspace && 67 | window.output == focused_window.output && 68 | window.window_properties.to_h[:class] != "i3bar" 69 | end 70 | .map { |hash| hash[:window] } 71 | 72 | if balance == :auto && windows.length > 0 73 | outputs = @i3.outputs.sort { |a, b| a.rect.x <=> b.rect.x } 74 | 75 | left_aligns = [] 76 | take = false 77 | outputs.each do |output| 78 | left_aligns << output.name if take 79 | take = true if output.primary 80 | end 81 | 82 | balance = :left if left_aligns.include?(windows[0].output) 83 | end 84 | 85 | dir = nil 86 | pid = `xprop -id $(xdotool getactivewindow) _NET_WM_PID` 87 | if pid && pid = pid.split(" ").last.to_i 88 | if pid > 0 89 | lpid = `pstree -aApT #{pid}` 90 | lpid = lpid.split("\n").select { |r| r =~ /zsh,|bash,/ }.first 91 | if lpid && lpid = lpid.split(",").last 92 | dir = `readlink /proc/#{lpid}/cwd`.strip 93 | if dir && !dir.empty? && !dir.include?("'") 94 | command = "cd #{dir} && #{command}" 95 | end 96 | end 97 | end 98 | end 99 | 100 | if windows.length < cols 101 | @i3.command("split h; exec #{command}") 102 | else 103 | full_cols = (windows.length - cols) / (rows - 1) 104 | full_rows = (windows.length - cols) % (rows - 1) 105 | index = cols - full_cols - 1 106 | 107 | index = full_cols * rows if balance == :left 108 | 109 | window = windows[index] 110 | 111 | split = full_rows == 0 ? " split v;" : "" 112 | @i3.command("[con_id=\"#{window.id}\"] focus; #{split} exec #{command}") 113 | end 114 | 115 | #p @i3.outputs 116 | end 117 | 118 | def focus(dir) 119 | floating_windows = [] 120 | focused_window = nil 121 | focused_workspace = nil 122 | 123 | recurse do |n, depth:, focused:, floating:| 124 | workspace = n.name if n.type == "workspace" 125 | 126 | if n.respond_to?(:window_properties) && floating 127 | floating_windows << { node: n, workspace: workspace } 128 | if focused 129 | focused_window = n 130 | focused_workspace = workspace 131 | end 132 | end 133 | end 134 | 135 | # we are on a floating window 136 | if focused_window 137 | floating_windows = 138 | floating_windows 139 | .filter do |node:, workspace:| 140 | workspace == focused_workspace && node.output != "__i3" 141 | end 142 | .map do |node:, workspace:| 143 | pos = 144 | begin 145 | case dir 146 | when "right", "left" 147 | node.rect.x + node.rect.width / 2 148 | when "up", "down" 149 | node.rect.y + node.rect.width / 2 150 | end 151 | end 152 | 153 | { node: node, pos: pos } 154 | end 155 | .sort_by { |hash| hash[:pos] } 156 | .map { |node:, pos:| node } 157 | 158 | pos = floating_windows.index(focused_window) 159 | 160 | new_pos = pos + (dir == "right" || dir == "down" ? 1 : -1) 161 | 162 | if new_pos >= floating_windows.length || new_pos < 0 163 | @i3.command("focus parent; focus #{dir}") 164 | else 165 | @i3.command("[con_id=\"#{floating_windows[new_pos].id}\"] focus") 166 | end 167 | 168 | return 169 | end 170 | 171 | @i3.command("focus #{dir}") 172 | end 173 | 174 | def move(dir) 175 | if is_floating? 176 | @i3.command("mark _last") 177 | @i3.command("move to output #{dir}") 178 | @i3.command('[con_mark="_last"] focus') 179 | else 180 | @i3.command("move #{dir}") 181 | end 182 | end 183 | 184 | def smart_center(arg) 185 | focused_window = nil 186 | is_floating = false 187 | 188 | recurse do |n, depth:, focused:, floating:| 189 | if n.respond_to?(:window_properties) 190 | if focused 191 | focused_window = n 192 | is_floating = floating 193 | end 194 | end 195 | end 196 | 197 | sizes = arg.split(",").map { |x| x.split("x").map(&:to_i) } 198 | 199 | if focused_window 200 | index = 201 | sizes 202 | .map(&:first) 203 | .index { |width| (focused_window.rect.width.to_i - width).abs < 15 } 204 | 205 | index = -1 if !is_floating 206 | 207 | width, height = sizes[((index || -1) + 1) % sizes.length] 208 | 209 | if is_floating 210 | @i3.command( 211 | "resize set width #{width} px; resize set height #{height} px; move position center; move up 5 px" 212 | ) 213 | else 214 | @i3.command( 215 | "floating enable; resize set width #{width} px; resize set height #{height} px; move position center; move up 5 px;" 216 | ) 217 | end 218 | end 219 | end 220 | 221 | def screenshot(args) 222 | focused_output = nil 223 | 224 | recurse do |n, depth:, focused:, floating:| 225 | focused_output = n if n.type == "output" 226 | 227 | break if focused 228 | end 229 | 230 | `mkdir -p ~/screenshots` 231 | 232 | rect = focused_output.rect 233 | 234 | filename = "~/screenshots/desktop-#{Time.now.strftime("%Y%m%d-%H%M%S")}.png" 235 | cmd = 236 | "import -silent -window root -crop #{rect.width}x#{rect.height}+#{rect.x}+#{rect.y} #{filename}" 237 | `#{cmd}` 238 | cmd = "pngquant -f --output #{filename} #{filename}" 239 | `#{cmd}` 240 | cmd = "bash -c 'copyq write image/png - < #{filename}' && copyq select 0" 241 | `#{cmd}` 242 | cmd = 243 | "notify-send '#{File.basename(filename)} #{File.size(File.expand_path(filename)) / 1024}k'" 244 | `#{cmd}` 245 | end 246 | 247 | def close 248 | @i3.close 249 | @i3 = nil 250 | end 251 | end 252 | 253 | helper = I3Helper.new 254 | 255 | def usage 256 | puts "i3-plus [COMMAND] [PARAMS]" 257 | puts "" 258 | puts "Commands:" 259 | puts " move [DIR]: will move the current window left, if it is floating it will move the window to next monitor " 260 | puts " focus [DIR]: slightly amended focus that breaks out of floated windows" 261 | puts " layout_exec: similar to exec except that it tries to maintain a certain layout" 262 | puts " screenshot: at the moment this takes a screenshot of current desktop and compresses" 263 | end 264 | 265 | case ARGV[0] 266 | when "layout_exec" 267 | helper.layout_exec "i3-sensible-terminal" 268 | when "move" 269 | helper.move(ARGV[1]) 270 | when "focus" 271 | helper.focus(ARGV[1]) 272 | when "smart_center" 273 | helper.smart_center(ARGV[1]) 274 | when "screenshot" 275 | helper.screenshot(ARGV[2..-1]) 276 | else 277 | puts "unknown command" 278 | usage 279 | helper.close 280 | exit 1 281 | end 282 | 283 | helper.close 284 | -------------------------------------------------------------------------------- /hypr/hyprland.conf: -------------------------------------------------------------------------------- 1 | ################ 2 | ### MONITORS ### 3 | ################ 4 | 5 | # See https://wiki.hyprland.org/Configuring/Monitors/ 6 | #monitor=,highrr,auto,auto 7 | monitor=DP-1,highrr,auto,1.6 8 | 9 | xwayland { 10 | force_zero_scaling = true 11 | } 12 | 13 | debug { 14 | disable_logs = false 15 | } 16 | 17 | ################### 18 | ### MY PROGRAMS ### 19 | ################### 20 | 21 | # See https://wiki.hyprland.org/Configuring/Keywords/ 22 | 23 | $terminal = ~/.config/hypr/terminal.sh 24 | $fileManager = dolphin 25 | # $menu = rofi -show drun 26 | # $menu = walker 27 | $menu = fuzzel 28 | 29 | 30 | ################# 31 | ### AUTOSTART ### 32 | ################# 33 | # Autostart necessary processes (like notifications daemons, status bars, etc.) 34 | # Or execute your favorite apps at launch like this: 35 | 36 | # exec-once = $terminal 37 | # exec-once = nm-applet & 38 | exec-once = systemctl --user start hyprpolkitagent 39 | exec-once = 1password --silent 40 | exec-once = env QT_QPA_PLATFORM=xcb copyq 41 | # exec-once = yubioath-desktop 42 | exec-once = telegram-desktop -startintray 43 | exec-once = slack -u 44 | exec-once = signal-desktop --start-in-tray 45 | exec-once = blueman-applet 46 | #exec-once = nm-applet --indicator 47 | exec-once = swww-daemon 48 | exec-once = hypridle 49 | # exec-once = firefox 50 | exec-once = waybar 51 | exec-once = dunst 52 | exec-once = opendeck --hide 53 | # exec-once = elephant 54 | # exec-once = walker -gapplication-service 55 | ############################# 56 | ### ENVIRONMENT VARIABLES ### 57 | ############################# 58 | 59 | # See https://wiki.hyprland.org/Configuring/Environment-variables/ 60 | 61 | env = XCURSOR_SIZE,24 62 | env = HYPRCURSOR_SIZE,24 63 | env = LIBVA_DRIVER_NAME,nvidia 64 | env = __GLX_VENDOR_LIBRARY_NAME,nvidia 65 | env = MOZ_ENABLE_WAYLAND,1 66 | env = GDK_BACKEND,wayland,x11 67 | env = SDL_VIDEODRIVER,wayland 68 | env = CLUTTER_BACKEND,wayland 69 | env = ELECTRON_OZONE_PLATFORM_HINT,wayland 70 | env = QT_ENABLE_HIGHDPI_SCALING,1 71 | env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1 72 | env = QT_QPA_PLATFORM,wayland;xcb 73 | env = NVD_BACKEND,direct 74 | env = GBM_BACKEND,nvidia-drm 75 | env = XDG_CURRENT_DESKTOP,Hyprland 76 | env = XDG_SESSION_TYPE,wayland 77 | env = XDG_SESSION_DESKTOP,Hyprland 78 | # can be 5 or 6 79 | env = QT_QPA_PLATFORMTHEME,qt6ct 80 | 81 | ################### 82 | ### PERMISSIONS ### 83 | ################### 84 | 85 | # See https://wiki.hyprland.org/Configuring/Permissions/ 86 | # Please note permission changes here require a Hyprland restart and are not applied on-the-fly 87 | # for security reasons 88 | 89 | # ecosystem { 90 | # enforce_permissions = 1 91 | # } 92 | 93 | # permission = /usr/(bin|local/bin)/grim, screencopy, allow 94 | # permission = /usr/(lib|libexec|lib64)/xdg-desktop-portal-hyprland, screencopy, allow 95 | # permission = /usr/(bin|local/bin)/hyprpm, plugin, allow 96 | 97 | 98 | ##################### 99 | ### LOOK AND FEEL ### 100 | ##################### 101 | 102 | # Refer to https://wiki.hyprland.org/Configuring/Variables/ 103 | 104 | # https://wiki.hyprland.org/Configuring/Variables/#general 105 | general { 106 | gaps_in = 4 107 | gaps_out = 4 108 | 109 | border_size = 1 110 | 111 | # https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors 112 | #col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg 113 | 114 | col.active_border = rgba(505050aa) 115 | col.inactive_border = rgba(595959aa) 116 | 117 | # Set to true enable resizing windows by clicking and dragging on borders and gaps 118 | resize_on_border = false 119 | 120 | # Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on 121 | allow_tearing = false 122 | 123 | layout = master 124 | } 125 | 126 | # https://wiki.hyprland.org/Configuring/Variables/#decoration 127 | decoration { 128 | rounding = 2 129 | rounding_power = 4 130 | 131 | active_opacity = 1.0 132 | inactive_opacity = 1.0 133 | 134 | shadow { 135 | enabled = true 136 | range = 4 137 | render_power = 3 138 | color = rgba(1a1a1aee) 139 | } 140 | 141 | # https://wiki.hyprland.org/Configuring/Variables/#blur 142 | blur { 143 | enabled = true 144 | size = 3 145 | passes = 1 146 | 147 | vibrancy = 0.1696 148 | } 149 | } 150 | 151 | # https://wiki.hyprland.org/Configuring/Variables/#animations 152 | animations { 153 | enabled = yes, please :) 154 | 155 | # Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more 156 | 157 | bezier = easeOutQuint,0.23,1,0.32,1 158 | bezier = easeInOutCubic,0.65,0.05,0.36,1 159 | bezier = linear,0,0,1,1 160 | bezier = almostLinear,0.5,0.5,0.75,1.0 161 | bezier = quick,0.15,0,0.1,1 162 | 163 | animation = global, 1, 10, default 164 | animation = border, 1, 5.39, easeOutQuint 165 | animation = windows, 1, 4.79, easeOutQuint 166 | animation = windowsIn, 1, 4.1, easeOutQuint, popin 87% 167 | animation = windowsOut, 1, 1.49, linear, popin 87% 168 | animation = fadeIn, 1, 1.73, almostLinear 169 | animation = fadeOut, 1, 1.46, almostLinear 170 | animation = fade, 1, 3.03, quick 171 | animation = layers, 1, 3.81, easeOutQuint 172 | animation = layersIn, 1, 4, easeOutQuint, fade 173 | animation = layersOut, 1, 1.5, linear, fade 174 | animation = fadeLayersIn, 1, 1.79, almostLinear 175 | animation = fadeLayersOut, 1, 1.39, almostLinear 176 | animation = workspaces, 1, 1.94, almostLinear, fade 177 | animation = workspacesIn, 1, 1.21, almostLinear, fade 178 | animation = workspacesOut, 1, 1.94, almostLinear, fade 179 | } 180 | 181 | # Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/ 182 | # "Smart gaps" / "No gaps when only" 183 | # uncomment all if you wish to use that. 184 | # workspace = w[tv1], gapsout:0, gapsin:0 185 | # workspace = f[1], gapsout:0, gapsin:0 186 | # windowrule = bordersize 0, floating:0, onworkspace:w[tv1] 187 | # windowrule = rounding 0, floating:0, onworkspace:w[tv1] 188 | # windowrule = bordersize 0, floating:0, onworkspace:f[1] 189 | # windowrule = rounding 0, floating:0, onworkspace:f[1] 190 | 191 | # See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more 192 | dwindle { 193 | pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below 194 | preserve_split = true # You probably want this 195 | 196 | #default_split_ratio = 1.5 197 | #split_bias = 1 198 | # force_split = 2 199 | # split_width_multiplier = 1.3 200 | 201 | } 202 | 203 | # See https://wiki.hyprland.org/Configuring/Master-Layout/ for more 204 | master { 205 | new_status = master 206 | orientation = center 207 | mfact = 0.4 208 | slave_count_for_center_master = 0 209 | new_status = slave 210 | } 211 | 212 | # https://wiki.hyprland.org/Configuring/Variables/#misc 213 | misc { 214 | force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers 215 | disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :( 216 | } 217 | 218 | 219 | # https://wiki.hyprland.org/Configuring/Variables/#input 220 | input { 221 | kb_layout = us 222 | kb_variant = 223 | kb_model = 224 | kb_options=caps:swapescape 225 | 226 | follow_mouse = 1 227 | 228 | sensitivity = 0 # -1.0 - 1.0, 0 means no modification. 229 | 230 | touchpad { 231 | natural_scroll = false 232 | } 233 | } 234 | 235 | # https://wiki.hyprland.org/Configuring/Variables/#gestures 236 | #gestures { 237 | # workspace_swipe = false 238 | #} 239 | 240 | # Example per-device config 241 | # See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more 242 | # device { 243 | # name = epic-mouse-v1 244 | # sensitivity = -0.5 245 | #} 246 | 247 | 248 | ################### 249 | ### KEYBINDINGS ### 250 | ################### 251 | 252 | # See https://wiki.hyprland.org/Configuring/Keywords/ 253 | $mainMod = SUPER # Sets "Windows" key as main modifier 254 | 255 | # Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more 256 | bind = $mainMod, return, exec, $terminal 257 | bind = $mainMod, Q, killactive, 258 | bind = $mainMod, F, exec, $fileManager 259 | bind = $mainMod, V, togglefloating, 260 | bind = $mainMod, D, exec, $menu 261 | #bind = $mainMod, P, togglepseudotile, # Toggles pseudotiling in dwindle layout 262 | bind = $mainMod, J, togglesplit, # dwindle 263 | bind = $mainMod, X, exec, ~/.config/hypr/exit.sh # Exits Hyprland 264 | # not required cause it is reloaded by default 265 | #bind = $mainMod, R, reload, # Reloads the Hyprland config 266 | 267 | bind = $mainMod, T, exec, ~/.config/hypr/toggle-master-layout.sh 268 | 269 | # Move focus with mainMod + arrow keys 270 | bind = $mainMod, left, movefocus, l 271 | bind = $mainMod, right, movefocus, r 272 | bind = $mainMod, up, movefocus, u 273 | bind = $mainMod, down, movefocus, d 274 | 275 | bind = $mainMod SHIFT, right, layoutmsg, swapnext 276 | bind = $mainMod SHIFT, left, layoutmsg, swapprev 277 | 278 | # Switch workspaces with mainMod + [0-9] 279 | bind = $mainMod, 1, workspace, 1 280 | bind = $mainMod, 2, workspace, 2 281 | bind = $mainMod, 3, workspace, 3 282 | bind = $mainMod, 4, workspace, 4 283 | bind = $mainMod, 5, workspace, 5 284 | bind = $mainMod, 6, workspace, 6 285 | bind = $mainMod, 7, workspace, 7 286 | bind = $mainMod, 8, workspace, 8 287 | bind = $mainMod, 9, workspace, 9 288 | bind = $mainMod, 0, workspace, 10 289 | 290 | # Move active window to a workspace with mainMod + SHIFT + [0-9] 291 | bind = $mainMod SHIFT, 1, movetoworkspace, 1 292 | bind = $mainMod SHIFT, 2, movetoworkspace, 2 293 | bind = $mainMod SHIFT, 3, movetoworkspace, 3 294 | bind = $mainMod SHIFT, 4, movetoworkspace, 4 295 | bind = $mainMod SHIFT, 5, movetoworkspace, 5 296 | bind = $mainMod SHIFT, 6, movetoworkspace, 6 297 | bind = $mainMod SHIFT, 7, movetoworkspace, 7 298 | bind = $mainMod SHIFT, 8, movetoworkspace, 8 299 | bind = $mainMod SHIFT, 9, movetoworkspace, 9 300 | bind = $mainMod SHIFT, 0, movetoworkspace, 10 301 | 302 | # Example special workspace (scratchpad) 303 | bind = $mainMod, S, togglespecialworkspace, magic 304 | bind = $mainMod SHIFT, S, movetoworkspace, special:magic 305 | 306 | # Scroll through existing workspaces with mainMod + scroll 307 | bind = $mainMod, mouse_down, workspace, e+1 308 | bind = $mainMod, mouse_up, workspace, e-1 309 | bind = , Print, exec, hyprshot -m region --clipboard-only 310 | bind = SHIFT , Print, exec, ~/.config/hypr/countdown 3 && hyprshot -z -m region --clipboard-only 311 | bind = CTRL, Print, exec, hyprshot -z -m output -m DP-1 --clipboard-only 312 | bind = $mainMod, E, exec, wl-paste --type image/png --no-newline | swappy -f - 313 | bind = $mainMod, R, exec, ~/.config/hypr/screen-record.sh 314 | 315 | # Move/resize windows with mainMod + LMB/RMB and dragging 316 | bindm = $mainMod, mouse:272, movewindow 317 | bindm = $mainMod, mouse:273, resizewindow 318 | 319 | # Laptop multimedia keys for volume and LCD brightness 320 | bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ 321 | bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- 322 | bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle 323 | bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle 324 | bindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+ 325 | bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%- 326 | 327 | # Requires playerctl 328 | bindl = , XF86AudioNext, exec, playerctl next 329 | bindl = , XF86AudioPause, exec, playerctl play-pause 330 | bindl = , XF86AudioPlay, exec, playerctl play-pause 331 | bindl = , XF86AudioPrev, exec, playerctl previous 332 | 333 | ############################## 334 | ### WINDOWS AND WORKSPACES ### 335 | ############################## 336 | 337 | # See https://wiki.hyprland.org/Configuring/Window-Rules/ for more 338 | # See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules 339 | 340 | # Example windowrule 341 | # windowrule = float,class:^(kitty)$,title:^(kitty)$ 342 | 343 | # Ignore maximize requests from apps. You'll probably like this. 344 | windowrule = suppressevent maximize, class:.* 345 | 346 | # Fix some dragging issues with XWayland 347 | windowrule = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 348 | windowrule = float, class:^(kruler)$ 349 | windowrule = float, class:^(org.kde.kolourpaint)$ 350 | windowrule = float, class:^(pavucontrol)$ 351 | windowrule = float, title:^(Terminator Preferences)$ 352 | windowrule = float, class:^(simplescreenrecorder)$ 353 | windowrule = float, class:^(1Password)$ 354 | windowrule = float, class:^(slack)$ 355 | windowrule = float, class:^(TelegramDesktop)$ 356 | windowrule = float, class:^(Git-gui)$ 357 | windowrule = float, class:^(gitg)$ 358 | windowrule = float, class:^(org.gnome.gitg)$ 359 | windowrule = float, class:^(org.ksnip.ksnip)$ 360 | windowrule = float, class:^(org.hyprland.xdg-desktop-portal-hyprland)$ 361 | windowrule = float, class:^(xdg-desktop-portal-gtk)$ 362 | 363 | layerrule = noanim, hyprpicker 364 | layerrule = noanim, selection 365 | -------------------------------------------------------------------------------- /vim/vimrc: -------------------------------------------------------------------------------- 1 | packadd minpac 2 | 3 | call minpac#init({'verbose': 3}) 4 | 5 | call minpac#add('github/copilot.vim') 6 | 7 | call minpac#add('jamessan/vim-gnupg', {'branch': 'main'}) 8 | 9 | call minpac#add('fatih/vim-go') 10 | 11 | " a pretty color scheme 12 | " call minpac#add('morhetz/gruvbox') 13 | call minpac#add('ellisonleao/gruvbox.nvim') 14 | " call minpac#add('rebelot/kanagawa.nvim') 15 | 16 | "call minpac#add('catppuccin/nvim') 17 | " awesome async syntax linting, errors are highlighted as they happen 18 | call minpac#add('dense-analysis/ale') 19 | 20 | " Git support note, considering dropping this 21 | " cause gina is async which helps a lot 22 | call minpac#add('tpope/vim-fugitive') 23 | call minpac#add('tpope/vim-rhubarb') 24 | call minpac#add('lambdalisue/gina.vim') 25 | 26 | " es6 syntax in vim appears a bit rough 27 | " this makes it better and supports stuff like `test` 28 | call minpac#add('isRuslan/vim-es6') 29 | 30 | " official ruby support 31 | call minpac#add('vim-ruby/vim-ruby') 32 | 33 | " awesome rails support 34 | call minpac#add('tpope/vim-rails') 35 | 36 | " wisely adds end when you do an if and so on in Ruby 37 | call minpac#add('tpope/vim-endwise') 38 | 39 | " . (repeat) support for tpope plugins 40 | call minpac#add('tpope/vim-repeat') 41 | 42 | " want to change "testing" to 'testing' cs"' and bang it is done 43 | call minpac#add('tpope/vim-surround') 44 | 45 | " mappings extracted from tpope's vimrc, [q ]q is very handy 46 | " for quick navigation around quickfix list 47 | call minpac#add('tpope/vim-unimpaired') 48 | 49 | " fancy search/replace fancy abbreviation support and coercion 50 | call minpac#add('tpope/vim-abolish') 51 | 52 | " very cool highlighting of recently yanked text 53 | call minpac#add('machakann/vim-highlightedyank') 54 | 55 | " :Gist to send a gist to Gist, webapi 56 | " is a plugin dependency 57 | call minpac#add('mattn/webapi-vim') 58 | call minpac#add('mattn/gist-vim') 59 | 60 | " file explorer with :NERDTree 61 | call minpac#add('scrooloose/nerdtree') 62 | 63 | " search across all files quickly 64 | call minpac#add('mileszs/ack.vim') 65 | 66 | " quickly comment out code blocks, highlight them and 67 | " hit CTRL-__ to toggle comments, tpope also has a version of this 68 | call minpac#add('tpope/vim-commentary') 69 | call minpac#add('Townk/vim-autoclose') 70 | 71 | call minpac#add('mustache/vim-mustache-handlebars') 72 | call minpac#add('groenewege/vim-less') 73 | call minpac#add('dgryski/vim-godef') 74 | call minpac#add('rodjek/vim-puppet') 75 | 76 | " can be used to see if stuff is indented right 77 | " to be honest I barely use it and am considering removing 78 | call minpac#add('nathanaelkane/vim-indent-guides') 79 | 80 | " I prefer using tab for autocompletion 81 | " habit from old visual studio days 82 | call minpac#add('ervandew/supertab') 83 | call minpac#add('hashivim/vim-terraform') 84 | call minpac#add('joukevandermaas/vim-ember-hbs') 85 | 86 | " A pretty tag browser with hacks to make ruby 87 | " browsing look better, use :Tagbar to bring it up 88 | call minpac#add('majutsushi/tagbar') 89 | 90 | " tries to keep track of focus when we pick a file 91 | " from quick fix window, better than always opening the wrong file 92 | call minpac#add('yssl/QFEnter') 93 | 94 | " fzf is an awesome fuzzy file finder I map it to CTRL-P 95 | " well it started failing ... so lets remove 96 | " call minpac#add('junegunn/fzf', { 'dir': '~/.fzf', 'do': './install -all' }) 97 | " call minpac#add('junegunn/fzf.vim') 98 | 99 | " I am used to CTRL-p so use it, additionally allow for some extra 100 | " help in normal/visual mode 101 | " nmap h (fzf-maps-n) 102 | " xmap h (fzf-maps-x) 103 | " let g:fzf_preview_window='' 104 | 105 | call minpac#add('rhysd/git-messenger.vim') 106 | call minpac#add('Einenlum/yaml-revealer') 107 | call minpac#add('RRethy/vim-hexokinase', { 'do': 'make hexokinase' }) 108 | call minpac#add('pechorin/any-jump.vim') 109 | call minpac#add('davidhalter/jedi-vim') 110 | 111 | call minpac#add('madox2/vim-ai') 112 | 113 | if has('nvim') 114 | call minpac#add('nvim-tree/nvim-web-devicons') 115 | call minpac#add('otavioschwanck/arrow.nvim') 116 | call minpac#add('nvim-lua/plenary.nvim') 117 | call minpac#add('nvim-telescope/telescope.nvim') 118 | call minpac#add('dangduc/fzf-native') 119 | call minpac#add('nvim-telescope/telescope-fzf-native.nvim') 120 | 121 | nmap Telescope find_files theme=get_ivy disable_devicons=true 122 | " nmap Telescope find_files find_command=rg,--ignore,--hidden,--files theme=get_ivy 123 | nnoremap ff Telescope find_files 124 | nnoremap fg Telescope live_grep 125 | nnoremap fb Telescope buffers 126 | nnoremap fh Telescope help_tags 127 | lua require('init') 128 | end 129 | 130 | " if has('nvim') 131 | " call minpac#add('williamboman/mason.nvim') 132 | " call minpac#add('williamboman/mason-lspconfig.nvim') 133 | " call minpac#add('neovim/nvim-lspconfig') 134 | " lua require('mason').setup() 135 | " lua require('mason-lspconfig').setup() 136 | " end 137 | 138 | 139 | if has('nvim') 140 | call minpac#add('vimlab/split-term.vim') 141 | " %s/test/test1 will perform replacement in-place 142 | set inccommand=nosplit 143 | " cause it likes an I-Beam without this 144 | set guicursor= 145 | set mouse=a 146 | 147 | " note for clipboard to work we want xsel or xclip installed 148 | " see :help clipboard 149 | end 150 | 151 | if !has('nvim') 152 | " sensible is enabled by default in nvim 153 | call minpac#add('tpope/vim-sensible') 154 | " I prefer no antialiasing on osx 155 | set noantialias 156 | 157 | " a Autoselect so its easy to select from vim into 158 | " other apps 159 | " g grey inactive menu items 160 | " i use vim icon 161 | " m show the menubar 162 | set guioptions=agim 163 | 164 | set ttymouse=sgr 165 | set mouse=a 166 | " This hack seems to only work in vim 167 | " protect indenting when pasting in stuff 168 | " in normal mode 169 | if &term =~ "xterm.*" 170 | let &t_ti = &t_ti . "\e[?2004h" 171 | let &t_te = "\e[?2004l" . &t_te 172 | function XTermPasteBegin(ret) 173 | set pastetoggle=[201~ 174 | set paste 175 | return a:ret 176 | endfunction 177 | map [200~ XTermPasteBegin("i") 178 | imap [200~ XTermPasteBegin("") 179 | cmap [200~ 180 | cmap [201~ 181 | end 182 | end 183 | 184 | " minpac is so minimal it has no commands, so we map a few 185 | command! PackUpdate call minpac#update() 186 | command! PackClean call minpac#clean() 187 | 188 | syntax on 189 | filetype plugin indent on 190 | set nocompatible 191 | set termguicolors 192 | set expandtab 193 | set tabstop=2 194 | set softtabstop=2 195 | set shiftwidth=2 196 | set autoindent 197 | set smartindent 198 | set cindent 199 | set guifont=Consolas\ 14 200 | set hlsearch 201 | set incsearch 202 | 203 | " history is rediculously low out of the box set at 20 204 | set history=1000 205 | 206 | " makes using cfdo easier and allows us to keep better history 207 | set hidden 208 | 209 | " move swap files out of the way 210 | " this gets really annoying having to add this to .gitignore 211 | " or checking them in by mistake when forcing an add 212 | set directory=$HOME/.vim/swapfiles/ 213 | set backupdir=$HOME/.vim/backupdir/ 214 | 215 | let mapleader=" " 216 | nnoremap 217 | 218 | map :set number! 219 | map :tabnext 220 | map :tabprevious 221 | 222 | let g:rubycomplete_buffer_loading = 1 223 | let g:rubycomplete_classes_in_global = 1 224 | let g:rubycomplete_rails = 1 225 | 226 | 227 | silent! ruby nil 228 | set completeopt=longest,menuone 229 | map :previous 230 | map :next 231 | 232 | " the silver searcher is way faster than ack we use it 233 | let g:ackprg = 'ag --nogroup --nocolor --column' 234 | 235 | au BufNewFile,BufRead Guardfile set filetype=ruby 236 | au BufNewFile,BufRead *.pill set filetype=ruby 237 | au BufNewFile,BufRead *.es6 set filetype=javascript 238 | au BufNewFile,BufRead *.es6.erb set filetype=javascript 239 | au BufNewFile,BufRead *.pp set filetype=puppet 240 | au BufNewFile,BufRead *.svelte setf svelte 241 | 242 | " Discourse specific helpers that force browser refresh / restart 243 | " nmap a :!touch tmp/restart 244 | nmap s :!touch tmp/refresh_browser 245 | 246 | nmap a :ALEFix 247 | 248 | " I prefer to check in with a GUI then using fugitive or Gina 249 | nmap g :Git gui 250 | 251 | let g:git_messenger_no_default_mappings=v:true 252 | let g:git_messenger_always_into_popup=v:true 253 | nmap m (git-messenger) 254 | 255 | " leader l is a nice way of quickly toggling hlsearch if we need it 256 | nnoremap l (&hls && v:hlsearch ? ':nohls' : ':set hls')."\n" 257 | 258 | nnoremap t :Tagbar 259 | 260 | nmap e :ALENext 261 | nmap b :ALEPrevious 262 | 263 | " I find CTRL-W CTRL-L etc. for changing windows so awkward 264 | " ALT -> right etc is so much simpler 265 | nmap :wincmd k 266 | nmap :wincmd j 267 | nmap :wincmd h 268 | nmap :wincmd l 269 | 270 | " force more colors in vim, not sure it is needed anymore in 271 | " 8 cause I am already forcing gui colors 272 | set t_Co=256 273 | 274 | function! MRIIndent() 275 | setlocal cindent 276 | setlocal noexpandtab 277 | setlocal shiftwidth=4 278 | setlocal softtabstop=4 279 | setlocal tabstop=8 280 | setlocal textwidth=80 281 | " Ensure function return types are not indented 282 | setlocal cinoptions=(0,t0 283 | endfunction 284 | 285 | filetype off 286 | filetype plugin indent off 287 | set runtimepath+=/usr/local/go/misc/vim 288 | filetype plugin indent on 289 | 290 | " :TagBar displays a split to the right that allows 291 | " us to navigate current file, these are rules for Ruby 292 | " and Go 293 | let g:tagbar_type_go = { 294 | \ 'ctagstype' : 'go', 295 | \ 'kinds' : [ 296 | \ 'p:package', 297 | \ 'i:imports:1', 298 | \ 'c:constants', 299 | \ 'v:variables', 300 | \ 't:types', 301 | \ 'n:interfaces', 302 | \ 'w:fields', 303 | \ 'e:embedded', 304 | \ 'm:methods', 305 | \ 'r:constructor', 306 | \ 'f:functions' 307 | \ ], 308 | \ 'sro' : '.', 309 | \ 'kind2scope' : { 310 | \ 't' : 'ctype', 311 | \ 'n' : 'ntype' 312 | \ }, 313 | \ 'scope2kind' : { 314 | \ 'ctype' : 't', 315 | \ 'ntype' : 'n' 316 | \ }, 317 | \ 'ctagsbin' : 'gotags', 318 | \ 'ctagsargs' : '-sort -silent' 319 | \ } 320 | 321 | let g:tagbar_type_ruby = { 322 | \ 'kinds' : [ 323 | \ 'm:modules', 324 | \ 'c:classes', 325 | \ 'd:describes', 326 | \ 'C:contexts', 327 | \ 'f:methods', 328 | \ 'F:singleton methods' 329 | \ ] 330 | \ } 331 | 332 | if executable('ripper-tags') 333 | let g:tagbar_type_ruby = { 334 | \ 'kinds' : ['m:modules', 335 | \ 'c:classes', 336 | \ 'C:constants', 337 | \ 'F:singleton methods', 338 | \ 'f:methods', 339 | \ 'a:aliases'], 340 | \ 'kind2scope' : { 'c' : 'class', 341 | \ 'm' : 'class' }, 342 | \ 'scope2kind' : { 'class' : 'c' }, 343 | \ 'ctagsbin' : 'ripper-tags', 344 | \ 'ctagsargs' : ['-f', '-'] 345 | \ } 346 | endif 347 | 348 | let g:ale_linters = { 'ruby': ['ruby','rubocop'], 'javascript': ['eslint','embertemplatelint'], 'handlebars': ['embertemplatelint', 'prettier'], 'glimmer' : ['eslint','embertemplatelint'] } 349 | let g:ale_lint_on_text_changed = 'never' 350 | let g:ale_lint_on_insert_leave = 0 351 | let g:ale_fixers = {'ruby': ['syntax_tree'] , 'javascript.glimmer': ['eslint','prettier'] , 'handlebars': ['prettier'], 'html.handlebars': ['prettier'], 'scss': ['prettier'], 'javascript': ['eslint','prettier'] } 352 | let g:ale_fix_on_save = 0 353 | let g:ale_linters_explicit = 1 354 | " let g:ale_javascript_prettier_executable = 'yarn prettier' 355 | 356 | " this was to deal with the newline at the end of each file 357 | autocmd FileType html.handlebars setlocal noeol binary 358 | 359 | " using the glimmer parser when formatting via prettier 360 | autocmd FileType html.handlebars let b:ale_javascript_prettier_options = '--parser=glimmer' 361 | 362 | 363 | cabbrev Ack Ack! 364 | 365 | " Discourse specific, on save we will notify 366 | " the exact position where a spec was saved 367 | " this allows us to run the spec at the exact right spot 368 | function! s:notify_file_change_discourse() 369 | let notify = getcwd() . "/bin/notify_file_change" 370 | 371 | if ! executable(notify) 372 | let root = rails#app().path() 373 | let notify = root . "/bin/notify_file_change" 374 | end 375 | 376 | if ! executable(notify) 377 | let notify = getcwd() . "../../bin/notify_file_change" 378 | end 379 | 380 | if executable(notify) 381 | if executable('socat') 382 | execute "!" . notify . ' ' . expand("%:p") . " " . line(".") 383 | end 384 | end 385 | " redraw! 386 | endfunction 387 | 388 | set backspace=indent,eol,start 389 | 390 | " we use pupped a lot and we need saner indent 391 | function! PuppetIndent() 392 | setlocal noexpandtab 393 | setlocal shiftwidth=4 394 | setlocal softtabstop=4 395 | setlocal tabstop=4 396 | setlocal textwidth=80 397 | endfunction 398 | 399 | " very annoying default behavior 400 | let g:puppet_align_hashes = 0 401 | 402 | " gruvbox default contrast is not enough for me 403 | let g:gruvbox_contrast_dark="hard" 404 | set background=dark 405 | colorscheme gruvbox 406 | "colorscheme catppuccin 407 | " colorscheme kanagawa 408 | 409 | 410 | " I find the amount folding ruby does to be too much of the folding way too much 411 | " this simply folds methods 412 | let ruby_foldable_group="def" 413 | 414 | " add git to status line 415 | set statusline=%<%f\ %h%m%r%{fugitive#statusline()}%=%-14.(%l,%c%V%)\ %P 416 | 417 | " map g in visual mode to provide a stable link to GitHub source 418 | " allows us to easily select some text in vim and talk about it 419 | function! s:GithubLink(line1, line2) 420 | let path = resolve(expand('%:p')) 421 | let dir = shellescape(fnamemodify(path, ':h')) 422 | let repoN = system("cd " . dir . " && git remote -v | awk '{ tmp = match($2, /github/); if (tmp) { split($2,a,/github.com[:\.]/); c = a[2]; split(c,b,/[.]/); print b[1]; exit; }}'") 423 | 424 | let repo = substitute(repoN, '\r\?\n\+$', '', '') 425 | let root = system("cd " . dir . " && git rev-parse --show-toplevel") 426 | let relative = strpart(path, strlen(root) - 1, strlen(path) - strlen(root) + 1) 427 | 428 | 429 | let repoShaN = system("cd " . dir . " && git rev-parse HEAD") 430 | let repoSha = substitute(repoShaN, '\r\?\n\+$', '', '') 431 | 432 | let link = "https://github.com/". repo . "/blob/" . repoSha . relative . "#L". a:line1 . "-L" . a:line2 433 | 434 | let @+ = link 435 | let @* = link 436 | 437 | echo link 438 | endfunction 439 | 440 | command! -bar -bang -range -nargs=* GithubLink 441 | \ keepjumps call GithubLink(, ) 442 | 443 | vmap g :GithubLink 444 | 445 | " highlight trailing white space 446 | highlight ExtraWhitespace ctermbg=red guibg=#CC0000 447 | match ExtraWhitespace /\s\+$/ 448 | 449 | nmap v :tabedit ~/.vimrc 450 | nmap V :tabedit ~/.config/nvim/lua/init.lua 451 | 452 | " resizing windows needs to be simpler 453 | nnoremap = :exe "resize " . (winheight(0) * 3/2) 454 | nnoremap - :exe "resize " . (winheight(0) * 2/3) 455 | 456 | " we group cause then we can cleanly reload all autocmd 457 | augroup vimrc 458 | " this clears all the commands (which we need to do on reload) 459 | autocmd! 460 | autocmd BufWinEnter * match ExtraWhitespace /\s\+$/ 461 | autocmd InsertEnter * match ExtraWhitespace /\s\+\%#\@ f :NERDTreeFind 490 | 491 | let g:python3_host_prog = '/usr/bin/python3' 492 | 493 | " Define a function to change the NERDTree root to Vim's current working directory 494 | function! SyncNERDTreeRoot() 495 | if exists("g:NERDTree") " && g:NERDTree.IsOpen() 496 | NERDTreeCWD 497 | endif 498 | endfunction 499 | 500 | " Create an autocmd that triggers the above function whenever the working directory is changed 501 | augroup NERDTreeSync 502 | autocmd! 503 | autocmd DirChanged * call SyncNERDTreeRoot() 504 | augroup END 505 | 506 | if has('nvim') 507 | lua require("init") 508 | endif 509 | 510 | let g:vim_ai_debug = "1" 511 | 512 | -------------------------------------------------------------------------------- /nvim/lua/plugins/ai.lua: -------------------------------------------------------------------------------- 1 | return { 2 | -- { 3 | -- { 4 | -- "codota/tabnine-nvim", 5 | -- build = "./dl_binaries.sh", 6 | -- init = function() 7 | -- require("tabnine").setup({ 8 | -- disable_auto_comment = true, 9 | -- accept_keymap = "", 10 | -- dismiss_keymap = "", 11 | -- debounce_ms = 800, 12 | -- suggestion_color = { gui = "#808080", cterm = 244 }, 13 | -- exclude_filetypes = { "TelescopePrompt", "NvimTree" }, 14 | -- log_file_path = nil, -- absolute path to Tabnine log file 15 | -- ignore_certificate_errors = false, 16 | -- -- workspace_folders = { 17 | -- -- paths = { "/your/project" }, 18 | -- -- get_paths = function() 19 | -- -- return { "/your/project" } 20 | -- -- end, 21 | -- -- }, 22 | -- }) 23 | -- end, 24 | -- }, 25 | -- }, 26 | -- { 27 | -- "CopilotC-Nvim/CopilotChat.nvim", 28 | -- -- dir = "/home/sam/Source/CopilotChat.nvim", 29 | -- -- name = "ccchat", 30 | -- dependencies = { 31 | -- { "github/copilot.vim" }, 32 | -- { "nvim-lua/plenary.nvim" }, -- for curl, log and async functions 33 | -- }, 34 | -- build = "make tiktoken", -- Only on MacOS or Linux 35 | -- init = function() 36 | -- -- copilot is annoying in copilot chat 37 | -- vim.api.nvim_create_autocmd("BufEnter", { 38 | -- pattern = "copilot-*", 39 | -- callback = function() 40 | -- vim.b.copilot_enabled = false 41 | -- end, 42 | -- }) 43 | -- -- errors are also super annoying 44 | -- vim.api.nvim_create_autocmd("FileType", { 45 | -- pattern = "copilot-chat", 46 | -- callback = function() 47 | -- vim.cmd("highlight Error NONE") 48 | -- end, 49 | -- }) 50 | -- end, 51 | -- opts = { 52 | -- model = "claude-sonnet-4.5", 53 | -- -- model = "gpt-4.1", 54 | -- -- model = "gpt-5", 55 | -- debug = false, 56 | -- auto_insert_mode = true, 57 | -- insert_at_end = false, 58 | -- chat_autocomplete = false, -- this is very annoying just lean on Tab 59 | -- highlight_selection = false, 60 | -- highlight_headers = true, 61 | -- headers = { 62 | -- user = "Sam ", 63 | -- }, 64 | -- seperator = "---", 65 | -- error_header = "> [!ERROR] Error", 66 | -- mappings = { 67 | -- complete = { 68 | -- insert = "", 69 | -- }, 70 | -- }, 71 | -- functions = { 72 | -- file = { 73 | -- group = "copilot", 74 | -- uri = "file://{path}", 75 | -- description = "Reads content from a specified file path", 76 | -- schema = { 77 | -- type = "object", 78 | -- required = { "path" }, 79 | -- properties = { 80 | -- path = { 81 | -- type = "string", 82 | -- description = "Path to file to include in chat context.", 83 | -- enum = function(source) 84 | -- local chat_winid = vim.api.nvim_get_current_win() 85 | -- local async = require("plenary.async") 86 | -- local fn = async.wrap(function(callback) 87 | -- local telescope = require("telescope.builtin") 88 | -- local actions = require("telescope.actions") 89 | -- local action_state = require("telescope.actions.state") 90 | 91 | -- telescope.find_files({ 92 | -- cwd = source.cwd(), -- Use source working directory 93 | -- attach_mappings = function(prompt_bufnr) 94 | -- actions.select_default:replace(function() 95 | -- actions.close(prompt_bufnr) 96 | -- local selection = action_state.get_selected_entry() 97 | 98 | -- -- Return focus to the chat window 99 | -- if vim.api.nvim_win_is_valid(chat_winid) then 100 | -- vim.api.nvim_set_current_win(chat_winid) 101 | -- vim.cmd("normal! a") 102 | -- end 103 | 104 | -- vim.schedule(function() 105 | -- callback(selection) 106 | -- end) 107 | -- end) 108 | -- return true 109 | -- end, 110 | -- }) 111 | -- end, 1) 112 | 113 | -- return fn() 114 | -- end, 115 | -- }, 116 | -- }, 117 | -- }, 118 | -- resolve = function(input, source) 119 | -- local utils = require("CopilotChat.utils") 120 | -- local resources = require("CopilotChat.resources") 121 | 122 | -- -- Handle relative path - make it absolute for reading 123 | -- local full_path = input.path 124 | -- if not vim.startswith(full_path, "/") then 125 | -- full_path = source.cwd() .. "/" .. input.path 126 | -- end 127 | 128 | -- utils.schedule_main() 129 | -- local data, mimetype = resources.get_file(full_path) 130 | -- if not data then 131 | -- error("File not found: " .. input.path) 132 | -- end 133 | 134 | -- return { 135 | -- { 136 | -- uri = "file://" .. input.path, -- Keep relative path in URI 137 | -- mimetype = mimetype, 138 | -- data = data, 139 | -- }, 140 | -- } 141 | -- end, 142 | -- }, 143 | -- gitmain = { 144 | -- group = "copilot", 145 | -- description = "Get diff against main branch", 146 | -- uri = "gitmain://diff", 147 | -- schema = { 148 | -- type = "object", 149 | -- required = {}, 150 | -- properties = {}, 151 | -- }, 152 | -- resolve = function() 153 | -- local utils = require("CopilotChat.utils") 154 | -- utils.schedule_main() 155 | -- -- Get diff against main branch including staged and unstaged changes 156 | -- local cmd = "git diff main HEAD && git diff" 157 | -- local output = vim.fn.system(cmd) 158 | -- return { 159 | -- { 160 | -- uri = "gitmain://diff", 161 | -- mimetype = "text/x-diff", 162 | -- data = output, 163 | -- }, 164 | -- } 165 | -- end, 166 | -- }, 167 | -- }, 168 | -- }, 169 | -- keys = { 170 | -- { 171 | -- "p", 172 | -- "CopilotChatToggle", 173 | -- desc = "Toggle Copilot Chat", 174 | -- mode = { "n", "v" }, 175 | -- }, 176 | -- { 177 | -- "c", 178 | -- function() 179 | -- local visualmode = vim.fn.mode() 180 | -- local input = vim.fn.input("Quick Chat: ") 181 | -- if input ~= "" then 182 | -- local chat = require("CopilotChat") 183 | -- local select = require("CopilotChat.select") 184 | 185 | -- local selection 186 | -- -- if we have a line in visual mode then select it 187 | -- if visualmode == "V" or visualmode == "v" or visualmode == "\22" then 188 | -- selection = select.visual 189 | -- else 190 | -- selection = select.buffer 191 | -- end 192 | 193 | -- chat.ask(input, { selection = selection }) 194 | -- end 195 | -- end, 196 | -- mode = { "n", "v" }, 197 | -- desc = "Start Copilot Chat", 198 | -- }, 199 | -- }, 200 | -- }, 201 | { "nvim-treesitter-textobjects", branch = "main", lazy = true }, 202 | { 203 | "github/copilot.vim", 204 | event = "InsertEnter", 205 | config = function() 206 | vim.cmd("Copilot") 207 | vim.g.copilot_no_tab_map = true 208 | vim.api.nvim_set_keymap("i", "", 'copilot#Accept("")', { silent = true, expr = true }) 209 | vim.api.nvim_set_keymap("i", "", "copilot#Dismiss()", { silent = true, expr = true }) 210 | end, 211 | }, 212 | { 213 | "folke/sidekick.nvim", 214 | dependencies = { 215 | "nvim-treesitter/nvim-treesitter-textobjects", 216 | }, 217 | opts = { 218 | }, 219 | keys = { 220 | { 221 | "", 222 | function() 223 | -- if there is a next edit, jump to it, otherwise apply it if any 224 | if not require("sidekick").nes_jump_or_apply() then 225 | return "" -- fallback to normal tab 226 | end 227 | end, 228 | expr = true, 229 | desc = "Goto/Apply Next Edit Suggestion", 230 | }, 231 | { 232 | "", 233 | function() require("sidekick.cli").toggle() end, 234 | desc = "Sidekick Toggle", 235 | mode = { "n", "t", "i", "x" }, 236 | }, 237 | { 238 | "aa", 239 | function() require("sidekick.cli").toggle() end, 240 | desc = "Sidekick Toggle CLI", 241 | }, 242 | { 243 | "as", 244 | function() require("sidekick.cli").select() end, 245 | -- Or to select only installed tools: 246 | -- require("sidekick.cli").select({ filter = { installed = true } }) 247 | desc = "Select CLI", 248 | }, 249 | { 250 | "ad", 251 | function() require("sidekick.cli").close() end, 252 | desc = "Detach a CLI Session", 253 | }, 254 | { 255 | "at", 256 | function() require("sidekick.cli").send({ msg = "{this}" }) end, 257 | mode = { "x", "n" }, 258 | desc = "Send This", 259 | }, 260 | { 261 | "af", 262 | function() require("sidekick.cli").send({ msg = "{file}" }) end, 263 | desc = "Send File", 264 | }, 265 | { 266 | "av", 267 | function() require("sidekick.cli").send({ msg = "{selection}" }) end, 268 | mode = { "x" }, 269 | desc = "Send Visual Selection", 270 | }, 271 | { 272 | "ap", 273 | function() require("sidekick.cli").prompt() end, 274 | mode = { "n", "x" }, 275 | desc = "Sidekick Select Prompt", 276 | }, 277 | { 278 | "ac", 279 | function() require("sidekick.cli").toggle({ name = "cursor", focus = true }) end, 280 | desc = "Sidekick Toggle Cursor", 281 | }, 282 | }, 283 | } 284 | -- { 285 | -- "olimorris/codecompanion.nvim", 286 | -- -- dir = "/home/sam/Source/codecompanion.nvim", 287 | -- init = function() 288 | -- -- copilot is annoying in copilot chat 289 | -- vim.api.nvim_create_autocmd("BufEnter", { 290 | -- pattern = "*\\[CodeCompanion\\]*", 291 | -- callback = function() 292 | -- vim.b.copilot_enabled = false 293 | -- end, 294 | -- }) 295 | -- -- errors are also super annoying 296 | -- vim.api.nvim_create_autocmd("FileType", { 297 | -- pattern = "*\\[CodeCompanion\\]*", 298 | -- callback = function() 299 | -- vim.cmd("highlight Error NONE") 300 | -- end, 301 | -- }) 302 | -- end, 303 | 304 | -- keys = { 305 | -- { 306 | -- "p", 307 | -- "CodeCompanionChat toggle", 308 | -- desc = "Toggle Copilot Chat", 309 | -- mode = { "n", "v" }, 310 | -- }, 311 | -- }, 312 | -- config = function() 313 | -- require("codecompanion").setup({ 314 | -- opts = { 315 | -- system_prompt = function(opts) 316 | -- local language = opts.language or "English" 317 | -- return string.format( 318 | -- [[ 319 | -- You are a technical advisor to an experienced software engineer working in Neovim. 320 | 321 | -- Assume advanced programming knowledge and familiarity with software engineering principles. 322 | 323 | -- When responding: 324 | -- - Prioritize technical depth and architectural implications 325 | -- - Focus on edge cases, performance considerations, and scalability 326 | -- - Discuss trade-offs between different approaches when relevant 327 | -- - Skip explanations of standard patterns or basic concepts unless requested 328 | -- - Reference advanced patterns, algorithms, or design principles when applicable 329 | -- - Prefer showing code over explaining it unless analysis is specifically requested 330 | -- - All non-code responses in %s 331 | 332 | -- For code improvement: 333 | -- - Focus on optimizations beyond obvious refactorings 334 | -- - Highlight potential concurrency issues, memory management concerns, or runtime complexity 335 | -- - Consider backwards compatibility, maintainability, and testing implications 336 | -- - Suggest modern idioms and language features when appropriate 337 | 338 | -- For architecture discussions: 339 | -- - Consider system boundaries, coupling concerns, and dependency management 340 | -- - Address long-term maintenance and extensibility implications 341 | -- - Discuss relevant architectural patterns without overexplaining them 342 | 343 | -- Deliver responses with professional brevity. Skip preamble and unnecessary context. 344 | -- ]], 345 | -- language 346 | -- ) 347 | -- end, 348 | -- }, 349 | -- display = { 350 | -- chat = { 351 | -- intro_message = "Press ? for options", 352 | -- show_token_count = true, -- Show the token count for each response? 353 | -- start_in_insert_mode = true, -- Open the chat buffer in insert mode? 354 | -- window = { 355 | -- layout = "vertical", 356 | -- position = "right", 357 | -- relative = "editor", 358 | -- full_height = true, 359 | -- }, 360 | -- }, 361 | -- }, 362 | -- strategies = { 363 | -- chat = { 364 | -- roles = { 365 | -- llm = function(adapter) 366 | -- local model_name = "" 367 | -- -- Try to get the model name from the adapter 368 | -- if adapter.schema and adapter.schema.model and adapter.schema.model.default then 369 | -- local model = adapter.schema.model.default 370 | -- if type(model) == "function" then 371 | -- model = model(adapter) 372 | -- end 373 | -- model_name = " - " .. model 374 | -- end 375 | 376 | -- return "Model (" .. adapter.formatted_name .. model_name .. ")" 377 | -- end, 378 | -- }, 379 | -- slash_commands = { 380 | -- ["file"] = { 381 | -- -- Location to the slash command in CodeCompanion 382 | -- callback = "strategies.chat.slash_commands.file", 383 | -- description = "Select a file using Telescope", 384 | -- opts = { 385 | -- provider = "telescope", -- Other options include 'default', 'mini_pick', 'fzf_lua', snacks 386 | -- contains_code = true, 387 | -- }, 388 | -- }, 389 | -- }, 390 | -- }, 391 | -- }, 392 | -- }) 393 | -- end, 394 | -- dependencies = { 395 | -- "nvim-lua/plenary.nvim", 396 | -- "nvim-treesitter/nvim-treesitter", 397 | -- }, 398 | -- }, 399 | -- { 400 | -- "GeorgesAlkhouri/nvim-aider", 401 | -- cmd = { 402 | -- "AiderTerminalToggle", 403 | -- "AiderHealth", 404 | -- }, 405 | -- keys = { 406 | -- { "a/", "AiderTerminalToggle", desc = "Open Aider" }, 407 | -- { "as", "AiderTerminalSend", desc = "Send to Aider", mode = { "n", "v" } }, 408 | -- { "ac", "AiderQuickSendCommand", desc = "Send Command To Aider" }, 409 | -- { "ab", "AiderQuickSendBuffer", desc = "Send Buffer To Aider" }, 410 | -- { "a+", "AiderQuickAddFile", desc = "Add File to Aider" }, 411 | -- { "a-", "AiderQuickDropFile", desc = "Drop File from Aider" }, 412 | -- { "ar", "AiderQuickReadOnlyFile", desc = "Add File as Read-Only" }, 413 | -- }, 414 | -- dependencies = { 415 | -- "folke/snacks.nvim", 416 | -- "nvim-telescope/telescope.nvim", 417 | -- }, 418 | -- config = true, 419 | -- }, 420 | -- { 421 | -- "yetone/avante.nvim", 422 | -- event = "VeryLazy", 423 | -- lazy = false, 424 | -- version = false, -- Set this to "*" to always pull the latest release version, or set it to false to update to the latest code changes. 425 | -- opts = { 426 | -- -- add any opts here 427 | -- }, 428 | -- -- if you want to build from source then do `make BUILD_FROM_SOURCE=true` 429 | -- build = "make", 430 | -- -- build = "powershell -ExecutionPolicy Bypass -File Build.ps1 -BuildFromSource false" -- for windows 431 | -- dependencies = { 432 | -- "stevearc/dressing.nvim", 433 | -- "nvim-lua/plenary.nvim", 434 | -- "MunifTanjim/nui.nvim", 435 | -- --- The below dependencies are optional, 436 | -- "echasnovski/mini.pick", -- for file_selector provider mini.pick 437 | -- "nvim-telescope/telescope.nvim", -- for file_selector provider telescope 438 | -- "hrsh7th/nvim-cmp", -- autocompletion for avante commands and mentions 439 | -- "ibhagwan/fzf-lua", -- for file_selector provider fzf 440 | -- "nvim-tree/nvim-web-devicons", -- or echasnovski/mini.icons 441 | -- "zbirenbaum/copilot.lua", -- for providers='copilot' 442 | -- { 443 | -- -- support for image pasting 444 | -- "HakonHarnes/img-clip.nvim", 445 | -- event = "VeryLazy", 446 | -- opts = { 447 | -- -- recommended settings 448 | -- default = { 449 | -- embed_image_as_base64 = false, 450 | -- prompt_for_file_name = false, 451 | -- drag_and_drop = { 452 | -- insert_mode = true, 453 | -- }, 454 | -- -- required for Windows users 455 | -- use_absolute_path = true, 456 | -- }, 457 | -- }, 458 | -- }, 459 | -- }, 460 | -- }, 461 | } 462 | -------------------------------------------------------------------------------- /nvim/lua/plugins/coding.lua: -------------------------------------------------------------------------------- 1 | local function ensure_d_rspec() 2 | local script = [=[ 3 | #!/bin/bash 4 | 5 | echo "$@" > /tmp/d-rspec-args.log 6 | 7 | args=() 8 | for arg in "$@"; do 9 | if [[ "$arg" == ./* ]]; then 10 | args+=("$(realpath "$arg")") 11 | else 12 | args+=("$arg") 13 | fi 14 | done 15 | 16 | # Check if any file argument is in a share/dv directory 17 | use_dv=false 18 | formatter_file="" 19 | output_file="" 20 | 21 | for ((i = 0; i < ${#args[@]}; i++)); do 22 | arg="${args[i]}" 23 | file_path="${arg%%:*}" 24 | 25 | if [[ "$file_path" == *.rb && -f "$file_path" && "$arg" == */share/dv/* ]]; then 26 | use_dv=true 27 | fi 28 | if [[ "$arg" == "--require" && -f "${args[i + 1]}" ]]; then 29 | formatter_file="${args[i + 1]}" 30 | fi 31 | if [[ "$arg" == "-o" ]]; then 32 | output_file="${args[i + 1]}" 33 | fi 34 | done 35 | 36 | if [[ "$use_dv" == true ]]; then 37 | # Path mapping constants 38 | local_dv_prefix="/home/sam/.local/share/dv/discourse_src" 39 | container_prefix="/var/www/discourse" 40 | 41 | # Copy formatter into container 42 | if [[ -n "$formatter_file" ]]; then 43 | container_formatter="/tmp/$(basename "$formatter_file")" 44 | dv cp "$formatter_file" "@:$container_formatter" 45 | 46 | # Update args to use container formatter path 47 | for ((i = 0; i < ${#args[@]}; i++)); do 48 | if [[ "${args[i]}" == "$formatter_file" ]]; then 49 | args[i]="$container_formatter" 50 | fi 51 | done 52 | fi 53 | 54 | # Run rspec in container 55 | container_output="/tmp/$(basename "$output_file")" 56 | 57 | # Update output file arg to container path 58 | for ((i = 0; i < ${#args[@]}; i++)); do 59 | if [[ "${args[i]}" == "$output_file" ]]; then 60 | args[i]="$container_output" 61 | fi 62 | done 63 | 64 | # Remap local dv paths to container paths in args 65 | for ((i = 0; i < ${#args[@]}; i++)); do 66 | if [[ "${args[i]}" == "$local_dv_prefix"* ]]; then 67 | args[i]="${args[i]/$local_dv_prefix/$container_prefix}" 68 | fi 69 | done 70 | 71 | dv run -- bin/rspec "${args[@]}" 72 | 73 | # Copy results back 74 | if [[ -n "$output_file" ]]; then 75 | dv cp "@:$container_output" "$output_file" 76 | 77 | cp "$output_file" "/tmp/d-rspec-output-backup.json" 78 | 79 | # Remap container paths back to local paths in the output file 80 | if [[ -f "$output_file" ]]; then 81 | sed -i "s|$container_prefix|$local_dv_prefix|g" "$output_file" 82 | fi 83 | fi 84 | else 85 | # Original behavior for non-dv files 86 | ( 87 | cd /home/sam/Source/discourse || exit 1 88 | if [[ "${args[*]}" == *"plugins"* ]]; then 89 | export LOAD_PLUGINS=1 90 | fi 91 | ./bin/rspec "${args[@]}" 92 | 93 | # Find the output file from arguments 94 | output_file="" 95 | for ((i = 0; i < ${#args[@]}; i++)); do 96 | if [[ "${args[i]}" == "-o" ]]; then 97 | output_file="${args[i + 1]}" 98 | break 99 | fi 100 | done 101 | 102 | # If we found an output file, process it 103 | if [[ -n "$output_file" && -f "$output_file" ]]; then 104 | plugin_id=$(jq -r '.examples[0].id // ""' "$output_file") 105 | test_file=$(jq -r '.examples[0].file_path // ""' "$output_file") 106 | 107 | skip_remap=false 108 | if [[ "$plugin_id" =~ ^\./plugins/[^/]+/ ]]; then 109 | # Extract plugin name from the path 110 | plugin_name=$(echo "$plugin_id" | sed -n 's|^\./plugins/\([^/]*\)/.*|\1|p') 111 | plugin_dir="/home/sam/Source/discourse/plugins/$plugin_name" 112 | 113 | # Check if plugin has its own git repository 114 | if [[ -d "$plugin_dir/.git" ]]; then 115 | skip_remap=true 116 | fi 117 | fi 118 | 119 | if [[ "$skip_remap" == false ]]; then 120 | temp_file=$(mktemp) 121 | jq '(.examples[]? | select(.id != null) | .id) |= sub("^\\./plugins/[^/]+/"; "./")' \ 122 | "$output_file" >"$temp_file" 123 | mv "$temp_file" "$output_file" 124 | fi 125 | fi 126 | ) 127 | fi 128 | ]=] 129 | local script_path = "/tmp/d-rspec" 130 | local f = io.open(script_path, "w") 131 | if f then 132 | f:write(script) 133 | f:close() 134 | os.execute("chmod +x " .. script_path) 135 | end 136 | end 137 | 138 | return { 139 | { 140 | "neovim/nvim-lspconfig", 141 | dependencies = { 142 | "mason-org/mason.nvim", 143 | "mason-org/mason-lspconfig.nvim", 144 | }, 145 | }, 146 | { 147 | "mason-org/mason.nvim", 148 | opts = {} 149 | }, 150 | { 151 | "ray-x/go.nvim", 152 | dependencies = { -- optional packages 153 | "ray-x/guihua.lua", 154 | "neovim/nvim-lspconfig", 155 | "nvim-treesitter/nvim-treesitter", 156 | }, 157 | opts = { 158 | -- lsp_keymaps = false, 159 | -- other options 160 | }, 161 | config = function(_, opts) 162 | require("go").setup(opts) 163 | local format_sync_grp = vim.api.nvim_create_augroup("GoFormat", {}) 164 | vim.api.nvim_create_autocmd("BufWritePre", { 165 | pattern = "*.go", 166 | callback = function() 167 | require('go.format').goimports() 168 | end, 169 | group = format_sync_grp, 170 | }) 171 | end, 172 | event = { "CmdlineEnter" }, 173 | ft = { "go", 'gomod' }, 174 | build = ':lua require("go.install").update_all_sync()' -- if you need to install/update all binaries 175 | }, 176 | { 177 | "mason-org/mason-lspconfig.nvim", 178 | keys = { { "cm", "Mason", desc = "Mason" } }, 179 | opts = { ensure_installed = { "erb-formatter", "erb-lint" } }, 180 | }, 181 | { 182 | "neovim/nvim-lspconfig", 183 | opts = { 184 | diagnostics = { 185 | underline = true, 186 | update_in_insert = false, 187 | virtual_text = { 188 | spacing = 4, 189 | source = "if_many", 190 | prefix = "●", 191 | }, 192 | signs = { 193 | text = { 194 | [vim.diagnostic.severity.ERROR] = " ", 195 | [vim.diagnostic.severity.WARN] = " ", 196 | [vim.diagnostic.severity.HINT] = " ", 197 | [vim.diagnostic.severity.INFO] = " ", 198 | }, 199 | }, 200 | }, 201 | servers = { 202 | lua_ls = { 203 | on_init = function(client) 204 | if client.workspace_folders then 205 | local path = client.workspace_folders[1].name 206 | if vim.uv.fs_stat(path .. "/.luarc.json") or vim.uv.fs_stat(path .. "/.luarc.jsonc") then 207 | return 208 | end 209 | end 210 | 211 | local runtime_files = vim.api.nvim_get_runtime_file("", true) 212 | for k, v in ipairs(runtime_files) do 213 | if v == "/home/sam/.config/nvim/after" or v == "/home/sam/.config/nvim" then 214 | table.remove(runtime_files, k) 215 | end 216 | end 217 | 218 | table.insert(runtime_files, "${3rd}/luv/library") 219 | 220 | local function get_all_plugin_paths() 221 | local paths = {} 222 | -- Get all plugin specs from lazy 223 | for _, plugin in pairs(require("lazy.core.config").plugins) do 224 | if type(plugin.dir) == "string" then 225 | table.insert(paths, plugin.dir) 226 | end 227 | end 228 | return paths 229 | end 230 | 231 | -- add all missing runtime paths 232 | for _, path in ipairs(get_all_plugin_paths()) do 233 | if not vim.tbl_contains(runtime_files, path) then 234 | table.insert(runtime_files, path) 235 | end 236 | end 237 | 238 | client.config.settings.Lua = vim.tbl_deep_extend("force", client.config.settings.Lua, { 239 | runtime = { 240 | -- Tell the language server which version of Lua you're using 241 | -- (most likely LuaJIT in the case of Neovim) 242 | version = "LuaJIT", 243 | }, 244 | workspace = { 245 | checkThirdParty = false, 246 | library = runtime_files, 247 | }, 248 | }) 249 | end, 250 | settings = { 251 | Lua = { 252 | workspace = { 253 | checkThirdParty = false, 254 | }, 255 | codeLens = { 256 | enable = true, 257 | }, 258 | completion = { 259 | callSnippet = "Replace", 260 | }, 261 | hint = { 262 | enable = true, 263 | setType = false, 264 | paramType = true, 265 | paramName = "Disable", 266 | semicolon = "Disable", 267 | arrayIndex = "Disable", 268 | }, 269 | }, 270 | }, 271 | }, 272 | ruby_lsp = { 273 | init_options = { 274 | addonSettings = { 275 | ["Ruby LSP Rails"] = { 276 | enablePendingMigrationsPrompt = false, 277 | }, 278 | }, 279 | } 280 | }, 281 | rubocop = {}, 282 | -- glint = {}, not working at the moment 283 | ember = {}, 284 | stylelint_lsp = {}, 285 | eslint = { 286 | filetypes = { 287 | "javascript", 288 | "typescript", 289 | "typescript.glimmer", 290 | "javascript.glimmer", 291 | "json", 292 | "markdown", 293 | }, 294 | }, 295 | ts_ls = {}, 296 | cssls = {} 297 | }, 298 | }, 299 | config = function(_, opts) 300 | local capabilities = require("cmp_nvim_lsp").default_capabilities() 301 | 302 | for server, config in pairs(opts.servers) do 303 | vim.lsp.config(server, config) 304 | end 305 | 306 | require("mason").setup() 307 | require("mason-lspconfig").setup({ 308 | automatic_enable = true, 309 | ensure_installed = vim.tbl_keys(opts.servers), 310 | automatic_installation = true, 311 | handlers = { 312 | function(server) 313 | local options = opts.servers[server] or {} 314 | options.capabilities = capabilities 315 | vim.lsp.config(server, options) 316 | end, 317 | }, 318 | }) 319 | 320 | vim.diagnostic.config(opts.diagnostics) 321 | 322 | -- vim.lsp.handlers["textDocument/publishDiagnostics"] = 323 | -- vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { 324 | -- underline = true, 325 | -- virtual_text = false, 326 | -- signs = true, 327 | -- update_in_insert = false, 328 | -- }) 329 | end, 330 | }, 331 | { 332 | "hrsh7th/nvim-cmp", 333 | dependencies = { 334 | "hrsh7th/cmp-nvim-lsp", 335 | "hrsh7th/cmp-buffer", 336 | "hrsh7th/cmp-path", 337 | "hrsh7th/cmp-cmdline", 338 | "hrsh7th/nvim-cmp", 339 | }, 340 | config = function() 341 | local cmp = require("cmp") 342 | 343 | local has_copilot_suggestion = function() 344 | local suggestion = vim.fn["copilot#GetDisplayedSuggestion"]() 345 | return suggestion.item ~= nil and not vim.tbl_isempty(suggestion.item) 346 | end 347 | 348 | -- has its own version of completions, not super polished but go with it 349 | cmp.setup.filetype("copilot-chat", { 350 | enabled = false, 351 | sources = {}, 352 | mapping = {}, 353 | }) 354 | 355 | cmp.setup({ 356 | completion = { 357 | autocomplete = false, 358 | }, 359 | mapping = { 360 | [""] = cmp.mapping({ 361 | i = function(fallback) 362 | local col = vim.fn.col(".") - 1 363 | local line = vim.api.nvim_get_current_line() 364 | local char_before = col > 0 and line:sub(col, col) 365 | local trigger_chars = char_before and string.match(char_before, "[%a%.<]") 366 | 367 | if cmp.visible() then 368 | cmp.select_next_item() 369 | elseif has_copilot_suggestion() then 370 | fallback() 371 | elseif trigger_chars then 372 | cmp.complete() 373 | else 374 | fallback() 375 | end 376 | end, 377 | }), 378 | [""] = cmp.mapping({ 379 | i = function() 380 | -- Ctrl Tab is very annoying and flaky to map 381 | -- going with ctrl-i instead for the override 382 | if cmp.visible() then 383 | cmp.select_next_item() 384 | else 385 | cmp.complete() 386 | end 387 | end, 388 | }), 389 | [""] = cmp.mapping({ 390 | i = function(fallback) 391 | if cmp.visible() then 392 | cmp.select_prev_item() 393 | else 394 | fallback() 395 | end 396 | end, 397 | }), 398 | [""] = cmp.mapping({ 399 | i = function(fallback) 400 | if cmp.visible() then 401 | cmp.select_next_item() 402 | else 403 | fallback() 404 | end 405 | end, 406 | }), 407 | [""] = cmp.mapping({ 408 | i = function(fallback) 409 | if cmp.visible() then 410 | cmp.select_prev_item() 411 | else 412 | fallback() 413 | end 414 | end, 415 | }), 416 | [""] = cmp.mapping.confirm({ select = true }), 417 | }, 418 | sources = { 419 | { name = "nvim_lsp" }, 420 | { name = "buffer" }, 421 | { name = "path" }, 422 | { name = "cmdline" }, 423 | }, 424 | }) 425 | end, 426 | }, 427 | { 428 | "MeanderingProgrammer/render-markdown.nvim", 429 | dependencies = { "nvim-treesitter/nvim-treesitter", "nvim-tree/nvim-web-devicons" }, -- if you prefer nvim-web-devicons 430 | ---@module 'render-markdown' 431 | ---@type render.md.UserConfig 432 | ft = { "markdown", "Avante", "codecompanion" }, 433 | }, 434 | { 435 | "stevearc/conform.nvim", 436 | event = { "BufWritePre" }, 437 | cmd = { "ConformInfo" }, 438 | keys = { 439 | { 440 | "b", 441 | function() 442 | require("conform").format({ async = true }) 443 | end, 444 | desc = "Format buffer", 445 | }, 446 | }, 447 | init = function() 448 | vim.o.formatexpr = "v:lua.require'conform'.formatexpr()" 449 | 450 | local util = require("conform.util") 451 | 452 | local function create_stree_formatter() 453 | -- Check if Gemfile.lock exists and contains syntax_tree using grep 454 | local has_stree_in_bundle = function() 455 | local gemfile_lock = vim.fn.findfile("Gemfile.lock", ".;") 456 | if gemfile_lock ~= "" then 457 | vim.fn.system("grep -q syntax_tree " .. vim.fn.shellescape(gemfile_lock)) 458 | return vim.v.shell_error == 0 459 | end 460 | return false 461 | end 462 | 463 | return { 464 | command = function() 465 | if has_stree_in_bundle() then 466 | return "bundle" 467 | else 468 | return "stree" 469 | end 470 | end, 471 | args = function() 472 | if has_stree_in_bundle() then 473 | return { "exec", "stree", "write", "$FILENAME" } 474 | else 475 | return { "write", "$FILENAME" } 476 | end 477 | end, 478 | stdin = false, 479 | cwd = util.root_file({ ".streerc" }), 480 | } 481 | end 482 | 483 | -- eslint_d is simply a daemon that actually runs eslint 484 | -- from current directory 485 | -- it is required cause eslint has no fix to console 486 | require("conform").setup({ 487 | formatters_by_ft = { 488 | lua = { "stylua" }, 489 | python = { "isort", "black" }, 490 | javascript = { "eslint_d", "prettier" }, 491 | ruby = { "syntax_tree" }, 492 | handlebars = { "prettier" }, 493 | hbs = { "prettier" }, 494 | css = { "prettier", "stylelint" }, 495 | scss = { "prettier", "stylelint" }, 496 | go = { "goimports", "gofmt" }, 497 | }, 498 | default_format_opts = { 499 | lsp_format = "fallback", 500 | }, 501 | format_after_save = { 502 | lsp_format = "fallback", 503 | }, 504 | formatters = { 505 | shfmt = { 506 | prepend_args = { "-i", "2" }, 507 | }, 508 | syntax_tree = create_stree_formatter(), 509 | }, 510 | }) 511 | end, 512 | }, 513 | { 514 | "vim-ruby/vim-ruby", 515 | ft = "ruby", 516 | }, 517 | { 518 | "tpope/vim-rails", 519 | ft = { "ruby", "eruby", "haml", "slim" }, 520 | config = function() 521 | -- disable autocmd set filetype=eruby.yaml, this breaks syntax highlighting 522 | vim.api.nvim_create_autocmd({ "BufNewFile", "BufReadPost" }, { 523 | pattern = { "*.yml" }, 524 | callback = function() 525 | vim.bo.filetype = "yaml" 526 | end, 527 | }) 528 | end, 529 | }, 530 | { 531 | "tpope/vim-endwise", 532 | event = "InsertEnter", 533 | }, 534 | { 535 | "tpope/vim-commentary", 536 | event = { "BufReadPre", "BufNewFile" }, 537 | }, 538 | { 539 | "tpope/vim-surround", 540 | event = { "BufReadPre", "BufNewFile" }, 541 | }, 542 | -- { 543 | -- "nvim-neotest/neotest", 544 | -- --- commit = "52fca6717ef972113ddd6ca223e30ad0abb2800c", 545 | -- lazy = true, 546 | -- dependencies = { 547 | -- "olimorris/neotest-rspec", 548 | -- "nvim-treesitter/nvim-treesitter", 549 | -- "nvim-neotest/nvim-nio", 550 | -- "nvim-lua/plenary.nvim", 551 | -- "mfussenegger/nvim-dap", 552 | -- "antoinemadec/FixCursorHold.nvim", 553 | -- }, 554 | -- -- stylua: ignore 555 | -- keys = { 556 | -- { "t", "", desc = "+test" }, 557 | -- { "tt", function() require("neotest").run.run(vim.fn.expand("%")) end, desc = "Run File (Neotest)" }, 558 | -- { "tT", function() require("neotest").run.run(vim.fn.getcwd()) end, desc = "Run All Test Files (Neotest)" }, 559 | -- { "tr", function() require("neotest").run.run() end, desc = "Run Nearest (Neotest)" }, 560 | -- { "tl", function() require("neotest").run.run_last() end, desc = "Run Last (Neotest)" }, 561 | -- { "ts", function() require("neotest").summary.toggle() end, desc = "Toggle Summary (Neotest)" }, 562 | -- { "to", function() require("neotest").output.open({ enter = true, auto_close = true }) end, desc = "Show Output (Neotest)" }, 563 | -- { "tO", function() require("neotest").output_panel.toggle() end, desc = "Toggle Output Panel (Neotest)" }, 564 | -- { "tS", function() require("neotest").run.stop() end, desc = "Stop (Neotest)" }, 565 | -- { "tw", function() require("neotest").watch.toggle(vim.fn.expand("%")) end, desc = "Toggle Watch (Neotest)" }, 566 | -- }, 567 | -- config = function() 568 | -- local neotest_rspec = require("neotest-rspec") 569 | -- require("neotest").setup({ 570 | -- adapters = { 571 | -- neotest_rspec({ 572 | -- rspec_cmd = "/tmp/d-rspec", 573 | -- }), 574 | -- }, 575 | -- output_panel = { 576 | -- open = "botright vsplit | vertical resize 80", 577 | -- }, 578 | -- }) 579 | -- ensure_d_rspec() 580 | -- end, 581 | -- }, 582 | { 583 | "mfussenegger/nvim-dap", 584 | dependencies = { 585 | "suketa/nvim-dap-ruby", 586 | }, 587 | config = function() 588 | require("dap-ruby").setup() 589 | end, 590 | }, 591 | { 592 | "nvim-treesitter/playground", 593 | dependencies = { "nvim-treesitter/nvim-treesitter" }, 594 | }, 595 | { 596 | "nvim-treesitter/nvim-treesitter", 597 | version = false, -- last release is way too old and doesn't work on Windows 598 | build = ":TSUpdate", 599 | lazy = vim.fn.argc(-1) == 0, -- load treesitter early when opening a file from the cmdline 600 | cmd = { "TSUpdateSync", "TSUpdate", "TSInstall" }, 601 | keys = { 602 | { "", desc = "Increment Selection" }, 603 | { "", desc = "Decrement Selection", mode = "x" }, 604 | }, 605 | config = function() 606 | local opts = { 607 | highlight = { enable = true }, 608 | indent = { enable = true }, 609 | ensure_installed = { 610 | "bash", 611 | "c", 612 | "diff", 613 | "html", 614 | "javascript", 615 | "glimmer", 616 | "jsdoc", 617 | "json", 618 | "jsonc", 619 | "lua", 620 | "luadoc", 621 | "luap", 622 | "markdown", 623 | "markdown_inline", 624 | "printf", 625 | "python", 626 | "query", 627 | "regex", 628 | "toml", 629 | "tsx", 630 | "typescript", 631 | "vim", 632 | "vimdoc", 633 | "xml", 634 | "yaml", 635 | "ruby", 636 | }, 637 | incremental_selection = { 638 | enable = true, 639 | keymaps = { 640 | init_selection = "", 641 | node_incremental = "", 642 | scope_incremental = false, 643 | node_decremental = "", 644 | }, 645 | }, 646 | textobjects = { 647 | move = { 648 | enable = true, 649 | goto_next_start = { 650 | ["]f"] = "@function.outer", 651 | ["]c"] = "@class.outer", 652 | ["]a"] = "@parameter.inner", 653 | }, 654 | goto_next_end = { 655 | ["]F"] = "@function.outer", 656 | ["]C"] = "@class.outer", 657 | ["]A"] = "@parameter.inner", 658 | }, 659 | goto_previous_start = { 660 | ["[f"] = "@function.outer", 661 | ["[c"] = "@class.outer", 662 | ["[a"] = "@parameter.inner", 663 | }, 664 | goto_previous_end = { 665 | ["[F"] = "@function.outer", 666 | ["[C"] = "@class.outer", 667 | ["[A"] = "@parameter.inner", 668 | }, 669 | }, 670 | }, 671 | } 672 | require("nvim-treesitter.configs").setup(opts) 673 | end, 674 | }, 675 | -- Automatically add closing tags for HTML and JSX 676 | { 677 | "windwp/nvim-ts-autotag", 678 | opts = {}, 679 | }, 680 | { 681 | "Rawnly/gist.nvim", 682 | cmd = { "GistCreate", "GistCreateFromFile", "GistsList" }, 683 | config = true, 684 | }, 685 | -- `GistsList` opens the selected gif in a terminal buffer, 686 | -- nvim-unception uses neovim remote rpc functionality to open the gist in an actual buffer 687 | -- and prevents neovim buffer inception 688 | { 689 | "samjwill/nvim-unception", 690 | lazy = false, 691 | init = function() 692 | vim.g.unception_block_while_host_edits = true 693 | end, 694 | }, 695 | } 696 | --------------------------------------------------------------------------------