Chris@17
|
1 <?php
|
Chris@17
|
2 namespace JakubOnderka\PhpConsoleHighlighter;
|
Chris@17
|
3
|
Chris@17
|
4 use JakubOnderka\PhpConsoleColor\ConsoleColor;
|
Chris@17
|
5
|
Chris@17
|
6 class Highlighter
|
Chris@17
|
7 {
|
Chris@17
|
8 const TOKEN_DEFAULT = 'token_default',
|
Chris@17
|
9 TOKEN_COMMENT = 'token_comment',
|
Chris@17
|
10 TOKEN_STRING = 'token_string',
|
Chris@17
|
11 TOKEN_HTML = 'token_html',
|
Chris@17
|
12 TOKEN_KEYWORD = 'token_keyword';
|
Chris@17
|
13
|
Chris@17
|
14 const ACTUAL_LINE_MARK = 'actual_line_mark',
|
Chris@17
|
15 LINE_NUMBER = 'line_number';
|
Chris@17
|
16
|
Chris@17
|
17 /** @var ConsoleColor */
|
Chris@17
|
18 private $color;
|
Chris@17
|
19
|
Chris@17
|
20 /** @var array */
|
Chris@17
|
21 private $defaultTheme = array(
|
Chris@17
|
22 self::TOKEN_STRING => 'red',
|
Chris@17
|
23 self::TOKEN_COMMENT => 'yellow',
|
Chris@17
|
24 self::TOKEN_KEYWORD => 'green',
|
Chris@17
|
25 self::TOKEN_DEFAULT => 'default',
|
Chris@17
|
26 self::TOKEN_HTML => 'cyan',
|
Chris@17
|
27
|
Chris@17
|
28 self::ACTUAL_LINE_MARK => 'red',
|
Chris@17
|
29 self::LINE_NUMBER => 'dark_gray',
|
Chris@17
|
30 );
|
Chris@17
|
31
|
Chris@17
|
32 /**
|
Chris@17
|
33 * @param ConsoleColor $color
|
Chris@17
|
34 * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
|
Chris@17
|
35 */
|
Chris@17
|
36 public function __construct(ConsoleColor $color)
|
Chris@17
|
37 {
|
Chris@17
|
38 $this->color = $color;
|
Chris@17
|
39
|
Chris@17
|
40 foreach ($this->defaultTheme as $name => $styles) {
|
Chris@17
|
41 if (!$this->color->hasTheme($name)) {
|
Chris@17
|
42 $this->color->addTheme($name, $styles);
|
Chris@17
|
43 }
|
Chris@17
|
44 }
|
Chris@17
|
45 }
|
Chris@17
|
46
|
Chris@17
|
47 /**
|
Chris@17
|
48 * @param string $source
|
Chris@17
|
49 * @param int $lineNumber
|
Chris@17
|
50 * @param int $linesBefore
|
Chris@17
|
51 * @param int $linesAfter
|
Chris@17
|
52 * @return string
|
Chris@17
|
53 * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
|
Chris@17
|
54 * @throws \InvalidArgumentException
|
Chris@17
|
55 */
|
Chris@17
|
56 public function getCodeSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2)
|
Chris@17
|
57 {
|
Chris@17
|
58 $tokenLines = $this->getHighlightedLines($source);
|
Chris@17
|
59
|
Chris@17
|
60 $offset = $lineNumber - $linesBefore - 1;
|
Chris@17
|
61 $offset = max($offset, 0);
|
Chris@17
|
62 $length = $linesAfter + $linesBefore + 1;
|
Chris@17
|
63 $tokenLines = array_slice($tokenLines, $offset, $length, $preserveKeys = true);
|
Chris@17
|
64
|
Chris@17
|
65 $lines = $this->colorLines($tokenLines);
|
Chris@17
|
66
|
Chris@17
|
67 return $this->lineNumbers($lines, $lineNumber);
|
Chris@17
|
68 }
|
Chris@17
|
69
|
Chris@17
|
70 /**
|
Chris@17
|
71 * @param string $source
|
Chris@17
|
72 * @return string
|
Chris@17
|
73 * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
|
Chris@17
|
74 * @throws \InvalidArgumentException
|
Chris@17
|
75 */
|
Chris@17
|
76 public function getWholeFile($source)
|
Chris@17
|
77 {
|
Chris@17
|
78 $tokenLines = $this->getHighlightedLines($source);
|
Chris@17
|
79 $lines = $this->colorLines($tokenLines);
|
Chris@17
|
80 return implode(PHP_EOL, $lines);
|
Chris@17
|
81 }
|
Chris@17
|
82
|
Chris@17
|
83 /**
|
Chris@17
|
84 * @param string $source
|
Chris@17
|
85 * @return string
|
Chris@17
|
86 * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
|
Chris@17
|
87 * @throws \InvalidArgumentException
|
Chris@17
|
88 */
|
Chris@17
|
89 public function getWholeFileWithLineNumbers($source)
|
Chris@17
|
90 {
|
Chris@17
|
91 $tokenLines = $this->getHighlightedLines($source);
|
Chris@17
|
92 $lines = $this->colorLines($tokenLines);
|
Chris@17
|
93 return $this->lineNumbers($lines);
|
Chris@17
|
94 }
|
Chris@17
|
95
|
Chris@17
|
96 /**
|
Chris@17
|
97 * @param string $source
|
Chris@17
|
98 * @return array
|
Chris@17
|
99 */
|
Chris@17
|
100 private function getHighlightedLines($source)
|
Chris@17
|
101 {
|
Chris@17
|
102 $source = str_replace(array("\r\n", "\r"), "\n", $source);
|
Chris@17
|
103 $tokens = $this->tokenize($source);
|
Chris@17
|
104 return $this->splitToLines($tokens);
|
Chris@17
|
105 }
|
Chris@17
|
106
|
Chris@17
|
107 /**
|
Chris@17
|
108 * @param string $source
|
Chris@17
|
109 * @return array
|
Chris@17
|
110 */
|
Chris@17
|
111 private function tokenize($source)
|
Chris@17
|
112 {
|
Chris@17
|
113 $tokens = token_get_all($source);
|
Chris@17
|
114
|
Chris@17
|
115 $output = array();
|
Chris@17
|
116 $currentType = null;
|
Chris@17
|
117 $buffer = '';
|
Chris@17
|
118
|
Chris@17
|
119 foreach ($tokens as $token) {
|
Chris@17
|
120 if (is_array($token)) {
|
Chris@17
|
121 switch ($token[0]) {
|
Chris@17
|
122 case T_WHITESPACE:
|
Chris@17
|
123 break;
|
Chris@17
|
124
|
Chris@17
|
125 case T_OPEN_TAG:
|
Chris@17
|
126 case T_OPEN_TAG_WITH_ECHO:
|
Chris@17
|
127 case T_CLOSE_TAG:
|
Chris@17
|
128 case T_STRING:
|
Chris@17
|
129 case T_VARIABLE:
|
Chris@17
|
130
|
Chris@17
|
131 // Constants
|
Chris@17
|
132 case T_DIR:
|
Chris@17
|
133 case T_FILE:
|
Chris@17
|
134 case T_METHOD_C:
|
Chris@17
|
135 case T_DNUMBER:
|
Chris@17
|
136 case T_LNUMBER:
|
Chris@17
|
137 case T_NS_C:
|
Chris@17
|
138 case T_LINE:
|
Chris@17
|
139 case T_CLASS_C:
|
Chris@17
|
140 case T_FUNC_C:
|
Chris@17
|
141 case T_TRAIT_C:
|
Chris@17
|
142 $newType = self::TOKEN_DEFAULT;
|
Chris@17
|
143 break;
|
Chris@17
|
144
|
Chris@17
|
145 case T_COMMENT:
|
Chris@17
|
146 case T_DOC_COMMENT:
|
Chris@17
|
147 $newType = self::TOKEN_COMMENT;
|
Chris@17
|
148 break;
|
Chris@17
|
149
|
Chris@17
|
150 case T_ENCAPSED_AND_WHITESPACE:
|
Chris@17
|
151 case T_CONSTANT_ENCAPSED_STRING:
|
Chris@17
|
152 $newType = self::TOKEN_STRING;
|
Chris@17
|
153 break;
|
Chris@17
|
154
|
Chris@17
|
155 case T_INLINE_HTML:
|
Chris@17
|
156 $newType = self::TOKEN_HTML;
|
Chris@17
|
157 break;
|
Chris@17
|
158
|
Chris@17
|
159 default:
|
Chris@17
|
160 $newType = self::TOKEN_KEYWORD;
|
Chris@17
|
161 }
|
Chris@17
|
162 } else {
|
Chris@17
|
163 $newType = $token === '"' ? self::TOKEN_STRING : self::TOKEN_KEYWORD;
|
Chris@17
|
164 }
|
Chris@17
|
165
|
Chris@17
|
166 if ($currentType === null) {
|
Chris@17
|
167 $currentType = $newType;
|
Chris@17
|
168 }
|
Chris@17
|
169
|
Chris@17
|
170 if ($currentType !== $newType) {
|
Chris@17
|
171 $output[] = array($currentType, $buffer);
|
Chris@17
|
172 $buffer = '';
|
Chris@17
|
173 $currentType = $newType;
|
Chris@17
|
174 }
|
Chris@17
|
175
|
Chris@17
|
176 $buffer .= is_array($token) ? $token[1] : $token;
|
Chris@17
|
177 }
|
Chris@17
|
178
|
Chris@17
|
179 if (isset($newType)) {
|
Chris@17
|
180 $output[] = array($newType, $buffer);
|
Chris@17
|
181 }
|
Chris@17
|
182
|
Chris@17
|
183 return $output;
|
Chris@17
|
184 }
|
Chris@17
|
185
|
Chris@17
|
186 /**
|
Chris@17
|
187 * @param array $tokens
|
Chris@17
|
188 * @return array
|
Chris@17
|
189 */
|
Chris@17
|
190 private function splitToLines(array $tokens)
|
Chris@17
|
191 {
|
Chris@17
|
192 $lines = array();
|
Chris@17
|
193
|
Chris@17
|
194 $line = array();
|
Chris@17
|
195 foreach ($tokens as $token) {
|
Chris@17
|
196 foreach (explode("\n", $token[1]) as $count => $tokenLine) {
|
Chris@17
|
197 if ($count > 0) {
|
Chris@17
|
198 $lines[] = $line;
|
Chris@17
|
199 $line = array();
|
Chris@17
|
200 }
|
Chris@17
|
201
|
Chris@17
|
202 if ($tokenLine === '') {
|
Chris@17
|
203 continue;
|
Chris@17
|
204 }
|
Chris@17
|
205
|
Chris@17
|
206 $line[] = array($token[0], $tokenLine);
|
Chris@17
|
207 }
|
Chris@17
|
208 }
|
Chris@17
|
209
|
Chris@17
|
210 $lines[] = $line;
|
Chris@17
|
211
|
Chris@17
|
212 return $lines;
|
Chris@17
|
213 }
|
Chris@17
|
214
|
Chris@17
|
215 /**
|
Chris@17
|
216 * @param array $tokenLines
|
Chris@17
|
217 * @return array
|
Chris@17
|
218 * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
|
Chris@17
|
219 * @throws \InvalidArgumentException
|
Chris@17
|
220 */
|
Chris@17
|
221 private function colorLines(array $tokenLines)
|
Chris@17
|
222 {
|
Chris@17
|
223 $lines = array();
|
Chris@17
|
224 foreach ($tokenLines as $lineCount => $tokenLine) {
|
Chris@17
|
225 $line = '';
|
Chris@17
|
226 foreach ($tokenLine as $token) {
|
Chris@17
|
227 list($tokenType, $tokenValue) = $token;
|
Chris@17
|
228 if ($this->color->hasTheme($tokenType)) {
|
Chris@17
|
229 $line .= $this->color->apply($tokenType, $tokenValue);
|
Chris@17
|
230 } else {
|
Chris@17
|
231 $line .= $tokenValue;
|
Chris@17
|
232 }
|
Chris@17
|
233 }
|
Chris@17
|
234 $lines[$lineCount] = $line;
|
Chris@17
|
235 }
|
Chris@17
|
236
|
Chris@17
|
237 return $lines;
|
Chris@17
|
238 }
|
Chris@17
|
239
|
Chris@17
|
240 /**
|
Chris@17
|
241 * @param array $lines
|
Chris@17
|
242 * @param null|int $markLine
|
Chris@17
|
243 * @return string
|
Chris@17
|
244 * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
|
Chris@17
|
245 */
|
Chris@17
|
246 private function lineNumbers(array $lines, $markLine = null)
|
Chris@17
|
247 {
|
Chris@17
|
248 end($lines);
|
Chris@17
|
249 $lineStrlen = strlen(key($lines) + 1);
|
Chris@17
|
250
|
Chris@17
|
251 $snippet = '';
|
Chris@17
|
252 foreach ($lines as $i => $line) {
|
Chris@17
|
253 if ($markLine !== null) {
|
Chris@17
|
254 $snippet .= ($markLine === $i + 1 ? $this->color->apply(self::ACTUAL_LINE_MARK, ' > ') : ' ');
|
Chris@17
|
255 }
|
Chris@17
|
256
|
Chris@17
|
257 $snippet .= $this->color->apply(self::LINE_NUMBER, str_pad($i + 1, $lineStrlen, ' ', STR_PAD_LEFT) . '| ');
|
Chris@17
|
258 $snippet .= $line . PHP_EOL;
|
Chris@17
|
259 }
|
Chris@17
|
260
|
Chris@17
|
261 return $snippet;
|
Chris@17
|
262 }
|
Chris@17
|
263 } |