Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace NunoMaduro\Collision;
6
 
7
use NunoMaduro\Collision\Contracts\Highlighter as HighlighterContract;
8
 
9
/**
10
 * @internal
11
 */
12
final class Highlighter implements HighlighterContract
13
{
14
    public const TOKEN_DEFAULT = 'token_default';
15
 
16
    public const TOKEN_COMMENT = 'token_comment';
17
 
18
    public const TOKEN_STRING = 'token_string';
19
 
20
    public const TOKEN_HTML = 'token_html';
21
 
22
    public const TOKEN_KEYWORD = 'token_keyword';
23
 
24
    public const ACTUAL_LINE_MARK = 'actual_line_mark';
25
 
26
    public const LINE_NUMBER = 'line_number';
27
 
28
    private const ARROW_SYMBOL = '>';
29
 
30
    private const DELIMITER = '|';
31
 
32
    private const ARROW_SYMBOL_UTF8 = '➜';
33
 
34
    private const DELIMITER_UTF8 = 'â–•'; // 'â–¶';
35
 
36
    private const LINE_NUMBER_DIVIDER = 'line_divider';
37
 
38
    private const MARKED_LINE_NUMBER = 'marked_line';
39
 
40
    private const WIDTH = 3;
41
 
42
    /**
43
     * Holds the theme.
44
     *
45
     * @var array
46
     */
47
    private const THEME = [
48
        self::TOKEN_STRING => ['light_gray'],
49
        self::TOKEN_COMMENT => ['dark_gray', 'italic'],
50
        self::TOKEN_KEYWORD => ['magenta', 'bold'],
51
        self::TOKEN_DEFAULT => ['default', 'bold'],
52
        self::TOKEN_HTML => ['blue', 'bold'],
53
 
54
        self::ACTUAL_LINE_MARK => ['red', 'bold'],
55
        self::LINE_NUMBER => ['dark_gray'],
56
        self::MARKED_LINE_NUMBER => ['italic', 'bold'],
57
        self::LINE_NUMBER_DIVIDER => ['dark_gray'],
58
    ];
59
 
60
    /** @var ConsoleColor */
61
    private $color;
62
 
63
    /** @var array */
64
    private const DEFAULT_THEME = [
65
        self::TOKEN_STRING => 'red',
66
        self::TOKEN_COMMENT => 'yellow',
67
        self::TOKEN_KEYWORD => 'green',
68
        self::TOKEN_DEFAULT => 'default',
69
        self::TOKEN_HTML => 'cyan',
70
 
71
        self::ACTUAL_LINE_MARK => 'dark_gray',
72
        self::LINE_NUMBER => 'dark_gray',
73
        self::MARKED_LINE_NUMBER => 'dark_gray',
74
        self::LINE_NUMBER_DIVIDER => 'dark_gray',
75
    ];
76
 
77
    /** @var string */
78
    private $delimiter = self::DELIMITER_UTF8;
79
 
80
    /** @var string */
81
    private $arrow = self::ARROW_SYMBOL_UTF8;
82
 
83
    /**
84
     * @var string
85
     */
86
    private const NO_MARK = '    ';
87
 
88
    /**
89
     * Creates an instance of the Highlighter.
90
     */
91
    public function __construct(ConsoleColor $color = null, bool $UTF8 = true)
92
    {
93
        $this->color = $color ?: new ConsoleColor();
94
 
95
        foreach (self::DEFAULT_THEME as $name => $styles) {
96
            if (! $this->color->hasTheme($name)) {
97
                $this->color->addTheme($name, $styles);
98
            }
99
        }
100
 
101
        foreach (self::THEME as $name => $styles) {
102
            $this->color->addTheme($name, $styles);
103
        }
104
        if (! $UTF8) {
105
            $this->delimiter = self::DELIMITER;
106
            $this->arrow = self::ARROW_SYMBOL;
107
        }
108
        $this->delimiter .= ' ';
109
    }
110
 
111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function highlight(string $content, int $line): string
115
    {
116
        return rtrim($this->getCodeSnippet($content, $line, 4, 4));
117
    }
118
 
119
    /**
120
     * @param  string  $source
121
     * @param  int  $lineNumber
122
     * @param  int  $linesBefore
123
     * @param  int  $linesAfter
124
     */
125
    public function getCodeSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2): string
126
    {
127
        $tokenLines = $this->getHighlightedLines($source);
128
 
129
        $offset = $lineNumber - $linesBefore - 1;
130
        $offset = max($offset, 0);
131
        $length = $linesAfter + $linesBefore + 1;
132
        $tokenLines = array_slice($tokenLines, $offset, $length, $preserveKeys = true);
133
 
134
        $lines = $this->colorLines($tokenLines);
135
 
136
        return $this->lineNumbers($lines, $lineNumber);
137
    }
138
 
139
    /**
140
     * @param  string  $source
141
     */
142
    private function getHighlightedLines($source): array
143
    {
144
        $source = str_replace(["\r\n", "\r"], "\n", $source);
145
        $tokens = $this->tokenize($source);
146
 
147
        return $this->splitToLines($tokens);
148
    }
149
 
150
    /**
151
     * @param  string  $source
152
     */
153
    private function tokenize($source): array
154
    {
155
        $tokens = token_get_all($source);
156
 
157
        $output = [];
158
        $currentType = null;
159
        $buffer = '';
160
 
161
        foreach ($tokens as $token) {
162
            if (is_array($token)) {
163
                switch ($token[0]) {
164
                    case T_WHITESPACE:
165
                        break;
166
 
167
                    case T_OPEN_TAG:
168
                    case T_OPEN_TAG_WITH_ECHO:
169
                    case T_CLOSE_TAG:
170
                    case T_STRING:
171
                    case T_VARIABLE:
172
                        // Constants
173
                    case T_DIR:
174
                    case T_FILE:
175
                    case T_METHOD_C:
176
                    case T_DNUMBER:
177
                    case T_LNUMBER:
178
                    case T_NS_C:
179
                    case T_LINE:
180
                    case T_CLASS_C:
181
                    case T_FUNC_C:
182
                    case T_TRAIT_C:
183
                        $newType = self::TOKEN_DEFAULT;
184
                        break;
185
 
186
                    case T_COMMENT:
187
                    case T_DOC_COMMENT:
188
                        $newType = self::TOKEN_COMMENT;
189
                        break;
190
 
191
                    case T_ENCAPSED_AND_WHITESPACE:
192
                    case T_CONSTANT_ENCAPSED_STRING:
193
                        $newType = self::TOKEN_STRING;
194
                        break;
195
 
196
                    case T_INLINE_HTML:
197
                        $newType = self::TOKEN_HTML;
198
                        break;
199
 
200
                    default:
201
                        $newType = self::TOKEN_KEYWORD;
202
                }
203
            } else {
204
                $newType = $token === '"' ? self::TOKEN_STRING : self::TOKEN_KEYWORD;
205
            }
206
 
207
            if ($currentType === null) {
208
                $currentType = $newType;
209
            }
210
 
211
            if ($currentType !== $newType) {
212
                $output[] = [$currentType, $buffer];
213
                $buffer = '';
214
                $currentType = $newType;
215
            }
216
 
217
            $buffer .= is_array($token) ? $token[1] : $token;
218
        }
219
 
220
        if (isset($newType)) {
221
            $output[] = [$newType, $buffer];
222
        }
223
 
224
        return $output;
225
    }
226
 
227
    private function splitToLines(array $tokens): array
228
    {
229
        $lines = [];
230
 
231
        $line = [];
232
        foreach ($tokens as $token) {
233
            foreach (explode("\n", $token[1]) as $count => $tokenLine) {
234
                if ($count > 0) {
235
                    $lines[] = $line;
236
                    $line = [];
237
                }
238
 
239
                if ($tokenLine === '') {
240
                    continue;
241
                }
242
 
243
                $line[] = [$token[0], $tokenLine];
244
            }
245
        }
246
 
247
        $lines[] = $line;
248
 
249
        return $lines;
250
    }
251
 
252
    private function colorLines(array $tokenLines): array
253
    {
254
        $lines = [];
255
        foreach ($tokenLines as $lineCount => $tokenLine) {
256
            $line = '';
257
            foreach ($tokenLine as $token) {
258
                [$tokenType, $tokenValue] = $token;
259
                if ($this->color->hasTheme($tokenType)) {
260
                    $line .= $this->color->apply($tokenType, $tokenValue);
261
                } else {
262
                    $line .= $tokenValue;
263
                }
264
            }
265
            $lines[$lineCount] = $line;
266
        }
267
 
268
        return $lines;
269
    }
270
 
271
    /**
272
     * @param  int|null  $markLine
273
     */
274
    private function lineNumbers(array $lines, $markLine = null): string
275
    {
276
        $lineStrlen = strlen((string) (array_key_last($lines) + 1));
277
        $lineStrlen = $lineStrlen < self::WIDTH ? self::WIDTH : $lineStrlen;
278
        $snippet = '';
279
        $mark = '  '.$this->arrow.' ';
280
        foreach ($lines as $i => $line) {
281
            $coloredLineNumber = $this->coloredLineNumber(self::LINE_NUMBER, $i, $lineStrlen);
282
 
283
            if (null !== $markLine) {
284
                $snippet .=
285
                    ($markLine === $i + 1
286
                        ? $this->color->apply(self::ACTUAL_LINE_MARK, $mark)
287
                        : self::NO_MARK
288
                    );
289
 
290
                $coloredLineNumber =
291
                    ($markLine === $i + 1 ?
292
                        $this->coloredLineNumber(self::MARKED_LINE_NUMBER, $i, $lineStrlen) :
293
                        $coloredLineNumber
294
                    );
295
            }
296
            $snippet .= $coloredLineNumber;
297
 
298
            $snippet .=
299
                $this->color->apply(self::LINE_NUMBER_DIVIDER, $this->delimiter);
300
 
301
            $snippet .= $line.PHP_EOL;
302
        }
303
 
304
        return $snippet;
305
    }
306
 
307
    /**
308
     * @param  string  $style
309
     * @param  int  $i
310
     * @param  int  $lineStrlen
311
     */
312
    private function coloredLineNumber($style, $i, $lineStrlen): string
313
    {
314
        return $this->color->apply($style, str_pad((string) ($i + 1), $lineStrlen, ' ', STR_PAD_LEFT));
315
    }
316
}