codedocs.nvim
July 13, 2026 · View on GitHub

Table of contents
- Features
- Requirements
- Installation
- Usage
- Configuration
- Language support
- Roadmap
- Contributing
- Motivation
- Comparison with alternatives
- Support
Features
- Autodetects functions, classes, or other code targets under the cursor and generates the appropriate annotation.
- Generates annotations beyond code, including in markup and other text-based filetypes
- Supports multiple languages and styles.
- Easily customize existing annotations or add new ones.
- All built-in annotations use Neovim's built-in snippet engine.
- Works out-of-the-box.
Requirements
No requirements are needed if you’re only using annotations unrelated to code (such as comments) and aren’t using the autodetection feature.
To use the full feature set, Codedocs relies on Treesitter for its core functionality. Neovim includes built-in Treesitter parsers for the following languages, so no additional setup is required:
- Lua
- C
- Markdown
For any other language, you'll need to install the corresponding Treesitter parser. The simplest way to do this is with nvim-treesitter.
Installation
Warning
Development is done using Trunk Based
Development, meaning all new changes are merged into main,
making it the equivalent of nightly; versions are done through Git tags.
All changes are thoroughly tested before merging, but it is still recommended to pin to a specific tag unless you wish to be constantly up to date.
Use the snippet for your plugin manager:
vim.pack
vim.pack.add({
{ src = "https://github.com/jeangiraldoo/codedocs.nvim" }
})
lazy.nvim
{
"jeangiraldoo/codedocs.nvim",
}
packer.nvim
use {
"jeangiraldoo/codedocs.nvim",
}
mini.deps
require("mini.deps").add({
source = "jeangiraldoo/codedocs.nvim",
})
Usage
Generate an annotation
An annotation insertion can be triggered using the :Codedocs command. There
are two ways to use the command:
-
Without arguments: The plugin attempts to detect the code target under the cursor, determines the default style for the current file’s language, and applies the corresponding annotation. If no target is recognized under the cursor, an inline comment is inserted. By default, a matching annotation exists for each target unless you’ve customized the configuration.
-
With an annotation name: You can pass the name of any annotation definition defined in the language’s default style. The plugin will generate and insert the annotation using that definition.
For a more convenient experience, you can bind the command to a keymap. For example:
vim.keymap.set(
"n", "<leader>k", "<cmd>Codedocs<CR>",
{ desc = "Insert annotation" }
)
Delete an annotation
Although it is not actually a Codedocs feature but a Neovim one, you should know
that any comment can be deleted by placing your cursor on it and pressing dgc.
Lua API
Tip
You can check the function signatures using a LSP such as LuaLS
A Lua API is provided in case you find any of the already existing functionality
useful. The API can be accessed by requiring the codedocs module:
local Codedocs = require("codedocs")
The following functions are available:
generate: Triggers an annotation generation (it's what:Codedocsuses under the hood)
Configuration
All options can be customized using the setup function. Here are most of the default options:
require("codedocs").setup {
logging = {
level = vim.log.levels.INFO,
path = (vim.fn.stdpath "log") .. "/codedocs.log",
},
languages = {
--- The `languages` table is too big to be displayed here.
--- See the `languages` configuration section
},
annot_builder = { -- Options for the component that builds annotations
state = {
lines = {}, -- Starter annotation lines
snip_idx_counter = 1, -- Counter for the `snip_idx` placeholder
},
placeholders = { -- Placeholders for the annotation `layout` option
general = { -- Placeholders available to any `layout` line
["%%>"] = function(_)
-- One level of indentation based on the Neovim settings
end,
["%%snip_idx"] = function(state)
-- Tabstop index for Neovim snippets
-- Counter is increased by 1 after each use
end,
},
items = { -- Item-exclusive placeholders
["%%item_name"] = function(_, item)
-- Item name
end,
["%%item_type"] = function(_, item)
-- Item type
end,
},
},
},
}
Languages
All language definitions follow the same structure, so only the python
configuration is shown below. Configurations for other languages use the same
fields with different values. See language support.
require("codedocs").setup {
languages = {
python = {
filetypes = { -- Neovim filetypes associated with this language
"python",
},
default_style = "reST", -- Default style used when generating annotations
-- Annotation style definitions
styles = {
-- Style name
reST = {
default_annot = "comment",
annots = { --- See the `annotations` section below
func = {}, -- `func` annotation
class = {}, -- `class` annotation
comment = {} -- `comment` annotation
}
},
},
targets = {
--- See the `targets` section below
},
},
},
}
Targets
A target is a code structure, such as a function or class, we extract units of
data called items from.
Ultimately, an item is a table with a name and type fields.
For example, the following function:
def foo(a: int, b: string) -> int:
...
has the following items:
{
parameters = { -- List of parameter items
{
name = "a",
type = "int"
},
{
name = "b",
type = "string"
},
},
returns = {
{
name = "",
type = "int"
}
}
}
Since the plugin relies on Treesitter for extraction, a target is identified by one or more Treesitter node types.
This is what the targets field looks like for python:
require("codedocs").setup {
languages = {
python = {
targets = {
--- See the targets section below
func = {
detection = {
type = "treesitter", -- Detection type to use. By default only "treesitter", you can define your own types
node_identifiers = { -- Tree-sitter node types recognized as functions
"function_definition",
} },
-- Functions responsible for extracting items from the target
extractors = {
parameters = function()
-- ...
end,
returns = function()
-- ...
end
},
},
}
}
}
}
When the target is processed the result is a table where each key corresponds to
one of the keys under extractors, and the values are the list of items
returned by each function.
Annotations
Important
Overriding a list of blocks replaces the default list entirely.
To modify a single block, see Customizing blocks.
Annotations are defined as a table with specific options.
| Option Name | Type | Behavior |
|---|---|---|
placement | string | What line to place the annotation relative to the cursor |
blocks | list | List of blocks forming the annotation |
Blocks are the fundamental building blocks of an annotation. Each block defines how a portion of the annotation is rendered, including its layout, formatting, and any associated behavior.
There are two types of blocks: general blocks and item blocks. Item blocks are an extension of general blocks, adding a small set of item-specific features.
General blocks
All blocks support the following options:
| Option Name | Type | Description |
|---|---|---|
name | string | Block identifier used by other options |
layout | string[] | Block lines. Supports layout placeholders |
gap_before | { [block_name] = { enabled = boolean, text = string } } | Maps block name values to the gap inserted before the matching block. |
items | table? | List of item blocks |
The layout option supports the following placeholders:
%snip_idx: Tabstop index for Neovim snippets. The counter is increased by 1 after each use%%>: One level of indentation based on your Neovim settings
Each key in gap_before is a block name, and its value supports the following
suboptions:
| Name | Expected Value Type | Behavior |
|---|---|---|
enabled | boolean | Whether the gap is inserted before the named block |
text | string | The string used as the gap |
For example, to insert a gap before the parameters block:
gap_before = {
parameters = {
enabled = true,
text = " *",
},
},
Item blocks
Item blocks are defined in a list under the items key of a general block.
When items are extracted from a target, they are grouped by name. For an item block to access those items, the group name for said items must be the same as the name of the block.
For example, given a func target with parameters and returns item groups,
a block named "parameters" will automatically have access to the parameter
items and can define how they render via the layout option, with support for
placeholders.
When used in an item block, the layout option supports the following
additional placeholders, in addition to the general placeholders:
%item_name: The item's name.%item_type: The item's type.
Lastly, item blocks have the following exclusive options:
| Name | Expected Value Type | Behavior |
|---|---|---|
insert_gap_between | {text: string, enabled = boolean} | Whether to insert a gap between items |
Customizing blocks
Because blocks replaces the entire default list, modifying a single block
requires copying the original list first.
local config_opts = require("codedocs.config").opts
local original_blocks = config_opts.languages.python.styles.Google.func.blocks
local new_blocks = vim.deepcopy(original_blocks)
-- Enable gaps between parameter items
new_blocks[2].items.insert_gap_between.enabled = true
require("codedocs").setup({
languages = {
python = {
styles = {
Google = {
func = {
blocks = new_blocks,
},
},
},
},
},
})
Printing the copied list with vim.inspect() can help identify which block you
want to modify.
print(vim.inspect(new_blocks))
Annotation example
The following is the func annotation for the reST style in python:
require("codedocs").setup {
languages = {
python = {
styles = {
reST = {
func = {
placement = "below",
blocks = {
{
name = "header",
layout = {
'%>"""',
"%>${%snip_idx:title}",
},
gap_before = {
parameters = {
enabled = true,
text = "",
},
returns = {
enabled = true,
text = "",
},
},
},
{
name = "parameters",
gap_before = {
returns = {
enabled = false,
text = "",
},
},
items = {
{
name = "parameters",
layout = {
"%>:param %item_name: ${%snip_idx:description}",
"%>:type %item_name: ${%snip_idx:%item_type}",
},
insert_gap_between = {
enabled = false,
text = "",
},
gap_before = {},
},
},
},
{
name = "returns",
gap_before = {
footer = {
enabled = false,
text = "",
},
},
items = {
{
name = "returns",
layout = {
"%>:return: ${%snip_idx:description}",
"%>:rtype: ${%snip_idx:%item_type}",
},
insert_gap_between = {
enabled = false,
text = "",
},
gap_before = {},
},
},
},
{
name = "footer",
layout = {
'%>"""',
},
},
},
},
}
}
}
}
}
Language support
Note
The Codedocs style is not an official style. It exists to provide
annotations for languages without native styles or to offer a custom
alternative.
Global annotations
The following annotations are available in all languages and styles:
comment
Language-specific annotations
| Language | Styles (* = default) | Annotations |
|---|---|---|
| bash | func, shebang | |
| c | *Doxygen | func |
| cpp | *Doxygen | func |
| go | *Godoc | func |
| gdscript | *Codedocs | export, onready, warning_ignore |
| html | *Codedocs | Only global annotations |
| javascript | *JSDoc | func, class |
| java | *JavaDoc | func, class |
| kotlin | *KDoc | func, class |
| lua | *EmmyLua, LDoc | func |
| markdown | *Codedocs | Only global annotations |
| python | Google, NumPy, *reST | func, class |
| php | *PHPDoc | func, phptag |
| ruby | *YARD | func |
| rust | *RustDoc | func |
| sql | *Codedocs | Only global annotations |
| typescript | *TSDoc | func, class |
| toml | *Codedocs | Only global annotations |
| treesitter | *Codedocs | Only global annotations |
Roadmap
You can see what's being worked on and which features are planned by checking the GitHub Milestones.
Contributing
Thank you for your interest in contributing to Codedocs! Feel free to read the Contribution guide to get started.
Motivation
I started working on Codedocs because I wanted to enhance my experience with Neovim, which I started using daily for my side projects and university assignments. I wanted a tool to make documenting my code easier and to contribute something useful to the community! :D
While I found a few plugins with similar functionality, none of them offered the level of customization and simplicity I was looking for. Sometimes, I feel that apps and plugins could be more intuitive and user-friendly while still providing the same powerful features.
Comparison with alternatives
| Project | Editor | Written in | Treesitter Usage |
|---|---|---|---|
| Codedocs | Neovim | Lua | Optional (only required for code annotations) |
| Neogen | Neovim | Lua | Required |
| vim-doge | Neovim / Vim | Rust, Vimscript | Required |
All of the aforementioned plugins fulfill the same core goal: annotating code structures. The main differences are regarding how the plugins are configured, and functional differences such as:
- Codedocs goes beyond annotating code as you can create annotations that aren't tied to some code structure (like a function or class), which is why Treesitter parsers aren't an absolute requirement.
- Since Codedocs goes beyond annotating code structures, it supports more languages and annotations per language.
The other projects likely support features that Codedocs doesn't, and viceversa, so it's still a good idea to check them out as they are great projects too!
Support
You can support me by starring ⭐ the plugin and by donating. It motivates me to spend more time improving Codedocs and working on new open-source projects!