• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.jetbrains.dokka
2 
3 import com.google.inject.Inject
4 import com.google.inject.name.Named
5 import org.jetbrains.dokka.Utilities.impliedPlatformsName
6 import java.util.*
7 
8 enum class ListKind {
9     Ordered,
10     Unordered
11 }
12 
13 private class ListState(val kind: ListKind, var size: Int = 1) {
getTagAndIncrementnull14     fun getTagAndIncrement() = when (kind) {
15         ListKind.Ordered -> "${size++}. "
16         else -> "* "
17     }
18 }
19 
20 private val TWO_LINE_BREAKS = System.lineSeparator() + System.lineSeparator()
21 
22 open class MarkdownOutputBuilder(to: StringBuilder,
23                                  location: Location,
24                                  generator: NodeLocationAwareGenerator,
25                                  languageService: LanguageService,
26                                  extension: String,
27                                  impliedPlatforms: List<String>)
28     : StructuredOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms)
29 {
30     private val listStack = ArrayDeque<ListState>()
31     protected var inTableCell = false
32     protected var inCodeBlock = false
33     private var lastTableCellStart = -1
34     private var maxBackticksInCodeBlock = 0
35 
appendNewlinenull36     private fun appendNewline() {
37         while (to.endsWith(' ')) {
38             to.setLength(to.length - 1)
39         }
40         to.appendln()
41     }
42 
ensureNewlinenull43     private fun ensureNewline() {
44         if (inTableCell && listStack.isEmpty()) {
45             if (to.length != lastTableCellStart && !to.endsWith("<br>")) {
46                 to.append("<br>")
47             }
48         }
49         else {
50             if (!endsWithNewline()) {
51                 appendNewline()
52             }
53         }
54     }
55 
endsWithNewlinenull56     private fun endsWithNewline(): Boolean {
57         var index = to.length - 1
58         while (index > 0) {
59             val c = to[index]
60             if (c != ' ') {
61                 return c == '\n'
62             }
63             index--
64         }
65         return false
66     }
67 
ensureParagraphnull68     override fun ensureParagraph() {
69         if (!to.endsWith(TWO_LINE_BREAKS)) {
70             if (!to.endsWith('\n')) {
71                 appendNewline()
72             }
73             appendNewline()
74         }
75     }
appendBreadcrumbSeparatornull76     override fun appendBreadcrumbSeparator() {
77         to.append(" / ")
78     }
79 
80     private val backTickFindingRegex = """(`+)""".toRegex()
81 
appendTextnull82     override fun appendText(text: String) {
83         if (inCodeBlock) {
84             to.append(text)
85             val backTicks = backTickFindingRegex.findAll(text)
86             val longestBackTickRun = backTicks.map { it.value.length }.max() ?: 0
87             maxBackticksInCodeBlock = maxBackticksInCodeBlock.coerceAtLeast(longestBackTickRun)
88         }
89         else {
90             if (text == "\n" && inTableCell) {
91                 to.append(" ")
92             } else {
93                 to.append(text.htmlEscape())
94             }
95         }
96     }
97 
appendCodenull98     override fun appendCode(body: () -> Unit) {
99         inCodeBlock = true
100         val codeBlockStart = to.length
101         maxBackticksInCodeBlock = 0
102 
103         wrapIfNotEmpty("`", "`", body, checkEndsWith = true)
104 
105         if (maxBackticksInCodeBlock > 0) {
106             val extraBackticks = "`".repeat(maxBackticksInCodeBlock)
107             to.insert(codeBlockStart, extraBackticks)
108             to.append(extraBackticks)
109         }
110 
111         inCodeBlock = false
112     }
113 
appendUnorderedListnull114     override fun appendUnorderedList(body: () -> Unit) {
115         listStack.push(ListState(ListKind.Unordered))
116         body()
117         listStack.pop()
118         ensureNewline()
119     }
120 
appendOrderedListnull121     override fun appendOrderedList(body: () -> Unit) {
122         listStack.push(ListState(ListKind.Ordered))
123         body()
124         listStack.pop()
125         ensureNewline()
126     }
127 
appendListItemnull128     override fun appendListItem(body: () -> Unit) {
129         ensureNewline()
130         to.append(listStack.peek()?.getTagAndIncrement())
131         body()
132         ensureNewline()
133     }
134 
appendStrongnull135     override fun appendStrong(body: () -> Unit) = wrap("**", "**", body)
136     override fun appendEmphasis(body: () -> Unit) = wrap("*", "*", body)
137     override fun appendStrikethrough(body: () -> Unit) = wrap("~~", "~~", body)
138 
139     override fun appendLink(href: String, body: () -> Unit) {
140         if (inCodeBlock) {
141             wrap("`[`", "`]($href)`", body)
142         }
143         else {
144             wrap("[", "]($href)", body)
145         }
146     }
147 
appendLinenull148     override fun appendLine() {
149         if (inTableCell) {
150             to.append("<br>")
151         }
152         else {
153             appendNewline()
154         }
155     }
156 
appendAnchornull157     override fun appendAnchor(anchor: String) {
158         // no anchors in Markdown
159     }
160 
appendParagraphnull161     override fun appendParagraph(body: () -> Unit) {
162         if (inTableCell) {
163             ensureNewline()
164             body()
165         } else if (listStack.isNotEmpty()) {
166             body()
167             ensureNewline()
168         } else {
169             ensureParagraph()
170             body()
171             ensureParagraph()
172         }
173     }
174 
appendHeadernull175     override fun appendHeader(level: Int, body: () -> Unit) {
176         ensureParagraph()
177         to.append("${"#".repeat(level)} ")
178         body()
179         ensureParagraph()
180     }
181 
appendBlockCodenull182     override fun appendBlockCode(language: String, body: () -> Unit) {
183         inCodeBlock = true
184         ensureParagraph()
185         to.appendln(if (language.isEmpty()) "```" else "``` $language")
186         body()
187         ensureNewline()
188         to.appendln("```")
189         appendLine()
190         inCodeBlock = false
191     }
192 
appendTablenull193     override fun appendTable(vararg columns: String, body: () -> Unit) {
194         ensureParagraph()
195         body()
196         ensureParagraph()
197     }
198 
appendTableBodynull199     override fun appendTableBody(body: () -> Unit) {
200         body()
201     }
202 
appendTableRownull203     override fun appendTableRow(body: () -> Unit) {
204         to.append("|")
205         body()
206         appendNewline()
207     }
208 
appendTableCellnull209     override fun appendTableCell(body: () -> Unit) {
210         to.append(" ")
211         inTableCell = true
212         lastTableCellStart = to.length
213         body()
214         inTableCell = false
215         to.append(" |")
216     }
217 
appendNonBreakingSpacenull218     override fun appendNonBreakingSpace() {
219         if (inCodeBlock) {
220             to.append(" ")
221         }
222         else {
223             to.append("&nbsp;")
224         }
225     }
226 }
227 
228 open class MarkdownFormatService(generator: NodeLocationAwareGenerator,
229                                  signatureGenerator: LanguageService,
230                                  linkExtension: String,
231                                  val impliedPlatforms: List<String>)
232 : StructuredFormatService(generator, signatureGenerator, "md", linkExtension) {
233     @Inject constructor(generator: NodeLocationAwareGenerator,
234                         signatureGenerator: LanguageService,
235                         @Named(impliedPlatformsName) impliedPlatforms: List<String>): this(generator, signatureGenerator, "md", impliedPlatforms)
236 
createOutputBuildernull237     override fun createOutputBuilder(to: StringBuilder, location: Location): FormattedOutputBuilder =
238         MarkdownOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms)
239 }
240