Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/markup/blockrule/PhutilRemarkupCodeBlockRule.php
12241 views
1
<?php
2
3
final class PhutilRemarkupCodeBlockRule extends PhutilRemarkupBlockRule {
4
5
public function getMatchingLineCount(array $lines, $cursor) {
6
$num_lines = 0;
7
$match_ticks = null;
8
if (preg_match('/^(\s{2,}).+/', $lines[$cursor])) {
9
$match_ticks = false;
10
} else if (preg_match('/^\s*(```)/', $lines[$cursor])) {
11
$match_ticks = true;
12
} else {
13
return $num_lines;
14
}
15
16
$num_lines++;
17
18
if ($match_ticks &&
19
preg_match('/^\s*(```)(.*)(```)\s*$/', $lines[$cursor])) {
20
return $num_lines;
21
}
22
23
$cursor++;
24
25
while (isset($lines[$cursor])) {
26
if ($match_ticks) {
27
if (preg_match('/```\s*$/', $lines[$cursor])) {
28
$num_lines++;
29
break;
30
}
31
$num_lines++;
32
} else {
33
if (strlen(trim($lines[$cursor]))) {
34
if (!preg_match('/^\s{2,}/', $lines[$cursor])) {
35
break;
36
}
37
}
38
$num_lines++;
39
}
40
$cursor++;
41
}
42
43
return $num_lines;
44
}
45
46
public function markupText($text, $children) {
47
if (preg_match('/^\s*```/', $text)) {
48
// If this is a ```-style block, trim off the backticks and any leading
49
// blank line.
50
$text = preg_replace('/^\s*```(\s*\n)?/', '', $text);
51
$text = preg_replace('/```\s*$/', '', $text);
52
}
53
54
$lines = explode("\n", $text);
55
while ($lines && !strlen(last($lines))) {
56
unset($lines[last_key($lines)]);
57
}
58
59
$options = array(
60
'counterexample' => false,
61
'lang' => null,
62
'name' => null,
63
'lines' => null,
64
);
65
66
$parser = new PhutilSimpleOptions();
67
$custom = $parser->parse(head($lines));
68
if ($custom) {
69
$valid = true;
70
foreach ($custom as $key => $value) {
71
if (!array_key_exists($key, $options)) {
72
$valid = false;
73
break;
74
}
75
}
76
if ($valid) {
77
array_shift($lines);
78
$options = $custom + $options;
79
}
80
}
81
82
// Normalize the text back to a 0-level indent.
83
$min_indent = 80;
84
foreach ($lines as $line) {
85
for ($ii = 0; $ii < strlen($line); $ii++) {
86
if ($line[$ii] != ' ') {
87
$min_indent = min($ii, $min_indent);
88
break;
89
}
90
}
91
}
92
93
$text = implode("\n", $lines);
94
if ($min_indent) {
95
$indent_string = str_repeat(' ', $min_indent);
96
$text = preg_replace('/^'.$indent_string.'/m', '', $text);
97
}
98
99
if ($this->getEngine()->isTextMode()) {
100
$out = array();
101
102
$header = array();
103
if ($options['counterexample']) {
104
$header[] = 'counterexample';
105
}
106
if ($options['name'] != '') {
107
$header[] = 'name='.$options['name'];
108
}
109
if ($header) {
110
$out[] = implode(', ', $header);
111
}
112
113
$text = preg_replace('/^/m', ' ', $text);
114
$out[] = $text;
115
116
return implode("\n", $out);
117
}
118
119
if (empty($options['lang'])) {
120
// If the user hasn't specified "lang=..." explicitly, try to guess the
121
// language. If we fail, fall back to configured defaults.
122
$lang = PhutilLanguageGuesser::guessLanguage($text);
123
if (!$lang) {
124
$lang = nonempty(
125
$this->getEngine()->getConfig('phutil.codeblock.language-default'),
126
'text');
127
}
128
$options['lang'] = $lang;
129
}
130
131
$code_body = $this->highlightSource($text, $options);
132
133
$name_header = null;
134
$block_style = null;
135
if ($this->getEngine()->isHTMLMailMode()) {
136
$map = $this->getEngine()->getConfig('phutil.codeblock.style-map');
137
138
if ($map) {
139
$raw_body = id(new PhutilPygmentizeParser())
140
->setMap($map)
141
->parse((string)$code_body);
142
$code_body = phutil_safe_html($raw_body);
143
}
144
145
$style_rules = array(
146
'padding: 6px 12px;',
147
'font-size: 13px;',
148
'font-weight: bold;',
149
'display: inline-block;',
150
'border-top-left-radius: 3px;',
151
'border-top-right-radius: 3px;',
152
'color: rgba(0,0,0,.75);',
153
);
154
155
if ($options['counterexample']) {
156
$style_rules[] = 'background: #f7e6e6';
157
} else {
158
$style_rules[] = 'background: rgba(71, 87, 120, 0.08);';
159
}
160
161
$header_attributes = array(
162
'style' => implode(' ', $style_rules),
163
);
164
165
$block_style = 'margin: 12px 0;';
166
} else {
167
$header_attributes = array(
168
'class' => 'remarkup-code-header',
169
);
170
}
171
172
if ($options['name']) {
173
$name_header = phutil_tag(
174
'div',
175
$header_attributes,
176
$options['name']);
177
}
178
179
$class = 'remarkup-code-block';
180
if ($options['counterexample']) {
181
$class = 'remarkup-code-block code-block-counterexample';
182
}
183
184
$attributes = array(
185
'class' => $class,
186
'style' => $block_style,
187
'data-code-lang' => $options['lang'],
188
'data-sigil' => 'remarkup-code-block',
189
);
190
191
return phutil_tag(
192
'div',
193
$attributes,
194
array($name_header, $code_body));
195
}
196
197
private function highlightSource($text, array $options) {
198
if ($options['counterexample']) {
199
$aux_class = ' remarkup-counterexample';
200
} else {
201
$aux_class = null;
202
}
203
204
$aux_style = null;
205
206
if ($this->getEngine()->isHTMLMailMode()) {
207
$aux_style = array(
208
'font: 11px/15px "Menlo", "Consolas", "Monaco", monospace;',
209
'padding: 12px;',
210
'margin: 0;',
211
);
212
213
if ($options['counterexample']) {
214
$aux_style[] = 'background: #f7e6e6;';
215
} else {
216
$aux_style[] = 'background: rgba(71, 87, 120, 0.08);';
217
}
218
219
$aux_style = implode(' ', $aux_style);
220
}
221
222
if ($options['lines']) {
223
// Put a minimum size on this because the scrollbar is otherwise
224
// unusable.
225
$height = max(6, (int)$options['lines']);
226
$aux_style = $aux_style
227
.' '
228
.'max-height: '
229
.(2 * $height)
230
.'em; overflow: auto;';
231
}
232
233
$engine = $this->getEngine()->getConfig('syntax-highlighter.engine');
234
if (!$engine) {
235
$engine = 'PhutilDefaultSyntaxHighlighterEngine';
236
}
237
$engine = newv($engine, array());
238
$engine->setConfig(
239
'pygments.enabled',
240
$this->getEngine()->getConfig('pygments.enabled'));
241
return phutil_tag(
242
'pre',
243
array(
244
'class' => 'remarkup-code'.$aux_class,
245
'style' => $aux_style,
246
),
247
PhutilSafeHTML::applyFunction(
248
'rtrim',
249
$engine->highlightSource($options['lang'], $text)));
250
}
251
252
}
253
254