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_range and Mark.mark_point to 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 in lua/99/prompt-settings.lua. This template embeds the range location, current selection text, and full file contents.
  • Request Initialization: Creates a Request object via make_prompt (in lua/99/ops/make-prompt.lua) that handles completions, agent rules, and streams the AI output while displaying live status updates through RequestStatus.

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:

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.visual API in lua/99/init.lua.
  • The Range system in lua/99/geo.lua converts 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.lua and lua/99/ops/make-prompt.lua.
  • Asynchronous handling preserves editor state using temporary marks in lua/99/ops/marks.lua while streaming LLM responses through lua/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →