Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
11.76% |
2 / 17 |
CRAP | |
16.85% |
15 / 89 |
Document | |
0.00% |
0 / 1 |
|
11.76% |
2 / 17 |
1007.26 | |
16.85% |
15 / 89 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
__get | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
new | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
row | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
isEmpty | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
open | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 10 |
|||
save | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 5 |
|||
insert | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
delete | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
insertRow | |
0.00% |
0 / 1 |
7.54 | |
53.33% |
8 / 15 |
|||
deleteRow | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
isDirty | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
insertNewline | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 12 |
|||
selectSyntaxHighlight | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 5 |
|||
rowsToString | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
refreshSyntax | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
refreshPHPSyntax | |
0.00% |
0 / 1 |
2.50 | |
50.00% |
2 / 4 |
1 | <?php declare(strict_types=1); |
2 | |
3 | namespace Aviat\Kilo; |
4 | |
5 | use Aviat\Kilo\Enum\RawKeyCode; |
6 | use Aviat\Kilo\Enum\KeyType; |
7 | use Aviat\Kilo\Tokens\PHP8; |
8 | use Aviat\Kilo\Type\Point; |
9 | |
10 | /** |
11 | * The representation of the current document being edited |
12 | * |
13 | * @property-read int $numRows |
14 | */ |
15 | class Document { |
16 | public FileType $fileType; |
17 | |
18 | // Tokens for highlighting PHP |
19 | public array $tokens = []; |
20 | |
21 | private function __construct( |
22 | public string $filename = '', |
23 | public array $rows = [], |
24 | public bool $dirty = FALSE, |
25 | ) { |
26 | $this->fileType = FileType::from($this->filename); |
27 | } |
28 | |
29 | public function __get(string $name): ?int |
30 | { |
31 | if ($name === 'numRows') |
32 | { |
33 | return count($this->rows); |
34 | } |
35 | |
36 | return NULL; |
37 | } |
38 | |
39 | public static function new(): self |
40 | { |
41 | return new self(); |
42 | } |
43 | |
44 | public function row(int $index): Row |
45 | { |
46 | return (array_key_exists($index, $this->rows)) |
47 | ? $this->rows[$index] |
48 | : Row::default(); |
49 | } |
50 | |
51 | public function isEmpty(): bool |
52 | { |
53 | return empty($this->rows); |
54 | } |
55 | |
56 | // ------------------------------------------------------------------------ |
57 | // ! File I/O |
58 | // ------------------------------------------------------------------------ |
59 | |
60 | public function open(string $filename): ?self |
61 | { |
62 | $handle = fopen($filename, 'rb'); |
63 | if ($handle === FALSE) |
64 | { |
65 | return NULL; |
66 | } |
67 | |
68 | $this->__construct($filename); |
69 | |
70 | while (($line = fgets($handle)) !== FALSE) |
71 | { |
72 | // Remove line endings when reading the file |
73 | $this->rows[] = Row::new($this, rtrim($line), $this->numRows); |
74 | } |
75 | |
76 | fclose($handle); |
77 | |
78 | $this->dirty = false; |
79 | $this->selectSyntaxHighlight(); |
80 | |
81 | return $this; |
82 | } |
83 | |
84 | public function save(): int|false |
85 | { |
86 | $contents = $this->rowsToString(); |
87 | |
88 | $res = file_put_contents($this->filename, $contents); |
89 | |
90 | if ($res === strlen($contents)) |
91 | { |
92 | $this->dirty = FALSE; |
93 | } |
94 | |
95 | return $res; |
96 | } |
97 | |
98 | public function insert(Point $at, string $c): void |
99 | { |
100 | if ($at->y > $this->numRows) |
101 | { |
102 | return; |
103 | } |
104 | |
105 | if ($c === KeyType::ENTER || $c === RawKeyCode::CARRIAGE_RETURN) |
106 | { |
107 | $this->insertNewline($at); |
108 | $this->dirty = true; |
109 | return; |
110 | } |
111 | |
112 | $this->rows[$at->y]->insert($at->x, $c); |
113 | $this->dirty = true; |
114 | } |
115 | |
116 | public function delete(Point $at): void |
117 | { |
118 | if ($at->y > $this->numRows) |
119 | { |
120 | return; |
121 | } |
122 | |
123 | $row =& $this->rows[$at->y]; |
124 | |
125 | if ($at->x === $this->rows[$at->y]->size && $at->y + 1 < $this->numRows) |
126 | { |
127 | $this->rows[$at->y]->append($this->rows[$at->y + 1]->chars); |
128 | $this->deleteRow($at->y + 1); |
129 | } |
130 | else |
131 | { |
132 | $row->delete($at->x); |
133 | } |
134 | |
135 | $this->dirty = true; |
136 | } |
137 | |
138 | public function insertRow(int $at, string $s, bool $updateSyntax = TRUE): void |
139 | { |
140 | if ($at > $this->numRows) |
141 | { |
142 | return; |
143 | } |
144 | |
145 | $row = Row::new($this, $s, $at); |
146 | |
147 | if ($at === $this->numRows) |
148 | { |
149 | $this->rows[] = $row; |
150 | } |
151 | else |
152 | { |
153 | $this->rows = [ |
154 | ...array_slice($this->rows, 0, $at), |
155 | $row, |
156 | ...array_slice($this->rows, $at), |
157 | ]; |
158 | |
159 | // Update indexes of each row so that correct highlighting is done |
160 | for ($idx = $at; $idx < $this->numRows; $idx++) |
161 | { |
162 | $this->rows[$idx]->idx = $idx; |
163 | } |
164 | } |
165 | |
166 | ksort($this->rows); |
167 | |
168 | // $this->rows[$at]->highlight(); |
169 | |
170 | // Re-tokenize the file |
171 | if ($updateSyntax) |
172 | { |
173 | $this->refreshPHPSyntax(); |
174 | } |
175 | |
176 | $this->dirty = true; |
177 | } |
178 | |
179 | protected function deleteRow(int $at): void |
180 | { |
181 | if ($at < 0 || $at >= $this->numRows) |
182 | { |
183 | return; |
184 | } |
185 | |
186 | // Remove the row |
187 | unset($this->rows[$at]); |
188 | |
189 | // Re-index the array of rows |
190 | $this->rows = array_values($this->rows); |
191 | for ($i = $at; $i < $this->numRows; $i++) |
192 | { |
193 | $this->rows[$i]->idx = $i; |
194 | } |
195 | |
196 | // Re-tokenize the file |
197 | $this->refreshPHPSyntax(); |
198 | |
199 | $this->dirty = true; |
200 | } |
201 | |
202 | public function isDirty(): bool |
203 | { |
204 | return $this->dirty; |
205 | } |
206 | |
207 | protected function insertNewline(Point $at): void |
208 | { |
209 | if ($at->y > $this->numRows) |
210 | { |
211 | return; |
212 | } |
213 | |
214 | if ($at->y === $this->numRows) |
215 | { |
216 | $this->insertRow($this->numRows, ''); |
217 | } |
218 | else if ($at->x === 1) |
219 | { |
220 | $this->insertRow($at->y, ''); |
221 | } |
222 | else |
223 | { |
224 | $row = $this->rows[$at->y]; |
225 | $chars = $row->chars; |
226 | $newChars = substr($chars, 0, $at->x); |
227 | |
228 | // Truncate the previous row |
229 | $row->setChars($newChars); |
230 | |
231 | // Add a new row with the contents of the previous row at the point of the split |
232 | $this->insertRow($at->y + 1, substr($chars, $at->x)); |
233 | } |
234 | |
235 | $this->dirty = true; |
236 | } |
237 | |
238 | protected function selectSyntaxHighlight(): void |
239 | { |
240 | if (empty($this->filename)) |
241 | { |
242 | return; |
243 | } |
244 | |
245 | if ($this->fileType->name === 'PHP') |
246 | { |
247 | $this->tokens = PHP8::getFileTokens($this->filename); |
248 | } |
249 | |
250 | $this->refreshSyntax(); |
251 | } |
252 | |
253 | protected function rowsToString(): string |
254 | { |
255 | $lines = array_map(fn (Row $row) => (string)$row, $this->rows); |
256 | |
257 | return implode('', $lines); |
258 | } |
259 | |
260 | public function refreshSyntax(): void |
261 | { |
262 | // Update the syntax highlighting for all the rows of the file |
263 | array_walk($this->rows, static fn (Row $row) => $row->update()); |
264 | } |
265 | |
266 | private function refreshPHPSyntax(): void |
267 | { |
268 | if ($this->fileType->name !== 'PHP') |
269 | { |
270 | return; |
271 | } |
272 | |
273 | $this->tokens = PHP8::getTokens($this->rowsToString()); |
274 | $this->refreshSyntax(); |
275 | } |
276 | } |