Skip to content

Commit 700800d

Browse files
shocomanyopox
authored andcommitted
display implied shortcut reference links
1 parent 6d69f84 commit 700800d

File tree

2 files changed

+77
-9
lines changed

2 files changed

+77
-9
lines changed

src/main/kotlin/org/rust/lang/doc/RsDocPipeline.kt

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.intellij.markdown.ast.getTextInNode
2222
import org.intellij.markdown.flavours.MarkdownFlavourDescriptor
2323
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
2424
import org.intellij.markdown.html.*
25+
import org.intellij.markdown.html.entities.EntityConverter
2526
import org.intellij.markdown.parser.LinkMap
2627
import org.intellij.markdown.parser.MarkdownParser
2728
import org.rust.cargo.util.AutoInjectedCrates.STD
@@ -137,13 +138,12 @@ private class RustDocMarkdownFlavourDescriptor(
137138
generatingProviders[MarkdownElementTypes.ATX_2] = SimpleTagProvider("h3")
138139
generatingProviders[MarkdownElementTypes.CODE_FENCE] = RsCodeFenceProvider(context, renderMode)
139140

140-
val absolutizeAnchorLinks = true
141141
generatingProviders[MarkdownElementTypes.SHORT_REFERENCE_LINK] =
142-
RsReferenceLinksGeneratingProvider(linkMap, uri ?: baseURI, absolutizeAnchorLinks)
142+
RsReferenceLinksGeneratingProvider(linkMap, uri ?: baseURI, resolveAnchors = true)
143143
generatingProviders[MarkdownElementTypes.FULL_REFERENCE_LINK] =
144-
RsReferenceLinksGeneratingProvider(linkMap, uri ?: baseURI, absolutizeAnchorLinks)
144+
RsReferenceLinksGeneratingProvider(linkMap, uri ?: baseURI, resolveAnchors = true)
145145
generatingProviders[MarkdownElementTypes.INLINE_LINK] =
146-
RsInlineLinkGeneratingProvider(uri ?: baseURI, absolutizeAnchorLinks)
146+
RsInlineLinkGeneratingProvider(uri ?: baseURI, resolveAnchors = true)
147147

148148
return generatingProviders
149149
}
@@ -250,20 +250,48 @@ enum class RsDocRenderMode {
250250
INLINE_DOC_COMMENT
251251
}
252252

253-
private fun markLinkAsLanguageItemIfItIsValidRustPath(link: CharSequence): CharSequence {
254-
return if (link.none { it in "/.#" }) "${DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL}$link" else link
253+
private fun linkIsProbablyValidRustPath(link: CharSequence): Boolean {
254+
return link.none { it in "/.#" || it.isWhitespace() }
255255
}
256256

257-
open class RsReferenceLinksGeneratingProvider(linkMap: LinkMap, baseURI: URI?, resolveAnchors: Boolean)
257+
private fun markLinkAsLanguageItemIfItIsRustPath(link: CharSequence): CharSequence {
258+
return if (linkIsProbablyValidRustPath(link)) "${DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL}$link" else link
259+
}
260+
261+
open class RsReferenceLinksGeneratingProvider(private val linkMap: LinkMap, baseURI: URI?, resolveAnchors: Boolean)
258262
: ReferenceLinksGeneratingProvider(linkMap, baseURI, resolveAnchors) {
259263
override fun renderLink(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode, info: RenderInfo) {
260-
super.renderLink(visitor, text, node, info.copy(destination = markLinkAsLanguageItemIfItIsValidRustPath(info.destination)))
264+
super.renderLink(visitor, text, node, info.copy(destination = markLinkAsLanguageItemIfItIsRustPath(info.destination)))
265+
}
266+
267+
override fun getRenderInfo(text: String, node: ASTNode): RenderInfo? {
268+
val label = node.children.firstOrNull { it.type == MarkdownElementTypes.LINK_LABEL } ?: return null
269+
val labelText = label.getTextInNode(text)
270+
271+
val linkInfo = linkMap.getLinkInfo(labelText)
272+
val (linkDestination, linkTitle) = if (linkInfo != null) {
273+
linkInfo.destination to linkInfo.title
274+
} else {
275+
// then maybe it's implied shortcut reference link, i.e. shortcut reference link without a matching link reference definition
276+
// (see https://rust-lang.github.io/rfcs/1946-intra-rustdoc-links.html#implied-shortcut-reference-links)
277+
// so "[Iterator]" is the same as "[Iterator](Iterator)" and will be eventually rendered as "<a href="psi_element://Iterator">Iterator</a>"
278+
val linkText = labelText.removeSurrounding("[", "]").removeSurrounding("`")
279+
if (!linkIsProbablyValidRustPath(linkText)) return null
280+
linkText to null
281+
}
282+
283+
val linkTextNode = node.children.firstOrNull { it.type == MarkdownElementTypes.LINK_TEXT }
284+
return RenderInfo(
285+
linkTextNode ?: label,
286+
EntityConverter.replaceEntities(linkDestination, processEntities = true, processEscapes = true),
287+
linkTitle?.let { EntityConverter.replaceEntities(it, processEntities = true, processEscapes = true) }
288+
)
261289
}
262290
}
263291

264292
open class RsInlineLinkGeneratingProvider(baseURI: URI?, resolveAnchors: Boolean)
265293
: InlineLinkGeneratingProvider(baseURI, resolveAnchors) {
266294
override fun renderLink(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode, info: RenderInfo) {
267-
super.renderLink(visitor, text, node, info.copy(destination = markLinkAsLanguageItemIfItIsValidRustPath(info.destination)))
295+
super.renderLink(visitor, text, node, info.copy(destination = markLinkAsLanguageItemIfItIsRustPath(info.destination)))
268296
}
269297
}

src/test/kotlin/org/rust/ide/docs/RsRenderedDocumentationTest.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,46 @@ class RsRenderedDocumentationTest : RsDocumentationProviderTest() {
141141
"""<p><a href="psi_element://Long::Path::For::MyType">my link</a></p>"""
142142
)
143143

144+
fun `test implied shortcut reference link`() = doTest("""
145+
fn foo() {}
146+
/// [foo]
147+
fn main() {}
148+
//^
149+
""",
150+
"""<p><a href="psi_element://foo">foo</a></p>"""
151+
)
152+
153+
fun `test implied shortcut reference link with backticks`() = doTest("""
154+
/// [`Iterator`]
155+
fn main() {}
156+
//^
157+
""",
158+
"""<p><a href="psi_element://Iterator"><code>Iterator</code></a></p>"""
159+
)
160+
161+
fun `test implied shortcut reference link, ignore non-valid rust identifier or path`() {
162+
val badVariants = listOf("some.web.site", "foo bar w spaces", "some/path")
163+
for (s in badVariants) {
164+
doTest("""
165+
/// [${s}]
166+
fn main() {}
167+
//^
168+
""", """<p>[${s}]</p>"""
169+
)
170+
}
171+
}
172+
173+
fun `test implied shortcut reference link with reference, reference has higher priority`() = doTest("""
174+
fn foo() {}
175+
/// [foo]
176+
///
177+
/// [foo]: Path::For::Smth
178+
fn main() {}
179+
//^
180+
""",
181+
"""<p><a href="psi_element://Path::For::Smth">foo</a></p>"""
182+
)
183+
144184
private fun doTest(@Language("Rust") code: String, @Language("Html") expected: String?) {
145185
doTest(code, expected) { originalItem, _ ->
146186
(originalItem as? RsDocAndAttributeOwner)

0 commit comments

Comments
 (0)