A Neovim plugin designed to interface with local LLMs (via Ollama) for asynchronous code transformation, review, and history management.
- Introduction
- Installation
- Configuration
- Usage
- Commands
- Keybindings
- History Management
- Prompt Library
Ajapopaja provides a bridge between your Neovim buffer and Large Language Models. It specializes in two main workflows:
- Code Transformation: Modify selected code based on natural language or predefined prompts. Iterate on LLM responses. Replace the original selection with the transformation result or insert at cursor position.
- Code Review: Provide actionable feedback on code quality and logic and LLM prompts to resolve identified issues.
All LLM calls are handled asynchronously using a Python-based RPC backend to ensure the Neovim UI remains responsive during generation.
- Neovim >= 0.10.0 (Recommended)
- Python 3.10+
- Ollama (running locally or accessible via network)
When using lazy.nvim add ~/.config/nvim/lua/plugins/ajapopaja.lua and restart nvim:
return {
"marcbaechinger/ajapopaja-nvim",
build = ":UpdateRemotePlugins",
config = function()
require("ajapopaja").setup({
-- Optional: defaults to http://localhost:11434
ollama_host = "http://localhost:11434"
})
end
}Ajapopaja requires a dedicated Python virtual environment containing pynvim and aiohttp. To set this up:
-
Provision Environment: Run the following command within Neovim:
:AjapopajaBootstrapThis creates a
.venvfolder inside the plugin directory and installs all necessary Python dependencies asynchronously. -
Restart Neovim: A full restart is required to initialize the new Python RPC host.
You can verify that everything is correctly configured by running:
:checkhealth ajapopaja
The health check will validate your Python provider path, Ollama connectivity, and RPC function registration.
The plugin is configured via the setup() function. Calling setup() is required to initialize commands and background model refreshing.
require("ajapopaja").setup({
-- The URL where your Ollama instance is reachable.
ollama_host = "http://localhost:11434", -- default
-- Set to false to disable the default keybindings.
default_keymaps = true, -- default
})g:ajapopaja_ollama_host
The host address for the Ollama API. This variable is set by the setup() function but can be manually overridden if needed for specific RPC calls.
Ajapopaja provides a get_status() function that indicates the current active model and a loading spinner () when the LLM is processing a request.
Recommended lualine.nvim configuration:
require("lualine").setup({
sections = {
lualine_x = {
{
function()
-- Displays active model and loading state
return require("ajapopaja").get_status()
end,
color = { fg = "#ff9e64" },
},
"encoding",
"fileformat",
"filetype",
},
},
})- Select a block of code in Visual Line Mode (V-line) or stay in Normal Mode for the whole buffer.
- Press
<leader>ai(free text transformation prompt) or<leader>as(select standard prompt). - In the free form prompt window, enter your transformation request (e.g., "Refactor this into a class"). Press
<CR>in normal mode to prompt. - The LLM processes the request. Once finished, use
<leader>atto apply the transformation result to the initial selection. Alternatively, use<leader>apto paste at the current cursor position in normal mode or to replace the current selection in visual mode.
When in the free text prompt window, <leader>as opens the prompt picker
to insert a standard prompt from the select dialog.
The last transformation result is also copied to the register 'c' and can be inserted with "cp.
Note: When <leader>at is used the original selection is only replaced if the selection content wasn't change in the mean time. In such a case, use <leader>ap to replace the current visual selection or insert at the cursor position normal mode.
One of the most powerful features of the History Window is the ability to "refine" or "iterate" on a previous output. By pressing i, you can provide additional instructions to the LLM based on its previous response. The plugin automatically handles the context transfer, ensuring the model understands the current state of the code transformation.
Ajapopaja maintains a persistent history of interactions.
- History items are categorized by "call type" (transform, review).
- The history window allows you to preview responses before applying them.
- Persistent storage is managed by the Python backend in
~/.ajapopaja/.
| Command | Description |
|---|---|
:AjapopajaBootstrap |
Initialize the Python virtual environment |
:AjapopajaTransform |
Open prompt window for selected text |
:AjapopajaSelectPrompt |
Select a standard prompt |
:AjapopajaApplyLatest |
Apply the latest transformation |
:AjapopajaInsertLatest |
Insert/replace with the latest transformation |
:AjapopajaReview |
Review selected text |
:AjapopajaHistory |
Open the history window |
:AjapopajaSelectModel |
Select the LLM to use |
:AjapopajaRefreshModels |
Refresh the list of available LLMs |
:AjapopajaEditPrompts |
Open the prompt directory for editing |
| Key | Mode | Action |
|---|---|---|
<leader>ai |
n, V | Prompt for transformation (:AjapopajaTransform) |
<leader>as |
n, V | Select from filetype-specific prompts (AjapopajaSelectPrompt) |
<leader>at |
n | Apply the latest transformation (:AjapopajaApplyLatest) |
<leader>ap |
n, V | Insert/replace with the latest transformation (:AjapopajaInsertLatest) |
<leader>ah |
n | Open the history window (`:AjapopajaHistory) |
<leader>am |
n | Select LLM (:AjapopajaSelectModel) |
<leader>aM |
n | Refresh list of LLMs (:AjapopajaRefreshModels) |
<leader>ae |
n | Edit prompts (:AjapopajaEditPrompts) |
The History Window is a sophisticated, interactive interface designed for browsing, managing, and applying past LLM interactions. It can be triggered via :AjapopajaHistory or the default mapping <leader>aw.
The window operates in two primary modes, which can be toggled:
- Transform: For code refactors and modifications.
- Review: For textual feedback and code analysis.
| Key | Action |
|---|---|
l |
Navigate to the next history item |
h |
Navigate to the previous history item |
t |
Switch view to "Transform" history |
r |
Switch view to "Review" history |
i |
Iterate: Trigger a custom refinement on the current item |
I |
Standard Refine: Use a library prompt to refine the current item |
x |
Delete the current history entry |
C |
Clear all history for the current view (Transform or Review) |
<CR> |
Apply: (Transform only) Replace buffer text with this result |
q |
Close the history window |
Ajapopaja uses a filetype-aware prompt library. This allows you to define specialized instructions for different programming languages.
Prompts are stored as Markdown files in the /prompts directory of the plugin root:
default.md(Fallback for any filetype)python.md(Python specific prompts)javascript.md(JavaScript specific prompts)lua.md(Lua specific prompts)
Each file contains one or more prompts. A prompt starts with a top-level heading (#) which acts as the title displayed in the selection UI. All text following the heading until the next heading is treated as the prompt content.
Example python.md:
# Add Docstring
Write a Google-style docstring for this Python function.
Include Args, Returns, and Raises sections.
# Refactor to Type Hints
Add PEP 484 type hints to this code. Ensure all function
signatures and variables are explicitly typed.When you trigger the prompt selection UI (<leader>as):
- The plugin detects the current buffer's 'filetype'.
- It looks for a corresponding
.mdfile in the/promptsdirectory. - If the specific file does not exist, it falls back to
default.md. - Prompts are cached in memory after the first read to ensure the UI remains snappy.
To add your own prompts, simply create a Markdown file named after the language's filetype in the plugin's prompts/ directory. If you are using a plugin manager like lazy.nvim, you can find this path via:
print(vim.api.nvim_get_runtime_file("prompts/default.md", false)[1])MIT
