How 99 Auto-Detects and Loads Language-Specific Configurations in Neovim

99 determines the language of the current buffer by reading Neovim's filetype option and mapping it to a pre-loaded language module that provides editor-specific helpers like log_item.

ThePrimeagen's 99 plugin for Neovim eliminates manual configuration by automatically detecting which programming language you're editing and loading the appropriate behavior modules. This auto-detection and load language-specific configurations system works through a lookup table built during initialization, requiring zero user intervention when switching between supported filetypes like Lua, Go, Java, or Elixir.

The Architecture Behind 99's Language Detection

The detection mechanism follows a five-step pipeline that bridges Neovim's buffer state with 99's modular language implementations.

Step 1: Defining Supported Languages in State

The core plugin state declares which languages are available. In lua/99/init.lua at line 91, the _99.State table defines a default array:

languages = { "lua", "go", "java", "elixir", "cpp", "ruby" }

This array controls which language modules 99 attempts to load during setup.

Step 2: Building the Language Module Lookup Table

When you call require('99').setup(), the plugin invokes Languages.initialize(_99_state) from lua/99/language/init.lua (lines 36-41). This function iterates over the languages array and dynamically requires each module:

for _, lang in ipairs(state.languages) do
    local ok, mod = pcall(require, "99.language." .. lang)
    if ok then
        M.languages[lang] = mod
    end
end

The resulting modules are stored in M.languages, a table that maps filetype strings to language module objects.

Step 3: Detecting the Current Buffer's Filetype

Whenever 99 needs language-specific behavior, it calls get_langauge(buf) (note: the function name contains a typo in the source as get_langauge). Located in lua/99/language/init.lua at lines 20-22, this function queries Neovim's API:

local function get_langauge(buffer)
    return vim.api.nvim_get_option_value("filetype", { buf = buffer })
end

This retrieves the exact filetype option that Neovim sets automatically when you open a file.

Step 4: Resolving and Loading the Language Module

The detection pipeline completes by indexing into the pre-built lookup table. At lines 22-26 in lua/99/language/init.lua, the code performs:

local file_type = get_langauge(buffer)
local lang = M.languages[file_type]
if not lang then
    -- fatal log: language not supported
end

If the filetype exists in M.languages, the corresponding module is returned. If not, 99 emits a fatal error indicating the language lacks support.

How 99 Uses Language-Specific Helpers

Once the language module is resolved, 99 delegates editor operations to language-specific implementations.

The log_item Function Example

The log_item function demonstrates this delegation pattern. In lua/99/language/init.lua (lines 47-52), the public API function forwards calls to the active language module:

function M.log_item(item_name)
    local lang = get_langauge(vim.api.nvim_get_current_buf())
    local mod = M.languages[lang]
    return mod.log_item(item_name)
end

Each language module implements its own log_item. For example:

Tree-sitter Integration

Language modules also export names tables that map semantic concepts to Tree-sitter node types. The lua/99/editor/treesitter.lua file imports these definitions to construct language-aware queries for operations like "find function" or "find class."

Extending 99 with Custom Language Support

The auto-detection system is fully extensible without modifying core files.

To add Python support, create two artifacts:

  1. Extend the language list in your setup configuration (or fork):
-- In your init.lua or 99 setup
require('99').setup({
    languages = { "lua", "go", "java", "elixir", "cpp", "ruby", "python" }
})
  1. Create the language module at lua/99/language/python.lua:
local M = {}

-- Optional: Tree-sitter node name mappings
M.names = {
    function_declaration = "function_definition",
    class_declaration = "class_definition",
}

-- Required: Generate language-specific log statement
function M.log_item(item_name)
    return string.format('print(f"{%s=}")', item_name)
end

return M

When you open a .py file, Neovim sets filetype=python, and 99 automatically resolves M.languages["python"] to your new module.

Summary

  • 99 reads Neovim's filetype option via vim.api.nvim_get_option_value to determine the current buffer's language.
  • A lookup table maps filetypes to modules built during setup() by iterating over the languages array in _99.State.
  • Language-specific behavior is delegated to modules like lua/99/language/go.lua, which implement functions such as log_item.
  • Extending support requires only adding a string to the languages list and creating a corresponding module file that exports the required interface.

Frequently Asked Questions

How does 99 handle unsupported filetypes?

If the current buffer's filetype is not present in the M.languages lookup table, 99 emits a fatal error message indicating that the language is not supported. The plugin does not fall back to a default language; it requires an exact match between Neovim's filetype value and a key in the language table.

Can I override an existing language module without forking the repository?

Yes. Since 99 uses Lua's require system to load language modules from lua/99/language/<name>.lua, you can use Neovim's runtimepath precedence to shadow the built-in module. Place a file at lua/99/language/go.lua (or any language) in your personal configuration directory (e.g., ~/.config/nvim/lua/99/language/go.lua), and Neovim will load your version instead of the bundled one when 99 initializes.

What functions must a custom language module implement?

At minimum, a language module must export a log_item function that accepts an item_name string and returns a string representing the language-specific logging statement. Optionally, the module can export a names table that maps abstract concepts like function_declaration to Tree-sitter node names used by 99's editor features. Refer to lua/99/language/lua.lua and lua/99/language/go.lua for complete implementation examples.

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 →