Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84b66dea7c |
@@ -1,6 +0,0 @@
|
||||
# The claude-sandbox build context is this whole repo (so it can COPY
|
||||
# bin/gitea-pr into the image), but it only needs a couple of paths from it.
|
||||
**
|
||||
!bin/gitea-pr
|
||||
!docker/claude-sandbox/**
|
||||
!config/nvim/**
|
||||
@@ -1,15 +1,7 @@
|
||||
existing
|
||||
minimal
|
||||
install_local.sh
|
||||
bin/dotfiles-install
|
||||
config/i3/config.de
|
||||
config/i3/config.local
|
||||
config/gtk-3.0/bookmarks
|
||||
config/xfce4/xfconf/xfce-perchannel-xml
|
||||
config/xfce4/terminal
|
||||
config/themes
|
||||
config/icons
|
||||
config/VSCodium
|
||||
dependencies/dotfiles-local
|
||||
packages/*.local
|
||||
.claude
|
||||
|
||||
@@ -16,9 +16,3 @@
|
||||
[submodule "dependencies/gruvbox"]
|
||||
path = dependencies/gruvbox
|
||||
url = https://github.com/morhetz/gruvbox
|
||||
[submodule "config/tmux/plugins/tpm"]
|
||||
path = config/tmux/plugins/tpm
|
||||
url = https://github.com/tmux-plugins/tpm
|
||||
[submodule "dependencies/fzf"]
|
||||
path = dependencies/fzf
|
||||
url = https://github.com/junegunn/fzf.git
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Update all locally existing PGP keys in pacman's gnupg keyring, that are
|
||||
# relevant for Arch Linux packaging using the distribution's Web Key Directory
|
||||
# (WKD).
|
||||
# This ensures, that new signatures on already existing keys are fetched before
|
||||
# a new version of archlinux-keyring is installed. Fetching signatures early
|
||||
# prevents marginal trust issues with packages that are signed by keys which
|
||||
# only gain full trust when updating to a new version of archlinux-keyring in
|
||||
# that same system upgrade action.
|
||||
|
||||
set -eu
|
||||
|
||||
readonly main_key_domain_match="@master-key.archlinux.org$"
|
||||
readonly packager_domain_match="@archlinux.org$"
|
||||
readonly homedir="$(pacman-conf GPGDir)"
|
||||
|
||||
domain_match=""
|
||||
uid=""
|
||||
gpg_locate_external=(
|
||||
# force update a key using WKD
|
||||
gpg
|
||||
--homedir
|
||||
"$homedir"
|
||||
--quiet
|
||||
--no-permission-warning
|
||||
--auto-key-locate
|
||||
"clear,nodefault,wkd"
|
||||
--locate-external-keys
|
||||
)
|
||||
# a list of <fingerprint> <mbox> tuples of all keys in the keyring
|
||||
# e.g.:
|
||||
# C7E7849466FE2358343588377258734B41C31549 dvzrv@archlinux.org
|
||||
# 8FC15A064950A99DD1BD14DD39E4B877E62EB915 svenstaro@gmail.com
|
||||
fingerprint_mboxes="$(
|
||||
gpg --homedir "$homedir" --no-permission-warning --list-keys --list-options show-only-fpr-mbox
|
||||
)"
|
||||
|
||||
# a list of <fingerprints> of all revoked keys and keys that have no valid main
|
||||
# key signatures
|
||||
old_fingerprints="$(
|
||||
gpg --homedir "$homedir" --no-permission-warning --list-keys --with-colons |
|
||||
awk -F: '$1 == "pub" && $2 ~ /-|q|r/ { getline; print $10 }'
|
||||
)"
|
||||
|
||||
if (( EUID != 0 )); then
|
||||
printf "This script must be run as root.\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# first update the main signing keys, then the packager keys
|
||||
for domain_match in "$main_key_domain_match" "$packager_domain_match"; do
|
||||
while read -ra fpr_email; do
|
||||
if [[ ${fpr_email[1]} =~ $domain_match && ! "$old_fingerprints" =~ ${fpr_email[0]} ]]; then
|
||||
printf "Refreshing key %s with UID %s...\n" "${fpr_email[0]}" "${fpr_email[1]}"
|
||||
"${gpg_locate_external[@]}" "${fpr_email[1]}"
|
||||
else
|
||||
printf "Skipping key %s with UID %s...\n" "${fpr_email[0]}" "${fpr_email[1]}"
|
||||
fi
|
||||
done <<< "$fingerprint_mboxes"
|
||||
done
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Launch nvim inside the claude-sandbox container (see docker/claude-sandbox/),
|
||||
# with a project directory bind-mounted at its real host path. Any `claude`
|
||||
# invocation inside (a shell, or claudecode.nvim's terminal) runs with
|
||||
# --dangerously-skip-permissions.
|
||||
#
|
||||
# Usage: claude-sandbox [path-to-project] [-- nvim args...]
|
||||
# claude-sandbox # sandbox the current directory
|
||||
# claude-sandbox ~/repos/myproject
|
||||
# claude-sandbox ~/repos/myproject src/foo.py # open a specific file
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COMPOSE_FILE="$SCRIPT_DIR/../docker/claude-sandbox/docker-compose.yml"
|
||||
|
||||
WORKSPACE_DIR="${1:-$(pwd)}"
|
||||
if [ ! -d "$WORKSPACE_DIR" ]; then
|
||||
echo "Not a directory: $WORKSPACE_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
shift || true
|
||||
|
||||
if [ "${1:-}" = "--" ]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
WORKSPACE_DIR="$(cd "$WORKSPACE_DIR" && pwd)"
|
||||
|
||||
SANDBOX_SSH_KEY="${SANDBOX_SSH_KEY:-}"
|
||||
if [ -n "$SANDBOX_SSH_KEY" ] && [ ! -f "$SANDBOX_SSH_KEY" ]; then
|
||||
echo "SANDBOX_SSH_KEY is set but not a file: $SANDBOX_SSH_KEY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$SANDBOX_SSH_KEY" ]; then
|
||||
key_perms=$(stat -c '%a' "$SANDBOX_SSH_KEY")
|
||||
if [ "${key_perms: -2}" != "00" ]; then
|
||||
echo "Warning: $SANDBOX_SSH_KEY is readable by group/other (mode $key_perms)." >&2
|
||||
echo "Recommended: chmod 600 $SANDBOX_SSH_KEY" >&2
|
||||
fi
|
||||
# SANDBOX_SSH_KEY is meant to be a dedicated, low-privilege deploy key
|
||||
# (see README "Headless git push"), not your real identity - it stays
|
||||
# loaded in an in-container agent for the whole session, unattended.
|
||||
# Nothing enforces that convention, so at least catch the common case
|
||||
# of pointing it at a default personal keyfile by mistake.
|
||||
case "$(basename -- "$SANDBOX_SSH_KEY")" in
|
||||
id_rsa|id_ecdsa|id_ed25519|id_dsa|id_ecdsa_sk|id_ed25519_sk)
|
||||
echo "Warning: $SANDBOX_SSH_KEY looks like a default personal SSH key," \
|
||||
"not a dedicated one." >&2
|
||||
echo "It will be loaded into the sandbox for the whole unattended session -" \
|
||||
"see README 'Headless git push'." >&2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
have_forwarded_agent=1
|
||||
if [ -z "${SSH_AUTH_SOCK:-}" ] || [ ! -S "${SSH_AUTH_SOCK}" ]; then
|
||||
have_forwarded_agent=0
|
||||
fi
|
||||
|
||||
if [ "$have_forwarded_agent" = 0 ] && [ -z "$SANDBOX_SSH_KEY" ]; then
|
||||
echo "SSH_AUTH_SOCK is not set or not a valid socket, and SANDBOX_SSH_KEY is not set." >&2
|
||||
echo "Either start ssh-agent and run ssh-add, or set SANDBOX_SSH_KEY to a dedicated" >&2
|
||||
echo "key for headless use - see README 'Headless git push' section." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A valid forwarded socket with zero identities loaded forwards fine (the
|
||||
# container sees a live, working agent) but is useless for git-over-ssh -
|
||||
# warn now rather than have that surface confusingly deep into a session as
|
||||
# a failed push. Non-fatal: a task might not need git-over-ssh at all, and
|
||||
# SANDBOX_SSH_KEY (if set) provides its own identity independently of this.
|
||||
if [ "$have_forwarded_agent" = 1 ] && ! ssh-add -l >/dev/null 2>&1; then
|
||||
echo "Warning: ssh-agent has no identities loaded (ssh-add -l shows none)." >&2
|
||||
echo "git-over-ssh (e.g. pushing to Gitea) will fail until you ssh-add a key," >&2
|
||||
echo "unless SANDBOX_SSH_KEY is also set." >&2
|
||||
fi
|
||||
|
||||
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||
GITEA_TOKEN_FILE="${GITEA_TOKEN_FILE:-$HOME/.config/gitea/token}"
|
||||
if [ -f "$GITEA_TOKEN_FILE" ]; then
|
||||
GITEA_TOKEN="$(<"$GITEA_TOKEN_FILE")"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${GH_TOKEN:-}" ]; then
|
||||
# Deliberately not `gh auth token` / gh's own hosts.yml: a normal `gh
|
||||
# auth login` session is write-capable, which would defeat the point.
|
||||
# This should be a dedicated read-only fine-grained PAT (Contents +
|
||||
# Metadata: read-only), generated separately for this sandbox.
|
||||
GH_TOKEN_FILE="${GH_TOKEN_FILE:-$HOME/.config/gh/sandbox-token}"
|
||||
if [ -f "$GH_TOKEN_FILE" ]; then
|
||||
GH_TOKEN="$(<"$GH_TOKEN_FILE")"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Read-write scratch space, separate from WORKSPACE_DIR, for passing files
|
||||
# (screenshots, temp scripts) back and forth with a sandboxed session without
|
||||
# touching the actual project checkout. Persists across sessions (it's a
|
||||
# real host directory, not a container volume) since the point is sharing
|
||||
# things with the host, not just scratch space for one run.
|
||||
SANDBOX_SHARE_DIR="${SANDBOX_SHARE_DIR:-$HOME/.local/share/claude-sandbox/shared}"
|
||||
mkdir -p "$SANDBOX_SHARE_DIR"
|
||||
|
||||
# ~/.claude/CLAUDE.md (carried into the container) tells agents to write
|
||||
# specs to ~/specs/ rather than a project-local path - make sure that
|
||||
# actually exists before it's bind-mounted, for a fresh machine that hasn't
|
||||
# used the convention yet.
|
||||
mkdir -p "$HOME/specs"
|
||||
|
||||
# Claude Code's own internal per-command sandboxing (~/.claude/settings.json's
|
||||
# "sandbox" block) is redundant here - it can't even function inside this
|
||||
# container (bwrap can't create a namespace, and getting it to would need
|
||||
# privilege escalation approaching --privileged, undoing this container's own
|
||||
# hardening), and this container is already a real, working sandbox on its
|
||||
# own. settings.json is mounted read-only precisely so a session can't
|
||||
# tamper with it, so it can't be patched from inside - generate a sandbox-
|
||||
# specific copy here instead and mount that in place of the real one.
|
||||
# Everything else in it (hooks, permissions, plugins) carries over unchanged.
|
||||
SANDBOX_SETTINGS_FILE="$(mktemp "${TMPDIR:-/tmp}/claude-sandbox-settings.XXXXXX.json")"
|
||||
jq '.sandbox.enabled = false' "$HOME/.claude/settings.json" > "$SANDBOX_SETTINGS_FILE"
|
||||
cleanup() { rm -f "$SANDBOX_SETTINGS_FILE"; rm -rf "$SANDBOX_GITCONFIG_DIR"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
# Git commits inside the sandbox need a user.name/user.email - carry over a
|
||||
# copy of the host's global gitconfig, read-write: entrypoint.sh's
|
||||
# `gh auth setup-git` (what makes GitHub read-only from in here - see
|
||||
# README) needs to *write* a credential-helper entry into it.
|
||||
#
|
||||
# A whole directory, not a single file bind-mounted at ~/.gitconfig: `git
|
||||
# config` writes via the usual write-temp-then-rename-over-original pattern,
|
||||
# which fails with "Device or resource busy" on a single-file bind mount -
|
||||
# Linux won't let that mount point's backing inode be replaced, only a file
|
||||
# *within* a mounted directory can be freely renamed over. Point git at the
|
||||
# copy via GIT_CONFIG_GLOBAL instead of relying on it being at ~/.gitconfig.
|
||||
# Nothing written in here ever reaches the user's real ~/.gitconfig either
|
||||
# way, since it's a copy in its own directory, not the real file/parent.
|
||||
SANDBOX_GITCONFIG_DIR="$(mktemp -d "${TMPDIR:-/tmp}/claude-sandbox-gitconfig.XXXXXX")"
|
||||
if [ -f "$HOME/.gitconfig" ]; then
|
||||
cp "$HOME/.gitconfig" "$SANDBOX_GITCONFIG_DIR/.gitconfig"
|
||||
else
|
||||
touch "$SANDBOX_GITCONFIG_DIR/.gitconfig"
|
||||
fi
|
||||
|
||||
export WORKSPACE_DIR
|
||||
# The image's user is built to match this uid/gid/username exactly (see
|
||||
# Dockerfile) - this is what makes $HOME-based absolute paths (dotfiles,
|
||||
# ~/.claude's plugin bookkeeping) resolve identically inside the container.
|
||||
export SANDBOX_USER="$(id -un)"
|
||||
export SANDBOX_UID="$(id -u)"
|
||||
export SANDBOX_GID="$(id -g)"
|
||||
export SANDBOX_SHARE_DIR
|
||||
export SANDBOX_SETTINGS_FILE
|
||||
export SANDBOX_GITCONFIG_DIR
|
||||
export SSH_AUTH_SOCK
|
||||
export SANDBOX_SSH_KEY_FILE="$SANDBOX_SSH_KEY"
|
||||
export SSH_KNOWN_HOSTS="${SSH_KNOWN_HOSTS:-$HOME/.ssh/known_hosts}"
|
||||
export GITEA_TOKEN="${GITEA_TOKEN:-}"
|
||||
export GITEA_SSH_HOST="${GITEA_SSH_HOST:-}"
|
||||
export GITEA_API="${GITEA_API:-}"
|
||||
export GH_TOKEN="${GH_TOKEN:-}"
|
||||
export TZ="${TZ:-$(readlink -f /etc/localtime 2>/dev/null | sed 's#.*/zoneinfo/##')}"
|
||||
|
||||
# Not `exec`: the EXIT trap above needs this shell to still be around
|
||||
# afterward to actually clean up the generated settings file.
|
||||
docker compose -f "$COMPOSE_FILE" run --rm --build claude "$@"
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Add a domain to the egress firewall allowlist of a running claude-sandbox
|
||||
# container, from outside it. The sandbox itself has no privilege to do this
|
||||
# (node has no sudo/setuid path back to root - see docker/claude-sandbox's
|
||||
# security notes); if a sandboxed agent hits a blocked domain it actually
|
||||
# needs, it should tell you the exact command to run here rather than try
|
||||
# to work around the firewall.
|
||||
#
|
||||
# Usage: claude-sandbox-allow <domain> [--global] [--container <name>]
|
||||
# claude-sandbox-allow crates.io # this running session only
|
||||
# claude-sandbox-allow crates.io --global # + persist for future sessions
|
||||
set -euo pipefail
|
||||
|
||||
domain=""
|
||||
global=0
|
||||
container=""
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--global) global=1; shift ;;
|
||||
--container) container="$2"; shift 2 ;;
|
||||
-*) echo "Unknown option: $1" >&2; exit 1 ;;
|
||||
*)
|
||||
if [ -n "$domain" ]; then
|
||||
echo "Usage: claude-sandbox-allow <domain> [--global] [--container <name>]" >&2
|
||||
exit 1
|
||||
fi
|
||||
domain="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$domain" ]; then
|
||||
echo "Usage: claude-sandbox-allow <domain> [--global] [--container <name>]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validated up front, not just passed as a positional arg below: this also
|
||||
# has to be a well-formed hostname to be a valid dnsmasq server=/domain/...
|
||||
# line (e.g. no embedded "/"), on top of ruling out shell metacharacters. A
|
||||
# sandboxed agent can suggest this exact command to the user to copy-paste
|
||||
# (see sandbox-context.sh), so a prompt-injected agent handing back a
|
||||
# malicious "domain" is a real path here, not just a theoretical one.
|
||||
if [[ ! "$domain" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,62}\.)*[a-zA-Z0-9-]{1,63}$ ]]; then
|
||||
echo "Not a valid domain: $domain" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$container" ]; then
|
||||
containers=$(docker ps --filter "label=com.docker.compose.project=claude-sandbox" --filter "status=running" --format '{{.Names}}')
|
||||
count=$(echo "$containers" | grep -c . || true)
|
||||
if [ "$count" -eq 0 ]; then
|
||||
echo "No running claude-sandbox container found." >&2
|
||||
exit 1
|
||||
elif [ "$count" -gt 1 ]; then
|
||||
echo "Multiple running claude-sandbox containers found; specify one with --container:" >&2
|
||||
echo "$containers" >&2
|
||||
exit 1
|
||||
fi
|
||||
container="$containers"
|
||||
fi
|
||||
|
||||
# The DNS proxy (dns-proxy.sh) NXDOMAINs anything not in its allowlist, so
|
||||
# this has to happen *before* resolving the domain below - otherwise the dig
|
||||
# right after this would itself get blocked. Append a server= line to
|
||||
# dnsmasq's servers-file (idempotent - skip if already present from an
|
||||
# earlier call) and SIGHUP it to reload just that file, no restart needed.
|
||||
# $domain is passed as a positional arg to the inner script (`$1`, inside a
|
||||
# single-quoted heredoc) rather than interpolated into the script text - it's
|
||||
# validated above, but this keeps it that way even if the validation regex
|
||||
# above is ever loosened later.
|
||||
docker exec -u root "$container" bash -c '
|
||||
domain="$1"
|
||||
entry="server=/${domain}/127.0.0.11"
|
||||
grep -qxF "$entry" /etc/dnsmasq-sandbox-servers.conf 2>/dev/null || echo "$entry" >> /etc/dnsmasq-sandbox-servers.conf
|
||||
kill -HUP "$(cat /var/run/dnsmasq-sandbox.pid)"
|
||||
' -- "$domain"
|
||||
|
||||
echo "Resolving $domain inside $container..."
|
||||
ips=$(docker exec "$container" dig +short A "$domain" | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' || true)
|
||||
if [ -z "$ips" ]; then
|
||||
echo "Failed to resolve $domain inside the container." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for ip in $ips; do
|
||||
echo "Allowing $ip ($domain)"
|
||||
docker exec -u root "$container" ipset add allowed-domains "$ip" -exist
|
||||
done
|
||||
|
||||
echo "$domain allowed for the running session ($container)."
|
||||
|
||||
if [ "$global" -eq 1 ]; then
|
||||
profile_file="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/config/profile"
|
||||
if [ -f "$profile_file" ] && grep -q '^export ALLOWED_DOMAINS=' "$profile_file"; then
|
||||
current=$(sed -nE 's/^export ALLOWED_DOMAINS="(.*)"$/\1/p' "$profile_file")
|
||||
if [[ " $current " == *" $domain "* ]]; then
|
||||
echo "$domain already in the global ALLOWED_DOMAINS (config/profile)."
|
||||
else
|
||||
new="${current:+$current }$domain"
|
||||
sed -i "s|^export ALLOWED_DOMAINS=.*|export ALLOWED_DOMAINS=\"$new\"|" "$profile_file"
|
||||
echo "Added $domain to config/profile's ALLOWED_DOMAINS (source your shell, or open a new one, for future sessions to pick it up)."
|
||||
fi
|
||||
else
|
||||
echo "Couldn't find 'export ALLOWED_DOMAINS=' in $profile_file to update." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Copy stdin to the system clipboard, regardless of platform or how deep in
|
||||
# ssh/tmux this shell is: prefer a local clipboard tool for the current
|
||||
# display server, and fall back to a single plain OSC 52 write when no local
|
||||
# tool is available (e.g. a headless remote host). Any tmux hops in between
|
||||
# here and the real terminal relay that onward themselves, recursively, via
|
||||
# the pane-set-clipboard hook in tmux.conf -- this doesn't need to know or
|
||||
# guess how many hops there are.
|
||||
if command -v pbcopy >/dev/null 2>&1; then
|
||||
exec pbcopy
|
||||
elif [ -n "$WAYLAND_DISPLAY" ] && command -v wl-copy >/dev/null 2>&1; then
|
||||
# wl-copy/xclip both hold the selection by staying alive as a background
|
||||
# process (X11/Wayland selections have no central store -- the owner has
|
||||
# to serve paste requests live). setsid -f detaches that process from
|
||||
# our session so tmux's copy-pipe job cleanup can't kill it right after.
|
||||
setsid -f wl-copy
|
||||
elif [ -n "$DISPLAY" ] && command -v xclip >/dev/null 2>&1; then
|
||||
setsid -f xclip -selection clipboard
|
||||
else
|
||||
data=$(base64 | tr -d '\n')
|
||||
# /dev/tty needs this process's own session to have a controlling
|
||||
# terminal, but a job spawned by nvim's clipboard provider doesn't get
|
||||
# one (nvim's job-spawning doesn't establish a ctty for children). Nvim
|
||||
# itself also isn't a single process: the one actually spawning jobs is
|
||||
# a `nvim --embed` core process with no tty of its own (shown as `?` in
|
||||
# ps) -- the real terminal belongs to a separate parent UI process. Walk
|
||||
# up the process tree past any tty-less ancestors to find it.
|
||||
real_tty=""
|
||||
pid="$PPID"
|
||||
i=0
|
||||
while [ "$i" -lt 10 ] && [ -n "$pid" ] && [ "$pid" != "0" ]; do
|
||||
t=$(ps -o tty= -p "$pid" 2>/dev/null | tr -d ' ')
|
||||
if [ -n "$t" ] && [ "$t" != "?" ]; then
|
||||
real_tty="/dev/$t"
|
||||
break
|
||||
fi
|
||||
pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')
|
||||
i=$((i + 1))
|
||||
done
|
||||
if [ -n "$real_tty" ] && [ -w "$real_tty" ]; then
|
||||
printf '\033]52;c;%s\a' "$data" > "$real_tty"
|
||||
else
|
||||
printf '\033]52;c;%s\a' "$data" > /dev/tty
|
||||
fi
|
||||
fi
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker compose pull
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
find . -type f -name "docker-compose.y*ml" -exec dirname {} \; | while read dir
|
||||
do
|
||||
if [[ "$dir" != *"DEPRECATED"* ]] && [[ "$dir" != *"MIGRATED"* ]] ; then
|
||||
pushd "$dir"
|
||||
docker compose down
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
popd
|
||||
fi
|
||||
done
|
||||
|
||||
-209
@@ -1,209 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
GITEA_TOKEN_FILE="${GITEA_TOKEN_FILE:-$HOME/.config/gitea/token}"
|
||||
|
||||
function usage() {
|
||||
echo "Usage: gitea-pr create [--title TITLE] [--body BODY] [--base BRANCH]"
|
||||
echo " gitea-pr comments PR_NUMBER"
|
||||
echo " gitea-pr merge PR_NUMBER"
|
||||
echo ""
|
||||
echo "Requires GITEA_SSH_HOST and GITEA_API set in the environment (e.g. in" \
|
||||
"your shell profile), and a token at $GITEA_TOKEN_FILE (or GITEA_TOKEN" \
|
||||
"set for this invocation only)."
|
||||
}
|
||||
|
||||
function require_config() {
|
||||
if [ -z "${GITEA_SSH_HOST:-}" ]; then
|
||||
echo "GITEA_SSH_HOST is not set. Add 'export GITEA_SSH_HOST=<gitea ssh hostname>'" \
|
||||
"(e.g. gitbox.bradina.localdomain) to ~/.profile.local" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${GITEA_API:-}" ]; then
|
||||
echo "GITEA_API is not set. Add 'export GITEA_API=https://<gitea host>/api/v1'" \
|
||||
"(e.g. https://gitbox.branderson.io/api/v1) to ~/.profile.local" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function require_token() {
|
||||
if [ -z "${GITEA_TOKEN:-}" ] && [ -f "$GITEA_TOKEN_FILE" ]; then
|
||||
GITEA_TOKEN=$(<"$GITEA_TOKEN_FILE")
|
||||
fi
|
||||
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||
echo "No Gitea token found. Generate one in the Gitea web UI" \
|
||||
"(Settings > Applications, scopes write:repository + write:issue)" \
|
||||
"and save it to $GITEA_TOKEN_FILE (chmod 600), or set GITEA_TOKEN" \
|
||||
"in the environment for this invocation only." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function gitea_owner_repo() {
|
||||
local remote_url host owner_repo
|
||||
remote_url=$(git remote get-url origin 2>/dev/null) || {
|
||||
echo "No 'origin' remote found in this repository" >&2
|
||||
exit 1
|
||||
}
|
||||
host=$(echo "$remote_url" | sed -E 's#^[^@]+@([^:]+):.*#\1#')
|
||||
if [ "$host" != "$GITEA_SSH_HOST" ]; then
|
||||
echo "origin remote ($remote_url) is not on $GITEA_SSH_HOST; gitea-pr only supports this host" >&2
|
||||
exit 1
|
||||
fi
|
||||
owner_repo=$(echo "$remote_url" | sed -E 's#^[^@]+@[^:]+:##; s#\.git$##')
|
||||
echo "$owner_repo"
|
||||
}
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
subcommand="$1"
|
||||
shift
|
||||
|
||||
case "$subcommand" in
|
||||
create|comments|merge)
|
||||
require_config
|
||||
require_token
|
||||
owner_repo=$(gitea_owner_repo)
|
||||
owner="${owner_repo%%/*}"
|
||||
repo="${owner_repo##*/}"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$subcommand" in
|
||||
create)
|
||||
title=""
|
||||
body=""
|
||||
base=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--title) title="$2"; shift 2 ;;
|
||||
--body) body="$2"; shift 2 ;;
|
||||
--base) base="$2"; shift 2 ;;
|
||||
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
body="**Summary generated by LLM agent**
|
||||
|
||||
${body}"
|
||||
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
if [ "$current_branch" = "HEAD" ]; then
|
||||
echo "Detached HEAD: check out the feature branch before creating a PR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$base" ]; then
|
||||
base=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's#refs/remotes/origin/##' || true)
|
||||
base="${base:-main}"
|
||||
fi
|
||||
|
||||
response=$(curl -sS -w '\n%{http_code}' "${GITEA_API}/repos/${owner}/${repo}/pulls?state=open" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}")
|
||||
http_code=$(echo "$response" | tail -1)
|
||||
json=$(echo "$response" | sed '$d')
|
||||
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
|
||||
echo "Failed to check for an existing PR (HTTP $http_code):" >&2
|
||||
echo "$json" >&2
|
||||
exit 1
|
||||
fi
|
||||
existing_url=$(echo "$json" | jq -r --arg head "$current_branch" --arg base "$base" \
|
||||
'.[] | select(.head.ref == $head and .base.ref == $base) | .html_url' | head -1)
|
||||
if [ -n "$existing_url" ]; then
|
||||
echo "$existing_url"
|
||||
exit 0
|
||||
fi
|
||||
if [ -z "$title" ]; then
|
||||
title="$current_branch"
|
||||
fi
|
||||
|
||||
response=$(curl -sS -w '\n%{http_code}' -X POST "${GITEA_API}/repos/${owner}/${repo}/pulls" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg head "$current_branch" --arg base "$base" --arg title "$title" --arg body "$body" \
|
||||
'{head: $head, base: $base, title: $title, body: $body}')")
|
||||
http_code=$(echo "$response" | tail -1)
|
||||
json=$(echo "$response" | sed '$d')
|
||||
|
||||
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
|
||||
echo "Failed to create PR (HTTP $http_code):" >&2
|
||||
echo "$json" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$json" | jq -r '.html_url'
|
||||
;;
|
||||
comments)
|
||||
pr_number="${1:-}"
|
||||
if [ -z "$pr_number" ]; then
|
||||
echo "Usage: gitea-pr comments PR_NUMBER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Issue comments ==="
|
||||
response=$(curl -sS -w '\n%{http_code}' "${GITEA_API}/repos/${owner}/${repo}/issues/${pr_number}/comments" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}")
|
||||
http_code=$(echo "$response" | tail -1)
|
||||
json=$(echo "$response" | sed '$d')
|
||||
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
|
||||
echo "Failed to fetch issue comments (HTTP $http_code):" >&2
|
||||
echo "$json" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$json" | jq -r '.[] | "[\(.user.login) @ \(.created_at)]\n\(.body)\n"'
|
||||
|
||||
echo "=== Reviews ==="
|
||||
response=$(curl -sS -w '\n%{http_code}' "${GITEA_API}/repos/${owner}/${repo}/pulls/${pr_number}/reviews" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}")
|
||||
http_code=$(echo "$response" | tail -1)
|
||||
reviews_json=$(echo "$response" | sed '$d')
|
||||
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
|
||||
echo "Failed to fetch reviews (HTTP $http_code):" >&2
|
||||
echo "$reviews_json" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$reviews_json" | jq -r '.[] | select(.body != "") | "[\(.user.login) @ \(.submitted_at) — \(.state)]\n\(.body)\n"'
|
||||
|
||||
echo "=== Inline review comments ==="
|
||||
review_ids=$(echo "$reviews_json" | jq -r '.[] | select(.comments_count > 0) | .id')
|
||||
for review_id in $review_ids; do
|
||||
response=$(curl -sS -w '\n%{http_code}' \
|
||||
"${GITEA_API}/repos/${owner}/${repo}/pulls/${pr_number}/reviews/${review_id}/comments" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}")
|
||||
http_code=$(echo "$response" | tail -1)
|
||||
json=$(echo "$response" | sed '$d')
|
||||
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
|
||||
echo "Failed to fetch comments for review ${review_id} (HTTP $http_code):" >&2
|
||||
echo "$json" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$json" | jq -r '.[] | "[\(.user.login) @ \(.path):\(.line // "?")]\n\(.body)\n"'
|
||||
done
|
||||
;;
|
||||
merge)
|
||||
pr_number="${1:-}"
|
||||
if [ -z "$pr_number" ]; then
|
||||
echo "Usage: gitea-pr merge PR_NUMBER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
response=$(curl -sS -w '\n%{http_code}' -X POST "${GITEA_API}/repos/${owner}/${repo}/pulls/${pr_number}/merge" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"Do": "merge"}')
|
||||
http_code=$(echo "$response" | tail -1)
|
||||
json=$(echo "$response" | sed '$d')
|
||||
|
||||
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
|
||||
echo "Failed to merge PR #${pr_number} (HTTP $http_code):" >&2
|
||||
echo "$json" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Merged PR #${pr_number}"
|
||||
;;
|
||||
esac
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
function usage() {
|
||||
echo "Usage: postgres-docker-backup container_name db_user dump_file"
|
||||
}
|
||||
|
||||
if [[ ! "$#" == 3 ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
container_name="$1"
|
||||
db_user="$2"
|
||||
dump_file="$3"
|
||||
if [ -z "$container_name" ] || [ -z "$db_user" ] || [ -z "$dump_file" ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec "$container_name" '/bin/sh' -c "pg_dumpall -U $db_user" > $dump_file
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
function usage() {
|
||||
echo "Usage: postgres-docker-restore container_name db_user"
|
||||
}
|
||||
|
||||
if [[ ! "$#" == 2 ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
container_name="$1"
|
||||
db_user="$2"
|
||||
if [ -z "$container_name" ] || [ -z "$db_user" ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec -it "$container_name" '/bin/sh' -c "psql -U $db_user"
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
function usage() {
|
||||
echo "Usage: postgres-docker-restore container_name db_user dump_file"
|
||||
echo " Note that dump_file is relative to docker filesystem and must be mounted"
|
||||
}
|
||||
|
||||
if [[ ! "$#" == 3 ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
container_name="$1"
|
||||
db_user="$2"
|
||||
dump_file="$3"
|
||||
if [ -z "$container_name" ] || [ -z "$db_user" ] || [ -z "$dump_file" ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec -it "$container_name" '/bin/sh' -c "psql -U $db_user < $dump_file"
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Read clipboard content on stdin (e.g. from `tmux save-buffer -`) and write
|
||||
# it as a raw OSC 52 escape sequence directly to the given tty device path.
|
||||
# Intended to be called from tmux's pane-set-clipboard hook as:
|
||||
# tmux save-buffer - | tmux-clipboard-relay "#{client_tty}"
|
||||
tty_path="$1"
|
||||
data=$(base64 | tr -d '\n')
|
||||
printf '\033]52;c;%s\a' "$data" > "$tty_path"
|
||||
@@ -1,77 +0,0 @@
|
||||
#!/bin/bash
|
||||
# TODO: Dotfiles may be in a different place
|
||||
if [ ! -f "$HOME/.dotfiles-config" ]; then
|
||||
echo "$HOME/.dotfiles-config not found, please run dotfiles/install.sh to create it"
|
||||
exit 1
|
||||
fi
|
||||
source "$HOME/.dotfiles-config"
|
||||
source "$DOTFILES_DIR/functions.sh"
|
||||
|
||||
# XAUTHORITY file may be in /tmp or it may be ~/.XAUTHORITY
|
||||
if [ -f "$HOME/.Xauthority" ]; then
|
||||
export XAUTHORITY="$HOME/.Xauthority"
|
||||
else
|
||||
auth_path="$(find /tmp -maxdepth 1 -name "xauth_*")"
|
||||
if [ ! -z "$auth_path" ]; then
|
||||
export XAUTHORITY="$auth_path"
|
||||
else
|
||||
echo "No Xauthority file found. Exiting"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
wallpapers_path="$HOME/wallpapers"
|
||||
wallpapers_vertical_path="$HOME/wallpapers-vertical"
|
||||
|
||||
if [ ! -d "$wallpapers_path" ]; then
|
||||
echo "ERROR: Wallpaper directory not found!"
|
||||
echo "Please create wallpapers directory in $wallpapers_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if program_installed xrandr; then
|
||||
display_count="$(xrandr | grep ' connected ' | wc -l)"
|
||||
display_order="$(xrandr --listmonitors | tail -n +2 | sed 's/[\+\*]//g' | awk '{ print $2}')"
|
||||
# Build feh command
|
||||
feh_args=("--bg-fill")
|
||||
for i in $(seq 1 "$display_count"); do
|
||||
display_name="$(echo "$display_order" | sed "${i}q;d")"
|
||||
# Get information about the current display, need verbose to always return rotation
|
||||
# and remove 'primary' for consistent column placement
|
||||
display_info="$(xrandr --query --verbose | grep "$display_name" | sed 's/primary //')"
|
||||
# Get the selected resolution for the display
|
||||
resolution="$(echo "$display_info" | awk '{ print $3 }' | sed 's/\+.*//')"
|
||||
# Get the rotation for each display
|
||||
rotation="$(echo "$display_info" | awk '{ print $5 }')"
|
||||
path="$wallpapers_path"
|
||||
resolution_segment=""
|
||||
if [ "$rotation" != "normal" ] && [ "$rotation" != "inverted" ]; then
|
||||
if [ ! -d "$wallpapers_vertical_path" ]; then
|
||||
echo "$rotation"
|
||||
echo "ERROR: Vertical display detected, but vertical wallpapers directory not found!"
|
||||
echo "Please create vertical wallpapers directory in $wallpapers_vertical_path"
|
||||
exit 1
|
||||
fi
|
||||
path="$wallpapers_vertical_path"
|
||||
fi
|
||||
# If we have a resolution-specific path for the display, use it
|
||||
if [ -d "$path/1080p" ]; then
|
||||
resolution_segment="/1080p"
|
||||
fi
|
||||
if [ -d "$path/4k" ]; then
|
||||
if [ "$resolution" = "3840x2160" ] || [ "$resolution" = "2160x3840" ]; then
|
||||
resolution_segment="/4k"
|
||||
fi
|
||||
fi
|
||||
display_wallpaper="$(shuf -en1 "$path$resolution_segment"/*)"
|
||||
feh_args+=("$display_wallpaper")
|
||||
done
|
||||
echo "feh ${feh_args[@]}"
|
||||
feh "${feh_args[@]}"
|
||||
else
|
||||
# Default to ~/wallpapers/1080p or ~/wallpapers for first display if no xrandr
|
||||
if [ -d "$path/1080p" ]; then
|
||||
feh --bg-fill "$(shuf -en1 $wallpapers_path/1080p/*)"
|
||||
else
|
||||
feh --bg-fill "$(shuf -en1 $wallpapers_path/*)"
|
||||
fi
|
||||
fi
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"workbench.colorTheme": "Gruvbox Dark Medium",
|
||||
"vim.camelCaseMotion.enable": true,
|
||||
"vim.easymotion": true,
|
||||
"vim.enableNeovim": true,
|
||||
"vim.handleKeys": {
|
||||
"<C-a>": false,
|
||||
"<C-d>": true,
|
||||
"<C-f>": false,
|
||||
"<C-s>": false,
|
||||
"<C-z>": false
|
||||
},
|
||||
"vim.insertModeKeyBindingsNonRecursive": [
|
||||
{
|
||||
"before": [",", ","],
|
||||
"after": ["<Esc>"]
|
||||
}
|
||||
],
|
||||
"vim.normalModeKeyBindingsNonRecursive": [
|
||||
{
|
||||
"before": ["<leader>", "d"],
|
||||
"after": ["d", "d"]
|
||||
},
|
||||
{
|
||||
"before": ["<leader>", "d"],
|
||||
"after": ["d", "d"]
|
||||
},
|
||||
{
|
||||
"before": [",", ",", "<Esc>"],
|
||||
"commands": [":nohl"]
|
||||
},
|
||||
{
|
||||
"before": ["<leader>", "q"],
|
||||
"commands": [":bp"]
|
||||
},
|
||||
{
|
||||
"before": ["<leader>", "w"],
|
||||
"commands": [":bn"]
|
||||
},
|
||||
{
|
||||
"before": ["<leader>", "r", "r"],
|
||||
"commands": [":action "]
|
||||
},
|
||||
{
|
||||
"before": ["<leader>", "<leader>", "c"],
|
||||
"commands": [":Blose"],
|
||||
"silent": true
|
||||
}
|
||||
],
|
||||
"vim.leader": ",",
|
||||
// To improve performance
|
||||
"extensions.experimental.affinity": {
|
||||
"vscodevim.vim": 1
|
||||
},
|
||||
"vim.neovimUseConfigFile": true,
|
||||
"editor.fontFamily": "'Source Code Pro', regular",
|
||||
"vim.normalModeKeyBindings": [],
|
||||
"python.venvPath": "~/.pyenv"
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import = [
|
||||
"~/.config/alacritty/themes/themes/gruvbox_dark.toml"
|
||||
]
|
||||
|
||||
[font]
|
||||
size = 9.0
|
||||
@@ -17,30 +20,3 @@ style = "Italic"
|
||||
[font.normal]
|
||||
family = "Source Code Pro for Powerline"
|
||||
style = "Regular"
|
||||
|
||||
[general]
|
||||
import = [
|
||||
"~/.config/alacritty/themes/themes/gruvbox_dark.toml"
|
||||
]
|
||||
|
||||
[selection]
|
||||
# X11 always sets PRIMARY (middle-click paste) on text selection; that part
|
||||
# needs no config. CLIPBOARD (Ctrl+V / Ctrl+Shift+V) is a separate buffer
|
||||
# that nothing populates without this, so mirror every selection into it
|
||||
# too. This is handled by Alacritty itself, so it covers anything visible
|
||||
# on screen -- local shell, nested tmux, SSH'd remote shells -- regardless
|
||||
# of whether the app underneath knows how to talk to the clipboard.
|
||||
save_to_clipboard = true
|
||||
|
||||
[keyboard]
|
||||
# Alacritty doesn't implement the CSI-u/Kitty keyboard protocol that lets a
|
||||
# terminal report Shift+Enter as distinct from plain Enter, so it needs an
|
||||
# explicit binding. Sending a raw line feed makes Shift+Enter equivalent to
|
||||
# Ctrl+J, which Claude Code (and most other terminal apps) already treats
|
||||
# as "insert newline" with zero further configuration -- this works
|
||||
# unchanged through any depth of nested tmux, since it's just a literal
|
||||
# byte rather than an escape sequence that needs to be interpreted along
|
||||
# the way.
|
||||
bindings = [
|
||||
{ key = "Return", mods = "Shift", chars = "\n" },
|
||||
]
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=USDB Syncer
|
||||
Comment=Download karaoke songs
|
||||
Exec=usdb_syncer
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Software;
|
||||
@@ -21,13 +21,6 @@ exec --no-startup-id dex --autostart --environment i3
|
||||
# applications #
|
||||
################
|
||||
|
||||
# set wallpaper
|
||||
exec --no-startup-id sleep 1 && ~/dotfiles/bin/update-wallpaper
|
||||
# exec --no-startup-id sleep 1 && systemctl start update-wallpaper.service
|
||||
|
||||
# start keyring
|
||||
exec --no-startup-id dbus-update-activation-environment --all
|
||||
|
||||
#exec_always --no-startup-id conky -c ~/.config/conky/conkyrc-stats
|
||||
|
||||
#exec --no-startup-id sleep 7 && firefox https://todo.bradina.haus/
|
||||
|
||||
+18
-31
@@ -10,8 +10,6 @@ bar {
|
||||
font pango: Source Code Pro for Powerline 10
|
||||
status_command i3blocks -c ~/.config/i3/i3blocks.conf
|
||||
position bottom
|
||||
output DP-0
|
||||
output DP-2
|
||||
#tray_padding 0
|
||||
|
||||
# When strip_workspace_numbers is set to yes,
|
||||
@@ -39,32 +37,21 @@ bar {
|
||||
# https://gist.github.com/lopspower/03fb1cc0ac9f32ef38f4
|
||||
# #08052be6 --> e6=90%
|
||||
|
||||
bar {
|
||||
font pango: Noto Sans Regular 10
|
||||
status_command i3blocks -c ~/.config/i3/i3blocks-2.conf
|
||||
i3bar_command i3bar --transparency
|
||||
output HDMI-0
|
||||
position bottom
|
||||
|
||||
colors {
|
||||
# gruvbox colors
|
||||
background $bg
|
||||
statusline $yellow
|
||||
# workspaces section
|
||||
# border bg txt
|
||||
focused_workspace $aqua $aqua $darkgray
|
||||
inactive_workspace $darkgray $darkgray $yellow
|
||||
active_workspace $darkgray $darkgray $yellow
|
||||
urgent_workspace $red $red $bg
|
||||
}
|
||||
# colors {
|
||||
# separator $purple
|
||||
# background $darkbluetrans
|
||||
# statusline $white
|
||||
# border bg txt indicator
|
||||
# focused_workspace $lighterblue $lighterblue $darkblue $purple
|
||||
# active_workspace $lightdblue $lightdblue $darkblue $purple
|
||||
# inactive_workspace $darkblue $darkblue $lightdblue $purple
|
||||
# urgent_workspace $urgentred $urgentred $white $purple
|
||||
# }
|
||||
}
|
||||
# bar {
|
||||
# font pango: Noto Sans Regular 10
|
||||
# status_command i3blocks -c ~/.config/i3/i3blocks-2.conf
|
||||
# i3bar_command i3bar --transparency
|
||||
# output HDMI-0
|
||||
# position bottom
|
||||
#
|
||||
# colors {
|
||||
# separator $purple
|
||||
# background $darkbluetrans
|
||||
# statusline $white
|
||||
# border bg txt indicator
|
||||
# focused_workspace $lighterblue $lighterblue $darkblue $purple
|
||||
# active_workspace $lightdblue $lightdblue $darkblue $purple
|
||||
# inactive_workspace $darkblue $darkblue $lightdblue $purple
|
||||
# urgent_workspace $urgentred $urgentred $white $purple
|
||||
# }
|
||||
#}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
alias vim=nvim
|
||||
@@ -1,142 +0,0 @@
|
||||
function program_installed {
|
||||
local return_=0
|
||||
|
||||
type $1 >/dev/null 2>&1 || { local return_=1; }
|
||||
|
||||
return "$return_"
|
||||
}
|
||||
|
||||
function list_processes() {
|
||||
# List all processes matching the given process name string
|
||||
ps aux | grep $(whoami) | grep -i "$1" | grep -v grep
|
||||
}
|
||||
|
||||
function kill_all() {
|
||||
# Kill all processes matching the given process name string
|
||||
if [ ! "$1" ]; then
|
||||
echo "kill_all requires a process name to kill all instances of"
|
||||
return 1
|
||||
fi
|
||||
kill_flag=15
|
||||
if [ "$15" ]; then
|
||||
kill_flag="$15"
|
||||
fi
|
||||
|
||||
count=$(list_processes "$1" | wc -l)
|
||||
|
||||
echo "Killing $count running processes matching $1"
|
||||
kill -"$kill_flag" $(list_processes "$1" | awk '{print $2}')
|
||||
|
||||
count=$(list_processes "$1" | wc -l)
|
||||
if [ $count -gt 0 ]; then
|
||||
echo "$count processes could not be killed"
|
||||
fi
|
||||
}
|
||||
|
||||
function ranger-cd() {
|
||||
# Make shell cd to ranger directory
|
||||
tempfile=$(mktemp)
|
||||
ranger --choosedir="$tempfile" "${@:-$(pwd)}" < $TTY
|
||||
test -f "$tempfile" &&
|
||||
if [ "$(cat -- "$tempfile")" != "$(echo -n `pwd`)" ]; then
|
||||
cd $(cat $tempfile)
|
||||
fi
|
||||
zle reset-prompt
|
||||
printf '\n'
|
||||
rm -f -- "$tempfile"
|
||||
}
|
||||
|
||||
function terminal-clock() {
|
||||
tty-clock -tcb < $TTY
|
||||
zle reset-prompt
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
function check_dotfiles_version {
|
||||
# Check if the version of the dotfiles directory is the latest version
|
||||
# Return 0 if repo is present and the latest version
|
||||
# Return 1 if the repo is not present
|
||||
# Return 2 if the repo is behind the remote
|
||||
# Return 3 if the repo is ahead of the remote
|
||||
# Return 4 if the repo has diverged from the remote
|
||||
# Return 5 if DOTFILES_DIR is not set
|
||||
if [ -z $DOTFILES_DIR ]; then
|
||||
return 5
|
||||
fi
|
||||
|
||||
if [ ! -d $DOTFILES_DIR ]; then
|
||||
return 2
|
||||
fi
|
||||
|
||||
pushd $DOTFILES_DIR > /dev/null
|
||||
|
||||
# TODO: This may be gitbox
|
||||
git fetch origin > /dev/null
|
||||
UPSTREAM=${1:-'@{u}'}
|
||||
LOCAL=$(git rev-parse @)
|
||||
REMOTE=$(git rev-parse "$UPSTREAM")
|
||||
BASE=$(git merge-base @ "$UPSTREAM")
|
||||
|
||||
popd > /dev/null
|
||||
|
||||
if [[ $LOCAL = $REMOTE ]]; then
|
||||
return 0
|
||||
elif [[ $LOCAL = $BASE ]]; then
|
||||
return 2
|
||||
elif [[ $REMOTE = $BASE ]]; then
|
||||
return 3
|
||||
else
|
||||
return 4
|
||||
fi
|
||||
}
|
||||
|
||||
function python_roots {
|
||||
if [ -d $HOME/.local/lib/ ]; then
|
||||
for directory in $(echo $HOME/.local/lib/python*); do
|
||||
echo "$directory"
|
||||
done
|
||||
fi
|
||||
if [ -d $HOME/.local/share/pipx/venvs/powerline-status/lib/ ]; then
|
||||
for directory in $(echo $HOME/.local/share/pipx/venvs/powerline-status/lib/python*); do
|
||||
echo "$directory"
|
||||
done
|
||||
fi
|
||||
if [ -d $HOME/.local/pipx/venvs/powerline-status/lib/ ]; then
|
||||
for directory in $(echo $HOME/.local/pipx/venvs/powerline-status/lib/python*); do
|
||||
echo "$directory"
|
||||
done
|
||||
fi
|
||||
if [ -d /usr/lib/ ]; then
|
||||
for directory in $(echo /usr/lib/python*); do
|
||||
echo "$directory"
|
||||
done
|
||||
fi
|
||||
if [ -d /usr/local/lib/ ]; then
|
||||
# TODO: Make this not print warnings when the directory doesn't exit
|
||||
for directory in $(echo /usr/local/lib/python*); do
|
||||
echo "$directory"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
function powerline_bash_sources {
|
||||
# To support bash 3.2 on macOS, can't use declare -ag so workaround with capturing output
|
||||
PYTHON_ROOTS=( $(python_roots) )
|
||||
# Powerline plugin from distribution agnostic install directory
|
||||
## Assemble list of possible sources
|
||||
if [[ -a $POWERLINE_ROOT/bindings/bash/powerline.sh ]]; then
|
||||
# Prefer user-set powerline directory
|
||||
echo "$POWERLINE_ROOT/bindings/bash/powerline.sh"
|
||||
fi
|
||||
if [ -d /usr/share/powerline ]; then
|
||||
# Apt installs here
|
||||
echo "/usr/share/powerline/bindings/bash/powerline.sh"
|
||||
fi
|
||||
# If $POWERLINE_ROOT not set or set incorrectly, try default locations
|
||||
for directory in "${PYTHON_ROOTS[@]}"; do
|
||||
if [ -d "$directory" ]; then
|
||||
echo "$directory"/site-packages/powerline/bindings/bash/powerline.sh
|
||||
echo "$directory"/dist-packages/powerline/bindings/bash/powerline.sh
|
||||
fi
|
||||
done
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
if [ -n "$BASH_VERSION" ]; then
|
||||
# include .bashrc if it exists
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
. "$HOME/.bashrc"
|
||||
fi
|
||||
fi
|
||||
@@ -1,84 +0,0 @@
|
||||
#PATHs
|
||||
if [ -d "$HOME/.local/bin" ]; then
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.bash_functions" ]; then
|
||||
source "$HOME/.bash_functions"
|
||||
fi
|
||||
|
||||
# User profiles
|
||||
source $HOME/.profile
|
||||
# Define $POWERLINE_ROOT
|
||||
if [ -f $HOME/.profile.local ]; then
|
||||
set -a
|
||||
source $HOME/.profile.local
|
||||
set +a
|
||||
fi
|
||||
|
||||
if [ -n "$DOTFILES_DIR" ] && [ -d "$DOTFILES_DIR" ]; then
|
||||
# Gruvbox colors
|
||||
[[ -f "$DOTFILES_DIR/dependencies/gruvbox/gruvbox_256palette.sh" ]] && source "$DOTFILES_DIR/dependencies/gruvbox/gruvbox_256palette.sh"
|
||||
fi
|
||||
|
||||
# Italic support
|
||||
if [ -f $HOME/.xterm-256color-italic.terminfo ]; then
|
||||
tic $HOME/.xterm-256color-italic.terminfo
|
||||
fi
|
||||
if [ -f $HOME/.screen-256color-italic.terminfo ]; then
|
||||
tic $HOME/.screen-256color-italic.terminfo
|
||||
fi
|
||||
|
||||
# Use neovim where available
|
||||
if program_installed neovim; then
|
||||
alias vim='nvim'
|
||||
fi
|
||||
|
||||
# fzf fuzzy finder
|
||||
if program_installed fzf; then
|
||||
# TODO: This is broken when fzf is old, should include a local copy
|
||||
eval "$(fzf --bash)"
|
||||
fi
|
||||
|
||||
# thefuck command correction
|
||||
if program_installed fuck; then
|
||||
eval "$(thefuck --alias)"
|
||||
fi
|
||||
|
||||
# aactivator
|
||||
if program_installed aactivator; then
|
||||
eval "$(aactivator init)"
|
||||
fi
|
||||
|
||||
# Preferred editor for local and remote sessions
|
||||
export EDITOR='vim'
|
||||
|
||||
# Powerline
|
||||
if program_installed powerline; then
|
||||
if [ -n "$POWERLINE_ROOT" ] && \
|
||||
[ -f "$POWERLINE_ROOT" ] && \
|
||||
[ -f "$POWERLINE_ROOT/bindings/bash/powerline.sh" ]; then
|
||||
# Skip lookup if location is known
|
||||
source "$POWERLINE_ROOT/bindings/bash/powerline.sh"
|
||||
else
|
||||
POWERLINE_BASH_SOURCES=( $(powerline_bash_sources) )
|
||||
found=0
|
||||
for file in "${POWERLINE_BASH_SOURCES[@]}"; do
|
||||
if [[ -a $file ]]; then
|
||||
# TODO: Store the POWERLINE_ROOT in .profile.local if found
|
||||
source $file
|
||||
found=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ $found -eq 0 ]; then
|
||||
echo "Powerline binding not found."
|
||||
echo "Please set \$POWERLINE_ROOT in .profile.local"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Local overrides
|
||||
if [ -f $HOME/.bashrc_local ]; then
|
||||
source $HOME/.bashrc_local
|
||||
fi
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse hook: block obviously destructive Bash commands regardless of permission mode.
|
||||
# Exit 2 blocks the tool call and returns stderr to Claude as feedback.
|
||||
|
||||
command=$(jq -r '.tool_input.command // empty')
|
||||
|
||||
deny_patterns=(
|
||||
'rm[[:space:]]+(-[a-zA-Z]*[rR][a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*[rR])[[:space:]]+/($|[[:space:]])'
|
||||
'rm[[:space:]]+(-[a-zA-Z]*[rR][a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*[rR])[[:space:]]+(--no-preserve-root|~($|/)|\*)'
|
||||
':\(\)[[:space:]]*\{[[:space:]]*:.*\|.*:.*&.*\}[[:space:]]*;[[:space:]]*:'
|
||||
'dd[[:space:]].*of=/dev/(sd|nvme|vd|hd)'
|
||||
'mkfs(\.[a-zA-Z0-9]+)?[[:space:]]'
|
||||
'>[[:space:]]*/dev/(sd|nvme|vd|hd)[a-z0-9]*($|[[:space:]])'
|
||||
'chmod[[:space:]]+-R[[:space:]]+[0-7]{3,4}[[:space:]]+/($|[[:space:]])'
|
||||
'chown[[:space:]]+-R[[:space:]].*[[:space:]]+/($|[[:space:]])'
|
||||
'(^|[[:space:]])(shutdown|reboot|poweroff|halt)([[:space:]]|$)'
|
||||
'wipefs[[:space:]]'
|
||||
'shred[[:space:]].*/dev/'
|
||||
'curl[[:space:]].*\|[[:space:]]*(sudo[[:space:]]+)?(sh|bash|zsh)($|[[:space:]])'
|
||||
'wget[[:space:]].*-O-.*\|[[:space:]]*(sudo[[:space:]]+)?(sh|bash|zsh)($|[[:space:]])'
|
||||
)
|
||||
|
||||
for pattern in "${deny_patterns[@]}"; do
|
||||
if [[ "$command" =~ $pattern ]]; then
|
||||
echo "Blocked by local denylist hook (~/.claude/hooks/deny-dangerous-bash.sh): command matches pattern '$pattern'" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -1,89 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse hook: inside claude-sandbox, only allow `git push` to the Gitea
|
||||
# host (GITEA_SSH_HOST / the GITEA_API host), and never directly to
|
||||
# main/master there - the sandbox workflow is feature branch + `gitea-pr
|
||||
# create`, never a direct push, and never to GitHub at all (read-only there).
|
||||
# This is a fast-fail guard against an overeager agent, not a hard security
|
||||
# boundary - a sandbox session has full shell access and could in principle
|
||||
# edit .git/config to route around it. No-op outside the sandbox
|
||||
# (CLAUDE_SANDBOX unset), since this restriction doesn't apply to normal host
|
||||
# usage.
|
||||
|
||||
if [ -z "${CLAUDE_SANDBOX:-}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
input=$(cat)
|
||||
command=$(echo "$input" | jq -r '.tool_input.command // empty')
|
||||
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
||||
|
||||
# Only care about `git push` invocations.
|
||||
if ! [[ "$command" =~ (^|[[:space:]&\|;])git([[:space:]]+-C[[:space:]]+[^[:space:]]+)?[[:space:]]+push([[:space:]]|$) ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
repo_dir="$cwd"
|
||||
if [[ "$command" =~ git[[:space:]]+-C[[:space:]]+([^[:space:]]+) ]]; then
|
||||
repo_dir="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
# Positionally parse everything after "push": first non-flag token is the
|
||||
# remote, second (if any) is the refspec. Word-splitting rather than a full
|
||||
# shell parse, so quoting edge cases aren't handled - same pragmatic
|
||||
# tradeoff as the sibling deny-dangerous-bash.sh denylist.
|
||||
after_push="${command#*push}"
|
||||
read -ra tokens <<< "$after_push"
|
||||
positional=()
|
||||
for tok in "${tokens[@]}"; do
|
||||
case "$tok" in
|
||||
-*) continue ;;
|
||||
*) positional+=("$tok") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
remote="origin"
|
||||
refspec=""
|
||||
if [ "${#positional[@]}" -ge 1 ]; then
|
||||
remote="${positional[0]}"
|
||||
fi
|
||||
if [ "${#positional[@]}" -ge 2 ]; then
|
||||
refspec="${positional[1]}"
|
||||
fi
|
||||
|
||||
remote_url=$(git -C "$repo_dir" remote get-url "$remote" 2>/dev/null)
|
||||
if [ -z "$remote_url" ]; then
|
||||
# Can't resolve a remote from here - let git produce the real error
|
||||
# rather than block or guess.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
host=$(echo "$remote_url" | sed -E 's#^[a-zA-Z]+://([^/@]+@)?([^/:]+).*#\2#; s#^[^@/]+@([^:/]+)[:/].*#\1#')
|
||||
|
||||
gitea_api_host=""
|
||||
if [ -n "${GITEA_API:-}" ]; then
|
||||
gitea_api_host=$(echo "$GITEA_API" | sed -E 's#^[a-zA-Z]+://([^/]+).*#\1#')
|
||||
fi
|
||||
|
||||
if [ "$host" != "${GITEA_SSH_HOST:-}" ] && [ "$host" != "$gitea_api_host" ]; then
|
||||
echo "Blocked by git-push-guard.sh: this sandbox may only push to the Gitea host (${GITEA_SSH_HOST:-<GITEA_SSH_HOST not set>}), not '$host'. Reading from GitHub is fine; pushing there is not." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Resolve the target branch: a src:dst refspec's dst, a bare branch name, or
|
||||
# (if no refspec was given at all, or it's HEAD) the current branch - `git
|
||||
# push origin` and `git push origin HEAD` both push the current branch.
|
||||
if [[ "$refspec" == *:* ]]; then
|
||||
branch="${refspec#*:}"
|
||||
elif [ -n "$refspec" ] && [ "$refspec" != "HEAD" ]; then
|
||||
branch="$refspec"
|
||||
else
|
||||
branch=$(git -C "$repo_dir" branch --show-current 2>/dev/null)
|
||||
fi
|
||||
branch="${branch#refs/heads/}"
|
||||
|
||||
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
|
||||
echo "Blocked by git-push-guard.sh: direct pushes to '$branch' aren't allowed in the sandbox. Push a feature branch and run 'gitea-pr create' instead." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# SessionStart hook: when running inside the claude-sandbox Docker container
|
||||
# (see ~/dotfiles/docker/claude-sandbox), inject a reminder of the sandbox's
|
||||
# constraints and how to escape it. No-op outside the container.
|
||||
|
||||
if [ -z "${CLAUDE_SANDBOX:-}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
session_id=$(jq -r '.session_id // empty')
|
||||
resume_id="${session_id:-<unknown-session-id>}"
|
||||
|
||||
context=$(cat <<EOF
|
||||
You are running inside the claude-sandbox Docker container
|
||||
(~/dotfiles/docker/claude-sandbox), not directly on the user's machine, with
|
||||
--dangerously-skip-permissions active (no tool-use confirmations).
|
||||
|
||||
What this means:
|
||||
- The workspace directory is bind-mounted read-write and is the real
|
||||
project. ~/shared is also a real, persistent host directory (not part of
|
||||
the project) - use it to hand the user a file (a screenshot, a temp
|
||||
script) without touching the project checkout, or to pick up something
|
||||
they left there for you. Everything else in the container besides
|
||||
~/.claude is disposable: installed system packages, anything outside the
|
||||
mounted paths, and the container itself go away when this run ends.
|
||||
- Outbound network is restricted to an allowlist (Anthropic's API, the npm
|
||||
registry, GitHub read-only, the configured Gitea host, plus anything set
|
||||
via ALLOWED_DOMAINS). Requests to anything else will simply fail to
|
||||
connect. If a task needs one more domain and nothing else, that's a quick
|
||||
fix, not a reason to stop: tell the user to run
|
||||
\`bin/claude-sandbox-allow <domain>\` (add \`--global\` to persist it past
|
||||
this session) from a terminal outside the sandbox, then continue.
|
||||
- Stay within the task you were given. Since there are no permission
|
||||
prompts here, be more conservative than usual about anything destructive,
|
||||
irreversible, or outside the project directory.
|
||||
|
||||
Git workflow - do this even though nothing stops you from doing otherwise:
|
||||
- Do new work in a git worktree for the feature branch, not by switching
|
||||
branches in place in the mounted checkout - that's the user's actual
|
||||
working copy, and changing its branch out from under them is disruptive
|
||||
even if you switch it back.
|
||||
- The worktree MUST live under the mounted project directory itself, e.g.
|
||||
\`git worktree add .worktrees/<branch> -b <branch>\` run from the project
|
||||
root - never \`../<branch>\` or any other path outside it, including
|
||||
anywhere under \$HOME. Only the exact directory bind-mounted into this
|
||||
container is backed by real storage on the host; everything else,
|
||||
including what looks like the project's own parent directory, is
|
||||
container-local and silently disappears when the session ends - a
|
||||
worktree created there is real work that just gets thrown away. Add
|
||||
\`.worktrees/\` to \`.git/info/exclude\` (not \`.gitignore\` - this is
|
||||
sandbox-local, not something to commit) so it doesn't clutter \`git
|
||||
status\` for the user's real checkout.
|
||||
- Push that branch to the Gitea remote, then run \`gitea-pr create\` from
|
||||
the worktree. Never push to GitHub (read-only, and not reachable for
|
||||
push regardless), and never push directly to main/master, on any remote.
|
||||
A \`git-push-guard.sh\` hook enforces both as a backstop, but treat them
|
||||
as the actual workflow to follow, not just a filter to route around.
|
||||
|
||||
If a task genuinely needs something else the sandbox can't provide -
|
||||
broader network access than a domain or two, host-level or system changes,
|
||||
GUI/hardware access - stop and explain clearly to the user what you need
|
||||
and why, instead of trying to work around the firewall or sandbox yourself.
|
||||
They can continue this exact session outside the container by running on
|
||||
the host:
|
||||
|
||||
claude --resume $resume_id
|
||||
|
||||
(session history is shared via the bind-mounted ~/.claude, so it will pick
|
||||
up right where this left off).
|
||||
EOF
|
||||
)
|
||||
|
||||
jq -n --arg context "$context" '{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": $context
|
||||
}
|
||||
}'
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "Gruvbox Dark",
|
||||
"base": "dark",
|
||||
"overrides": {
|
||||
"claude": "#d3869b",
|
||||
"text": "#ebdbb2",
|
||||
"inactive": "#665c54",
|
||||
"subtle": "#7c6f64",
|
||||
"muted": "#928374",
|
||||
"success": "#b8bb26",
|
||||
"error": "#fb4934",
|
||||
"warning": "#fabd2f",
|
||||
"link": "#83a598",
|
||||
"highlight": "#fabd2f",
|
||||
"selection": "#504945",
|
||||
"diffAdded": "#b8bb26",
|
||||
"diffRemoved": "#fb4934",
|
||||
"promptBorder": "#504945",
|
||||
"bashBorder": "#504945",
|
||||
"mcpBorder": "#504945",
|
||||
"planMode": "#83a598",
|
||||
"spinner": "#d3869b",
|
||||
"header": "#8ec07c",
|
||||
"separator": "#3c3836",
|
||||
"badge": "#fe8019",
|
||||
"permission": "#fe8019",
|
||||
"suggestion": "#8ec07c",
|
||||
"fastMode": "#fabd2f"
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
@define-color borders_breeze #4c4e51;
|
||||
@define-color content_view_bg_breeze #141618;
|
||||
@define-color error_color_backdrop_breeze #da4453;
|
||||
@define-color error_color_breeze #da4453;
|
||||
@define-color error_color_insensitive_backdrop_breeze #55242b;
|
||||
@define-color error_color_insensitive_breeze #55242b;
|
||||
@define-color insensitive_base_color_breeze #131517;
|
||||
@define-color insensitive_base_fg_color_breeze #606263;
|
||||
@define-color insensitive_bg_color_breeze #1f2124;
|
||||
@define-color insensitive_borders_breeze #2d3033;
|
||||
@define-color insensitive_fg_color_breeze #686a6c;
|
||||
@define-color insensitive_selected_bg_color_breeze #1f2124;
|
||||
@define-color insensitive_selected_fg_color_breeze #686a6c;
|
||||
@define-color insensitive_unfocused_bg_color_breeze #1f2124;
|
||||
@define-color insensitive_unfocused_fg_color_breeze #686a6c;
|
||||
@define-color insensitive_unfocused_selected_bg_color_breeze #1f2124;
|
||||
@define-color insensitive_unfocused_selected_fg_color_breeze #686a6c;
|
||||
@define-color link_color_breeze #926ee4;
|
||||
@define-color link_visited_color_breeze #9b59b6;
|
||||
@define-color success_color_backdrop_breeze #27ae60;
|
||||
@define-color success_color_breeze #27ae60;
|
||||
@define-color success_color_insensitive_backdrop_breeze #19482f;
|
||||
@define-color success_color_insensitive_breeze #19482f;
|
||||
@define-color theme_base_color_breeze #141618;
|
||||
@define-color theme_bg_color_breeze #202326;
|
||||
@define-color theme_button_background_backdrop_breeze #292c30;
|
||||
@define-color theme_button_background_backdrop_insensitive_breeze #272a2e;
|
||||
@define-color theme_button_background_insensitive_breeze #272a2e;
|
||||
@define-color theme_button_background_normal_breeze #292c30;
|
||||
@define-color theme_button_decoration_focus_backdrop_breeze #926ee4;
|
||||
@define-color theme_button_decoration_focus_backdrop_insensitive_breeze #4a406a;
|
||||
@define-color theme_button_decoration_focus_breeze #926ee4;
|
||||
@define-color theme_button_decoration_focus_insensitive_breeze #4a406a;
|
||||
@define-color theme_button_decoration_hover_backdrop_breeze #926ee4;
|
||||
@define-color theme_button_decoration_hover_backdrop_insensitive_breeze #4a406a;
|
||||
@define-color theme_button_decoration_hover_breeze #926ee4;
|
||||
@define-color theme_button_decoration_hover_insensitive_breeze #4a406a;
|
||||
@define-color theme_button_foreground_active_backdrop_breeze #fcfcfc;
|
||||
@define-color theme_button_foreground_active_backdrop_insensitive_breeze #686a6c;
|
||||
@define-color theme_button_foreground_active_breeze #ffffff;
|
||||
@define-color theme_button_foreground_active_insensitive_breeze #686a6c;
|
||||
@define-color theme_button_foreground_backdrop_breeze #fcfcfc;
|
||||
@define-color theme_button_foreground_backdrop_insensitive_breeze #6d6f72;
|
||||
@define-color theme_button_foreground_insensitive_breeze #6d6f72;
|
||||
@define-color theme_button_foreground_normal_breeze #fcfcfc;
|
||||
@define-color theme_fg_color_breeze #fcfcfc;
|
||||
@define-color theme_header_background_backdrop_breeze #202326;
|
||||
@define-color theme_header_background_breeze #292c30;
|
||||
@define-color theme_header_background_light_breeze #202326;
|
||||
@define-color theme_header_foreground_backdrop_breeze #fcfcfc;
|
||||
@define-color theme_header_foreground_breeze #fcfcfc;
|
||||
@define-color theme_header_foreground_insensitive_backdrop_breeze #fcfcfc;
|
||||
@define-color theme_header_foreground_insensitive_breeze #fcfcfc;
|
||||
@define-color theme_hovering_selected_bg_color_breeze #926ee4;
|
||||
@define-color theme_selected_bg_color_breeze #6c53a6;
|
||||
@define-color theme_selected_fg_color_breeze #ffffff;
|
||||
@define-color theme_text_color_breeze #fcfcfc;
|
||||
@define-color theme_titlebar_background_backdrop_breeze #202326;
|
||||
@define-color theme_titlebar_background_breeze #292c30;
|
||||
@define-color theme_titlebar_background_light_breeze #202326;
|
||||
@define-color theme_titlebar_foreground_backdrop_breeze #fcfcfc;
|
||||
@define-color theme_titlebar_foreground_breeze #fcfcfc;
|
||||
@define-color theme_titlebar_foreground_insensitive_backdrop_breeze #fcfcfc;
|
||||
@define-color theme_titlebar_foreground_insensitive_breeze #fcfcfc;
|
||||
@define-color theme_unfocused_base_color_breeze #141618;
|
||||
@define-color theme_unfocused_bg_color_breeze #202326;
|
||||
@define-color theme_unfocused_fg_color_breeze #fcfcfc;
|
||||
@define-color theme_unfocused_selected_bg_color_alt_breeze #362c50;
|
||||
@define-color theme_unfocused_selected_bg_color_breeze #362c50;
|
||||
@define-color theme_unfocused_selected_fg_color_breeze #fcfcfc;
|
||||
@define-color theme_unfocused_text_color_breeze #fcfcfc;
|
||||
@define-color theme_unfocused_view_bg_color_breeze #131517;
|
||||
@define-color theme_unfocused_view_text_color_breeze #606263;
|
||||
@define-color theme_view_active_decoration_color_breeze #926ee4;
|
||||
@define-color theme_view_hover_decoration_color_breeze #926ee4;
|
||||
@define-color tooltip_background_breeze #292c30;
|
||||
@define-color tooltip_border_breeze #535659;
|
||||
@define-color tooltip_text_breeze #fcfcfc;
|
||||
@define-color unfocused_borders_breeze #4c4e51;
|
||||
@define-color unfocused_insensitive_borders_breeze #2d3033;
|
||||
@define-color warning_color_backdrop_breeze #f67400;
|
||||
@define-color warning_color_breeze #f67400;
|
||||
@define-color warning_color_insensitive_backdrop_breeze #5e340f;
|
||||
@define-color warning_color_insensitive_breeze #5e340f;
|
||||
@@ -1,4 +1,3 @@
|
||||
VteTerminal, vte-terminal {
|
||||
padding: 13px;
|
||||
}
|
||||
@import 'colors.css';
|
||||
@@ -1,25 +1,16 @@
|
||||
[Settings]
|
||||
gtk-application-prefer-dark-theme=true
|
||||
gtk-button-images=true
|
||||
gtk-cursor-blink=true
|
||||
gtk-cursor-blink-time=1000
|
||||
gtk-cursor-theme-name=breeze_cursors
|
||||
gtk-cursor-theme-size=24
|
||||
gtk-decoration-layout=icon:minimize,maximize,close
|
||||
gtk-enable-animations=true
|
||||
gtk-font-name=Liberation Sans Regular 10
|
||||
gtk-theme-name=Gruvbox-Dark-BL
|
||||
gtk-icon-theme-name=Gruvbox_Dark
|
||||
gtk-cursor-theme-name=Gruvbox-cursors
|
||||
gtk-cursor-theme-size=16
|
||||
gtk-toolbar-style=GTK_TOOLBAR_BOTH_HORIZ
|
||||
gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
|
||||
gtk-button-images=1
|
||||
gtk-menu-images=1
|
||||
gtk-enable-event-sounds=0
|
||||
gtk-enable-input-feedback-sounds=0
|
||||
gtk-font-name=Noto Sans, 10
|
||||
gtk-icon-theme-name=breeze-dark
|
||||
gtk-menu-images=true
|
||||
gtk-modules=colorreload-gtk-module
|
||||
gtk-primary-button-warps-slider=true
|
||||
gtk-sound-theme-name=ocean
|
||||
gtk-theme-name=Gruvbox-Dark-BL
|
||||
gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
|
||||
gtk-toolbar-style=3
|
||||
gtk-xft-antialias=1
|
||||
gtk-xft-dpi=122880
|
||||
gtk-xft-hinting=1
|
||||
gtk-xft-hintstyle=hintmedium
|
||||
gtk-xft-rgba=none
|
||||
|
||||
@@ -22,15 +22,6 @@ set $ws7 "7"
|
||||
set $ws8 "8"
|
||||
set $ws9 "9"
|
||||
set $ws10 "10"
|
||||
set $ws11 "11"
|
||||
set $ws12 "12"
|
||||
set $ws13 "13"
|
||||
set $ws14 "14"
|
||||
set $ws15 "15"
|
||||
set $ws16 "16"
|
||||
set $ws17 "17"
|
||||
set $ws18 "18"
|
||||
set $ws19 "19"
|
||||
|
||||
# Define colors variables:
|
||||
# gruvbox colors
|
||||
|
||||
@@ -12,19 +12,20 @@ exec --no-startup-id numlockx on
|
||||
# use arandr to setup displays and save the file as default:
|
||||
exec --no-startup-id ~/.screenlayout/default.sh
|
||||
|
||||
# set wallpaper
|
||||
exec --no-startup-id sleep 1 && feh --bg-fill --randomize ~/wallpapers
|
||||
|
||||
# set powersavings for display if not handled by DE:
|
||||
# Usage: xset dpms {standby_time} {suspend_time} {off_time}
|
||||
#exec --no-startup-id xset dpms 600 0 0
|
||||
# exec --no-startup-id xset dpms 600 0 0
|
||||
|
||||
# disable power saving (for example if using xscreensaver)
|
||||
exec --no-startup-id xset -dpms
|
||||
#exec --no-startup-id xset -dpms
|
||||
|
||||
# use xautolock to use autosuspend rules for mobile devices
|
||||
# https://wiki.archlinux.org/title/Session_lock#xautolock
|
||||
#exec --no-startup-id xautolock -time 60 -locker "systemctl suspend"
|
||||
|
||||
# set wallpaper(s) on i3 start/restart
|
||||
exec_always --no-startup-id "[ -f ~/.dotfiles-config ] && source ~/.dotfiles-config && $DOTFILES_DIR/bin/update-wallpaper"
|
||||
|
||||
# xscreensaver
|
||||
# https://www.jwz.org/xscreensaver
|
||||
@@ -44,17 +45,15 @@ exec --no-startup-id /usr/bin/dunst
|
||||
# Scratchpad with notes
|
||||
# `tmuxinator open notes` first for this to work
|
||||
exec --no-startup-id alacritty --title notebook -e zsh -c 'tmuxinator start notes'
|
||||
for_window [class="^Alacritty$" title="notebook"] move scratchpad
|
||||
#, scratchpad hide
|
||||
for_window [class="^Alacritty$" title="notebook"] move scratchpad, scratchpad hide
|
||||
|
||||
# Performance monitor
|
||||
exec --no-startup-id alacritty --title monitoring -e zsh -c 'htop'
|
||||
for_window [class="^Alacritty$" title="monitoring"] move scratchpad
|
||||
#, scratchpad hide
|
||||
for_window [class="^Alacritty$" title="monitoring"] move scratchpad, scratchpad hide
|
||||
|
||||
# Remote connections
|
||||
# exec --no-startup-id alacritty --title servers -e zsh -c 'tmuxinator start servers'
|
||||
# for_window [class="^Alacritty$" title="servers"] move scratchpad, scratchpad hide
|
||||
exec --no-startup-id alacritty --title servers -e zsh -c 'tmuxinator start servers'
|
||||
for_window [class="^Alacritty$" title="servers"] move scratchpad, scratchpad hide
|
||||
|
||||
###############
|
||||
# system tray #
|
||||
|
||||
@@ -7,15 +7,10 @@ floating_modifier $mod
|
||||
# switch/iterate between workspaces
|
||||
bindsym $mod+Tab workspace next
|
||||
bindsym $mod+Shift+Tab workspace prev
|
||||
bindsym $mod+c exec --no-startup-id ~/.config/i3/scripts/empty_workspace
|
||||
bindsym $mod+Shift+c exec --no-startup-id ~/.config/i3/scripts/move_container_empty_workspace
|
||||
|
||||
bindsym $mod+slash [class="^Alacritty$" title="notebook"] scratchpad show
|
||||
# bindsym $mod+slash scratchpad show
|
||||
bindsym $mod+m [class="^Alacritty$" title="monitoring"] scratchpad show
|
||||
# bindsym $mod+Shift+colon [class="^Alacritty$" title="servers"] scratchpad show
|
||||
bindsym $mod+Shift+slash move scratchpad
|
||||
bindsym $mod+Control+slash scratchpad show
|
||||
bindsym $mod+Shift+colon [class="^Alacritty$" title="servers"] scratchpad show
|
||||
|
||||
# switch to workspace
|
||||
bindsym $mod+1 workspace $ws1
|
||||
@@ -167,8 +162,7 @@ mode "$mode_gaps_outer" {
|
||||
######################################
|
||||
|
||||
# start a terminal
|
||||
bindsym $mod+Return exec --no-startup-id alacritty -e sh -c "tmux new-session"
|
||||
# bindsym $mod+Return exec --no-startup-id alacritty
|
||||
bindsym $mod+Return exec --no-startup-id alacritty
|
||||
|
||||
# kill focused window
|
||||
bindsym $mod+q kill
|
||||
@@ -177,7 +171,7 @@ bindsym $mod+q kill
|
||||
#bindsym --release button2 kill
|
||||
|
||||
# reload the configuration file
|
||||
bindsym $mod+Control+r reload
|
||||
bindsym $mod+Shift+c reload
|
||||
|
||||
# restart i3 inplace (preserves your layout/session, can be used to update i3)
|
||||
bindsym $mod+Shift+r restart
|
||||
@@ -212,19 +206,7 @@ bindsym $mod+Shift+Down move down
|
||||
bindsym $mod+Shift+Up move up
|
||||
bindsym $mod+Shift+Right move right
|
||||
|
||||
# move focused workspace
|
||||
bindsym $mod+Control+h move workspace to output left
|
||||
bindsym $mod+Control+j move workspace to output down
|
||||
bindsym $mod+Control+k move workspace to output up
|
||||
bindsym $mod+Control+l move workspace to output right
|
||||
|
||||
# alternatively, you can use the cursor keys:
|
||||
bindsym $mod+Control+Left move workspace to output left
|
||||
bindsym $mod+Control+Down move workspace to output down
|
||||
bindsym $mod+Control+Up move workspace to output up
|
||||
bindsym $mod+Control+Right move workspace to output right
|
||||
|
||||
# split in vertical orientation
|
||||
# splij in vertical orientation
|
||||
bindsym $mod+minus split v
|
||||
bindsym $mod+KP_Subtract split v
|
||||
|
||||
@@ -272,7 +254,6 @@ bindsym $mod+p exec --no-startup-id /usr/local/bin/switch-audio-port
|
||||
## App shortcuts
|
||||
bindsym $mod+w exec /usr/bin/firefox
|
||||
bindsym Print exec scrot ~/Pictures/Screenshots/%Y-%m-%d-%T-screenshot.png && notify-send "Screenshot saved to ~/Pictures/Screenshots/$(date +"%Y-%m-%d-%T")-screenshot.png"
|
||||
#bindsym Print exec spectactle --output ~/Pictures/Screenshots/%Y-%m-%d-%T-screenshot.png
|
||||
|
||||
# Power Profiles menu switcher (rofi)
|
||||
bindsym $mod+Shift+p exec --no-startup-id ~/.config/i3/scripts/power-profiles
|
||||
@@ -291,12 +272,5 @@ bindsym $mod+t exec --no-startup-id rofi -show window \
|
||||
|
||||
## rofi bindings to manage clipboard (install rofi-greenclip from the AUR)
|
||||
exec --no-startup-id greenclip daemon>/dev/null
|
||||
bindsym $mod+b exec --no-startup-id rofi -modi "clipboard:greenclip print" -show clipboard \
|
||||
bindsym $mod+c exec --no-startup-id rofi -modi "clipboard:greenclip print" -show clipboard \
|
||||
-config ~/.config/rofi/rofidmenu.rasi
|
||||
|
||||
## autoclicker x 100
|
||||
bindsym $mod+Shift+o exec --no-startup-id xdotool click --repeat 1000 --delay 10 1
|
||||
bindsym $mod+Shift+i exec --no-startup-id xdotool click --repeat 10000 --delay 10 1
|
||||
|
||||
# calculator
|
||||
bindsym XF86Calculator exec --no-startup-id galculator
|
||||
|
||||
@@ -9,6 +9,7 @@ default_border pixel 5
|
||||
|
||||
# thin borders
|
||||
hide_edge_borders smart
|
||||
smart_borders on
|
||||
|
||||
# Set inner/outer gaps
|
||||
gaps inner 10
|
||||
|
||||
@@ -13,3 +13,18 @@ workspace_layout default
|
||||
# Each new window will open fullscreen as a tab (left to right)
|
||||
#workspace_layout tabbed
|
||||
|
||||
##########################################
|
||||
# configuration for workspace behaviour: #
|
||||
##########################################
|
||||
|
||||
# use workspaces on different displays:
|
||||
# where you have to replace VGA-0/HDMI-0 with the names for your displays
|
||||
# you can get from xrandr command
|
||||
#workspace $ws1 output DP-2
|
||||
#workspace $ws3 output DP-2
|
||||
#workspace $ws5 output DP-2
|
||||
#workspace $ws2 output DP-4
|
||||
#workspace $ws4 output DP-4
|
||||
#workspace $ws6 output DP-4
|
||||
|
||||
exec i3-msg workspace 2
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/home/brad/dotfiles/config/plasma-i3/i3-bar.conf
|
||||
+1
@@ -0,0 +1 @@
|
||||
/home/brad/dotfiles/config/plasma-i3/i3-keybindings.conf
|
||||
@@ -1,102 +0,0 @@
|
||||
# i3blocks config file changed for EndeavourOS-i3 setup
|
||||
|
||||
# source is available here:
|
||||
# https://raw.githubusercontent.com/endeavouros-team/endeavouros-i3wm-setup/main/.config/i3/i3blocks.conf
|
||||
# Maintainer: joekamprad [joekamprad //a_t// endeavouros.com]
|
||||
# Former Visual Designer: Florent Valetti [@FLVAL EndeavourOS]
|
||||
# created for i3wm setup on EndeavourOS
|
||||
# https://endeavouros.com
|
||||
|
||||
# cheatsheet for icon fonts used on the block-bar:
|
||||
# https://fontawesome.com/v4.7/cheatsheet/
|
||||
|
||||
# --> to update this run the following command:
|
||||
# wget --backups=1 https://raw.githubusercontent.com/endeavouros-team/endeavouros-i3wm-setup/main/.config/i3/i3blocks.conf -P ~/.config/i3/
|
||||
|
||||
# Please see man i3blocks for a complete reference!
|
||||
# The man page is also hosted at http://vivien.github.io/i3blocks
|
||||
|
||||
|
||||
# List of valid properties:
|
||||
#
|
||||
# align
|
||||
# color
|
||||
# command
|
||||
# full_text
|
||||
# instance
|
||||
# interval
|
||||
# label
|
||||
# min_width
|
||||
# name
|
||||
# separator
|
||||
# separator_block_width
|
||||
# short_text
|
||||
# signal
|
||||
# urgent
|
||||
|
||||
# Global properties
|
||||
#
|
||||
# The top properties below are applied to every block, but can be overridden.
|
||||
separator=false
|
||||
markup=pango
|
||||
separator_block_width=15
|
||||
|
||||
#[Weather]
|
||||
#command=~/.config/i3/scripts/openweather
|
||||
# or:
|
||||
#command=~/.config/i3/scripts/openweather-city
|
||||
#interval=1800
|
||||
#color=#7275b3
|
||||
|
||||
separator_block_width=10
|
||||
[spacer]
|
||||
separator_block_width=10
|
||||
full_text=
|
||||
color=#b16286
|
||||
|
||||
[pavucontrol]
|
||||
separator_block_width=10
|
||||
full_text=
|
||||
command=pavucontrol
|
||||
color=#689d6a
|
||||
|
||||
[volume-pulseaudio]
|
||||
command=~/.config/i3/scripts/volume
|
||||
instance=Master
|
||||
color=#689d6a
|
||||
interval=1
|
||||
|
||||
separator_block_width=10
|
||||
[spacer]
|
||||
separator_block_width=10
|
||||
full_text=
|
||||
color=#b16286
|
||||
|
||||
[time]
|
||||
label=
|
||||
command=date '+%a %b %d %I:%M:%S %p'
|
||||
color=#98971a
|
||||
interval=1
|
||||
|
||||
separator_block_width=10
|
||||
[spacer]
|
||||
separator_block_width=10
|
||||
full_text=
|
||||
color=#b16286
|
||||
|
||||
[keybindings]
|
||||
full_text=
|
||||
command=~/.config/i3/scripts/keyhint
|
||||
|
||||
[sleep_screen]
|
||||
full_text=🖵
|
||||
command=~/.config/i3/scripts/blur-lock
|
||||
|
||||
[shutdown_menu]
|
||||
full_text=
|
||||
command=~/.config/i3/scripts/powermenu
|
||||
|
||||
separator_block_width=15
|
||||
[simple-2]
|
||||
full_text=
|
||||
color=#b16286
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
MAX_DESKTOPS=20
|
||||
|
||||
WORKSPACES=$(seq -s '\n' 1 1 ${MAX_DESKTOPS})
|
||||
|
||||
EMPTY_WORKSPACE=$( (i3-msg -t get_workspaces | tr ',' '\n' | grep num | awk -F: '{print int($2)}' ; \
|
||||
echo -e ${WORKSPACES} ) | sort -n | uniq -u | head -n 1)
|
||||
|
||||
i3-msg move container to workspace ${EMPTY_WORKSPACE}
|
||||
@@ -1,63 +0,0 @@
|
||||
" .ideavimrc is a configuration file for IdeaVim plugin. It uses
|
||||
" the same commands as the original .vimrc configuration.
|
||||
" You can find a list of commands here: https://jb.gg/h38q75
|
||||
" Find more examples here: https://jb.gg/share-ideavimrc
|
||||
|
||||
|
||||
"" -- Suggested options --
|
||||
" Show a few lines of context around the cursor. Note that this makes the
|
||||
" text scroll if you mouse-click near the start or end of the window.
|
||||
set scrolloff=5
|
||||
|
||||
" Do incremental searching.
|
||||
set incsearch
|
||||
|
||||
" Don't use Ex mode, use Q for formatting.
|
||||
map Q gq
|
||||
|
||||
" --- Enable IdeaVim plugins https://jb.gg/ideavim-plugins
|
||||
|
||||
" Highlight copied text
|
||||
Plug 'machakann/vim-highlightedyank'
|
||||
" Commentary plugin
|
||||
Plug 'tpope/vim-commentary'
|
||||
|
||||
|
||||
"" -- Map IDE actions to IdeaVim -- https://jb.gg/abva4t
|
||||
"" Map \r to the Reformat Code action
|
||||
"map \r <Action>(ReformatCode)
|
||||
|
||||
"" Map <leader>d to start debug
|
||||
"map <leader>d <Action>(Debug)
|
||||
|
||||
"" Map \b to toggle the breakpoint on the current line
|
||||
"map \b <Action>(ToggleLineBreakpoint)
|
||||
|
||||
inoremap ,, <ESC>
|
||||
cnoremap ,, <ESC><ESC>
|
||||
nmap j gj
|
||||
nmap k gk
|
||||
" Only set on first run or it overwrites YCM behavior
|
||||
" if has('vim_starting')
|
||||
" imap <tab> <C-X><C-O>
|
||||
" endif
|
||||
let mapleader=","
|
||||
let maplocalleader="\\"
|
||||
|
||||
" Close buffer
|
||||
map <leader><leader>c :Bclose<CR>
|
||||
" Prev/next buffer
|
||||
map <leader>q :bp<CR>
|
||||
map <leader>w :bn<CR>
|
||||
|
||||
" System clipboard cut copy paste
|
||||
map <leader>y "+y
|
||||
map <leader>Y "+Y
|
||||
map <leader>p "+p
|
||||
map <leader>P "+P
|
||||
map <leader>x "+x
|
||||
map <leader>dd "+dd
|
||||
|
||||
" Update ideavimrc
|
||||
nnoremap <leader>v :e ~/.ideavimrc<CR>;
|
||||
nnoremap <leader>rr :action IdeaVim.ReloadVimRc.reload<CR>;
|
||||
@@ -1,4 +1 @@
|
||||
NO_NEOFETCH=1
|
||||
NO_GIT_CHECK=1
|
||||
NO_ZSH_COWSAY=1
|
||||
LOAD_SSH_KEYS=0
|
||||
POWERLINE_ROOT=/usr/share/powerline
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
# nvim config map
|
||||
|
||||
Lua config managed by [lazy.nvim](https://github.com/folke/lazy.nvim). Leader is `,`, local leader is `\`.
|
||||
|
||||
## Layout
|
||||
|
||||
- `init.lua` — bootstraps lazy.nvim, loads `config/` then `plugins/`
|
||||
- `lua/config/options.lua` — `vim.opt` settings
|
||||
- `lua/config/keymaps.lua` — keymaps not tied to a specific plugin
|
||||
- `lua/config/autocmds.lua` — autocommands
|
||||
- `lua/plugins/*.lua` — one lazy.nvim spec file per topic (each file returns a plugin spec table; lazy.nvim loads every file in the directory automatically)
|
||||
- `ftplugin/*.lua` — filetype-specific settings (Python, vimwiki)
|
||||
- `lua/util.lua` — small helper functions (`ranger()`, `delete_trailing_ws()`) used from keymaps
|
||||
|
||||
## Plugins
|
||||
|
||||
### Navigation & editing (`plugins/editor.lua`)
|
||||
|
||||
- **[neo-tree.nvim](https://github.com/nvim-neo-tree/neo-tree.nvim)** — file explorer sidebar
|
||||
- `<leader>t` — toggle
|
||||
- `<leader>cd` — cd to current file's directory and open
|
||||
- **[telescope.nvim](https://github.com/nvim-telescope/telescope.nvim)** — fuzzy finder for files/buffers/grep/LSP symbols
|
||||
- `<leader>.` — jump to symbol
|
||||
- `<leader>;` — jump to buffer
|
||||
- `<leader>be` — jump to buffer, sorted by most recently used
|
||||
- `<leader>ag` — live grep
|
||||
- `<leader>?` — fuzzy-search all keybindings
|
||||
- **[flash.nvim](https://github.com/folke/flash.nvim)** — jump to any visible location via search-like labels
|
||||
- `<leader>/` — jump
|
||||
- `<leader>s` — search jump
|
||||
- `<leader><leader>s` — treesitter jump
|
||||
- `<leader>f` — forward jump
|
||||
- **[nvim-surround](https://github.com/kylechui/nvim-surround)** — add/change/delete surrounding pairs (quotes, tags, brackets)
|
||||
- `ys`, `cs`, `ds` — plugin defaults
|
||||
- **[nvim-autopairs](https://github.com/windwp/nvim-autopairs)** — auto-close brackets/quotes, automatic
|
||||
- **[nvim-treesitter-context](https://github.com/nvim-treesitter/nvim-treesitter-context)** — sticky header showing enclosing function/class while scrolling, automatic
|
||||
- **[mini.align](https://github.com/echasnovski/mini.nvim)** — align text on a character
|
||||
- `ga=`, `ga:`
|
||||
- **[mini.bufremove](https://github.com/echasnovski/mini.nvim)** — close a buffer without closing its window/split
|
||||
- `<leader><leader>c`
|
||||
- **[vim-visual-multi](https://github.com/mg979/vim-visual-multi)** — multiple cursors
|
||||
- `<C-n>` — add cursor on word
|
||||
- **[vim-tmux-navigator](https://github.com/christoomey/vim-tmux-navigator)** — move between nvim splits and tmux panes; also works from inside `:terminal` buffers (e.g. the Claude Code terminal)
|
||||
- `<C-h/j/k/l>` — move left/down/up/right
|
||||
- **[emmet-vim](https://github.com/mattn/emmet-vim)** — Emmet expansion, HTML/CSS files only
|
||||
|
||||
### UI (`plugins/ui.lua`)
|
||||
|
||||
- **[gruvbox.nvim](https://github.com/ellisonleao/gruvbox.nvim)** — colorscheme
|
||||
- **[lualine.nvim](https://github.com/nvim-lualine/lualine.nvim)** — statusline
|
||||
- **[bufferline.nvim](https://github.com/akinsho/bufferline.nvim)** — buffer tabs along the top
|
||||
- **[indent-blankline.nvim](https://github.com/lukas-reineke/indent-blankline.nvim)** — indent guides
|
||||
- **[rainbow-delimiters.nvim](https://github.com/HiPhish/rainbow-delimiters.nvim)** — rainbow-colored matching brackets
|
||||
- **[nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)** — filetype icons (dependency for several plugins above)
|
||||
- **[alpha-nvim](https://github.com/goolord/alpha-nvim)** — start screen shown on launch, see [Dashboard](#dashboard) below
|
||||
- `<leader><leader>o` — reopen
|
||||
|
||||
### Completion & LSP
|
||||
|
||||
- **[blink.cmp](https://github.com/Saghen/blink.cmp)** — completion engine (LSP, path, buffer sources) — `plugins/completion.lua`
|
||||
- **[mason.nvim](https://github.com/mason-org/mason.nvim)** / **[mason-lspconfig.nvim](https://github.com/mason-org/mason-lspconfig.nvim)** — installs/manages LSP servers — `plugins/lsp.lua`
|
||||
- **[nvim-lspconfig](https://github.com/neovim/nvim-lspconfig)** — LSP client config for `ts_ls`, `svelte`, `html`, `cssls`, `basedpyright`, `ruff`, `bashls` — `plugins/lsp.lua`
|
||||
- **[rustaceanvim](https://github.com/mrcjkb/rustaceanvim)** — Rust LSP setup (`rust_analyzer`), replaces the standard lspconfig flow for Rust — `plugins/rust.lua`
|
||||
- `<localleader>rr` — cargo run
|
||||
|
||||
LSP buffer keymaps (set on `LspAttach`, see `plugins/lsp.lua`):
|
||||
|
||||
- `K` — hover
|
||||
- `gd` — definition
|
||||
- `gy` — type definition
|
||||
- `gi` — implementation
|
||||
- `gr` — references
|
||||
- `rn` — rename
|
||||
- `ff` (visual) — format selection
|
||||
|
||||
### Debugging (`plugins/dap.lua`)
|
||||
|
||||
- **[nvim-dap](https://github.com/mfussenegger/nvim-dap)** — Debug Adapter Protocol client
|
||||
- `<leader>db` — toggle breakpoint
|
||||
- `<leader>dc` — continue / start debugging
|
||||
- `<leader>di` / `<leader>do` / `<leader>dO` — step into / over / out
|
||||
- `<leader>dr` — toggle REPL
|
||||
- `<leader>dq` — terminate session
|
||||
- **[nvim-dap-python](https://github.com/mfussenegger/nvim-dap-python)** — configures nvim-dap for Python using `debugpy`. Requires `:MasonInstall debugpy`; without it, debugging is skipped with a warning.
|
||||
|
||||
### Formatting & linting (`plugins/format.lua`)
|
||||
|
||||
- **[conform.nvim](https://github.com/stevearc/conform.nvim)** — format-on-save (prettier, ruff_format, rustfmt, stylua per filetype)
|
||||
- **[nvim-lint](https://github.com/mfussenegger/nvim-lint)** — async linting on save/insert-leave (eslint_d, shellcheck)
|
||||
|
||||
### Treesitter (`plugins/treesitter.lua`)
|
||||
|
||||
**[nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter)** — syntax highlighting, folding, and indentation for the installed parser list. Uses core Neovim's built-in `vim.treesitter.start()`/foldexpr rather than the old treesitter modules.
|
||||
|
||||
### Git (`plugins/git.lua`)
|
||||
|
||||
- **[gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim)** — git change signs in the gutter, hunk stage/preview/blame
|
||||
- **[vim-fugitive](https://github.com/tpope/vim-fugitive)** — Git commands
|
||||
- `:Git`, `:Gdiffsplit`, `:Gvdiffsplit`, `:Gread`, `:Gwrite`, `:Gclog`
|
||||
|
||||
### AI (`plugins/ai.lua`)
|
||||
|
||||
- **[snacks.nvim](https://github.com/folke/snacks.nvim)** — utility library (terminal provider etc.), dependency for claudecode.nvim
|
||||
- **[claudecode.nvim](https://github.com/coder/claudecode.nvim)** — Claude Code terminal integration. Diffs render as a single unified (VS Code-style, red/green interleaved) buffer in one new split next to your active pane, and keyboard focus stays in the Claude terminal instead of jumping to the diff.
|
||||
- `<leader>ac` — toggle
|
||||
- `<leader>af` — focus
|
||||
- `<leader>ab` — add current buffer
|
||||
- `<leader>as` (visual) — send selection
|
||||
- `<leader>aa` / `<leader>ad` — accept/deny diff
|
||||
|
||||
### Wiki / notes (`plugins/vimwiki.lua`)
|
||||
|
||||
- **[vimwiki](https://github.com/vimwiki/vimwiki)** — personal wiki across 4 notebooks (bradwiki, work, private, shared under `~/synced-notebooks/`)
|
||||
- `<leader>m...` — wiki keymaps (`vim.g.vimwiki_map_prefix`)
|
||||
- **[vim-zettel](https://github.com/michal-h21/vim-zettel)** — Zettelkasten-style note linking on top of vimwiki
|
||||
- **[vim-table-mode](https://github.com/dhruvasagar/vim-table-mode)** — markdown/wiki table formatting
|
||||
- `:TableModeToggle`, or `<leader><leader>t...` prefix
|
||||
- **[markdown-preview.nvim](https://github.com/iamcco/markdown-preview.nvim)** — live browser preview for markdown/vimwiki
|
||||
- `<localleader>v` — toggle preview
|
||||
|
||||
### Misc (`plugins/misc.lua`)
|
||||
|
||||
- **[aerial.nvim](https://github.com/stevearc/aerial.nvim)** — code outline/symbol sidebar
|
||||
- `<leader>ct` — toggle
|
||||
- **[undotree](https://github.com/jiaoshijie/undotree)** — visual undo history tree
|
||||
- `<leader>u` — toggle
|
||||
- **[persistence.nvim](https://github.com/folke/persistence.nvim)** — session save/restore per project
|
||||
- restore from the dashboard's `s` button
|
||||
- **[which-key.nvim](https://github.com/folke/which-key.nvim)** — shows available keybindings as you type a prefix (e.g. pause after `<leader>`), automatic
|
||||
|
||||
## Dashboard
|
||||
|
||||
The start screen (shown on launch, reopen with `<leader><leader>o`) is [alpha-nvim](https://github.com/goolord/alpha-nvim), configured in `plugins/ui.lua`. It shows a random `fortune | cowthink` header plus buttons:
|
||||
|
||||
- `e` — new file
|
||||
- `f` — find file (Telescope)
|
||||
- `g` — live grep (Telescope)
|
||||
- `s` — restore last session (persistence.nvim)
|
||||
- `q` — quit
|
||||
|
||||
## Buffer navigation
|
||||
|
||||
`<leader>be` fuzzy-searches open buffers via Telescope, sorted by most recently used, excluding the current buffer. `<leader>;` does the same but in default Telescope order. Both are fuzzy-filterable as you type.
|
||||
@@ -1,21 +0,0 @@
|
||||
" Source config files
|
||||
source $HOME/.config/nvim/config/plugin.vimrc
|
||||
source $HOME/.config/nvim/config/bindings-vimwiki.vimrc
|
||||
source $HOME/.config/nvim/config/init.vimrc
|
||||
source $HOME/.config/nvim/config/general.vimrc
|
||||
source $HOME/.config/nvim/config/bindings.vimrc
|
||||
source $HOME/.config/nvim/config/functions.vimrc
|
||||
|
||||
" Python dependencies
|
||||
if isdirectory(expand($HOME.'/.pyenv/versions/neovim2'))
|
||||
let g:python_host_prog=expand($HOME.'/.pyenv/versions/neovim2/bin/python')
|
||||
endif
|
||||
if isdirectory(expand($HOME.'/.pyenv/versions/neovim3'))
|
||||
let g:python3_host_prog=expand($HOME.'/.pyenv/versions/neovim3/bin/python')
|
||||
endif
|
||||
|
||||
" Local overrides
|
||||
let $LOCALFILE=expand("$HOME/.nvim_local.vimrc")
|
||||
if filereadable($LOCALFILE)
|
||||
source $LOCALFILE
|
||||
endif
|
||||
@@ -0,0 +1,78 @@
|
||||
vim.cmd([[
|
||||
function! MapVimwiki()
|
||||
" grep: if exists('g:loaded_vimwiki')
|
||||
" double m
|
||||
nmap <leader>mm <Plug>VimwikiIndex
|
||||
" m wiki
|
||||
nmap <leader>mw <Plug>VimwikiUISelect
|
||||
" m help
|
||||
nmap <leader>mhelp :verbose map ,m<CR>
|
||||
" m go
|
||||
nmap <leader>mg <Plug>VimwikiGoto
|
||||
" m personal
|
||||
nmap <leader>mf <Plug>VimwikiUISelect1<CR>
|
||||
" m work
|
||||
nmap <leader>m<Space> <Plug>VimwikiUISelect2<CR>
|
||||
" m private
|
||||
nmap <leader>mp <Plug>VimwikiUISelect3<CR>
|
||||
" m shared
|
||||
nmap <leader>ms <Plug>VimwikiUISelect4<CR>
|
||||
|
||||
" <C-Enter> and <S-Enter> (the original bindings for these) don't work
|
||||
" because they send the same keycode and probably other reasons
|
||||
nmap <leader>m- <Plug>VimwikiSplitLink
|
||||
nmap <leader>m<bar> <Plug>VimwikiVSplitLink
|
||||
|
||||
nmap <leader>mnt <Plug>VimwikiNextTask
|
||||
" TODO This command doesn't exist
|
||||
" nmap <leader>mN <Plug>VimwikiPrevTask
|
||||
nmap <leader>mnl <Plug>VimwikiNextLink
|
||||
nmap <leader>mNl <Plug>VimwikiPrevLink
|
||||
" nmap <leader>m/ <Plug>VimwikiSearch /
|
||||
" nmap <leader>maq <Plug>ZettelOpen
|
||||
nmap <Tab> <Plug>VimwikiIncreaseLvlSingleItem
|
||||
nmap <S-Tab> <Plug>VimwikiDecreaseLvlSingleItem
|
||||
imap <Tab> <Plug>VimwikiIncreaseLvlSingleItem
|
||||
imap <S-Tab> <Plug>VimwikiDecreaseLvlSingleItem
|
||||
vmap <Tab> <Plug>VimwikiIncreaseLvlSingleItem
|
||||
vmap <S-Tab> <Plug>VimwikiDecreaseLvlSingleItem
|
||||
" This is how to map insert mode plug commands <Esc><Plug>{command}a
|
||||
" This always jumps to the end of the line, but with 'a' it doesn't skip
|
||||
" the [ ] when adding at the start of the line
|
||||
imap <C-Space> <Esc><Plug>VimwikiToggleListItemA
|
||||
nmap <C-d><C-Space> <Plug>VimwikiRemoveSingleCB
|
||||
imap <C-d><C-Space> <Esc><Plug>VimwikiRemoveSingleCBa
|
||||
nmap <leader>mb <Plug>VimwikiBackLinks
|
||||
" nmap <leader>m\ <Plug>VimwikiTable
|
||||
nmap <leader>mqtoc <Plug>VimwikiTOC
|
||||
nmap <leader>mqgtl <Plug>VimwikiRebuildTags<CR><Plug>VimwikiGenerateTagLinks<CR>
|
||||
|
||||
" Insert link from system clipboard
|
||||
vmap <leader>mk c[]<Esc>Pea()<Esc>"+P
|
||||
imap <leader>mk <Esc>viW<C-k>ea
|
||||
nmap <leader>mk ciw[]<Esc>Pea()<Esc>"+P
|
||||
|
||||
" TODO: None of these work
|
||||
" nmap <leader>m| :VimwikiVSplitLink 1 1<CR>
|
||||
" nmap <leader>m- :VimwikiSplitLink 1 1<CR>
|
||||
" vmap <leader>m- :VimwikiSplitLink 1 1<CR>
|
||||
|
||||
" Vim-Zettel
|
||||
" TODO The <Plug> syntax is correct but broken
|
||||
" autocmd Filetype vimwiki nmap <leader>mag <Plug>ZettelOpen
|
||||
autocmd Filetype vimwiki nmap <leader>mag :ZettelOpen<CR>
|
||||
" autocmd Filetype vimwiki nmap <leader>mp0 :ZettelOpen<CR>p0
|
||||
" autocmd Filetype vimwiki nmap <leader>mp1 :ZettelOpen<CR>p1
|
||||
" TODO This doesn't work
|
||||
autocmd Filetype vimwiki nmap <leader>mat :ZettelOpen<CR>title:
|
||||
endfunction
|
||||
|
||||
augroup Vimwiki_Mappings
|
||||
autocmd!
|
||||
" TODO: This isn't working, vimwiki bindings are not being initialized
|
||||
autocmd FileType vimwiki :call MapVimwiki()
|
||||
augroup END
|
||||
" TODO This shouldn't be necessary, why does this need to be called before vimwiki
|
||||
" and after startup?
|
||||
" :call MapVimwiki()
|
||||
]])
|
||||
@@ -0,0 +1,191 @@
|
||||
vim.cmd([[
|
||||
" --- Mappings ---
|
||||
" Explanation of mapping commands: https://stackoverflow.com/a/3776182
|
||||
" - General -
|
||||
" TODO: Remap this to easymotion but don't step on other bindings
|
||||
" noremap <space> :
|
||||
inoremap ,, <ESC>
|
||||
cnoremap ,, <ESC><ESC>
|
||||
nmap j gj
|
||||
nmap k gk
|
||||
" Only set on first run or it overwrites YCM behavior
|
||||
" if has('vim_starting')
|
||||
" imap <tab> <C-X><C-O>
|
||||
" endif
|
||||
let mapleader=","
|
||||
let maplocalleader="\\"
|
||||
" Clear highlighting on ,, in normal mode
|
||||
map <leader><leader><ESC> :nohl<CR>
|
||||
" Enable hex mode
|
||||
map <leader>\he :%!xxd<CR>
|
||||
" Disable hex mode
|
||||
map <leader>\hd :%!xxd -r<CR>
|
||||
" Show whitespace
|
||||
nmap <leader><leader>lw :set list!<CR>
|
||||
" List registers
|
||||
map <leader><leader>lr :reg<CR>
|
||||
" List marks
|
||||
map <leader><leader>lm :marks<CR>
|
||||
" Close buffer
|
||||
map <leader><leader>c :Bclose<CR>
|
||||
" Prev/next buffer
|
||||
map <leader>q :bp<CR>
|
||||
map <leader>w :bn<CR>
|
||||
" System clipboard cut copy paste
|
||||
map <leader>y "+y
|
||||
map <leader>Y "+Y
|
||||
map <leader>p "+p
|
||||
map <leader>P "+P
|
||||
map <leader>x "+x
|
||||
map <leader>dd "+dd
|
||||
" Line break above
|
||||
" TODO urxvt (and other ttys) treat C-CR, S-CR, and CR as the same input
|
||||
" imap <C-CR> <CR><ESC><ESC>Bkki
|
||||
" Move between splits
|
||||
nnoremap <C-j> <C-W><C-j>
|
||||
nnoremap <C-k> <C-W><C-k>
|
||||
nnoremap <C-h> <C-W><C-h>
|
||||
nnoremap <C-l> <C-W><C-l>
|
||||
" Make split
|
||||
nnoremap <leader>- :sp<space>
|
||||
nnoremap <leader><bar> :vsp<space>
|
||||
set splitbelow
|
||||
set splitright
|
||||
" Open vimrc
|
||||
map <leader>v :e $MYVIMRC<CR>
|
||||
" Set working directory to current file directory
|
||||
map <leader>cd :cd %:p:h<CR>:pwd<CR>:NERDTreeCWD<CR>:NERDTreeToggle<CR>
|
||||
" Update vimrc
|
||||
map <leader>rr :call ReloadVimRC()<CR>;
|
||||
" Open GVim menu
|
||||
" map <leader>m :emenu <C-Z><C-Z>
|
||||
" Low-light mode
|
||||
map <leader><leader>ll :set background=dark<CR>
|
||||
" Daylight mode
|
||||
map <leader><leader>LL :set background=light<CR>
|
||||
" Open ranger
|
||||
nmap <leader>ra :call Ranger()<CR>
|
||||
" Delete whitespace
|
||||
nmap <leader>rw :call DeleteTrailingWS()<CR>
|
||||
" Fix syntax highlighting
|
||||
noremap <F12> <Esc>:syntax sync fromstart<CR>
|
||||
|
||||
|
||||
function! MapPlugins()
|
||||
" - Plugins -
|
||||
if exists('g:loaded_startify')
|
||||
" Open Startify
|
||||
map <leader><leader>o :Startify<CR>
|
||||
endif
|
||||
|
||||
if exists('g:loaded_nerd_tree')
|
||||
" Toggle directory view
|
||||
map <leader>t :NERDTreeToggle<CR>
|
||||
endif
|
||||
|
||||
" Generative AI
|
||||
|
||||
if exists('g:loaded_tagbar')
|
||||
" Toggle tagbar
|
||||
map <leader>ct :TagbarToggle<CR>
|
||||
endif
|
||||
|
||||
if exists('g:loaded_vimwiki')
|
||||
" TODO This shouldn't be necessary!
|
||||
" NOTE: Changes must be mirrored to config/bindings-vimwiki.vimrc!
|
||||
endif
|
||||
|
||||
if exists('g:loaded_table_mode')
|
||||
endif
|
||||
|
||||
if exists('g:loaded_zettel')
|
||||
let g:zettel_default_mappings = 0
|
||||
endif
|
||||
|
||||
" Update ctags
|
||||
" nnoremap <leader>ct :! ctags -R<CR><CR>
|
||||
|
||||
if exists('g:loaded_ctrlp')
|
||||
" Jump to a tag
|
||||
nnoremap <leader>. :CtrlPTag<CR>
|
||||
" Jump to a buffer
|
||||
nnoremap <leader>; :CtrlPBuffer<CR>
|
||||
endif
|
||||
|
||||
" Make a config for YCM from Makefile
|
||||
map <leader>ygc :YcmGenerateConfig<CR>
|
||||
|
||||
" Ag
|
||||
map <leader>ag :Ag<space>
|
||||
|
||||
" Easymotion
|
||||
if exists('g:EasyMotion_loaded')
|
||||
" let g:EasyMotion_do_mapping = 0
|
||||
let g:EasyMotion_smartcase = 1
|
||||
map <leader>/ <Plug>(easymotion-sn)
|
||||
omap <leader>/ <Plug>(easymotion-tn)
|
||||
" map <leader>n <Plug>(easymotion-next)
|
||||
" map <leader>N <Plug>(easymotion-prev)
|
||||
" Bidirectional single-letter search
|
||||
map <leader>s <Plug>(easymotion-s)
|
||||
" Bidirectional 2-letter search
|
||||
map <leader><leader>s <Plug>(easymotion-s2)
|
||||
" Forward single letter search
|
||||
map <leader>f <Plug>(easymotion-f)
|
||||
" Directional jumps
|
||||
map <leader>l <Plug>(easymotion-lineforward)
|
||||
map <leader>j <Plug>(easymotion-j)
|
||||
map <leader>k <Plug>(easymotion-k)
|
||||
map <leader>h <Plug>(easymotion-linebackward)
|
||||
endif
|
||||
|
||||
" Multiple Cursors
|
||||
let g:multi_cursor_use_default_mapping=0
|
||||
let g:multi_cursor_next_key='n'
|
||||
let g:multi_cursor_prev_key='m'
|
||||
let g:multi_cursor_skip_key='.'
|
||||
let g:multi_cursor_quit_key=','
|
||||
let g:multi_cursor_start_key='<C-n>'
|
||||
|
||||
if exists('g:tabular_loaded')
|
||||
" Tabular
|
||||
map <leader>a= :Tabularize /=<CR>
|
||||
map <leader>a: :Tabularize /:\zs<CR>
|
||||
endif
|
||||
|
||||
if exists('g:loaded_nerd_comments')
|
||||
" NERD Commenter
|
||||
" Enter the NERD Commenter menu which can be tabbed through
|
||||
map <leader>nc :emenu Plugin.comment.<C-Z><C-Z>
|
||||
endif
|
||||
|
||||
if exists('g:loaded_gundo')
|
||||
" Gundo
|
||||
nnoremap <leader>u :GundoToggle<CR>
|
||||
endif
|
||||
|
||||
" --- Language Specific ---
|
||||
" - General
|
||||
" COC
|
||||
" Use K to show documentation in preview window
|
||||
nnoremap K :call ShowDocumentation()
|
||||
|
||||
nmap ff <plug>(coc-format-selected)
|
||||
nmap rn <plug>(coc-rename)
|
||||
nmap gd <plug>(coc-definition)
|
||||
nmap gy <plug>(coc-type-definition)
|
||||
nmap gi <plug>(coc-implementation)
|
||||
nmap gr <plug>(coc-references)
|
||||
|
||||
" - Rust -
|
||||
map <localleader>rr :RustRun<CR>
|
||||
|
||||
" - Markdown -
|
||||
map <localleader>v :LivedownToggle<CR>
|
||||
endfunction
|
||||
|
||||
augroup Plugin_Mappings
|
||||
autocmd!
|
||||
autocmd VimEnter * :call MapPlugins()
|
||||
augroup END
|
||||
]])
|
||||
@@ -82,6 +82,8 @@ function! MapPlugins()
|
||||
map <leader>t :NERDTreeToggle<CR>
|
||||
endif
|
||||
|
||||
" Generative AI
|
||||
|
||||
if exists('g:loaded_tagbar')
|
||||
" Toggle tagbar
|
||||
map <leader>ct :TagbarToggle<CR>
|
||||
@@ -161,16 +163,6 @@ function! MapPlugins()
|
||||
nnoremap <leader>u :GundoToggle<CR>
|
||||
endif
|
||||
|
||||
if exists('g:loaded_claudecode')
|
||||
" Claude Code
|
||||
nnoremap <leader>ac :ClaudeCode<CR>
|
||||
nnoremap <leader>af :ClaudeCodeFocus<CR>
|
||||
nnoremap <leader>ab :ClaudeCodeAdd %<CR>
|
||||
vnoremap <leader>as :ClaudeCodeSend<CR>
|
||||
nnoremap <leader>aa :ClaudeCodeDiffAccept<CR>
|
||||
nnoremap <leader>ad :ClaudeCodeDiffDeny<CR>
|
||||
endif
|
||||
|
||||
" --- Language Specific ---
|
||||
" - General
|
||||
" COC
|
||||
@@ -188,8 +180,7 @@ function! MapPlugins()
|
||||
map <localleader>rr :RustRun<CR>
|
||||
|
||||
" - Markdown -
|
||||
" map <localleader>v :LivedownToggle<CR>
|
||||
nmap <localleader>v <Plug>MarkdownPreviewToggle
|
||||
map <localleader>v :LivedownToggle<CR>
|
||||
endfunction
|
||||
|
||||
augroup Plugin_Mappings
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
vim.cmd([[
|
||||
"------ Functions ------
|
||||
if executable('ranger')
|
||||
" Open ranger from within vim
|
||||
function! Ranger()
|
||||
" Get a temp file name without creating it
|
||||
let tmpfile = substitute(system('mktemp -u'), '\n', '', '')
|
||||
" Launch ranger, passing it the temp file name
|
||||
silent exec '!RANGER_RETURN_FILE='.tmpfile.' ranger'
|
||||
" If the temp file has been written by ranger
|
||||
if filereadable(tmpfile)
|
||||
" Get the selected file name from the temp file
|
||||
let filetoedit = system('cat '.tmpfile)
|
||||
exec 'edit '.filetoedit
|
||||
call delete(tmpfile)
|
||||
endif
|
||||
redraw!
|
||||
endfunction
|
||||
endif
|
||||
|
||||
" set viminfo=%M
|
||||
|
||||
" Delete trailing white space on save, useful for Python and CoffeeScript ;)
|
||||
func! DeleteTrailingWS()
|
||||
silent! normal mz
|
||||
silent! %s/\s\+$//ge
|
||||
silent! normal `z
|
||||
endfunc
|
||||
" autocmd BufWrite *.py :call DeleteTrailingWS()
|
||||
" autocmd BufWrite *.coffee :call DeleteTrailingWS()
|
||||
|
||||
" Only define if not defined, otherwise it'll be redefined in the middle of
|
||||
" execution
|
||||
if !exists("*ReloadVimRC")
|
||||
func! ReloadVimRC()
|
||||
" IMPORTANT: After way too much messing with this trying to make everything work properly
|
||||
" after a vimrc reload, my advice is DON'T RELOAD THE VIMRC WHILE EDITING A FILE!
|
||||
" Side effects will include: Some syntax highlighting lost until next save, indentline
|
||||
" plugin will break, newly opened buffers will use buffer reloaded on's syntax file for
|
||||
" comment plugins (and possibly other things)
|
||||
" Ommision of 'syntax on' here will prevent anything from breaking inside the current buffer,
|
||||
" however new buffers will be opened without any syntax highlighting
|
||||
source $MYVIMRC
|
||||
" Need to toggle this twice, once toggles every other time, none turns
|
||||
" it off on reload. I have no idea why this is.
|
||||
RainbowToggle
|
||||
RainbowToggle
|
||||
syntax on
|
||||
" Workaround for indentline turning off on syntax enable
|
||||
IndentLinesReset
|
||||
endfunc
|
||||
endif
|
||||
|
||||
" COC Show Documentation under cursor
|
||||
function! ShowDocumentation()
|
||||
if (index(['vim','help'], &filetype) >= 0)
|
||||
execute 'h '.expand('')
|
||||
else
|
||||
call CocAction('doHover')
|
||||
endif
|
||||
endfunction
|
||||
]])
|
||||
@@ -0,0 +1,164 @@
|
||||
vim.cmd([[
|
||||
" Needed for vimwiki
|
||||
set nocompatible
|
||||
filetype plugin on
|
||||
syntax on
|
||||
|
||||
" Control-X Control-O to open autocomplete box
|
||||
set omnifunc=syntaxcomplete#Complete
|
||||
" Leave hidden buffers open
|
||||
set hidden
|
||||
set autoread " Reload files changed outside vim
|
||||
|
||||
" Turn off .swp files
|
||||
set noswapfile
|
||||
set nobackup
|
||||
|
||||
set t_Co=256 " Run in 256-color mode
|
||||
set number " Show line numbers
|
||||
|
||||
" Set the clipbaord
|
||||
" set clipboard+=unnamedplus
|
||||
" let g:clipboard = {
|
||||
" \ 'name': 'myClipboard',
|
||||
" \ 'copy': {
|
||||
" \ '+': 'tmux load-buffer -',
|
||||
" \ '*': 'tmux load-buffer -',
|
||||
" \ },
|
||||
" \ 'paste': {
|
||||
" \ '+': 'tmux save-buffer -',
|
||||
" \ '*': 'tmux save-buffer -',
|
||||
" \ },
|
||||
" \ 'cache_enabled': 1,
|
||||
" \ }
|
||||
" let g:loaded_clipboard_provider=1
|
||||
|
||||
|
||||
" Matchit plugin enhances %
|
||||
runtime macros/matchit.vim
|
||||
|
||||
" Set unix line endings
|
||||
" set fileformat=unix
|
||||
|
||||
" Set encoding for vim-devicons
|
||||
set encoding=UTF-8
|
||||
|
||||
" Don't redraw screen when not needed
|
||||
set lazyredraw
|
||||
|
||||
" Allow backspacing over everything
|
||||
set backspace=indent,eol,start
|
||||
|
||||
" Keep longer history
|
||||
set history=700
|
||||
|
||||
" Incremental search
|
||||
set incsearch
|
||||
|
||||
" Smart case insensitive search
|
||||
set ignorecase
|
||||
set smartcase
|
||||
|
||||
" Maintain context around cursor
|
||||
set scrolloff=15
|
||||
|
||||
" Wildmode configuration
|
||||
set wildmode=longest,list,full
|
||||
set wildmenu
|
||||
" Make hardcoded tab work
|
||||
:set wildcharm=<C-Z>
|
||||
|
||||
" Mouse support
|
||||
set mouse=a
|
||||
|
||||
" Configure tab behavior
|
||||
set tabstop=4
|
||||
set softtabstop=4
|
||||
set expandtab
|
||||
set shiftwidth=4
|
||||
" set autoindent
|
||||
set cindent
|
||||
set cinkeys-=0#
|
||||
set indentkeys-=0#
|
||||
" set smartindent
|
||||
|
||||
" Linewrap
|
||||
set wrap linebreak nolist
|
||||
|
||||
" Show current position
|
||||
set ruler
|
||||
|
||||
" Show matching brackets
|
||||
set showmatch
|
||||
|
||||
" Make conceals less of an issue to edit by expanding on cursor
|
||||
let g:indentLine_concealcursor=""
|
||||
let g:indentLine_conceallevel=2
|
||||
|
||||
" Code folding
|
||||
set foldmethod=syntax
|
||||
set foldcolumn=5
|
||||
set foldlevel=2
|
||||
set foldlevelstart=2
|
||||
let g:vimwiki_folding='list'
|
||||
let g:markdown_folding=1
|
||||
|
||||
" Make views automatic (they store folds)
|
||||
set viewoptions-=options
|
||||
" TODO: These caused all of my buffers to cycle on buffer operations and
|
||||
" slowed things down
|
||||
" autocmd BufWinLeave * if expand("%") != "" | silent! mkview! | endif
|
||||
" autocmd BufWinEnter * if expand("%") != "" | silent! loadview | endif
|
||||
|
||||
" Don't open empty buffer when starting existing session
|
||||
set shortmess+=I
|
||||
|
||||
" Load default menus
|
||||
source $VIMRUNTIME/menu.vim
|
||||
|
||||
" Always display airline statusline
|
||||
set laststatus=2
|
||||
" Hide default statusline text
|
||||
set noshowmode "
|
||||
|
||||
" Theme
|
||||
" colorscheme zenburn
|
||||
" colorscheme solarized
|
||||
" colorscheme base16-default
|
||||
" colorscheme hybrid_reverse
|
||||
" colorscheme hybrid_material
|
||||
try
|
||||
silent! colorscheme gruvbox
|
||||
catch /^Vim\%((\a\++)\)\=:E185/
|
||||
colorscheme desert
|
||||
endtry
|
||||
|
||||
set termguicolors
|
||||
|
||||
let g:enable_bold_font = 1
|
||||
|
||||
" Have vrm extend background color to full terminal screen
|
||||
set t_ut=
|
||||
|
||||
" --- GUI settings ---
|
||||
" set lines=50
|
||||
" set columns=150
|
||||
|
||||
" --- Language specific settings ---
|
||||
" autocmd! BufNewFile,BufReadPre,FileReadPre,BufEnter * set expandtab softtabstop=4 shiftwidth=4
|
||||
augroup Enter_Buffer
|
||||
autocmd!
|
||||
autocmd BufNewFile,BufReadPre,FileReadPre,BufEnter *.pde setlocal softtabstop=2 shiftwidth=2
|
||||
autocmd BufNewFile,BufReadPre,FileReadPre,BufEnter Makefile setlocal noexpandtab softtabstop=0 shiftwidth=8
|
||||
autocmd BufNewFile,BufReadPre,FileReadPre,BufEnter *.asm,*.S setlocal softtabstop=8 shiftwidth=8
|
||||
augroup END
|
||||
|
||||
au BufNewFile,BufRead *.py
|
||||
\ setlocal tabstop=4 |
|
||||
\ setlocal softtabstop=4 |
|
||||
\ setlocal shiftwidth=4 |
|
||||
\ setlocal textwidth=79 |
|
||||
\ setlocal expandtab |
|
||||
\ setlocal autoindent |
|
||||
\ setlocal fileformat=unix |
|
||||
]])
|
||||
@@ -0,0 +1,187 @@
|
||||
vim.cmd([[
|
||||
let g:vim_plug_install_dir = "~/.local/share/nvim/site/autoload"
|
||||
let g:plugin_install_dir = "~/.local/share/nvim/plugged"
|
||||
|
||||
" Install vim-plug if not present
|
||||
if empty(glob(g:vim_plug_install_dir . "/plug.vim"))
|
||||
execute "!curl -fLo " . g:vim_plug_install_dir . "/plug.vim "
|
||||
\ "--create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim"
|
||||
endif
|
||||
|
||||
call plug#begin('~/.vim/plugged')
|
||||
|
||||
" --- General ---
|
||||
" - Visual -
|
||||
" Rainbow parentheses
|
||||
Plug 'luochen1990/rainbow'
|
||||
" Gruvbox
|
||||
Plug 'morhetz/gruvbox'
|
||||
" Airline
|
||||
" TODO This now depends on lua
|
||||
Plug 'bling/vim-airline'
|
||||
|
||||
" - Views -
|
||||
" Start screen
|
||||
Plug 'mhinz/vim-startify'
|
||||
" Directory view
|
||||
Plug 'scrooloose/nerdtree'
|
||||
" Visualize undo tree
|
||||
Plug 'sjl/gundo.vim'
|
||||
" Much nicer buffer management
|
||||
Plug 'jlanzarotta/bufexplorer'
|
||||
" Shows indentation level
|
||||
Plug 'yggdroot/indentLine'
|
||||
" Show marks
|
||||
" TODO Make this not conflict with ,mm for vimwiki, probably just an issue
|
||||
" with not having vimwiki installed yet
|
||||
" Plug 'kshenoy/vim-signature'
|
||||
"
|
||||
" - Sessions -
|
||||
" Better sessions
|
||||
Plug 'tpope/vim-obsession'
|
||||
|
||||
" - Generative AI -
|
||||
Plug 'David-Kunz/gen.nvim'
|
||||
|
||||
" - Ctags -
|
||||
" Ctags view
|
||||
Plug 'majutsushi/tagbar'
|
||||
" Automatically keep ctags up to date
|
||||
" Plug 'xolox/vim-easytags'
|
||||
" TODO Fix compatibility with vim-zettel
|
||||
" Plug 'c0r73x/neotags.nvim'
|
||||
|
||||
" - Syntax and completion -
|
||||
" Asynchronous linting
|
||||
Plug 'neomake/neomake'
|
||||
" Completion
|
||||
Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }
|
||||
" Awesome autocompletion for almost everything
|
||||
" Plug 'Valloric/YouCompleteMe', {
|
||||
" \ 'build' : {
|
||||
" \ 'mac' : './install.sh --clang-completer --system-libclang --omnisharp-completer --racer-completer',
|
||||
" \ 'unix' : './install.sh --clang-completer --system-libclang --omnisharp-completer --racer-completer',
|
||||
" \ 'windows' : './install.sh --clang-completer --system-libclang --omnisharp-completer --racer-completer',
|
||||
" \ 'cygwin' : './install.sh --clang-completer --system-libclang --omnisharp-completer --racer-completer'
|
||||
" \ }
|
||||
" \ }
|
||||
|
||||
" - Commenting -
|
||||
" Comment toggling with lots of options
|
||||
Plug 'scrooloose/nerdcommenter'
|
||||
" Simple, quick commenting
|
||||
Plug 'tpope/vim-commentary'
|
||||
|
||||
" - Git -
|
||||
" Shows git changes in the side gutter
|
||||
" Plug 'airblade/vim-gitgutter'
|
||||
Plug 'mhinz/vim-signify'
|
||||
" Git wrapper inside vim
|
||||
Plug 'tpope/vim-fugitive'
|
||||
|
||||
" - Automation -
|
||||
" Surround things and change surroundings
|
||||
Plug 'tpope/vim-surround'
|
||||
" Parentheses and bracket auto-close
|
||||
Plug 'Raimondi/delimitMate'
|
||||
" Much nicer navigation
|
||||
" TODO Move this to space key
|
||||
Plug 'easymotion/vim-easymotion'
|
||||
" Multiple cursors
|
||||
Plug 'terryma/vim-multiple-cursors'
|
||||
" Automatic alignment
|
||||
Plug 'godlygeek/tabular'
|
||||
" Tables
|
||||
Plug 'dhruvasagar/vim-table-mode'
|
||||
|
||||
" - Search -
|
||||
" Kickass fuzzy finder for whole filesystem
|
||||
Plug 'ctrlpvim/ctrlp.vim'
|
||||
" Can use Ag from vim
|
||||
" Plug 'rking/ag.vim'
|
||||
|
||||
" - Utility and dependencies
|
||||
" Library required for some plugins
|
||||
" Plug 'L9'
|
||||
" Close buffers
|
||||
Plug 'rbgrouleff/bclose.vim'
|
||||
" Seamless navigation between tmux panes and vim splits
|
||||
Plug 'christoomey/vim-tmux-navigator'
|
||||
" Required for easytags
|
||||
Plug 'xolox/vim-misc'
|
||||
" Make . work for plugins
|
||||
Plug 'tpope/vim-repeat'
|
||||
" Auto reload changed config
|
||||
Plug 'xolox/vim-reload'
|
||||
" Make configuration files for YCM
|
||||
" Plug 'rdnetto/YCM-Generator'
|
||||
|
||||
" - Note Taking -
|
||||
" Note taking and wiki managment
|
||||
Plug 'vimwiki/vimwiki'
|
||||
" TODO Why can't these go in Search?
|
||||
Plug 'junegunn/fzf'
|
||||
Plug 'junegunn/fzf.vim'
|
||||
Plug 'michal-h21/vim-zettel'
|
||||
" TODO Make this pull on buffer open / load
|
||||
" Plug 'michal-h21/vimwiki-sync'
|
||||
|
||||
" --- Language specific ---
|
||||
" - All -
|
||||
Plug 'sheerun/vim-polyglot'
|
||||
" Integration with language servers but manual configuration
|
||||
Plug 'neoclide/coc.nvim', {'branch': 'release'}
|
||||
" Allows vim to configure filetype by file section (for svelte)
|
||||
Plug 'Shougo/context_filetype.vim'
|
||||
" Prettier
|
||||
Plug 'prettier/vim-prettier', {
|
||||
\ 'do': 'npm install',
|
||||
\ 'for': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'graphql', 'markdown', 'vue', 'svelte', 'yaml', 'html'] }
|
||||
|
||||
" - Web -
|
||||
Plug 'mattn/emmet-vim'
|
||||
Plug 'mattn/webapi-vim'
|
||||
Plug 'cakebaker/scss-syntax.vim'
|
||||
|
||||
" - Python -
|
||||
" Plug 'klen/python-mode'
|
||||
" Plug 'davidhalter/jedi-vim'
|
||||
" Completion
|
||||
" Plug 'zchee/deoplete-jedi'
|
||||
|
||||
" - C# -
|
||||
" Omnicompletion and syntax
|
||||
" Plug 'OmniSharp/omnisharp-vim'
|
||||
|
||||
" - Rust -
|
||||
" Syntax and autocompletion
|
||||
" Plug 'rust-lang/rust.vim'
|
||||
" Plug 'phildawes/racer'
|
||||
|
||||
" - Javascript -
|
||||
Plug 'pangloss/vim-javascript'
|
||||
|
||||
" - Typescript -
|
||||
Plug 'HerringtonDarkholme/yats.vim'
|
||||
" TODO: Needs to be installed with `:CocInstall coc-tsserver`, figure out how
|
||||
" to config this
|
||||
" Plug 'coc-extensions/coc-tsserver'
|
||||
|
||||
" - Svelte
|
||||
" Plug 'evanleck/vim-svelte', {'branch': 'main'}
|
||||
Plug 'leafOfTree/vim-svelte-plugin'
|
||||
Plug 'coc-extensions/coc-svelte'
|
||||
|
||||
" - Processing -
|
||||
Plug 'sophacles/vim-processing'
|
||||
|
||||
" - Markdown -
|
||||
" Plug 'shime/vim-livedown'
|
||||
|
||||
|
||||
" --- Icons ---
|
||||
" For some reason this needs to be loaded last
|
||||
Plug 'ryanoasis/vim-devicons'
|
||||
|
||||
call plug#end()
|
||||
]])
|
||||
@@ -1,5 +1,5 @@
|
||||
let g:vim_plug_install_dir = "~/.local/share/nvim/site/autoload"
|
||||
let g:plugin_install_dir = "~/.vim/plugged"
|
||||
let g:plugin_install_dir = "~/.local/share/nvim/plugged"
|
||||
|
||||
" Install vim-plug if not present
|
||||
if empty(glob(g:vim_plug_install_dir . "/plug.vim"))
|
||||
@@ -39,6 +39,9 @@ Plug 'yggdroot/indentLine'
|
||||
" Better sessions
|
||||
Plug 'tpope/vim-obsession'
|
||||
|
||||
" - Generative AI -
|
||||
Plug 'David-Kunz/gen.nvim'
|
||||
|
||||
" - Ctags -
|
||||
" Ctags view
|
||||
Plug 'majutsushi/tagbar'
|
||||
@@ -116,7 +119,7 @@ Plug 'xolox/vim-reload'
|
||||
" Note taking and wiki managment
|
||||
Plug 'vimwiki/vimwiki'
|
||||
" TODO Why can't these go in Search?
|
||||
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
|
||||
Plug 'junegunn/fzf'
|
||||
Plug 'junegunn/fzf.vim'
|
||||
Plug 'michal-h21/vim-zettel'
|
||||
" TODO Make this pull on buffer open / load
|
||||
@@ -173,14 +176,6 @@ Plug 'sophacles/vim-processing'
|
||||
|
||||
" - Markdown -
|
||||
" Plug 'shime/vim-livedown'
|
||||
" Plug 'iamcco/markdown-preview.nvim', { 'do': 'cd app && npx --yes yarn install' }
|
||||
Plug 'iamcco/markdown-preview.nvim', { 'do': { -> mkdp#util#install() }, 'for': ['markdown', 'vim-plug']}
|
||||
|
||||
" --- AI ---
|
||||
" Floating/split terminal windows used by claudecode.nvim
|
||||
Plug 'folke/snacks.nvim'
|
||||
" Claude Code integration
|
||||
Plug 'coder/claudecode.nvim'
|
||||
|
||||
|
||||
" --- Icons ---
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
require('gen').setup({
|
||||
model = "phi",
|
||||
host = "10.0.2.11",
|
||||
port = "11434"
|
||||
})
|
||||
vim.cmd([[
|
||||
" Table of Contents
|
||||
" --- General ---
|
||||
" - Visual -
|
||||
" - Views -
|
||||
" -
|
||||
" TODO
|
||||
|
||||
" --- General ---
|
||||
" - Sessions -
|
||||
" Create new session optionally with given name. These show up in Startify
|
||||
command -nargs=? SessionNew :Obsess ~/.local/share/nvim/session/<args>
|
||||
command -nargs=0 SessionEnd :Obsess!
|
||||
command -nargs=1 SessionLoad :source ~/.local/share/nvim/session/<args>
|
||||
" TODO Could use g:startify_session_persistence and just rely on that
|
||||
|
||||
" - Visual -
|
||||
set background=dark
|
||||
let base16colorspace=256
|
||||
" let g:solarized_termtrans = 3
|
||||
|
||||
" Airline
|
||||
let g:airline_powerline_fonts=1
|
||||
" PERF: Could cause bad performance
|
||||
" let g:airline_skip_empty_sections=1
|
||||
let g:airline#extensions#tabline#enabled=1
|
||||
" Modify the statusbar if a recording is in session
|
||||
" TODO: This prevents some extensions (tagbar, virtualenv) from using the
|
||||
" section
|
||||
" TODO: Only if Obsession installed!
|
||||
let g:airline_section_x='%{ObsessionStatus("Session Recording |", "")} %{&filetype}'
|
||||
|
||||
" Gruvbox
|
||||
let g:gruvbox_italic=1
|
||||
let g:gruvbox_contrast_dark="medium"
|
||||
let g:gruvbox_contrast_light="soft"
|
||||
let g:gruvbox_invert_selection=1
|
||||
let g:gruvbox_invert_signs=0
|
||||
let g:gruvbox_improved_strings=0
|
||||
let g:gruvbox_improved_warnings=1
|
||||
let g:gruvbox_italicize_strings=1
|
||||
|
||||
" Rainbow parentheses
|
||||
" if exists('g:rainbow_conf')
|
||||
" augroup Rainbow_Parentheses
|
||||
" autocmd!
|
||||
" autocmd VimEnter * if exists('g:rainbow_conf') |
|
||||
" \ RainbowToggle |
|
||||
" \ endif
|
||||
" augroup END
|
||||
" endif
|
||||
|
||||
" - Views -
|
||||
" Startify
|
||||
let g:startify_bookmarks = [
|
||||
\"~/dotfiles/config/nvim/config/init.vimrc",
|
||||
\"~/dotfiles/config/nvim/config/general.vimrc",
|
||||
\"~/dotfiles/config/nvim/config/plugin.vimrc",
|
||||
\"~/dotfiles/config/nvim/config/bindings.vimrc",
|
||||
\"~/dotfiles/config/nvim/config/functions.vimrc",
|
||||
\"~/dotfiles/zshrc",
|
||||
\"~/dotfiles/config/i3/config"
|
||||
\]
|
||||
let g:startify_custom_indices = ['f', 'd', 's', 'a', 'g']
|
||||
let g:startify_custom_header = map(split(system('fortune -a -s | fmt -80 -s | cowthink -$(shuf -n 1 -e b d g p s t w y) -f $(shuf -n 1 -e $(cowsay -l | tail -n +2)) -n'), '\n'), '" ". v:val') + [","]
|
||||
" let g:startify_lists = [
|
||||
" \ { 'type': 'files', 'header': [' MRU'] },
|
||||
" \ { 'type': 'dir', 'header': [' MRU '. getcwd()] },
|
||||
" \ { 'type': 'sessions', 'header': [' Sessions'] },
|
||||
" \ { 'type': 'bookmarks', 'header': [' Bookmarks'] },
|
||||
" \ { 'type': 'commands', 'header': [' Commands'] },
|
||||
" \ ]
|
||||
|
||||
" Automatically open tagbar when entering supported buffer
|
||||
" autocmd BufEnter * nested :call tagbar#autoopen(0)
|
||||
|
||||
" - Generative AI -
|
||||
|
||||
" - Utility -
|
||||
" DelimitMate sane bracing
|
||||
let delimitMate_expand_cr = 1
|
||||
|
||||
" Indent Guide configuration
|
||||
" set ts=2 sw=2 et
|
||||
let g:indent_guides_enable_on_vim_startup = 0
|
||||
let g:indent_guides_start_level = 2
|
||||
let g:indent_guides_auto_colors = 0
|
||||
augroup Indent_Guides
|
||||
autocmd!
|
||||
autocmd VimEnter,Colorscheme * if exists('g:indentLine_loaded') |
|
||||
\ :hi IndentGuidesOdd ctermbg=darkgreen |
|
||||
\ endif
|
||||
autocmd VimEnter,Colorscheme * if exists('g:indentLine_loaded') |
|
||||
\ :hi IndentGuidesEven ctermbg=cyan |
|
||||
\ endif
|
||||
augroup END
|
||||
|
||||
" Indent Line configuration
|
||||
let g:indentLine_faster = 1
|
||||
" let g:indentLine_leadingSpaceEnabled = 1
|
||||
" let g:indentLine_leadingSpaceChar = '.'
|
||||
|
||||
" GitGutter
|
||||
let g:gitgutter_map_keys = 0
|
||||
|
||||
" NERD Commenter
|
||||
" let g:NERDMenuMode = 1
|
||||
|
||||
" NERD Tree
|
||||
" This causes an extra buffer to open when nvim starts
|
||||
" augroup DIRCHAANGE
|
||||
" au!
|
||||
" autocmd DirChanged global :NERDTreeCWD
|
||||
" augroup END
|
||||
|
||||
" Sort by modification date
|
||||
let NERDTreeSortOrder = ['[ [-timestamp] ]']
|
||||
|
||||
" Ctrl-P configuration
|
||||
" Use nearest version control directory as cwd
|
||||
let g:ctrlp_working_path_mode = 'r'
|
||||
|
||||
" Syntastic configuration
|
||||
let g:syntastic_aggregate_errors = 1
|
||||
|
||||
let g:syntastic_always_populate_loc_list = 1
|
||||
let g:syntastic_auto_loc_list = 1
|
||||
let g:syntastic_check_on_open = 1
|
||||
let g:syntastic_check_on_wq = 0
|
||||
let g:syntastic_mode_map = {
|
||||
\ "mode": "active",
|
||||
\ "passive_filetypes": ['asm', 'scss'] }
|
||||
|
||||
" YCM configuration
|
||||
let g:ycm_global_ycm_extra_conf = '~/dotfiles/.ycm_extra_conf.py'
|
||||
let g:ycm_extra_conf_globlist = ['~/dotfiles/.ycm_extra_conf.py']
|
||||
|
||||
let g:ycm_server_keep_logfiles = 1
|
||||
let g:ycm_server_log_level = 'debug'
|
||||
" let g:loaded_youcompleteme = 1
|
||||
" let g:ycm_min_num_of_chars_for_completion = 100
|
||||
let g:neobundle#install_process_timeout = 1500
|
||||
" nnoremap <leader><leader>zy :let g:ycm_auto_trigger=0<CR>
|
||||
" nnoremap <leader><leader>zY :let g:ycm_auto_trigger=1<CR>
|
||||
|
||||
" Powerline configuration
|
||||
let g:powerline_pycmd = "py"
|
||||
" let g:powerline_pyeval = "pyeval"
|
||||
|
||||
" Neotags configuration
|
||||
" let g:neotags_enabled = 1
|
||||
|
||||
" Vimwiki configuration
|
||||
let g:vimwiki_list = [
|
||||
\{'path': '~/synced-notebooks/bradwiki/', 'syntax': 'markdown', 'ext': '.md', 'diary_rel_path': 'daily_notes/', 'diary_index': 'index', 'diary_header': 'Daily Notes', 'auto_tags': 1, 'auto_diary_index': 1, 'auto_generate_links': 1, 'auto_generate_tags': 1},
|
||||
\{'path': '~/synced-notebooks/work_notebook/', 'syntax': 'markdown', 'ext': '.md', 'diary_rel_path': 'daily_notes/', 'diary_index': 'index', 'diary_header': 'Daily Notes', 'auto_tags': 1, 'auto_diary_index': 1, 'auto_generate_links': 1, 'auto_generate_tags': 1},
|
||||
\{'path': '~/synced-notebooks/privatewiki/', 'syntax': 'markdown', 'ext': '.md'},
|
||||
\{'path': '~/synced-notebooks/sharedwiki/', 'syntax': 'markdown', 'ext': '.md'}]
|
||||
let g:vimwiki_map_prefix = '<Leader>m'
|
||||
augroup filetype_vimwiki
|
||||
autocmd!
|
||||
" Wrap lines nicely
|
||||
autocmd Filetype vimwiki set breakindent
|
||||
autocmd Filetype vimwiki set breakindentopt=shift:2
|
||||
augroup END
|
||||
|
||||
" Vim-Zettel configuration
|
||||
let g:zettel_options = [{}, {"front_matter": [["tags", ""], ["type", "note"] ], "template": "~/work_notebook/templates/daily_note.tpl"}]
|
||||
|
||||
" Table Mode configuration
|
||||
let g:table_mode_map_prefix = '<Leader><Leader>t'
|
||||
|
||||
" --- Language Specific ---
|
||||
" - All -
|
||||
" let g:polyglot_disabled = ['python']
|
||||
|
||||
" COC
|
||||
" let g:coc_node_path = '$HOME/.nvm/versions/node/v12.16.3/bin/node'
|
||||
|
||||
set updatetime=300
|
||||
set shortmess+=c " don't give |ins-completion-menu| messages.
|
||||
|
||||
" - Svelte -
|
||||
let g:svelte_preprocessor_tags = [
|
||||
\ { 'name': "ts", 'tag': "script", 'as': "typescript" }
|
||||
\ ]
|
||||
let g:svelte_preprocessors = ["scss", "sass", "typescript", "ts"]
|
||||
let g:vim_svelte_plugin_load_full_syntax = 1
|
||||
let g:vim_svelte_plugin_use_sass = 1
|
||||
let g:vim_svelte_plugin_use_typescript = 1
|
||||
let g:vim_svelte_plugin_use_foldexpr = 0
|
||||
|
||||
if !exists('g:context_filetype#same_filetypes')
|
||||
let g:context_filetype#filetypes = {}
|
||||
endif
|
||||
|
||||
let g:context_filetype#filetypes.svelte =
|
||||
\ [
|
||||
\ {'filetype' : 'typescript', 'start': '<script\%( [^>]*\)\? \%(ts\|lang="\%(ts\|typescript\)"\)\%( [^>]*\)\?>', 'end': '</script>'},
|
||||
\ {'filetype' : 'javascript', 'start' : '<script \?.*>', 'end' : '</script>'},
|
||||
\ {'filetype' : 'scss', 'start' : '<style \?.*lang=''scss''>', 'end' : '</style>'},
|
||||
\ {'filetype' : 'sass', 'start' : '<style \?.*lang="sass">', 'end' : '</style>'},
|
||||
\ {'filetype' : 'css', 'start' : '<style \?.*>', 'end' : '</style>'},
|
||||
\ ]
|
||||
|
||||
let g:ft = ''
|
||||
|
||||
" For NERDCommenter
|
||||
let g:NERDSpaceDelims = 1
|
||||
" let g:NERDCompactSexyComs = 1
|
||||
|
||||
fu! NERDCommenter_before()
|
||||
if (&ft == 'html') || (&ft == 'svelte')
|
||||
let g:ft = &ft
|
||||
let cfts = context_filetype#get_filetypes()
|
||||
if len(cfts) > 0
|
||||
if cfts[0] == 'svelte'
|
||||
let cft = 'html'
|
||||
else
|
||||
let cft = cfts[0]
|
||||
endif
|
||||
echo "Using filetype=".cft." for comment"
|
||||
exe 'setf '.cft
|
||||
if exists('b:NERDCommenterDelims')
|
||||
unlet b:NERDCommenterDelims
|
||||
endif
|
||||
call nerdcommenter#SetUp()
|
||||
endif
|
||||
endif
|
||||
endfu
|
||||
|
||||
fu! NERDCommenter_after()
|
||||
if g:ft == 'svelte'
|
||||
exec 'setf '.g:ft
|
||||
if exists('b:NERDCommenterDelims')
|
||||
unlet b:NERDCommenterDelims
|
||||
endif
|
||||
call nerdcommenter#SetUp()
|
||||
let g:ft = ''
|
||||
endif
|
||||
endfu
|
||||
|
||||
" Prettier
|
||||
let g:prettier#quickfix_enabled = 0
|
||||
let g:prettier#autoformat_require_pragma = 0
|
||||
au BufWritePre *.css,*.svelte,*.pcss,*.html,*.ts,*.js,*.json PrettierAsync
|
||||
|
||||
" - Web -
|
||||
let g:user_emmet_install_global=0
|
||||
augroup Emmet
|
||||
autocmd!
|
||||
autocmd Filetype html,css if exists('g:loaded_emmet_vim') |
|
||||
\ EmmetInstall |
|
||||
\ endif
|
||||
augroup END
|
||||
" let g:user_emmet_leader_key='<localleader>'
|
||||
|
||||
" - Javascript -
|
||||
" Javascript configuration
|
||||
let g:javascript_enable_dom_htmlcss = 1
|
||||
|
||||
" - Python -
|
||||
" Run in python using F9
|
||||
augroup Python
|
||||
autocmd!
|
||||
autocmd FileType python nnoremap <buffer> <F9> :exec '!python' shellescape(@%, 1)<cr>
|
||||
augroup END
|
||||
|
||||
" Documentation
|
||||
let g:pymode_doc = 1
|
||||
let g:pymode_doc_key = 'K'
|
||||
|
||||
" Linting
|
||||
let g:pymode_lint = 1
|
||||
let g:pymode_lint_checkers = ['pyflakes', 'pep8']
|
||||
let g:pymode_lint_message = 1
|
||||
|
||||
" Auto check on save
|
||||
let g:pymode_lint_write = 1
|
||||
|
||||
" Support virtualenv
|
||||
let g:pymode_virtualenv = 1
|
||||
|
||||
" Enable breakpoints plugin
|
||||
let g:pymode_breakpoint = 1
|
||||
let g:pymode_breakpoint_bind = '<localleader>b'
|
||||
|
||||
" Syntax highlighting
|
||||
let g:pymode_syntax = 1
|
||||
let g:pymode_syntax_all = 1
|
||||
let g:pymode_syntax_indent_errors = g:pymode_syntax_all
|
||||
let g:pymode_syntax_space_errors = g:pymode_syntax_all
|
||||
|
||||
" Enable python folding
|
||||
let g:pymode_folding = 1
|
||||
|
||||
" Enable pymode motion
|
||||
let g:pymode_motion = 1
|
||||
|
||||
" Set up default python options
|
||||
let g:pymode_options = 1
|
||||
|
||||
" Trim whitespace on save
|
||||
let g:pymode_trim_whitespaces = 1
|
||||
|
||||
" PEP8-compatible python indent
|
||||
" let g:pymode_indent = 1
|
||||
|
||||
" Rope support
|
||||
let g:pymode_rope = 0
|
||||
|
||||
" [YELP] Ignore line too long
|
||||
let g:pymode_lint_ignore = "W503,E128"
|
||||
|
||||
" - Rust -
|
||||
" Rust racer
|
||||
let g:racer_cmd = "~/dotfiles/racer/target/release/racer"
|
||||
" Must install Rust source to this location first
|
||||
let $RUST_SRC_PATH="/usr/local/src/rust/src/"
|
||||
" Rust folding
|
||||
let g:rust_fold = 2
|
||||
|
||||
" - Processing -
|
||||
let g:processing_fold = 1
|
||||
|
||||
" - Markdown -
|
||||
let g:livedown_open = 1
|
||||
]])
|
||||
@@ -73,6 +73,8 @@ let g:startify_custom_header = map(split(system('fortune -a -s | fmt -80 -s | co
|
||||
" Automatically open tagbar when entering supported buffer
|
||||
" autocmd BufEnter * nested :call tagbar#autoopen(0)
|
||||
|
||||
" - Generative AI -
|
||||
|
||||
" - Utility -
|
||||
" DelimitMate sane bracing
|
||||
let delimitMate_expand_cr = 1
|
||||
@@ -173,7 +175,6 @@ let g:table_mode_map_prefix = '<Leader><Leader>t'
|
||||
|
||||
" COC
|
||||
" let g:coc_node_path = '$HOME/.nvm/versions/node/v12.16.3/bin/node'
|
||||
let g:coc_disable_startup_warning = 1
|
||||
|
||||
set updatetime=300
|
||||
set shortmess+=c " don't give |ins-completion-menu| messages.
|
||||
@@ -322,22 +323,4 @@ let g:rust_fold = 2
|
||||
let g:processing_fold = 1
|
||||
|
||||
" - Markdown -
|
||||
" Livedown
|
||||
let g:livedown_open = 1
|
||||
" Markdown Preview
|
||||
|
||||
" --- AI ---
|
||||
" Claude Code
|
||||
" plugin/claudecode.lua auto-calls require('claudecode').setup(g:claudecode_auto_setup)
|
||||
" once the plugin loads (during plug#end() below), so this must be a plain
|
||||
" vimscript dict rather than a lua block -- see the guarded keymaps in
|
||||
" bindings.vimrc for the commands this enables.
|
||||
" Full option list: https://github.com/coder/claudecode.nvim#configuration-options
|
||||
let g:claudecode_auto_setup = {
|
||||
\ 'terminal': {
|
||||
\ 'provider': 'snacks',
|
||||
\ 'split_side': 'right',
|
||||
\ 'split_width_percentage': 0.30,
|
||||
\ },
|
||||
\ }
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
vim.bo.textwidth = 79
|
||||
if vim.bo.modifiable then
|
||||
vim.bo.fileformat = "unix"
|
||||
end
|
||||
@@ -1,36 +0,0 @@
|
||||
local map = function(mode, lhs, rhs)
|
||||
vim.keymap.set(mode, lhs, rhs, { buffer = true })
|
||||
end
|
||||
|
||||
map("n", "<leader>mm", "<Plug>VimwikiIndex")
|
||||
map("n", "<leader>mw", "<Plug>VimwikiUISelect")
|
||||
map("n", "<leader>mhelp", ":verbose map ,m<CR>")
|
||||
map("n", "<leader>mg", "<Plug>VimwikiGoto")
|
||||
map("n", "<leader>mf", "<Plug>VimwikiUISelect1<CR>")
|
||||
map("n", "<leader>m<Space>", "<Plug>VimwikiUISelect2<CR>")
|
||||
map("n", "<leader>mp", "<Plug>VimwikiUISelect3<CR>")
|
||||
map("n", "<leader>ms", "<Plug>VimwikiUISelect4<CR>")
|
||||
|
||||
map("n", "<leader>m-", "<Plug>VimwikiSplitLink")
|
||||
map("n", "<leader>m<Bar>", "<Plug>VimwikiVSplitLink")
|
||||
|
||||
map("n", "<leader>mnt", "<Plug>VimwikiNextTask")
|
||||
map("n", "<leader>mnl", "<Plug>VimwikiNextLink")
|
||||
map("n", "<leader>mNl", "<Plug>VimwikiPrevLink")
|
||||
|
||||
map({ "n", "i", "v" }, "<Tab>", "<Plug>VimwikiIncreaseLvlSingleItem")
|
||||
map({ "n", "i", "v" }, "<S-Tab>", "<Plug>VimwikiDecreaseLvlSingleItem")
|
||||
|
||||
map("i", "<C-Space>", "<Esc><Plug>VimwikiToggleListItemA")
|
||||
map("n", "<C-d><C-Space>", "<Plug>VimwikiRemoveSingleCB")
|
||||
map("i", "<C-d><C-Space>", "<Esc><Plug>VimwikiRemoveSingleCBa")
|
||||
map("n", "<leader>mb", "<Plug>VimwikiBackLinks")
|
||||
map("n", "<leader>mqtoc", "<Plug>VimwikiTOC")
|
||||
map("n", "<leader>mqgtl", "<Plug>VimwikiRebuildTags<CR><Plug>VimwikiGenerateTagLinks<CR>")
|
||||
|
||||
map("v", "<leader>mk", 'c[]<Esc>Pea()<Esc>"+P')
|
||||
map("i", "<leader>mk", '<Esc>viW<C-k>ea')
|
||||
map("n", "<leader>mk", 'ciw[]<Esc>Pea()<Esc>"+P')
|
||||
|
||||
map("n", "<leader>mag", ":ZettelOpen<CR>")
|
||||
map("n", "<leader>mat", ":ZettelOpen<CR>title:")
|
||||
+17
-89
@@ -1,91 +1,19 @@
|
||||
-- Version guard: native LSP config (vim.lsp.config/vim.lsp.enable, added in
|
||||
-- 0.11) and the pinned nvim-treesitter (hard-requires 0.12 in its own health
|
||||
-- check) both need a current Neovim. Older versions -- e.g. Debian's apt
|
||||
-- package, which lags upstream by a long way even fully updated -- don't
|
||||
-- fail here; they fail later with a confusing cascade of lspconfig and
|
||||
-- mason-lspconfig errors. Check up front instead, before anything else in
|
||||
-- this file runs (some of it, like vim.uv below, doesn't exist pre-0.10).
|
||||
if vim.fn.has("nvim-0.12") == 0 then
|
||||
local ok, v = pcall(vim.version)
|
||||
local current = ok and string.format("%d.%d.%d", v.major, v.minor, v.patch) or "an older version"
|
||||
vim.cmd([[
|
||||
" -- Source config files
|
||||
source $HOME/.config/nvim/config/bindings-vimwiki.vimrc
|
||||
source $HOME/.config/nvim/config/init.vimrc
|
||||
source $HOME/.config/nvim/config/plugin.vimrc
|
||||
source $HOME/.config/nvim/config/general.vimrc
|
||||
source $HOME/.config/nvim/config/bindings.vimrc
|
||||
source $HOME/.config/nvim/config/functions.vimrc
|
||||
|
||||
-- vim.loop (not vim.uv -- that alias doesn't exist pre-0.10) to detect the
|
||||
-- machine's arch, since Neovim ships separate release assets per arch and
|
||||
-- the install command below needs to name the right one.
|
||||
local machine = (vim.loop.os_uname() or {}).machine or ""
|
||||
local asset_arch = ({ x86_64 = "x86_64", amd64 = "x86_64", aarch64 = "arm64", arm64 = "arm64" })[machine]
|
||||
" -- Python dependencies
|
||||
let g:python_host_prog = expand("~/.pyenv/versions/neovim2/bin/python")
|
||||
let g:python3_host_prog = expand("~/.pyenv/versions/neovim3/bin/python")
|
||||
|
||||
if not asset_arch then
|
||||
vim.api.nvim_echo({
|
||||
{
|
||||
"This config requires Neovim 0.12.0 or newer (found "
|
||||
.. current
|
||||
.. "). No prebuilt Neovim release is published for this machine's architecture ("
|
||||
.. (machine ~= "" and machine or "unknown")
|
||||
.. "); check https://github.com/neovim/neovim/releases/latest for a build, or install from source.\n",
|
||||
"ErrorMsg",
|
||||
},
|
||||
}, true, {})
|
||||
vim.fn.getchar()
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
local sysname = ((vim.loop.os_uname() or {}).sysname or ""):lower()
|
||||
local is_mac = sysname == "darwin"
|
||||
local asset = (is_mac and "nvim-macos-" or "nvim-linux-") .. asset_arch
|
||||
|
||||
local lines = {
|
||||
{ "This config requires Neovim 0.12.0 or newer (found " .. current .. ", " .. machine .. ").\n\n", "ErrorMsg" },
|
||||
{ "Install a current build into ~/.local (no root needed):\n", "None" },
|
||||
{ " curl -LO https://github.com/neovim/neovim/releases/latest/download/" .. asset .. ".tar.gz\n", "None" },
|
||||
{ " mkdir -p ~/.local && tar xzf " .. asset .. ".tar.gz --strip-components=1 -C ~/.local\n", "None" },
|
||||
{ " rm " .. asset .. ".tar.gz\n", "None" },
|
||||
{ " # then make sure ~/.local/bin is on your PATH\n\n", "Comment" },
|
||||
}
|
||||
if not is_mac then
|
||||
vim.list_extend(lines, {
|
||||
{ "Or system-wide (needs root):\n", "None" },
|
||||
{ " curl -LO https://github.com/neovim/neovim/releases/latest/download/" .. asset .. ".tar.gz\n", "None" },
|
||||
{ " sudo mkdir -p /opt/nvim && sudo tar xzf " .. asset .. ".tar.gz --strip-components=1 -C /opt/nvim\n", "None" },
|
||||
{ " sudo ln -sf /opt/nvim/bin/nvim /usr/local/bin/nvim\n", "None" },
|
||||
{ " rm " .. asset .. ".tar.gz\n", "None" },
|
||||
})
|
||||
end
|
||||
vim.api.nvim_echo(lines, true, {})
|
||||
vim.fn.getchar()
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
-- Leader keys MUST be set before lazy.setup() so lazy-loaded `keys = {...}`
|
||||
-- specs resolve <leader> correctly when they register their keymaps.
|
||||
vim.g.mapleader = ","
|
||||
vim.g.maplocalleader = "\\"
|
||||
|
||||
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||
if not vim.uv.fs_stat(lazypath) then
|
||||
local out = vim.fn.system({
|
||||
"git",
|
||||
"clone",
|
||||
"--filter=blob:none",
|
||||
"https://github.com/folke/lazy.nvim.git",
|
||||
"--branch=stable",
|
||||
lazypath,
|
||||
})
|
||||
if vim.v.shell_error ~= 0 then
|
||||
vim.api.nvim_echo({
|
||||
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
|
||||
{ out, "WarningMsg" },
|
||||
}, true, {})
|
||||
vim.fn.getchar()
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
vim.opt.rtp:prepend(lazypath)
|
||||
|
||||
require("config.options")
|
||||
require("config.keymaps")
|
||||
require("config.autocmds")
|
||||
|
||||
require("lazy").setup("plugins", {
|
||||
change_detection = { notify = false },
|
||||
})
|
||||
" -- Local overrides
|
||||
let $LOCALFILE=expand("$HOME/.nvim_local.vimrc")
|
||||
if filereadable($LOCALFILE)
|
||||
source $LOCALFILE
|
||||
endif
|
||||
]])
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"aerial.nvim": { "branch": "master", "commit": "28fe6e822ae344544c379d60fcb13c9519a1f08a" },
|
||||
"alpha-nvim": { "branch": "main", "commit": "6c6a89d5b068b5251c8bdf0dd57bb921bcfeeb09" },
|
||||
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
|
||||
"blink.lib": { "branch": "main", "commit": "5876dd95deeb70aadbe9f1c0b7117a135061cdac" },
|
||||
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
||||
"claudecode.nvim": { "branch": "main", "commit": "2390c6e45c4789072c293ac69de051d169668b29" },
|
||||
"conform.nvim": { "branch": "master", "commit": "619363c30309d29ffa631e67c8183f2a72caa373" },
|
||||
"emmet-vim": { "branch": "master", "commit": "92ef2f74f4093edc99db5e9e4cf7e40116a85bd6" },
|
||||
"flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" },
|
||||
"gitsigns.nvim": { "branch": "main", "commit": "eb60cc7b94c46005237fd34170d76f3a089a90aa" },
|
||||
"gruvbox.nvim": { "branch": "main", "commit": "154eb5ff5b96d0641307113fa385eaf0d36d9796" },
|
||||
"indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" },
|
||||
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
|
||||
"markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "47059d71b42d74b0a1e9f61c1d99d301039c3b5b" },
|
||||
"mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
|
||||
"mini.align": { "branch": "main", "commit": "8cebe89d82f20e6d21b27346bcde4b4b6244a9f1" },
|
||||
"mini.bufremove": { "branch": "main", "commit": "4cfbb201b2760da79ca6e1a00e9f67cbc99cbb8f" },
|
||||
"neo-tree.nvim": { "branch": "v3.x", "commit": "ebd66767191714e008ce73b769518a763ff31bdc" },
|
||||
"nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
|
||||
"nvim-autopairs": { "branch": "master", "commit": "7b9923abad60b903ece7c52940e1321d39eccc79" },
|
||||
"nvim-dap": { "branch": "master", "commit": "9e848e09a697ee95302a3ef2dd43fd6eb709e570" },
|
||||
"nvim-dap-python": { "branch": "master", "commit": "1808458eba2b18f178f990e01376941a42c7f93b" },
|
||||
"nvim-lint": { "branch": "master", "commit": "a219b2c9e5b4765e5c845aba119dad55806fcaf1" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "d224a1920728ba129880efc700d4a0180ac4ecbb" },
|
||||
"nvim-surround": { "branch": "main", "commit": "8b47db616ef658b8fc27e61db2896aa2f40134de" },
|
||||
"nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
|
||||
"nvim-treesitter-context": { "branch": "master", "commit": "b311b30818951d01f7b4bf650521b868b3fece16" },
|
||||
"nvim-web-devicons": { "branch": "master", "commit": "dad71387de386a946b123079d0e53f23028f3abd" },
|
||||
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
|
||||
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
|
||||
"rainbow-delimiters.nvim": { "branch": "master", "commit": "a798325b7f36acc62741d1029930a7b96d4dd4bf" },
|
||||
"rustaceanvim": { "branch": "main", "commit": "88575b98bb9937fb9983ddec5e532b67e75ce677" },
|
||||
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
|
||||
"telescope-fzf-native.nvim": { "branch": "main", "commit": "b25b749b9db64d375d782094e2b9dce53ad53a40" },
|
||||
"telescope.nvim": { "branch": "master", "commit": "427b576c16792edad01a92b89721d923c19ad60f" },
|
||||
"undotree": { "branch": "main", "commit": "02b69aed427b848c4dca483fc5e9524b6019c296" },
|
||||
"vim-fugitive": { "branch": "master", "commit": "3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0" },
|
||||
"vim-table-mode": { "branch": "master", "commit": "bb025308a45c67c7c8f0763ba37bc2ee3f534df0" },
|
||||
"vim-tmux-navigator": { "branch": "master", "commit": "e41c431a0c7b7388ae7ba341f01a0d217eb3a432" },
|
||||
"vim-visual-multi": { "branch": "master", "commit": "a6975e7c1ee157615bbc80fc25e4392f71c344d4" },
|
||||
"vim-zettel": { "branch": "master", "commit": "554d16bf65b04ae86b92db08a44b4fbcbc31023f" },
|
||||
"vimwiki": { "branch": "dev", "commit": "a54a3002e229c4b43d69ced170ff77663a5b2c40" },
|
||||
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
-- autoread (options.lua) only reloads a buffer once Vim checks the file's
|
||||
-- timestamp, which by default only happens on a handful of internal
|
||||
-- triggers. Force that check on focus/buffer-switch/idle so external edits
|
||||
-- (e.g. from claudecode.nvim) show up immediately instead of surfacing the
|
||||
-- stale-buffer prompt later.
|
||||
vim.api.nvim_create_autocmd({ "FocusGained", "BufEnter", "CursorHold", "CursorHoldI" }, {
|
||||
group = vim.api.nvim_create_augroup("CheckTimeOnFocus", { clear = true }),
|
||||
callback = function()
|
||||
if vim.fn.getcmdwintype() == "" then
|
||||
vim.cmd("checktime")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local augroup = vim.api.nvim_create_augroup("EnterBuffer", { clear = true })
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufNewFile", "BufReadPre", "FileReadPre", "BufEnter" }, {
|
||||
group = augroup,
|
||||
pattern = "Makefile",
|
||||
callback = function()
|
||||
vim.bo.expandtab = false
|
||||
vim.bo.softtabstop = 0
|
||||
vim.bo.shiftwidth = 8
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufNewFile", "BufReadPre", "FileReadPre", "BufEnter" }, {
|
||||
group = augroup,
|
||||
pattern = { "*.asm", "*.S" },
|
||||
callback = function()
|
||||
vim.bo.softtabstop = 8
|
||||
vim.bo.shiftwidth = 8
|
||||
end,
|
||||
})
|
||||
|
||||
-- claude-sandbox (docker/claude-sandbox) sets CLAUDE_SANDBOX, never present
|
||||
-- on a normal host session, so this only fires inside the sandbox: open
|
||||
-- claudecode.nvim's terminal on startup rather than requiring <leader>ac,
|
||||
-- since starting a Claude session is the entire point of being in there.
|
||||
-- :ClaudeCode is defined by the plugin itself, so it doesn't exist until
|
||||
-- claudecode.nvim has actually loaded - it's lazy-loaded on the <leader>ac
|
||||
-- keymap only (no `cmd` in its lazy.nvim spec), so force that load first.
|
||||
if vim.env.CLAUDE_SANDBOX then
|
||||
vim.api.nvim_create_autocmd("VimEnter", {
|
||||
once = true,
|
||||
callback = function()
|
||||
vim.schedule(function()
|
||||
require("lazy").load({ plugins = { "claudecode.nvim" } })
|
||||
vim.cmd("ClaudeCode")
|
||||
end)
|
||||
end,
|
||||
})
|
||||
end
|
||||
@@ -1,59 +0,0 @@
|
||||
local map = vim.keymap.set
|
||||
|
||||
-- Insert/command-mode escape via ,,
|
||||
map("i", ",,", "<Esc>", { desc = "Escape insert mode" })
|
||||
map("c", ",,", "<Esc><Esc>", { desc = "Escape command mode" })
|
||||
-- Terminal mode is a separate mode from Insert, so the above doesn't cover
|
||||
-- it (e.g. the Claude Code terminal). <C-\><C-n> is the real escape back to
|
||||
-- Normal mode there.
|
||||
map("t", ",,", "<C-\\><C-n>", { desc = "Escape terminal mode" })
|
||||
|
||||
-- Move by display line, not physical line
|
||||
map("n", "j", "gj", { desc = "Move down by display line" })
|
||||
map("n", "k", "gk", { desc = "Move up by display line" })
|
||||
|
||||
-- Clear search highlight
|
||||
map("n", "<leader><leader><Esc>", ":nohl<CR>", { desc = "Clear search highlight" })
|
||||
|
||||
-- Hex mode
|
||||
map("n", "<leader>\\he", ":%!xxd<CR>", { desc = "Hex dump buffer" })
|
||||
map("n", "<leader>\\hd", ":%!xxd -r<CR>", { desc = "Undo hex dump" })
|
||||
|
||||
-- Toggle whitespace display
|
||||
map("n", "<leader><leader>lw", ":set list!<CR>", { desc = "Toggle whitespace display" })
|
||||
-- List registers / marks
|
||||
map("n", "<leader><leader>lr", ":reg<CR>", { desc = "List registers" })
|
||||
map("n", "<leader><leader>lm", ":marks<CR>", { desc = "List marks" })
|
||||
|
||||
-- Prev/next buffer
|
||||
map("n", "<leader>q", ":bp<CR>", { desc = "Previous buffer" })
|
||||
map("n", "<leader>w", ":bn<CR>", { desc = "Next buffer" })
|
||||
|
||||
-- System clipboard
|
||||
map({ "n", "v" }, "<leader>y", '"+y', { desc = "Yank to system clipboard" })
|
||||
map({ "n", "v" }, "<leader>Y", '"+Y', { desc = "Yank line to system clipboard" })
|
||||
map({ "n", "v" }, "<leader>p", '"+p', { desc = "Paste from system clipboard" })
|
||||
map({ "n", "v" }, "<leader>P", '"+P', { desc = "Paste before from system clipboard" })
|
||||
map({ "n", "v" }, "<leader>x", '"+x', { desc = "Cut to system clipboard" })
|
||||
map("n", "<leader>dd", '"+dd', { desc = "Delete line to system clipboard" })
|
||||
|
||||
-- Make split
|
||||
map("n", "<leader>-", ":sp<Space>", { desc = "Horizontal split" })
|
||||
map("n", "<leader><Bar>", ":vsp<Space>", { desc = "Vertical split" })
|
||||
|
||||
-- Open init.lua
|
||||
map("n", "<leader>v", ":e $MYVIMRC<CR>", { desc = "Edit init.lua" })
|
||||
|
||||
-- Low-light / daylight mode
|
||||
map("n", "<leader><leader>ll", ":set background=dark<CR>", { desc = "Dark background" })
|
||||
map("n", "<leader><leader>LL", ":set background=light<CR>", { desc = "Light background" })
|
||||
|
||||
-- Ranger / trailing whitespace utilities
|
||||
map("n", "<leader>ra", function() require("util").ranger() end, { desc = "Open ranger" })
|
||||
map("n", "<leader>rw", function() require("util").delete_trailing_ws() end, { desc = "Delete trailing whitespace" })
|
||||
|
||||
-- Terminal-mode Ctrl-Z otherwise goes straight to the embedded process (e.g.
|
||||
-- suspending the claude CLI inside claudecode.nvim's terminal with no way to
|
||||
-- fg it back) instead of suspending Neovim itself, like Ctrl-Z does by
|
||||
-- default in Normal mode.
|
||||
map("t", "<C-z>", "<C-\\><C-n><C-z>", { desc = "Suspend Neovim" })
|
||||
@@ -1,87 +0,0 @@
|
||||
local opt = vim.opt
|
||||
|
||||
-- Completion / buffers
|
||||
opt.omnifunc = "syntaxcomplete#Complete"
|
||||
opt.hidden = true
|
||||
opt.autoread = true
|
||||
|
||||
-- No swap/backup files
|
||||
opt.swapfile = false
|
||||
opt.backup = false
|
||||
|
||||
opt.number = true
|
||||
|
||||
-- matchit is built into Neovim's runtime already; no `runtime macros/matchit.vim` needed
|
||||
|
||||
opt.encoding = "utf-8"
|
||||
opt.lazyredraw = true
|
||||
opt.backspace = { "indent", "eol", "start" }
|
||||
opt.history = 700
|
||||
opt.incsearch = true
|
||||
opt.ignorecase = true
|
||||
opt.smartcase = true
|
||||
opt.scrolloff = 15
|
||||
|
||||
opt.wildmode = { "longest", "list", "full" }
|
||||
opt.wildmenu = true
|
||||
opt.wildcharm = vim.api.nvim_replace_termcodes("<C-Z>", true, true, true):byte()
|
||||
|
||||
opt.mouse = "a"
|
||||
|
||||
-- Tabs
|
||||
opt.tabstop = 4
|
||||
opt.softtabstop = 4
|
||||
opt.expandtab = true
|
||||
opt.shiftwidth = 4
|
||||
|
||||
opt.wrap = true
|
||||
opt.linebreak = true
|
||||
opt.list = false
|
||||
|
||||
opt.ruler = true
|
||||
opt.showmatch = true
|
||||
|
||||
opt.viewoptions:remove("options")
|
||||
|
||||
opt.shortmess:append("I")
|
||||
|
||||
opt.laststatus = 2
|
||||
opt.showmode = false
|
||||
|
||||
opt.termguicolors = true
|
||||
vim.g.enable_bold_font = 1
|
||||
|
||||
-- Have vrm extend background color to full terminal screen
|
||||
vim.cmd("set t_ut=")
|
||||
|
||||
opt.splitbelow = true
|
||||
opt.splitright = true
|
||||
|
||||
opt.updatetime = 300
|
||||
opt.shortmess:append("c")
|
||||
|
||||
-- Clipboard: copy-only, via dotfiles/bin/clipboard-copy. This is a small
|
||||
-- script (rather than nvim's built-in raw-OSC-52-only provider) because
|
||||
-- reaching the real clipboard needs more than a raw OSC 52 write: it picks
|
||||
-- pbcopy/xclip/wl-copy when nvim is running locally, and when none of those
|
||||
-- are available (nvim on a remote host with no local display) sends a
|
||||
-- single plain OSC 52 write. Any tmux hops between here and the real
|
||||
-- terminal relay that onward themselves via the pane-set-clipboard hook in
|
||||
-- tmux.conf, so this works whether nvim is local or several ssh/tmux hops
|
||||
-- deep, on any machine sharing this dotfiles repo, without needing to know
|
||||
-- how many hops there are. Paste is intentionally left as a no-op: OSC 52
|
||||
-- read support is inconsistent across terminals and can hang waiting for a
|
||||
-- reply, so use the terminal's own native paste (e.g. Ctrl-Shift-V)
|
||||
-- instead.
|
||||
local clipboard_copy = vim.fn.expand("~/dotfiles/bin/clipboard-copy")
|
||||
vim.g.clipboard = {
|
||||
name = "clipboard-copy (copy-only)",
|
||||
copy = {
|
||||
["+"] = { clipboard_copy },
|
||||
["*"] = { clipboard_copy },
|
||||
},
|
||||
paste = {
|
||||
["+"] = function() return {} end,
|
||||
["*"] = function() return {} end,
|
||||
},
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
return {
|
||||
{ "folke/snacks.nvim", lazy = false },
|
||||
|
||||
{
|
||||
"coder/claudecode.nvim",
|
||||
dependencies = { "folke/snacks.nvim" },
|
||||
opts = {
|
||||
terminal = {
|
||||
provider = "snacks",
|
||||
split_side = "right",
|
||||
split_width_percentage = 0.30,
|
||||
},
|
||||
diff_opts = {
|
||||
keep_terminal_focus = true,
|
||||
-- Unified (single-buffer, VS Code-style) diff instead of side-by-side
|
||||
-- splits: it only ever opens one new window, so it can't add a second
|
||||
-- split and squish the rest of the layout the way the side-by-side
|
||||
-- diff sometimes did.
|
||||
layout = "unified",
|
||||
},
|
||||
},
|
||||
config = function(_, opts)
|
||||
require("claudecode").setup(opts)
|
||||
|
||||
-- claudecode picks a window for the diff by scanning all windows for the
|
||||
-- first non-sidebar/non-terminal one, not the one you're actually
|
||||
-- focused on, so the diff could land away from your active pane. Prefer
|
||||
-- the current window when it's a normal editing window.
|
||||
local diff = require("claudecode.diff")
|
||||
local default_find_main_editor_window = diff._find_main_editor_window
|
||||
diff._find_main_editor_window = function()
|
||||
local win = vim.api.nvim_get_current_win()
|
||||
local buf = vim.api.nvim_win_get_buf(win)
|
||||
local buftype = vim.api.nvim_buf_get_option(buf, "buftype")
|
||||
local win_config = vim.api.nvim_win_get_config(win)
|
||||
local is_floating = win_config.relative and win_config.relative ~= ""
|
||||
local is_diff_win = vim.api.nvim_win_get_option(win, "diff")
|
||||
if not is_floating and not is_diff_win and buftype ~= "terminal" and buftype ~= "prompt" then
|
||||
return win
|
||||
end
|
||||
return default_find_main_editor_window()
|
||||
end
|
||||
|
||||
-- claudecode's unified diff buffers pick up treesitter's foldexpr via
|
||||
-- the normal FileType autocmd, so they otherwise inherit the global
|
||||
-- foldlevelstart and open partially collapsed. Fully expand folds in
|
||||
-- these specifically instead of changing the default for all buffers.
|
||||
vim.api.nvim_create_autocmd("BufWinEnter", {
|
||||
group = vim.api.nvim_create_augroup("ClaudeCodeDiffUnfold", { clear = true }),
|
||||
callback = function(args)
|
||||
if vim.b[args.buf].claudecode_inline_diff then
|
||||
vim.wo.foldlevel = 99
|
||||
end
|
||||
end,
|
||||
})
|
||||
end,
|
||||
keys = {
|
||||
{ "<leader>ac", "<cmd>ClaudeCode<CR>", desc = "Toggle Claude Code" },
|
||||
{ "<leader>af", "<cmd>ClaudeCodeFocus<CR>", desc = "Focus Claude Code" },
|
||||
{ "<leader>ab", "<cmd>ClaudeCodeAdd %<CR>", desc = "Add buffer to Claude Code" },
|
||||
{ "<leader>as", "<cmd>ClaudeCodeSend<CR>", mode = "v", desc = "Send selection to Claude Code" },
|
||||
{ "<leader>aa", "<cmd>ClaudeCodeDiffAccept<CR>", desc = "Accept Claude Code diff" },
|
||||
{ "<leader>ad", "<cmd>ClaudeCodeDiffDeny<CR>", desc = "Deny Claude Code diff" },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
return {
|
||||
"saghen/blink.cmp",
|
||||
version = "*",
|
||||
opts = {
|
||||
keymap = { preset = "default" },
|
||||
appearance = { nerd_font_variant = "mono" },
|
||||
completion = { documentation = { auto_show = true } },
|
||||
sources = { default = { "lsp", "path", "buffer" } },
|
||||
},
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
return {
|
||||
{
|
||||
"mfussenegger/nvim-dap",
|
||||
keys = {
|
||||
{ "<leader>db", function() require("dap").toggle_breakpoint() end, desc = "Toggle breakpoint" },
|
||||
{ "<leader>dc", function() require("dap").continue() end, desc = "Debug: continue" },
|
||||
{ "<leader>di", function() require("dap").step_into() end, desc = "Debug: step into" },
|
||||
{ "<leader>do", function() require("dap").step_over() end, desc = "Debug: step over" },
|
||||
{ "<leader>dO", function() require("dap").step_out() end, desc = "Debug: step out" },
|
||||
{ "<leader>dr", function() require("dap").repl.toggle() end, desc = "Debug: toggle REPL" },
|
||||
{ "<leader>dq", function() require("dap").terminate() end, desc = "Debug: terminate" },
|
||||
},
|
||||
},
|
||||
{
|
||||
-- Configures nvim-dap for Python using debugpy, installed via Mason
|
||||
-- (:MasonInstall debugpy) so it lives in an isolated venv rather than
|
||||
-- needing debugpy in every project's own environment.
|
||||
"mfussenegger/nvim-dap-python",
|
||||
dependencies = { "mfussenegger/nvim-dap" },
|
||||
ft = "python",
|
||||
config = function()
|
||||
local python = vim.fn.stdpath("data") .. "/mason/packages/debugpy/venv/bin/python"
|
||||
if vim.fn.executable(python) == 0 then
|
||||
vim.notify(
|
||||
"debugpy not installed: run :MasonInstall debugpy to enable Python debugging",
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
return
|
||||
end
|
||||
require("dap-python").setup(python)
|
||||
end,
|
||||
},
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
return {
|
||||
{
|
||||
"nvim-neo-tree/neo-tree.nvim",
|
||||
branch = "v3.x",
|
||||
dependencies = {
|
||||
"nvim-lua/plenary.nvim",
|
||||
"nvim-tree/nvim-web-devicons",
|
||||
"MunifTanjim/nui.nvim",
|
||||
},
|
||||
keys = {
|
||||
{ "<leader>t", ":Neotree toggle<CR>", desc = "Toggle file explorer" },
|
||||
{
|
||||
"<leader>cd",
|
||||
function()
|
||||
vim.cmd("cd " .. vim.fn.fnameescape(vim.fn.expand("%:p:h")))
|
||||
vim.cmd("pwd")
|
||||
vim.cmd("Neotree dir=" .. vim.fn.fnameescape(vim.fn.getcwd()))
|
||||
end,
|
||||
desc = "cd to current file's directory and open explorer",
|
||||
},
|
||||
},
|
||||
opts = {},
|
||||
},
|
||||
|
||||
{
|
||||
"nvim-telescope/telescope.nvim",
|
||||
dependencies = {
|
||||
"nvim-lua/plenary.nvim",
|
||||
{ "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
|
||||
},
|
||||
cmd = "Telescope",
|
||||
keys = {
|
||||
{ "<leader>.", "<cmd>Telescope lsp_document_symbols<CR>", desc = "Jump to symbol" },
|
||||
{ "<leader>;", "<cmd>Telescope buffers<CR>", desc = "Jump to buffer" },
|
||||
{
|
||||
"<leader>be",
|
||||
"<cmd>Telescope buffers sort_mru=true ignore_current_buffer=true<CR>",
|
||||
desc = "Jump to buffer (MRU)",
|
||||
},
|
||||
{ "<leader>ag", "<cmd>Telescope live_grep<CR>", desc = "Grep" },
|
||||
{ "<leader>?", "<cmd>Telescope keymaps<CR>", desc = "Search keybindings" },
|
||||
},
|
||||
config = function()
|
||||
local telescope = require("telescope")
|
||||
telescope.setup({})
|
||||
telescope.load_extension("fzf")
|
||||
end,
|
||||
},
|
||||
|
||||
{
|
||||
"folke/flash.nvim",
|
||||
event = "VeryLazy",
|
||||
opts = {},
|
||||
keys = {
|
||||
{ "<leader>/", function() require("flash").jump() end, mode = { "n", "x", "o" }, desc = "Flash jump" },
|
||||
{ "<leader>s", function() require("flash").jump({ search = { mode = "search" } }) end, mode = { "n", "x", "o" }, desc = "Flash search jump" },
|
||||
{ "<leader><leader>s", function() require("flash").treesitter() end, mode = { "n", "x", "o" }, desc = "Flash treesitter" },
|
||||
{ "<leader>f", function() require("flash").jump({ search = { forward = true, wrap = false } }) end, mode = { "n", "x", "o" }, desc = "Flash forward" },
|
||||
},
|
||||
},
|
||||
|
||||
{ "kylechui/nvim-surround", event = { "BufReadPost", "BufNewFile" }, opts = {} },
|
||||
{ "windwp/nvim-autopairs", event = "InsertEnter", opts = {} },
|
||||
|
||||
{
|
||||
"nvim-treesitter/nvim-treesitter-context",
|
||||
event = { "BufReadPost", "BufNewFile" },
|
||||
dependencies = { "nvim-treesitter/nvim-treesitter" },
|
||||
opts = {},
|
||||
},
|
||||
|
||||
{
|
||||
"echasnovski/mini.align",
|
||||
version = "*",
|
||||
keys = {
|
||||
{ "ga=", mode = { "n", "x" }, desc = "Align on =" },
|
||||
{ "ga:", mode = { "n", "x" }, desc = "Align on :" },
|
||||
},
|
||||
config = function()
|
||||
require("mini.align").setup({
|
||||
mappings = { start = "ga", start_with_preview = "" },
|
||||
})
|
||||
end,
|
||||
},
|
||||
|
||||
{ "echasnovski/mini.bufremove", version = "*", keys = {
|
||||
{ "<leader><leader>c", function() require("mini.bufremove").delete(0, false) end, desc = "Close buffer" },
|
||||
} },
|
||||
|
||||
{
|
||||
"mg979/vim-visual-multi",
|
||||
branch = "master",
|
||||
keys = { "<C-n>" },
|
||||
init = function()
|
||||
vim.g.VM_maps = {
|
||||
["Find Under"] = "<C-n>",
|
||||
["Find Subword Under"] = "<C-n>",
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
{
|
||||
"christoomey/vim-tmux-navigator",
|
||||
lazy = false,
|
||||
-- Plugin's own default mappings rely on a Vim-only <C-w> terminal escape
|
||||
-- that Neovim doesn't support, so :terminal buffers (e.g. claudecode.nvim)
|
||||
-- leak the raw keystrokes to the embedded process. Disable them and
|
||||
-- define our own, using Neovim's actual terminal-mode escape.
|
||||
init = function()
|
||||
vim.g.tmux_navigator_no_mappings = 1
|
||||
end,
|
||||
keys = {
|
||||
{ "<C-h>", "<cmd>TmuxNavigateLeft<CR>" },
|
||||
{ "<C-j>", "<cmd>TmuxNavigateDown<CR>" },
|
||||
{ "<C-k>", "<cmd>TmuxNavigateUp<CR>" },
|
||||
{ "<C-l>", "<cmd>TmuxNavigateRight<CR>" },
|
||||
{ "<C-h>", "<C-\\><C-n><cmd>TmuxNavigateLeft<CR>", mode = "t" },
|
||||
{ "<C-j>", "<C-\\><C-n><cmd>TmuxNavigateDown<CR>", mode = "t" },
|
||||
{ "<C-k>", "<C-\\><C-n><cmd>TmuxNavigateUp<CR>", mode = "t" },
|
||||
{ "<C-l>", "<C-\\><C-n><cmd>TmuxNavigateRight<CR>", mode = "t" },
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"mattn/emmet-vim",
|
||||
ft = { "html", "css" },
|
||||
init = function()
|
||||
vim.g.user_emmet_install_global = 0
|
||||
end,
|
||||
config = function()
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
pattern = { "html", "css" },
|
||||
callback = function() vim.cmd("EmmetInstall") end,
|
||||
})
|
||||
end,
|
||||
},
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
return {
|
||||
{
|
||||
"stevearc/conform.nvim",
|
||||
event = { "BufWritePre" },
|
||||
cmd = { "ConformInfo" },
|
||||
opts = {
|
||||
formatters_by_ft = {
|
||||
css = { "prettier" },
|
||||
svelte = { "prettier" },
|
||||
pcss = { "prettier" },
|
||||
html = { "prettier" },
|
||||
typescript = { "prettier" },
|
||||
typescriptreact = { "prettier" },
|
||||
javascript = { "prettier" },
|
||||
javascriptreact = { "prettier" },
|
||||
json = { "prettier" },
|
||||
yaml = { "prettier" },
|
||||
markdown = { "prettier" },
|
||||
python = { "ruff_format" },
|
||||
rust = { "rustfmt" },
|
||||
lua = { "stylua" },
|
||||
},
|
||||
format_on_save = {
|
||||
lsp_format = "fallback",
|
||||
timeout_ms = 2000,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"mfussenegger/nvim-lint",
|
||||
event = { "BufWritePost", "BufReadPost", "InsertLeave" },
|
||||
config = function()
|
||||
require("lint").linters_by_ft = {
|
||||
javascript = { "eslint_d" },
|
||||
typescript = { "eslint_d" },
|
||||
javascriptreact = { "eslint_d" },
|
||||
typescriptreact = { "eslint_d" },
|
||||
svelte = { "eslint_d" },
|
||||
}
|
||||
|
||||
if vim.fn.executable("shellcheck") == 1 then
|
||||
require("lint").linters_by_ft.sh = { "shellcheck" }
|
||||
else
|
||||
vim.notify("shellcheck not found: skipping shell linting", vim.log.levels.WARN)
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost", "InsertLeave" }, {
|
||||
callback = function()
|
||||
require("lint").try_lint()
|
||||
end,
|
||||
})
|
||||
end,
|
||||
},
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
return {
|
||||
{ "lewis6991/gitsigns.nvim", event = { "BufReadPre", "BufNewFile" }, opts = {} },
|
||||
{ "tpope/vim-fugitive", cmd = { "Git", "Gdiffsplit", "Gvdiffsplit", "Gread", "Gwrite", "Gclog" } },
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
local servers = { "basedpyright", "ruff" }
|
||||
|
||||
local npm_servers = { "ts_ls", "svelte", "html", "cssls", "bashls" }
|
||||
local function has_public_npm()
|
||||
if vim.fn.executable("npm") ~= 1 then
|
||||
return false
|
||||
end
|
||||
local result = vim.fn.system("npm config get registry")
|
||||
return result:find("registry.npmjs.org") ~= nil
|
||||
end
|
||||
|
||||
if has_public_npm() then
|
||||
vim.list_extend(servers, npm_servers)
|
||||
else
|
||||
vim.notify(
|
||||
"public npm registry unavailable: skipping LSP servers " .. table.concat(npm_servers, ", "),
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
end
|
||||
|
||||
return {
|
||||
{
|
||||
"mason-org/mason.nvim",
|
||||
opts = {},
|
||||
},
|
||||
{
|
||||
"mason-org/mason-lspconfig.nvim",
|
||||
dependencies = { "mason-org/mason.nvim" },
|
||||
opts = {
|
||||
ensure_installed = servers,
|
||||
},
|
||||
},
|
||||
{
|
||||
"neovim/nvim-lspconfig",
|
||||
dependencies = { "mason-org/mason-lspconfig.nvim", "saghen/blink.cmp", "saghen/blink.lib" },
|
||||
config = function()
|
||||
local capabilities = require("blink.cmp").get_lsp_capabilities()
|
||||
|
||||
vim.api.nvim_create_autocmd("LspAttach", {
|
||||
group = vim.api.nvim_create_augroup("LspKeymaps", { clear = true }),
|
||||
callback = function(args)
|
||||
local opts = { buffer = args.buf }
|
||||
vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
|
||||
vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
|
||||
vim.keymap.set("n", "gy", vim.lsp.buf.type_definition, opts)
|
||||
vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts)
|
||||
vim.keymap.set("n", "gr", vim.lsp.buf.references, opts)
|
||||
vim.keymap.set("n", "rn", vim.lsp.buf.rename, opts)
|
||||
vim.keymap.set("v", "ff", function() vim.lsp.buf.format({ async = true }) end, opts)
|
||||
end,
|
||||
})
|
||||
|
||||
for _, server in ipairs(servers) do
|
||||
vim.lsp.config(server, { capabilities = capabilities })
|
||||
vim.lsp.enable(server)
|
||||
end
|
||||
end,
|
||||
},
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
return {
|
||||
{
|
||||
"stevearc/aerial.nvim",
|
||||
dependencies = { "nvim-treesitter/nvim-treesitter", "nvim-tree/nvim-web-devicons" },
|
||||
keys = {
|
||||
{ "<leader>ct", "<cmd>AerialToggle<CR>", desc = "Toggle symbol outline" },
|
||||
},
|
||||
opts = {},
|
||||
},
|
||||
|
||||
{
|
||||
"jiaoshijie/undotree",
|
||||
dependencies = { "nvim-lua/plenary.nvim" },
|
||||
keys = {
|
||||
{ "<leader>u", function() require("undotree").toggle() end, desc = "Toggle undo tree" },
|
||||
},
|
||||
opts = {},
|
||||
},
|
||||
|
||||
{
|
||||
"folke/persistence.nvim",
|
||||
event = "BufReadPre",
|
||||
opts = {},
|
||||
},
|
||||
|
||||
{
|
||||
"folke/which-key.nvim",
|
||||
event = "VeryLazy",
|
||||
opts = {},
|
||||
},
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
return {
|
||||
{
|
||||
"mrcjkb/rustaceanvim",
|
||||
version = "^6",
|
||||
lazy = false,
|
||||
init = function()
|
||||
vim.g.rustaceanvim = {
|
||||
tools = {},
|
||||
server = {
|
||||
on_attach = function(_, bufnr)
|
||||
vim.keymap.set("n", "<localleader>rr", function()
|
||||
vim.cmd.RustLsp("run")
|
||||
end, { buffer = bufnr })
|
||||
end,
|
||||
},
|
||||
}
|
||||
end,
|
||||
},
|
||||
{
|
||||
"mason-org/mason-lspconfig.nvim",
|
||||
opts = {
|
||||
automatic_enable = { exclude = { "rust_analyzer" } },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
return {
|
||||
"nvim-treesitter/nvim-treesitter",
|
||||
lazy = false,
|
||||
build = ":TSUpdate",
|
||||
config = function()
|
||||
local parsers = {
|
||||
"javascript",
|
||||
"typescript",
|
||||
"tsx",
|
||||
"svelte",
|
||||
"html",
|
||||
"css",
|
||||
"scss",
|
||||
"json",
|
||||
"yaml",
|
||||
"python",
|
||||
"rust",
|
||||
"bash",
|
||||
"lua",
|
||||
"vim",
|
||||
"vimdoc",
|
||||
"query",
|
||||
"markdown",
|
||||
"markdown_inline",
|
||||
"regex",
|
||||
}
|
||||
|
||||
-- Install parsers (skip if the tree-sitter CLI isn't available; parsers
|
||||
-- already cached from a previous run on this machine still work fine)
|
||||
if vim.fn.executable("tree-sitter") == 1 then
|
||||
require("nvim-treesitter").install(parsers)
|
||||
else
|
||||
local uname = vim.uv.os_uname()
|
||||
local sysname = (uname.sysname or ""):lower()
|
||||
local machine = uname.machine or ""
|
||||
local is_mac = sysname == "darwin"
|
||||
|
||||
local install_cmd
|
||||
if is_mac then
|
||||
local arch_part = ({ x86_64 = "x64", amd64 = "x64", aarch64 = "arm64", arm64 = "arm64" })[machine] or "x64"
|
||||
local asset = "tree-sitter-macos-" .. arch_part
|
||||
install_cmd = " curl -L https://github.com/tree-sitter/tree-sitter/releases/latest/download/"
|
||||
.. asset
|
||||
.. ".gz | gunzip > ~/.local/bin/tree-sitter && chmod +x ~/.local/bin/tree-sitter"
|
||||
else
|
||||
install_cmd = " cargo install --root ~/.local tree-sitter-cli"
|
||||
end
|
||||
|
||||
vim.notify(
|
||||
"tree-sitter CLI not found: skipping install of missing parsers.\n"
|
||||
.. "Install into ~/.local/bin:\n"
|
||||
.. install_cmd,
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
end
|
||||
|
||||
-- Enable treesitter highlighting, folding, and indentation for
|
||||
-- filetypes with an installed parser. Note some filetype names differ
|
||||
-- from their parser names (e.g. "bash" parser -> "sh" filetype).
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
pattern = {
|
||||
"javascript",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"javascriptreact",
|
||||
"svelte",
|
||||
"html",
|
||||
"css",
|
||||
"scss",
|
||||
"json",
|
||||
"yaml",
|
||||
"python",
|
||||
"rust",
|
||||
"sh",
|
||||
"lua",
|
||||
"vim",
|
||||
"help",
|
||||
"query",
|
||||
"markdown",
|
||||
},
|
||||
callback = function()
|
||||
-- Highlighting, provided by Neovim. Falls back to Vim's legacy
|
||||
-- syntax/indent if no parser is installed for this filetype.
|
||||
if not pcall(vim.treesitter.start) then
|
||||
return
|
||||
end
|
||||
-- Folding, provided by Neovim
|
||||
vim.wo.foldexpr = "v:lua.vim.treesitter.foldexpr()"
|
||||
vim.wo.foldmethod = "expr"
|
||||
-- Indentation, provided by nvim-treesitter
|
||||
vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()"
|
||||
end,
|
||||
})
|
||||
|
||||
-- General fold display settings (not treesitter-specific)
|
||||
vim.opt.foldlevel = 2
|
||||
vim.opt.foldlevelstart = 2
|
||||
vim.opt.foldcolumn = "5"
|
||||
end,
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
return {
|
||||
{
|
||||
"ellisonleao/gruvbox.nvim",
|
||||
priority = 1000,
|
||||
opts = {
|
||||
italic = { strings = true, comments = true, operators = false, folds = true },
|
||||
invert_selection = true,
|
||||
invert_signs = false,
|
||||
},
|
||||
config = function(_, opts)
|
||||
require("gruvbox").setup(opts)
|
||||
vim.o.background = "dark"
|
||||
local ok = pcall(vim.cmd.colorscheme, "gruvbox")
|
||||
if not ok then
|
||||
vim.cmd.colorscheme("desert")
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
{
|
||||
"nvim-lualine/lualine.nvim",
|
||||
dependencies = { "nvim-tree/nvim-web-devicons" },
|
||||
event = "VeryLazy",
|
||||
opts = {
|
||||
options = { theme = "gruvbox" },
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"akinsho/bufferline.nvim",
|
||||
dependencies = { "nvim-tree/nvim-web-devicons" },
|
||||
event = "VeryLazy",
|
||||
opts = {},
|
||||
},
|
||||
|
||||
{
|
||||
"lukas-reineke/indent-blankline.nvim",
|
||||
main = "ibl",
|
||||
event = { "BufReadPost", "BufNewFile" },
|
||||
opts = {},
|
||||
},
|
||||
|
||||
{
|
||||
"HiPhish/rainbow-delimiters.nvim",
|
||||
event = { "BufReadPost", "BufNewFile" },
|
||||
},
|
||||
|
||||
{ "nvim-tree/nvim-web-devicons", opts = {} },
|
||||
|
||||
{
|
||||
"goolord/alpha-nvim",
|
||||
dependencies = { "nvim-tree/nvim-web-devicons" },
|
||||
event = "VimEnter",
|
||||
config = function()
|
||||
local alpha = require("alpha")
|
||||
local dashboard = require("alpha.themes.dashboard")
|
||||
|
||||
local function fortune_header()
|
||||
-- cowsay -l prints a header line, then wraps every cow name across
|
||||
-- several space-separated lines (not one name per line), so a name
|
||||
-- has to be picked from all of them split on whitespace, not by
|
||||
-- treating a whole (possibly multi-name) line as one name.
|
||||
local raw = vim.fn.systemlist("cowsay -l")
|
||||
local cow = "default"
|
||||
local names = {}
|
||||
for i = 2, #raw do
|
||||
for name in raw[i]:gmatch("%S+") do
|
||||
table.insert(names, name)
|
||||
end
|
||||
end
|
||||
if #names > 0 then
|
||||
cow = names[math.random(#names)]
|
||||
end
|
||||
local moods = { "b", "d", "g", "p", "s", "t", "w", "y" }
|
||||
local mood = moods[math.random(#moods)]
|
||||
local cmd = string.format(
|
||||
"fortune -a -s | fmt -80 -s | cowthink -%s -f %s -n",
|
||||
mood,
|
||||
cow
|
||||
)
|
||||
local ok, lines = pcall(vim.fn.systemlist, cmd)
|
||||
if not ok or #lines == 0 then
|
||||
return { "Welcome to Neovim" }
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
dashboard.section.header.val = fortune_header()
|
||||
dashboard.section.buttons.val = {
|
||||
dashboard.button("e", " New file", ":enew<CR>"),
|
||||
dashboard.button("f", " Find file", ":Telescope find_files<CR>"),
|
||||
dashboard.button("g", " Live grep", ":Telescope live_grep<CR>"),
|
||||
dashboard.button("s", " Restore session", ":lua require('persistence').load()<CR>"),
|
||||
dashboard.button("q", " Quit", ":qa<CR>"),
|
||||
}
|
||||
alpha.setup(dashboard.opts)
|
||||
|
||||
vim.keymap.set("n", "<leader><leader>o", ":Alpha<CR>")
|
||||
end,
|
||||
},
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
vim.g.vimwiki_list = {
|
||||
{
|
||||
path = "~/synced-notebooks/bradwiki/",
|
||||
syntax = "markdown",
|
||||
ext = ".md",
|
||||
diary_rel_path = "daily_notes/",
|
||||
diary_index = "index",
|
||||
diary_header = "Daily Notes",
|
||||
auto_tags = 1,
|
||||
auto_diary_index = 1,
|
||||
auto_generate_links = 1,
|
||||
auto_generate_tags = 1,
|
||||
},
|
||||
{
|
||||
path = "~/synced-notebooks/work_notebook/",
|
||||
syntax = "markdown",
|
||||
ext = ".md",
|
||||
diary_rel_path = "daily_notes/",
|
||||
diary_index = "index",
|
||||
diary_header = "Daily Notes",
|
||||
auto_tags = 1,
|
||||
auto_diary_index = 1,
|
||||
auto_generate_links = 1,
|
||||
auto_generate_tags = 1,
|
||||
},
|
||||
{ path = "~/synced-notebooks/privatewiki/", syntax = "markdown", ext = ".md" },
|
||||
{ path = "~/synced-notebooks/sharedwiki/", syntax = "markdown", ext = ".md" },
|
||||
}
|
||||
vim.g.vimwiki_map_prefix = "<Leader>m"
|
||||
vim.g.vimwiki_folding = "list"
|
||||
vim.g.markdown_folding = 1
|
||||
|
||||
vim.g.zettel_options = {
|
||||
{},
|
||||
{
|
||||
front_matter = { { "tags", "" }, { "type", "note" } },
|
||||
template = "~/work_notebook/templates/daily_note.tpl",
|
||||
},
|
||||
}
|
||||
|
||||
vim.g.table_mode_map_prefix = "<Leader><Leader>t"
|
||||
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
group = vim.api.nvim_create_augroup("VimwikiFiletype", { clear = true }),
|
||||
pattern = "vimwiki",
|
||||
callback = function()
|
||||
vim.opt_local.breakindent = true
|
||||
vim.opt_local.breakindentopt = "shift:2"
|
||||
end,
|
||||
})
|
||||
|
||||
return {
|
||||
{ "vimwiki/vimwiki", lazy = false },
|
||||
{ "michal-h21/vim-zettel", dependencies = { "vimwiki/vimwiki" }, lazy = false },
|
||||
{ "dhruvasagar/vim-table-mode", cmd = "TableModeToggle" },
|
||||
{
|
||||
"iamcco/markdown-preview.nvim",
|
||||
build = function() vim.fn["mkdp#util#install"]() end,
|
||||
ft = { "markdown", "vimwiki" },
|
||||
keys = {
|
||||
{ "<localleader>v", "<Plug>MarkdownPreviewToggle", ft = { "markdown", "vimwiki" }, desc = "Toggle markdown preview" },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
local M = {}
|
||||
|
||||
-- Open ranger from within nvim; if it writes a chosen path to its return
|
||||
-- file, open that file. Mirrors the old vimscript Ranger() function.
|
||||
function M.ranger()
|
||||
if vim.fn.executable("ranger") == 0 then
|
||||
vim.notify("ranger is not installed", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
local tmpfile = vim.fn.system("mktemp -u"):gsub("\n", "")
|
||||
vim.cmd(("silent !RANGER_RETURN_FILE=%s ranger"):format(tmpfile))
|
||||
if vim.fn.filereadable(tmpfile) == 1 then
|
||||
local filetoedit = vim.fn.system("cat " .. tmpfile)
|
||||
vim.cmd("edit " .. filetoedit)
|
||||
vim.fn.delete(tmpfile)
|
||||
end
|
||||
vim.cmd("redraw!")
|
||||
end
|
||||
|
||||
-- Delete trailing whitespace on the current buffer, preserving cursor
|
||||
-- position. Mirrors the old vimscript DeleteTrailingWS() function.
|
||||
function M.delete_trailing_ws()
|
||||
local view = vim.fn.winsaveview()
|
||||
vim.cmd([[silent! %s/\s\+$//ge]])
|
||||
vim.fn.winrestview(view)
|
||||
end
|
||||
|
||||
return M
|
||||
+3
-4
@@ -22,6 +22,7 @@ shadow-exclude = [
|
||||
"class_g = 'lattedock'",
|
||||
"_GTK_FRAME_EXTENTS@:c"
|
||||
]
|
||||
xinerama-shadow-crop = true;
|
||||
|
||||
### Blur
|
||||
blur:{
|
||||
@@ -51,6 +52,8 @@ backend = "glx";
|
||||
#backend = "xrender";
|
||||
vsync = true;
|
||||
|
||||
glx-no-stencil = true;
|
||||
glx-no-rebind-pixmap = true;
|
||||
xrender-sync-fence = true;
|
||||
|
||||
dbus=true;
|
||||
@@ -71,7 +74,3 @@ mark-wmwin-focused = true;
|
||||
mark-ovredir-focused = true;
|
||||
use-ewmh-active-win = true;
|
||||
detect-client-leader = true;
|
||||
|
||||
## Fixes graphical issues (jitter/flickering) in Jetbrains IDEs
|
||||
## TODO: This doesn't fully fix the issue
|
||||
unredir-if-possible = false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"groups": {
|
||||
"stack_depth": { "fg": "gray1", "bg": "gray10", "attrs": ["bold"] }
|
||||
"stack_depth": { "fg": "gb:light1", "bg": "gb:light4", "attrs": ["bold"] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"groups": {
|
||||
"stack_depth": { "fg": "gb:light1", "bg": "gb:light4", "attrs": ["bold"] }
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
},
|
||||
"pdb": {
|
||||
"colorscheme": "gruvbox",
|
||||
"colorscheme": "default",
|
||||
"theme": "default"
|
||||
},
|
||||
"shell": {
|
||||
|
||||
+5
-129
@@ -1,130 +1,6 @@
|
||||
# Override in .profile.local for custom repo
|
||||
DOTFILES_LOCAL_REPOSITORY="https://gitbox.branderson.io/branderson/dotfiles-local"
|
||||
export TERMINAL=alacritty
|
||||
export EDITOR=nvim
|
||||
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket"
|
||||
alias vim=nvim
|
||||
XDG_DATA_DIRS="/var/lib/flatpak/exports/share:$XDG_DATA_DIRS"
|
||||
|
||||
# Programs expected to be installed by tools
|
||||
# These are the name on $PATH, not the package name
|
||||
dependencies=(
|
||||
tree
|
||||
nvim
|
||||
ag
|
||||
fzf
|
||||
node
|
||||
cowsay
|
||||
fortune
|
||||
)
|
||||
|
||||
# Load dotfiles directory into DOTFILES_DIR
|
||||
# Dotfiles directory should be in environment or ~/.dotfiles-config
|
||||
if [ -f "$HOME/.dotfiles-config" ]; then
|
||||
source "$HOME/.dotfiles-config"
|
||||
fi
|
||||
|
||||
# PATHs
|
||||
## Earlier paths in PATH checked first
|
||||
if [ -d /usr/games ]; then
|
||||
export PATH="/usr/games:$PATH"
|
||||
fi
|
||||
if [ -d /opt/homebrew/opt/coreutils/libexec/gnubin ]; then
|
||||
export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH"
|
||||
fi
|
||||
if [ -d /opt/homebrew/opt/python@3.13/libexec/bin ]; then
|
||||
export PATH="/opt/homebrew/opt/python@3.13/libexec/bin:$PATH"
|
||||
fi
|
||||
if [ -d /opt/homebrew/bin ]; then
|
||||
export PATH="/opt/homebrew/bin:$PATH"
|
||||
fi
|
||||
## Keep user bin directories first in PATH so they're preferred
|
||||
if [ -d "$DOTFILES_DIR/bin" ]; then
|
||||
export PATH="$DOTFILES_DIR/bin:$PATH"
|
||||
fi
|
||||
## OS-specific binaries
|
||||
if [[ $OSTYPE == 'darwin'* ]]; then
|
||||
if [[ -d "$DOTFILES_DIR/macbin" ]]; then
|
||||
export PATH="$DOTFILES_DIR/macbin:$PATH"
|
||||
fi
|
||||
elif [[ $OSTYPE == 'linux'* ]]; then
|
||||
if [[ -d "$DOTFILES_DIR/linbin" ]]; then
|
||||
export PATH="$DOTFILES_DIR/linbin:$PATH"
|
||||
fi
|
||||
fi
|
||||
if [ -d "$HOME/.local/bin" ]; then
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
fi
|
||||
if [ -d "$HOME/bin" ]; then
|
||||
export PATH="$HOME/bin:$PATH"
|
||||
fi
|
||||
|
||||
source "$HOME/.bash_functions"
|
||||
|
||||
# Set up brew if on macOS
|
||||
if [[ $OSTYPE == 'darwin'* ]]; then
|
||||
if [ -f /opt/homebrew/bin/brew ]; then
|
||||
eval "$(/opt/homebrew/bin/brew shellenv)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Prefer alacritty terminal if installed
|
||||
if program_installed alacritty; then
|
||||
export TERMINAL=alacritty
|
||||
fi
|
||||
|
||||
# Prefer neovim, if installed
|
||||
if program_installed nvim; then
|
||||
export EDITOR=nvim
|
||||
alias vim=nvim
|
||||
fi
|
||||
|
||||
if program_installed powerline; then
|
||||
# Make powerline support Python virtualenvs
|
||||
export POWERLINE_COMMAND="$DOTFILES_DIR/scripts/powerline-wrapper.sh"
|
||||
fi
|
||||
|
||||
# alias make='nocorrect make'
|
||||
# alias git='nocorrect git'
|
||||
# Crontab
|
||||
#alias crontab-e='vim ~/.crontab && crontab ~/.crontab'
|
||||
|
||||
#alias sl="sl -laF"
|
||||
|
||||
# Put flatpak apps in XDG
|
||||
if program_installed flatpak; then
|
||||
export XDG_DATA_DIRS="/var/lib/flatpak/exports/share:$XDG_DATA_DIRS"
|
||||
fi
|
||||
|
||||
# pyenv paths
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
export PATH="$PYENV_ROOT/bin:$PATH"
|
||||
|
||||
# Make sure ssh-agent is available
|
||||
if [ -n "$LOAD_SSH_KEYS" ] && [ "$LOAD_SSH_KEYS" -eq 1 ]; then
|
||||
if program_installed ssh-add && [ ! -z "$SSH_AUTH_SOCK" ] && [ -d "$SSH_AUTH_SOCK" ] > /dev/null 2>&1 ; then
|
||||
# Add SSH keys to agent if not already added
|
||||
## Find existing keys, supporting one layer of folder organization
|
||||
public_keys=($(find "$HOME/.ssh/" -maxdepth 2 -name "*.pub"))
|
||||
if [ ${#public_keys[@]} -gt 0 ]; then
|
||||
for public_key in "${public_keys[@]}"; do
|
||||
if [[ $(uname -s) == Linux* ]]; then
|
||||
# Add SSH keys only if not already present
|
||||
ssh-add -l | grep -q `ssh-keygen -lf "$public_key" | awk '{print $2}'` || ssh-add -k
|
||||
break
|
||||
elif [[ $(uname -s) == Darwin* ]]; then
|
||||
# Add SSH keys only if not already present
|
||||
ssh-add -l | grep -q `ssh-keygen -lf "$public_key" | awk '{print $2}'` || ssh-add --apple-use-keychain
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if program_installed i3; then
|
||||
# Set QT environment variables
|
||||
export QT_QPA_PLATFORMTHEME="qt5ct"
|
||||
fi
|
||||
|
||||
# Extra domains claude-sandbox's firewall should allow beyond its defaults
|
||||
export ALLOWED_DOMAINS=""
|
||||
|
||||
# Gitea host, used by bin/gitea-pr and claude-sandbox's git-push-guard hook
|
||||
export GITEA_SSH_HOST="gitbox.bradina.localdomain"
|
||||
export GITEA_API="https://gitbox.branderson.io/api/v1"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
[Unit]
|
||||
After=network-online.target nss-lookup.target
|
||||
ConditionPathIsDirectory=/etc/pacman.d/gnupg/
|
||||
ConditionPathIsReadWrite=/etc/pacman.d/gnupg/
|
||||
ConditionFileIsExecutable=/usr/bin/archlinux-keyring-wkd-sync
|
||||
Description=Refresh existing keys of archlinux-keyring
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/archlinux-keyring-wkd-sync
|
||||
|
||||
CapabilityBoundingSet=
|
||||
DeviceAllow=
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
NoNewPrivileges=true
|
||||
PrivateDevices=true
|
||||
PrivateTmp=true
|
||||
ProtectClock=true
|
||||
ProtectControlGroups=true
|
||||
ProtectHome=true
|
||||
ProtectHostname=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectProc=noaccess
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/etc/pacman.d/gnupg
|
||||
RemoveIPC=true
|
||||
RestrictAddressFamilies=~AF_PACKET AF_NETLINK
|
||||
RestrictNamespaces=true
|
||||
RestrictRealtime=true
|
||||
RestrictSUIDSGID=true
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallFilter=~@resources
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
[Unit]
|
||||
Description=Refresh existing PGP keys of archlinux-keyring regularly
|
||||
|
||||
[Timer]
|
||||
OnCalendar=weekly
|
||||
Persistent=true
|
||||
RandomizedDelaySec=12h
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
[Unit]
|
||||
Description=Update wallpapers when display change detected
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=brad
|
||||
Environment=DISPLAY=:0
|
||||
ExecStartPre=/bin/sleep 3
|
||||
# TODO: The file might not be here if ~/.dotfiles-config set to somewhere else
|
||||
ExecStart=/home/brad/dotfiles/bin/update-wallpaper
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
+2
-163
@@ -1,163 +1,2 @@
|
||||
# ----------------------------------------------------------------------------
|
||||
# Prefix
|
||||
# ----------------------------------------------------------------------------
|
||||
set -g prefix C-a
|
||||
unbind C-b
|
||||
bind C-a send-prefix
|
||||
bind Tab last-window
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# General behavior
|
||||
# ----------------------------------------------------------------------------
|
||||
set-option -g set-titles on
|
||||
set -g default-terminal "screen-256color"
|
||||
set -g history-limit 10000
|
||||
set -sg escape-time 1
|
||||
set-option -g base-index 1
|
||||
setw -g pane-base-index 1
|
||||
setw -g aggressive-resize on
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Windows and panes
|
||||
# ----------------------------------------------------------------------------
|
||||
bind T swap-window -t 1
|
||||
bind | split-window -h -c "#{pane_current_path}"
|
||||
bind - split-window -v -c "#{pane_current_path}"
|
||||
bind c new-window -c "#{pane_current_path}"
|
||||
|
||||
bind h select-pane -L
|
||||
bind j select-pane -D
|
||||
bind k select-pane -U
|
||||
bind l select-pane -R
|
||||
|
||||
# Smart pane switching with awareness of vim splits
|
||||
is_vim='echo "#{pane_current_command}" | grep -iqE "(^|\/)g?(view|n?vim?)(diff)?$"'
|
||||
# Claude Code also wants its own use of C-j (insert newline), same as vim
|
||||
# wants C-h/j/k/l for its own window navigation -- forward it raw instead of
|
||||
# letting tmux swallow it for pane-switching.
|
||||
is_vim_or_claude='echo "#{pane_current_command}" | grep -iqE "(^|\/)(g?(view|n?vim?)(diff)?|claude)$"'
|
||||
bind -n C-h if-shell "$is_vim" "send-keys C-h" "select-pane -L"
|
||||
bind -n C-j if-shell "$is_vim_or_claude" "send-keys C-j" "select-pane -D"
|
||||
bind -n C-k if-shell "$is_vim" "send-keys C-k" "select-pane -U"
|
||||
bind -n C-l if-shell "$is_vim" "send-keys C-l" "select-pane -R"
|
||||
bind -n 'C-\' if-shell "$is_vim" "send-keys 'C-\\'" "select-pane -l"
|
||||
|
||||
bind -r C-h select-window -t :-
|
||||
bind -r C-l select-window -t :+
|
||||
|
||||
bind a setw synchronize-panes
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Copy mode and mouse
|
||||
# ----------------------------------------------------------------------------
|
||||
setw -g mode-keys vi
|
||||
set-option -g -q mouse on
|
||||
|
||||
bind y run 'tmux save-buffer - | $HOME/dotfiles/bin/clipboard-copy'
|
||||
bind C-y run 'tmux save-buffer - | $HOME/dotfiles/bin/clipboard-copy'
|
||||
|
||||
# Let escape sequences a pane explicitly DCS-wraps for passthrough (e.g.
|
||||
# sixel image data, see the iTerm2 terminal-features line below) reach the
|
||||
# real terminal, even when nested behind ssh/tmux layers. Not used for the
|
||||
# clipboard -- that has its own relay via the pane-set-clipboard hook above.
|
||||
# allow-passthrough was added in tmux 3.3; older tmux doesn't understand the
|
||||
# option, so only set it when the running tmux is new enough.
|
||||
if-shell "tmux -V | awk '{print $2}' | awk -F. '{if ($1*1000+$2 >= 3003) exit 0; else exit 1}'" 'set -g allow-passthrough on'
|
||||
|
||||
# Let tmux tell Shift+Enter apart from plain Enter and forward that
|
||||
# distinction inward, so Claude Code's Shift+Enter-inserts-newline shortcut
|
||||
# works. extended-keys/terminal-features need tmux >= 3.2. The terminal
|
||||
# name is matched with '*' rather than the commonly-suggested 'xterm*',
|
||||
# because a nested tmux's "terminal" is the outer tmux, which reports
|
||||
# itself using default-terminal above (screen-256color) -- 'xterm*' would
|
||||
# never match that, breaking the nested case this is meant to support.
|
||||
#
|
||||
# Investigated as a suspect for multi-line paste getting corrupted with
|
||||
# literal CSI 27;<mod>;<key>~ garbage in place of newlines (see Claude Code
|
||||
# issues #43169 and #30239) -- turning this off did not fix that paste
|
||||
# corruption, so it's an upstream Claude Code paste-tokenizer bug, not
|
||||
# something caused by this setting. Restored.
|
||||
if-shell "tmux -V | awk '{print $2}' | awk -F. '{if ($1*1000+$2 >= 3002) exit 0; else exit 1}'" 'set -s extended-keys on; set -as terminal-features "*:extkeys"'
|
||||
|
||||
# Accept pane-originated OSC 52 clipboard-set attempts into a real tmux
|
||||
# buffer. Only on-vs-off matters here -- off would refuse to even look at
|
||||
# these escapes; "on" (rather than "external") is needed so the pane-set-
|
||||
# clipboard hook below has an actual buffer to read.
|
||||
set -g set-clipboard on
|
||||
|
||||
# tmux's own mechanism for forwarding an accepted clipboard-set (or its own
|
||||
# copy-mode copies) to the real terminal is generating a fresh OSC 52 write
|
||||
# via the "Ms" terminfo capability -- confirmed unreliable here (both
|
||||
# pane-forwarded and tmux-generated OSC 52 silently failed to reach the
|
||||
# clipboard even with Ms correctly registered against Alacritty). Relay it
|
||||
# ourselves instead: whenever any pane sets the clipboard, grab what was
|
||||
# just captured above and write it straight to this client's own terminal
|
||||
# device. If that's a real terminal, this is the final hop and we're done.
|
||||
# If it's actually another tmux one layer further out (e.g. this tmux is
|
||||
# itself running inside an outer tmux over ssh), that data becomes pane
|
||||
# output there, and the *same* hook -- present there too, since it's the
|
||||
# same dotfiles -- fires again. That recurses cleanly through however many
|
||||
# tmux layers are actually in the chain, without any of them needing to
|
||||
# know the total count in advance.
|
||||
set-hook -g pane-set-clipboard 'run-shell "tmux save-buffer - | $HOME/dotfiles/bin/tmux-clipboard-relay #{client_tty}"'
|
||||
|
||||
# The default/no-argument form of copy-pipe (used by double/triple-click and
|
||||
# a couple of default bindings) pipes to this option instead of a literal
|
||||
# command -- route it through the same working script as the explicit
|
||||
# copy-pipe bindings in tmux/modern.conf.
|
||||
set -g copy-command '$HOME/dotfiles/bin/clipboard-copy'
|
||||
|
||||
# iTerm2 supports SIXEL images (since 3.3.0) but doesn't set a distinct TERM
|
||||
# to identify itself, so this matches on its usual "xterm-256color" report --
|
||||
# any other client reporting that same TERM will also be treated as
|
||||
# sixel-capable. Alacritty is deliberately left off: upstream Alacritty has
|
||||
# no sixel support, so telling tmux otherwise would replace today's clean
|
||||
# text placeholder with garbled escape data.
|
||||
set -as terminal-features ",xterm-256color:sixel"
|
||||
|
||||
# tmux renamed the copy-mode key table (vi-copy -> copy-mode-vi) and changed
|
||||
# bind-key's table flag (-t -> -T) in 2.4. Source the file matching the
|
||||
# running tmux so old tmux never parses syntax it doesn't understand.
|
||||
if-shell "tmux -V | awk '{print $2}' | awk -F. '{if ($1*1000+$2 < 2004) exit 0; else exit 1}'" 'source-file ~/.tmux/legacy.conf' 'source-file ~/.tmux/modern.conf'
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Misc bindings
|
||||
# ----------------------------------------------------------------------------
|
||||
bind-key C run-shell "cal $(date +%Y)"
|
||||
bind r source-file ~/.tmux.conf \; display "Reloaded config"
|
||||
|
||||
# A pane whose connection died mid-redraw (e.g. an SSH session killed by the
|
||||
# laptop sleeping) can get stuck with tmux thinking the cursor is hidden,
|
||||
# since that's tracked from the pane's own output and nothing ever sent the
|
||||
# matching show-cursor sequence. Re-run it in the active pane to fix it.
|
||||
bind R send-keys "printf '\\033[?25h'" Enter
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Powerline (guarded so machines without it don't error on load)
|
||||
# ----------------------------------------------------------------------------
|
||||
run-shell "command -v powerline-daemon >/dev/null 2>&1 && powerline-daemon -q; true"
|
||||
run-shell "command -v powerline-config >/dev/null 2>&1 && powerline-config tmux setup; true"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Status bar
|
||||
# ----------------------------------------------------------------------------
|
||||
# Flash the status bar when this client's prefix key is armed, so nested
|
||||
# tmux sessions (e.g. laptop -> ssh -> devbox tmux) make it obvious which
|
||||
# layer is about to receive the next keystroke. Falls back to whatever
|
||||
# status-style powerline set above (captured here) the rest of the time.
|
||||
# Colors match gruvbox's own visual/selection convention (bg0 on orange).
|
||||
set -g @status_style_armed "bg=#fe8019,fg=#282828"
|
||||
run-shell 'tmux set-option -g @status_style_normal "$(tmux show-options -gv status-style)"'
|
||||
set -g status-style "#{?client_prefix,#{@status_style_armed},#{@status_style_normal}}"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Plugins (TPM)
|
||||
# ----------------------------------------------------------------------------
|
||||
set -g @plugin 'tmux-plugins/tpm'
|
||||
set -g @plugin 'tmux-plugins/tmux-sensible'
|
||||
|
||||
# Local overrides
|
||||
if-shell "[ -f ~/.tmux_local.conf ]" 'source ~/.tmux_local.conf'
|
||||
|
||||
# Initialize TPM (must be the last line)
|
||||
run '~/.tmux/plugins/tpm/tpm'
|
||||
# run-shell "~/.tmux/verify_tmux_version.sh"
|
||||
source-file "~/.tmux/tmux_3.1c.conf"
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# tmux < 2.4 copy-mode key table (vi-copy, old -t flag) and mouse-wheel/
|
||||
# PageUp passthrough. Sourced conditionally from tmux.conf; see
|
||||
# tmux/modern.conf for tmux >= 2.4.
|
||||
|
||||
bind-key -t vi-copy PPage page-up
|
||||
bind-key -t vi-copy NPage page-down
|
||||
bind-key -t vi-copy WheelUpPane halfpage-up
|
||||
bind-key -t vi-copy WheelDownPane halfpage-down
|
||||
bind-key -t vi-copy v begin-selection
|
||||
bind-key -t vi-copy y copy-pipe "$HOME/dotfiles/bin/clipboard-copy"
|
||||
unbind-key -t vi-copy Enter
|
||||
bind-key -t vi-copy Enter copy-pipe "$HOME/dotfiles/bin/clipboard-copy"
|
||||
|
||||
bind-key -n PPage if-shell -F "#{alternate_on}" "send-keys PPage" "copy-mode -e; send-keys PPage"
|
||||
bind-key -n WheelUpPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; copy-mode -e; send-keys -M"
|
||||
bind-key -n WheelDownPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; send-keys -M"
|
||||
@@ -1,24 +0,0 @@
|
||||
# tmux >= 2.4 copy-mode key table and mouse-wheel/PageUp passthrough.
|
||||
# Sourced conditionally from tmux.conf; see tmux/legacy.conf for older tmux.
|
||||
|
||||
bind-key -T copy-mode-vi PPage send-keys -X page-up
|
||||
bind-key -T copy-mode-vi NPage send-keys -X page-down
|
||||
bind-key -T copy-mode-vi WheelUpPane send-keys -X halfpage-up
|
||||
bind-key -T copy-mode-vi WheelDownPane send-keys -X halfpage-down
|
||||
bind-key -T copy-mode-vi v send-keys -X begin-selection
|
||||
bind-key -T copy-mode-vi y send-keys -X copy-pipe "$HOME/dotfiles/bin/clipboard-copy"
|
||||
unbind-key -T copy-mode-vi Enter
|
||||
bind-key -T copy-mode-vi Enter send-keys -X copy-pipe "$HOME/dotfiles/bin/clipboard-copy"
|
||||
|
||||
# The default MouseDragEnd1Pane binding (copy-selection-and-cancel) relies on
|
||||
# tmux's own OSC 52 write via the Ms terminfo capability, which doesn't
|
||||
# reliably reach every terminal (confirmed broken against Alacritty here).
|
||||
# Route mouse-drag copies through the same clipboard-copy script as keyboard
|
||||
# yanks instead, so both use the mechanism that's actually proven to work.
|
||||
bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "$HOME/dotfiles/bin/clipboard-copy"
|
||||
|
||||
# Let mouse wheel / PageUp pass through to apps using the alternate screen
|
||||
# (vim, less, etc.) instead of always entering copy-mode
|
||||
bind-key -T root PPage if-shell -F "#{alternate_on}" "send-keys PPage" "copy-mode -e; send-keys PPage"
|
||||
bind-key -T root WheelUpPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; copy-mode -e; send-keys -M"
|
||||
bind-key -T root WheelDownPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; send-keys -M"
|
||||
Submodule config/tmux/plugins/tpm deleted from 99469c4a9b
@@ -0,0 +1,37 @@
|
||||
# PREFIX | splits horizontally and PREFIX - splits vertically
|
||||
bind | split-window -h
|
||||
bind - split-window -v
|
||||
|
||||
# mouse support
|
||||
set -g mode-mouse on # scrolling
|
||||
set -g mouse-select-pane on # clicking into panes
|
||||
set -g mouse-resize-pane on # resizing panes
|
||||
set -g mouse-select-window on # select windows
|
||||
|
||||
# toggle mouse support
|
||||
bind m \
|
||||
set -g mode-mouse on \;\
|
||||
set -g mouse-resize-pane on \;\
|
||||
set -g mouse-select-pane on \;\
|
||||
set -g mouse-select-window on \;\
|
||||
display 'Mouse: ON'
|
||||
|
||||
bind M \
|
||||
set -g mode-mouse off \;\
|
||||
set -g mouse-resize-pane off \;\
|
||||
set -g mouse-select-pane off \;\
|
||||
set -g mouse-select-window off \;\
|
||||
display 'Mouse: OFF'
|
||||
|
||||
# ----------------------
|
||||
# set some pretty colors
|
||||
# ----------------------
|
||||
# set pane colors - hilight the active pane
|
||||
# set-option -g pane-border-fg colour235 #base02
|
||||
# set-option -g pane-active-border-fg colour240 #base01
|
||||
|
||||
# # colorize messages in the command line
|
||||
# set-option -g message-bg black #base02
|
||||
# set-option -g message-fg brightred #orange
|
||||
|
||||
source "$HOME/.tmux/tmux_shared.conf"
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
source "$HOME/.tmux/tmux_shared.conf"
|
||||
@@ -0,0 +1,48 @@
|
||||
# PREFIX | splits horizontally and PREFIX - splits vertically
|
||||
bind | split-window -h -c "#{pane_current_path}"
|
||||
bind - split-window -v -c "#{pane_current_path}"
|
||||
|
||||
# open new window with pwd
|
||||
bind c new-window -c "#{pane_current_path}"
|
||||
|
||||
# PREFIX T makes current window index 1
|
||||
bind T swap-window -t 1
|
||||
|
||||
# use vim movement for panes
|
||||
bind h select-pane -L
|
||||
bind j select-pane -D
|
||||
bind k select-pane -U
|
||||
bind l select-pane -R
|
||||
|
||||
# mouse support
|
||||
set-option -g -q mouse on
|
||||
|
||||
# Copy Mode
|
||||
bind-key -t root PPage if-shell -F "#{alternate_on}" "send-keys PPage" "copy-mode -e; send-keys PPage"
|
||||
bind-key -t vi-copy PPage page-up
|
||||
bind-key -t vi-copy NPage page-down
|
||||
# Mouse Wheel
|
||||
bind-key -t root WheelUpPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; copy-mode -e; send-keys -M"
|
||||
bind-key -t root WheelDownPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; send-keys -M"
|
||||
bind-key -t vi-copy WheelUpPane halfpage-up
|
||||
bind-key -t vi-copy WheelDownPane halfpage-down
|
||||
|
||||
# Make sure the client is always using the correct environment
|
||||
# eval $(tmux switch-client \; show-environment -s)
|
||||
|
||||
# Copy/Paste
|
||||
#
|
||||
# provide access to clipboard
|
||||
# set-option -g default-command "reattach-to-user-namespace -l zsh"
|
||||
# set-window-option -g automatic-rename on
|
||||
|
||||
# use vim keybindings in copy mode
|
||||
setw -g mode-keys vi
|
||||
|
||||
# v selection
|
||||
bind-key -t vi-copy v begin-selection
|
||||
bind-key -t vi-copy y copy-pipe "reattach-to-user-namespace pbcopy"
|
||||
|
||||
# make enter use copy pipe
|
||||
unbind -t vi-copy Enter
|
||||
bind-key -t vi-copy Enter copy-pipe "reattach-to-user-namespace pbcopy"
|
||||
@@ -0,0 +1,42 @@
|
||||
# PREFIX | splits horizontally and PREFIX - splits vertically
|
||||
bind | split-window -h -c "#{pane_current_path}"
|
||||
bind - split-window -v -c "#{pane_current_path}"
|
||||
|
||||
# open new window with pwd
|
||||
bind c new-window -c "#{pane_current_path}"
|
||||
|
||||
# use vim keybindings in copy mode
|
||||
setw -g mode-keys vi
|
||||
|
||||
# Copy Mode
|
||||
bind-key -T copy-mode-vi PPage send-keys -X page-up
|
||||
bind-key -T copy-mode-vi NPage send-keys -X page-down
|
||||
|
||||
# Mouse Wheel
|
||||
bind-key -T copy-mode-vi WheelUpPane send-keys -X halfpage-up
|
||||
bind-key -T copy-mode-vi WheelDownPane send-keys -X halfpage-down
|
||||
|
||||
# v selection
|
||||
bind-key -T copy-mode-vi v send-keys -X begin-selection
|
||||
bind-key -T copy-mode-vi y send-keys -X copy-pipe "reattach-to-user-namespace pbcopy"
|
||||
|
||||
# make enter use copy pipe
|
||||
unbind -T copy-mode-vi Enter
|
||||
bind-key -T copy-mode-vi Enter send-keys -X copy-pipe "reattach-to-user-namespace pbcopy"
|
||||
|
||||
set-option -g -q mouse on
|
||||
set-option -g -q mouse-resize-pane on
|
||||
set-option -g -q mouse-select-pane on
|
||||
set-option -g -q mouse-select-window on
|
||||
|
||||
# Enable middle click in urxvt
|
||||
set-option -ga terminal-override ',rxvt-uni*:XT:Ms=\E]52;%p1%s;%p2%s\007'
|
||||
|
||||
# Copy Mode
|
||||
bind-key -T root PPage if-shell -F "#{alternate_on}" "send-keys PPage" "copy-mode -e; send-keys PPage"
|
||||
|
||||
# Mouse Wheel
|
||||
bind-key -T root WheelUpPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; copy-mode -e; send-keys -M"
|
||||
bind-key -T root WheelDownPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; send-keys -M"
|
||||
|
||||
source ~/.tmux/tmux_shared.conf
|
||||
@@ -0,0 +1,42 @@
|
||||
# PREFIX | splits horizontally and PREFIX - splits vertically
|
||||
bind | split-window -h -c "#{pane_current_path}"
|
||||
bind - split-window -v -c "#{pane_current_path}"
|
||||
|
||||
# open new window with pwd
|
||||
bind c new-window -c "#{pane_current_path}"
|
||||
|
||||
# use vim keybindings in copy mode
|
||||
setw -g mode-keys vi
|
||||
|
||||
# Copy Mode
|
||||
bind-key -T copy-mode-vi PPage send-keys -X page-up
|
||||
bind-key -T copy-mode-vi NPage send-keys -X page-down
|
||||
|
||||
# Mouse Wheel
|
||||
bind-key -T copy-mode-vi WheelUpPane send-keys -X halfpage-up
|
||||
bind-key -T copy-mode-vi WheelDownPane send-keys -X halfpage-down
|
||||
|
||||
# v selection
|
||||
bind-key -T copy-mode-vi v send-keys -X begin-selection
|
||||
bind-key -T copy-mode-vi y send-keys -X copy-pipe "reattach-to-user-namespace pbcopy"
|
||||
|
||||
# make enter use copy pipe
|
||||
unbind -T copy-mode-vi Enter
|
||||
bind-key -T copy-mode-vi Enter send-keys -X copy-pipe "reattach-to-user-namespace pbcopy"
|
||||
|
||||
set-option -g -q mouse on
|
||||
set-option -g -q mouse-resize-pane on
|
||||
set-option -g -q mouse-select-pane on
|
||||
set-option -g -q mouse-select-window on
|
||||
|
||||
# Enable middle click in urxvt
|
||||
set-option -ga terminal-override ',rxvt-uni*:XT:Ms=\E]52;%p1%s;%p2%s\007'
|
||||
|
||||
# Copy Mode
|
||||
bind-key -T root PPage if-shell -F "#{alternate_on}" "send-keys PPage" "copy-mode -e; send-keys PPage"
|
||||
|
||||
# Mouse Wheel
|
||||
bind-key -T root WheelUpPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; copy-mode -e; send-keys -M"
|
||||
bind-key -T root WheelDownPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; send-keys -M"
|
||||
|
||||
source ~/.tmux/tmux_shared.conf
|
||||
@@ -0,0 +1,106 @@
|
||||
# use UTF8
|
||||
# set -g utf8
|
||||
# set-window-option -g utf8 on
|
||||
|
||||
# 256 color mode
|
||||
set -g default-terminal "screen-256color"
|
||||
# set-option -sa terminal-overrides ',screen*:RGB'
|
||||
# set-option -ga terminal-overrides ',screen*:Tc'
|
||||
# set-option -sa terminal-overrides ',xterm*:RGB'
|
||||
# set-option -ga terminal-overrides ',xterm*:Tc'
|
||||
# set-option -sa terminal-overrides ',tmux*:RGB'
|
||||
# set-option -ga terminal-overrides ',tmux*:Tc'
|
||||
# set-option -sa terminal-overrides ',rxvt*:RGB'
|
||||
# set-option -ga terminal-overrides ',rxvt*:Tc'
|
||||
|
||||
# Properly handle ssh agent forwarding when reconnecting to session
|
||||
# set-environment -g 'SSH_AUTH_SOCK' ~/.ssh/ssh_auth_sock
|
||||
|
||||
set-option -g set-titles on
|
||||
# Tmux attach will create a new session if one doesn't exist
|
||||
# new-session -n $HOST
|
||||
|
||||
# set scrollback history
|
||||
set -g history-limit 10000
|
||||
|
||||
# set Ctrl-a as prefix and unbind Ctrl-b
|
||||
set -g prefix C-a
|
||||
unbind C-b
|
||||
|
||||
# last active window
|
||||
# bind-key C-a last-window
|
||||
bind Tab last-window
|
||||
|
||||
# pass prefix through to application
|
||||
bind C-a send-prefix
|
||||
|
||||
# don't constrain size unless smaller client looking at window
|
||||
setw -g aggressive-resize on
|
||||
|
||||
# set command delay
|
||||
set -sg escape-time 1
|
||||
|
||||
# set base index to 1 rather than 0
|
||||
set-option -g base-index 1
|
||||
setw -g pane-base-index 1
|
||||
|
||||
# use PREFIX-r to reload config
|
||||
bind r source-file ~/.tmux/tmux_shared.conf \; display "Reloaded config"
|
||||
|
||||
# PREFIX T makes current window index 1
|
||||
bind T swap-window -t 1
|
||||
|
||||
# use vim movement for panes
|
||||
bind h select-pane -L
|
||||
bind j select-pane -D
|
||||
bind k select-pane -U
|
||||
bind l select-pane -R
|
||||
|
||||
# Smart pane switching with awareness of vim splits
|
||||
is_vim='echo "#{pane_current_command}" | grep -iqE "(^|\/)g?(view|n?vim?)(diff)?$"'
|
||||
bind -n C-h if-shell "$is_vim" "send-keys C-h" "select-pane -L"
|
||||
bind -n C-j if-shell "$is_vim" "send-keys C-j" "select-pane -D"
|
||||
bind -n C-k if-shell "$is_vim" "send-keys C-k" "select-pane -U"
|
||||
bind -n C-l if-shell "$is_vim" "send-keys C-l" "select-pane -R"
|
||||
bind -n 'C-\' if-shell "$is_vim" "send-keys 'C-\\'" "select-pane -l"
|
||||
|
||||
# bind C-h and C-l to cycle through panes
|
||||
bind -r C-h select-window -t :-
|
||||
bind -r C-l select-window -t :+
|
||||
|
||||
# Synchonize keyboard input across panes
|
||||
bind a setw synchronize-panes
|
||||
|
||||
# open calendar
|
||||
bind-key C run-shell "cal $(date +%Y)"
|
||||
|
||||
# Make sure the client is always using the correct environment
|
||||
# eval $(tmux switch-client \; show-environment -s)
|
||||
|
||||
bind y run 'tmux save-buffer - | reattach-to-user-namespace pbcopy '
|
||||
bind C-y run 'tmux save-buffer - | reattach-to-user-namespace pbcopy '1
|
||||
|
||||
# ----------------------
|
||||
# set some pretty colors
|
||||
# ----------------------
|
||||
# set pane colors - hilight the active pane
|
||||
# set-option -g pane-border-fg colour235 #base02
|
||||
# set-option -g pane-active-border-fg colour240 #base01
|
||||
|
||||
# Show active pane
|
||||
# set-window-option -g window-style 'bg=#181818'
|
||||
# set-window-option -g window-active-style 'bg=black'
|
||||
# set-window-option -g pane-active-border-style ''
|
||||
# set-window-option -g pane-active-border-style 'fg=#181818'
|
||||
|
||||
# colorize messages in the command line
|
||||
# set-option -g message-bg black #base02
|
||||
# set-option -g message-fg brightred #orange
|
||||
|
||||
# Powerline
|
||||
run-shell "powerline-daemon -q"
|
||||
run-shell "powerline-config tmux setup"
|
||||
# source-file $POWERLINE_ROOT/bindings/tmux/powerline.conf
|
||||
|
||||
# Local overrides
|
||||
if-shell "[ -f ~/.tmux_local.conf ]" 'source ~/.tmux_local.conf'
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
verify_tmux_version() {
|
||||
tmux_home=~/.tmux
|
||||
tmux_version="$(tmux -V | cut -c 6-)"
|
||||
|
||||
# This is necessary because 3.1c gives a syntax error in bc
|
||||
# TODO Fix this it will break with the next tmux version
|
||||
if [[ $tmux_version -eq "3.1c"]] ; then
|
||||
tmux source-file $tmux_home/tmux_3.1c.conf
|
||||
exit
|
||||
if [[ $(echo "$tmux_version >= 2.5" | bc) -eq 1 ]] ; then
|
||||
tmux source-file $tmux_home/tmux_3.1c.conf
|
||||
exit
|
||||
elif [[ $(echo "$tmux_version >= 2.1" | bc) -eq 1 ]] ; then
|
||||
tmux source-file $tmux_home/tmux_2.1_to_2.5.conf
|
||||
exit
|
||||
elif [[ $(echo "$tmux_version >= 1.9" | bc) -eq 1 ]] ; then
|
||||
tmux source-file $tmux_home/tmux_1.9_to_2.1.conf
|
||||
exit
|
||||
else
|
||||
tmux source-file $tmux_home/tmux_1.9_down.conf
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
verify_tmux_version
|
||||
@@ -1 +0,0 @@
|
||||
ACTION=="change", SUBSYSTEM=="drm", RUN+="/usr/bin/systemctl start --no-block update-wallpapers.service"
|
||||
+3
-90
@@ -1,30 +1,3 @@
|
||||
# Returns 0 if program is installed and 1 otherwise
|
||||
function program_installed {
|
||||
local return_=0
|
||||
|
||||
type $1 >/dev/null 2>&1 || { local return_=1; }
|
||||
|
||||
return "$return_"
|
||||
}
|
||||
|
||||
function cd {
|
||||
builtin cd "$@"
|
||||
|
||||
if [[ -z "$VIRTUAL_ENV" ]] ; then
|
||||
# If env folder is found then activate the vitualenv
|
||||
if [[ -d ./venv ]] ; then
|
||||
source ./venv/bin/activate
|
||||
fi
|
||||
else
|
||||
# If no longer within virtualenv, deactivate
|
||||
parentdir="$(dirname "$VIRTUAL_ENV")"
|
||||
if [[ "$PWD"/ != "$parentdir"/* ]] ; then
|
||||
deactivate
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
function list_processes() {
|
||||
# List all processes matching the given process name string
|
||||
ps aux | grep $(whoami) | grep -i "$1" | grep -v grep
|
||||
@@ -78,19 +51,13 @@ function check_dotfiles_version {
|
||||
# Return 2 if the repo is behind the remote
|
||||
# Return 3 if the repo is ahead of the remote
|
||||
# Return 4 if the repo has diverged from the remote
|
||||
# Return 5 if DOTFILES_DIR is not set
|
||||
if [ -z $DOTFILES_DIR ]; then
|
||||
return 5
|
||||
fi
|
||||
|
||||
if [ ! -d $DOTFILES_DIR ]; then
|
||||
if [ ! -d $HOME/dotfiles ]; then
|
||||
return 2
|
||||
fi
|
||||
|
||||
pushd $DOTFILES_DIR > /dev/null
|
||||
pushd $HOME/dotfiles > /dev/null
|
||||
|
||||
# TODO: This may be gitbox
|
||||
git fetch origin > /dev/null
|
||||
git fetch origin
|
||||
UPSTREAM=${1:-'@{u}'}
|
||||
LOCAL=$(git rev-parse @)
|
||||
REMOTE=$(git rev-parse "$UPSTREAM")
|
||||
@@ -108,57 +75,3 @@ function check_dotfiles_version {
|
||||
return 4
|
||||
fi
|
||||
}
|
||||
|
||||
function set_python_roots {
|
||||
PYTHON_ROOTS=()
|
||||
if [ -d $HOME/.local/lib/ ]; then
|
||||
for directory in $(echo $HOME/.local/lib/python*(N)); do
|
||||
PYTHON_ROOTS+=$directory
|
||||
done
|
||||
fi
|
||||
if [ -d $HOME/.local/share/pipx/venvs/powerline-status/lib/ ]; then
|
||||
for directory in $(echo $HOME/.local/share/pipx/venvs/powerline-status/lib/python*(N)); do
|
||||
PYTHON_ROOTS+=$directory
|
||||
done
|
||||
fi
|
||||
if [ -d $HOME/.local/pipx/venvs/powerline-status/lib/ ]; then
|
||||
for directory in $(echo $HOME/.local/pipx/venvs/powerline-status/lib/python*(N)); do
|
||||
PYTHON_ROOTS+=$directory
|
||||
done
|
||||
fi
|
||||
if [ -d /usr/lib/ ]; then
|
||||
for directory in $(echo /usr/lib/python*(N)); do
|
||||
PYTHON_ROOTS+=$directory
|
||||
done
|
||||
fi
|
||||
if [ -d /usr/local/lib/ ]; then
|
||||
# TODO: Make this not print warnings when the directory doesn't exit
|
||||
for directory in $(echo /usr/local/lib/python*(N)); do
|
||||
PYTHON_ROOTS+=$directory
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
function set_powerline_zsh_sources {
|
||||
set_python_roots
|
||||
# Powerline plugin from distribution agnostic install directory
|
||||
## Assemble list of possible sources
|
||||
declare -ag POWERLINE_ZSH_SOURCES=()
|
||||
if [[ -a $POWERLINE_ROOT/bindings/zsh/powerline.zsh ]]; then
|
||||
# Prefer user-set powerline directory
|
||||
POWERLINE_ZSH_SOURCES+=$POWERLINE_ROOT/bindings/zsh/powerline.zsh
|
||||
fi
|
||||
if [ -d /usr/share/powerline ]; then
|
||||
# Apt installs here
|
||||
POWERLINE_ZSH_SOURCES+="/usr/share/powerline/bindings/zsh/powerline.zsh"
|
||||
fi
|
||||
# If $POWERLINE_ROOT not set or set incorrectly, try default locations
|
||||
for directory in $PYTHON_ROOTS; do
|
||||
#for directory in $(echo $template); do
|
||||
if [ -d "$directory" ]; then
|
||||
POWERLINE_ZSH_SOURCES+="$directory"/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
POWERLINE_ZSH_SOURCES+="$directory"/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
fi
|
||||
# done
|
||||
done
|
||||
}
|
||||
|
||||
+185
-191
@@ -1,92 +1,93 @@
|
||||
# ZSH-specific environment variables
|
||||
# Override in .profile.local
|
||||
|
||||
## oh-my-zsh
|
||||
# Automatically update oh-my-zsh without asking
|
||||
DISABLE_UPDATE_PROMPT=true
|
||||
# Disable oh-my-zsh auto-update checks
|
||||
DISABLE_AUTO_UPDATE=false
|
||||
# How often to check for oh-my-zsh updates
|
||||
UPDATE_ZSH_DAYS=30
|
||||
# Disable cowsay on login by default.
|
||||
NO_ZSHRC_COWSAY=1
|
||||
# Theme to load [~/.oh-my-zsh/themes/]
|
||||
ZSH_THEME="agnoster"
|
||||
# Automatic window title setting
|
||||
DISABLE_AUTO_TITLE="true"
|
||||
# Command correction support
|
||||
ENABLE_CORRECTION="false"
|
||||
# Case-sensitive completion
|
||||
CASE_SENSITIVE="false"
|
||||
# ls colors
|
||||
DISABLE_LS_COLORS="false"
|
||||
# Red ... on completion waiting
|
||||
COMPLETION_WAITING_DOTS="true"
|
||||
|
||||
# Uncomment the following line if you want to disable marking untracked files
|
||||
# under VCS as dirty. This makes repository status check for large repositories
|
||||
# much, much faster.
|
||||
# DISABLE_UNTRACKED_FILES_DIRTY="true"
|
||||
#
|
||||
# Uncomment the following line if you want to change the command execution time
|
||||
# stamp shown in the history command output.
|
||||
# The optional three formats: "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"
|
||||
# HIST_STAMPS="mm/dd/yyyy"
|
||||
|
||||
# User profiles
|
||||
source $HOME/.profile
|
||||
# Define $POWERLINE_ROOT
|
||||
if [ -f $HOME/.profile.local ]; then
|
||||
set -a
|
||||
source $HOME/.profile.local
|
||||
set +a
|
||||
fi
|
||||
# Gruvbox colors
|
||||
# source "$HOME/.vim/bundle/gruvbox/gruvbox_256palette.sh"
|
||||
[[ -f "$HOME/dotfiles/dependencies/gruvbox/gruvbox_256palette.sh" ]] && source "$HOME/dotfiles/dependencies/gruvbox/gruvbox_256palette.sh"
|
||||
|
||||
# Custom functions
|
||||
source "$HOME/.zsh_functions"
|
||||
|
||||
if [ -n "$DOTFILES_DIR" ] && [ -d "$DOTFILES_DIR" ]; then
|
||||
# Load dependencies
|
||||
## oh-my-zsh
|
||||
if [ ! -f "$DOTFILES_DIR/dependencies/ohmyzsh/oh-my-zsh.sh" ]; then
|
||||
cd "$DOTFILES_DIR"
|
||||
git submodule update --init
|
||||
cd -
|
||||
fi
|
||||
export ZSH=$DOTFILES_DIR/dependencies/ohmyzsh
|
||||
## Gruvbox colors
|
||||
[[ -f "$DOTFILES_DIR/dependencies/gruvbox/gruvbox_256palette.sh" ]] && source "$DOTFILES_DIR/dependencies/gruvbox/gruvbox_256palette.sh"
|
||||
fi
|
||||
missing_dependencies=()
|
||||
for dependency in "${dependencies[@]}"; do
|
||||
if ! program_installed $dependency; then
|
||||
missing_dependencies+="$dependency"
|
||||
fi
|
||||
done
|
||||
if [ "${#missing_dependencies[@]}" -gt 0 ]; then
|
||||
echo "Missing dependencies: ${missing_dependencies[@]}"
|
||||
fi
|
||||
# Path to your oh-my-zsh installation.
|
||||
export ZSH=$HOME/dotfiles/dependencies/ohmyzsh
|
||||
#export RUST_SRC_PATH=/usr/local/src/rust/src
|
||||
|
||||
# Dircolors
|
||||
# eval $(dircolors ~/.dircolors)
|
||||
# Z
|
||||
# . $HOME/dotfiles/z/z.sh
|
||||
# Set name of the theme to load.
|
||||
# # Look in ~/.oh-my-zsh/themes/
|
||||
# # Optionally, if you set this to "random", it'll load a random theme each
|
||||
# # time that oh-my-zsh is loaded.
|
||||
ZSH_THEME="agnoster"
|
||||
# Base16 Shell
|
||||
# BASE16_SHELL="$HOME/.config/base16-shell/base16-tomorrow.dark.sh"
|
||||
# [[ -s $BASE16_SHELL ]] && source $BASE16_SHELL
|
||||
# BASE16_SHELL="$HOME/.config/base16-shell/base16-ocean.dark.sh"
|
||||
# [[ -s $BASE16_SHELL ]] && source $BASE16_SHELL
|
||||
#
|
||||
# # Uncomment the following line to use case-sensitive completion.
|
||||
# # CASE_SENSITIVE="true"
|
||||
#
|
||||
# # Uncomment the following line to disable bi-weekly auto-update checks.
|
||||
# # DISABLE_AUTO_UPDATE="true"
|
||||
#
|
||||
# # Uncomment the following line to change how often to auto-update (in days).
|
||||
# # export UPDATE_ZSH_DAYS=13
|
||||
#
|
||||
# # Uncomment the following line to disable colors in ls.
|
||||
# # DISABLE_LS_COLORS="true"
|
||||
#
|
||||
# # Uncomment the following line to disable auto-setting terminal title.
|
||||
# # DISABLE_AUTO_TITLE="true"
|
||||
#
|
||||
# # Uncomment the following line to enable command auto-correction.
|
||||
# # ENABLE_CORRECTION="true"
|
||||
#
|
||||
# # Uncomment the following line to display red dots whilst waiting for completion.
|
||||
# # COMPLETION_WAITING_DOTS="true"
|
||||
#
|
||||
# # Uncomment the following line if you want to disable marking untracked files
|
||||
# # under VCS as dirty. This makes repository status check for large repositories
|
||||
# # much, much faster.
|
||||
# # DISABLE_UNTRACKED_FILES_DIRTY="true"
|
||||
#
|
||||
# # Uncomment the following line if you want to change the command execution time
|
||||
# # stamp shown in the history command output.
|
||||
# # The optional three formats: "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"
|
||||
# # HIST_STAMPS="mm/dd/yyyy"
|
||||
#
|
||||
# # Would you like to use another custom folder than $ZSH/custom?
|
||||
# # ZSH_CUSTOM=/path/to/new-custom-folder
|
||||
|
||||
# # zsh-syntax-highlighting must come last
|
||||
plugins=(archlinux colorize command-not-found cp git github bower grunt npm macos)
|
||||
# tmux
|
||||
zmodload zsh/zpty
|
||||
|
||||
# # User configuration
|
||||
|
||||
# Enable environment variables as CD directories
|
||||
setopt cdablevars
|
||||
# Enable correction for mistyped commands
|
||||
unsetopt correctall
|
||||
setopt appendhistory autocd extendedglob
|
||||
unsetopt beep
|
||||
# alias make='nocorrect make'
|
||||
# alias git='nocorrect git'
|
||||
|
||||
alias -s txt=vim
|
||||
alias -s .c=vim
|
||||
#PATHs
|
||||
#export PATH=$HOME/bin:/usr/local/bin:$PATH
|
||||
#export MANPATH="/usr/local/man:$MANPATH"
|
||||
#PATH="$(ruby -e 'puts Gem.user_dir')/bin:$PATH"
|
||||
# Add user cowpath to COWPATH
|
||||
#COWPATH="$COWPATH:$HOME/dotfiles/cowfiles"
|
||||
# Make a random (cow?) with a random face say something
|
||||
# fortune -a | fmt -80 -s | cowthink -$(shuf -n 1 -e b d g p s t w y) -f $(shuf -n 1 -e $(cowsay -l | tail -n +2)) -n
|
||||
|
||||
if [ -d $ZSH ] && [ ! -z $ZSH ]; then
|
||||
source $ZSH/oh-my-zsh.sh
|
||||
# User profiles
|
||||
source $HOME/.profile
|
||||
# Define $POWERLINE_ROOT
|
||||
if [ -f $HOME/.profile.local ]; then
|
||||
source $HOME/.profile.local
|
||||
fi
|
||||
|
||||
source $ZSH/oh-my-zsh.sh
|
||||
|
||||
# Italic support
|
||||
if [ -f $HOME/.xterm-256color-italic.terminfo ]; then
|
||||
tic $HOME/.xterm-256color-italic.terminfo
|
||||
@@ -95,19 +96,114 @@ if [ -f $HOME/.screen-256color-italic.terminfo ]; then
|
||||
tic $HOME/.screen-256color-italic.terminfo
|
||||
fi
|
||||
|
||||
# TODO: Remove this
|
||||
# Use neovim where available
|
||||
# if type nvim > /dev/null 2>&1; then
|
||||
# alias vim='nvim'
|
||||
# fi
|
||||
if type nvim > /dev/null 2>&1; then
|
||||
alias vim='nvim'
|
||||
fi
|
||||
|
||||
# # Preferred editor for local and remote sessions
|
||||
# # if [[ -n $SSH_CONNECTION ]]; then
|
||||
# export EDITOR='vim'
|
||||
export EDITOR='vim'
|
||||
# export TERM=xterm-256color
|
||||
# TODO ====
|
||||
|
||||
# # else
|
||||
# # export EDITOR='mvim'
|
||||
# # fi
|
||||
#
|
||||
# # Compilation flags
|
||||
# # export ARCHFLAGS="-arch x86_64"
|
||||
#
|
||||
# # ssh
|
||||
# # export SSH_KEY_PATH="~/.ssh/dsa_id"
|
||||
#
|
||||
# # Set personal aliases, overriding those provided by oh-my-zsh libs,
|
||||
# # plugins, and themes. Aliases can be placed here, though oh-my-zsh
|
||||
# # users are encouraged to define aliases within the ZSH_CUSTOM folder.
|
||||
# # For a full list of active aliases, run `alias`.
|
||||
# #
|
||||
# # Example aliases
|
||||
# # alias zshconfig="mate ~/.zshrc"
|
||||
# # alias ohmyzsh="mate ~/.oh-my-zsh"
|
||||
alias -s txt=vim
|
||||
alias -s .c=vim
|
||||
# Crontab
|
||||
#alias crontab-e='vim ~/.crontab && crontab ~/.crontab'
|
||||
|
||||
# Lines configured by zsh-newuser-install
|
||||
setopt appendhistory autocd extendedglob
|
||||
unsetopt beep
|
||||
bindkey -v
|
||||
# End of lines configured by zsh-newuser-install
|
||||
# The following lines were added by compinstall
|
||||
zstyle :compinstall filename '~/.zshrc'
|
||||
|
||||
autoload -Uz compinit
|
||||
compinit
|
||||
# End of lines added by compinstall
|
||||
|
||||
DEFAULT_USER=brad
|
||||
|
||||
# Syntax highlighting configuration
|
||||
# ZSH_HIGHLIGHT_STYLES[path]='bold'
|
||||
# ZSH_HIGHLIGHT_STYLES[path_prefix]=none
|
||||
# ZSH_HIGHLIGHT_STYLES[path_approx]='fg=yellow'
|
||||
# ZSH_HIGHLIGHT_STYLES[globbing]='fg=yellow'
|
||||
|
||||
# Source files
|
||||
#typeset -ga sources
|
||||
#sources+=$HOME/dotfiles/shortcuts.txt
|
||||
|
||||
# Powerline plugin from distribution agnostic install directory
|
||||
# pip install --user powerline-status &>/dev/null
|
||||
if [[ -a $POWERLINE_ROOT/bindings/zsh/powerline.zsh ]]; then
|
||||
# Prefer user-set powerline directory
|
||||
sources+=$POWERLINE_ROOT/bindings/zsh/powerline.zsh
|
||||
else
|
||||
echo "Attempting to source Powerline bindings from default locations.\nPlease set \$POWERLINE_ROOT in .profile.local"
|
||||
# If $POWERLINE_ROOT not set or set incorrectly, try default locations
|
||||
sources+=$HOME/.local/lib/python2.6/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=$HOME/.local/lib/python2.6/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=$HOME/.local/lib/python2.7/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=$HOME/.local/lib/python2.7/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=$HOME/.local/lib/python3.6/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=$HOME/.local/lib/python3.6/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/lib/python2.6/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/lib/python2.6/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/local/lib/python2.6/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/local/lib/python2.6/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/lib/python2.7/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/lib/python2.7/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/local/lib/python2.7/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/local/lib/python2.7/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/lib/python3.6/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/lib/python3.6/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/local/lib/python3.6/site-packages/powerline/bindings/zsh/powerline.zsh
|
||||
sources+=/usr/local/lib/python3.6/dist-packages/powerline/bindings/zsh/powerline.zsh
|
||||
fi
|
||||
foreach file (`echo $sources`)
|
||||
if [[ -a $file ]]; then
|
||||
source $file
|
||||
fi
|
||||
end
|
||||
|
||||
# Check dotfiles version
|
||||
check_dotfiles_version
|
||||
dotfiles_version=$?
|
||||
if [[ $dotfiles_version = 1 ]]; then
|
||||
echo "Dotfiles repository not present at $HOME/dotfiles"
|
||||
elif [[ $dotfiles_version = 2 ]]; then
|
||||
echo "Dotfiles repository at $HOME/dotfiles is outdated"
|
||||
elif [[ $dotfiles_version = 3 ]]; then
|
||||
echo "Dotfiles repository at $HOME/dotfiles is ahead of origin"
|
||||
elif [[ $dotfiles_version = 4 ]]; then
|
||||
echo "Dotfiles repository at $HOME/dotfiles has diverged from origin"
|
||||
fi
|
||||
|
||||
# TMuxinator Completion
|
||||
# source ~/bin/tmuxinator.zsh
|
||||
|
||||
# Dircolors
|
||||
# eval $(dircolors ~/.dircolors)
|
||||
|
||||
# Keybindings
|
||||
bindkey -M vicmd 'K' run-help
|
||||
@@ -119,128 +215,26 @@ bindkey -M viins ',,' vi-cmd-mode
|
||||
#bindkey '^o' ranger-cd
|
||||
#zle -N terminal-clock
|
||||
#bindkey '^t' terminal-clock
|
||||
# TMuxinator Completion
|
||||
# source ~/bin/tmuxinator.zsh
|
||||
|
||||
zstyle :compinstall filename '~/.zshrc'
|
||||
autoload -Uz compinit
|
||||
compinit
|
||||
|
||||
DEFAULT_USER="$USER"
|
||||
|
||||
# Syntax highlighting configuration
|
||||
# ZSH_HIGHLIGHT_STYLES[path]='bold'
|
||||
# ZSH_HIGHLIGHT_STYLES[path_prefix]=none
|
||||
# ZSH_HIGHLIGHT_STYLES[path_approx]='fg=yellow'
|
||||
# ZSH_HIGHLIGHT_STYLES[globbing]='fg=yellow'
|
||||
|
||||
# Source files
|
||||
#typeset -ga sources
|
||||
#sources+=$DOTFILES_DIR/shortcuts.txt
|
||||
|
||||
# Check dotfiles version
|
||||
if [ -n "$NO_GIT_CHECK" ] && [ ! "$NO_GIT_CHECK" -eq 1 ]; then
|
||||
# 5 second timeout to avoid delaying
|
||||
timeout -k 5 5s $(which zsh) -c 'source ~/.zsh_functions && check_dotfiles_version'
|
||||
dotfiles_version=$?
|
||||
if [[ $dotfiles_version = 1 ]]; then
|
||||
echo "Dotfiles repository not present at $DOTFILES_DIR"
|
||||
elif [[ $dotfiles_version = 2 ]]; then
|
||||
echo "Dotfiles repository at $DOTFILES_DIR is outdated"
|
||||
elif [[ $dotfiles_version = 3 ]]; then
|
||||
echo "Dotfiles repository at $DOTFILES_DIR is ahead of origin"
|
||||
elif [[ $dotfiles_version = 4 ]]; then
|
||||
echo "Dotfiles repository at $DOTFILES_DIR has diverged from origin"
|
||||
elif [[ $dotfiles_version = 5 ]]; then
|
||||
echo "Dotfiles repository not set in $HOME/.dotfiles-config"
|
||||
echo "Please run ./install.sh in dotfiles repository and exit to create ~/.dotfiles-config"
|
||||
echo "The following will not work until this is set:"
|
||||
echo " - oh-my-zsh will not be started"
|
||||
echo " - gruvbox color palette will not be loaded"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# Powerline
|
||||
if program_installed powerline; then
|
||||
if [ -n "$POWERLINE_ROOT" ] && \
|
||||
[ -f "$POWERLINE_ROOT" ] && \
|
||||
[ -f "$POWERLINE_ROOT/bindings/zsh/powerline.zsh" ]; then
|
||||
# Skip lookup if location is known
|
||||
source "$POWERLINE_ROOT/bindings/zsh/powerline.zsh"
|
||||
else
|
||||
found=0
|
||||
set_powerline_zsh_sources
|
||||
for file in $POWERLINE_ZSH_SOURCES; do
|
||||
if [[ -a $file ]]; then
|
||||
# TODO: Store the POWERLINE_ROOT in .profile.local if found
|
||||
source $file
|
||||
found=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ $found -eq 0 ]; then
|
||||
echo "Powerline binding not found.\nPlease set \$POWERLINE_ROOT in .profile.local"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
#alias sl="sl -laF"
|
||||
|
||||
# Tool options
|
||||
export SHELLCHECK_OPTS="-e SC2029 -e SC2155"
|
||||
|
||||
# Limit claude-code: cap CPU/memory/process count
|
||||
# Adjust the -p values if these limits are ever too tight.
|
||||
# if program_installed claude && program_installed systemd-run; then
|
||||
# claude() {
|
||||
# systemd-run --user --scope --quiet \
|
||||
# -p CPUQuota=50% \
|
||||
# -p MemoryMax=6G \
|
||||
# -p TasksMax=512 \
|
||||
# -- command claude "$@"
|
||||
# }
|
||||
# fi
|
||||
|
||||
# Syntax highlighting
|
||||
if [ -f /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]; then
|
||||
source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
|
||||
fi
|
||||
|
||||
# fzf fuzzy finder
|
||||
if program_installed fzf; then
|
||||
# TODO: This is broken on debian fzf (no --zsh)
|
||||
source <(fzf --zsh)
|
||||
fi
|
||||
|
||||
# thefuck command correction
|
||||
if program_installed fuck; then
|
||||
eval "$(thefuck --alias)"
|
||||
fi
|
||||
|
||||
# pyenv-virtualenv (for managing virtualenvs for different python versions)
|
||||
if program_installed pyenv; then
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
export PYENV_VIRTUALENV_DISABLE_PROMPT=1
|
||||
[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"
|
||||
eval "$(pyenv init - zsh)"
|
||||
# eval "$(pyenv virtualenv-init -)"
|
||||
fi
|
||||
|
||||
# oh-my-zsh plugins (must come after all initialization above)
|
||||
plugins=(archlinux colored-man-pages colorize command-not-found cp docker docker-compose fzf git history history-substring-search nmap npm pip poetry pyenv macos rsync snap ssh sudo systemd terraform thefuck tmux tmuxinator vi-mode virtualenv yarn zsh-interactive-cd zsh-navigation-tools)
|
||||
|
||||
# Local overrides
|
||||
if [ -f $HOME/.zshrc_local ]; then
|
||||
source $HOME/.zshrc_local
|
||||
fi
|
||||
|
||||
# Fun stupid opt-in stuff on new shell. Enable these in .profile.local
|
||||
if program_installed neofetch; then
|
||||
if [ ! -n "$NO_NEOFETCH" ] || [ "$NO_NEOFETCH" -eq 1 ]; then
|
||||
else
|
||||
neofetch
|
||||
fi
|
||||
if [ -f ~/.fzf.zsh ]; then
|
||||
source ~/.fzf.zsh
|
||||
fi
|
||||
if [ -n "$NO_ZSHRC_COWSAY" ] && [ ! "$NO_ZSHRC_COWSAY" -eq 1 ]; then
|
||||
# Make a random (cow?) with a random face say something
|
||||
fortune -a | fmt -80 -s | cowthink -$(shuf -n 1 -e b d g p s t w y) -f $(shuf -n 1 -e $(cowsay -l | tail -n +2)) -n
|
||||
|
||||
if [ -f /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]; then
|
||||
source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
|
||||
fi
|
||||
|
||||
# pyenv-virtualenv (for managing virtualenvs for different python versions)
|
||||
#export PYENV_VIRTUALENV_DISABLE_PROMPT=1
|
||||
#eval "$(pyenv init -)"
|
||||
#eval "$(pyenv virtualenv-init -)"
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
# Based on the official Anthropic reference dev container:
|
||||
# https://github.com/anthropics/claude-code/tree/main/.devcontainer
|
||||
FROM node:22
|
||||
|
||||
ARG TZ
|
||||
ENV TZ="$TZ"
|
||||
|
||||
ARG CLAUDE_CODE_VERSION=latest
|
||||
|
||||
# Install basic development tools, iptables/ipset (for the egress firewall),
|
||||
# openssh-client (for git-over-ssh and agent forwarding), and the toolchain
|
||||
# nvim's plugin config (config/nvim, telescope/treesitter/mason) expects.
|
||||
#
|
||||
# Deliberately NOT installing bubblewrap/socat: those exist to let Claude
|
||||
# Code's own internal per-command sandboxing (~/.claude/settings.json's
|
||||
# "sandbox" block) and /remote-control run, but that inner sandbox is
|
||||
# redundant on top of this container - it can't even function inside it
|
||||
# anyway (bwrap can't create a namespace here, and fixing that would need
|
||||
# privilege escalation approaching --privileged, undoing this container's
|
||||
# own hardening). bin/claude-sandbox mounts a settings.json copy with
|
||||
# sandbox.enabled forced to false, so there's nothing here to satisfy.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
less \
|
||||
git \
|
||||
openssh-client \
|
||||
procps \
|
||||
fzf \
|
||||
zsh \
|
||||
man-db \
|
||||
unzip \
|
||||
gnupg2 \
|
||||
gh \
|
||||
iptables \
|
||||
ipset \
|
||||
iproute2 \
|
||||
dnsutils \
|
||||
aggregate \
|
||||
jq \
|
||||
dnsmasq \
|
||||
ulogd2 \
|
||||
ulogd2-json \
|
||||
nano \
|
||||
vim \
|
||||
ripgrep \
|
||||
fd-find \
|
||||
build-essential \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
xz-utils \
|
||||
cowsay \
|
||||
fortune-mod \
|
||||
fortunes-min \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& ln -s "$(command -v fdfind)" /usr/local/bin/fd
|
||||
|
||||
# cowsay/cowthink/fortune install to /usr/games on Debian, which isn't on
|
||||
# PATH by default - config/nvim's start-screen header (plugins/ui.lua) shells
|
||||
# out to them by bare name. Set via ENV (not a profile.d script) so it's
|
||||
# picked up by every process unconditionally, including nvim's non-login
|
||||
# subprocess spawns, which wouldn't source /etc/profile.d.
|
||||
ENV PATH=/usr/games:$PATH
|
||||
|
||||
# Replace the base image's built-in `node` user (uid/gid 1000,
|
||||
# $HOME=/home/node) with one matching the host user who runs
|
||||
# bin/claude-sandbox - so absolute paths under $HOME (dotfiles, and
|
||||
# ~/.claude/plugins/installed_plugins.json's own installPath bookkeeping,
|
||||
# which Claude Code resolves as a literal host-absolute string rather than
|
||||
# relative to whatever $HOME happens to be) resolve identically inside and
|
||||
# outside the container, instead of needing a one-off dual-mount for every
|
||||
# individual thing that assumes the real host path. Defaults below
|
||||
# reproduce today's node/1000/1000//home/node exactly, so a build that
|
||||
# never overrides them (bin/claude-sandbox always does; the test suite
|
||||
# never does) exercises this same rename path rather than a separate,
|
||||
# untested one.
|
||||
ARG SANDBOX_USER=node
|
||||
ARG SANDBOX_UID=1000
|
||||
ARG SANDBOX_GID=1000
|
||||
ARG SANDBOX_HOME=/home/node
|
||||
RUN userdel -r node 2>/dev/null; \
|
||||
groupdel node 2>/dev/null; \
|
||||
groupadd -g "$SANDBOX_GID" "$SANDBOX_USER" && \
|
||||
useradd -u "$SANDBOX_UID" -g "$SANDBOX_GID" -m -d "$SANDBOX_HOME" -s /bin/bash "$SANDBOX_USER"
|
||||
ENV SANDBOX_USER=$SANDBOX_USER
|
||||
ENV SANDBOX_HOME=$SANDBOX_HOME
|
||||
|
||||
# Ensure the sandbox user has access to /usr/local/share
|
||||
RUN mkdir -p /usr/local/share/npm-global && \
|
||||
chown -R $SANDBOX_USER:$SANDBOX_USER /usr/local/share
|
||||
|
||||
# Persist bash history.
|
||||
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
|
||||
&& mkdir /commandhistory \
|
||||
&& touch /commandhistory/.bash_history \
|
||||
&& chown -R $SANDBOX_USER /commandhistory
|
||||
|
||||
# Set `DEVCONTAINER` environment variable to help with orientation
|
||||
ENV DEVCONTAINER=true
|
||||
|
||||
# Disable non-essential network traffic (telemetry/error reporting) so the
|
||||
# firewall allowlist can stay small.
|
||||
ENV CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
||||
|
||||
# Lets Claude Code (and the SessionStart hook in ~/.claude/hooks) know it's
|
||||
# running inside this container rather than directly on the host.
|
||||
ENV CLAUDE_SANDBOX=1
|
||||
|
||||
# Dedicated unprivileged user for ulogd2 (see entrypoint.sh) - unlike
|
||||
# dnsmasq, the ulogd2 Debian package doesn't create one of its own. ulogd2
|
||||
# opens its log/output files and the NFLOG netlink socket before dropping to
|
||||
# this user, so it doesn't need to own anything itself.
|
||||
RUN useradd -r -s /usr/sbin/nologin ulogd
|
||||
|
||||
# Create the ssh directory and set permissions. The project directory itself
|
||||
# is bind-mounted at runtime at its real host path (see docker-compose.yml),
|
||||
# not a fixed /workspace, so it can share ~/.claude project/session history
|
||||
# with the host. ~/.claude is bind-mounted too, not baked in here.
|
||||
RUN mkdir -p $SANDBOX_HOME/.ssh && \
|
||||
chown -R $SANDBOX_USER:$SANDBOX_USER $SANDBOX_HOME/.ssh && \
|
||||
chmod 700 $SANDBOX_HOME/.ssh
|
||||
|
||||
# Pre-create the nvim data/state/cache dirs owned by the sandbox user,
|
||||
# since they're mounted as named volumes in docker-compose.yml: Docker
|
||||
# seeds a fresh named volume from whatever already exists at the mount
|
||||
# path in the image, ownership included, so this is what keeps those
|
||||
# volumes writable by that user instead of defaulting to root.
|
||||
RUN mkdir -p $SANDBOX_HOME/.local/share/nvim $SANDBOX_HOME/.local/state/nvim $SANDBOX_HOME/.cache/nvim && \
|
||||
chown -R $SANDBOX_USER:$SANDBOX_USER $SANDBOX_HOME/.local $SANDBOX_HOME/.cache
|
||||
|
||||
ARG GIT_DELTA_VERSION=0.18.2
|
||||
RUN ARCH=$(dpkg --print-architecture) && \
|
||||
wget "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
|
||||
dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
|
||||
rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb"
|
||||
|
||||
# gosu lets entrypoint.sh (root) drop to the node user for the actual
|
||||
# session after setting up the firewall, without leaving any setuid/sudo
|
||||
# escalation path available to node afterward. Verified against the
|
||||
# published SHA256SUMS since it runs as root.
|
||||
ARG GOSU_VERSION=1.19
|
||||
RUN ARCH=$(dpkg --print-architecture) && \
|
||||
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-${ARCH}" && \
|
||||
wget -O /tmp/gosu.sha256sums "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/SHA256SUMS" && \
|
||||
grep -E "gosu-${ARCH}\$" /tmp/gosu.sha256sums | sed "s#gosu-${ARCH}#/usr/local/bin/gosu#" | sha256sum -c - && \
|
||||
rm /tmp/gosu.sha256sums && \
|
||||
chmod +x /usr/local/bin/gosu
|
||||
|
||||
# Install the latest stable Neovim release. Debian's packaged version is too
|
||||
# old for this config's plugins (blink.cmp etc. need a recent Neovim), so
|
||||
# fetch the current release from GitHub directly rather than pinning a
|
||||
# version that may go stale.
|
||||
RUN asset_url=$(curl -fsSL https://api.github.com/repos/neovim/neovim/releases/latest \
|
||||
| jq -r '[.assets[] | select(.name | test("linux.*x86_64.*\\.tar\\.gz$"))][0].browser_download_url') && \
|
||||
wget -O nvim.tar.gz "$asset_url" && \
|
||||
tar -C /usr/local --strip-components=1 -xzf nvim.tar.gz && \
|
||||
rm nvim.tar.gz
|
||||
|
||||
# Install the latest stable Go toolchain. go.dev's own release JSON gives
|
||||
# both the download URL and a SHA256 per file, verified below since this
|
||||
# runs as root - same pattern as gosu above. `go build`/`go mod download`
|
||||
# need proxy.golang.org and sum.golang.org reachable at runtime - see
|
||||
# fixed-domains.sh.
|
||||
RUN ARCH=$(dpkg --print-architecture) && \
|
||||
release_json=$(curl -fsSL 'https://go.dev/dl/?mode=json') && \
|
||||
filename=$(echo "$release_json" | jq -r --arg arch "$ARCH" \
|
||||
'[.[] | select(.stable == true)][0].files[] | select(.os == "linux" and .arch == $arch and .kind == "archive") | .filename') && \
|
||||
sha256=$(echo "$release_json" | jq -r --arg arch "$ARCH" \
|
||||
'[.[] | select(.stable == true)][0].files[] | select(.os == "linux" and .arch == $arch and .kind == "archive") | .sha256') && \
|
||||
wget -O go.tar.gz "https://go.dev/dl/${filename}" && \
|
||||
echo "${sha256} go.tar.gz" | sha256sum -c - && \
|
||||
tar -C /usr/local -xzf go.tar.gz && \
|
||||
rm go.tar.gz
|
||||
ENV PATH=$PATH:/usr/local/go/bin
|
||||
|
||||
# System libraries Chromium needs (fonts, libnss, libatk, etc.) for
|
||||
# Playwright, installed here since it needs apt/root - the actual
|
||||
# `playwright` package and browser binary install as node, further down,
|
||||
# so they land in node's own npm prefix/cache correctly.
|
||||
ARG PLAYWRIGHT_VERSION=1.61.1
|
||||
RUN npx -y playwright@${PLAYWRIGHT_VERSION} install-deps chromium
|
||||
|
||||
# Set up non-root user
|
||||
USER $SANDBOX_USER
|
||||
|
||||
# Install global packages
|
||||
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
|
||||
ENV PATH=$PATH:/usr/local/share/npm-global/bin
|
||||
# A global npm install only puts a package's CLI on PATH, not its module on
|
||||
# Node's require() resolution path - NODE_PATH is what makes `require
|
||||
# ("playwright")` work from any script anywhere, not just the project's own
|
||||
# node_modules (which it likely doesn't have without its own npm install).
|
||||
ENV NODE_PATH=/usr/local/share/npm-global/lib/node_modules
|
||||
|
||||
# Set the default shell to zsh rather than sh
|
||||
ENV SHELL=/bin/zsh
|
||||
|
||||
# Set the default editor and visual
|
||||
ENV EDITOR=nano
|
||||
ENV VISUAL=nano
|
||||
|
||||
# Default powerline10k theme
|
||||
ARG ZSH_IN_DOCKER_VERSION=1.2.0
|
||||
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \
|
||||
-p git \
|
||||
-p fzf \
|
||||
-a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \
|
||||
-a "source /usr/share/doc/fzf/examples/completion.zsh" \
|
||||
-a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
|
||||
-x
|
||||
|
||||
# Install Claude Code
|
||||
RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}
|
||||
|
||||
# Install the tree-sitter CLI, which nvim-treesitter (plugins/treesitter.lua)
|
||||
# needs to install/update parsers. Pinned below latest: newer tree-sitter-cli
|
||||
# prebuilds require glibc >=2.39, which this (Debian 12 bookworm) image
|
||||
# doesn't have.
|
||||
RUN npm install -g tree-sitter-cli@0.24.7
|
||||
|
||||
# Playwright, for testing frontend changes against a locally-running dev
|
||||
# server - localhost traffic never goes through the egress firewall, so
|
||||
# this needs no allowlist changes for that. System deps were already
|
||||
# installed as root above; this installs the npm package and downloads the
|
||||
# matching Chromium build into node's own cache, both at build time so a
|
||||
# session never needs network access for either.
|
||||
#
|
||||
# Chromium's own internal process sandbox is questionable here: it launches
|
||||
# without --no-sandbox and without error, but its setuid sandbox helper
|
||||
# (chrome_sandbox) isn't actually setuid-root in this image, and this host
|
||||
# has the same unprivileged-userns restriction that broke bubblewrap - so
|
||||
# its namespace-based isolation almost certainly isn't active either, it's
|
||||
# just not hard-failing the way bubblewrap did. Not treated as a real
|
||||
# security boundary either way: this container is already the boundary that
|
||||
# matters, and a compromised session already has full `node`-level bash
|
||||
# access, which a Chromium exploit couldn't exceed regardless of whichever
|
||||
# of its own internal sandbox layers happen to be active.
|
||||
RUN npm install -g playwright@${PLAYWRIGHT_VERSION} && \
|
||||
npx playwright install chromium
|
||||
|
||||
# Copy and set up firewall + DNS-proxy + entrypoint scripts, the gitea-pr
|
||||
# helper from this repo's bin/ directory, the claude wrapper that pins
|
||||
# --dangerously-skip-permissions, and this user's real nvim config.
|
||||
COPY docker/claude-sandbox/fixed-domains.sh /usr/local/bin/
|
||||
COPY docker/claude-sandbox/dns-proxy.sh /usr/local/bin/
|
||||
COPY docker/claude-sandbox/init-firewall.sh /usr/local/bin/
|
||||
COPY docker/claude-sandbox/log-shipper.sh /usr/local/bin/
|
||||
COPY docker/claude-sandbox/ulogd.conf /etc/ulogd-sandbox.conf
|
||||
COPY docker/claude-sandbox/entrypoint.sh /usr/local/bin/
|
||||
COPY docker/claude-sandbox/claude-wrapper.sh /usr/local/bin/claude
|
||||
COPY bin/gitea-pr /usr/local/bin/gitea-pr
|
||||
COPY config/nvim $SANDBOX_HOME/.config/nvim
|
||||
|
||||
USER root
|
||||
RUN chmod +x /usr/local/bin/dns-proxy.sh /usr/local/bin/init-firewall.sh /usr/local/bin/log-shipper.sh /usr/local/bin/entrypoint.sh /usr/local/bin/gitea-pr /usr/local/bin/claude && \
|
||||
chown -R $SANDBOX_USER:$SANDBOX_USER $SANDBOX_HOME/.config/nvim && \
|
||||
echo 'export PATH="$PATH:/usr/local/share/npm-global/bin"' > /etc/profile.d/npm-global-path.sh
|
||||
|
||||
# Entrypoint runs as root (no USER node here) so it can set up the firewall
|
||||
# directly, with no sudo/setuid escalation path available to node at any
|
||||
# point afterward. It drops to node itself via gosu before execing nvim.
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
@@ -1,516 +0,0 @@
|
||||
# claude-sandbox
|
||||
|
||||
Runs Claude Code inside a Docker container with `--dangerously-skip-permissions`
|
||||
(no permission prompts, all tool use auto-accepted) against a project mounted
|
||||
from the host. Based on
|
||||
[Anthropic's reference dev container](https://github.com/anthropics/claude-code/tree/main/.devcontainer):
|
||||
same base image, non-root user, and egress-firewall approach, adapted to run
|
||||
via `docker compose` directly instead of through a VS Code dev container, and
|
||||
extended with this user's own nvim config and a `gitea-pr` helper for
|
||||
opening/iterating on PRs.
|
||||
|
||||
This directory (`docker/claude-sandbox/`) only holds the tooling. It is
|
||||
**not** where any project lives — the project you point it at is bind-mounted
|
||||
into the container at runtime from wherever it actually sits on disk.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker with the `docker compose` v2 plugin.
|
||||
- `ssh-agent` running with the key for your Gitea remote(s) added
|
||||
(`ssh-add -l` should list it). The container never sees your private key,
|
||||
only a forwarded agent socket. `bin/claude-sandbox` warns (non-fatally) at
|
||||
launch if the agent has zero identities loaded - the socket still forwards
|
||||
fine either way, but git-over-ssh will fail until you `ssh-add` a key. That
|
||||
takes effect immediately in an already-running session too (it's a live
|
||||
reference to the same host agent process, not a snapshot) - no need to
|
||||
restart the sandbox after `ssh-add`ing.
|
||||
For a sandbox that needs to keep working unattended after your client
|
||||
disconnects, see "Headless git push without agent forwarding" below —
|
||||
`SANDBOX_SSH_KEY` is an alternative to this forwarded-agent requirement,
|
||||
not just an addition to it.
|
||||
- `GITEA_SSH_HOST` and `GITEA_API` exported in your shell (same variables
|
||||
`bin/gitea-pr` already needs — see its `require_config`), and a token
|
||||
available either via `GITEA_TOKEN` in the environment or at
|
||||
`~/.config/gitea/token`.
|
||||
- Optional: a **read-only** fine-grained GitHub PAT (GitHub → Settings →
|
||||
Developer settings → Fine-grained tokens → generate, with Contents and
|
||||
Metadata set to read-only, no write scopes) for `gh`/private-repo HTTPS
|
||||
access, available via `GH_TOKEN` in the environment or at
|
||||
`~/.config/gh/sandbox-token`. Don't use a normal `gh auth login` session
|
||||
here — that's write-capable, which defeats the point (see "GitHub is
|
||||
read-only" below). Without it, public GitHub repos still clone/fetch fine
|
||||
anonymously over HTTPS; only private repos and `gh` API calls need it.
|
||||
- Optional: `LOKI_URL` (a base URL, e.g. `http://loki.internal:3100`) to ship
|
||||
DNS-query and firewall allow/reject logs to a Loki push endpoint - see
|
||||
"Network request logging" below. Logging is skipped entirely if unset.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
bin/claude-sandbox # sandbox the current directory
|
||||
bin/claude-sandbox ~/repos/some-project
|
||||
bin/claude-sandbox ~/repos/some-project src/foo.py # open a specific file
|
||||
```
|
||||
|
||||
The entry point is nvim, opened on the bind-mounted project directory, using
|
||||
this user's real nvim config (`config/nvim`, including
|
||||
[claudecode.nvim](https://github.com/coder/claudecode.nvim) — `<leader>ac`
|
||||
toggles a Claude Code terminal). `claude` is also on `PATH` directly for use
|
||||
from any shell/terminal inside the container. Either way it always runs with
|
||||
`--dangerously-skip-permissions`, regardless of how it's invoked.
|
||||
|
||||
Typical loop: open the project, ask Claude to implement something and open a
|
||||
PR (it has `gitea-pr` on `PATH`); from the host, `gitea-pr comments <PR#>` to
|
||||
pull review feedback, feed it back in, repeat.
|
||||
|
||||
## Config parity with your host Claude Code setup
|
||||
|
||||
`~/.claude` and `~/.claude.json` are bind-mounted read-write from the host
|
||||
into the container, so global `CLAUDE.md`, settings, hooks, plugins, MCP
|
||||
server config, and your existing login/credentials all carry over
|
||||
automatically — the container is a faithful copy of this setup, not a
|
||||
separate identity. There's no separate first-run login.
|
||||
|
||||
The project directory is mounted at the *same absolute path* inside the
|
||||
container as on the host (not a fixed `/workspace`), so Claude Code's
|
||||
project/session-history keying lines up between host and container. A
|
||||
session started in the sandbox can be resumed on the host afterward with
|
||||
`claude --resume <session-id>`.
|
||||
|
||||
The container's own user is built to match yours exactly — same username,
|
||||
uid, gid, and home directory (`bin/claude-sandbox` passes these as Docker
|
||||
build args; see `Dockerfile`). This is what makes `$HOME`-relative
|
||||
assumptions actually work inside the container instead of just superficially
|
||||
resembling your setup — your dotfiles, and Claude Code's own plugin
|
||||
bookkeeping (which records install locations as literal host-absolute
|
||||
paths, not relative to whatever `$HOME` happens to be), resolve to the same
|
||||
paths on both sides. The build defaults to `node`/1000/1000/`/home/node` if
|
||||
these aren't supplied, so building the image directly (bypassing
|
||||
`bin/claude-sandbox`) still works, just without this parity. On a shared
|
||||
machine, the built image is tagged per-user (`claude-sandbox:<username>`) so
|
||||
two accounts building concurrently don't clobber each other's image.
|
||||
|
||||
Caveat: MCP servers or hooks that shell out to host-specific binaries (e.g. a
|
||||
browser for Playwright, a language runtime not installed in this image) may
|
||||
not work inside the container unless that tooling is also added to the
|
||||
`Dockerfile`.
|
||||
|
||||
Your global `~/.gitconfig` (`user.name`/`user.email` and the like) is also
|
||||
carried over, so commits made inside the sandbox have the right identity
|
||||
without any extra setup - falls back to an empty config if you don't have
|
||||
one, it's not a hard requirement. It's a throwaway copy rather than the
|
||||
real file, in its own directory rather than mounted directly at
|
||||
`~/.gitconfig` (`GIT_CONFIG_GLOBAL` points git at it instead) - `gh auth
|
||||
setup-git` needs to write a credential-helper entry into it, and both a
|
||||
read-only mount and a single-file bind mount at `~/.gitconfig` break that
|
||||
write in different ways. Nothing written to it during a session reaches
|
||||
your real `~/.gitconfig`.
|
||||
|
||||
### Headless git push without agent forwarding
|
||||
|
||||
Agent forwarding (the default — see Prerequisites) ties the sandbox's SSH
|
||||
access to whichever client's agent is currently forwarded. On a devbox
|
||||
reached over SSH+tmux, that agent belongs to whichever client connected —
|
||||
when that client disconnects, the forwarded socket dies with it, breaking
|
||||
git-over-ssh for anything still running headless in the sandbox.
|
||||
|
||||
For sandboxes meant to keep running unattended, generate a dedicated,
|
||||
purpose-built keypair instead — not your personal key:
|
||||
|
||||
```sh
|
||||
ssh-keygen -t ed25519 -N '' -f ~/.ssh/claude-sandbox_ed25519
|
||||
```
|
||||
|
||||
Passphrase-less is deliberate: headless operation has no human to answer a
|
||||
prompt, and this is meant to be a low-privilege credential that only ever
|
||||
lives inside the container, not your personal identity. Add the public
|
||||
half as a Gitea deploy key scoped to only the repos that actually need push
|
||||
access, then:
|
||||
|
||||
```sh
|
||||
SANDBOX_SSH_KEY=~/.ssh/claude-sandbox_ed25519 bin/claude-sandbox ~/repos/some-project
|
||||
```
|
||||
|
||||
The key is bind-mounted read-only at `/root/.ssh-sandbox-key-src` — under
|
||||
`/root`, not `$SANDBOX_HOME` — so only entrypoint.sh's root-run startup code
|
||||
can ever read it; the sandbox user (which the session actually runs as) has
|
||||
no path to that directory at all. At container start, `entrypoint.sh` loads
|
||||
it into a fresh `ssh-agent` running inside the container itself —
|
||||
independent of the forwarded socket — copies it out just long enough to do
|
||||
that, and deletes the copy immediately afterward. A compromised session can
|
||||
use the agent's socket to sign git-over-ssh operations (bounded by whatever
|
||||
the deploy key has push access to), but can't read the raw key material
|
||||
itself. When `SANDBOX_SSH_KEY` is set, a live forwarded agent is no longer
|
||||
required to start the sandbox at all.
|
||||
|
||||
## Shared scratch space
|
||||
|
||||
`~/shared` inside the container is a read-write bind mount of a real host
|
||||
directory (`$SANDBOX_SHARE_DIR`, default `~/.local/share/claude-sandbox/shared`,
|
||||
created automatically) - separate from the project mount, for passing files
|
||||
(screenshots, temp scripts) back and forth with a sandboxed session without
|
||||
touching the actual project checkout. It persists across sessions since it's
|
||||
a real directory, not a container volume. Set `SANDBOX_SHARE_DIR` to point
|
||||
it somewhere else.
|
||||
|
||||
`~/specs` is also bind-mounted read-write, at the fixed host path `~/specs`
|
||||
(created automatically if it doesn't exist yet) - not configurable like
|
||||
`SANDBOX_SHARE_DIR`, since it exists to make the global `CLAUDE.md`
|
||||
convention of writing specs to `~/specs/` (also carried into the container)
|
||||
actually work there instead of writing into a path that vanishes with the
|
||||
session.
|
||||
|
||||
## Testing frontend changes
|
||||
|
||||
Playwright (Chromium) is pre-installed, browser included - `require
|
||||
("playwright")` works from any script, anywhere in the container, not just
|
||||
a project with its own `npm install playwright`. Both the npm package and
|
||||
the matching Chromium build are baked in at image build time, so a session
|
||||
never needs network access for either.
|
||||
|
||||
Testing against a project's own local dev server needs no firewall changes
|
||||
at all - `localhost`/loopback traffic never goes through the egress
|
||||
firewall in the first place. Testing against a real external site works
|
||||
the same as any other network use: the domain needs to be on the allowlist
|
||||
(`ALLOWED_DOMAINS`, or `bin/claude-sandbox-allow <domain>` for a running
|
||||
session) - verified the firewall applies to the browser's own traffic
|
||||
exactly like everything else (a non-allowlisted domain gets blocked from
|
||||
inside the browser too, not just from `curl`/`git`).
|
||||
|
||||
If a project has its *own* pinned `playwright`/`@playwright/test`
|
||||
devDependency at a different version than the one baked in here, its
|
||||
`npm install` may still try to download a matching Chromium build from
|
||||
Playwright's own CDN, which isn't on the default allowlist - allow it the
|
||||
same way as any other one-off domain need if that comes up.
|
||||
|
||||
Chromium's own internal process sandbox is best-effort here, not something
|
||||
to rely on: it launches without erroring, but its setuid sandbox helper
|
||||
isn't actually setuid in this image, and this host has the same
|
||||
unprivileged-userns restriction that keeps Claude Code's own internal
|
||||
sandboxing (see Security notes) from working either - so its namespace-
|
||||
based isolation almost certainly isn't active, Chromium just doesn't
|
||||
hard-fail the way `bubblewrap` does when it's missing. Not treated as a
|
||||
real boundary: this container is already the boundary that matters, and a
|
||||
compromised session already has full bash access as the container's own
|
||||
user regardless, which a Chromium exploit couldn't exceed.
|
||||
|
||||
## Sandbox self-awareness
|
||||
|
||||
A `SessionStart` hook (`~/.claude/hooks/sandbox-context.sh`, registered in
|
||||
`~/.claude/settings.json`) checks for the `CLAUDE_SANDBOX` environment
|
||||
variable this container sets, and when present, injects a reminder that the
|
||||
session is running in a disposable, firewalled container and should stay
|
||||
within the task at hand — and that if a task genuinely needs to break out of
|
||||
the sandbox (broader network access, host-level changes, GUI/hardware), it
|
||||
should stop and tell the user rather than trying to work around the
|
||||
firewall, since the same session can be resumed unrestricted on the host via
|
||||
`claude --resume`. It's a no-op outside the container. This is on top of the
|
||||
existing `deny-dangerous-bash.sh` `PreToolUse` hook, which also applies here
|
||||
since `~/.claude` is shared.
|
||||
|
||||
`git-push-guard.sh` (also `PreToolUse`/`Bash`, also `CLAUDE_SANDBOX`-gated)
|
||||
blocks any `git push` whose resolved remote isn't the Gitea host, and any
|
||||
push to `main`/`master` even there — the sandbox workflow is feature branch
|
||||
+ `gitea-pr create`, never a direct push. Like the other hooks, this is a
|
||||
fast-fail guard against an overeager agent, not a hard security boundary: a
|
||||
sandbox session has full shell access and could in principle edit
|
||||
`.git/config` to route around it. For "never pushes to GitHub" specifically,
|
||||
the firewall backs this up with real enforcement (see below); for "never
|
||||
pushes to main/master", the authoritative version of that is branch
|
||||
protection on the Gitea repo itself, which this doesn't set up for you.
|
||||
|
||||
sandbox-context.sh also tells the agent the actual workflow to follow
|
||||
proactively, not just what's blocked: do new work in a `git worktree`
|
||||
rather than switching branches in the mounted checkout in place (that's the
|
||||
user's real working copy), then push the feature branch and run
|
||||
`gitea-pr create` from the worktree. The worktree has to live *under* the
|
||||
mounted project directory (e.g. `.worktrees/<branch>`, not `../<branch>`) -
|
||||
only the exact bind-mounted path is backed by real host storage, so a
|
||||
worktree created as a sibling of the project (or anywhere under `$HOME`)
|
||||
looks fine but silently disappears with the container, worktree and all.
|
||||
|
||||
## Network egress firewall
|
||||
|
||||
On start, the container runs an allowlist-only `iptables`/`ipset` firewall
|
||||
(`init-firewall.sh`), matching the reference dev container. Allowed by
|
||||
default: DNS resolution via a local DNS proxy (see "DNS is allowlisted too"
|
||||
below), the Docker host itself (not its whole subnet), GitHub's published IP
|
||||
ranges (port 443 only - see "GitHub is read-only" below),
|
||||
`release-assets.githubusercontent.com` (GitHub release assets - e.g. mason's
|
||||
own registry index - redirect here, a separate CDN from github.com's own
|
||||
IPs), `registry.npmjs.org`, `pypi.org` and `files.pythonhosted.org` (mason's
|
||||
Python-based LSP tools - `ruff`, `basedpyright`, `debugpy` - install via
|
||||
pip), `proxy.golang.org` and `sum.golang.org` (`go build`/`go mod download`'s
|
||||
module proxy and checksum database), `api.anthropic.com`, and whatever hosts
|
||||
`GITEA_SSH_HOST`/`GITEA_API`
|
||||
resolve to — that last group isn't port-restricted, so git-over-ssh to Gitea
|
||||
works without a separate blanket "port 22 to anywhere" rule. Set
|
||||
`ALLOWED_DOMAINS` (space-separated) in the environment to allow more (e.g.
|
||||
`crates.io` for a Rust project needing more than what mason/npm already
|
||||
cover). Everything else is rejected. Set `ENABLE_FIREWALL=0` to disable it
|
||||
entirely (e.g. for debugging a blocked domain).
|
||||
|
||||
IPv6 is blocked outright (`ip6tables` policy DROP on INPUT/OUTPUT/FORWARD,
|
||||
and dnsmasq strips AAAA answers so nothing wastes a connection attempt on an
|
||||
address that could never be reached) - there's no IPv6 equivalent of the
|
||||
allowlist above, so leaving it open would bypass the entire firewall on any
|
||||
host with IPv6 egress enabled.
|
||||
|
||||
If your Gitea host is only resolvable via a LAN-local DNS server, make sure
|
||||
the Docker host itself can resolve it — containers inherit the host's
|
||||
resolver by default.
|
||||
|
||||
### DNS is allowlisted too
|
||||
|
||||
Resolution itself is restricted, not just the IP connections made
|
||||
afterward: `dns-proxy.sh` runs a local `dnsmasq` bound to 127.0.0.1 with no
|
||||
fallback resolver, and `/etc/resolv.conf` is repointed at it. Only names
|
||||
matching the same allowlist as above (plus `github.com`/
|
||||
`githubusercontent.com`/`githubassets.com`, needed since GitHub is
|
||||
allowlisted by IP range rather than by resolving those names - see
|
||||
`fixed-domains.sh`) get forwarded to Docker's real resolver; anything else
|
||||
gets no upstream configured at all and is refused outright. Without this, a
|
||||
compromised session could still exfiltrate data by encoding it into query
|
||||
names to an attacker-controlled domain even though it could never open a
|
||||
direct connection there - IP-based filtering alone doesn't stop that, since
|
||||
the DNS query itself was never restricted.
|
||||
|
||||
### Adding a domain to a running session
|
||||
|
||||
`ALLOWED_DOMAINS` only takes effect at container start. For a domain a
|
||||
*running* session needs right now, run from the host (not inside the
|
||||
sandbox — it has no privilege to do this itself):
|
||||
|
||||
```sh
|
||||
bin/claude-sandbox-allow crates.io # this session only
|
||||
bin/claude-sandbox-allow crates.io --global # + persist in config/profile
|
||||
```
|
||||
|
||||
It first adds the domain to the running dnsmasq's allowlist (appends a
|
||||
`server=` line to its servers-file and sends `SIGHUP` to reload just that
|
||||
file - no restart), then resolves it through the now-unblocked container
|
||||
resolver and adds the IP(s) to the running firewall's `ipset`, all via
|
||||
`docker exec -u root`. `--global` also appends the domain to
|
||||
`ALLOWED_DOMAINS` in `config/profile` for future sessions; source your shell
|
||||
(or open a new one) for that part to take effect. If more than one
|
||||
claude-sandbox container is running, pass `--container <name>` to
|
||||
disambiguate (`docker ps` to find it).
|
||||
|
||||
The sandbox-context.sh `SessionStart` hook tells the agent to ask for this
|
||||
by name when a task is blocked on a single missing domain, rather than
|
||||
stopping the whole session over it.
|
||||
|
||||
## Network request logging
|
||||
|
||||
If `LOKI_URL` is set (see Prerequisites), every DNS query and every new
|
||||
outbound connection attempt - allowed or rejected - is shipped to that Loki
|
||||
endpoint. Two sources, both tailed and pushed by `log-shipper.sh`:
|
||||
|
||||
- `dns-proxy.sh`'s dnsmasq logs every query and whether it was forwarded
|
||||
(allowed) or refused (not on the allowlist) - labeled `job=claude-sandbox-dns`.
|
||||
- `init-firewall.sh` tags its terminal ACCEPT/REJECT rules with `NFLOG`
|
||||
(group 1 for allowed, group 2 for rejected), which `ulogd2`
|
||||
(`ulogd.conf`) turns into JSON lines with source/dest IP, port, and an
|
||||
`action` field - labeled `job=claude-sandbox-net`. Only new connection
|
||||
attempts are logged, not every packet: the firewall's existing
|
||||
ESTABLISHED,RELATED rule short-circuits the rest of a connection before it
|
||||
ever reaches the tagged rules.
|
||||
|
||||
This is visibility on top of the firewall/DNS-proxy enforcement above, not a
|
||||
substitute for it - nothing about logging changes what's actually allowed to
|
||||
resolve or connect.
|
||||
|
||||
### Viewing logs in Grafana
|
||||
|
||||
Nothing sandbox-specific needed on the Grafana side beyond a Loki data
|
||||
source pointed at wherever `LOKI_URL` resolves to (Connections → Data
|
||||
sources → Loki, if one isn't already configured against that instance) -
|
||||
`log-shipper.sh` pushes directly to Loki's HTTP API, there's no Prometheus
|
||||
scrape target or exporter involved.
|
||||
|
||||
In Grafana's Explore view (or a dashboard panel with a Loki query), both
|
||||
streams are selectable by their `job` label:
|
||||
|
||||
```logql
|
||||
{job="claude-sandbox-dns"} # every DNS query + verdict
|
||||
{job="claude-sandbox-net"} # every connection attempt, as JSON
|
||||
```
|
||||
|
||||
Useful filters on top of those:
|
||||
|
||||
```logql
|
||||
# DNS lookups that got refused (not on the allowlist)
|
||||
{job="claude-sandbox-dns"} |= "REFUSED"
|
||||
|
||||
# Rejected connections only (parses ulogd2's JSON, filters on its action field)
|
||||
{job="claude-sandbox-net"} | json | action="blocked"
|
||||
|
||||
# Both streams together, e.g. to watch a live session across sources
|
||||
{source="claude-sandbox"}
|
||||
```
|
||||
|
||||
`{job="claude-sandbox-net"}` lines are one full ulogd2 JSON object per
|
||||
connection attempt (`| json` in a query unpacks fields like `dest_ip`,
|
||||
`dest_port`, `action`) - Loki's Explore view shows these expandable/
|
||||
pretty-printed by default, no extra parsing setup needed.
|
||||
|
||||
## GitHub is read-only
|
||||
|
||||
The sandbox can read from GitHub (clone, fetch, `gh` API calls) but can
|
||||
never push there, and this is enforced at the network layer, not just by the
|
||||
`git-push-guard.sh` hook above: GitHub's IP ranges are only allowlisted on
|
||||
port 443. Port 22 is unreachable, so SSH-based push (which could otherwise
|
||||
use the forwarded agent's real key - agent forwarding doesn't distinguish
|
||||
"push" from "pull") has no transport to use at all, regardless of what any
|
||||
hook does or doesn't catch. HTTPS access itself has no write-capable
|
||||
credential either: `gh auth setup-git` (run at container start if `GH_TOKEN`
|
||||
is set - see Prerequisites) points git's github.com credential helper at
|
||||
`gh`, which only ever holds whatever token `GH_TOKEN` provides. Use a
|
||||
read-only PAT there and even a push attempt over HTTPS gets rejected by
|
||||
GitHub itself (403), not just blocked locally.
|
||||
|
||||
## Security notes
|
||||
|
||||
**Threat model, honestly stated:** this reduces the blast radius of a
|
||||
misbehaving or prompt-injected coding agent - it does not make it safe to
|
||||
point at arbitrary untrusted or adversarial input, and it does not turn
|
||||
`--dangerously-skip-permissions` into something without real risk. Point it
|
||||
at repositories you already trust. Everything below is about how bad a
|
||||
compromised session *inside* that trust boundary can get, not a claim that
|
||||
one can't happen.
|
||||
|
||||
- `--dangerously-skip-permissions` removes your chance to review tool calls
|
||||
before they run. Only point this at trusted repositories.
|
||||
- The mounted project itself has **no write protection**. `deny-dangerous-
|
||||
bash.sh` and `git-push-guard.sh` are regex pattern-matching on the literal
|
||||
Bash command string - a fast-fail guard against an overeager agent doing
|
||||
something obviously destructive, not a boundary against one that's
|
||||
actively trying to get around them (a different scripting language, an
|
||||
encoded command, editing `.git/config` directly all sail right through).
|
||||
Nothing here stops a compromised or badly-instructed session from
|
||||
modifying or deleting the real working copy it's mounted against.
|
||||
- Allowing egress to a domain at all creates a channel for exfiltrating data
|
||||
through *that service's own request surface* (query strings, lookups,
|
||||
etc.), not just to a literal attacker-controlled destination. Domain-level
|
||||
allowlisting closes off reaching somewhere new; it can't inspect or
|
||||
restrict what gets sent to somewhere already trusted for normal operation
|
||||
(installing packages, calling the model API). That's a structural limit of
|
||||
allowlist-only firewalls in general, not something specific to this setup,
|
||||
and nothing here addresses it.
|
||||
- This is container-level isolation (Linux namespaces plus a small
|
||||
capability set), not VM-level isolation - no gVisor/Firecracker or
|
||||
similar. It shares the host kernel, so a container-escape vulnerability
|
||||
would defeat every other control described here at once.
|
||||
- Everything in this document has been verified by hand, at a point in
|
||||
time - there's no automated test suite re-checking these properties on
|
||||
every change, so a future edit could silently reintroduce a gap. This has
|
||||
already happened once in this file's own history (a missing `ipset`
|
||||
dedup flag broke firewall startup entirely, twice, on two different
|
||||
ipsets, before being caught).
|
||||
- Because `~/.claude` (including `.credentials.json`) is bind-mounted in,
|
||||
anything reachable from inside the container (the allowed domains above)
|
||||
is a potential exfiltration path for those credentials if a malicious repo
|
||||
manages to manipulate a session. The firewall limits this to
|
||||
Anthropic/npm/GitHub/your Gitea host, not the open internet.
|
||||
- `settings.json`, `CLAUDE.md`, and `plugins/` under `~/.claude` are
|
||||
layered back read-only on top of the (otherwise read-write) `~/.claude`
|
||||
mount. Hooks execute automatically with no separate approval step, so
|
||||
without this a compromised session could plant a backdoored hook, rewrite
|
||||
`CLAUDE.md` with injected instructions, or tamper with plugin code -
|
||||
something that would then run **unsandboxed on the host** the next time
|
||||
Claude Code starts there, turning a single compromised sandbox session
|
||||
into persistent host compromise. Everything else under `~/.claude`
|
||||
(session/project history, credentials refresh, caches) stays read-write,
|
||||
since it's runtime state Claude Code needs to update.
|
||||
`~/.claude.json` is a single file mixing that same kind of live state with
|
||||
config (e.g. `mcpServers`, if ever set) and can't be split at the mount
|
||||
level, so it's left fully read-write - accepted as a residual risk rather
|
||||
than solved.
|
||||
- DNS resolution itself is allowlisted (see "DNS is allowlisted too" above),
|
||||
not just the IP connections made afterward, closing off exfiltration via
|
||||
encoding data into query names to an attacker-controlled domain - verified
|
||||
a non-allowlisted domain gets refused outright (`status: REFUSED`) rather
|
||||
than resolving and then only failing to connect.
|
||||
- Don't mount other host secrets (`~/.ssh` private keys, cloud credential
|
||||
files) into the container. Git push auth goes through the forwarded SSH
|
||||
agent instead, which never exposes the key material itself - though note
|
||||
the agent will sign for *any* Gitea repo your key has access to, not just
|
||||
the mounted project, and the firewall's Gitea allowlist is host-based
|
||||
rather than repo-scoped, so a compromised session can still reach (and
|
||||
push to) other repos on that host you have access to. This doesn't extend
|
||||
to GitHub: port 22 there is blocked outright, so the forwarded agent has
|
||||
no transport to use for it at all (see "GitHub is read-only" above).
|
||||
- `SANDBOX_SSH_KEY` (see "Headless git push without agent forwarding") is a
|
||||
second, independent credential-loading path with the same "signs for any
|
||||
Gitea repo it has access to" shape as the forwarded agent above - the
|
||||
mitigation is that it's meant to be a dedicated deploy key scoped to only
|
||||
the repos that actually need push access, not your personal identity.
|
||||
That scoping is a convention, not something enforced here: nothing checks
|
||||
what `SANDBOX_SSH_KEY` actually points at, so pointing it at your real
|
||||
`~/.ssh/id_ed25519`/`id_rsa` instead of a dedicated key would load your
|
||||
full personal identity into the container for the whole session (`bin/
|
||||
claude-sandbox` warns if this looks like it might be happening - see
|
||||
below - but doesn't block it). The key source itself is mounted read-only
|
||||
at `/root/...`, not under the sandbox user's own home, specifically so
|
||||
that user (which the session actually runs as) has no path to read the
|
||||
raw key material - only the agent socket it's loaded into.
|
||||
- The sandbox's own user (matching your host user — see "Config parity"
|
||||
above) has no `sudo`/setuid path back to root once the container is up:
|
||||
`entrypoint.sh` runs as root, sets up the firewall directly, then drops to
|
||||
that user via `gosu` before execing `nvim`. A compromised session can't
|
||||
re-run `init-firewall.sh` with a wider allowlist to open its own
|
||||
exfiltration path. Verified: capabilities (`NET_ADMIN`/`NET_RAW`) are
|
||||
cleared from the effective/permitted sets on the `gosu` drop, and that
|
||||
user gets a permission error running `iptables` directly.
|
||||
|
||||
## Tests
|
||||
|
||||
`test/` has a [bats](https://github.com/bats-core/bats-core) suite covering
|
||||
the security invariants above - not a substitute for reading this document,
|
||||
but a way to actually catch a future change silently breaking one of them
|
||||
instead of finding out the hard way:
|
||||
|
||||
```sh
|
||||
bats docker/claude-sandbox/test/ # everything
|
||||
bats docker/claude-sandbox/test/static.bats # fast, no Docker/build needed
|
||||
SKIP_BUILD=1 bats docker/claude-sandbox/test/runtime.bats # reuse the current image
|
||||
```
|
||||
|
||||
`static.bats` greps the repo's own files - no sudo in the `Dockerfile`, every
|
||||
`ipset add` is duplicate-safe, no `docker.sock`/host-credential mounts, the
|
||||
`~/.claude` read-only overlay lines are present, `bin/claude-sandbox-allow`
|
||||
rejects a malicious domain argument. `runtime.bats` builds the image, boots
|
||||
a real container running the actual `entrypoint.sh` (a `SANDBOX_TEST_MODE`
|
||||
env var makes it stop just short of execing `nvim`, so the suite can attach
|
||||
to a fully-initialized container instead of reimplementing its own copy of
|
||||
the setup sequence), and asserts against it: IPv6 blocked, DNS/IP allowlist
|
||||
enforcement in both directions, GitHub read-only, no working `sudo`/
|
||||
`iptables`/`init-firewall.sh` as `node`, capabilities actually stripped
|
||||
(not just absent from a `docker exec` that doesn't have them to begin
|
||||
with), `ulogd2` unprivileged, and the dynamic-ipset CDN-rotation fix.
|
||||
|
||||
Every mutation in `runtime.bats` (firewall rules, `ipset` entries) happens
|
||||
inside the test container's own network namespace, same as any other
|
||||
container - none of it touches the host. Container/network names are
|
||||
derived from bats' own per-run temp directory, not anything guessable or
|
||||
likely to collide with a real container you have running.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `Dockerfile` | Base image, dev tools, nvim, Claude Code + wrapper, `gitea-pr` |
|
||||
| `fixed-domains.sh` | Shared domain list sourced by both `init-firewall.sh` and `dns-proxy.sh` |
|
||||
| `dns-proxy.sh` | Starts the DNS-allowlisting `dnsmasq`, repoints `resolv.conf` at it |
|
||||
| `init-firewall.sh` | IPv4/IPv6 egress allowlist firewall, run at container start |
|
||||
| `ulogd.conf` | `ulogd2` config: turns `init-firewall.sh`'s NFLOG-tagged packets into JSON |
|
||||
| `log-shipper.sh` | Tails the dnsmasq/ulogd2 logs and ships them to `$LOKI_URL` |
|
||||
| `entrypoint.sh` | Runs as root: DNS proxy, firewall, logging, drops to `node` via `gosu`, execs `nvim` |
|
||||
| `claude-wrapper.sh` | Shadows `claude` on `PATH` to always add `--dangerously-skip-permissions` |
|
||||
| `docker-compose.yml` | Wires up mounts/env; invoked via `bin/claude-sandbox`, not directly |
|
||||
|
||||
`bin/claude-sandbox` (repo root `bin/`) is the entry point — it resolves the
|
||||
project path, checks for a live `ssh-agent`, loads `GITEA_TOKEN`/`GH_TOKEN`
|
||||
if not already set, and runs `docker compose run`. `bin/claude-sandbox-allow`
|
||||
adds a domain to a running session's firewall from the host - see "Adding a
|
||||
domain to a running session" above.
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Shadows the real `claude` binary (installed under npm-global/bin, later on
|
||||
# PATH) so every invocation - interactive shell, or claudecode.nvim's
|
||||
# terminal - runs with --dangerously-skip-permissions, without callers
|
||||
# needing to know that.
|
||||
exec /usr/local/share/npm-global/bin/claude --dangerously-skip-permissions "$@"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user