How the Range Operation Applies AI Suggestions to Code in 99
The range operation (over-range) in 99 selects a region of code in Neovim, sends it to an LLM with surrounding context and a user prompt, and automatically replaces the selected text with the AI-generated suggestion.
The 99 plugin by ThePrimeagen is an AI coding assistant for Neovim that enables visual-AI workflows. At its core lies the range operation (99.ops.over-range), which bridges the gap between visual selections and LLM-powered code transformations. This operation captures your highlighted code, preserves positional anchors using Neovim extmarks, and orchestrates the entire request lifecycle from prompt construction to text replacement.
High-Level Flow of the Over-Range Operation
The range operation executes through a coordinated pipeline across multiple modules:
- Prepare marks – Creates two extmarks: one above the selection start (
Mark.mark_above_range) and one at the selection end (Mark.mark_point) to survive buffer modifications. - Build the request – Instantiates a
Requestobject with the currentRequestContext. - Assemble the prompt –
make_promptcomposes the system prompt (prompts.visual_selection) and injects the user-providedadditional_prompt, parsing any completion tokens. - Show AI status – Attaches
RequestStatusspinners to the marks, displaying "Implementing …" virtual text during streaming. - Send the request –
Request:startwrites the prompt to a temporary file and invokes the provider (OpenCodeProvider) as a background process. - Handle completion – On finish, the callback verifies mark validity, builds a new
Rangefrom the marks, splits the LLM response into lines, inserts a blank line to preserve line numbering, and callsRange:replace_textto overwrite the original selection. - Clean-up – Removes marks, stops spinners, kills the background process, and deletes temporary files.
Deep Dive into the Implementation
Entry Point and Mark Preparation
The operation begins in lua/99/ops/over-range.lua with the over_range function:
local function over_range(context, range, opts)
opts = opts or {}
local logger = context.logger:set_area("visual")
local request = Request.new(context)
-- Create marks surrounding the visual selection
local top_mark = Mark.mark_above_range(range)
local bottom_mark = Mark.mark_point(range.buffer, range.end_)
context.marks.top_mark = top_mark
context.marks.bottom_mark = bottom_mark
…
The function receives the RequestContext (containing logger, buffer, cwd), the Range derived from the visual selection, and optional user options. The marks are created via lua/99/ops/marks.lua to ensure positional stability during the asynchronous LLM call.
Prompt Construction
The prompt assembly happens in lua/99/ops/make-prompt.lua:
local system_cmd = context._99.prompts.prompts.visual_selection(range)
local prompt, refs = make_prompt(context, system_cmd, opts)
request:add_prompt_content(prompt)
context:add_references(refs)
The visual_selection(range) function generates a system prompt containing the file path, language, and the text inside the selected range. Then make_prompt merges this with the user-supplied additional_prompt (e.g., "Refactor this to use async/await"), parses any @completion directives for context references, and returns the final prompt plus reference snippets.
Real-Time Feedback with Status Spinners
While the LLM processes the request, the user sees live feedback via lua/99/ops/request_status.lua:
local top_status = RequestStatus.new(250, context._99.ai_stdout_rows or 1,
"Implementing", top_mark)
local bottom_status = RequestStatus.new(250, 1, "Implementing", bottom_mark)
local clean_up = make_clean_up(function()
top_status:stop()
bottom_status:stop()
context:clear_marks()
request:cancel()
end)
Two RequestStatus objects attach to the extmarks, displaying animated spinners (e.g., ⠋⠙⠹⠸⠼⠴) with "Implementing" labels. The clean_up closure ensures these stop regardless of success or failure.
Applying the AI Suggestion
When the provider finishes, the callback in over-range.lua handles the replacement using lua/99/geo.lua:
top_status:start()
bottom_status:start()
request:start(make_observer(clean_up, {
on_complete = function(status, response)
if status == "success" then
-- Apply AI suggestion
local new_range = Range.from_marks(top_mark, bottom_mark)
local lines = vim.split(response, "\n")
table.insert(lines, 1, "") -- keep original line number
new_range:replace_text(lines)
end
end,
on_stdout = function(line) -- stream AI output to the spinner
if display_ai_status then top_status:push(line) end
end,
}))
The Range.from_marks reconstructs the selection boundaries from the extmarks. The response is split into lines, prepended with an empty line to compensate for mark_above_range placing the top mark one line above the selection, and finally Range:replace_text overwrites the buffer content.
Resource Cleanup
The lua/99/ops/clean-up.lua module provides make_clean_up, which composes multiple cleanup functions into a single closure. This guarantees that extmarks are deleted, spinners halt, background LLM processes terminate, and temporary prompt files are removed—even if the user cancels the operation or an error occurs.
Practical Usage Example
To use the range operation in your workflow:
" 1. Visually select the code you want the AI to rewrite
" (e.g., a function block)
" 2. Call the over-range operation via the plugin's command:
:lua require("99").ops.over_range(
require("99").context_from_current_buffer(),
require("99").geo.Range.from_visual_selection(),
{ additional_prompt = "Refactor this to use async/await." }
)
What you’ll see:
- A virtual-text spinner appears just above your selection displaying "Implementing …"
- The LLM streams its output line-by-line into the status indicator
- Upon completion, your selected code block is silently replaced with the AI-generated suggestion
- All marks and temporary files are automatically cleaned up
Summary
- The range operation (
over-range) is the core mechanism in 99 for applying AI suggestions to specific code regions in Neovim. - Extmarks (
mark_above_rangeandmark_point) serve as durable anchors that survive asynchronous LLM calls, ensuring precise text replacement. - The prompt pipeline (
make_prompt,visual_selection) merges system context, selected code, and user instructions into a structured LLM request. - Real-time feedback via
RequestStatusspinners provides visual confirmation during streaming generation. - Automatic cleanup guarantees resource disposal regardless of success or cancellation, preventing extmark leakage and orphaned processes.
Frequently Asked Questions
How does the range operation handle line number changes during AI generation?
The operation inserts a blank line at the beginning of the LLM response (table.insert(lines, 1, "")) to compensate for the top extmark being placed one line above the selection via mark_above_range. This ensures that when Range:replace_text executes, the replacement aligns perfectly with the original visual selection boundaries.
What happens if I cancel the AI request mid-generation?
The make_clean_up closure in lua/99/ops/clean-up.lua ensures deterministic teardown. It stops the RequestStatus spinners, clears the extmarks via context:clear_marks(), and cancels the running request with request:cancel(). This prevents UI artifacts and kills the background LLM process even if the operation is interrupted.
Can I customize the system prompt used for visual selections?
Yes. The system prompt is generated by context._99.prompts.prompts.visual_selection(range) in lua/99/ops/make-prompt.lua. You can modify the prompt templates in your 99 configuration to change how file paths, language detection, and surrounding context are presented to the LLM before your additional_prompt is injected.
Which LLM providers work with the range operation?
By default, 99 uses the OpenCodeProvider defined in lua/99/providers.lua, which executes the LLM as a background process. The Request abstraction in lua/99/request/init.lua is provider-agnostic, allowing you to configure alternative providers that implement the same interface for streaming responses and process management.
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 →