From 2ddfa0521c05cc431c044ced426dcc10c089969a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 20 Aug 2026 21:09:27 +1200 Subject: [PATCH] Render inline code references as HTML --- lib/utopia/project/document.rb | 51 ++++++++++++++++++++++++--------- lib/utopia/project/renderer.rb | 12 ++------ releases.md | 4 +++ test/utopia/project/document.rb | 16 +++++++++-- 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/lib/utopia/project/document.rb b/lib/utopia/project/document.rb index 3283cda..1ee494e 100644 --- a/lib/utopia/project/document.rb +++ b/lib/utopia/project/document.rb @@ -174,30 +174,53 @@ def code_node(content, language = nil) private - # Replace source code references in the given text with HTML anchors. + # Resolve a source code reference to HTML. # @parameter content [String] The source code reference. # @parameter language [String | Nil] The explicit source language. - # @returns [Markly::Node] The resolved link or code node. + # @returns [Markly::Node] The inline HTML node. def reference_node(content, language: nil) + reference, definition = resolve_reference(content, language: language) + + if definition + content = definition.qualified_form + language = reference.language.name + elsif reference + content = reference.identifier + language = reference.language.name + end + + attributes = {} + attributes[:class] = "language-#{language}" if language + + markup = XRB::Builder.fragment do |builder| + builder.inline("code", attributes) do + if definition + builder.inline("a", href: @base.link_for(definition), title: reference.identifier) do + builder.text(content) + end + else + builder.text(content) + end + end + end + + return inline_html_node(markup.to_s) + end + + # Resolve source code reference metadata and its indexed definition. + # @parameter content [String] The source code reference. + # @parameter language [String | Nil] The explicit source language. + # @returns [Array] The parsed reference and resolved definition. + def resolve_reference(content, language: nil) reference = if language @index.languages.reference_for(language, content) else @index.languages.parse_reference(content, default_language: @default_language) end - if reference - definition = @index.lookup(reference, relative_to: @definition) - end + definition = @index.lookup(reference, relative_to: @definition) if reference - if definition - link_node(reference.identifier, @base.link_for(definition), - code_node(definition.qualified_form, reference.language.name) - ) - elsif reference - code_node(reference.identifier, reference.language.name) - else - code_node(content, language) - end + return reference, definition end def resolve(root) diff --git a/lib/utopia/project/renderer.rb b/lib/utopia/project/renderer.rb index b82fc9c..1b652fe 100644 --- a/lib/utopia/project/renderer.rb +++ b/lib/utopia/project/renderer.rb @@ -11,10 +11,9 @@ module Project # Renders project Markdown with support for Mermaid code blocks. class Renderer < Markly::Renderer::HTML # Initialize the project renderer. - # @parameter inline_code_resolver [Proc | Nil] Resolves language-prefixed inline code into a replacement node. + # @parameter inline_code_resolver [Proc | Nil] Resolves language-prefixed inline code into an inline HTML node. def initialize(inline_code_resolver: nil, **options) @inline_code_resolver = inline_code_resolver - @resolving_inline_code = false super(**options) end @@ -58,13 +57,8 @@ def code_block(node) # Render inline code, resolving language-prefixed references when possible. # @parameter node [Markly::Node] The inline code node. def code(node) - if @inline_code_resolver && !@resolving_inline_code && (language = node.code_language) - begin - @resolving_inline_code = true - out(@inline_code_resolver.call(node.string_content, language: language)) - ensure - @resolving_inline_code = false - end + if @inline_code_resolver && (language = node.code_language) + out(@inline_code_resolver.call(node.string_content, language: language)) else super end diff --git a/releases.md b/releases.md index e2b76f0..6f62ad6 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Changes +## Unreleased + + - Render resolved inline code references with links inside their code elements so hover and keyboard focus affect the complete reference. + ## v0.44.0 - Add support for language-prefixed inline code references such as ruby:`Object.new`. diff --git a/test/utopia/project/document.rb b/test/utopia/project/document.rb index b527bf9..45d702d 100644 --- a/test/utopia/project/document.rb +++ b/test/utopia/project/document.rb @@ -33,6 +33,15 @@ expect(document.to_markdown).to be == "Use ruby:`Object.new` to create an object.\n" end + it "escapes language-prefixed inline code" do + root = File.expand_path("../../..", __dir__) + base = Utopia::Project::Base.new(root) + document = subject.new("Compare ruby:`foo < bar`.", base) + html = document.to_html.to_s + + expect(html).to be(:include?, 'foo < bar') + end + it "resolves language-prefixed inline code references" do root = File.expand_path("../../..", __dir__) base = Utopia::Project::Base.new(root) @@ -41,8 +50,8 @@ document = subject.new("See ruby:`Utopia::Project::Document#root`.", base) html = document.to_html.to_s - expect(html).to be(:include?, 'Utopia::Project::Document#root') + expect(html).to be(:include?, 'Utopia::Project::Document#root") end it "continues to resolve legacy brace references" do @@ -53,7 +62,8 @@ document = subject.new("See {ruby Utopia::Project::Document#root}.", base) html = document.to_html.to_s - expect(html).to be(:include?, '