~ubuntu-branches/ubuntu/vivid/phabricator/vivid-proposed

« back to all changes in this revision

Viewing changes to libphutil/src/markup/engine/remarkup/blockrule/PhutilRemarkupCodeBlockRule.php

  • Committer: Package Import Robot
  • Author(s): Richard Sellam
  • Date: 2014-10-23 20:49:26 UTC
  • mfrom: (0.2.1) (0.1.1)
  • Revision ID: package-import@ubuntu.com-20141023204926-vq80u1op4df44azb
Tags: 0~git20141023-1
Initial release (closes: #703046)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
          'php');
 
127
      }
 
128
      $options['lang'] = $lang;
 
129
    }
 
130
 
 
131
    $code_body = $this->highlightSource($text, $options);
 
132
 
 
133
    $name_header = null;
 
134
    if ($options['name']) {
 
135
      $name_header = phutil_tag(
 
136
        'div',
 
137
        array(
 
138
          'class' => 'remarkup-code-header',
 
139
        ),
 
140
        $options['name']);
 
141
    }
 
142
 
 
143
    return phutil_tag(
 
144
      'div',
 
145
      array(
 
146
        'class' => 'remarkup-code-block',
 
147
        'data-code-lang' => $options['lang'],
 
148
        'data-sigil' => 'remarkup-code-block',
 
149
      ),
 
150
      array($name_header, $code_body));
 
151
  }
 
152
 
 
153
  private function highlightSource($text, array $options) {
 
154
    if ($options['counterexample']) {
 
155
      $aux_class = ' remarkup-counterexample';
 
156
    } else {
 
157
      $aux_class = null;
 
158
    }
 
159
 
 
160
    $aux_style = null;
 
161
    if ($options['lines']) {
 
162
      // Put a minimum size on this because the scrollbar is otherwise
 
163
      // unusable.
 
164
      $height = max(6, (int)$options['lines']);
 
165
      $aux_style = 'max-height: '.(2 * $height).'em;';
 
166
    }
 
167
 
 
168
    $engine = $this->getEngine()->getConfig('syntax-highlighter.engine');
 
169
    if (!$engine) {
 
170
      $engine = 'PhutilDefaultSyntaxHighlighterEngine';
 
171
    }
 
172
    $engine = newv($engine, array());
 
173
    $engine->setConfig(
 
174
      'pygments.enabled',
 
175
      $this->getEngine()->getConfig('pygments.enabled'));
 
176
    return phutil_tag(
 
177
      'pre',
 
178
      array(
 
179
        'class' => 'remarkup-code'.$aux_class,
 
180
        'style' => $aux_style,
 
181
      ),
 
182
      PhutilSafeHTML::applyFunction(
 
183
        'rtrim',
 
184
        $engine->highlightSource($options['lang'], $text)));
 
185
  }
 
186
 
 
187
}