How 99's Visual Selection Mode Integrates with AI Code Generation in Neovim
99's visual selection mode converts highlighted code into structured LLM prompts and automatically replaces the selection with AI-generated output using internal Range and Request APIs.
ThePrimeagen's 99 plugin transforms Neovim's visual selection mode into a seamless AI code generation interface. By leveraging internal Lua APIs, the plugin captures your highlighted code, constructs context-rich prompts, and streams LLM responses directly back into your buffer. This workflow eliminates the need for external scripts while maintaining full editor responsiveness.
How Visual Selection Mode Works in 99
The integration follows a strict pipeline from selection capture to text replacement, orchestrated through several specialized modules.
Entry Point: The visual API
When you invoke :lua require('99').visual() or its mapped keybinding, the function _99.visual in lua/99/init.lua initiates the workflow. This function prepares the request context using get_context("visual"), then immediately calls set_selection_marks() followed by Range.from_visual_selection() to capture the selected region.
Capturing the Selection Range
The Range.from_visual_selection function in lua/99/geo.lua reads Neovim's '< and '> marks to determine the exact boundaries of your selection. It normalizes line-end handling and returns a Range object containing the buffer number, start/end points, and helper methods including :to_string(), :to_text(), and :replace_text().
Processing the AI Request
The captured range is passed to ops.over_range in lua/99/ops/over-range.lua, which orchestrates the core logic:
- Mark Preservation: Creates temporary top and bottom marks using
Mark.mark_above_rangeandMark.mark_pointto ensure the original selection can be restored after the request completes. - Prompt Construction: Builds the visual-selection prompt by calling
context._99.prompts.prompts.visual_selection(range), which references the template inlua/99/prompt-settings.lua. This template embeds the range location, current selection text, and full file contents. - Request Initialization: Creates a
Requestobject viamake_prompt(inlua/99/ops/make-prompt.lua) that handles completions, agent rules, and streams the AI output while displaying live status updates throughRequestStatus.
Replacing Text with Generated Code
When the request finishes with status "success", over_range validates that the original marks remain valid (accounting for potential user edits during generation). It reconstructs the target range using Range.from_marks and executes new_range:replace_text(lines), where lines contains the AI-generated content split on newlines. The function injects a leading empty line to maintain visual line alignment before writing the output back into the buffer.
Cleanup occurs regardless of success or failure via clean_up, which stops visual status indicators, clears temporary marks, and cancels any pending requests.
Key Components of the Visual Selection Workflow
The integration relies on these specific modules:
lua/99/init.lua– Public API entry point; defines_99.visualthat captures selections and invokes core operations.lua/99/geo.lua– ImplementsRange.from_visual_selectionfor converting Neovim marks into structured range objects.lua/99/ops/over-range.lua– Core workflow engine handling mark preservation, prompt building, request execution, and text replacement.lua/99/prompt-settings.lua– Contains thevisual_selectionprompt template that structures context for the LLM.lua/99/ops/make-prompt.lua– Combines visual prompts with user input and extracts completion references.lua/99/request/init.lua– Manages AI request lifecycle including streaming and cancellation.lua/99/ops/request_status.lua– Provides live status updates during LLM generation.lua/99/ops/marks.lua– Utility for creating and managing temporary marks that survive asynchronous operations.
Practical Examples
Triggering Visual Selection Mode
Visually select the code you want to replace, then execute the command (typically mapped to <Leader>v):
-- Visually select code, then run:
vim.cmd('lua require("99").visual()')
Understanding the Internal Flow
The plugin processes your selection through several internal stages:
-- _99.visual (lua/99/init.lua)
local range = Range.from_visual_selection() -- Captures '< and '> marks
ops.over_range(context, range, opts) -- Hands to core logic
The LLM receives a structured prompt generated from lua/99/prompt-settings.lua:
-- visual_selection prompt template structure
-- You receive a selection in neovim that you need to replace with new code.
-- <SELECTION_LOCATION>
-- range(1:5,1:12)
-- </SELECTION_LOCATION>
-- <SELECTION_CONTENT>
-- old code…
-- </SELECTION_CONTENT>
-- <FILE_CONTAINING_SELECTION>
-- path/to/file.lua
-- </FILE_CONTAINING_SELECTION>
After generation, the plugin replaces the original text:
-- Replacing selection (lua/99/ops/over-range.lua)
local new_range = Range.from_marks(top_mark, bottom_mark)
local lines = vim.split(response, "\n")
table.insert(lines, 1, "") -- Maintains line alignment
new_range:replace_text(lines) -- Writes AI output to buffer
Simulating Requests in Tests
You can test the workflow programmatically:
-- In lua/99/test/visual_spec.lua (excerpt)
local range = Range:new(buf, Point.from_0_based(0,0), Point.from_0_based(2,0))
local context = fake_context()
ops.over_range(context, range, {})
-- Expect the buffer to contain the AI-generated content after the call
Summary
- 99's visual selection mode transforms highlighted Neovim code into AI generation requests through the
_99.visualAPI inlua/99/init.lua. - The Range system in
lua/99/geo.luaconverts Neovim's visual marks into structured objects with text replacement capabilities. - Prompt construction combines selection context, file contents, and user input via templates in
lua/99/prompt-settings.luaandlua/99/ops/make-prompt.lua. - Asynchronous handling preserves editor state using temporary marks in
lua/99/ops/marks.luawhile streaming LLM responses throughlua/99/request/init.lua. - Automatic replacement swaps the original selection with generated code using
Range:replace_text()after validating mark integrity.
Frequently Asked Questions
How do I trigger 99's visual selection mode?
Visually select the code you want to modify using Neovim's visual mode (character-wise, line-wise, or block-wise), then execute :lua require('99').visual() or use the default keybinding mapped to <Leader>v. The plugin immediately captures the selection boundaries and initiates the AI request workflow.
What happens to my original code while waiting for AI generation?
The plugin creates temporary marks above and below your selection using Mark.mark_above_range and Mark.mark_point in lua/99/ops/marks.lua to preserve the exact location. While the LLM generates code, your original text remains visible and editable. If you modify the buffer during generation, the plugin validates mark integrity before replacement and aborts if the context has changed significantly.
Can I customize the prompt sent to the LLM in visual selection mode?
Yes, the visual selection prompt template is defined in lua/99/prompt-settings.lua under prompts.visual_selection. This template automatically injects the selection location, content, and file path. You can provide additional context through the opts parameter in _99.visual() or by configuring the global prompt settings to include specific coding standards, language preferences, or agent rules that get combined via make_prompt in lua/99/ops/make-prompt.lua.
Does 99's visual selection mode work with multiple cursors or block selections?
The current implementation in lua/99/geo.lua uses Neovim's '< and '> marks via Range.from_visual_selection(), which captures the last visual selection area. While this supports character-wise (v), line-wise (V), and block-wise (<C-v>) visual modes, it processes the selection as a single continuous range. Multiple cursor support would require extending the Range system to handle disjointed selections, which is not currently implemented in the ops.over_range workflow.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →