2021-03-10 16:24:02 -05:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Aviat\Kilo;
|
|
|
|
|
2021-03-17 15:38:52 -04:00
|
|
|
use Aviat\Kilo\Enum\KeyCode;
|
2021-03-16 18:37:53 -04:00
|
|
|
use Aviat\Kilo\Tokens\PHP8;
|
2021-03-11 17:11:00 -05:00
|
|
|
use Aviat\Kilo\Type\Point;
|
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
/**
|
|
|
|
* The representation of the current document being edited
|
|
|
|
*
|
|
|
|
* @property-read int $numRows
|
|
|
|
*/
|
2021-03-10 16:24:02 -05:00
|
|
|
class Document {
|
2021-03-16 18:37:53 -04:00
|
|
|
public FileType $fileType;
|
2021-03-11 16:56:02 -05:00
|
|
|
|
2021-03-11 17:11:00 -05:00
|
|
|
// Tokens for highlighting PHP
|
|
|
|
public array $tokens = [];
|
|
|
|
|
2021-03-10 16:24:02 -05:00
|
|
|
private function __construct(
|
2021-03-16 18:37:53 -04:00
|
|
|
public string $filename = '',
|
2021-03-17 13:14:16 -04:00
|
|
|
public array $rows = [],
|
2021-03-16 18:37:53 -04:00
|
|
|
public bool $dirty = FALSE,
|
|
|
|
) {
|
|
|
|
$this->fileType = FileType::from($this->filename);
|
|
|
|
}
|
2021-03-10 16:24:02 -05:00
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
public function __get(string $name): ?int
|
|
|
|
{
|
|
|
|
if ($name === 'numRows')
|
|
|
|
{
|
|
|
|
return count($this->rows);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
public static function new(): self
|
2021-03-11 16:56:02 -05:00
|
|
|
{
|
|
|
|
return new self();
|
|
|
|
}
|
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
public function row(int $index): Row
|
2021-03-10 16:24:02 -05:00
|
|
|
{
|
2021-03-18 16:26:30 -04:00
|
|
|
return (array_key_exists($index, $this->rows))
|
|
|
|
? $this->rows[$index]
|
|
|
|
: Row::default();
|
2021-03-10 16:24:02 -05:00
|
|
|
}
|
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
public function isEmpty(): bool
|
|
|
|
{
|
|
|
|
return empty($this->rows);
|
|
|
|
}
|
2021-03-17 15:38:52 -04:00
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! File I/O
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
|
|
public function open(string $filename): ?self
|
2021-03-11 16:56:02 -05:00
|
|
|
{
|
2021-03-16 18:37:53 -04:00
|
|
|
$handle = fopen($filename, 'rb');
|
|
|
|
if ($handle === FALSE)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
$this->__construct($filename);
|
2021-03-16 18:37:53 -04:00
|
|
|
|
|
|
|
while (($line = fgets($handle)) !== FALSE)
|
|
|
|
{
|
|
|
|
// Remove line endings when reading the file
|
2021-03-17 13:14:16 -04:00
|
|
|
$this->rows[] = Row::new($this, rtrim($line), $this->numRows);
|
2021-03-16 18:37:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fclose($handle);
|
|
|
|
|
2021-04-07 11:35:25 -04:00
|
|
|
$this->dirty = false;
|
2021-03-17 13:14:16 -04:00
|
|
|
$this->selectSyntaxHighlight();
|
2021-03-16 18:37:53 -04:00
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
return $this;
|
2021-03-16 18:37:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
public function save(): int|false
|
|
|
|
{
|
|
|
|
$contents = $this->rowsToString();
|
|
|
|
|
|
|
|
$res = file_put_contents($this->filename, $contents);
|
|
|
|
|
|
|
|
if ($res === strlen($contents))
|
|
|
|
{
|
|
|
|
$this->dirty = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $res;
|
2021-03-11 16:56:02 -05:00
|
|
|
}
|
|
|
|
|
2021-03-17 08:52:17 -04:00
|
|
|
public function insert(Point $at, string $c): void
|
2021-03-11 16:56:02 -05:00
|
|
|
{
|
2021-03-17 13:14:16 -04:00
|
|
|
if ($at->y > $this->numRows)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->dirty = true;
|
|
|
|
|
2021-03-17 15:38:52 -04:00
|
|
|
if ($c === KeyCode::ENTER || $c === KeyCode::CARRIAGE_RETURN)
|
2021-03-17 13:14:16 -04:00
|
|
|
{
|
|
|
|
$this->insertNewline($at);
|
2021-03-17 15:38:52 -04:00
|
|
|
return;
|
2021-03-17 13:14:16 -04:00
|
|
|
}
|
2021-03-17 15:38:52 -04:00
|
|
|
|
|
|
|
if ($at->y === $this->numRows)
|
2021-03-17 08:52:17 -04:00
|
|
|
{
|
|
|
|
$this->insertRow($this->numRows, '');
|
|
|
|
}
|
2021-03-17 13:14:16 -04:00
|
|
|
|
|
|
|
$this->rows[$at->y]->insert($at->x, $c);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function delete(Point $at): void
|
|
|
|
{
|
|
|
|
if ($at->y > $this->numRows)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->dirty = true;
|
|
|
|
$row =& $this->rows[$at->y];
|
|
|
|
|
|
|
|
if ($at->x === $this->rows[$at->y]->size && $at->y + 1 < $this->numRows)
|
|
|
|
{
|
|
|
|
$this->rows[$at->y]->append($this->rows[$at->y + 1]->chars);
|
|
|
|
$this->deleteRow($at->y + 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$row->delete($at->x);
|
|
|
|
}
|
2021-03-11 16:56:02 -05:00
|
|
|
}
|
|
|
|
|
2021-03-16 18:37:53 -04:00
|
|
|
public function insertRow(int $at, string $s, bool $updateSyntax = TRUE): void
|
|
|
|
{
|
|
|
|
if ($at > $this->numRows)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$row = Row::new($this, $s, $at);
|
|
|
|
|
|
|
|
if ($at === $this->numRows)
|
|
|
|
{
|
|
|
|
$this->rows[] = $row;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$this->rows = [
|
|
|
|
...array_slice($this->rows, 0, $at),
|
|
|
|
$row,
|
|
|
|
...array_slice($this->rows, $at),
|
|
|
|
];
|
|
|
|
|
|
|
|
// Update indexes of each row so that correct highlighting is done
|
|
|
|
for ($idx = $at; $idx < $this->numRows; $idx++)
|
|
|
|
{
|
|
|
|
$this->rows[$idx]->idx = $idx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ksort($this->rows);
|
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
// $this->rows[$at]->highlight();
|
2021-03-16 18:37:53 -04:00
|
|
|
|
|
|
|
// Re-tokenize the file
|
|
|
|
if ($updateSyntax)
|
|
|
|
{
|
|
|
|
$this->refreshPHPSyntax();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
protected function deleteRow(int $at): void
|
2021-03-11 16:56:02 -05:00
|
|
|
{
|
2021-03-17 13:14:16 -04:00
|
|
|
if ($at < 0 || $at >= $this->numRows)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the row
|
|
|
|
unset($this->rows[$at]);
|
|
|
|
|
|
|
|
// Re-index the array of rows
|
|
|
|
$this->rows = array_values($this->rows);
|
|
|
|
for ($i = $at; $i < $this->numRows; $i++)
|
|
|
|
{
|
|
|
|
$this->rows[$i]->idx = $i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Re-tokenize the file
|
|
|
|
$this->refreshPHPSyntax();
|
|
|
|
|
|
|
|
$this->dirty = true;
|
2021-03-11 16:56:02 -05:00
|
|
|
}
|
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
public function isDirty(): bool
|
2021-03-11 16:56:02 -05:00
|
|
|
{
|
2021-03-17 13:14:16 -04:00
|
|
|
return $this->dirty;
|
2021-03-11 16:56:02 -05:00
|
|
|
}
|
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
protected function insertNewline(Point $at): void
|
2021-03-11 16:56:02 -05:00
|
|
|
{
|
2021-03-17 13:14:16 -04:00
|
|
|
if ($at->y > $this->numRows)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2021-03-11 16:56:02 -05:00
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
if ($at->y === $this->numRows)
|
2021-03-17 08:52:17 -04:00
|
|
|
{
|
2021-03-17 13:14:16 -04:00
|
|
|
$this->insertRow($this->numRows, '');
|
|
|
|
}
|
|
|
|
else if ($at->x === 1)
|
|
|
|
{
|
|
|
|
$this->insertRow($at->y, '');
|
2021-03-17 08:52:17 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-03-17 13:14:16 -04:00
|
|
|
$row = $this->rows[$at->y];
|
2021-03-17 08:52:17 -04:00
|
|
|
$chars = $row->chars;
|
2021-03-17 13:14:16 -04:00
|
|
|
$newChars = substr($chars, 0, $at->x);
|
2021-03-17 08:52:17 -04:00
|
|
|
|
|
|
|
// Truncate the previous row
|
2021-03-17 15:38:52 -04:00
|
|
|
$row->chars = $newChars;
|
2021-03-17 08:52:17 -04:00
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
// Add a new row with the contents of the previous row at the point of the split
|
|
|
|
$this->insertRow($at->y + 1, substr($chars, $at->x));
|
2021-03-17 08:52:17 -04:00
|
|
|
}
|
2021-03-11 16:56:02 -05:00
|
|
|
}
|
2021-03-16 18:37:53 -04:00
|
|
|
|
2021-03-17 15:38:52 -04:00
|
|
|
protected function selectSyntaxHighlight(): void
|
2021-03-16 18:37:53 -04:00
|
|
|
{
|
|
|
|
if (empty($this->filename))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->fileType->name === 'PHP')
|
|
|
|
{
|
|
|
|
$this->tokens = PHP8::getFileTokens($this->filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->refreshSyntax();
|
|
|
|
}
|
|
|
|
|
2021-03-17 15:38:52 -04:00
|
|
|
protected function rowsToString(): string
|
|
|
|
{
|
|
|
|
$lines = array_map(fn (Row $row) => (string)$row, $this->rows);
|
|
|
|
|
|
|
|
return implode('', $lines);
|
|
|
|
}
|
|
|
|
|
2021-03-16 18:37:53 -04:00
|
|
|
public function refreshSyntax(): void
|
|
|
|
{
|
|
|
|
// Update the syntax highlighting for all the rows of the file
|
2021-03-17 13:14:16 -04:00
|
|
|
array_walk($this->rows, static fn (Row $row) => $row->highlight());
|
2021-03-16 18:37:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private function refreshPHPSyntax(): void
|
|
|
|
{
|
|
|
|
if ($this->fileType->name !== 'PHP')
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->tokens = PHP8::getTokens($this->rowsToString());
|
|
|
|
$this->refreshSyntax();
|
|
|
|
}
|
2021-03-11 16:56:02 -05:00
|
|
|
}
|