Fix auto links to subheader when TOC is present (#20279)

Fix links to subheader when TOC is present

It was observed (in https://github.com/nim-lang/Nim/pull/20112)
that links to 2nd- (and subsequent) -level headings
fail if TOC is present, e.g.:
```nim
.. contents::

Type relations
==============

Convertible relation
--------------------

Ref. `Convertible relation`_
```

The problem here is that links are resolved in `rst.nim` but later
`rstgen.nim` fixes ("fixes") anchors to make them unique so that
TOC always works (if e.g. there was another sub-section like
"Convertible relation").
The solution implemented in this PR is to move that fix-up of anchors
into `rst.nim`, so that link resolution could know final anchors.

The bug seems to be added in https://github.com/nim-lang/Nim/pull/2332
in 2015, that is it is present in Nim 1.0.
This commit is contained in:
Andrey Makarov 2022-08-28 00:28:26 +03:00 • committed by GitHub
commit de9cbf6af1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 165 additions and 104 deletions

View file

@ -58,10 +58,6 @@ type
outHtml, # output is HTML
outLatex # output is Latex
TocEntry = object
n*: PRstNode
refname*, header*: string
MetaEnum* = enum
metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion
@ -74,7 +70,7 @@ type
config*: StringTableRef
splitAfter*: int # split too long entries in the TOC
listingCounter*: int
tocPart*: seq[TocEntry]
tocPart*: seq[PRstNode] # headings for Table of Contents
hasToc*: bool
theIndex: string # Contents of the index file to be dumped at the end.
findFile*: FindFileHandler
@ -120,7 +116,8 @@ proc initRstGenerator*(g: var RstGenerator, target: OutputTarget,
config: StringTableRef, filename: string,
findFile: FindFileHandler = nil,
msgHandler: MsgHandler = nil,
filenames = default(RstFileTable)) =
filenames = default(RstFileTable),
hasToc = false) =
## Initializes a ``RstGenerator``.
##
## You need to call this before using a ``RstGenerator`` with any other
@ -165,6 +162,7 @@ proc initRstGenerator*(g: var RstGenerator, target: OutputTarget,
g.config = config
g.target = target
g.tocPart = @[]
g.hasToc = hasToc
g.filename = filename
g.filenames = filenames
g.splitAfter = 20
@ -773,31 +771,18 @@ proc renderHeadline(d: PDoc, n: PRstNode, result: var string) =
var tmp = ""
for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp)
d.currentSection = tmp
# Find the last higher level section for unique reference name
var sectionPrefix = ""
for i in countdown(d.tocPart.high, 0):
let n2 = d.tocPart[i].n
if n2.level < n.level:
sectionPrefix = rstnodeToRefname(n2) & "-"
break
var refname = sectionPrefix & rstnodeToRefname(n)
var tocName = esc(d.target, renderRstToText(n), escMode = emOption)
# for Latex: simple text without commands that may break TOC/hyperref
if d.hasToc:
var length = len(d.tocPart)
setLen(d.tocPart, length + 1)
d.tocPart[length].refname = refname
d.tocPart[length].n = n
d.tocPart[length].header = tmp
d.tocPart.add n
dispA(d.target, result, "\n<h$1><a class=\"toc-backref\"" &
"$2 href=\"#$5\">$3</a></h$1>", "\\rsth$4[$6]{$3}$2\n",
[$n.level, refname.idS, tmp,
$chr(n.level - 1 + ord('A')), refname, tocName])
[$n.level, n.anchor.idS, tmp,
$chr(n.level - 1 + ord('A')), n.anchor, tocName])
else:
dispA(d.target, result, "\n<h$1$2>$3</h$1>",
"\\rsth$4[$5]{$3}$2\n", [
$n.level, refname.idS, tmp,
$n.level, n.anchor.idS, tmp,
$chr(n.level - 1 + ord('A')), tocName])
# Generate index entry using spaces to indicate TOC level for the output HTML.
@ -810,7 +795,7 @@ proc renderHeadline(d: PDoc, n: PRstNode, result: var string) =
# outDir = /foo -\
# destFile = /foo/bar/zoo.html -|-> bar/zoo.html
d.destFile.relativePath(d.outDir, '/')
setIndexTerm(d, htmlFileRelPath, refname, tmp.stripTocHtml,
setIndexTerm(d, htmlFileRelPath, n.anchor, tmp.stripTocHtml,
spaces(max(0, n.level)) & tmp)
proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
@ -829,18 +814,20 @@ proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
var tocName = esc(d.target, renderRstToText(n), escMode=emOption)
dispA(d.target, result, "<h$1$2><center>$3</center></h$1>",
"\\rstov$4[$5]{$3}$2\n", [$n.level,
rstnodeToRefname(n).idS, tmp, $chr(n.level - 1 + ord('A')), tocName])
n.anchor.idS, tmp, $chr(n.level - 1 + ord('A')), tocName])
proc renderTocEntry(d: PDoc, e: TocEntry, result: var string) =
proc renderTocEntry(d: PDoc, n: PRstNode, result: var string) =
var header = ""
for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], header)
dispA(d.target, result,
"<li><a class=\"reference\" id=\"$1_toc\" href=\"#$1\">$2</a></li>\n",
"\\item\\label{$1_toc} $2\\ref{$1}\n", [e.refname, e.header])
"\\item\\label{$1_toc} $2\\ref{$1}\n", [n.anchor, header])
proc renderTocEntries*(d: var RstGenerator, j: var int, lvl: int,
result: var string) =
var tmp = ""
while j <= high(d.tocPart):
var a = abs(d.tocPart[j].n.level)
var a = abs(d.tocPart[j].level)
if a == lvl:
renderTocEntry(d, d.tocPart[j], tmp)
inc(j)
@ -1630,11 +1617,12 @@ proc rstToHtml*(s: string, options: RstParseOptions,
result = ""
const filen = "input"
let (rst, filenames, _) = rstParse(s, filen,
let (rst, filenames, t) = rstParse(s, filen,
line=LineRstInit, column=ColRstInit,
options, myFindFile, msgHandler)
var d: RstGenerator
initRstGenerator(d, outHtml, config, filen, myFindFile, msgHandler, filenames)
initRstGenerator(d, outHtml, config, filen, myFindFile, msgHandler,
filenames, hasToc = t)
result = ""
renderRstToOut(d, rst, result)
strbasics.strip(result)
@ -1644,10 +1632,11 @@ proc rstToLatex*(rstSource: string; options: RstParseOptions): string {.inline,
## Convenience proc for `renderRstToOut` and `initRstGenerator`.
runnableExamples: doAssert rstToLatex("*Hello* **world**", {}) == """\emph{Hello} \textbf{world}"""
if rstSource.len == 0: return
let (rst, filenames, _) = rstParse(rstSource, "",
let (rst, filenames, t) = rstParse(rstSource, "",
line=LineRstInit, column=ColRstInit,
options)
var rstGenera: RstGenerator
rstGenera.initRstGenerator(outLatex, defaultConfig(), "input", filenames=filenames)
rstGenera.initRstGenerator(outLatex, defaultConfig(), "input",
filenames=filenames, hasToc = t)
rstGenera.renderRstToOut(rst, result)
strbasics.strip(result)