Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
11.76% covered (danger)
11.76%
2 / 17
CRAP
16.85% covered (danger)
16.85%
15 / 89
Document
0.00% covered (danger)
0.00%
0 / 1
11.76% covered (danger)
11.76%
2 / 17
1007.26
16.85% covered (danger)
16.85%
15 / 89
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 __get
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 new
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 row
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 isEmpty
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 open
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 10
 save
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 5
 insert
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 8
 delete
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 8
 insertRow
0.00% covered (danger)
0.00%
0 / 1
7.54
53.33% covered (warning)
53.33%
8 / 15
 deleteRow
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 8
 isDirty
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 insertNewline
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 12
 selectSyntaxHighlight
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
 rowsToString
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 refreshSyntax
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 refreshPHPSyntax
0.00% covered (danger)
0.00%
0 / 1
2.50
50.00% covered (danger)
50.00%
2 / 4
1<?php declare(strict_types=1);
2
3namespace Aviat\Kilo;
4
5use Aviat\Kilo\Enum\RawKeyCode;
6use Aviat\Kilo\Enum\KeyType;
7use Aviat\Kilo\Tokens\PHP8;
8use Aviat\Kilo\Type\Point;
9
10/**
11 * The representation of the current document being edited
12 *
13 * @property-read int $numRows
14 */
15class 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}