Highlight::DELIMITER, T_ARRAY => Highlight::DELIMITER, T_CURLY_OPEN => Highlight::DELIMITER, T_DOLLAR_OPEN_CURLY_BRACES => Highlight::DELIMITER, T_OPEN_TAG => Highlight::DELIMITER, T_OPEN_TAG_WITH_ECHO => Highlight::DELIMITER, // Number literals T_DNUMBER => Highlight::NUMBER, T_LNUMBER => Highlight::NUMBER, // Simple string literals T_CONSTANT_ENCAPSED_STRING => Highlight::STRING, // Simple variables T_VARIABLE => Highlight::VARIABLE, T_STRING_VARNAME => Highlight::VARIABLE, // Operators T_AND_EQUAL => Highlight::OPERATOR, T_BOOLEAN_AND => Highlight::OPERATOR, T_BOOLEAN_OR => Highlight::OPERATOR, T_COALESCE => Highlight::OPERATOR, T_CONCAT_EQUAL => Highlight::OPERATOR, T_DEC => Highlight::OPERATOR, T_DIV_EQUAL => Highlight::OPERATOR, T_DOUBLE_ARROW => Highlight::OPERATOR, T_DOUBLE_COLON => Highlight::OPERATOR, T_ELLIPSIS => Highlight::OPERATOR, T_INC => Highlight::OPERATOR, T_IS_EQUAL => Highlight::OPERATOR, T_IS_GREATER_OR_EQUAL => Highlight::OPERATOR, T_IS_IDENTICAL => Highlight::OPERATOR, T_IS_NOT_EQUAL => Highlight::OPERATOR, T_IS_NOT_IDENTICAL => Highlight::OPERATOR, T_IS_SMALLER_OR_EQUAL => Highlight::OPERATOR, T_SPACESHIP => Highlight::OPERATOR, T_LOGICAL_AND => Highlight::OPERATOR, T_LOGICAL_OR => Highlight::OPERATOR, T_LOGICAL_XOR => Highlight::OPERATOR, T_MINUS_EQUAL => Highlight::OPERATOR, T_MOD_EQUAL => Highlight::OPERATOR, T_MUL_EQUAL => Highlight::OPERATOR, T_NS_SEPARATOR => Highlight::OPERATOR, T_OBJECT_OPERATOR => Highlight::OPERATOR, T_OR_EQUAL => Highlight::OPERATOR, T_PLUS_EQUAL => Highlight::OPERATOR, T_POW => Highlight::OPERATOR, T_POW_EQUAL => Highlight::OPERATOR, T_SL => Highlight::OPERATOR, T_SL_EQUAL => Highlight::OPERATOR, T_SR => Highlight::OPERATOR, T_SR_EQUAL => Highlight::OPERATOR, T_XOR_EQUAL => Highlight::OPERATOR, // Keywords1 T_ABSTRACT => Highlight::KEYWORD1, T_AS => Highlight::KEYWORD1, T_BREAK => Highlight::KEYWORD1, T_CASE => Highlight::KEYWORD1, T_CATCH => Highlight::KEYWORD1, T_CLASS => Highlight::KEYWORD1, T_CLONE => Highlight::KEYWORD1, T_CONST => Highlight::KEYWORD1, T_CONTINUE => Highlight::KEYWORD1, T_DECLARE => Highlight::KEYWORD1, T_DEFAULT => Highlight::KEYWORD1, T_DO => Highlight::KEYWORD1, T_ELSE => Highlight::KEYWORD1, T_ELSEIF => Highlight::KEYWORD1, T_ENDDECLARE => Highlight::KEYWORD1, T_ENDFOR => Highlight::KEYWORD1, T_ENDFOREACH => Highlight::KEYWORD1, T_ENDIF => Highlight::KEYWORD1, T_ENDSWITCH => Highlight::KEYWORD1, T_ENDWHILE => Highlight::KEYWORD1, T_EXTENDS => Highlight::KEYWORD1, T_FINAL => Highlight::KEYWORD1, T_FINALLY => Highlight::KEYWORD1, T_FOR => Highlight::KEYWORD1, T_FOREACH => Highlight::KEYWORD1, T_FUNCTION => Highlight::KEYWORD1, T_GLOBAL => Highlight::KEYWORD1, T_GOTO => Highlight::KEYWORD1, T_HALT_COMPILER => Highlight::KEYWORD1, T_IF => Highlight::KEYWORD1, T_IMPLEMENTS => Highlight::KEYWORD1, T_INSTANCEOF => Highlight::KEYWORD1, T_INSTEADOF => Highlight::KEYWORD1, T_INTERFACE => Highlight::KEYWORD1, T_NAMESPACE => Highlight::KEYWORD1, T_NEW => Highlight::KEYWORD1, T_PRIVATE => Highlight::KEYWORD1, T_PUBLIC => Highlight::KEYWORD1, T_PROTECTED => Highlight::KEYWORD1, T_RETURN => Highlight::KEYWORD1, T_STATIC => Highlight::KEYWORD1, T_SWITCH => Highlight::KEYWORD1, T_THROW => Highlight::KEYWORD1, T_TRAIT => Highlight::KEYWORD1, T_TRY => Highlight::KEYWORD1, T_USE => Highlight::KEYWORD1, T_VAR => Highlight::KEYWORD1, T_WHILE => Highlight::KEYWORD1, T_YIELD => Highlight::KEYWORD1, T_YIELD_FROM => Highlight::KEYWORD1, // Not string literals, but identifiers, keywords, etc. // T_STRING => Highlight::KEYWORD2, ]; private array $phpCharacterHighlightMap = [ // Delimiter characters '[' => Highlight::DELIMITER, ']' => Highlight::DELIMITER, '{' => Highlight::DELIMITER, '}' => Highlight::DELIMITER, '(' => Highlight::DELIMITER, ')' => Highlight::DELIMITER, // Single character operators ',' => Highlight::OPERATOR, ';' => Highlight::OPERATOR, ':' => Highlight::OPERATOR, '^' => Highlight::OPERATOR, '%' => Highlight::OPERATOR, '+' => Highlight::OPERATOR, '-' => Highlight::OPERATOR, '*' => Highlight::OPERATOR, '/' => Highlight::OPERATOR, '.' => Highlight::OPERATOR, '|' => Highlight::OPERATOR, '~' => Highlight::OPERATOR, '>' => Highlight::OPERATOR, '<' => Highlight::OPERATOR, '=' => Highlight::OPERATOR, '!' => Highlight::OPERATOR, ]; public static function new(Editor $parent, string $chars, int $idx): self { $self = new self(); $self->chars = $chars; $self->parent = $parent; $self->idx = $idx; return $self; } private function __construct() {} public function __get(string $name) { switch ($name) { case 'size': return strlen($this->chars); case 'rsize': return strlen($this->render); case 'chars': return $this->chars; default: return NULL; } } public function __set(string $key, $value): void { if ($key === 'chars') { $this->chars = $value; $this->update(); } } public function __toString(): string { return $this->chars . "\n"; } public function insertChar(int $at, string $c): void { if ($at < 0 || $at > $this->size) { $at = $this->size; } // Safely insert into arbitrary position in the existing string $this->chars = substr($this->chars, 0, $at) . $c . substr($this->chars, $at); $this->update(); $this->parent->dirty++; } public function appendString(string $s): void { $this->chars .= $s; $this->update(); $this->parent->dirty++; } public function deleteChar(int $at): void { if ($at < 0 || $at >= $this->size) { return; } $this->chars = substr_replace($this->chars, '', $at, 1); $this->update(); $this->parent->dirty++; } public function update(): void { $idx = 0; // Make sure the render buffer is empty before updating // otherwise, you will have persistent output where you // don't want it. $this->render = ''; for ($i = 0; $i < $this->size; $i++) { if ($this->chars[$i] === "\t") { $this->render[$idx++] = ' '; while ($idx % KILO_TAB_STOP !== 0) { $this->render[$idx++] = ' '; } } else { $this->render[$idx++] = $this->chars[$i]; } } $this->updateSyntax(); } // ------------------------------------------------------------------------ // ! Syntax Highlighting // ------------------------------------------------------------------------ public function updateSyntax(): void { $this->hl = array_fill(0, $this->rsize, Highlight::NORMAL); if ($this->parent->syntax->filetype === 'PHP') { $this->updateSyntaxPHP(); return; } $keywords1 = $this->parent->syntax->keywords1; $keywords2 = $this->parent->syntax->keywords2; $scs = $this->parent->syntax->singleLineCommentStart; $mcs = $this->parent->syntax->multiLineCommentStart; $mce = $this->parent->syntax->multiLineCommentEnd; $scsLen = strlen($scs); $mcsLen = strlen($mcs); $mceLen = strlen($mce); $prevSep = TRUE; $inString = ''; $inComment = ($this->idx > 0 && $this->parent->rows[$this->idx - 1]->hlOpenComment); $i = 0; while ($i < $this->rsize) { $char = $this->render[$i]; $prevHl = ($i > 0) ? $this->hl[$i - 1] : Highlight::NORMAL; if ($this->parent->syntax === NULL) { return; } // Single-line comments if ($scsLen > 0 && $inString === '' && $inComment === FALSE && substr($this->render, $i, $scsLen) === $scs) { array_replace_range($this->hl, $i, $this->rsize - $i, Highlight::COMMENT); break; } // Multi-line comments if ($mcsLen > 0 && $mceLen > 0 && $inString === '') { if ($inComment) { $this->hl[$i] = Highlight::ML_COMMENT; if (substr($this->render, $i, $mceLen) === $mce) { array_replace_range($this->hl, $i, $mceLen, Highlight::ML_COMMENT); $i += $mceLen; $inComment = FALSE; $prevSep = TRUE; continue; } $i++; continue; } if (substr($this->render, $i, $mcsLen) === $mcs) { array_replace_range($this->hl, $i, $mcsLen, Highlight::ML_COMMENT); $i += $mcsLen; $inComment = TRUE; continue; } } // String/Char literals if ($this->parent->syntax->flags & Syntax::HIGHLIGHT_STRINGS) { if ($inString !== '') { $this->hl[$i] = Highlight::STRING; // Check for escaped character if ($char === '\\' && $i+1 < $this->rsize) { $this->hl[$i + 1] = Highlight::STRING; $i += 2; continue; } if ($char === $inString) { $inString = ''; } $i++; $prevSep = 1; continue; } if ( $char === '""' || $char === '\'') { $inString = $char; $this->hl[$i] = Highlight::STRING; $i++; continue; } } // Numbers, including decimal points if ($this->parent->syntax->flags & Syntax::HIGHLIGHT_NUMBERS) { if ( ($char === '.' && $prevHl === Highlight::NUMBER) || (($prevSep || $prevHl === Highlight::NUMBER) && is_digit($char)) ) { $this->hl[$i] = Highlight::NUMBER; $i++; $prevSep = FALSE; continue; } } // Keywords if ($prevSep) { $findKeywords = function (array $keywords, int $syntaxType) use (&$i): void { foreach ($keywords as $k) { $klen = strlen($k); $nextCharOffset = $i + $klen; $isEndOfLine = $nextCharOffset >= $this->rsize; $nextChar = ($isEndOfLine) ? "\0" : $this->render[$nextCharOffset]; if (substr($this->render, $i, $klen) === $k && is_separator($nextChar)) { array_replace_range($this->hl, $i, $klen, $syntaxType); $i += $klen - 1; break; } } }; $findKeywords($keywords1, Highlight::KEYWORD1); $findKeywords($keywords2, Highlight::KEYWORD2); } $prevSep = is_separator($char); $i++; } $changed = $this->hlOpenComment !== $inComment; $this->hlOpenComment = $inComment; if ($changed && $this->idx + 1 < $this->parent->numRows) { $this->parent->rows[$this->idx + 1]->updateSyntax(); } } protected function updateSyntaxPHP():void { if ( ! isset($this->parent->syntax->tokens)) { return; } // The index for the tokens should exist $tokens = $this->parent->syntax->tokens[$this->idx + 1]; // $inComment = ($this->idx > 0 && $this->parent->rows[$this->idx - 1]->hlOpenComment); // Keep track of where you are in the line, so that // multiples of the same tokens can be effectively matched $offset = 0; foreach ($tokens as $token) { if ($offset >= $this->rsize) { break; } $char = $token['char']; $charLen = strlen($char); $charStart = strpos($this->render, $char, $offset); if ($charStart === FALSE) { $offset++; continue; } $charEnd = $charStart + $charLen; // Highlight raw characters if (($token['typeName'] === 'RAW') && array_key_exists($token['char'], $this->phpCharacterHighlightMap)) { $hl = $this->phpCharacterHighlightMap[$token['char']]; array_replace_range($this->hl, $charStart, $charLen, $hl); $offset = $charEnd; continue; } // Highlight specific tokens if (array_key_exists($token['type'], $this->phpTokenHighlightMap)) { $hl = $this->phpTokenHighlightMap[$token['type']]; array_replace_range($this->hl, $charStart, $charLen, $hl); $offset = $charEnd; continue; } } } }