feat: public dotfiles tier — no credential, no identity, one installer

Fresh history. This is the repo a throwaway VM clones anonymously: it brings a
machine to a working baseline and carries nothing that makes it mine.

56 files. 50 land in $HOME, 3 are chezmoi metadata, 2 are repo documentation,
1 is the manifest, and a 15-file test harness stays behind in .tests/.

What did not travel, and why:

  encrypted_private_bws-token.age   a real credential; age is dropped entirely
  .chezmoidata/bws.toml             env-var -> secret-id map; belongs with the
                                    tier that can use it
  SECRETS.md                        documentation of the rules, not config
  finish-setup.sh.tmpl              superseded by dotup
  nvim/init.lua.backup              dead file
  dot_claude/**, dot_codex/**,      120 files of agent config, private tier
  dot_pi/**

De-identified rather than dropped:

  .gitconfig   [user], the GitHub ssh rewrite and both Gitea host rewrites are
               identity, not configuration. They move behind an [include] of
               ~/.config/git/config.local, which the private tier writes. Git
               treats a missing include as a no-op, so a public-only machine
               reads the file and stops.
  .zshrc       the two gitea aliases carried a personal domain and a LAN IP.
               They move behind a guarded source of ~/.config/zsh/local.zsh,
               the sibling of the secrets.zsh seam phase 2 established.
  nvim         a commented-out LM Studio endpoint naming a LAN address.
  ghostty      a stale auto-generated header naming an absolute home directory.

Newly captured, never tracked before: ~/.zshenv, ~/.config/gh/config.yml. The
former sourced ~/.cargo/env unguarded, so every zsh on a machine without rustup
printed an error -- the same shape as the unguarded oh-my-zsh source phase 2
fixed. It is guarded now.

.chezmoiexternal.toml grows from one entry to six. oh-my-zsh, powerlevel10k,
zsh-autosuggestions, zsh-ai and tpm were hand-installed and declared nowhere,
which is why `chezmoi init --apply` on a clean box produced a .zshrc that broke
the shell it configures. The theme and both plugins nest under
.oh-my-zsh/custom/, which is what $ZSH_CUSTOM resolves to.

dotup gains an install engine. It resolves each selected package to a channel
(apt, brew, npm, uv, snap, deb, flatpak, tarball, script, builtin) through one
function every consumer reads, probes apt-cache before batching so a name apt
does not know moves to brew instead of failing all thirty, and retries
individually if a batch still fails -- which earned its keep on the first real
container run, where mermaid-cli's puppeteer dependency failed and the other
twelve npm packages installed anyway. --unattended computes safe defaults fresh
from the manifest rather than inheriting a state file, and refuses private and
invasive rows outright even when a stale state file ticks them.

The manifest gains @spec, a second directive kind alongside @needs, carrying the
argument a channel needs but a package name cannot supply -- the scoped npm
name, the flatpak app id, the .deb source. The TSV stays five columns wide.

Three bugs the container runs found, all fixed here:

  * `apt install nodejs` gives you node WITHOUT npm on Ubuntu, so all thirteen
    npm packages failed on a fresh box. The manifest asks apt for both names.
  * A tool installed a moment ago is not on this process's PATH -- uv lands in
    ~/.local/bin, npm -g honours the ~/.npmrc prefix, linuxbrew is outside a
    non-login PATH. Resolved by looking in the places we just wrote to, never by
    exporting a modified PATH.
  * `A || { B && C; }` is one || list, so when `command -v sudo` failed the list
    failed and `set -e` killed dotup at load. On a non-root machine with no
    sudo it died before printing anything. There is a regression test.

.zshenv and .p10k.zsh are marked private_. Both are shell code the login shell
executes and both applied at 664, group-writable. Third occurrence of the class
of bug phase 1 found on .pi/agent/auth.json and phase 2 found on .zshrc; the
first one found on purpose rather than by accident.

Verification: 81 assertions, 81/81 on this box and in ubuntu:24.04, ubuntu:22.04
and debian:12. The installer is driven against a directory of fake package
managers that record what they were asked to do and install nothing, so the
engine is exercised end to end without a package landing on the test machine.
`gitleaks detect` over the full history and the working tree: no leaks found,
with no allowlist and no .gitleaks.toml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
bcherb2
2026-08-17 00:11:52 -04:00
commit b487b0e855
71 changed files with 7824 additions and 0 deletions
+505
View File
@@ -0,0 +1,505 @@
{{- if eq .chezmoi.os "darwin" -}}
#? Config file for btop v. 1.4.5
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
color_theme = "TTY"
#* If the theme set background should be shown, set to False if you want terminal background transparency.
theme_background = True
#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.
truecolor = True
#* Set to true to force tty mode regardless if a real tty has been detected or not.
#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols.
force_tty = False
#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.
#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box.
#* Use whitespace " " as separator between different presets.
#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty"
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"
#* Set to True to enable "h,j,k,l,g,G" keys for directional control in lists.
#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift.
vim_keys = False
#* Rounded corners on boxes, is ignored if TTY mode is ON.
rounded_corners = True
#* Default symbols to use for graph creation, "braille", "block" or "tty".
#* "braille" offers the highest resolution but might not be included in all fonts.
#* "block" has half the resolution of braille but uses more common characters.
#* "tty" uses only 3 different symbols but will work with most fonts and should work in a real TTY.
#* Note that "tty" only has half the horizontal resolution of the other two, so will show a shorter historical view.
graph_symbol = "braille"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_cpu = "default"
# Graph symbol to use for graphs in gpu box, "default", "braille", "block" or "tty".
graph_symbol_gpu = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_mem = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_net = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_proc = "default"
#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace.
shown_boxes = "cpu mem net proc"
#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs.
update_ms = 2000
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct",
#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly.
proc_sorting = "cpu lazy"
#* Reverse sorting order, True or False.
proc_reversed = False
#* Show processes as a tree.
proc_tree = False
#* Use the cpu graph colors in the process list.
proc_colors = True
#* Use a darkening gradient in the process list.
proc_gradient = True
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.
proc_per_core = True
#* Show process memory as bytes instead of percent.
proc_mem_bytes = True
#* Show cpu graph for each process.
proc_cpu_graphs = True
#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)
proc_info_smaps = False
#* Show proc box on left side of screen instead of right.
proc_left = False
#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).
proc_filter_kernel = False
#* In tree-view, always accumulate child process resources in the parent process.
proc_aggregate = False
#* Sets the CPU stat shown in upper half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_upper = "Auto"
#* Sets the CPU stat shown in lower half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_lower = "iowait"
#* If gpu info should be shown in the cpu box. Available values = "Auto", "On" and "Off".
show_gpu_info = "Auto"
#* Toggles if the lower CPU graph should be inverted.
cpu_invert_lower = True
#* Set to True to completely disable the lower CPU graph.
cpu_single_graph = False
#* Show cpu box at bottom of screen instead of top.
cpu_bottom = False
#* Shows the system uptime in the CPU box.
show_uptime = True
#* Shows the CPU package current power consumption in watts. Requires running `make setcap` or `make setuid` or running with sudo.
show_cpu_watts = True
#* Show cpu temperature.
check_temp = True
#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.
cpu_sensor = "Auto"
#* Show temperatures for cpu cores also if check_temp is True and sensors has been found.
show_coretemp = True
#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.
#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.
#* Format "x:y" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.
#* Example: "4:0 5:1 6:3"
cpu_core_map = ""
#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine".
temp_scale = "celsius"
#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024.
base_10_sizes = False
#* Show CPU frequency.
show_cpu_freq = True
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.
#* Special formatting: /host = hostname | /user = username | /uptime = system uptime
clock_format = "%X"
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.
background_update = True
#* Custom cpu model name, empty string to disable.
custom_cpu_name = ""
#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace " ".
#* Only disks matching the filter will be shown. Prepend exclude= to only show disks not matching the filter. Examples: disk_filter="/boot /home/user", disks_filter="exclude=/boot /home/user"
disks_filter = ""
#* Show graphs instead of meters for memory values.
mem_graphs = True
#* Show mem box below net box instead of above.
mem_below_net = False
#* Count ZFS ARC in cached and available memory.
zfs_arc_cached = True
#* If swap memory should be shown in memory box.
show_swap = True
#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.
swap_disk = True
#* If mem box should be split to also show disks info.
show_disks = True
#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.
only_physical = False
#* Read disks list from /etc/fstab. This also disables only_physical.
use_fstab = True
#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)
zfs_hide_datasets = False
#* Set to true to show available disk space for privileged users.
disk_free_priv = True
#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view.
show_io_stat = True
#* Toggles io mode for disks, showing big graphs for disk read/write speeds.
io_mode = False
#* Set to True to show combined read/write io graphs in io mode.
io_graph_combined = False
#* Set the top speed for the io graphs in MiB/s (100 by default), use format "mountpoint:speed" separate disks with whitespace " ".
#* Example: "/mnt/media:100 /:20 /boot:1".
io_graph_speeds = ""
#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.
net_download = 100
net_upload = 100
#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.
net_auto = True
#* Sync the auto scaling for download and upload to whichever currently has the highest scale.
net_sync = True
#* Starts with the Network Interface specified here.
net_iface = ""
#* "True" shows bitrates in base 10 (Kbps, Mbps). "False" shows bitrates in binary sizes (Kibps, Mibps, etc.). "Auto" uses base_10_sizes.
base_10_bitrate = "Auto"
#* Show battery stats in top right if battery is present.
show_battery = True
#* Which battery to use if multiple are present. "Auto" for auto detection.
selected_battery = "Auto"
#* Show power stats of battery next to charge indicator.
show_battery_watts = True
#* Set loglevel for "~/.config/btop/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG".
#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info.
log_level = "WARNING"
#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards.
nvml_measure_pcie_speeds = True
#* Measure PCIe throughput on AMD cards, may impact performance on certain cards.
rsmi_measure_pcie_speeds = True
#* Horizontally mirror the GPU graph.
gpu_mirror_graph = True
#* Custom gpu0 model name, empty string to disable.
custom_gpu_name0 = ""
#* Custom gpu1 model name, empty string to disable.
custom_gpu_name1 = ""
#* Custom gpu2 model name, empty string to disable.
custom_gpu_name2 = ""
#* Custom gpu3 model name, empty string to disable.
custom_gpu_name3 = ""
#* Custom gpu4 model name, empty string to disable.
custom_gpu_name4 = ""
#* Custom gpu5 model name, empty string to disable.
custom_gpu_name5 = ""
{{- else if eq .chezmoi.os "linux" -}}
#? Config file for btop v. 1.3.0
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
color_theme = "TTY"
#* If the theme set background should be shown, set to False if you want terminal background transparency.
theme_background = True
#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.
truecolor = True
#* Set to true to force tty mode regardless if a real tty has been detected or not.
#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols.
force_tty = False
#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.
#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box.
#* Use whitespace " " as separator between different presets.
#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty"
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"
#* Set to True to enable "h,j,k,l,g,G" keys for directional control in lists.
#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift.
vim_keys = False
#* Rounded corners on boxes, is ignored if TTY mode is ON.
rounded_corners = True
#* Default symbols to use for graph creation, "braille", "block" or "tty".
#* "braille" offers the highest resolution but might not be included in all fonts.
#* "block" has half the resolution of braille but uses more common characters.
#* "tty" uses only 3 different symbols but will work with most fonts and should work in a real TTY.
#* Note that "tty" only has half the horizontal resolution of the other two, so will show a shorter historical view.
graph_symbol = "braille"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_cpu = "default"
# Graph symbol to use for graphs in gpu box, "default", "braille", "block" or "tty".
graph_symbol_gpu = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_mem = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_net = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_proc = "default"
#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace.
shown_boxes = "cpu mem net proc"
#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs.
update_ms = 2000
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct",
#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly.
proc_sorting = "cpu direct"
#* Reverse sorting order, True or False.
proc_reversed = False
#* Show processes as a tree.
proc_tree = False
#* Use the cpu graph colors in the process list.
proc_colors = True
#* Use a darkening gradient in the process list.
proc_gradient = True
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.
proc_per_core = True
#* Show process memory as bytes instead of percent.
proc_mem_bytes = True
#* Show cpu graph for each process.
proc_cpu_graphs = True
#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)
proc_info_smaps = False
#* Show proc box on left side of screen instead of right.
proc_left = False
#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).
proc_filter_kernel = False
#* In tree-view, always accumulate child process resources in the parent process.
proc_aggregate = False
#* Sets the CPU stat shown in upper half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_upper = "Auto"
#* Sets the CPU stat shown in lower half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_lower = "iowait"
#* If gpu info should be shown in the cpu box. Available values = "Auto", "On" and "Off".
show_gpu_info = "Auto"
#* Toggles if the lower CPU graph should be inverted.
cpu_invert_lower = True
#* Set to True to completely disable the lower CPU graph.
cpu_single_graph = False
#* Show cpu box at bottom of screen instead of top.
cpu_bottom = False
#* Shows the system uptime in the CPU box.
show_uptime = True
#* Show cpu temperature.
check_temp = True
#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.
cpu_sensor = "Auto"
#* Show temperatures for cpu cores also if check_temp is True and sensors has been found.
show_coretemp = True
#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.
#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.
#* Format "x:y" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.
#* Example: "4:0 5:1 6:3"
cpu_core_map = ""
#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine".
temp_scale = "celsius"
#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024.
base_10_sizes = False
#* Show CPU frequency.
show_cpu_freq = True
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.
#* Special formatting: /host = hostname | /user = username | /uptime = system uptime
clock_format = "%X"
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.
background_update = True
#* Custom cpu model name, empty string to disable.
custom_cpu_name = ""
#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace " ".
#* Begin line with "exclude=" to change to exclude filter, otherwise defaults to "most include" filter. Example: disks_filter="exclude=/boot /home/user".
disks_filter = ""
#* Show graphs instead of meters for memory values.
mem_graphs = True
#* Show mem box below net box instead of above.
mem_below_net = False
#* Count ZFS ARC in cached and available memory.
zfs_arc_cached = True
#* If swap memory should be shown in memory box.
show_swap = True
#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.
swap_disk = True
#* If mem box should be split to also show disks info.
show_disks = True
#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.
only_physical = False
#* Read disks list from /etc/fstab. This also disables only_physical.
use_fstab = True
#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)
zfs_hide_datasets = False
#* Set to true to show available disk space for privileged users.
disk_free_priv = True
#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view.
show_io_stat = True
#* Toggles io mode for disks, showing big graphs for disk read/write speeds.
io_mode = False
#* Set to True to show combined read/write io graphs in io mode.
io_graph_combined = False
#* Set the top speed for the io graphs in MiB/s (100 by default), use format "mountpoint:speed" separate disks with whitespace " ".
#* Example: "/mnt/media:100 /:20 /boot:1".
io_graph_speeds = ""
#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.
net_download = 100
net_upload = 100
#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.
net_auto = False
#* Sync the auto scaling for download and upload to whichever currently has the highest scale.
net_sync = True
#* Starts with the Network Interface specified here.
net_iface = ""
#* Show battery stats in top right if battery is present.
show_battery = True
#* Which battery to use if multiple are present. "Auto" for auto detection.
selected_battery = "Auto"
#* Set loglevel for "~/.config/btop/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG".
#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info.
log_level = "WARNING"
#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards.
nvml_measure_pcie_speeds = True
#* Horizontally mirror the GPU graph.
gpu_mirror_graph = True
#* Custom gpu0 model name, empty string to disable.
custom_gpu_name0 = ""
#* Custom gpu1 model name, empty string to disable.
custom_gpu_name1 = ""
#* Custom gpu2 model name, empty string to disable.
custom_gpu_name2 = ""
#* Custom gpu3 model name, empty string to disable.
custom_gpu_name3 = ""
#* Custom gpu4 model name, empty string to disable.
custom_gpu_name4 = ""
#* Custom gpu5 model name, empty string to disable.
custom_gpu_name5 = ""
{{- end }}
+27
View File
@@ -0,0 +1,27 @@
# The current version of the config schema
version: 1
# What protocol to use when performing git operations. Supported values: ssh, https
git_protocol: https
# What editor gh should run when creating issues, pull requests, etc. If blank, will refer to environment.
editor:
# When to interactively prompt. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled
prompt: enabled
# Preference for editor-based interactive prompting. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled
prefer_editor_prompt: disabled
# A pager program to send command output to, e.g. "less". If blank, will refer to environment. Set the value to "cat" to disable the pager.
pager:
# Aliases allow you to create nicknames for gh commands
aliases:
co: pr checkout
# The path to a unix socket through which to send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport.
http_unix_socket:
# What web browser gh should use when opening URLs. If blank, will refer to environment.
browser:
# Whether to display labels using their RGB hex color codes in terminals that support truecolor. Supported values: enabled, disabled
color_labels: disabled
# Whether customizable, 4-bit accessible colors should be used. Supported values: enabled, disabled
accessible_colors: disabled
# Whether an accessible prompter should be used. Supported values: enabled, disabled
accessible_prompter: disabled
# Whether to use a animated spinner as a progress indicator. If disabled, a textual progress indicator is used instead. Supported values: enabled, disabled
spinner: enabled
+56
View File
@@ -0,0 +1,56 @@
# This is the configuration file for Ghostty.
#
# The template does not set any default options, since Ghostty ships
# with sensible defaults for all options. Users should only need to set
# options that they want to change from the default.
#
# Run `ghostty +show-config --default --docs` to view a list of
# all available config options and their default values.
#
# Additionally, each config option is also explained in detail
# on Ghostty's website, at https://ghostty.org/docs/config.
# Config syntax crash course
# ==========================
# # The config file consists of simple key-value pairs,
# # separated by equals signs.
# font-family = Iosevka
# window-padding-x = 2
#
# # Spacing around the equals sign does not matter.
# # All of these are identical:
# key=value
# key= value
# key =value
# key = value
#
# # Any line beginning with a # is a comment. It's not possible to put
# # a comment after a config option, since it would be interpreted as a
# # part of the value. For example, this will have a value of "#123abc":
# background = #123abc
#
# # Empty values are used to reset config keys to default.
# key =
#
# # Some config options have unique syntaxes for their value,
# # which is explained in the docs for that config option.
# # Just for example:
# resize-overlay-duration = 4s 200ms
shell-integration-features = sudo
keybind = shift+enter=text:\n
#font-family = "CaskaydiaMono Nerd Font Mono ExtraLight"
font-family = "JetBrainsMono NFM Thin"
font-family-italic = "JetBrainsMono NFM Thin"
font-family-bold = "JetBrainsMono NFM Light"
font-family-bold-italic = "JetBrainsMono NFM Light"
# VS Code-like visual improvements
font-size = 13
adjust-cell-height = 15%
window-padding-x = 12
window-padding-y = 8
cursor-style = bar
cursor-style-blink = true
{{ if eq .chezmoi.os "darwin" }}
background-opacity = 1.00
{{- end }}
+1
View File
@@ -0,0 +1 @@
**/.claude/settings.local.json
+35
View File
@@ -0,0 +1,35 @@
[ui]
agent_panel_sort = "spaces"
[ui.sound]
enabled = false
[ui.toast]
delivery = "herdr"
[experimental]
kitty_graphics = true
[keys]
next_tab = "shift+right"
previous_tab = "shift+left"
next_workspace = "shift+down"
previous_workspace = "shift+up"
[[keys.command]]
key = "prefix+p"
type = "plugin_action"
command = "jt.command-palette.open"
description = "Command palette"
[[keys.command]]
key = "cmd+r"
type = "plugin_action"
command = "persiyanov.reviewr.toggle"
description = "reviewr: toggle sidebar"
[[keys.command]]
key = "prefix+shift+a"
type = "plugin_action"
command = "herdr-spreader.apply"
description = "Spreader: apply layout"
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,210 @@
# LazyVim Plugin Cheatsheet
Quick reference for the custom plugins in your configuration.
---
## Git Tools (`lua/plugins/git.lua`)
### git-blame.nvim
Shows git blame information at the end of each line.
**Commands:**
- `:GitBlameToggle` - Toggle git blame on/off
- `:GitBlameEnable` - Enable git blame
- `:GitBlameDisable` - Disable git blame
- `:GitBlameOpenCommitURL` - Open commit in browser
- `:GitBlameCopySHA` - Copy commit SHA to clipboard
**Default:** Enabled on startup
---
### diffview.nvim
Advanced git diff and history viewer.
**Key Bindings:**
- `<leader>gd` - Open diff view of current changes
- `<leader>gh` - View current file's git history
- `<leader>gH` - View entire repo's git history
**Commands:**
- `:DiffviewOpen` - Open diff view
- `:DiffviewClose` - Close diff view
- `:DiffviewFileHistory` - View repo history
- `:DiffviewFileHistory %` - View current file history
- `:DiffviewRefresh` - Refresh the diff view
**In Diffview:**
- `]c` / `[c` - Jump to next/previous change
- `<tab>` / `<S-tab>` - Select next/previous file
- `gf` - Open file in new tab
- `q` - Close diffview
---
## UI Enhancements (`lua/plugins/ui-enhancements.lua`)
### render-markdown.nvim
Renders markdown with better formatting while editing.
**Features:**
- Automatically styles headers, lists, code blocks
- Works only in markdown files
- No commands needed - always active in `.md` files
**Toggle:** Disable by setting `enabled = false` in config
---
### hlargs.nvim
Highlights function arguments with different colors.
**Features:**
- Automatically highlights function parameters
- Helps distinguish arguments visually
- Works across all supported languages
**No commands needed** - always active
---
### twilight.nvim
Dims inactive code to focus on current block.
**Key Bindings:**
- `<leader>ut` - Toggle Twilight mode
**Commands:**
- `:Twilight` - Toggle twilight
- `:TwilightEnable` - Enable twilight
- `:TwilightDisable` - Disable twilight
**Best for:** Focusing on specific functions or code blocks
---
### theme-hub.nvim
Browse and switch colorschemes with live preview.
**Key Bindings:**
- `<leader>uT` - Open Theme Hub
**Commands:**
- `:ThemeHub` - Open theme browser
**In Theme Hub:**
- `j` / `k` - Navigate themes
- `<CR>` - Apply selected theme
- `<Esc>` / `q` - Close without applying
---
## Search Tools (`lua/plugins/search.lua`)
### nvim-hlslens
Enhanced search highlighting with match counts.
**Key Bindings:**
- `n` - Next search result (enhanced)
- `N` - Previous search result (enhanced)
- `*` - Search word under cursor (enhanced)
- `#` - Search word backward (enhanced)
- `g*` / `g#` - Search without word boundaries
**Features:**
- Shows match count (e.g., "3/15")
- Floating window near cursor
- Automatically clears after cursor movement
---
### search-replace.nvim
Advanced multi-buffer search and replace.
**Single Buffer Operations:**
- `<leader>rs` - Search/replace in visual selection
- `<leader>ro` - Search/replace (open prompt)
- `<leader>rw` - Search/replace word under cursor
- `<leader>rW` - Search/replace WORD under cursor
- `<leader>re` - Search/replace expression
- `<leader>rf` - Search/replace filename
**Multi-Buffer Operations:**
- `<leader>rbs` - Search/replace selections (all buffers)
- `<leader>rbo` - Search/replace open (all buffers)
- `<leader>rbw` - Search/replace word (all buffers)
- `<leader>rbW` - Search/replace WORD (all buffers)
- `<leader>rbe` - Search/replace expression (all buffers)
- `<leader>rbf` - Search/replace filename (all buffers)
**Commands:**
- `:SearchReplaceSingleBuffer` - Single buffer mode
- `:SearchReplaceMultiBuffer` - Multi-buffer mode
- `:SearchReplaceWithinVisualSelection` - Within selection
---
## File Type Tools (`lua/plugins/filetypes.lua`)
### csvview.nvim
Formats and aligns CSV/TSV files for better readability.
**Key Bindings:**
- `<leader>cv` - Toggle CSV view (only in CSV/TSV files)
**Commands:**
- `:CsvViewEnable` - Enable CSV formatting
- `:CsvViewDisable` - Disable CSV formatting
- `:CsvViewToggle` - Toggle CSV view
**File Types:** Automatically works with `.csv` and `.tsv` files
---
## Built-in LazyVim Extras
These are enabled via `lazyvim.json` and have their own keybindings:
### Telescope
- `<leader>ff` - Find files
- `<leader>fg` - Live grep
- `<leader>fb` - Browse buffers
- `<leader>fh` - Help tags
### Neo-tree
- `<leader>e` - Toggle file explorer
- `<leader>E` - Toggle file explorer (current file)
### Harpoon2
- `<leader>h` - Harpoon menu
- `<leader>a` - Add file to harpoon
### Aerial (Code Outline)
- `<leader>cs` - Open symbols outline
### Project Management
- `<leader>fp` - Find project
---
## Tips
1. **Enable/Disable Plugins:** Edit the respective file in `lua/plugins/` and set `enabled = false`
2. **View All Keymaps:** Press `<leader>sk` (search keymaps) in Telescope
3. **Check Plugin Status:** Run `:Lazy` to see installed plugins
4. **Update Plugins:** Run `:Lazy update`
5. **View Help:** Most plugins have `:help <plugin-name>` documentation
---
## Plugin Files Location
```
~/.config/nvim/lua/plugins/
├── git.lua # Git blame & diffview
├── ui-enhancements.lua # Render-markdown, hlargs, twilight, theme-hub
├── search.lua # Hlslens, search-replace
├── filetypes.lua # CSV viewer
└── markdown.lua # Markdown-specific config
```
+4
View File
@@ -0,0 +1,4 @@
# 💤 LazyVim
A starter template for [LazyVim](https://github.com/LazyVim/LazyVim).
Refer to the [documentation](https://lazyvim.github.io/installation) to get started.
+9
View File
@@ -0,0 +1,9 @@
tt.*
.tests
doc/tags
debug
.repro
foo.*
*.log
data
.claude
+15
View File
@@ -0,0 +1,15 @@
{
"neodev": {
"library": {
"enabled": true,
"plugins": true
}
},
"neoconf": {
"plugins": {
"lua_ls": {
"enabled": true
}
}
}
}
+2
View File
@@ -0,0 +1,2 @@
-- bootstrap lazy.nvim, LazyVim and your plugins
require("config.lazy")
+28
View File
@@ -0,0 +1,28 @@
{
"extras": [
"lazyvim.plugins.extras.ai.claudecode",
"lazyvim.plugins.extras.coding.yanky",
"lazyvim.plugins.extras.editor.aerial",
"lazyvim.plugins.extras.editor.harpoon2",
"lazyvim.plugins.extras.editor.illuminate",
"lazyvim.plugins.extras.editor.mini-diff",
"lazyvim.plugins.extras.editor.neo-tree",
"lazyvim.plugins.extras.editor.refactoring",
"lazyvim.plugins.extras.editor.telescope",
"lazyvim.plugins.extras.lang.docker",
"lazyvim.plugins.extras.lang.go",
"lazyvim.plugins.extras.lang.json",
"lazyvim.plugins.extras.lang.python",
"lazyvim.plugins.extras.lang.yaml",
"lazyvim.plugins.extras.ui.smear-cursor",
"lazyvim.plugins.extras.ui.treesitter-context",
"lazyvim.plugins.extras.util.chezmoi",
"lazyvim.plugins.extras.util.dot",
"lazyvim.plugins.extras.util.project"
],
"install_version": 8,
"news": {
"NEWS.md": "11866"
},
"version": 8
}
@@ -0,0 +1,8 @@
-- Autocmds are automatically loaded on the VeryLazy event
-- Default autocmds that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/autocmds.lua
--
-- Add any additional autocmds here
-- with `vim.api.nvim_create_autocmd`
--
-- Or remove existing autocmds by their group name (which is prefixed with `lazyvim_` for the defaults)
-- e.g. vim.api.nvim_del_augroup_by_name("lazyvim_wrap_spell")
@@ -0,0 +1,15 @@
-- Keymaps are automatically loaded on the VeryLazy event
-- Default keymaps that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/keymaps.lua
-- Add any additional keymaps here
--
-- -- Copy relative path
vim.keymap.set("n", "<leader>cy", function()
vim.fn.setreg("+", vim.fn.expand("%"))
print("Copied relative path: " .. vim.fn.expand("%"))
end, { desc = "Copy relative path" })
-- Copy absolute path
vim.keymap.set("n", "<leader>cY", function()
vim.fn.setreg("+", vim.fn.expand("%:p"))
print("Copied absolute path: " .. vim.fn.expand("%:p"))
end, { desc = "Copy absolute path" })
@@ -0,0 +1,53 @@
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
if vim.v.shell_error ~= 0 then
vim.api.nvim_echo({
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
{ out, "WarningMsg" },
{ "\nPress any key to exit..." },
}, true, {})
vim.fn.getchar()
os.exit(1)
end
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({
spec = {
-- add LazyVim and import its plugins
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
-- import/override with your plugins
{ import = "plugins" },
},
defaults = {
-- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup.
-- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default.
lazy = false,
-- It's recommended to leave version=false for now, since a lot the plugin that support versioning,
-- have outdated releases, which may break your Neovim install.
version = false, -- always use the latest git commit
-- version = "*", -- try installing the latest stable version for plugins that support semver
},
install = { colorscheme = { "tokyonight", "habamax" } },
checker = {
enabled = true, -- check for plugin updates periodically
notify = false, -- notify on update
}, -- automatically check for plugin updates
performance = {
rtp = {
-- disable some rtp plugins
disabled_plugins = {
"gzip",
-- "matchit",
-- "matchparen",
-- "netrwPlugin",
"tarPlugin",
"tohtml",
"tutor",
"zipPlugin",
},
},
},
})
@@ -0,0 +1,82 @@
-- Options are automatically loaded before lazy.nvim startup
-- Default options that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/options.lua
-- Add any additional options here
-- Clipboard provider. LazyVim disables `clipboard=unnamedplus` under SSH, so
-- select the right provider and explicitly re-enable clipboard-linked yanks:
-- * tmux -> tmux's own clipboard buffer (readable)
-- * SSH/remote -> OSC 52 copy-only (see note below)
-- * local macOS -> native pbcopy/pbpaste.
-- Do NOT use OSC 52 locally: yanky's `sync_with_ring` reads the system
-- clipboard, and OSC 52 reads depend on the terminal answering a query, which
-- Ghostty refuses by default -> OSC 52 error + yanky stack trace. Neovim 0.12
-- also auto-prefers OSC 52 over pbcopy, so the macOS provider must be explicit.
if vim.env.TMUX then
vim.g.clipboard = "tmux"
elseif vim.env.SSH_CONNECTION then
-- Remote session (bare SSH, or a multiplexer like herdr that exports no env
-- var of its own). Copy via OSC 52 so yanks reach the OUTER terminal's
-- clipboard, but NEVER read the clipboard back: OSC 52 reads require the
-- terminal to answer a query, which Warp/herdr don't, so a read returns stale
-- data (or hangs) and blows up yanky's ring sync. Trade-off: pasting FROM the
-- local host INTO nvim over SSH won't work here -- use the terminal's own
-- paste in insert mode.
--
-- The paste function below must NOT call vim.fn.getreg('"')/'*'/'+' itself:
-- Neovim's clipboard provider (autoload/provider/clipboard.vim) guards
-- against re-entrant provider calls (`s:here`, see nvim#7184), so a getreg()
-- of a clipboard-linked register issued from *inside* a registered paste()
-- callback is swallowed and silently returns empty -- not the register's
-- real content. Cache what copy() was last given instead, and have paste()
-- return that cached value directly.
local osc52 = require("vim.ui.clipboard.osc52")
local last = { lines = {}, regtype = "" }
local function make_copy(reg)
local osc52_copy = osc52.copy(reg)
return function(lines, regtype)
last.lines, last.regtype = lines, regtype
osc52_copy(lines, regtype)
end
end
local function paste_from_cache()
return { last.lines, last.regtype }
end
vim.g.clipboard = {
name = "osc52-copyonly",
copy = { ["+"] = make_copy("+"), ["*"] = make_copy("*") },
paste = { ["+"] = paste_from_cache, ["*"] = paste_from_cache },
cache_enabled = 0,
}
elseif vim.fn.has("mac") == 1 then
vim.g.clipboard = {
name = "pbcopy",
copy = { ["+"] = "pbcopy", ["*"] = "pbcopy" },
paste = { ["+"] = "pbpaste", ["*"] = "pbpaste" },
cache_enabled = 0,
}
end
-- Make ordinary y/Y use the selected provider. LazyVim applies its SSH default
-- after this file is read, so restore the option once startup has settled. The
-- schedule keeps this callback after LazyVim's own VeryLazy clipboard callback.
vim.api.nvim_create_autocmd("User", {
pattern = "VeryLazy",
once = true,
callback = function()
vim.schedule(function()
vim.opt.clipboard = "unnamedplus"
end)
end,
})
-- VS Code-like visual improvements
vim.opt.cursorline = true -- Highlight current line
-- vim.opt.colorcolumn = "80,120" -- Disabled: renders poorly with some themes
vim.opt.scrolloff = 8 -- Keep 8 lines visible above/below cursor
vim.opt.sidescrolloff = 8 -- Keep 8 columns visible left/right
vim.opt.smoothscroll = true -- Smooth scrolling
-- Make Shift+movement and mouse selections behave like a conventional editor.
-- Keep `cmd` out of selectmode so v/V continue to enter Vim's Visual mode.
vim.opt.keymodel = { "startsel", "stopsel" }
vim.opt.selectmode = { "key", "mouse" }
@@ -0,0 +1,37 @@
local completion_state = {
enabled = true, -- current state
backup = { -- store the original values so we can restore them
omnifunc = nil,
completefunc = nil,
completeopt = nil,
},
}
vim.keymap.set("n", "<leader>co", function()
if completion_state.enabled then
-- ----- DISABLE -----
completion_state.backup.omnifunc = vim.opt_local.omnifunc:get()
completion_state.backup.completefunc = vim.opt_local.completefunc:get()
completion_state.backup.completeopt = vim.opt_local.completeopt:get()
-- Clear the sources
vim.opt_local.omnifunc = ""
vim.opt_local.completefunc = ""
-- Hide the menu even if something tries to show it
vim.opt_local.completeopt = { "noinsert", "noselect" }
completion_state.enabled = false
print("⛔ Autocompletion disabled")
else
-- ----- ENABLE -----
vim.opt_local.omnifunc = completion_state.backup.omnifunc
vim.opt_local.completefunc = completion_state.backup.completefunc
-- Restore the previous `completeopt` (or a safe default)
vim.opt_local.completeopt = completion_state.backup.completeopt or { "menu", "preview", "noinsert", "noselect" }
completion_state.enabled = true
print("✅ Autocompletion re‑enabled")
end
end, { desc = "Toggle all autocompletion (omnifunc, completefunc, menu)" })
@@ -0,0 +1,35 @@
return {
dir = vim.fn.expand("~/code/nvim-ai-assist"),
name = "nvim_ai_assist",
lazy = false,
cmd = { "AI", "AIHealth", "AIRefreshContext" },
keys = {
{ "<leader>ai", ":AI<CR>", desc = "AI command (prompt)", mode = "n" },
{ "<leader>aA", ":AI!<CR>", desc = "AI command (auto-execute)", mode = "n" },
{ "<leader>ah", ":AIHealth<CR>", desc = "AI health + context stats", mode = "n" },
},
config = function()
require("nvim_ai_assist").setup({
-- Active: Z.AI Coding Plan (uses ZAI_API_KEY from env)
provider = "zai-coding",
model = "glm-4.7",
max_tokens = 300,
temperature = 0.05,
debug = true, -- writes request/response to ~/.cache/nvim/nvim_ai_assist.log; :AILog to view
context = {
enabled = true,
max_chars = 1500,
refresh_seconds = 60,
},
-- Local LM Studio fallback — uncomment to use, and comment out the
-- provider/model lines above.
-- The host is deliberately not written down here: this repo is public,
-- and a LAN address is one of the things the tier split exists to keep
-- out of it. Put yours in and do not commit it back.
-- endpoint = "http://<lm-studio-host>:1234/v1/chat/completions",
-- api_key = "lm-studio",
-- model = "qwen3-almost-human-y1-7b",
})
end,
}
@@ -0,0 +1,32 @@
-- Tame blink.cmp completions for prose:
-- * drop the `buffer` (next-word) source everywhere
-- * markdown/md-render buffers use LSP+path only (no snippet callout noise)
-- * disable friendly-snippets' global snippets (datetime/dateMDY/lorem/...)
-- Inline AI suggestions are handled by minuet instead (see minuet.lua).
return {
"saghen/blink.cmp",
opts = function(_, opts)
opts.sources = opts.sources or {}
-- Drop the buffer (next-word) source everywhere.
opts.sources.default = vim.tbl_filter(function(source)
return source ~= "buffer"
end, opts.sources.default or { "lsp", "path", "snippets" })
-- Markdown editing (ft=markdown) and md-render's render buffer (ft=md-render):
-- LSP+path only, so callout snippets like !WARNING don't pop up. md-render
-- flips the buffer filetype between these two as you edit, so cover both.
opts.sources.per_filetype = opts.sources.per_filetype or {}
opts.sources.per_filetype.markdown = { "lsp", "path" }
opts.sources.per_filetype["md-render"] = { "lsp", "path" }
-- Kill friendly-snippets' GLOBAL snippets (global.json: datetime, dateMDY,
-- lorem, etc.). These load in every filetype regardless of the per_filetype
-- rules above, which is why they leaked through while editing markdown.
-- Per-language snippets (friendly_snippets) are unaffected.
opts.sources.providers = opts.sources.providers or {}
opts.sources.providers.snippets = opts.sources.providers.snippets or {}
opts.sources.providers.snippets.opts = opts.sources.providers.snippets.opts or {}
opts.sources.providers.snippets.opts.global_snippets = {}
end,
}
@@ -0,0 +1,14 @@
return {
"sindrets/diffview.nvim",
cmd = { "DiffviewOpen", "DiffviewFileHistory", "DiffviewClose" },
keys = {
{ "<leader>dv", "<cmd>DiffviewOpen<cr>", desc = "Open diffview" },
{ "<leader>ds", "<cmd>DiffviewOpen --staged<cr>", desc = "Diffview staged" },
{ "<leader>dh", "<cmd>DiffviewFileHistory %<cr>", desc = "File history (current)" },
{ "<leader>dH", "<cmd>DiffviewFileHistory<cr>", desc = "File history (all)" },
},
config = function()
require("diffview").setup({})
-- removed the vim.keymap.set lines from here
end,
}
@@ -0,0 +1,197 @@
-- since this is just an example spec, don't actually load anything here and return an empty spec
-- stylua: ignore
if true then return {} end
-- every spec file under the "plugins" directory will be loaded automatically by lazy.nvim
--
-- In your plugin files, you can:
-- * add extra plugins
-- * disable/enabled LazyVim plugins
-- * override the configuration of LazyVim plugins
return {
-- add gruvbox
{ "ellisonleao/gruvbox.nvim" },
-- Configure LazyVim to load gruvbox
{
"LazyVim/LazyVim",
opts = {
colorscheme = "gruvbox",
},
},
-- change trouble config
{
"folke/trouble.nvim",
-- opts will be merged with the parent spec
opts = { use_diagnostic_signs = true },
},
-- disable trouble
{ "folke/trouble.nvim", enabled = false },
-- override nvim-cmp and add cmp-emoji
{
"hrsh7th/nvim-cmp",
dependencies = { "hrsh7th/cmp-emoji" },
---@param opts cmp.ConfigSchema
opts = function(_, opts)
table.insert(opts.sources, { name = "emoji" })
end,
},
-- change some telescope options and a keymap to browse plugin files
{
"nvim-telescope/telescope.nvim",
keys = {
-- add a keymap to browse plugin files
-- stylua: ignore
{
"<leader>fp",
function() require("telescope.builtin").find_files({ cwd = require("lazy.core.config").options.root }) end,
desc = "Find Plugin File",
},
},
-- change some options
opts = {
defaults = {
layout_strategy = "horizontal",
layout_config = { prompt_position = "top" },
sorting_strategy = "ascending",
winblend = 0,
},
},
},
-- add pyright to lspconfig
{
"neovim/nvim-lspconfig",
---@class PluginLspOpts
opts = {
---@type lspconfig.options
servers = {
-- pyright will be automatically installed with mason and loaded with lspconfig
pyright = {},
},
},
},
-- add tsserver and setup with typescript.nvim instead of lspconfig
{
"neovim/nvim-lspconfig",
dependencies = {
"jose-elias-alvarez/typescript.nvim",
init = function()
require("lazyvim.util").lsp.on_attach(function(_, buffer)
-- stylua: ignore
vim.keymap.set( "n", "<leader>co", "TypescriptOrganizeImports", { buffer = buffer, desc = "Organize Imports" })
vim.keymap.set("n", "<leader>cR", "TypescriptRenameFile", { desc = "Rename File", buffer = buffer })
end)
end,
},
---@class PluginLspOpts
opts = {
---@type lspconfig.options
servers = {
-- tsserver will be automatically installed with mason and loaded with lspconfig
tsserver = {},
},
-- you can do any additional lsp server setup here
-- return true if you don't want this server to be setup with lspconfig
---@type table<string, fun(server:string, opts:_.lspconfig.options):boolean?>
setup = {
-- example to setup with typescript.nvim
tsserver = function(_, opts)
require("typescript").setup({ server = opts })
return true
end,
-- Specify * to use this function as a fallback for any server
-- ["*"] = function(server, opts) end,
},
},
},
-- for typescript, LazyVim also includes extra specs to properly setup lspconfig,
-- treesitter, mason and typescript.nvim. So instead of the above, you can use:
{ import = "lazyvim.plugins.extras.lang.typescript" },
-- add more treesitter parsers
{
"nvim-treesitter/nvim-treesitter",
opts = {
ensure_installed = {
"bash",
"html",
"javascript",
"json",
"lua",
"markdown",
"markdown_inline",
"python",
"query",
"regex",
"tsx",
"typescript",
"vim",
"yaml",
},
},
},
-- since `vim.tbl_deep_extend`, can only merge tables and not lists, the code above
-- would overwrite `ensure_installed` with the new value.
-- If you'd rather extend the default config, use the code below instead:
{
"nvim-treesitter/nvim-treesitter",
opts = function(_, opts)
-- add tsx and treesitter
vim.list_extend(opts.ensure_installed, {
"tsx",
"typescript",
})
end,
},
-- the opts function can also be used to change the default opts:
{
"nvim-lualine/lualine.nvim",
event = "VeryLazy",
opts = function(_, opts)
table.insert(opts.sections.lualine_x, {
function()
return "😄"
end,
})
end,
},
-- or you can return new options to override all the defaults
{
"nvim-lualine/lualine.nvim",
event = "VeryLazy",
opts = function()
return {
--[[add your custom lualine config here]]
}
end,
},
-- use mini.starter instead of alpha
{ import = "lazyvim.plugins.extras.ui.mini-starter" },
-- add jsonls and schemastore packages, and setup treesitter for json, json5 and jsonc
{ import = "lazyvim.plugins.extras.lang.json" },
-- add any tools you want to have installed below
{
"williamboman/mason.nvim",
opts = {
ensure_installed = {
"stylua",
"shellcheck",
"shfmt",
"flake8",
},
},
},
}
@@ -0,0 +1,36 @@
-- Filetype-specific plugins
return {
-- CSV file viewer with column alignment
-- Automatically formats CSV files for better readability
-- Toggle with :CsvViewEnable, :CsvViewDisable
{
"hat0uma/csvview.nvim",
ft = { "csv", "tsv" },
enabled = true, -- Set to false to disable
opts = {
parser = {
async = true,
delimiter = {
default = ",",
ft = {
tsv = "\t",
},
},
comments = {
-- Example: Set comment prefix for specific filetypes
-- ft = {
-- csv = "#",
-- },
},
},
view = {
min_column_width = 5,
spacing = 2,
display_mode = "border", -- Options: "highlight", "border"
},
},
keys = {
{ "<leader>cv", "<cmd>CsvViewToggle<cr>", desc = "Toggle CSV View", ft = { "csv", "tsv" } },
},
},
}
@@ -0,0 +1,36 @@
-- Git enhancement plugins
return {
-- Git blame annotations in virtual text
-- Shows git blame info at the end of each line
-- Toggle with :GitBlameToggle
{
"f-person/git-blame.nvim",
event = "VeryLazy",
opts = {
enabled = true, -- Start enabled
message_template = " <summary> • <date> • <author>",
date_format = "%r",
virtual_text_column = 80,
},
},
-- Advanced git diff viewer
-- Better git diff interface with file history
-- Open with :DiffviewOpen, :DiffviewFileHistory
{
"sindrets/diffview.nvim",
cmd = { "DiffviewOpen", "DiffviewClose", "DiffviewToggleFiles", "DiffviewFocusFiles", "DiffviewFileHistory" },
opts = {
enhanced_diff_hl = true,
view = {
default = { layout = "diff2_horizontal" },
file_history = { layout = "diff2_horizontal" },
},
},
keys = {
{ "<leader>gd", "<cmd>DiffviewOpen<cr>", desc = "Open Diffview" },
{ "<leader>gh", "<cmd>DiffviewFileHistory %<cr>", desc = "File History" },
{ "<leader>gH", "<cmd>DiffviewFileHistory<cr>", desc = "Repo History" },
},
},
}
@@ -0,0 +1,861 @@
-- Markdown rendering with inline mermaid via md-render.nvim.
--
-- Why not snacks.image / diagram.nvim / image.nvim?
-- Those all try to overlay the rendered image on top of the source fenced
-- code block. snacks issue #1384 ("mermaid blocks text behind it") was
-- closed as "not planned" — overlap-in-source is a fundamental limitation
-- of the current kitty-graphics-protocol design in nvim.
--
-- md-render takes the opposite approach: render mode is a *separate buffer*.
-- Normal mode -> auto-swap to render (full diagrams, no source text in the way).
-- Insert mode -> swap back to source so we can edit. The hop is per-buffer.
--
-- Ghostty itself is verified by the plugin; the SSH transport shim below is
-- local glue so the terminal never has to read remote-only image paths.
local function use_direct_kitty_bytes()
return true
end
local function image_debug(message)
if vim.env.MD_RENDER_IMAGE_DEBUG ~= "1" then return end
local path = vim.fn.stdpath("cache") .. "/md-render-image-debug.log"
local line = os.date("%Y-%m-%d %H:%M:%S") .. " " .. message .. "\n"
vim.fn.writefile({ line }, path, "a")
end
return {
{
"delphinus/md-render.nvim",
version = "*",
ft = { "markdown" },
dependencies = {
{ "nvim-tree/nvim-web-devicons", version = "*" },
},
init = function()
-- Ghostty does not normally forward TERM_PROGRAM /
-- GHOSTTY_RESOURCES_DIR into the remote shell. Neovim 0.12 therefore
-- cannot prove Kitty graphics support from the remote side on its own.
-- Herdr panes expose HERDR_ENV=1, but the underlying display path is
-- still Ghostty-compatible when kitty_graphics is enabled.
--
-- Keep the terminal identity as Ghostty. md-render uses that identity to
-- avoid Ghostty's flaky stored-image re-placement path during redraws.
if use_direct_kitty_bytes() and not vim.env.TERM_PROGRAM then
vim.env.TERM_PROGRAM = "ghostty"
end
-- In auto mode the render view is a separate, nomodifiable buffer, so
-- normal-mode editing (dd/x/ciw/p/…) does nothing — you'd have to toggle
-- to source first. md-render already solves this for insert-entry keys
-- (i/I/a/A/o/O): pressing one hops to the source at the mapped line and
-- replays the key (see AUTO_INSERT_KEYS in the plugin). It intentionally
-- leaves operator/normal-mode keys alone.
--
-- We extend that same hop to editing keys, scoped buffer-locally to the
-- render buffer (ft=md-render) only — the real source buffer is never
-- touched. Press dd/x/p/etc. while viewing the render and it transparently
-- drops to source on the right line and performs the edit. (Pure-motion
-- keys h/j/k/w/G/… are deliberately NOT mapped, so you can still navigate
-- the render freely.) Yank keys stay in the read-only render buffer so
-- copying text does not dismiss rendered diagrams. Visual-entry keys hop
-- to source so the resulting selection can be changed safely.
-- NOTE: the plugin sets the render buffer's filetype under
-- `eventignore=all` (preview.lua, so markdown ftplugins/treesitter don't
-- clobber the pre-rendered content), so a `FileType md-render` autocmd
-- never fires. Hook `BufEnter` instead — it fires when `toggle` swaps the
-- render buffer into the window — and guard on filetype + a buffer flag so
-- the maps install exactly once per render buffer.
local HOP_KEYS = {
"d", "D", "c", "C", "x", "X", "s", "S", "r", "R",
"p", "P", "J", ">", "<", "=", "u", "v", "V", ".",
}
local hop_group = vim.api.nvim_create_augroup("MdRenderEditHop", { clear = true })
vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, {
group = hop_group,
desc = "md-render: editing keys hop to source then replay",
callback = function(ev)
if vim.bo[ev.buf].filetype ~= "md-render" then return end
if vim.b[ev.buf].md_render_hop_installed then return end
vim.b[ev.buf].md_render_hop_installed = true
for _, key in ipairs(HOP_KEYS) do
vim.keymap.set("n", key, function()
-- Preserve a leading count and explicit register (e.g. 3dd, "ayy)
-- so the replayed operator behaves like the keystroke you typed.
local count = vim.v.count
local reg = vim.v.register
local prefix = (reg and reg ~= '"') and ('"' .. reg) or ""
if count > 0 then prefix = prefix .. tostring(count) end
-- toggle() is synchronous: on return the window already shows the
-- source buffer with the cursor on the mapped line. Feed the key
-- *synchronously* (no vim.schedule) so it enters the typeahead
-- before any follow-up keystroke. That keeps multi-key operators
-- in order — `d` becomes operator-pending and your next key (e.g.
-- the second `d`, or `w`, `ip`) completes it natively in source.
-- (The plugin schedules its insert-entry hop because those are
-- single keys that immediately switch to Insert mode; operators
-- need the ordering guarantee a sync feed provides.)
require("md-render").preview.toggle()
if key == "v" or key == "V" then
-- Enter Visual mode before the user's next queued key arrives.
-- feedkeys() would put v/V behind that key, turning `Vd` into
-- `dV`; :normal! establishes the selection synchronously.
local visual_count = count > 0 and tostring(count) or ""
vim.cmd("normal! " .. visual_count .. key)
else
vim.api.nvim_feedkeys(prefix .. key, "n", false)
end
end, {
buffer = ev.buf,
noremap = true,
silent = true,
desc = "md-render: hop to source then " .. key,
})
end
end,
})
end,
keys = {
{
"<leader>mp",
function() require("md-render").preview.show({ max_width = 140 }) end,
ft = "markdown",
desc = "Markdown preview (wide float toggle)",
},
{
"<leader>mt",
function() require("md-render").preview.show_tab({ max_width = 140 }) end,
ft = "markdown",
desc = "Markdown preview (wide tab toggle)",
},
{
"<leader>ms",
function() require("md-render").preview.split({ max_width = 140 }) end,
ft = "markdown",
desc = "Markdown source/render split",
},
{
"<leader>mr",
function() require("md-render").preview.toggle({ max_width = 140 }) end,
ft = "markdown",
desc = "Markdown render toggle in-place",
},
{ "<leader>md", "<Plug>(md-render-demo)", desc = "Markdown render demo" },
},
config = function()
local function patch_mermaid_rendering()
local img = require("md-render.image")
if img._thembones_mermaid_patch then return end
img._thembones_mermaid_patch = true
local MERMAID_SCALE = "4"
local MERMAID_ROWS = 45
local original_calc_display_size = img.calc_display_size
img.calc_display_size = function(img_w, img_h, max_cols, max_rows)
if max_rows == 25 then
max_rows = MERMAID_ROWS
end
return original_calc_display_size(img_w, img_h, max_cols, max_rows)
end
local function mermaid_cache_dir()
local dir = vim.fn.stdpath("cache") .. "/md-render/mermaid"
vim.fn.mkdir(dir, "p")
return dir
end
local function mermaid_theme_args()
local bg = vim.o.background
local hl = vim.api.nvim_get_hl(0, { name = "NormalFloat", link = false })
if not hl.bg then
hl = vim.api.nvim_get_hl(0, { name = "Normal", link = false })
end
local bg_color = hl.bg
if bg == "dark" then
return "dark", bg_color and string.format("#%06x", bg_color) or "#1e1e2e"
end
return "default", bg_color and string.format("#%06x", bg_color) or "#ffffff"
end
local function mermaid_cache_path(source)
local theme, bg_hex = mermaid_theme_args()
local key = table.concat({ source, theme, bg_hex, "scale=" .. MERMAID_SCALE }, "|")
local hash = vim.fn.sha256(key):sub(1, 16)
return mermaid_cache_dir() .. "/" .. hash .. ".png"
end
local function mmdc_cmd(input_path, output_path)
local cmd
if vim.fn.executable("mmdc") == 1 then
cmd = { "mmdc" }
elseif vim.fn.executable("npx") == 1 then
cmd = { "npx", "-y", "@mermaid-js/mermaid-cli" }
else
return nil
end
local theme, bg_hex = mermaid_theme_args()
vim.list_extend(cmd, {
"-i", input_path,
"-o", output_path,
"-t", theme,
"-b", bg_hex,
"-s", MERMAID_SCALE,
})
return cmd
end
img.get_mermaid_cached = function(source)
local path = mermaid_cache_path(source)
if vim.fn.filereadable(path) == 1 then
return path
end
return nil
end
img.render_mermaid = function(source)
local cache_path = mermaid_cache_path(source)
if vim.fn.filereadable(cache_path) == 1 then
return cache_path
end
local tmp_input = vim.fn.tempname() .. ".mmd"
local f = io.open(tmp_input, "w")
if not f then return nil end
f:write(source)
f:close()
local cmd = mmdc_cmd(tmp_input, cache_path)
if not cmd then
os.remove(tmp_input)
return nil
end
vim.system(cmd, { text = true, timeout = 30000 }):wait()
os.remove(tmp_input)
if vim.fn.filereadable(cache_path) == 1 then
return cache_path
end
return nil
end
-- Callbacks per in-flight cache path, so concurrent requests for the
-- same diagram share one mmdc process instead of racing on the output.
local mermaid_inflight = {}
img.render_mermaid_async = function(source, callback)
local cache_path = mermaid_cache_path(source)
if vim.fn.filereadable(cache_path) == 1 then
callback(cache_path)
return
end
if mermaid_inflight[cache_path] then
table.insert(mermaid_inflight[cache_path], callback)
return
end
mermaid_inflight[cache_path] = { callback }
local function finish(result)
local callbacks = mermaid_inflight[cache_path]
mermaid_inflight[cache_path] = nil
for _, cb in ipairs(callbacks) do
cb(result)
end
end
local tmp_input = vim.fn.tempname() .. ".mmd"
local f = io.open(tmp_input, "w")
if not f then
finish(nil)
return
end
f:write(source)
f:close()
local cmd = mmdc_cmd(tmp_input, cache_path)
if not cmd then
os.remove(tmp_input)
finish(nil)
return
end
vim.system(cmd, { text = true, timeout = 30000 }, function()
vim.schedule(function()
os.remove(tmp_input)
if vim.fn.filereadable(cache_path) == 1 then
finish(cache_path)
else
finish(nil)
end
end)
end)
end
end
patch_mermaid_rendering()
-- md-render only processes placements near the viewport on first paint,
-- and its WinScrolled retry loop skips placements without a `path`.
-- Uncached mermaid placements carry only `mermaid_source`, so a diagram
-- that starts off-screen never renders at all, no matter how far you
-- scroll. Eagerly kick off rendering for every mermaid placement when
-- images are set up: the plugin's own rebuild-on-complete then swaps in
-- a normal `path` placement, which its retry loop handles.
local function patch_offscreen_mermaid()
local du = require("md-render.display_utils")
if du._thembones_offscreen_mermaid_patch then return end
du._thembones_offscreen_mermaid_patch = true
local original_setup_images = du.setup_images
du.setup_images = function(win, content, ns, opts)
local state = original_setup_images(win, content, ns, opts)
if state and state.placements and state.process_placement then
vim.schedule(function()
for _, placement in ipairs(state.placements) do
if placement.mermaid_source then
local ok, err = pcall(state.process_placement, placement)
if not ok then
image_debug("offscreen mermaid eager render err=" .. tostring(err))
end
end
end
end)
end
return state
end
end
patch_offscreen_mermaid()
-- md-render's Ghostty path intentionally re-transmits images on every
-- placement so redraws stay visible. Its stock transport uses `t=f`,
-- which means "the terminal reads this file path". Over SSH that path is
-- remote-only, so Ghostty cannot load it. In Herdr/Ghostty, file-path
-- transfer can reserve the image area without painting the pixels, so
-- avoid that path there too. Keep Ghostty's placement model, but switch
-- transport to direct PNG bytes (`t=d`).
if use_direct_kitty_bytes() then
image_debug("direct-byte override enabled")
local img = require("md-render.image")
local tty = require("md-render.tty")
local original_begin_batch = img.begin_batch
local original_flush_batch = img.flush_batch
local original_clear_all = img.clear_all
local original_delete_image = img.delete_image
local original_delete_images = img.delete_images
local original_put_image = img.put_image
local original_get_tty_path = tty.get_tty_path
local CHUNK = 4096
local next_image_id = 800000
local direct_images = {}
local batch_depth = 0
local after_flush = {}
tty.get_tty_path = function()
if vim.env.SSH_TTY and vim.fn.filereadable(vim.env.SSH_TTY) == 1 then
return vim.env.SSH_TTY
end
return original_get_tty_path()
end
local function queue_terminal(data)
image_debug("queue_terminal bytes=" .. tostring(#data) .. " batch_depth=" .. tostring(batch_depth))
if batch_depth > 0 then
table.insert(after_flush, data)
else
vim.api.nvim_ui_send(data)
end
end
local function flush_direct_queue()
if batch_depth == 0 and #after_flush > 0 then
image_debug("flush_direct_queue chunks=" .. tostring(#after_flush))
for _, data in ipairs(after_flush) do
vim.api.nvim_ui_send(data)
end
after_flush = {}
end
end
img.begin_batch = function(...)
batch_depth = batch_depth + 1
return original_begin_batch(...)
end
img.flush_batch = function(...)
local result = original_flush_batch(...)
batch_depth = math.max(0, batch_depth - 1)
flush_direct_queue()
return result
end
local function next_direct_id()
next_image_id = next_image_id + 1
return next_image_id
end
local function read_png_base64(path)
local f = io.open(path, "rb")
if not f then
return nil
end
local data = f:read("*a")
f:close()
return vim.base64.encode(data)
end
local function send_chunked(header, payload)
image_debug("send_chunked header=" .. header .. " payload_len=" .. tostring(#payload))
local i = 1
local first = true
while i <= #payload do
local piece = payload:sub(i, i + CHUNK - 1)
i = i + CHUNK
local more = i <= #payload and 1 or 0
local hdr
if first then
hdr = string.format("%s,m=%d", header, more)
first = false
else
hdr = string.format("m=%d,q=2", more)
end
queue_terminal("\27_G" .. hdr .. ";" .. piece .. "\27\\")
end
end
local function crop_params_for(win, row, col, display_cols, display_rows, img_w, img_h)
if not vim.api.nvim_win_is_valid(win) then return nil end
local win_pos = vim.api.nvim_win_get_position(win)
local wininfo = vim.fn.getwininfo(win)[1]
if not wininfo then return nil end
local win_height = wininfo.height
local topline = wininfo.topline - 1
local leftcol = wininfo.leftcol or 0
local textoff = wininfo.textoff or 0
local img_end_row = row + display_rows - 1
if img_end_row < topline or row >= topline + win_height then return nil end
local visual_row = row - topline
local border_left_width = 0
local border_top_height = 0
local ok_cfg, win_cfg = pcall(vim.api.nvim_win_get_config, win)
if ok_cfg and win_cfg.border then
local border = win_cfg.border
if type(border) == "table" then
local left = border[8]
if type(left) == "table" then left = left[1] end
if left and left ~= "" then
border_left_width = vim.api.nvim_strwidth(left)
end
local top = border[2]
if type(top) == "table" then top = top[1] end
if top and top ~= "" then
border_top_height = 1
end
elseif border ~= "none" and border ~= "" then
border_left_width = vim.api.nvim_strwidth("|")
border_top_height = 1
end
end
local visual_col = col - leftcol
local visible_text_cols = vim.api.nvim_win_get_width(win) - textoff
local img_end_col = col + display_cols - 1
if img_end_col < leftcol or col >= leftcol + visible_text_cols then return nil end
local screen_col = win_pos[2] + visual_col + border_left_width + textoff + 1
local src_x, src_y, src_w, src_h
if visual_col < 0 then
local hidden_cols = -visual_col
if img_w then
src_x = math.floor(img_w * hidden_cols / display_cols)
src_w = img_w - src_x
end
display_cols = display_cols - hidden_cols
visual_col = 0
screen_col = win_pos[2] + border_left_width + textoff + 1
end
if visual_row < 0 then
local hidden_rows = -visual_row
if img_h then
src_y = math.floor(img_h * hidden_rows / display_rows)
src_h = img_h - src_y
end
display_rows = display_rows - hidden_rows
visual_row = 0
end
local winbar_height = 0
local ok_wb, wb = pcall(function() return vim.wo[win].winbar end)
if ok_wb and wb and wb ~= "" then
winbar_height = 1
end
local screen_row = wininfo.winrow + visual_row + border_top_height + winbar_height
local visible_rows = win_height - visual_row
if visible_rows <= 0 then return nil end
if display_rows > visible_rows and img_h then
local remaining_h = src_h or img_h
src_h = math.floor(remaining_h * visible_rows / display_rows)
display_rows = visible_rows
end
local visible_cols = visible_text_cols - visual_col
if visible_cols <= 0 then return nil end
if display_cols > visible_cols and img_w then
local remaining_w = src_w or img_w
src_w = math.floor(remaining_w * visible_cols / display_cols)
display_cols = visible_cols
end
local crop_params = ""
if (src_x or src_y or src_w or src_h) and img_w and img_h then
crop_params = string.format(
",x=%d,y=%d,w=%d,h=%d",
src_x or 0,
src_y or 0,
src_w or img_w,
src_h or img_h
)
end
return {
screen_col = screen_col,
screen_row = screen_row,
display_cols = display_cols,
display_rows = display_rows,
crop_params = crop_params,
}
end
local function transmit_direct(path, callback)
image_debug("transmit_direct path=" .. tostring(path))
local payload = read_png_base64(path)
if not payload then
image_debug("transmit_direct no payload")
callback(nil)
return
end
local id = next_direct_id()
local img_w, img_h = img.image_dimensions(path)
image_debug("transmit_direct id=" .. tostring(id) .. " dims=" .. tostring(img_w) .. "x" .. tostring(img_h))
direct_images[id] = {
path = path,
payload = payload,
width = img_w,
height = img_h,
}
send_chunked(
string.format("a=t,f=100,t=d,i=%d,q=2", id),
payload
)
callback(id, img_w, img_h)
end
-- Herdr re-encodes every visible placement as base64 RGBA of the FULL
-- source image (bytes ≈ w*h*4*4/3, ~16x the PNG size) when forwarding
-- frames to its client, and drops ALL graphics for any frame whose
-- total exceeds 32MiB ("dropping oversized graphics payload" in
-- herdr-server.log). A scale-4 mermaid PNG alone is ~60MB on that
-- wire, so nothing paints. Cap source pixels inside herdr panes; the
-- render view is ≤45 rows so the downscale is visually lossless.
local HERDR_MAX_PIXELS = 1500000
local function herdr_scaled_path(png_path, max_dim)
local mtime = vim.fn.getftime(png_path)
local key = png_path .. "|" .. tostring(mtime) .. "|" .. tostring(max_dim)
local dir = vim.fn.stdpath("cache") .. "/md-render/herdr-scaled"
vim.fn.mkdir(dir, "p")
return dir .. "/" .. vim.fn.sha256(key):sub(1, 16) .. ".png"
end
-- Downscale tool detection, same priority as md-render's own
-- find_convert_tool(): sips (macOS) → ffmpeg → magick (IM7) →
-- convert (IM6, Linux distro ImageMagick installs).
local _downscale_tool = nil
local _downscale_checked = false
local function find_downscale_tool()
if _downscale_checked then return _downscale_tool end
_downscale_checked = true
if vim.fn.has("mac") == 1 and vim.fn.executable("sips") == 1 then
_downscale_tool = "sips"
elseif vim.fn.executable("ffmpeg") == 1 then
_downscale_tool = "ffmpeg"
elseif vim.fn.executable("magick") == 1 then
_downscale_tool = "magick"
elseif vim.fn.executable("convert") == 1 then
_downscale_tool = "convert"
end
return _downscale_tool
end
local function build_downscale_cmd(tool, src, dst, max_dim)
local dim = tostring(max_dim)
if tool == "sips" then
return { "sips", "-Z", dim, src, "--out", dst }
elseif tool == "ffmpeg" then
return {
"ffmpeg", "-y", "-i", src,
"-vframes", "1",
"-vf", "scale='min(" .. dim .. ",iw)':'min(" .. dim .. ",ih)':force_original_aspect_ratio=decrease",
dst,
}
else -- magick / convert share the same argument shape
return { tool, src, "-resize", dim .. "x" .. dim .. ">", dst }
end
end
local function downscale_for_herdr(png_path, callback)
if vim.env.HERDR_ENV ~= "1" then
callback(png_path)
return
end
local w, h = img.image_dimensions(png_path)
if not w or not h or w * h <= HERDR_MAX_PIXELS then
callback(png_path)
return
end
local scale = math.sqrt(HERDR_MAX_PIXELS / (w * h))
local max_dim = math.floor(math.max(w, h) * scale)
local out = herdr_scaled_path(png_path, max_dim)
if vim.fn.filereadable(out) == 1 then
image_debug("downscale_for_herdr cached " .. out)
callback(out)
return
end
-- If no tool is available or it fails, fall back to the original
-- (which herdr will drop, same as before this patch).
local tool = find_downscale_tool()
if not tool then
image_debug("downscale_for_herdr no downscale tool")
callback(png_path)
return
end
vim.system(
build_downscale_cmd(tool, png_path, out, max_dim),
{ text = true, timeout = 15000 },
function()
vim.schedule(function()
if vim.fn.filereadable(out) == 1 then
image_debug("downscale_for_herdr " .. tool .. " " .. w .. "x" .. h .. " -> max_dim=" .. max_dim)
callback(out)
else
image_debug("downscale_for_herdr " .. tool .. " failed")
callback(png_path)
end
end)
end
)
end
img.transmit_image_async = function(path, callback)
image_debug("transmit_image_async path=" .. tostring(path))
if not img.supports_kitty() then
image_debug("transmit_image_async no kitty support")
callback(nil)
return
end
img.ensure_png_async(path, function(png_path)
if not png_path then
image_debug("transmit_image_async no png_path")
callback(nil)
return
end
image_debug("transmit_image_async png_path=" .. tostring(png_path))
downscale_for_herdr(png_path, function(final_path)
transmit_direct(final_path, callback)
end)
end)
end
img.put_image = function(image_id, win, row, col, display_cols, display_rows, anim_path, img_w, img_h)
image_debug(
"put_image id=" .. tostring(image_id)
.. " row=" .. tostring(row)
.. " col=" .. tostring(col)
.. " cols=" .. tostring(display_cols)
.. " rows=" .. tostring(display_rows)
.. " anim=" .. tostring(anim_path)
)
local direct = direct_images[image_id]
if not direct or anim_path then
image_debug("put_image fallback direct=" .. tostring(direct ~= nil) .. " anim=" .. tostring(anim_path))
return original_put_image(image_id, win, row, col, display_cols, display_rows, anim_path, img_w, img_h)
end
if not img.supports_kitty() then return end
local placement = crop_params_for(
win,
row,
col,
display_cols,
display_rows,
img_w or direct.width,
img_h or direct.height
)
if not placement then
image_debug("put_image no placement")
return
end
image_debug(
"put_image placement screen="
.. tostring(placement.screen_row)
.. ","
.. tostring(placement.screen_col)
.. " cells="
.. tostring(placement.display_cols)
.. "x"
.. tostring(placement.display_rows)
.. " crop="
.. tostring(placement.crop_params)
)
queue_terminal("\27[s")
queue_terminal(string.format("\27[%d;%dH", placement.screen_row, placement.screen_col))
send_chunked(
string.format(
"a=T,f=100,t=d,i=%d,c=%d,r=%d%s,C=1,q=2",
image_id,
placement.display_cols,
placement.display_rows,
placement.crop_params
),
direct.payload
)
queue_terminal("\27[u")
end
img.clear_all = function(...)
direct_images = {}
return original_clear_all(...)
end
img.delete_image = function(image_id, ...)
direct_images[image_id] = nil
return original_delete_image(image_id, ...)
end
img.delete_images = function(image_ids, ...)
for _, image_id in ipairs(image_ids or {}) do
direct_images[image_id] = nil
end
return original_delete_images(image_ids, ...)
end
end
local function mermaid_sources(buf)
local sources = {}
local in_mermaid = false
local block = {}
for _, line in ipairs(vim.api.nvim_buf_get_lines(buf, 0, -1, false)) do
if not in_mermaid then
if line:match("^%s*```%s*mermaid%s*$") then
in_mermaid = true
block = {}
end
elseif line:match("^%s*```%s*$") then
if #block > 0 then
table.insert(sources, table.concat(block, "\n"))
end
in_mermaid = false
block = {}
else
table.insert(block, line)
end
end
return sources
end
local function prime_mermaid_cache(buf)
if not vim.api.nvim_buf_is_valid(buf) then return end
local ok, image = pcall(require, "md-render.image")
if not ok or not image.has_mmdc() then return end
for _, source in ipairs(mermaid_sources(buf)) do
if not image.get_mermaid_cached(source) then
image.render_mermaid(source)
end
end
end
local function ensure_md_render_auto(buf)
if not vim.api.nvim_buf_is_valid(buf) then return end
if vim.bo[buf].filetype ~= "markdown" then return end
if not vim.b[buf].md_render_auto then
local cache_group = vim.api.nvim_create_augroup("MdRenderMermaidCache" .. buf, { clear = true })
vim.api.nvim_create_autocmd({ "InsertLeave", "BufWritePost" }, {
group = cache_group,
buffer = buf,
callback = function()
prime_mermaid_cache(buf)
end,
desc = "Prime md-render Mermaid cache before redraw",
})
end
prime_mermaid_cache(buf)
if vim.b[buf].md_render_auto then return end
local ok, md = pcall(require, "md-render")
if ok then
local win = vim.api.nvim_get_current_win()
local ok_state, state = pcall(vim.api.nvim_win_get_var, win, "md_render_state")
if ok_state and type(state) == "table" and state.source_buf ~= buf then
pcall(vim.api.nvim_win_del_var, win, "md_render_state")
end
pcall(md.preview.auto_on, { max_width = 140 })
end
end
local group = vim.api.nvim_create_augroup("MdRenderMarkdownAuto", { clear = true })
vim.api.nvim_create_autocmd("FileType", {
group = group,
pattern = { "markdown", "*.md", "*.markdown" },
callback = function(ev)
-- Defer so the FileType pass that loads md-render completes first.
vim.schedule(function()
if vim.api.nvim_get_current_buf() == ev.buf then
ensure_md_render_auto(ev.buf)
end
end)
end,
desc = "Enable md-render auto mode for markdown buffers",
})
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
vim.schedule(function()
ensure_md_render_auto(buf)
end)
end
end,
},
-- Disable the previous attempts so they don't fight md-render for the same
-- terminal cells (the "1 frame then gone" / "behind the text" symptoms).
{ "3rd/image.nvim", enabled = false },
{ "3rd/diagram.nvim", enabled = false },
{
"folke/snacks.nvim",
opts = function(_, opts)
opts.image = vim.tbl_deep_extend("force", opts.image or {}, {
enabled = false,
})
return opts
end,
},
}
@@ -0,0 +1,17 @@
-- Override LazyVim's indent-blankline for VS Code-like indent guides
return {
{
"lukas-reineke/indent-blankline.nvim",
main = "ibl", -- v3 module name
opts = {
indent = {
char = "│",
},
scope = {
enabled = true,
show_start = false,
show_end = false,
},
},
},
}
@@ -0,0 +1,12 @@
return {
{
"mfussenegger/nvim-lint",
optional = true,
opts = {
linters_by_ft = {
markdown = {}, -- no linters for .md
["markdown.mdx"] = {}, -- no linters for .mdx
},
},
},
}
@@ -0,0 +1,27 @@
return {
-- Disable markdown diagnostics/linting but keep LSP for formatting
{
"neovim/nvim-lspconfig",
opts = {
servers = {
marksman = {
-- Keep marksman for markdown LSP features like formatting
handlers = {
-- Disable diagnostics from marksman
["textDocument/publishDiagnostics"] = function() end,
},
},
},
},
},
-- Also disable any lint-related diagnostics for markdown files
{
"mfussenegger/nvim-lint",
optional = true,
opts = {
linters_by_ft = {
markdown = {},
},
},
},
}
@@ -0,0 +1,52 @@
-- VS Code-style minimap using neominimap.nvim
return {
{
"Isrothy/neominimap.nvim",
version = "v3.*.*",
lazy = false,
keys = {
{ "<leader>um", "<cmd>Neominimap Toggle<cr>", desc = "Toggle minimap" },
{ "<leader>uf", "<cmd>Neominimap Focus<cr>", desc = "Focus minimap" },
},
init = function()
vim.g.neominimap = {
auto_enable = true,
-- Higher resolution (lower = denser)
x_multiplier = 2,
y_multiplier = 1,
layout = "float",
float = {
minimap_width = 8,
window_border = "none",
},
-- Enable mouse clicks
click = {
enabled = true,
auto_switch_focus = true,
},
-- Annotations
diagnostic = {
enabled = true,
mode = "line", -- Highlight full line
},
git = {
enabled = true,
mode = "line", -- Full line highlight for git changes
},
search = {
enabled = true,
mode = "line",
},
treesitter = {
enabled = true,
},
exclude_filetypes = { "help", "neo-tree", "lazy", "mason", "dashboard" },
}
end,
},
}
@@ -0,0 +1,115 @@
-- Inline LLM (ghost-text) completion tuned for writing markdown/prose.
-- Reuses ZAI_API_KEY from the environment (same key the :AI command uses).
-- Suggestions are virtual text, auto-triggered behind a debounce delay.
local PROSE_FT = {
markdown = true,
["md-render"] = true,
text = true,
rst = true,
tex = true,
gitcommit = true,
mail = true,
}
return {
"milanglacier/minuet-ai.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
-- Load eagerly: minuet only sets its buffer-local auto-trigger flag from a
-- FileType autocmd registered in setup(). Lazy-loading on InsertEnter would
-- register that autocmd *after* the current buffer's FileType already fired,
-- so virtualtext would never auto-trigger on the buffer you're editing.
lazy = false,
opts = {
provider = "openai_compatible",
-- One natural continuation, not three alternatives — prose has a single
-- "next sentence", and fewer candidates means less to read past.
n_completions = 1,
-- Give the model more of the text *before* the cursor (default 0.75).
context_ratio = 0.85,
-- Cap how much surrounding text is sent. Smaller = lower, steadier latency
-- (the full default of 16000 chars is overkill for prose continuation and
-- inflates time-to-first-token). 4000 chars ≈ plenty of preceding context.
context_window = 4000,
-- Delay knobs: wait for an idle pause, and rate-limit requests. Groq is fast
-- and deterministic, so we can afford a snappier debounce than the cloud
-- defaults; throttle stays moderate to respect the free-tier rate limit.
throttle = 800, -- min ms between requests
debounce = 400, -- ms of idle typing before a request fires
provider_options = {
openai_compatible = {
api_key = "GROQ_API_KEY", -- env var NAME; minuet reads it at runtime
name = "Groq",
-- Groq's LPU inference gives ~80-100ms time-to-first-token with very low
-- variance — that consistency is the whole reason for moving off Z.AI's
-- hit-or-miss coding endpoint. OpenAI-compatible, so the schema is unchanged.
end_point = "https://api.groq.com/openai/v1/chat/completions",
-- llama-3.3-70b is Groq's flagship general model: strong prose, still fast
-- on their hardware. If you ever want the absolute lowest latency, swap to
-- "llama-3.1-8b-instant" (faster, slightly weaker prose).
model = "llama-3.3-70b-versatile",
-- Swap minuet's "code completion engine" system prompt for a prose one
-- in writing filetypes; keep the default (string) for code. Guidelines
-- and few-shots are left at defaults so minuet's <endCompletion> parsing
-- still works.
system = {
prompt = function()
if PROSE_FT[vim.bo.filetype] then
return [[
You are a prose writing assistant embedded in a text editor. Continue the
author's text at the <cursorPosition> marker naturally and fluently, matching
their voice, tone, and sentence rhythm.
- Output ONLY the continuation text — no preamble, no explanation.
- Never wrap the output in markdown code fences or backticks.
- Do not add headings, bullets, or numbering unless the surrounding text
already uses them.
- Keep it to at most 1-3 sentences and stop at a natural boundary.]]
end
return require("minuet.config").default_system_prefix_first.prompt
end,
},
optional = {
-- Short, focused completions. (No `thinking` field here — that was a
-- Z.AI-specific param; Groq's Llama models 400 on unknown body fields.)
max_tokens = 96,
temperature = 0.3,
},
},
},
virtualtext = {
auto_trigger_ft = { "*" },
-- Alt keymaps are kept as a secondary path, but the primary accept key is
-- <Tab>, wired in config() below (Alt+* doesn't fire in Ghostty without
-- macos-option-as-alt, and Tab is the natural "accept" key for prose).
keymap = {
accept = "<A-y>", -- accept full suggestion
accept_line = "<A-l>", -- accept one line
prev = "<A-[>",
next = "<A-]>",
dismiss = "<A-e>",
},
},
},
config = function(_, opts)
require("minuet").setup(opts)
-- Smart <Tab>: accept a visible minuet ghost-text suggestion; otherwise fall
-- back to the exact super-tab behavior we had before (jump an active snippet,
-- else insert a literal Tab). Blink's menu navigates with <C-n>/<C-p>, not
-- Tab, so this doesn't fight the completion popup.
local vt = require("minuet.virtualtext").action
vim.keymap.set("i", "<Tab>", function()
if vt.is_visible() then
vt.accept()
elseif vim.snippet.active({ direction = 1 }) then
vim.snippet.jump(1)
else
vim.api.nvim_feedkeys(
vim.api.nvim_replace_termcodes("<Tab>", true, false, true),
"n",
false
)
end
end, { desc = "minuet: accept suggestion, else snippet jump / Tab" })
end,
}
@@ -0,0 +1,3 @@
-- Navigation enhancement plugins
-- (neoscroll removed due to conflicts with smear-cursor)
return {}
@@ -0,0 +1,14 @@
return {
"nvim-neo-tree/neo-tree.nvim",
opts = function(_, opts)
opts.filesystem = opts.filesystem or {}
opts.filesystem.filtered_items = opts.filesystem.filtered_items or {}
-- make sure .log files are visible
opts.filesystem.filtered_items.hide_gitignored = false
opts.filesystem.filtered_items.hide_dotfiles = false
opts.filesystem.filtered_items.always_show = opts.filesystem.filtered_items.always_show or {}
table.insert(opts.filesystem.filtered_items.always_show, "*.log")
end,
}
@@ -0,0 +1,4 @@
-- Disable noice.nvim (causing treesitter errors)
return {
{ "folke/noice.nvim", enabled = false },
}
@@ -0,0 +1,12 @@
return {
"nvim-tree/nvim-tree.lua",
dependencies = {
"nvim-tree/nvim-web-devicons",
},
keys = {
{ "<leader>e", "<cmd>NvimTreeToggle<cr>", desc = "Toggle file explorer" },
},
config = function()
require("nvim-tree").setup({})
end,
}
@@ -0,0 +1,35 @@
return {
"nvim-telescope/telescope.nvim",
opts = {
defaults = {
-- Don't respect .gitignore - show logs and temp files
file_ignore_patterns = {
"node_modules/",
"__pycache__/",
".git/",
".pytest_cache/",
"%.pyc$",
"%.pyo$",
},
-- Make sure to not use git_files by default
vimgrep_arguments = {
"rg",
"--color=never",
"--no-heading",
"--with-filename",
"--line-number",
"--column",
"--smart-case",
-- Don't respect gitignore
"--no-ignore",
},
},
pickers = {
find_files = {
-- Show hidden files and don't respect gitignore
hidden = true,
no_ignore = true,
},
},
},
}
@@ -0,0 +1,14 @@
return {
{
"ThePrimeagen/refactoring.nvim",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter",
"lewis6991/async.nvim",
},
ft = { "lua", "python", "javascript", "typescript", "go", "c", "cpp", "java", "php", "ruby" },
config = function()
require("refactoring").setup()
end,
},
}
@@ -0,0 +1,58 @@
-- Enhanced search and replace functionality
return {
-- Better search highlighting with match counts
-- Shows "X/Y" match indicator and improves n/N navigation
{
"kevinhwang91/nvim-hlslens",
event = "VeryLazy",
enabled = true, -- Set to false to disable
keys = {
{ "n", [[<Cmd>execute('normal! ' . v:count1 . 'n')<CR><Cmd>lua require('hlslens').start()<CR>]], desc = "Next search result" },
{ "N", [[<Cmd>execute('normal! ' . v:count1 . 'N')<CR><Cmd>lua require('hlslens').start()<CR>]], desc = "Previous search result" },
{ "*", [[*<Cmd>lua require('hlslens').start()<CR>]], desc = "Search word under cursor" },
{ "#", [[#<Cmd>lua require('hlslens').start()<CR>]], desc = "Search word under cursor (backward)" },
{ "g*", [[g*<Cmd>lua require('hlslens').start()<CR>]], desc = "Search word under cursor (no boundary)" },
{ "g#", [[g#<Cmd>lua require('hlslens').start()<CR>]], desc = "Search word under cursor (no boundary, backward)" },
},
opts = {
calm_down = true,
nearest_only = true,
nearest_float_when = "always",
},
},
-- Advanced search and replace with multiple strategies
-- Provides visual search/replace interface
-- Use :SearchReplaceSingleBuffer, :SearchReplaceMultiBuffer
{
"roobert/search-replace.nvim",
cmd = {
"SearchReplaceSingleBuffer",
"SearchReplaceMultiBuffer",
"SearchReplaceWithinVisualSelection",
"SearchReplaceWithinVisualSelectionCWord",
},
enabled = true, -- Set to false to disable
opts = {
default_replace_single_buffer_options = "gcI",
default_replace_multi_buffer_options = "egcI",
},
keys = {
-- Single buffer replacements
{ "<leader>rs", "<CMD>SearchReplaceSingleBufferSelections<CR>", desc = "Search/Replace Selections", mode = "v" },
{ "<leader>ro", "<CMD>SearchReplaceSingleBufferOpen<CR>", desc = "Search/Replace Open" },
{ "<leader>rw", "<CMD>SearchReplaceSingleBufferCWord<CR>", desc = "Search/Replace Word" },
{ "<leader>rW", "<CMD>SearchReplaceSingleBufferCWORD<CR>", desc = "Search/Replace WORD" },
{ "<leader>re", "<CMD>SearchReplaceSingleBufferCExpr<CR>", desc = "Search/Replace Expr" },
{ "<leader>rf", "<CMD>SearchReplaceSingleBufferCFile<CR>", desc = "Search/Replace File" },
-- Multi buffer replacements
{ "<leader>rbs", "<CMD>SearchReplaceMultiBufferSelections<CR>", desc = "Search/Replace Selections (Multi)", mode = "v" },
{ "<leader>rbo", "<CMD>SearchReplaceMultiBufferOpen<CR>", desc = "Search/Replace Open (Multi)" },
{ "<leader>rbw", "<CMD>SearchReplaceMultiBufferCWord<CR>", desc = "Search/Replace Word (Multi)" },
{ "<leader>rbW", "<CMD>SearchReplaceMultiBufferCWORD<CR>", desc = "Search/Replace WORD (Multi)" },
{ "<leader>rbe", "<CMD>SearchReplaceMultiBufferCExpr<CR>", desc = "Search/Replace Expr (Multi)" },
{ "<leader>rbf", "<CMD>SearchReplaceMultiBufferCFile<CR>", desc = "Search/Replace File (Multi)" },
},
},
}
@@ -0,0 +1,9 @@
return {
"sphamba/smear-cursor.nvim",
opts = {
smear_between_buffers = true,
smear_between_neighbor_lines = true,
scroll_buffer_space = true,
smear_insert_mode = true,
},
}
@@ -0,0 +1,273 @@
-- Colorscheme/theme plugins
return {
-- Catppuccin
{
"catppuccin/nvim",
name = "catppuccin",
lazy = false,
priority = 1000,
},
-- Tokyo Night
{
"folke/tokyonight.nvim",
lazy = false,
priority = 1000,
},
-- Kanagawa
{
"rebelot/kanagawa.nvim",
lazy = false,
priority = 1000,
},
-- Rose Pine
{
"rose-pine/neovim",
name = "rose-pine",
lazy = false,
priority = 1000,
},
-- Nightfox family (Nightfox, Nordfox, Dawnfox, Duskfox, Carbonfox, Terafox)
{
"EdenEast/nightfox.nvim",
lazy = false,
priority = 1000,
},
-- Gruvbox
{
"ellisonleao/gruvbox.nvim",
lazy = false,
priority = 1000,
},
-- Gruvbox Material
{
"sainnhe/gruvbox-material",
lazy = false,
priority = 1000,
},
-- Nord
{
"shaunsingh/nord.nvim",
lazy = false,
priority = 1000,
},
-- Nordic
{
"AlexvZyl/nordic.nvim",
lazy = false,
priority = 1000,
},
-- Everforest
{
"neanias/everforest-nvim",
lazy = false,
priority = 1000,
},
-- Dracula
{
"Mofiqul/dracula.nvim",
lazy = false,
priority = 1000,
},
-- OneDark
{
"navarasu/onedark.nvim",
lazy = false,
priority = 1000,
},
-- One Dark Pro
{
"olimorris/onedarkpro.nvim",
lazy = false,
priority = 1000,
},
-- Solarized
{
"maxmx03/solarized.nvim",
lazy = false,
priority = 1000,
},
-- Monokai Pro
{
"loctvl842/monokai-pro.nvim",
lazy = false,
priority = 1000,
},
-- Cyberdream
{
"scottmckendry/cyberdream.nvim",
lazy = false,
priority = 1000,
},
-- Oxocarbon
{
"nyoom-engineering/oxocarbon.nvim",
lazy = false,
priority = 1000,
},
-- Melange
{
"savq/melange-nvim",
lazy = false,
priority = 1000,
},
-- Nightfly
{
"bluz71/vim-nightfly-colors",
name = "nightfly",
lazy = false,
priority = 1000,
},
-- Moonfly
{
"bluz71/vim-moonfly-colors",
name = "moonfly",
lazy = false,
priority = 1000,
},
-- Sonokai
{
"sainnhe/sonokai",
lazy = false,
priority = 1000,
},
-- Edge
{
"sainnhe/edge",
lazy = false,
priority = 1000,
},
-- Ayu
{
"Shatur/neovim-ayu",
lazy = false,
priority = 1000,
},
-- Material
{
"marko-cerovac/material.nvim",
lazy = false,
priority = 1000,
},
-- Palenight
{
"drewtempelmeyer/palenight.vim",
lazy = false,
priority = 1000,
},
-- GitHub theme
{
"projekt0n/github-nvim-theme",
lazy = false,
priority = 1000,
},
-- Tokyodark
{
"tiagovla/tokyodark.nvim",
lazy = false,
priority = 1000,
},
-- Darkplus (VS Code dark theme)
{
"lunarvim/darkplus.nvim",
lazy = false,
priority = 1000,
},
-- Poimandres
{
"olivercederborg/poimandres.nvim",
lazy = false,
priority = 1000,
},
-- Flow
{
"0xstepit/flow.nvim",
lazy = false,
priority = 1000,
},
-- Modus themes
{
"miikanissi/modus-themes.nvim",
lazy = false,
priority = 1000,
},
-- Zenbones
{
"mcchrish/zenbones.nvim",
dependencies = "rktjmp/lush.nvim",
lazy = false,
priority = 1000,
},
-- Apprentice
{
"romainl/Apprentice",
lazy = false,
priority = 1000,
},
-- Jellybeans
{
"nanotech/jellybeans.vim",
lazy = false,
priority = 1000,
},
-- Tender
{
"jacoborus/tender.vim",
lazy = false,
priority = 1000,
},
-- Horizon
{
"ntk148v/vim-horizon",
lazy = false,
priority = 1000,
},
-- Moonlight
{
"shaunsingh/moonlight.nvim",
lazy = false,
priority = 1000,
},
-- Lackluster
{
"slugbyte/lackluster.nvim",
lazy = false,
priority = 1000,
},
}
@@ -0,0 +1,162 @@
-- UI enhancement plugins for better visual experience
return {
-- Render markdown with better formatting in buffers
{
"MeanderingProgrammer/render-markdown.nvim",
ft = "markdown",
enabled = true,
dependencies = { "nvim-treesitter/nvim-treesitter", "nvim-tree/nvim-web-devicons" },
opts = {
render_modes = { "n", "i", "c", "t" },
heading = {
enabled = true,
sign = true,
icons = { "󰲡 ", "󰲣 ", "󰲥 ", "󰲧 ", "󰲩 ", "󰲫 " },
},
code = {
enabled = true,
sign = true,
style = "normal",
width = "block",
},
bullet = {
enabled = true,
icons = { "●", "○", "◆", "◇" },
},
pipe_table = {
cell = "trimmed",
},
},
},
-- Highlight function arguments with different colors
-- Makes it easier to distinguish parameters
{
"m-demare/hlargs.nvim",
event = "VeryLazy",
enabled = true, -- Set to false to disable
dependencies = { "nvim-treesitter/nvim-treesitter" },
opts = {
color = "#ef9062",
highlight = {},
excluded_filetypes = {},
paint_arg_declarations = true,
paint_arg_usages = true,
performance = {
parse_delay = 1,
slow_parse_delay = 50,
max_iterations = 400,
max_concurrent_partial_parses = 30,
},
},
},
-- Dim inactive portions of code
-- Great for focusing on specific functions/blocks
-- Toggle with :Twilight
{
"folke/twilight.nvim",
cmd = { "Twilight", "TwilightEnable", "TwilightDisable" },
enabled = true, -- Set to false to disable
opts = {
dimming = {
alpha = 0.25,
color = { "Normal", "#ffffff" },
term_bg = "#000000",
inactive = false,
},
context = 10,
treesitter = true,
expand = {
"function",
"method",
"table",
"if_statement",
},
},
keys = {
{ "<leader>ut", "<cmd>Twilight<cr>", desc = "Toggle Twilight" },
},
},
-- Theme switcher with live preview
-- Browse and switch between colorschemes with instant preview
-- Open with :Themery
{
"zaldih/themery.nvim",
lazy = false, -- Load on startup to apply saved theme
priority = 1000, -- Load before other plugins
enabled = true,
keys = {
{ "<leader>uT", "<cmd>Themery<cr>", desc = "Theme Picker" },
},
config = function()
require("themery").setup({
themes = {
-- Catppuccin variants
{ name = "Catppuccin Latte", colorscheme = "catppuccin-latte" },
{ name = "Catppuccin Frappe", colorscheme = "catppuccin-frappe" },
{ name = "Catppuccin Macchiato", colorscheme = "catppuccin-macchiato" },
{ name = "Catppuccin Mocha", colorscheme = "catppuccin-mocha" },
-- Tokyo Night variants
{ name = "Tokyo Night", colorscheme = "tokyonight" },
{ name = "Tokyo Night - Night", colorscheme = "tokyonight-night" },
{ name = "Tokyo Night - Storm", colorscheme = "tokyonight-storm" },
{ name = "Tokyo Night - Day", colorscheme = "tokyonight-day" },
{ name = "Tokyo Night - Moon", colorscheme = "tokyonight-moon" },
-- Kanagawa variants
{ name = "Kanagawa", colorscheme = "kanagawa" },
{ name = "Kanagawa Wave", colorscheme = "kanagawa-wave" },
{ name = "Kanagawa Dragon", colorscheme = "kanagawa-dragon" },
{ name = "Kanagawa Lotus", colorscheme = "kanagawa-lotus" },
-- Rose Pine variants
{ name = "Rose Pine", colorscheme = "rose-pine" },
{ name = "Rose Pine Moon", colorscheme = "rose-pine-moon" },
{ name = "Rose Pine Dawn", colorscheme = "rose-pine-dawn" },
-- Nightfox variants
{ name = "Nightfox", colorscheme = "nightfox" },
{ name = "Nordfox", colorscheme = "nordfox" },
{ name = "Dawnfox", colorscheme = "dawnfox" },
{ name = "Duskfox", colorscheme = "duskfox" },
{ name = "Carbonfox", colorscheme = "carbonfox" },
{ name = "Terafox", colorscheme = "terafox" },
-- Gruvbox variants
{ name = "Gruvbox Dark", colorscheme = "gruvbox" },
{ name = "Gruvbox Material", colorscheme = "gruvbox-material" },
-- Popular themes that actually work
{ name = "Nord", colorscheme = "nord" },
{ name = "Everforest", colorscheme = "everforest" },
{ name = "Dracula", colorscheme = "dracula" },
{ name = "OneDark", colorscheme = "onedark" },
{ name = "Solarized", colorscheme = "solarized" },
{ name = "Monokai Pro", colorscheme = "monokai-pro" },
{ name = "Cyberdream", colorscheme = "cyberdream" },
{ name = "Oxocarbon", colorscheme = "oxocarbon" },
{ name = "Melange", colorscheme = "melange" },
{ name = "Nightfly", colorscheme = "nightfly" },
{ name = "Moonfly", colorscheme = "moonfly" },
{ name = "Sonokai", colorscheme = "sonokai" },
{ name = "Edge", colorscheme = "edge" },
{ name = "Ayu", colorscheme = "ayu" },
{ name = "Material", colorscheme = "material" },
{ name = "Palenight", colorscheme = "palenight" },
{ name = "GitHub Dark", colorscheme = "github_dark" },
{ name = "GitHub Dark Dimmed", colorscheme = "github_dark_dimmed" },
{ name = "GitHub Light", colorscheme = "github_light" },
{ name = "Tokyodark", colorscheme = "tokyodark" },
{ name = "Darkplus", colorscheme = "darkplus" },
{ name = "Poimandres", colorscheme = "poimandres" },
{ name = "Modus Vivendi", colorscheme = "modus" },
{ name = "Zenbones", colorscheme = "zenbones" },
{ name = "Nordic", colorscheme = "nordic" },
{ name = "Jellybeans", colorscheme = "jellybeans" },
{ name = "Tender", colorscheme = "tender" },
{ name = "Horizon", colorscheme = "horizon" },
{ name = "Moonlight", colorscheme = "moonlight" },
{ name = "Lackluster", colorscheme = "lackluster" },
},
livePreview = true, -- Apply theme while navigating
})
end,
},
}
@@ -0,0 +1,14 @@
-- Yanky is enabled via LazyVim's `coding.yanky` extra. Its default
-- `system_clipboard.sync_with_ring = true` reads the `+` register on every yank.
-- Over SSH/herdr the clipboard provider is copy-only OSC 52 (see
-- config/options.lua), so a ring<->clipboard sync is pointless and historically
-- threw when OSC 52 reads were attempted. Disable it on remote sessions only;
-- keep full sync locally where wl-copy/pbcopy make reads cheap and useful.
return {
"gbprod/yanky.nvim",
opts = {
system_clipboard = {
sync_with_ring = vim.env.SSH_CONNECTION == nil,
},
},
}
+3
View File
@@ -0,0 +1,3 @@
indent_type = "Spaces"
indent_width = 2
column_width = 120