How Visual Mode Uses Treesitter for Code Selection and Transformation in ThePrimeagen/99

Visual mode in ThePrimeagen/99 primarily relies on Neovim's native visual marks for text selection, while Treesitter provides semantic context for function-level transformations and node-based marking operations.

ThePrimeagen/99 is a Neovim plugin that bridges visual selection with AI-powered code generation. Understanding how visual mode uses Treesitter for code selection and transformation requires distinguishing between the raw text extraction mechanism—which uses Vim's built-in APIs—and the semantic layer that Treesitter provides for intelligent code manipulation.

Visual Mode Core: Selection Without Treesitter

The fundamental visual mode workflow in 99 does not use Treesitter for the initial selection. Instead, it leverages Neovim's visual mark API to capture exact byte-wise positions.

When you invoke :99.visual() or the mapped key, the flow begins in lua/99/init.lua:

-- From lua/99/init.lua
set_selection_marks()  -- Sends <Esc> to Neovim to escape visual mode

This ensures a clean state before building the range object. The actual selection coordinates come from the '< and '> marks, processed in lua/99/geo.lua:

-- From lua/99/geo.lua
local range = Range.from_visual_selection()

This method reads the visual marks, corrects Vim's line-wise selection quirks (such as handling empty lines or maximum integer column values), and returns a Range object containing start/end Points and the buffer number.

Treesitter's Role in Semantic Code Operations

While the basic visual mode workflow operates on raw text ranges, Treesitter integration enables semantic code selection and transformation. The plugin uses Treesitter to understand code structure beyond simple character positions.

Detecting Surrounding Functions

In lua/99/editor/treesitter.lua, the M.containing_function method locates the function node surrounding the cursor or selection:

-- From lua/99/editor/treesitter.lua
local func_node = ts.containing_function(context, cursor_point)

This enables operations that treat the visual selection as a function boundary rather than arbitrary text.

Locating Function Calls

The fn_call method in the same file identifies function call nodes, supporting features like "replace call with AI-generated implementation":

-- Treesitter-based call detection
local call_node = ts.fn_call(context, point)

Node-Based Marking System

Treesitter nodes serve as anchors for extmarks that persist through buffer modifications. In lua/99/ops/marks.lua, methods like mark_above_func and mark_func_body create marks tied to specific Treesitter nodes:

-- From lua/99/ops/marks.lua
local top_mark = Mark.mark_above_func(func_node)
local body_mark = Mark.mark_func_body(func_node)

These marks enable the visual range replacement to track regions even if the buffer changes before the AI response arrives.

The Complete Visual Mode Execution Flow

Understanding how visual mode uses Treesitter for code selection and transformation requires examining the full execution path from keypress to text replacement.

Step 1: Range Construction

The process begins in lua/99/init.lua with set_selection_marks(), which escapes visual mode and preserves the selection boundaries. The Range.from_visual_selection() method in lua/99/geo.lua then constructs a proper Range object from the '< and '> marks, handling edge cases like line-wise selections and empty lines.

Step 2: Over-Range Operation

The core logic resides in lua/99/ops/over-range.lua. The ops.over_range(context, range, opts) function:

  1. Formats a visual-selection prompt using definitions from lua/99/prompt-settings.lua
  2. Creates extmarks via ops.marks.mark_above_range and ops.marks.mark_point to anchor the original selection
  3. Sends the request to the AI backend

Step 3: Text Replacement

When the AI response arrives, the code calls range:replace_text(lines) implemented in lua/99/geo.lua. This method uses Neovim's nvim_buf_set_text to splice the new text into the buffer at the exact coordinates captured from the visual selection.

Treesitter Integration Points

While the basic flow operates on raw ranges, Treesitter-based helpers remain available for extensions. The containing_function and fn_call methods in lua/99/editor/treesitter.lua enable downstream operations that treat visual selections as semantic units (functions, calls) rather than arbitrary text blocks.

Extending Visual Selection with Treesitter

For workflows requiring semantic boundaries, you can combine visual mode with Treesitter to perform function-level transformations.

Replacing a Function Body

local ts = require("99.editor.treesitter")
local ops = require("99.ops.over-range")
local context = require("99.request-context").from_current_buffer(state, trace_id)

-- Get cursor position
local cursor_pt = Point:from_1_based(vim.fn.line("."), vim.fn.col("."))

-- Treesitter finds the containing function
local func_node = ts.containing_function(context, cursor_pt)
local func = ts.Function.from_ts_node(func_node, cursor_pt, context)

-- Use the function body range for AI transformation
ops.over_range(context, func.body_range, {})

In this pattern, Treesitter supplies the semantic boundaries (func.body_range), while the visual-mode core (ops.over_range) handles the AI interaction and text replacement.

Validating Selections with Marks

The mark system ensures selection integrity during async operations:

local Mark = require("99.ops.marks")

-- Create marks around the current range
local top_mark = Mark.mark_above_range(range)
local bottom_mark = Mark.mark_point(range.buffer, range.end_)

-- After AI response:
if top_mark:is_valid() and bottom_mark:is_valid() then
  local new_range = Range.from_marks(top_mark, bottom_mark)
  new_range:replace_text(ai_lines)
else
  logger:fatal("Original visual selection was destroyed")
end

This validation prevents errors when the buffer changes before the AI returns a response.

Summary

  • Visual mode selection in ThePrimeagen/99 relies on Neovim's native '< and '> marks via Range.from_visual_selection() in lua/99/geo.lua, not Treesitter parsing.
  • Treesitter integration provides semantic context through lua/99/editor/treesitter.lua, enabling function detection (containing_function), call identification (fn_call), and node-based marking.
  • The execution flow spans lua/99/init.lua (entry point), lua/99/ops/over-range.lua (core operator), and lua/99/geo.lua (text replacement), using extmarks for selection validation.
  • Extension pattern: Combine Treesitter's semantic boundaries with visual mode's replacement engine for function-level transformations.

Frequently Asked Questions

Does visual mode in 99 require Treesitter to work?

No. The basic visual mode workflow operates entirely through Neovim's visual mark API ('< and '>). Treesitter is optional and only required when you need semantic code understanding, such as selecting entire functions or identifying specific code constructs beyond raw text positions.

How does 99 handle visual selection boundaries during async AI operations?

The plugin uses Neovim's extmark system via lua/99/ops/marks.lua. When you trigger a visual replacement, the code creates marks at the selection boundaries using mark_above_range and mark_point. After the AI response arrives, it validates these marks with is_valid() before replacing text, ensuring the original selection hasn't been destroyed by concurrent edits.

Can I use Treesitter to select a function body and then apply 99's visual mode transformation?

Yes. You can combine Treesitter's semantic analysis with the visual mode replacement engine. Use lua/99/editor/treesitter.lua's containing_function to locate the function node, convert it to a Range via Function.from_ts_node, then pass that range to ops.over_range in lua/99/ops/over-range.lua for AI transformation.

What is the difference between Range.from_visual_selection and Treesitter-based ranges?

Range.from_visual_selection in lua/99/geo.lua creates a range from Vim's visual marks ('<, '>), representing exactly what the user highlighted regardless of code structure. Treesitter-based ranges (from lua/99/editor/treesitter.lua) represent semantic code boundaries like function bodies or call expressions, independent of the user's visual selection. The former handles raw text replacement; the latter enables semantic refactoring.

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 →