How 99 Tracks and Displays In-Flight Request Status with the Throbber

99 uses a self-scheduling Throbber object that polls the internal _99_state.__request_by_id registry every 100 milliseconds to animate a Unicode spinner and render a floating window listing all active requests marked with status = "requesting".

The 99 plugin for Neovim provides real-time visibility into asynchronous operations through its in-flight request throbber. When you enable show_in_flight_requests in your setup configuration, 99 creates a lightweight status window that updates continuously to show exactly which requests are currently executing. This article examines the internal machinery that makes this possible, from state bookkeeping to the animation loop that drives the visual spinner.

State Bookkeeping for Active Requests

99 maintains a centralized registry of all active operations inside _99_state.__request_by_id. Each entry in this table represents a single request context containing a status field. While a request is executing, its status is set to "requesting".

The state module exposes an active_request_count() method that iterates over __request_by_id and returns the number of entries currently marked as in-flight. This count drives the numeric display in the throbber window.

Source: lua/99/init.lua (lines 476-500) – request loop and state inspection.

Throbber Lifecycle and Animation Logic

The visual heartbeat of the feature is the Throbber class defined in lua/99/ops/throbber.lua. This object manages a self-scheduling animation loop that updates the UI without blocking Neovim's main thread.

Creating the Throbber Instance

You instantiate a throbber with Throbber.new(callback, opts). The callback receives a Unicode icon string on every tick. The opts table accepts throb_time (duration of the spinning phase) and cooldown_time (pause between cycles).

Source: lua/99/ops/throbber.lua (lines 15-31) – constructor and initialization.

The Animation Loop and Timing

The internal _run routine computes a progress percentage based on elapsed time and selects an icon from a randomly chosen icon set. The throbber alternates between an active "throbbing" phase (spinning) and a short cooldown pause.

Scheduling relies on vim.defer_fn with a 100 ms tick interval, ensuring the spinner updates ten times per second without manual timer management.

Source: lua/99/ops/throbber.lua (lines 95-115) – run loop and timer scheduling.

Rendering the In-Flight Status Window

When show_in_flight_requests is enabled, 99 creates a dedicated status window. The throbber's callback function assembles the content displayed inside this window.

The callback constructs an array of lines:

  • Line 1: The current spinner icon, the total count of in-flight requests (from active_request_count()), and the icon repeated for visual balance.
  • Subsequent lines: The operation string of each request whose status == "requesting", providing human-readable context for what is currently executing.

After assembling the lines, the callback resizes the floating window and writes the content to the buffer using vim.api.nvim_buf_set_lines.

Source: lua/99/init.lua (lines 505-531) – window creation and callback implementation.

Resource Cleanup and Window Teardown

To prevent resource leaks, 99 implements shut_down_in_flight_requests_window. This helper triggers when the active request count reaches zero or when the window becomes invalid.

The cleanup routine calls throb:stop() to halt the deferred timer, then closes the floating window and nils the buffer reference. This ensures the 100 ms animation loop terminates and memory is reclaimed.

Source: lua/99/init.lua (lines 776-786) – cleanup logic and throbber shutdown.

Configuration and Usage Examples

Enable the in-flight request throbber in your Neovim configuration by setting show_in_flight_requests to true during setup.

-- Enable the in-flight request throbber in your Neovim config
require("99").setup{
  show_in_flight_requests = true,   -- turn the throbber on
  -- other 99 options …
}

When you initiate a request, it automatically appears in the throbber window.

-- Manually start a request and see it appear in the throbber window
local request = require("99.request").new(function(ctx)
  ctx:run("git status")   -- the operation string shown by the throbber
end)

request:run()   -- while this runs, the throbber window updates

For advanced use cases, you can instantiate a standalone throbber to drive custom UI elements.

-- Custom throbber example (rarely needed)
local Throbber = require("99.ops.throbber")
local my_throb = Throbber.new(function(icon)
  -- Write the icon somewhere custom, e.g. statusline
  vim.api.nvim_set_var("my_throb_icon", icon)
end, { throb_time = 800, cooldown_time = 150 })

my_throb:start()
-- … later …
my_throb:stop()

Summary

  • State tracking: 99 stores active requests in _99_state.__request_by_id with a status field set to "requesting" during execution, exposing active_request_count() for UI updates.
  • Animation engine: The Throbber class in lua/99/ops/throbber.lua uses vim.defer_fn with a 100 ms interval to cycle through Unicode icons while alternating between throbbing and cooldown phases.
  • Window rendering: The throbber callback assembles lines showing the spinner icon, total request count, and individual operation strings, then writes them to a floating buffer via vim.api.nvim_buf_set_lines.
  • Resource management: shut_down_in_flight_requests_window stops the throbber timer and closes the window when requests complete, preventing memory leaks and orphaned timers.

Frequently Asked Questions

How does the throbber impact Neovim's performance?

The throbber uses vim.defer_fn with a 100 millisecond tick interval, which is sufficiently coarse to avoid consuming significant CPU cycles. Because the callback only updates a small floating window and iterates over the active request table, the overhead is negligible even with multiple concurrent requests.

Can I customize the throbber icons or animation timing?

Yes. When creating a throbber via Throbber.new(callback, opts), you can pass an options table specifying throb_time (duration of the spinning phase in milliseconds) and cooldown_time (pause duration). While the built-in throbber uses predefined Unicode icon sets selected randomly, you can implement a custom callback to render any character or symbol you prefer.

What is the difference between the throbber and request_status.lua?

The throbber (lua/99/ops/throbber.lua) manages a global floating window that aggregates all in-flight requests and displays a spinning animation alongside the total count. In contrast, request_status.lua provides localized virtual text spinners that attach to individual buffer lines, showing the status of a specific request directly in the editor margin. The throbber is ideal for a dashboard overview, while request_status.lua offers granular, inline feedback.

How do I disable the in-flight request window after enabling it?

Set show_in_flight_requests = false in your require("99").setup() call and restart Neovim. If you need to disable it programmatically at runtime, you can call the internal cleanup helper shut_down_in_flight_requests_window(), which stops the throbber timer and closes the floating window immediately.

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 →