ffi = $ffi; if ( ! $this->getWindowSize()) { die('Failed to get screen size'); } } // ------------------------------------------------------------------------ // ! Terminal // ------------------------------------------------------------------------ protected function readKey(): string { $c = read_stdin(1); if ($c === '\x1b') { $seq = read_stdin(); if (strpos($seq, '[') === 0) { if ((int)$seq[1] >= 0 && (int)$seq[1] <= 9) { if (strpos($seq, '~') === 2) { switch($seq[1]) { case '1': return Key::HOME_KEY; case '3': return Key::DEL_KEY; case '4': return Key::END_KEY; case '5': return Key::PAGE_UP; case '6': return Key::PAGE_DOWN; case '7': return Key::HOME_KEY; case '8': return Key::END_KEY; } } } else { switch($seq[1]) { case 'A': return Key::ARROW_UP; case 'B': return Key::ARROW_DOWN; case 'C': return Key::ARROW_RIGHT; case 'D': return Key::ARROW_LEFT; case 'H': return Key::HOME_KEY; case 'F': return Key::END_KEY; } } } else if (strpos($seq, 'O') === 0) { switch($seq[1]) { case 'H': return Key::HOME_KEY; case 'F': return Key::END_KEY; } } return '\x1b'; } return $c; } /** * @TODO fix */ private function getCursorPosition(): bool { write_stdout("\x1b[999C\x1b[999B"); write_stdout("\x1b[6n"); $buffer = read_stdout(32); $rows = 0; $cols = 0; $res = sscanf($buffer, '\x1b[%d;%dR', $rows, $cols); if ($res === -1 || $buffer[0] !== '\x1b' || $buffer[1] !== '[') { die('Failed to get screen size'); return false; } $this->screenRows = $rows; $this->screenCols = $cols; return true; } private function getWindowSize(): bool { $ws = $this->ffi->new('struct winsize'); $res = $this->ffi->ioctl(STDOUT_FILENO, TIOCGWINSZ, FFI::addr($ws)); if ($res === -1 || $ws->ws_col === 0) { return $this->getCursorPosition(); } $this->screenCols = $ws->ws_col; $this->screenRows = $ws->ws_row; return true; } // ------------------------------------------------------------------------ // ! Output // ------------------------------------------------------------------------ protected function drawRows(): void { for ($y = 0; $y < $this->screenRows; $y++) { if ($y === ($this->screenRows / 3)) { $welcome = sprintf('PHP Kilo editor -- version %s', KILO_VERSION); $welcomelen = strlen($welcome); if ($welcomelen > $this->screenCols) { $welcomelen = $this->screenCols; } $padding = ($this->screenCols - $welcomelen) / 2; if ($padding) { $this->ab .= '~'; $padding--; } while ($padding--) { $this->ab .= ' '; } $this->ab .= substr($welcome, 0, $welcomelen); } else { $this->ab .= '~'; } $this->ab .= "\x1b[K"; // Clear the current line if ($y < $this->screenRows - 1) { $this->ab .= "\r\n"; } } } public function refreshScreen(): void { $this->ab = ''; $this->ab .= "\x1b[?25l"; // Hide the cursor $this->ab .= "\x1b[H"; // Reposition cursor to top-left $this->drawRows(); // Specify the current cursor position $this->ab .= sprintf("\x1b[%d;%dH", $this->cursory + 1, $this->cursorx + 1); $this->ab .= "\x1b[?25h"; // Show the cursor write_stdout($this->ab); } // ------------------------------------------------------------------------ // ! Input // ------------------------------------------------------------------------ protected function moveCursor(string $key): void { switch ($key) { case Key::ARROW_LEFT: if ($this->cursorx !== 0) { $this->cursorx--; } break; case Key::ARROW_RIGHT: if ($this->cursorx !== $this->screenCols - 1) { $this->cursorx++; } break; case Key::ARROW_UP: if ($this->cursory !== 0) { $this->cursory--; } break; case Key::ARROW_DOWN: if ($this->cursory !== $this->screenRows - 1) { $this->cursory++; } break; } } public function processKeypress(): string { $c = $this->readKey(); switch($c) { case chr(ctrl_key('q')): write_stdout("\x1b[2J"); // Clear the screen write_stdout("\x1b[H"); // Reposition cursor to top-left exit(0); break; case Key::HOME_KEY: $this->cursorx = 0; break; case Key::END_KEY: $this->cursorx = $this->screenCols - 1; break; case Key::PAGE_UP: case Key::PAGE_DOWN: { $times = $this->screenRows; while ($times--) { $this->moveCursor($c === Key::PAGE_UP ? Key::ARROW_UP : Key::ARROW_DOWN); } } break; case Key::ARROW_UP: case Key::ARROW_DOWN: case Key::ARROW_LEFT: case Key::ARROW_RIGHT: $this->moveCursor($c); break; } return $c; } }