Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 12 additions & 1 deletion ExampleMarkdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ For the following styles, see ExampleRDoc.rdoc for style examples:

These items all use the same styles as RDoc format files.

## Anchor Links

RDoc supports GitHub-style anchor links. You can link to any heading using its
anchor, which is the heading text converted to lowercase with spaces replaced
by hyphens and special characters removed.

For example:

* [Link to Footnotes](#footnotes)
* [Link to Blockquotes](#blockquotes)
* [Link to Anchor Links](#anchor-links)

## Footnotes

Footnotes are rendered at the bottom of the documentation section[^1]. For
Expand All @@ -36,4 +48,3 @@ Here is how a blockquote looks.
> > 75 years?

This text is from [Riker Ipsum](http://rikeripsum.com)

12 changes: 12 additions & 0 deletions ExampleRDoc.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
This document contains example output to show RDoc styling. This file was
created from a RDoc Markup file.

== Anchor Links

RDoc generates GitHub-style anchors for headings. The anchor is the heading
text converted to lowercase with spaces replaced by hyphens and special
characters removed.

You can link to headings using Markdown-style syntax:

- {Link to Headings}[#headings]
- {Link to Paragraphs}[#paragraphs]
- {Link to Verbatim sections}[#verbatim-sections]

== Headings

You should not use headings beyond level 3, it is a sign of poor organization
Expand Down
24 changes: 16 additions & 8 deletions doc/rdoc/markup_reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -957,22 +957,30 @@
#
# [Heading]
#
# - Link: <tt>RDoc::RD@LICENSE</tt> links to RDoc::RDoc::RD@LICENSE.
# Headings generate GitHub-style anchors: lowercase, spaces as hyphens,
# special characters removed. For example, <tt>== Hello World</tt> generates
# anchor <tt>hello-world</tt>.
#
# Note that spaces in the actual heading are represented by <tt>+</tt> characters
# in the linkable text.
# Link to headings are recommended to use the GitHub-style anchor format:
#
# - Link: <tt>RDoc::Options@Saved+Options</tt>
# links to RDoc::Options@Saved+Options.
# - <tt>RDoc::Options@saved-options</tt> links to RDoc::Options@saved-options.
# - <tt>RDoc::RD@license</tt> links to RDoc::RD@license.
#
# Punctuation and other special characters must be escaped like CGI.escape.
# To link to headings on the same page, you can also use Markdown-style anchor links:
#
# - <tt>{link text}[#hello-world]</tt> links to the heading <tt>== Hello World</tt>.
# - <tt>{link text}[#saved-options]</tt> links to the heading <tt>== Saved Options</tt>.
#
# The legacy format with <tt>+</tt> for spaces is also supported, but not recommended:
#
# - <tt>RDoc::Options@Saved+Options</tt> links to RDoc::Options@Saved+Options.
#
# Pro tip: The link to any heading is available in the alphabetical table of contents
# at the top left of the page for the class or module.
# at the right sidebar of the page.
#
# [Section]
#
# See {Directives for Organizing Documentation}[#class-RDoc::MarkupReference-label-Directives+for+Organizing+Documentation].
# See {Directives for Organizing Documentation}[#class-rdoc-markupreference-directives-for-organizing-documentation].
#
# - Link: <tt>RDoc::Markup::ToHtml@Visitor</tt> links to RDoc::Markup::ToHtml@Visitor.
#
Expand Down
20 changes: 18 additions & 2 deletions lib/rdoc/code_object/class_module.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,26 @@ def aref_prefix # :nodoc:
end

##
# HTML fragment reference for this module or class. See
# RDoc::NormalClass#aref and RDoc::NormalModule#aref
# HTML fragment reference for this module or class using GitHub-style
# anchor format (lowercase, :: replaced with -).
#
# Examples:
# Foo -> class-foo
# Foo::Bar -> class-foo-bar

def aref
"#{aref_prefix}-#{full_name.downcase.gsub('::', '-')}"
end

##
# Legacy HTML fragment reference for backward compatibility.
# Returns the old RDoc-style anchor format.
#
# Examples:
# Foo -> class-Foo
# Foo::Bar -> class-Foo::Bar

def legacy_aref
"#{aref_prefix}-#{full_name}"
end

Expand Down
21 changes: 20 additions & 1 deletion lib/rdoc/code_object/context/section.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,30 @@ def add_comment(comment)
end

##
# Anchor reference for linking to this section
# Anchor reference for linking to this section using GitHub-style format.
#
# Examples:
# "Section" -> "section"
# "One Two" -> "one-two"
# "[untitled]" -> "untitled"

def aref
title = @title || '[untitled]'

RDoc::Text.to_anchor(title)
end

##
# Legacy anchor reference for backward compatibility.
#
# Examples:
# "Section" -> "section"
# "One Two" -> "one+two"
# "[untitled]" -> "5Buntitled-5D"

def legacy_aref
title = @title || '[untitled]'

CGI.escape(title).gsub('%', '-').sub(/^-/, '')
end

Expand Down
2 changes: 2 additions & 0 deletions lib/rdoc/generator/template/aliki/class.rhtml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
</ol>
<% end %>

<span id="<%= h klass.legacy_aref %>" class="legacy-anchor"></span>
<h1 id="<%= h klass.aref %>" class="anchor-link <%= klass.type %>">
<%= klass.type %> <%= klass.full_name %>
</h1>
Expand All @@ -38,6 +39,7 @@
</section>

<%- klass.each_section do |section, constants, attributes| %>
<span id="<%= section.legacy_aref %>" class="legacy-anchor"></span>
<section id="<%= section.aref %>" class="documentation-section anchor-link">
<%- if section.title then %>
<header class="documentation-section-title">
Expand Down
20 changes: 20 additions & 0 deletions lib/rdoc/generator/template/aliki/css/rdoc.css
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,26 @@ main .anchor-link:target {
scroll-margin-top: calc(var(--layout-header-height) + 2rem);
}

/* Legacy anchor for backward compatibility with old label- prefix links */
.legacy-anchor {
display: block;
position: relative;
visibility: hidden;
scroll-margin-top: calc(var(--layout-header-height) + 2rem);
}

/* When a legacy anchor is targeted, highlight the next heading sibling */
.legacy-anchor:target + h1,
.legacy-anchor:target + h2,
.legacy-anchor:target + h3,
.legacy-anchor:target + h4,
.legacy-anchor:target + h5,
.legacy-anchor:target + h6 {
margin-left: calc(-1 * var(--space-5));
padding-left: calc(var(--space-5) / 2);
border-left: calc(var(--space-5) / 2) solid var(--color-border-default);
}


/* Utility Classes */
.hide { display: none !important; }
Expand Down
2 changes: 2 additions & 0 deletions lib/rdoc/generator/template/darkfish/class.rhtml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
</ol>
<% end %>

<span id="<%= h klass.legacy_aref %>" class="legacy-anchor"></span>
<h1 id="<%= h klass.aref %>" class="anchor-link <%= klass.type %>">
<%= klass.type %> <%= klass.full_name %>
</h1>
Expand All @@ -42,6 +43,7 @@
</section>

<%- klass.each_section do |section, constants, attributes| %>
<span id="<%= section.legacy_aref %>" class="legacy-anchor"></span>
<section id="<%= section.aref %>" class="documentation-section anchor-link">
<%- if section.title then %>
<header class="documentation-section-title">
Expand Down
19 changes: 19 additions & 0 deletions lib/rdoc/generator/template/darkfish/css/rdoc.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,25 @@ main .anchor-link:target {
scroll-margin-top: 1rem;
}

/* Legacy anchor for backward compatibility with old label- prefix links */
.legacy-anchor {
display: block;
position: relative;
visibility: hidden;
scroll-margin-top: 1rem;
}

/* When a legacy anchor is targeted, highlight the next heading sibling */
.legacy-anchor:target + h1,
.legacy-anchor:target + h2,
.legacy-anchor:target + h3,
.legacy-anchor:target + h4,
.legacy-anchor:target + h5,
.legacy-anchor:target + h6 {
margin-left: -10px;
border-left: 10px solid var(--border-color);
}

/* 4. Links */
a {
color: var(--link-color);
Expand Down
77 changes: 70 additions & 7 deletions lib/rdoc/markup/heading.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,82 @@ def accept(visitor)
visitor.accept_heading(self)
end

# An HTML-safe anchor reference for this header.
# An HTML-safe anchor reference for this header using GitHub-style formatting:
# - Lowercase
# - Spaces converted to hyphens
# - Special characters removed (except hyphens)
#
# Examples:
# "Hello" -> "hello"
# "Hello World" -> "hello-world"
# "Foo Bar Baz" -> "foo-bar-baz"
#
#: () -> String
def aref
"label-#{self.class.to_label.convert text.dup}"
self.class.to_label.convert text.dup
end

# Creates a fully-qualified label which will include the label from +context+. This helps keep ids unique in HTML.
# An HTML-safe anchor reference using legacy RDoc formatting:
# - Prefixed with "label-"
# - Original case preserved
# - Spaces converted to + (URL encoding style)
# - Special characters percent-encoded
#
# Returns nil if it would be the same as the GitHub-style aref (no alias needed).
#
# Examples:
# "hello" -> "label-hello" (different due to label- prefix)
# "Hello" -> "label-Hello"
# "Hello World" -> "label-Hello+World"
# "Foo Bar Baz" -> "label-Foo+Bar+Baz"
#
#: () -> String?
def legacy_aref
"label-#{self.class.to_label.convert_legacy text.dup}"
end

# Creates a fully-qualified label (GitHub-style) which includes the context's aref prefix.
# This helps keep IDs unique in HTML when headings appear within class/method documentation.
#
# Examples (without context):
# "Hello World" -> "hello-world"
#
# Examples (with context being class Foo):
# "Hello World" -> "class-foo-hello-world"
#
# Examples (with context being method #bar):
# "Hello World" -> "method-i-bar-hello-world"
#
#: (RDoc::Context?) -> String
def label(context = nil)
label = +""
label << "#{context.aref}-" if context&.respond_to?(:aref)
label << aref
label
result = +""
result << "#{context.aref}-" if context&.respond_to?(:aref)
result << aref
result
end

# Creates a fully-qualified legacy label for backward compatibility.
# This is used to generate a secondary ID attribute on the heading's inner anchor,
# allowing old-style links (e.g., #label-Hello+World) to continue working.
#
# Examples (without context):
# "hello" -> "label-hello"
# "Hello World" -> "label-Hello+World"
#
# Examples (with context being class Foo):
# "hello" -> "class-Foo-label-hello"
# "Hello World" -> "class-Foo-label-Hello+World"
#
#: (RDoc::Context?) -> String
def legacy_label(context = nil)
result = +""
if context&.respond_to?(:legacy_aref)
result << "#{context.legacy_aref}-"
elsif context&.respond_to?(:aref)
result << "#{context.aref}-"
end
result << legacy_aref
result
end

# HTML markup of the text of this label without the surrounding header element.
Expand Down
27 changes: 20 additions & 7 deletions lib/rdoc/markup/to_html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,13 @@ def accept_heading(heading)
level = [6, heading.level].min

label = heading.label @code_object
legacy_label = heading.legacy_label @code_object

# Add legacy anchor before the heading for backward compatibility.
# This allows old links with label- prefix to still work.
if @options.output_decoration && [email protected]
@res << "\n<span id=\"#{legacy_label}\" class=\"legacy-anchor\"></span>"
end

@res << if @options.output_decoration
"\n<h#{level} id=\"#{label}\">"
Expand Down Expand Up @@ -368,14 +375,18 @@ def convert_string(text)
end

##
# Generate a link to +url+ with content +text+. Handles the special cases
# for img: and link: described under handle_regexp_HYPERLINK
# Generates an HTML link or image tag for the given +url+ and +text+.
#
# - Image URLs (http/https/link ending in .gif, .png, .jpg, .jpeg, .bmp)
# become <img> tags
# - File references (.rb, .rdoc, .md) are converted to .html paths
# - Anchor URLs (#foo) pass through unchanged for GitHub-style header linking
# - Footnote links get wrapped in <sup> tags

def gen_url(url, text)
scheme, url, id = parse_url url

if %w[http https link].include?(scheme) and
url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then
if %w[http https link].include?(scheme) && url =~ /\.(gif|png|jpg|jpeg|bmp)\z/
"<img src=\"#{url}\" />"
else
if scheme != 'link' and %r%\A((?!https?:)(?:[^/#]*/)*+)([^/#]+)\.(rb|rdoc|md)(?=\z|#)%i =~ url
Expand All @@ -387,9 +398,11 @@ def gen_url(url, text)

link = "<a#{id} href=\"#{url}\">#{text}</a>"

link = "<sup>#{link}</sup>" if /"foot/ =~ id

link
if /"foot/.match?(id)
"<sup>#{link}</sup>"
else
link
end
end
end

Expand Down
Loading
Loading