Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7eac4cc
feat(nodes): Add linkable arguments with wrapper and headerlink
tony Jan 25, 2026
5c7f1d0
feat(renderer): Thread id_prefix to argument nodes
tony Jan 25, 2026
4a942cd
style(css): Add argument wrapper and headerlink styles
tony Jan 25, 2026
c9ec5ac
test(nodes): Add tests for ID generation and HTML rendering
tony Jan 25, 2026
6be7fcc
style(css): Add light mode overrides for argument backgrounds
tony Jan 25, 2026
40eddab
style(css): Add width fit-content to argument wrapper
tony Jan 25, 2026
0116210
style(css): Refine argument name styling with monokai background
tony Jan 25, 2026
e7706ef
style(css): Add light mode headerlink hover color
tony Jan 25, 2026
3eae0dd
style(css): Style default values as inline code in argument meta
tony Jan 25, 2026
9a3feb6
style(css): Use custom --argparse-code-background variable
tony Jan 25, 2026
fe4a03d
style(css): Adjust argument styling and fix mypy warnings
tony Jan 25, 2026
24a86d3
style(css): Show headerlink when argument is targeted via URL fragment
tony Jan 25, 2026
358b3e8
style(css): Consolidate duplicate selectors and use CSS variable
tony Jan 25, 2026
d89198d
style(css): Position headerlink outside argument name element
tony Jan 25, 2026
f5c8591
style(css): Remove unnecessary width: fit-content from wrapper
tony Jan 25, 2026
4a7cbb0
style(css): Reduce default value badge size with smaller font and pad…
tony Jan 25, 2026
c97be8a
style(css): Remove italic from default value badge
tony Jan 25, 2026
1802705
style(css): Consolidate .cli-command and .cli-choice green color
tony Jan 25, 2026
2133a0d
style(css): Consolidate light mode headerlink selectors with nesting
tony Jan 25, 2026
643e215
style(css): Format with biome (tabs, lowercase hex, leading zeros)
tony Jan 25, 2026
4f22db0
docs(CHANGES): Add user-focused notes for linkable CLI arguments (#1010)
tony Jan 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force
_Notes on the upcoming release will go here._
<!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE -->

### Documentation

#### Linkable CLI arguments and options (#1010)

CLI documentation now supports direct linking to specific arguments:

- **Linkable options**: Each `--option` and positional argument has a permanent URL anchor (e.g., `cli/load.html#-d`)
- **Headerlinks**: Hover over any argument to reveal a ¶ link for easy sharing
- **Visual styling**: Argument names displayed with syntax-highlighted backgrounds for better readability
- **Default value badges**: Default values shown as styled inline code (e.g., `auto`)

## tmuxp 1.64.0 (2026-01-24)

### Documentation
Expand Down
70 changes: 68 additions & 2 deletions docs/_ext/sphinx_argparse_neo/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,48 @@
from sphinx_argparse_neo.utils import strip_ansi # noqa: E402


def _generate_argument_id(names: list[str], id_prefix: str = "") -> str:
"""Generate unique ID for an argument based on its names.

Creates a slug-style ID suitable for HTML anchors by:
1. Stripping leading dashes from option names
2. Joining multiple names with hyphens
3. Prepending optional prefix for namespace isolation

Parameters
----------
names : list[str]
List of argument names (e.g., ["-L", "--socket-name"]).
id_prefix : str
Optional prefix for uniqueness (e.g., "shell" -> "shell-L-socket-name").

Returns
-------
str
A slug-style ID suitable for HTML anchors.

Examples
--------
>>> _generate_argument_id(["-L"])
'L'
>>> _generate_argument_id(["--help"])
'help'
>>> _generate_argument_id(["-v", "--verbose"])
'v-verbose'
>>> _generate_argument_id(["-L"], "shell")
'shell-L'
>>> _generate_argument_id(["filename"])
'filename'
>>> _generate_argument_id([])
''
"""
clean_names = [name.lstrip("-") for name in names if name.lstrip("-")]
if not clean_names:
return ""
name_part = "-".join(clean_names)
return f"{id_prefix}-{name_part}" if id_prefix else name_part


def _token_to_css_class(token_type: t.Any) -> str:
"""Map a Pygments token type to its CSS class abbreviation.

Expand Down Expand Up @@ -419,6 +461,9 @@ def visit_argparse_argument_html(
- Positional arguments get class 'nl' (Name.Label)
- Metavars get class 'nv' (Name.Variable)

The argument is wrapped in a container div with a unique ID for linking.
A headerlink anchor (¶) is added for direct navigation.

Parameters
----------
self : HTML5Translator
Expand All @@ -428,11 +473,28 @@ def visit_argparse_argument_html(
"""
names: list[str] = node.get("names", [])
metavar = node.get("metavar")
id_prefix: str = node.get("id_prefix", "")

# Generate unique ID for this argument
arg_id = _generate_argument_id(names, id_prefix)

# Open wrapper div with ID for linking
if arg_id:
self.body.append(f'<div class="argparse-argument-wrapper" id="{arg_id}">\n')
else:
self.body.append('<div class="argparse-argument-wrapper">\n')

# Build the argument signature with syntax highlighting
highlighted_sig = _highlight_argument_names(names, metavar, self.encode)

self.body.append(f'<dt class="argparse-argument-name">{highlighted_sig}</dt>\n')
# Add headerlink anchor inside dt for navigation
headerlink = ""
if arg_id:
headerlink = f'<a class="headerlink" href="#{arg_id}">¶</a>'

self.body.append(
f'<dt class="argparse-argument-name">{highlighted_sig}{headerlink}</dt>\n'
)
self.body.append('<dd class="argparse-argument-help">')

# Add help text
Expand All @@ -447,6 +509,7 @@ def depart_argparse_argument_html(
"""Depart argparse_argument node - close argument entry.

Adds default, choices, and type information if present.
Default values are wrapped in ``<span class="nv">`` for styled display.

Parameters
----------
Expand All @@ -460,7 +523,8 @@ def depart_argparse_argument_html(

default = node.get("default_string")
if default is not None:
metadata.append(f"Default: {self.encode(default)}")
# Wrap default value in nv span for yellow/italic styling
metadata.append(f'Default: <span class="nv">{self.encode(default)}</span>')

choices = node.get("choices")
if choices:
Expand All @@ -480,6 +544,8 @@ def depart_argparse_argument_html(
self.body.append(f'<p class="argparse-argument-meta">{meta_str}</p>')

self.body.append("</dd>\n")
# Close wrapper div
self.body.append("</div>\n")


def visit_argparse_subcommands_html(
Expand Down
32 changes: 25 additions & 7 deletions docs/_ext/sphinx_argparse_neo/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,18 @@ def render_group_section(
section += nodes.title(title, title)

# Create the styled group container (with empty title - section provides it)
group_node = self.render_group(group, include_title=False)
# Pass id_prefix to render_group so arguments get unique IDs
group_node = self.render_group(group, include_title=False, id_prefix=id_prefix)
section += group_node

return section

def render_group(
self, group: ArgumentGroup, include_title: bool = True
self,
group: ArgumentGroup,
include_title: bool = True,
*,
id_prefix: str = "",
) -> argparse_group:
"""Render an argument group.

Expand All @@ -376,6 +381,10 @@ def render_group(
Whether to include the title in the group node. When False,
the title is assumed to come from a parent section node.
Default is True for backwards compatibility.
id_prefix : str
Optional prefix for argument IDs (e.g., "shell" -> "shell-h").
Used to ensure unique IDs when multiple argparse directives exist
on the same page.

Returns
-------
Expand All @@ -397,23 +406,29 @@ def render_group(

# Add individual arguments
for arg in group.arguments:
arg_node = self.render_argument(arg)
arg_node = self.render_argument(arg, id_prefix=id_prefix)
group_node.append(arg_node)

# Add mutually exclusive groups
for mutex in group.mutually_exclusive:
mutex_nodes = self.render_mutex_group(mutex)
mutex_nodes = self.render_mutex_group(mutex, id_prefix=id_prefix)
group_node.extend(mutex_nodes)

return group_node

def render_argument(self, arg: ArgumentInfo) -> argparse_argument:
def render_argument(
self, arg: ArgumentInfo, *, id_prefix: str = ""
) -> argparse_argument:
"""Render a single argument.

Parameters
----------
arg : ArgumentInfo
The argument to render.
id_prefix : str
Optional prefix for the argument ID (e.g., "shell" -> "shell-L").
Used to ensure unique IDs when multiple argparse directives exist
on the same page.

Returns
-------
Expand All @@ -425,6 +440,7 @@ def render_argument(self, arg: ArgumentInfo) -> argparse_argument:
arg_node["help"] = arg.help
arg_node["metavar"] = arg.metavar
arg_node["required"] = arg.required
arg_node["id_prefix"] = id_prefix

if self.config.show_defaults:
arg_node["default_string"] = arg.default_string
Expand All @@ -438,14 +454,16 @@ def render_argument(self, arg: ArgumentInfo) -> argparse_argument:
return arg_node

def render_mutex_group(
self, mutex: MutuallyExclusiveGroup
self, mutex: MutuallyExclusiveGroup, *, id_prefix: str = ""
) -> list[argparse_argument]:
"""Render a mutually exclusive group.

Parameters
----------
mutex : MutuallyExclusiveGroup
The mutually exclusive group.
id_prefix : str
Optional prefix for argument IDs (e.g., "shell" -> "shell-h").

Returns
-------
Expand All @@ -454,7 +472,7 @@ def render_mutex_group(
"""
result: list[argparse_argument] = []
for arg in mutex.arguments:
arg_node = self.render_argument(arg)
arg_node = self.render_argument(arg, id_prefix=id_prefix)
# Mark as part of mutex group
arg_node["mutex"] = True
arg_node["mutex_required"] = mutex.required
Expand Down
Loading