# How Request Status Tracking Works Internally in ThePrimeagen's 99 Plugin

> Discover how ThePrimeagen's 99 plugin tracks request status internally using defer loops, spinners, and virtual text. Learn about its flexible output options.

- Repository: [ThePrimeagen/99](https://github.com/theprimeagen/99)
- Tags: internals
- Published: 2026-02-16

---

**ThePrimeagen's 99 plugin implements request status tracking through the `RequestStatus` class in [`lua/99/ops/request_status.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/request_status.lua), which uses a self-rescheduling `vim.defer_fn` loop to update a Braille-character spinner, manage a rolling buffer of status messages, and render output either as Neovim virtual text via marks or through a user-provided callback.**

The 99 plugin by ThePrimeagen provides live feedback during asynchronous operations like LSP requests. At the heart of this functionality lies a sophisticated **request status tracking** system that combines visual spinners, message buffering, and Neovim's virtual text API. Understanding this implementation reveals how to build non-blocking status indicators in Lua-based Neovim plugins.

## Core Architecture of the RequestStatus Class

The `RequestStatus` class defined in [`lua/99/ops/request_status.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/request_status.lua) serves as the central coordinator for all status tracking operations. It aggregates three distinct subsystems to create a cohesive user experience.

### The Three Core Components

Every `RequestStatus` instance combines these pieces:

- **`StatusLine`** – A spinning Braille-character indicator that advances on each tick to provide visual feedback that work is in progress.
- **`lines` buffer** – A Lua table holding the most recent status messages, capped by the `max_lines` parameter to prevent unbounded growth.
- **`mark` or `callback`** – Either a `Mark` object from [`lua/99/ops/marks.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/marks.lua) that updates Neovim virtual text, or a user-provided function that receives the status lines array.

### Constructor: Initializing the Tracker

The `RequestStatus.new()` function (lines 46-58 of [`request_status.lua`](https://github.com/ThePrimeagen/99/blob/main/request_status.lua)) sets up the initial state:

```lua
function RequestStatus.new(update_time, max_lines, title_line, mark_or_fn)
  local self = setmetatable({}, RequestStatus)
  self.update_time = update_time                      -- ms between spinner updates
  self.max_lines   = max_lines
  self.status_line = StatusLine.new(title_line)      -- spinner + title
  self.lines       = {}
  self.running     = false
  if type(mark_or_fn) == "function" then
    self.callback = mark_or_fn                       -- user-supplied fn(status_lines)
  else
    self.mark = mark_or_fn                            -- a 99.ops.marks.Mark object
  end
  return self
end

```

The constructor distinguishes between callback-based and mark-based rendering by checking the type of the fourth argument.

## The Status Update Loop

The heart of the request status tracking system is the self-rescheduling timer loop that updates the visual state without blocking Neovim's main thread.

### Starting the Spinner with vim.defer_fn

The `RequestStatus:start()` method (lines 78-96) initiates the update cycle:

```lua
function RequestStatus:start()
  local function update_spinner()
    if not self.running then return end

    self.status_line:update()                     -- advance spinner index
    if self.mark then
      self.mark:set_virtual_text(self:get())      -- update virtual text
    end
    if self.callback then
      self.callback(self:get())                   -- invoke user callback
    end
    vim.defer_fn(update_spinner, self.update_time) -- schedule next tick
  end

  self.running = true
  vim.defer_fn(update_spinner, self.update_time)   -- first tick
end

```

This implementation uses `vim.defer_fn` to schedule the next iteration, creating a non-blocking loop that terminates automatically when `self.running` becomes `false`.

### Rendering the Status Output

The `RequestStatus:get()` method (lines 61-67) assembles the display array:

```lua
function RequestStatus:get()
  local result = { self.status_line:to_string() }   -- spinner + title
  for _, line in ipairs(self.lines) do
    table.insert(result, line)                     -- recent status lines
  end
  return result
end

```

The `StatusLine:to_string()` method (lines 24-28 of the same file) renders the current Braille character from the `braille_chars` table combined with the title text.

### Pushing New Status Messages

To add messages to the rolling buffer, the `RequestStatus:push()` method (lines 70-75) maintains the size constraint:

```lua
function RequestStatus:push(line)
  table.insert(self.lines, line)
  if #self.lines > self.max_lines - 1 then         -- keep only `max_lines-1` messages
    table.remove(self.lines, 1)                    -- drop the oldest
  end
end

```

This ensures the status display never exceeds the configured `max_lines` limit, preventing UI overflow during long-running operations.

## Integration with Neovim's Virtual Text

The request status tracking system offers two output mechanisms: direct virtual text attachment via the `Mark` class, or custom handling through user callbacks.

### Using Marks for Buffer Attachment

When initialized with a `Mark` object (from [`lua/99/ops/marks.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/marks.lua)), the `RequestStatus` instance updates virtual text directly on a buffer line. The `Mark:set_virtual_text()` method receives the string array from `RequestStatus:get()` and renders it as ephemeral text attached to the buffer coordinates defined during mark creation.

This approach is ideal for showing status inline with code, such as displaying "⠹ Running LSP request" on the line where the request originated.

### Callback-Based Custom Rendering

Alternatively, passing a function as the fourth argument to `RequestStatus.new()` enables callback-driven updates:

```lua
local function ui_update(lines)
  -- `lines` is a table like { "⠹ TITLE", "step 1", "step 2" }
  vim.api.nvim_echo({ { table.concat(lines, "\n") } }, false, {})
end

local status = RequestStatus.new(200, 4, "Deploying", ui_update)

```

This decouples the status tracker from Neovim's virtual text system, allowing integration with external UI plugins, logging systems, or remote monitoring tools.

## Practical Implementation Examples

### Example 1: Show a Spinner in the Current Buffer

This example demonstrates attaching a status tracker to a specific buffer line using the `Mark` class:

```lua
local RequestStatus = require("99.ops.request_status")
local Mark          = require("99.ops.marks")
local test_utils    = require("99.test.test_utils")   -- helper to obtain a buffer

local buf = test_utils.create_file({ "function foo() end" }, "lua", 1, 1)
local point = require("99.geo").Point:from_1_based(1, 1)
local mark = Mark.mark_point(buf, point)

-- Show a spinner that updates every 150 ms and keeps up to 5 lines
local status = RequestStatus.new(150, 5, "Running LSP request", mark)
status:start()

-- Append status messages as the request progresses
status:push("Sending query…")
status:push("Awaiting response…")
-- …
-- When done:
status:stop()

```

### Example 2: Feed Status to a Custom UI via Callback

For scenarios requiring custom display logic, use the callback interface:

```lua
local RequestStatus = require("99.ops.request_status")

local function ui_update(lines)
  -- `lines` is a table like { "⠹ TITLE", "step 1", "step 2" }
  vim.api.nvim_echo({ { table.concat(lines, "\n") } }, false, {})
end

local status = RequestStatus.new(200, 4, "Deploying", ui_update)
status:start()

-- Simulate progress
status:push("Building")
vim.wait(500)               -- wait a bit
status:push("Uploading")
vim.wait(500)
status:stop()

```

## Summary

- The **request status tracking** system centers on the `RequestStatus` class in [`lua/99/ops/request_status.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/request_status.lua), which orchestrates visual feedback for asynchronous operations.
- It combines a **Braille-character spinner** (`StatusLine`), a **rolling message buffer** (`lines`), and either a **virtual text mark** or **user callback** for output.
- The update mechanism relies on `vim.defer_fn` to create a non-blocking loop that refreshes the display every `update_time` milliseconds until `stop()` sets `running` to false.
- Message history is automatically capped using `max_lines`, ensuring the UI remains concise during long-running tasks.

## Frequently Asked Questions

### How does the RequestStatus class update the spinner animation without blocking Neovim?

The class uses `vim.defer_fn` to schedule asynchronous updates. When `start()` is called, it initiates a recursive function that updates the spinner index, renders the current state, and then reschedules itself after `update_time` milliseconds. This creates a cooperative multitasking loop that yields control back to Neovim between ticks, preventing UI freezing.

### What is the difference between using a Mark and a callback in RequestStatus?

When initialized with a **Mark** object (from [`lua/99/ops/marks.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/marks.lua)), `RequestStatus` calls `mark:set_virtual_text()` on every tick to display status directly on a specific buffer line as ephemeral virtual text. When initialized with a **callback function**, the class passes the status line array to that function instead, allowing developers to route the data to custom UI components, log files, or external monitoring systems.

### How does the status message buffer prevent memory leaks during long operations?

The `RequestStatus:push()` method enforces a hard limit on stored messages through the `max_lines` parameter set during construction. When new lines are added via `table.insert()`, the code checks if the buffer exceeds `max_lines - 1` entries. If so, it immediately removes the oldest entry with `table.remove(self.lines, 1)`, ensuring the memory footprint remains constant regardless of operation duration.

### Where is the Braille spinner character sequence defined and how is it advanced?

The Braille spinner sequence is managed by the `StatusLine` class instantiated within `RequestStatus`. The `StatusLine:update()` method advances an internal index that cycles through a table of Braille characters (defined as `braille_chars`). On each tick of the main defer loop, `self.status_line:update()` is called to advance the spinner, and `self.status_line:to_string()` renders the current Braille character concatenated with the title line provided during construction.