diff --git a/src/Document.php b/src/Document.php index efc5291..a4766c8 100644 --- a/src/Document.php +++ b/src/Document.php @@ -2,6 +2,7 @@ namespace Aviat\Kilo; +use Aviat\Kilo\Tokens\PHP8; use Aviat\Kilo\Type\Point; /** @@ -10,16 +11,18 @@ use Aviat\Kilo\Type\Point; * @property-read int $numRows */ class Document { - public ?Syntax $syntax = NULL; + public FileType $fileType; // Tokens for highlighting PHP public array $tokens = []; private function __construct( public array $rows = [], - public ?string $filename = NULL, - private bool $dirty = FALSE, - ) {} + public string $filename = '', + public bool $dirty = FALSE, + ) { + $this->fileType = FileType::from($this->filename); + } public function __get(string $name): ?int { @@ -31,21 +34,53 @@ class Document { return NULL; } - public static function new(): self + public static function default(): self { return new self(); } - public static function open(?string $filename = NULL): self + protected function rowsToString(): string { - // @TODO move logic from Editor - return new self(filename: $filename); + $lines = array_map(fn (Row $row) => (string)$row, $this->rows); + + return implode('', $lines); } - public function save(): bool + public static function open(string $filename): ?self { - // @TODO move logic - return false; + $handle = fopen($filename, 'rb'); + if ($handle === FALSE) + { + return NULL; + } + + $self = new self(filename: $filename); + + while (($line = fgets($handle)) !== FALSE) + { + // Remove line endings when reading the file + $self->insertRow($self->numRows, rtrim($line), FALSE); + } + + fclose($handle); + + $self->selectSyntaxHighlight(); + + return $self; + } + + public function save(): int|false + { + $contents = $this->rowsToString(); + + $res = file_put_contents($this->filename, $contents); + + if ($res === strlen($contents)) + { + $this->dirty = FALSE; + } + + return $res; } public function insertChar(Point $at, string $c): void @@ -53,6 +88,47 @@ class Document { } + 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); + + $this->rows[$at]->update(); + + $this->dirty = true; + + // Re-tokenize the file + if ($updateSyntax) + { + $this->refreshPHPSyntax(); + } + } + public function isDirty(): bool { return $this->dirty; @@ -67,4 +143,36 @@ class Document { { } + + public function selectSyntaxHighlight(): void + { + if (empty($this->filename)) + { + return; + } + + if ($this->fileType->name === 'PHP') + { + $this->tokens = PHP8::getFileTokens($this->filename); + } + + $this->refreshSyntax(); + } + + public function refreshSyntax(): void + { + // Update the syntax highlighting for all the rows of the file + array_walk($this->rows, static fn (Row $row) => $row->update()); + } + + private function refreshPHPSyntax(): void + { + if ($this->fileType->name !== 'PHP') + { + return; + } + + $this->tokens = PHP8::getTokens($this->rowsToString()); + $this->refreshSyntax(); + } } diff --git a/src/Editor.php b/src/Editor.php index 0a63174..5de826b 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -9,7 +9,6 @@ use Aviat\Kilo\Type\{Point, StatusMessage}; /** * // Don't highlight this! - * @property-read int $numRows */ class Editor { use Traits\MagicProperties; diff --git a/src/FileType.php b/src/FileType.php new file mode 100644 index 0000000..149fab0 --- /dev/null +++ b/src/FileType.php @@ -0,0 +1,76 @@ +filetype, $syntax); + } + + private static function getSyntaxFromFilename(string $filename): Syntax + { + $ext = (string)strstr(basename($filename), '.'); + return match ($ext) { + '.php', 'kilo' => Syntax::new( + 'PHP', + ['.php', 'kilo'], + ), + '.c', '.h', '.cpp', '.cxx', '.cc', '.hpp' => Syntax::new( + 'C', + ['.c', '.h', '.cpp'], + [ + 'continue', 'typedef', 'switch', 'return', 'static', 'while', 'break', 'struct', + 'union', 'class', 'else', 'enum', 'for', 'case', 'if', + ], + [ + '#include', 'unsigned', '#define', '#ifndef', 'double', 'signed', '#endif', + '#ifdef', 'float', '#error', '#undef', 'long', 'char', 'int', 'void', '#if', + ], + ), + '.css', '.less', '.sass', '.scss' => Syntax::new( + 'CSS', + ['.css', '.less', '.sass', 'scss'], + slcs: '', + ), + '.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es' => Syntax::new( + 'JavaScript', + ['.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es'], + [ + 'instanceof', 'continue', 'debugger', 'function', 'default', 'extends', + 'finally', 'delete', 'export', 'import', 'return', 'switch', 'typeof', + 'break', 'catch', 'class', 'const', 'super', 'throw', 'while', 'yield', + 'case', 'else', 'this', 'void', 'with', 'from', 'for', 'new', 'try', + 'var', 'do', 'if', 'in', 'as', + ], + [ + '=>', 'Number', 'String', 'Object', 'Math', 'JSON', 'Boolean', + ], + ), + '.rs' => Syntax::new( + 'Rust', + ['.rs'], + [ + 'continue', 'return', 'static', 'struct', 'unsafe', 'break', 'const', 'crate', + 'extern', 'match', 'super', 'trait', 'where', 'else', 'enum', 'false', 'impl', + 'loop', 'move', 'self', 'type', 'while', 'for', 'let', 'mod', 'pub', 'ref', 'true', + 'use', 'mut', 'as', 'fn', 'if', 'in', + ], + [ + 'DoubleEndedIterator', 'ExactSizeIterator', 'IntoIterator', 'PartialOrd', 'PartialEq', + 'Iterator', 'ToString', 'Default', 'ToOwned', 'Extend', 'FnOnce', 'Option', 'String', + 'AsMut', 'AsRef', 'Clone', 'Debug', 'FnMut', 'Sized', 'Unpin', 'array', 'isize', + 'usize', '&str', 'Copy', 'Drop', 'From', 'Into', 'None', 'Self', 'Send', 'Some', + 'Sync', 'bool', 'char', 'i128', 'u128', 'Box', 'Err', 'Ord', 'Vec', 'dyn', 'f32', + 'f64', 'i16', 'i32', 'i64', 'str', 'u16', 'u32', 'u64', 'Eq', 'Fn', 'Ok', 'i8', 'u8', + ], + ), + default => Syntax::default(), + }; + } + + private function __construct(public string $name, public Syntax $syntax) {} +} \ No newline at end of file diff --git a/src/Syntax.php b/src/Syntax.php index 9b3aecf..0569c36 100644 --- a/src/Syntax.php +++ b/src/Syntax.php @@ -17,12 +17,17 @@ class Syntax { string $slcs = '//', string $mcs = '/*', string $mce = '*/', - int $flags = 0, + int $flags = self::HIGHLIGHT_NUMBERS | self::HIGHLIGHT_STRINGS, ): self { return new self($name, $extList, $keywords1, $keywords2, $slcs, $mcs, $mce, $flags); } + public static function default(): self + { + return self::new('No filetype', slcs: '', mcs: '', mce: '', flags: 0); + } + private function __construct( /** The name of the programming language */ public string $filetype,