Lets "+y/"+p sync with the system clipboard over SSH (Termius -> VPS/homelab) without relying on a local clipboard provider.
287 lines
9.4 KiB
Nix
287 lines
9.4 KiB
Nix
# Declarative Neovim config via nixvim. Goal: a real, lean IDE — not a
|
|
# feature-for-feature VSCode clone. Every plugin here earns its place.
|
|
|
|
{ pkgs, ... }:
|
|
|
|
{
|
|
programs.nixvim = {
|
|
enable = true;
|
|
defaultEditor = true;
|
|
vimAlias = true; # `vim` -> nvim, handy when SSH'd into the VPS/homelab
|
|
|
|
colorschemes.catppuccin = {
|
|
enable = true;
|
|
settings.flavour = "mocha";
|
|
};
|
|
|
|
globals.mapleader = " ";
|
|
|
|
# --- System clipboard over OSC 52, so "+y / "+p work over SSH
|
|
# (Termius -> VPS/homelab) without a local clipboard provider.
|
|
# Requires Neovim 0.10+ for vim.ui.clipboard.osc52; confirmed
|
|
# present (Neovim 0.12.5 on the pinned nixpkgs as of 2026-09).
|
|
extraConfigLua = ''
|
|
vim.g.clipboard = {
|
|
name = "OSC 52",
|
|
copy = {
|
|
["+"] = require("vim.ui.clipboard.osc52").copy("+"),
|
|
["*"] = require("vim.ui.clipboard.osc52").copy("*"),
|
|
},
|
|
paste = {
|
|
["+"] = require("vim.ui.clipboard.osc52").paste("+"),
|
|
["*"] = require("vim.ui.clipboard.osc52").paste("*"),
|
|
},
|
|
}
|
|
'';
|
|
|
|
opts = {
|
|
number = true;
|
|
relativenumber = true;
|
|
shiftwidth = 2;
|
|
tabstop = 2;
|
|
expandtab = true;
|
|
termguicolors = true;
|
|
signcolumn = "yes";
|
|
scrolloff = 8;
|
|
};
|
|
|
|
# --- File explorer ---
|
|
plugins.neo-tree = {
|
|
enable = true;
|
|
settings.window.width = 30;
|
|
};
|
|
|
|
# --- Fuzzy finder: files, grep, buffers, LSP symbols ---
|
|
plugins.telescope = {
|
|
enable = true;
|
|
extensions.fzf-native.enable = true;
|
|
};
|
|
|
|
# --- Treesitter: syntax highlighting/parsing for your stack ---
|
|
plugins.treesitter = {
|
|
enable = true;
|
|
settings.ensure_installed = [
|
|
"nix" "bash" "lua" "markdown" "json" "yaml"
|
|
"rust" "go" "gomod" "c" "cpp" "c_sharp" "python"
|
|
"javascript" "typescript" "tsx" "vue" "html" "css" "dockerfile"
|
|
];
|
|
};
|
|
|
|
# --- LSP: one server per language/toolchain you use ---
|
|
plugins.lsp = {
|
|
enable = true;
|
|
servers = {
|
|
nixd.enable = true;
|
|
bashls.enable = true;
|
|
rust_analyzer = {
|
|
enable = true;
|
|
installCargo = true;
|
|
installRustc = true;
|
|
};
|
|
gopls.enable = true;
|
|
clangd.enable = true; # C / C++
|
|
csharp_ls.enable = true;
|
|
pyright.enable = true;
|
|
ts_ls.enable = true; # JS/TS, React
|
|
volar.enable = true; # Vue
|
|
html.enable = true;
|
|
cssls.enable = true;
|
|
tailwindcss.enable = true;
|
|
jsonls.enable = true;
|
|
dockerls.enable = true;
|
|
docker_compose_language_service.enable = true;
|
|
};
|
|
keymaps.lspBuf = {
|
|
"gd" = "definition";
|
|
"gr" = "references";
|
|
"K" = "hover";
|
|
"<leader>rn" = "rename";
|
|
"<leader>ca" = "code_action";
|
|
};
|
|
};
|
|
|
|
# --- Autocompletion ---
|
|
plugins.cmp = {
|
|
enable = true;
|
|
settings.sources = [
|
|
{ name = "nvim_lsp"; }
|
|
{ name = "luasnip"; }
|
|
{ name = "path"; }
|
|
{ name = "buffer"; }
|
|
];
|
|
settings.snippet.expand = ''
|
|
function(args)
|
|
require("luasnip").lsp_expand(args.body)
|
|
end
|
|
'';
|
|
settings.mapping = {
|
|
"<Tab>" = ''
|
|
cmp.mapping(function(fallback)
|
|
if cmp.visible() then
|
|
cmp.select_next_item()
|
|
elseif require("luasnip").expand_or_jumpable() then
|
|
require("luasnip").expand_or_jump()
|
|
else
|
|
fallback()
|
|
end
|
|
end, { "i", "s" })
|
|
'';
|
|
"<S-Tab>" = ''
|
|
cmp.mapping(function(fallback)
|
|
if cmp.visible() then
|
|
cmp.select_prev_item()
|
|
elseif require("luasnip").jumpable(-1) then
|
|
require("luasnip").jump(-1)
|
|
else
|
|
fallback()
|
|
end
|
|
end, { "i", "s" })
|
|
'';
|
|
};
|
|
};
|
|
plugins.cmp-nvim-lsp.enable = true;
|
|
plugins.cmp_luasnip.enable = true;
|
|
plugins.cmp-path.enable = true;
|
|
plugins.cmp-buffer.enable = true;
|
|
|
|
# --- Snippets ---
|
|
plugins.luasnip = {
|
|
enable = true;
|
|
fromVscode = [ { paths = "${pkgs.vimPlugins.friendly-snippets}"; } ];
|
|
};
|
|
|
|
# --- Pairs and surrounding text ---
|
|
plugins.nvim-autopairs.enable = true;
|
|
plugins.nvim-surround.enable = true;
|
|
|
|
# --- Format on save ---
|
|
plugins.conform-nvim = {
|
|
enable = true;
|
|
settings = {
|
|
format_on_save = {
|
|
lsp_fallback = true;
|
|
timeout_ms = 500;
|
|
};
|
|
formatters_by_ft = {
|
|
rust = [ "rustfmt" ];
|
|
go = [ "gofmt" ];
|
|
c = [ "clang-format" ];
|
|
cpp = [ "clang-format" ];
|
|
python = [ "black" ];
|
|
javascript = [ "prettier" ];
|
|
typescript = [ "prettier" ];
|
|
javascriptreact = [ "prettier" ];
|
|
typescriptreact = [ "prettier" ];
|
|
vue = [ "prettier" ];
|
|
nix = [ "nixpkgs-fmt" ];
|
|
};
|
|
};
|
|
};
|
|
|
|
# --- Git: gutter signs, hunk stage/reset, blame ---
|
|
plugins.gitsigns.enable = true;
|
|
|
|
# --- Git: interactive status/commit/diff/blame ---
|
|
plugins.fugitive.enable = true;
|
|
|
|
# --- Statusline ---
|
|
plugins.lualine.enable = true;
|
|
|
|
# --- Discoverable keybindings while you're still learning ---
|
|
plugins.which-key.enable = true;
|
|
|
|
# --- AI agent: CodeCompanion, curated natively in nixvim ---
|
|
# Configured to reuse your existing `claude`/`codex` CLI auth via ACP
|
|
# adapters instead of a separate Anthropic Console/OpenAI API key.
|
|
#
|
|
# Verified against https://codecompanion.olimorris.dev/configuration/adapters-acp
|
|
# (CodeCompanion >= v18.0.0, which is what's pinned by nixvim's nixpkgs as of
|
|
# 2026-08). Two things changed from an older/assumed layout:
|
|
# - the default-adapter key is `interactions.chat.adapter`, not
|
|
# `strategies.chat.adapter` (renamed in v18.0.0);
|
|
# - the built-in adapter name is `claude_code` (no `_acp` suffix) and
|
|
# lives under `adapters.acp.<name>`.
|
|
#
|
|
# Neither adapter has a true zero-config mode that silently piggybacks on
|
|
# an already-logged-in CLI session:
|
|
# - codex: extends the built-in `codex` preset. `defaults.auth_method`
|
|
# accepts "api-key" | "chat-gpt" (confirmed against the docs above,
|
|
# not "chatgpt"/"openai-api-key"/"codex-api-key"). "chat-gpt" reuses
|
|
# your ChatGPT/Codex CLI login instead of an OPENAI_API_KEY.
|
|
# - claude_code: there's no oauth-personal equivalent. Run
|
|
# `claude setup-token` once (ties to your Pro/Max subscription rather
|
|
# than a Console API key) and save the result to
|
|
# ~/.config/codecompanion/claude-oauth-token (chmod 600, outside the
|
|
# nix store). It's read at runtime via the `cmd:` prefix below rather
|
|
# than embedded in this file.
|
|
plugins.codecompanion = {
|
|
enable = true;
|
|
settings = {
|
|
interactions.chat.adapter = "claude_code"; # switch to "codex" per-chat
|
|
interactions.inline.adapter = "claude_code";
|
|
adapters.acp = {
|
|
opts.show_presets = false;
|
|
claude_code.__raw = ''
|
|
function()
|
|
return require("codecompanion.adapters").extend("claude_code", {
|
|
env = {
|
|
CLAUDE_CODE_OAUTH_TOKEN = "cmd:cat ~/.config/codecompanion/claude-oauth-token",
|
|
},
|
|
})
|
|
end
|
|
'';
|
|
codex.__raw = ''
|
|
function()
|
|
return require("codecompanion.adapters").extend("codex", {
|
|
defaults = {
|
|
auth_method = "chat-gpt",
|
|
},
|
|
})
|
|
end
|
|
'';
|
|
};
|
|
adapters.http.opts.show_presets = false;
|
|
display.chat.window = {
|
|
layout = "vertical";
|
|
position = "right";
|
|
width = 0.4;
|
|
};
|
|
};
|
|
};
|
|
|
|
# --- Editor-level tooling only: formatters conform.nvim shells out to.
|
|
# Language toolchains themselves (cargo, go, dotnet-sdk, nodejs,
|
|
# a compiler, a python interpreter) come from your per-project
|
|
# nix-shell / flake devShell, not from here — launch nvim from
|
|
# inside that shell (`nix develop`, or nix-shell + direnv) so LSP
|
|
# servers resolve the right toolchain via PATH.
|
|
extraPackages = with pkgs; [
|
|
python3Packages.black
|
|
prettier
|
|
nixpkgs-fmt
|
|
file
|
|
ripgrep
|
|
sqlite
|
|
];
|
|
|
|
keymaps = [
|
|
{ mode = "n"; key = "<leader>e"; action = "<cmd>Neotree toggle<CR>"; }
|
|
{ mode = "n"; key = "<leader>ff"; action = "<cmd>Telescope find_files<CR>"; }
|
|
{ mode = "n"; key = "<leader>fg"; action = "<cmd>Telescope live_grep<CR>"; }
|
|
{ mode = "n"; key = "<leader>fb"; action = "<cmd>Telescope buffers<CR>"; }
|
|
|
|
# CodeCompanion
|
|
{ mode = [ "n" "v" ]; key = "<leader>ac"; action = "<cmd>CodeCompanionChat Toggle<CR>"; }
|
|
{ mode = "v"; key = "<leader>aa"; action = "<cmd>CodeCompanionChat Add<CR>"; }
|
|
{ mode = "n"; key = "<leader>ai"; action = "<cmd>CodeCompanion<CR>"; } # inline assistant
|
|
|
|
# Fugitive
|
|
{ mode = "n"; key = "<leader>gs"; action = "<cmd>Git<CR>"; } # status
|
|
{ mode = "n"; key = "<leader>gc"; action = "<cmd>Git commit<CR>"; } # interactive commit
|
|
{ mode = "n"; key = "<leader>gp"; action = "<cmd>Git push<CR>"; }
|
|
{ mode = "n"; key = "<leader>gl"; action = "<cmd>Git pull<CR>"; }
|
|
{ mode = "n"; key = "<leader>gd"; action = "<cmd>Gvdiffsplit<CR>"; } # diff current file
|
|
];
|
|
};
|
|
}
|