Serenity Operating System
1/*
2 * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/StringBuilder.h>
28#include <LibMarkdown/MDCodeBlock.h>
29
30MDText::Style MDCodeBlock::style() const
31{
32 if (m_style_spec.spans().is_empty())
33 return {};
34 return m_style_spec.spans()[0].style;
35}
36
37String MDCodeBlock::style_language() const
38{
39 if (m_style_spec.spans().is_empty())
40 return {};
41 return m_style_spec.spans()[0].text;
42}
43
44String MDCodeBlock::render_to_html() const
45{
46 StringBuilder builder;
47
48 String style_language = this->style_language();
49 MDText::Style style = this->style();
50
51 if (style.strong)
52 builder.append("<b>");
53 if (style.emph)
54 builder.append("<i>");
55
56 if (style_language.is_null())
57 builder.append("<code style=\"white-space: pre;\">");
58 else
59 builder.appendf("<code style=\"white-space: pre;\" class=\"%s\">", style_language.characters());
60
61 // TODO: This should also be done in other places.
62 for (size_t i = 0; i < m_code.length(); i++)
63 if (m_code[i] == '<')
64 builder.append("<");
65 else if (m_code[i] == '>')
66 builder.append(">");
67 else if (m_code[i] == '&')
68 builder.append("&");
69 else
70 builder.append(m_code[i]);
71
72 builder.append("</code>");
73
74 if (style.emph)
75 builder.append("</i>");
76 if (style.strong)
77 builder.append("</b>");
78
79 builder.append('\n');
80
81 return builder.build();
82}
83
84String MDCodeBlock::render_for_terminal() const
85{
86 StringBuilder builder;
87
88 MDText::Style style = this->style();
89 bool needs_styling = style.strong || style.emph;
90 if (needs_styling) {
91 builder.append("\033[");
92 bool first = true;
93 if (style.strong) {
94 builder.append('1');
95 first = false;
96 }
97 if (style.emph) {
98 if (!first)
99 builder.append(';');
100 builder.append('4');
101 }
102 builder.append('m');
103 }
104
105 builder.append(m_code);
106
107 if (needs_styling)
108 builder.append("\033[0m");
109
110 builder.append("\n\n");
111
112 return builder.build();
113}
114
115bool MDCodeBlock::parse(Vector<StringView>::ConstIterator& lines)
116{
117 if (lines.is_end())
118 return false;
119
120 constexpr auto tick_tick_tick = "```";
121
122 StringView line = *lines;
123 if (!line.starts_with(tick_tick_tick))
124 return false;
125
126 // Our Markdown extension: we allow
127 // specifying a style and a language
128 // for a code block, like so:
129 //
130 // ```**sh**
131 // $ echo hello friends!
132 // ````
133 //
134 // The code block will be made bold,
135 // and if possible syntax-highlighted
136 // as appropriate for a shell script.
137 StringView style_spec = line.substring_view(3, line.length() - 3);
138 bool success = m_style_spec.parse(style_spec);
139 ASSERT(success);
140
141 ++lines;
142
143 bool first = true;
144 StringBuilder builder;
145
146 while (true) {
147 if (lines.is_end())
148 break;
149 line = *lines;
150 ++lines;
151 if (line == tick_tick_tick)
152 break;
153 if (!first)
154 builder.append('\n');
155 builder.append(line);
156 first = false;
157 }
158
159 m_code = builder.build();
160 return true;
161}