Neovim as an AI-Powered Coding Assistant on Linux

Neovim as an AI-Powered Coding Assistant on Linux
Neovim has evolved from a modal text editor into a fully extensible IDE platform. When you layer modern AI coding assistants on top of its Lua-based plugin ecosystem, you get something that rivals — and often surpasses — GUI editors like VS Code in terms of speed, customization, and integration depth. This tutorial walks through every major AI plugin available for Neovim, from GitHub Copilot to fully local models running through Ollama, with complete configuration snippets you can drop directly into your setup.
Why Neovim and AI Is a Powerful Combination
The terminal-native workflow that Neovim enforces means AI suggestions appear in context without switching windows, copying code, or leaving your SSH session. Everything operates in the same buffer you're already editing. Neovim's Tree-sitter integration gives AI plugins access to a parsed AST of your code, so context passed to language models is structurally aware rather than raw text. The Lua runtime lets you write adapter logic, custom prompts, and workflow automation that GUI editors simply cannot match.
For Linux developers working on servers, in containers, or across SSH connections, this matters enormously. You can run Neovim over SSH with full AI capabilities using local or remote models. You maintain the same muscle memory and keybindings regardless of whether you're on your workstation or a production jump host.
Prerequisites
Neovim 0.9 or Later
Most AI plugins require Neovim 0.9+ and several require 0.10+ for features like vim.lsp.buf.execute_command improvements and the new vim.system() API. Install from the official release if your distribution ships an older version:
# Check current version
nvim --version
# Install latest stable via AppImage (works on any Linux distro)
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz
tar xzf nvim-linux64.tar.gz
sudo mv nvim-linux64 /opt/nvim
sudo ln -sf /opt/nvim/bin/nvim /usr/local/bin/nvim
# Or via snap
sudo snap install nvim --classiclazy.nvim Plugin Manager
All configurations in this tutorial use lazy.nvim. Bootstrap it in your init.lua if you haven't already:
-- ~/.config/nvim/init.lua
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git", "clone", "--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", lazypath,
})
end
vim.opt.rtp:prepend(lazypath)Node.js and Other Dependencies
# Node.js 18+ required for Copilot
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Python provider (needed by some plugins)
pip3 install --user pynvim
# ripgrep for telescope integration used by some AI plugins
sudo apt-get install ripgrep
# curl and jq for API debugging
sudo apt-get install curl jqGitHub Copilot in Neovim
copilot.lua (Recommended)
The Lua-native copilot.lua by zbirenbaum offers better performance than the original VimScript plugin and integrates with nvim-cmp for completion menu entries:
-- In your lazy.nvim plugin spec
{
"zbirenbaum/copilot.lua",
cmd = "Copilot",
event = "InsertEnter",
config = function()
require("copilot").setup({
suggestion = {
enabled = true,
auto_trigger = true,
debounce = 75,
keymap = {
accept = "",
accept_word = "",
accept_line = "",
next = "",
prev = "",
dismiss = "",
},
},
panel = {
enabled = true,
auto_refresh = false,
keymap = {
jump_prev = "[[",
jump_next = "]]",
accept = "",
refresh = "gr",
open = "",
},
layout = {
position = "bottom",
ratio = 0.4,
},
},
filetypes = {
yaml = false,
markdown = true,
help = false,
gitcommit = true,
gitrebase = true,
["."] = false,
},
copilot_node_command = "node",
server_opts_overrides = {},
})
end,
},
-- nvim-cmp source for Copilot (shows in completion menu)
{
"zbirenbaum/copilot-cmp",
dependencies = { "zbirenbaum/copilot.lua" },
config = function()
require("copilot_cmp").setup({
method = "getCompletionsCycling",
})
end,
}, Authentication
# Inside Neovim, run the authentication command
:Copilot auth
# This opens a browser or gives you a device code
# Follow the prompts to link your GitHub account
# Verify authentication succeeded
:Copilot statusCopilot Chat
CopilotChat.nvim brings a conversational interface for asking questions about your code, generating tests, and refactoring:
{
"CopilotC-Nvim/CopilotChat.nvim",
branch = "canary",
dependencies = {
{ "zbirenbaum/copilot.lua" },
{ "nvim-lua/plenary.nvim" },
},
config = function()
require("CopilotChat").setup({
debug = false,
model = "gpt-4o",
temperature = 0.1,
system_prompt = require("CopilotChat.prompts").COPILOT_INSTRUCTIONS,
window = {
layout = "vertical",
width = 0.4,
},
mappings = {
complete = { detail = "Use @ or / for options.", insert = "" },
close = { normal = "q", insert = "" },
reset = { normal = "", insert = "" },
submit_prompt = { normal = "", insert = "" },
toggle_sticky = { detail = "Makes line under cursor sticky or deletes sticky lines.", normal = "gr" },
accept_diff = { normal = "", insert = "" },
yank_diff = { normal = "gy" },
show_diff = { normal = "gd" },
show_system_prompt = { normal = "gp" },
show_user_selection = { normal = "gs" },
},
})
end,
keys = {
{ "cc", "CopilotChatToggle", desc = "Copilot Chat Toggle" },
{ "ce", "CopilotChatExplain", mode = "v", desc = "Explain selection" },
{ "ct", "CopilotChatTests", mode = "v", desc = "Generate tests" },
{ "cr", "CopilotChatReview", mode = "v", desc = "Review code" },
{ "cf", "CopilotChatFix", mode = "v", desc = "Fix code" },
},
}, Avante.nvim — Claude/GPT Sidebar
Avante.nvim provides a Cursor-like experience directly in Neovim: a persistent sidebar that shows AI suggestions as diffs you can accept or reject, with support for Claude, GPT-4, and local models.
{
"yetone/avante.nvim",
event = "VeryLazy",
lazy = false,
version = false,
build = "make",
dependencies = {
"nvim-treesitter/nvim-treesitter",
"stevearc/dressing.nvim",
"nvim-lua/plenary.nvim",
"MunifTanjim/nui.nvim",
"nvim-tree/nvim-web-devicons",
{
"HakonHarnes/img-clip.nvim",
event = "VeryLazy",
opts = {
default = {
embed_image_as_base64 = false,
prompt_for_file_name = false,
drag_and_drop = { insert_mode = true },
use_absolute_path = true,
},
},
},
{
"MeanderingProgrammer/render-markdown.nvim",
opts = { file_types = { "markdown", "Avante" } },
ft = { "markdown", "Avante" },
},
},
opts = {
provider = "claude",
auto_suggestions_provider = "claude",
claude = {
endpoint = "https://api.anthropic.com",
model = "claude-3-5-sonnet-20241022",
timeout = 30000,
temperature = 0,
max_tokens = 8192,
},
openai = {
endpoint = "https://api.openai.com/v1",
model = "gpt-4o",
timeout = 30000,
temperature = 0,
max_tokens = 4096,
},
behaviour = {
auto_suggestions = false,
auto_set_highlight_group = true,
auto_set_keymaps = true,
auto_apply_diff_after_generation = false,
support_paste_from_clipboard = false,
},
mappings = {
diff = {
ours = "co",
theirs = "ct",
all_theirs = "ca",
both = "cb",
cursor = "cc",
next = "]x",
prev = "[x",
},
suggestion = {
accept = "",
next = "",
prev = "",
dismiss = "",
},
jump = { next = "]]", prev = "[[" },
submit = { normal = "", insert = "" },
sidebar = {
apply_all = "A",
apply_cursor = "a",
switch_windows = "",
reverse_switch_windows = "",
},
},
hints = { enabled = true },
windows = {
position = "right",
wrap = true,
width = 30,
sidebar_header = {
align = "center",
rounded = true,
},
},
highlights = {
diff = {
current = "DiffText",
incoming = "DiffAdd",
},
},
diff = {
autojump = true,
list_opener = "copen",
},
},
}, Setting API Keys for Avante
# Add to ~/.bashrc or ~/.zshrc
export ANTHROPIC_API_KEY="sk-ant-your-key-here"
export OPENAI_API_KEY="sk-your-key-here"
# Or use a secrets manager and load dynamically
# In Neovim, you can also set via:
# :lua vim.env.ANTHROPIC_API_KEY = "sk-ant-..."Open Avante with <leader>aa (default binding), type your prompt, and the plugin generates a diff displayed inline in your buffer alongside a sidebar explanation.
CodeCompanion.nvim
CodeCompanion takes a different approach: it's built around a flexible adapter system that separates the UI from the model provider, making it easy to switch between Claude, OpenAI, Gemini, and local Ollama models without changing your workflow.
{
"olimorris/codecompanion.nvim",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter",
"hrsh7th/nvim-cmp",
{ "stevearc/dressing.nvim", opts = {} },
},
config = function()
require("codecompanion").setup({
strategies = {
chat = {
adapter = "anthropic",
roles = {
llm = "CodeCompanion",
user = "You",
},
keymaps = {
send = { modes = { n = "", i = "" } },
close = { modes = { n = "q" } },
stop = { modes = { n = "" } },
clear = { modes = { n = "gc" } },
},
},
inline = {
adapter = "anthropic",
keymaps = {
accept_change = { modes = { n = "ga" }, description = "Accept change" },
reject_change = { modes = { n = "gr" }, description = "Reject change" },
},
},
agent = {
adapter = "anthropic",
},
},
adapters = {
anthropic = function()
return require("codecompanion.adapters").extend("anthropic", {
env = { api_key = "ANTHROPIC_API_KEY" },
schema = {
model = {
default = "claude-3-5-sonnet-20241022",
},
},
})
end,
ollama = function()
return require("codecompanion.adapters").extend("ollama", {
schema = {
model = { default = "codellama:13b" },
num_ctx = { default = 16384 },
},
})
end,
},
display = {
action_palette = { width = 95, height = 10 },
chat = {
window = {
layout = "vertical",
border = "single",
height = 0.8,
width = 0.45,
relative = "editor",
},
},
inline = { diff = { enabled = true } },
},
})
end,
keys = {
{ "ai", "CodeCompanion", desc = "CodeCompanion inline" },
{ "ac", "CodeCompanionChat Toggle", desc = "CodeCompanion Chat" },
{ "aa", "CodeCompanionActions", desc = "CodeCompanion Actions", mode = { "n", "v" } },
{ "ga", "CodeCompanionChat Add", mode = "v", desc = "Add selection to chat" },
},
}, cmp-ai with Ollama for Local Completion
For completion that never leaves your machine, cmp-ai hooks into nvim-cmp and queries any OpenAI-compatible API — including Ollama running locally.
# Install Ollama
curl -fsSL https://ollama.com/install.sh | sh
# Pull a code-focused model
ollama pull codellama:13b
ollama pull deepseek-coder:6.7b
ollama pull qwen2.5-coder:7b
# Verify it's running
curl http://localhost:11434/api/tags | jq '.models[].name'{
"tzachar/cmp-ai",
dependencies = { "hrsh7th/nvim-cmp" },
config = function()
local cmp_ai = require("cmp_ai.config")
cmp_ai:setup({
max_lines = 1000,
provider = "Ollama",
provider_options = {
model = "codellama:13b",
base_url = "http://localhost:11434/api/generate",
temperature = 0.2,
top_p = 0.9,
},
notify = true,
notify_callback = function(msg)
vim.notify(msg, vim.log.levels.INFO)
end,
run_on_every_keystroke = false,
ignored_file_types = { "TelescopePrompt" },
})
end,
},Register it as a cmp source in your nvim-cmp configuration:
-- Inside your nvim-cmp sources table
sources = cmp.config.sources({
{ name = "copilot", group_index = 2 },
{ name = "cmp_ai", group_index = 2 },
{ name = "nvim_lsp", group_index = 1 },
{ name = "luasnip", group_index = 1 },
{ name = "buffer", group_index = 3, keyword_length = 3 },
{ name = "path", group_index = 3 },
}),Fully Local AI with Ollama
Running models entirely locally gives you zero API costs, complete privacy (no code leaves your machine), and offline operation. The tradeoff is that consumer hardware typically runs smaller models that produce lower-quality completions than frontier models.
# Run Ollama as a systemd service for persistent access
sudo systemctl enable ollama
sudo systemctl start ollama
# Check GPU acceleration (CUDA or ROCm)
ollama run codellama:13b "Write a Python function to merge two sorted lists"
# Monitor GPU usage during inference
watch -n 1 nvidia-smi # NVIDIA
watch -n 1 rocm-smi # AMD
# Use a larger context window for better code understanding
OLLAMA_NUM_PARALLEL=2 OLLAMA_MAX_LOADED_MODELS=2 ollama serveConfigure CodeCompanion to use your local Ollama instance for the chat strategy while keeping the inline strategy on a cloud model:
strategies = {
chat = { adapter = "ollama" }, -- free, private
inline = { adapter = "anthropic" }, -- higher quality for edits
},Comparing the Approaches
- copilot.lua + CopilotChat: Best for developers with GitHub Copilot subscriptions who want seamless inline completions and lightweight chat. Lowest setup friction, broadest language support.
- Avante.nvim: Best when you want a Cursor-style diff workflow. The sidebar with accept/reject hunks is ideal for larger refactors where you review every change before applying.
- CodeCompanion.nvim: Best for teams or individuals who switch between providers frequently, or who want a single interface that works identically against Claude, OpenAI, or Ollama.
- cmp-ai with Ollama: Best for air-gapped environments, work with sensitive codebases where API transmission is unacceptable, or developers who want to experiment with local model fine-tuning.
Complete lazy.nvim Configuration
-- ~/.config/nvim/lua/plugins/ai.lua
return {
-- Copilot inline suggestions
{
"zbirenbaum/copilot.lua",
cmd = "Copilot",
event = "InsertEnter",
opts = {
suggestion = { enabled = false }, -- handled by copilot-cmp
panel = { enabled = false },
filetypes = { markdown = true, yaml = false },
},
},
-- Copilot in cmp menu
{
"zbirenbaum/copilot-cmp",
dependencies = "zbirenbaum/copilot.lua",
config = true,
},
-- Copilot Chat
{
"CopilotC-Nvim/CopilotChat.nvim",
branch = "canary",
dependencies = { "zbirenbaum/copilot.lua", "nvim-lua/plenary.nvim" },
opts = {
model = "gpt-4o",
window = { layout = "vertical", width = 0.4 },
},
keys = {
{ "cc", "CopilotChatToggle", desc = "Copilot Chat" },
{ "ce", "CopilotChatExplain", mode = "v", desc = "Explain" },
{ "ct", "CopilotChatTests", mode = "v", desc = "Tests" },
},
},
-- Avante sidebar
{
"yetone/avante.nvim",
event = "VeryLazy",
build = "make",
dependencies = {
"nvim-treesitter/nvim-treesitter",
"stevearc/dressing.nvim",
"nvim-lua/plenary.nvim",
"MunifTanjim/nui.nvim",
},
opts = {
provider = "claude",
claude = { model = "claude-3-5-sonnet-20241022", max_tokens = 8192 },
windows = { position = "right", width = 35 },
},
},
-- CodeCompanion multi-provider
{
"olimorris/codecompanion.nvim",
dependencies = { "nvim-lua/plenary.nvim", "nvim-treesitter/nvim-treesitter" },
config = function()
require("codecompanion").setup({
strategies = {
chat = { adapter = "anthropic" },
inline = { adapter = "anthropic" },
agent = { adapter = "anthropic" },
},
adapters = {
anthropic = function()
return require("codecompanion.adapters").extend("anthropic", {
env = { api_key = "ANTHROPIC_API_KEY" },
schema = { model = { default = "claude-3-5-sonnet-20241022" } },
})
end,
ollama = function()
return require("codecompanion.adapters").extend("ollama", {
schema = {
model = { default = "deepseek-coder:6.7b" },
num_ctx = { default = 16384 },
},
})
end,
},
})
end,
keys = {
{ "ai", "CodeCompanion", desc = "AI Inline" },
{ "ac", "CodeCompanionChat Toggle", desc = "AI Chat" },
{ "aa", "CodeCompanionActions", mode = { "n","v" }, desc = "AI Actions" },
},
},
-- Local completions via Ollama
{
"tzachar/cmp-ai",
dependencies = "hrsh7th/nvim-cmp",
config = function()
require("cmp_ai.config"):setup({
max_lines = 500,
provider = "Ollama",
provider_options = {
model = "deepseek-coder:6.7b",
base_url = "http://localhost:11434/api/generate",
temperature = 0.1,
},
run_on_every_keystroke = false,
})
end,
},
} Key Mappings and Workflow
-- ~/.config/nvim/lua/config/keymaps.lua (AI section)
local map = vim.keymap.set
-- Copilot Chat
map("n", "cc", "CopilotChatToggle", { desc = "Copilot Chat toggle" })
map("v", "ce", "CopilotChatExplain", { desc = "Explain selection" })
map("v", "ct", "CopilotChatTests", { desc = "Generate tests" })
map("v", "cr", "CopilotChatReview", { desc = "Review code" })
map("v", "cf", "CopilotChatFix", { desc = "Fix bugs" })
map("n", "cp", "CopilotChatPrompts", { desc = "Open prompt library" })
-- Avante
map({ "n","v" }, "aa", "AvanteAsk", { desc = "Avante Ask" })
map({ "n","v" }, "ae", "AvanteEdit", { desc = "Avante Edit" })
map("n", "ar", "AvanteRefresh",{ desc = "Avante Refresh" })
-- CodeCompanion
map("n", "ai", "CodeCompanion", { desc = "Inline AI" })
map("n", "ac", "CodeCompanionChat Toggle", { desc = "AI Chat" })
map({ "n","v" }, "ap", "CodeCompanionActions", { desc = "AI Actions palette" })
map("v", "ga", "CodeCompanionChat Add", { desc = "Add to AI chat" })
-- Quick model switch for CodeCompanion
map("n", "aol", function()
vim.cmd("CodeCompanionChat Toggle")
vim.notify("Switched to Ollama (local)", vim.log.levels.INFO)
end, { desc = "Open local AI chat" }) Practical Workflow Example
- Open a file and write a function signature with a docstring comment describing intent.
- Press
<Tab>to accept Copilot's inline completion for the implementation. - Visually select the function, press
<leader>ct</code> to generate unit tests in a chat buffer. - Copy the tests to a new file, then visually select an edge case and press
<leader>aa</code> to ask Avante to extend it. - Review the diff in the Avante sidebar, accept with
ca</code> (accept all) or navigate hunks with]]</code> and apply individually.
Performance Tuning
Running multiple AI plugins simultaneously can increase startup time and memory usage. Lazy-load aggressively: Copilot on InsertEnter, Avante on VeryLazy, CodeCompanion on its keybindings only. Profile with :Lazy profile after setup.
# Monitor Neovim memory while using AI plugins
# From another terminal:
ps aux | grep nvim | awk '{print $6/1024 " MB", $11}'
# Check Ollama resource usage
ollama ps
# Reduce Ollama context to save VRAM on smaller GPUs
# In cmp-ai or CodeCompanion adapter config:
# num_ctx = 4096 (instead of 16384)Keep your API keys out of your dotfiles repository. Use ~/.config/nvim/lua/local.lua (gitignored) or export them from your shell's secret management system like pass or 1password-cli into environment variables that Neovim inherits at launch.
