diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 555a171685b..4a560517f22 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -70,6 +70,8 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa } case *ast.CodeSpan: g.transformCodeSpan(ctx, v, reader) + case *ast.FencedCodeBlock: + g.transformFencedCodeblock(v, reader) case *ast.Blockquote: return g.transformBlockquote(v, reader) } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index e231b037cc1..2f14a0fae98 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -600,3 +600,22 @@ func TestMarkdownUlDir(t *testing.T) { `, string(result)) } + +func TestMarkdownFencedCodeBlock(t *testing.T) { + testRender := func(input, expected string) { + buffer, err := markdown.RenderString(markup.NewTestRenderContext(), input) + assert.NoError(t, err) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + } + const nl = "\n" + const prefix = `
` + const suffix = `
code`+nl+``+suffix)
+
+ const jsCommon = prefix + `code` + nl + `` + suffix
+ testRender("```js\ncode\n```", jsCommon)
+ testRender("```js:app.ts\ncode\n```", jsCommon)
+ testRender("```js,ignore\ncode\n```", jsCommon)
+ testRender("```js ignore\ncode\n```", jsCommon)
+}
diff --git a/modules/markup/markdown/transform_codeblock.go b/modules/markup/markdown/transform_codeblock.go
new file mode 100644
index 00000000000..de9264c4c49
--- /dev/null
+++ b/modules/markup/markdown/transform_codeblock.go
@@ -0,0 +1,32 @@
+// Copyright 2026 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package markdown
+
+import (
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/text"
+)
+
+func (g *ASTTransformer) transformFencedCodeblock(v *ast.FencedCodeBlock, reader text.Reader) {
+ // * Some engines support a meta syntax for appending the filename after the language, separated by a colon
+ // * https://www.glukhov.org/documentation-tools/markdown/markdown-codeblocks/
+ // * Some engines support additional "options" after the language, separated by a space or comma: ```rust,ignore```
+ // * https://docs.readme.com/rdmd/docs/code-blocks
+ // * https://next-book.vercel.app/reference/fencedcode
+ if v.Info == nil {
+ return
+ }
+ info := v.Info.Segment.Value(reader.Source())
+ newEnd := -1
+ for i, b := range info {
+ if b == ' ' || b == ',' || b == ':' {
+ newEnd = i
+ break
+ }
+ }
+ if newEnd != -1 {
+ start := v.Info.Segment.Start
+ v.Info = ast.NewTextSegment(text.NewSegment(start, start+newEnd))
+ }
+}