A Neovim plugin that sends text under cursor or visual selection to Ollama or OpenAI-compatible services, displaying the response in a floating window.
- π€ Support for Ollama and OpenAI-compatible APIs
- β‘ Streaming response with real-time display
- π Adaptive floating window size
- π Works in Normal mode (word under cursor) and Visual mode (selected text)
- π Customizable prompts for different actions (translate, explain, refactor, etc.)
- β³ Built-in loading indicator
The demo showcases:
- π Translation - Translate text between Chinese and English
- π Regex Explain - Break down complex regex patterns into readable explanations
- β° Cron Explain - Convert cron expressions to human-readable sentences
- π» Shell Explain - Analyze shell commands and their parameters
- π Emoji - Convert text to emoji expressions
- Neovim >= 0.10.0 (uses
vim.systemfor async HTTP) curlinstalled on your system- Ollama running locally, or an OpenAI-compatible API endpoint
Using lazy.nvim
{
"Root-lee/popai.nvim",
config = function()
require("popai").setup({
-- your configuration here
})
end,
cmd = "Popai",
keys = {
{ "<leader>t", ":Popai translate<CR>", mode = { "n", "v" }, desc = "Translate with PopAI" },
},
}require("popai").setup({
-- Service type: "ollama" or "openai"
service = "ollama",
-- Global system prompt (optional)
-- Applies to both Ollama and OpenAI
system_prompt = "Act as a concise coding assistant. Provide direct answers without unnecessary conversational filler.",
-- Ollama configuration
ollama = {
url = "http://127.0.0.1:11434/api/generate",
model = "llama3",
},
-- OpenAI-compatible configuration
openai = {
url = "https://api.openai.com/v1/chat/completions",
model = "gpt-3.5-turbo",
api_key = os.getenv("OPENAI_API_KEY"),
},
-- Prompts for different actions
prompts = {
translate_ch = "Translate the following text to Simplified Chinese. Only output the translation result without any explanation:\n\n{input}",
translate_en = "Translate the following text to English. Only output the translation result without any explanation:\n\n{input}",
regex_explain = "Explain this regex concisely: {input} Format the output as follows:\nFunction: [Brief description]\nLogic: [Component breakdown]\nExample: [One matching string]\nUse Markdown. No conversational filler.",
shell_explain = "Break down this shell command and explain what each flag/parameter does: {input}",
cron_explain = "Translate this Cron expression into a human-readable sentence (e.g., 'Every 15 minutes, Monday through Friday'): {input}",
},
-- UI configuration
ui = {
width_ratio = 0.4, -- Window width as percentage of screen
height_ratio = 0.3, -- Max window height as percentage of screen
border = "rounded", -- Border style: "none", "single", "double", "rounded", "solid", "shadow"
title = " PopAI ", -- Window title
},
})require("popai").setup({
service = "ollama",
ollama = {
model = "qwen3:4b",
},
})require("popai").setup({
service = "openai",
openai = {
api_key = os.getenv("OPENAI_API_KEY"),
model = "gpt-4",
},
})require("popai").setup({
service = "openai",
openai = {
url = "https://api.deepseek.com/v1/chat/completions",
api_key = os.getenv("DEEPSEEK_API_KEY"),
model = "deepseek-chat",
},
})require("popai").setup({
prompts = {
translate_jp = "Translate the following text to Japanese. Only output the translation result without any explanation:\n\n{input}",
emoji = "Express the following text using ONLY emojis. Do not use any words or letters:\n\n{input}",
sql = "Format this SQL query and explain what it does concisely:\n\n{input}",
},
})You can also use the {input} placeholder to insert the selected text at a specific position in the prompt. If {input} is not present, the text will be appended to the end of the prompt.
require("popai").setup({
prompts = {
-- Text will be inserted at {input}
custom_task = "Task: Process the following content: {input}\n\nRequirements: ...",
},
})| Command | Description |
|---|---|
:Popai translate_ch |
Translate to Chinese |
:Popai translate_en |
Translate to English |
:Popai regex_explain |
Explain regex |
:Popai shell_explain |
Explain shell command |
:Popai cron_explain |
Explain cron expression |
:Popai <custom> |
Run any custom prompt you defined |
- Normal mode: Place cursor on a word, then run
:Popai translate_ch - Visual mode: Select text, then run
:'<,'>Popai translate_ch - Close window: Press
qor<Esc>in the floating window
-- In your lazy.nvim plugin spec
keys = {
{ "<leader>pc", ":Popai translate_ch<CR>", mode = { "n", "v" }, desc = "Translate to Chinese" },
{ "<leader>pe", ":Popai translate_en<CR>", mode = { "n", "v" }, desc = "Translate to English" },
{ "<leader>pr", ":Popai regex_explain<CR>", mode = { "n", "v" }, desc = "Regex Explain" },
{ "<leader>ps", ":Popai shell_explain<CR>", mode = { "n", "v" }, desc = "Shell Explain" },
{ "<leader>pt", ":Popai cron_explain<CR>", mode = { "n", "v" }, desc = "Cron Explain" },
}
-- Or manually in your config
vim.keymap.set({ "n", "v" }, "<leader>pc", ":Popai translate_ch<CR>", { desc = "Translate to Chinese" })
vim.keymap.set({ "n", "v" }, "<leader>pe", ":Popai translate_en<CR>", { desc = "Translate to English" })
vim.keymap.set({ "n", "v" }, "<leader>pr", ":Popai regex_explain<CR>", { desc = "Regex Explain" })
vim.keymap.set({ "n", "v" }, "<leader>ps", ":Popai shell_explain<CR>", { desc = "Shell Explain" })
vim.keymap.set({ "n", "v" }, "<leader>pt", ":Popai cron_explain<CR>", { desc = "Cron Explain" })MIT
