2019-10-14 16:21:41 -04:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
2019-11-08 16:27:08 -05:00
|
|
|
namespace Aviat\Kilo;
|
|
|
|
|
2023-10-10 14:20:19 -04:00
|
|
|
use Aviat\Kilo\Enum\{Highlight, KeyType, RawKeyCode, SearchDirection};
|
2023-10-10 13:02:05 -04:00
|
|
|
use Aviat\Kilo\Terminal\ANSI;
|
2023-10-10 14:20:19 -04:00
|
|
|
use Aviat\Kilo\Terminal\Enum\Color;
|
2023-10-10 13:02:05 -04:00
|
|
|
use Aviat\Kilo\Terminal\Terminal;
|
2021-03-11 16:56:02 -05:00
|
|
|
use Aviat\Kilo\Type\{Point, StatusMessage};
|
2023-10-10 13:02:05 -04:00
|
|
|
use Aviat\Kilo\Type\TerminalSize;
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
/**
|
2019-10-25 16:36:03 -04:00
|
|
|
* // Don't highlight this!
|
2019-10-16 16:43:15 -04:00
|
|
|
*/
|
2019-10-14 16:21:41 -04:00
|
|
|
class Editor {
|
2021-03-09 12:46:30 -05:00
|
|
|
/**
|
|
|
|
* @var string The screen buffer
|
|
|
|
*/
|
2021-03-04 12:03:51 -05:00
|
|
|
private string $outputBuffer = '';
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2021-03-09 12:46:30 -05:00
|
|
|
/**
|
2021-03-09 17:22:49 -05:00
|
|
|
* @var Point The 0-based location of the cursor in the current viewport
|
2021-03-09 12:46:30 -05:00
|
|
|
*/
|
2021-03-09 17:22:49 -05:00
|
|
|
protected Point $cursor;
|
2021-03-09 12:46:30 -05:00
|
|
|
|
|
|
|
/**
|
2021-03-09 17:22:49 -05:00
|
|
|
* @var Point The scroll offset of the file in the current viewport
|
2021-03-09 12:46:30 -05:00
|
|
|
*/
|
2021-03-09 17:22:49 -05:00
|
|
|
protected Point $offset;
|
2021-03-09 12:46:30 -05:00
|
|
|
|
|
|
|
/**
|
2021-03-11 16:56:02 -05:00
|
|
|
* @var Document The document being edited
|
2021-03-09 12:46:30 -05:00
|
|
|
*/
|
2021-03-11 16:56:02 -05:00
|
|
|
protected Document $document;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var StatusMessage A disappearing status message
|
|
|
|
*/
|
|
|
|
protected StatusMessage $statusMessage;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var TerminalSize The size of the terminal in rows and columns
|
|
|
|
*/
|
|
|
|
protected TerminalSize $terminalSize;
|
2021-03-09 12:46:30 -05:00
|
|
|
|
|
|
|
/**
|
2021-03-11 16:56:02 -05:00
|
|
|
* @var int The rendered cursor position
|
2021-03-09 12:46:30 -05:00
|
|
|
*/
|
2021-03-11 16:56:02 -05:00
|
|
|
protected int $renderX = 0;
|
2021-03-09 12:46:30 -05:00
|
|
|
|
|
|
|
/**
|
2021-03-11 16:56:02 -05:00
|
|
|
* @var bool Should we stop the rendering loop?
|
2021-03-09 12:46:30 -05:00
|
|
|
*/
|
2021-03-11 16:56:02 -05:00
|
|
|
protected bool $shouldQuit = false;
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2021-03-09 17:22:49 -05:00
|
|
|
/**
|
|
|
|
* @var int The number of times to confirm you wish to quit
|
|
|
|
*/
|
|
|
|
protected int $quitTimes = KILO_QUIT_TIMES;
|
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
/**
|
|
|
|
* Create the Editor instance with CLI arguments
|
|
|
|
*
|
|
|
|
* @param int $argc
|
|
|
|
* @param array $argv
|
|
|
|
* @return Editor
|
|
|
|
*/
|
|
|
|
public static function new(int $argc = 0, array $argv = []): Editor
|
2019-10-15 13:23:25 -04:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
if ($argc >= 2 && ! empty($argv[1]))
|
|
|
|
{
|
|
|
|
return new self($argv[1]);
|
|
|
|
}
|
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
return new self();
|
2019-10-15 13:23:25 -04:00
|
|
|
}
|
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
/**
|
|
|
|
* The real constructor, ladies and gentlemen
|
|
|
|
*
|
|
|
|
* @param string|null $filename
|
|
|
|
*/
|
|
|
|
private function __construct(?string $filename = NULL)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
$this->statusMessage = StatusMessage::from('HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find');
|
2021-03-09 17:22:49 -05:00
|
|
|
$this->cursor = Point::new();
|
|
|
|
$this->offset = Point::new();
|
2021-03-11 16:56:02 -05:00
|
|
|
$this->terminalSize = Terminal::size();
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
if (is_string($filename))
|
|
|
|
{
|
2021-03-17 13:14:16 -04:00
|
|
|
$maybeDocument = Document::new()->open($filename);
|
2021-03-17 08:52:17 -04:00
|
|
|
if ($maybeDocument === NULL)
|
|
|
|
{
|
2021-03-17 13:14:16 -04:00
|
|
|
$this->document = Document::new();
|
2021-03-17 08:52:17 -04:00
|
|
|
$this->setStatusMessage("ERR: Could not open file: {}", $filename);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$this->document = $maybeDocument;
|
|
|
|
}
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
2021-03-17 15:38:52 -04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
$this->document = Document::new();
|
|
|
|
}
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
2019-11-20 15:03:48 -05:00
|
|
|
public function __debugInfo(): array
|
|
|
|
{
|
|
|
|
return [
|
2021-03-09 12:46:30 -05:00
|
|
|
'cursor' => $this->cursor,
|
2021-03-11 16:56:02 -05:00
|
|
|
'document' => $this->document,
|
2021-03-09 12:46:30 -05:00
|
|
|
'offset' => $this->offset,
|
2019-11-20 15:03:48 -05:00
|
|
|
'renderX' => $this->renderX,
|
2021-03-11 16:56:02 -05:00
|
|
|
'terminalSize' => $this->terminalSize,
|
|
|
|
'statusMessage' => $this->statusMessage,
|
2019-11-20 15:03:48 -05:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
/**
|
|
|
|
* Start the input loop
|
|
|
|
*/
|
|
|
|
public function run(): void
|
|
|
|
{
|
2022-08-23 14:09:00 -04:00
|
|
|
$this->refreshScreen();
|
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
while ( ! $this->shouldQuit)
|
|
|
|
{
|
2022-08-23 14:09:00 -04:00
|
|
|
// Don't redraw unless the screen actually needs to update!
|
|
|
|
if ($this->processKeypress() !== false)
|
|
|
|
{
|
|
|
|
$this->refreshScreen();
|
|
|
|
}
|
2021-03-11 16:56:02 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
/**
|
|
|
|
* Set a status message to be displayed, using printf formatting
|
|
|
|
* @param string $fmt
|
|
|
|
* @param mixed ...$args
|
|
|
|
*/
|
|
|
|
public function setStatusMessage(string $fmt, mixed ...$args): void
|
|
|
|
{
|
|
|
|
$text = func_num_args() > 1
|
|
|
|
? sprintf($fmt, ...$args)
|
|
|
|
: $fmt;
|
|
|
|
|
|
|
|
$this->statusMessage = StatusMessage::from($text);
|
|
|
|
}
|
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! Row Operations
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2021-03-09 13:37:03 -05:00
|
|
|
/**
|
|
|
|
* Cursor X to Render X
|
|
|
|
*
|
|
|
|
* @param Row $row
|
|
|
|
* @param int $cx
|
|
|
|
* @return int
|
|
|
|
*/
|
2019-10-16 16:43:15 -04:00
|
|
|
protected function rowCxToRx(Row $row, int $cx): int
|
|
|
|
{
|
|
|
|
$rx = 0;
|
|
|
|
for ($i = 0; $i < $cx; $i++)
|
|
|
|
{
|
2021-04-09 13:52:01 -04:00
|
|
|
if ($row->chars[$i] === RawKeyCode::TAB)
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
|
|
|
$rx += (KILO_TAB_STOP - 1) - ($rx % KILO_TAB_STOP);
|
|
|
|
}
|
|
|
|
$rx++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $rx;
|
|
|
|
}
|
|
|
|
|
2021-03-09 13:37:03 -05:00
|
|
|
/**
|
|
|
|
* Render X to Cursor X
|
|
|
|
*
|
|
|
|
* @param Row $row
|
|
|
|
* @param int $rx
|
|
|
|
* @return int
|
|
|
|
*/
|
2019-10-23 10:36:04 -04:00
|
|
|
protected function rowRxToCx(Row $row, int $rx): int
|
|
|
|
{
|
|
|
|
$cur_rx = 0;
|
|
|
|
for ($cx = 0; $cx < $row->size; $cx++)
|
|
|
|
{
|
2021-04-09 13:52:01 -04:00
|
|
|
if ($row->chars[$cx] === RawKeyCode::TAB)
|
2019-10-23 10:36:04 -04:00
|
|
|
{
|
|
|
|
$cur_rx += (KILO_TAB_STOP - 1) - ($cur_rx % KILO_TAB_STOP);
|
|
|
|
}
|
|
|
|
$cur_rx++;
|
|
|
|
|
|
|
|
if ($cur_rx > $rx)
|
|
|
|
{
|
|
|
|
return $cx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $cx;
|
|
|
|
}
|
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! File I/O
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2019-10-22 12:09:11 -04:00
|
|
|
protected function save(): void
|
|
|
|
{
|
2021-03-17 08:52:17 -04:00
|
|
|
if ($this->document->filename === '')
|
2019-10-22 12:09:11 -04:00
|
|
|
{
|
2019-10-22 16:44:55 -04:00
|
|
|
$newFilename = $this->prompt('Save as: %s');
|
|
|
|
if ($newFilename === '')
|
2019-10-22 16:16:28 -04:00
|
|
|
{
|
|
|
|
$this->setStatusMessage('Save aborted');
|
|
|
|
return;
|
|
|
|
}
|
2019-10-22 16:44:55 -04:00
|
|
|
|
2021-03-17 08:52:17 -04:00
|
|
|
$this->document->filename = $newFilename;
|
2019-10-22 12:09:11 -04:00
|
|
|
}
|
|
|
|
|
2021-03-17 08:52:17 -04:00
|
|
|
$res = $this->document->save();
|
2019-10-22 12:09:11 -04:00
|
|
|
|
2021-03-17 08:52:17 -04:00
|
|
|
if ($res !== FALSE)
|
2019-10-22 12:09:11 -04:00
|
|
|
{
|
2021-03-17 08:52:17 -04:00
|
|
|
$this->setStatusMessage('%d bytes written to disk', $res);
|
2019-10-22 12:09:11 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
$this->setStatusMessage('Failed to save! I/O error: %s', error_get_last()['message'] ?? '');
|
2019-10-15 13:23:25 -04:00
|
|
|
}
|
|
|
|
|
2019-10-23 10:36:04 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! Find
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
protected function findCallback(string $query, string|KeyType $key): void
|
2019-10-23 10:36:04 -04:00
|
|
|
{
|
2021-03-18 16:26:30 -04:00
|
|
|
static $lastMatch = NO_MATCH;
|
|
|
|
static $direction = SearchDirection::FORWARD;
|
2019-10-23 10:36:04 -04:00
|
|
|
|
2019-10-24 16:22:52 -04:00
|
|
|
static $savedHlLine = 0;
|
|
|
|
static $savedHl = [];
|
|
|
|
|
|
|
|
if ( ! empty($savedHl))
|
|
|
|
{
|
2021-03-17 15:38:52 -04:00
|
|
|
$row = $this->document->row($savedHlLine);
|
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
if ($row->isValid())
|
2021-03-17 15:38:52 -04:00
|
|
|
{
|
|
|
|
$row->hl = $savedHl;
|
|
|
|
}
|
|
|
|
|
2019-10-24 16:22:52 -04:00
|
|
|
$savedHl = [];
|
|
|
|
}
|
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
$direction = match ($key) {
|
2021-12-03 11:59:42 -05:00
|
|
|
KeyType::ArrowUp, KeyType::ArrowLeft => SearchDirection::BACKWARD,
|
2021-03-18 16:26:30 -04:00
|
|
|
default => SearchDirection::FORWARD
|
|
|
|
};
|
2019-10-23 10:36:04 -04:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
$arrowKeys = [KeyType::ArrowUp, KeyType::ArrowDown, KeyType::ArrowLeft, KeyType::ArrowRight];
|
2020-02-05 14:50:31 -05:00
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
// Reset search state with non arrow-key input
|
|
|
|
if ( ! in_array($key, $arrowKeys, true))
|
|
|
|
{
|
|
|
|
$lastMatch = NO_MATCH;
|
|
|
|
$direction = SearchDirection::FORWARD;
|
2020-02-05 14:50:31 -05:00
|
|
|
|
2021-04-09 13:52:01 -04:00
|
|
|
if ($key === RawKeyCode::ENTER || $key === RawKeyCode::ESCAPE)
|
2021-03-18 16:26:30 -04:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2019-10-23 10:36:04 -04:00
|
|
|
}
|
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
if ($lastMatch === NO_MATCH)
|
2019-10-23 10:36:04 -04:00
|
|
|
{
|
2021-03-18 16:26:30 -04:00
|
|
|
$direction = SearchDirection::FORWARD;
|
2019-10-23 10:36:04 -04:00
|
|
|
}
|
2019-10-25 16:36:03 -04:00
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
$current = (int)$lastMatch;
|
2019-10-23 10:36:04 -04:00
|
|
|
|
2020-12-04 11:18:21 -05:00
|
|
|
if (empty($query))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-17 08:52:17 -04:00
|
|
|
for ($i = 0; $i < $this->document->numRows; $i++)
|
2019-10-23 10:36:04 -04:00
|
|
|
{
|
2021-12-03 11:59:42 -05:00
|
|
|
$current += $direction->value;
|
2019-10-23 10:36:04 -04:00
|
|
|
if ($current === -1)
|
|
|
|
{
|
2021-03-17 08:52:17 -04:00
|
|
|
$current = $this->document->numRows - 1;
|
2019-10-23 10:36:04 -04:00
|
|
|
}
|
2021-03-17 08:52:17 -04:00
|
|
|
else if ($current === $this->document->numRows)
|
2019-10-23 10:36:04 -04:00
|
|
|
{
|
|
|
|
$current = 0;
|
|
|
|
}
|
|
|
|
|
2021-03-17 15:38:52 -04:00
|
|
|
$row = $this->document->row($current);
|
2021-03-18 16:26:30 -04:00
|
|
|
if ( ! $row->isValid())
|
2021-03-17 15:38:52 -04:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2020-02-05 16:32:17 -05:00
|
|
|
|
|
|
|
$match = strpos($row->render, $query);
|
2019-10-23 10:36:04 -04:00
|
|
|
if ($match !== FALSE)
|
|
|
|
{
|
|
|
|
$lastMatch = $current;
|
2021-03-09 12:46:30 -05:00
|
|
|
$this->cursor->y = (int)$current;
|
|
|
|
$this->cursor->x = $this->rowRxToCx($row, $match);
|
2021-03-17 08:52:17 -04:00
|
|
|
$this->offset->y = $this->document->numRows;
|
2019-10-24 12:01:00 -04:00
|
|
|
|
2019-10-24 16:22:52 -04:00
|
|
|
$savedHlLine = $current;
|
2020-02-05 16:32:17 -05:00
|
|
|
$savedHl = $row->hl;
|
2019-10-24 12:01:00 -04:00
|
|
|
// Update the highlight array of the relevant row with the 'MATCH' type
|
2021-12-03 11:59:42 -05:00
|
|
|
array_replace_range($row->hl, $match, strlen($query), Highlight::SearchMatch);
|
2019-10-24 12:01:00 -04:00
|
|
|
|
2019-10-23 10:36:04 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function find(): void
|
|
|
|
{
|
2021-03-09 17:22:49 -05:00
|
|
|
$savedCursor = Point::from($this->cursor);
|
|
|
|
$savedOffset = Point::from($this->offset);
|
2019-10-23 10:36:04 -04:00
|
|
|
|
|
|
|
$query = $this->prompt('Search: %s (Use ESC/Arrows/Enter)', [$this, 'findCallback']);
|
|
|
|
|
|
|
|
// If they pressed escape, the query will be empty,
|
|
|
|
// restore original cursor and scroll locations
|
|
|
|
if ($query === '')
|
|
|
|
{
|
2021-03-09 17:22:49 -05:00
|
|
|
$this->cursor = Point::from($savedCursor);
|
|
|
|
$this->offset = Point::from($savedOffset);
|
2019-10-23 10:36:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! Output
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
protected function scroll(): void
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->renderX = 0;
|
2021-03-17 08:52:17 -04:00
|
|
|
if ($this->cursor->y < $this->document->numRows)
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-17 15:38:52 -04:00
|
|
|
$row = $this->document->row($this->cursor->y);
|
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
if ($row->isValid())
|
2021-03-17 15:38:52 -04:00
|
|
|
{
|
|
|
|
$this->renderX = $this->rowCxToRx($row, $this->cursor->x);
|
|
|
|
}
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Vertical Scrolling
|
2021-03-09 12:46:30 -05:00
|
|
|
if ($this->cursor->y < $this->offset->y)
|
2019-10-15 13:23:25 -04:00
|
|
|
{
|
2021-03-09 12:46:30 -05:00
|
|
|
$this->offset->y = $this->cursor->y;
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
2021-03-11 16:56:02 -05:00
|
|
|
else if ($this->cursor->y >= ($this->offset->y + $this->terminalSize->rows))
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
$this->offset->y = $this->cursor->y - $this->terminalSize->rows + 1;
|
2019-10-15 13:23:25 -04:00
|
|
|
}
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
// Horizontal Scrolling
|
2021-03-09 12:46:30 -05:00
|
|
|
if ($this->renderX < $this->offset->x)
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-09 12:46:30 -05:00
|
|
|
$this->offset->x = $this->renderX;
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
2021-03-11 16:56:02 -05:00
|
|
|
else if ($this->renderX >= ($this->offset->x + $this->terminalSize->cols))
|
2019-10-15 13:23:25 -04:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
$this->offset->x = $this->renderX - $this->terminalSize->cols + 1;
|
2019-10-15 13:23:25 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
protected function drawRows(): void
|
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
for ($y = 0; $y < $this->terminalSize->rows; $y++)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-17 13:14:16 -04:00
|
|
|
$fileRow = $y + $this->offset->y;
|
2021-03-04 12:03:51 -05:00
|
|
|
|
2021-03-09 17:22:49 -05:00
|
|
|
$this->outputBuffer .= ANSI::CLEAR_LINE;
|
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
($fileRow >= $this->document->numRows)
|
2021-03-04 12:03:51 -05:00
|
|
|
? $this->drawPlaceholderRow($y)
|
2021-03-17 13:14:16 -04:00
|
|
|
: $this->drawRow($fileRow);
|
2021-03-04 12:03:51 -05:00
|
|
|
|
|
|
|
$this->outputBuffer .= "\r\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function drawRow(int $rowIdx): void
|
|
|
|
{
|
2021-03-17 15:38:52 -04:00
|
|
|
$row = $this->document->row($rowIdx);
|
2021-03-18 16:26:30 -04:00
|
|
|
if ( ! $row->isValid())
|
2021-03-17 15:38:52 -04:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$len = $row->rsize - $this->offset->x;
|
2021-03-04 12:03:51 -05:00
|
|
|
if ($len < 0)
|
|
|
|
{
|
|
|
|
$len = 0;
|
|
|
|
}
|
2021-03-11 16:56:02 -05:00
|
|
|
if ($len > $this->terminalSize->cols)
|
2021-03-04 12:03:51 -05:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
$len = $this->terminalSize->cols;
|
2021-03-04 12:03:51 -05:00
|
|
|
}
|
|
|
|
|
2021-03-17 15:38:52 -04:00
|
|
|
$chars = substr($row->render, $this->offset->x, (int)$len);
|
|
|
|
$hl = array_slice($row->hl, $this->offset->x, (int)$len);
|
2021-03-04 12:03:51 -05:00
|
|
|
|
|
|
|
$currentColor = -1;
|
|
|
|
|
|
|
|
for ($i = 0; $i < $len; $i++)
|
|
|
|
{
|
|
|
|
$ch = $chars[$i];
|
|
|
|
|
|
|
|
// Handle 'non-printable' characters
|
|
|
|
if (is_ctrl($ch))
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-04 12:03:51 -05:00
|
|
|
$sym = (ord($ch) <= 26)
|
|
|
|
? chr(ord('@') + ord($ch))
|
|
|
|
: '?';
|
2022-08-26 15:44:00 -04:00
|
|
|
$this->outputBuffer .= ANSI::invert($sym);
|
2021-03-04 12:03:51 -05:00
|
|
|
if ($currentColor !== -1)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= ANSI::color($currentColor);
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2021-03-04 12:03:51 -05:00
|
|
|
}
|
2021-12-03 11:59:42 -05:00
|
|
|
else if ($hl[$i] === Highlight::Normal)
|
2021-03-04 12:03:51 -05:00
|
|
|
{
|
|
|
|
if ($currentColor !== -1)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= ANSI::RESET_TEXT;
|
|
|
|
$this->outputBuffer .= ANSI::color(Color::FG_WHITE);
|
|
|
|
$currentColor = -1;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= $ch;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-08-13 11:16:57 -04:00
|
|
|
$color = get_syntax_color($hl[$i]);
|
2021-03-04 12:03:51 -05:00
|
|
|
if ($color !== $currentColor)
|
2019-10-15 13:23:25 -04:00
|
|
|
{
|
2021-03-04 12:03:51 -05:00
|
|
|
$currentColor = $color;
|
|
|
|
$this->outputBuffer .= ANSI::RESET_TEXT;
|
|
|
|
$this->outputBuffer .= ANSI::color($color);
|
2019-10-15 13:23:25 -04:00
|
|
|
}
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= $ch;
|
|
|
|
}
|
|
|
|
}
|
2019-10-23 16:10:56 -04:00
|
|
|
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= ANSI::RESET_TEXT;
|
|
|
|
$this->outputBuffer .= ANSI::color(Color::FG_WHITE);
|
|
|
|
}
|
2019-10-23 13:36:16 -04:00
|
|
|
|
2021-03-04 12:03:51 -05:00
|
|
|
protected function drawPlaceholderRow(int $y): void
|
|
|
|
{
|
2021-03-17 08:52:17 -04:00
|
|
|
if ($this->document->numRows === 0 && $y === (int)($this->terminalSize->rows / 2))
|
2021-03-04 12:03:51 -05:00
|
|
|
{
|
|
|
|
$welcome = sprintf('PHP Kilo editor -- version %s', KILO_VERSION);
|
|
|
|
$welcomelen = strlen($welcome);
|
2021-03-11 16:56:02 -05:00
|
|
|
if ($welcomelen > $this->terminalSize->cols)
|
2021-03-04 12:03:51 -05:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
$welcomelen = $this->terminalSize->cols;
|
2021-03-04 12:03:51 -05:00
|
|
|
}
|
2019-10-23 16:10:56 -04:00
|
|
|
|
2023-10-10 15:21:24 -04:00
|
|
|
$padding = (int)floor(($this->terminalSize->cols - $welcomelen) / 2);
|
2021-03-04 12:03:51 -05:00
|
|
|
if ($padding > 0)
|
|
|
|
{
|
|
|
|
$this->outputBuffer .= '~';
|
|
|
|
$padding--;
|
|
|
|
}
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2023-10-10 15:21:24 -04:00
|
|
|
$this->outputBuffer .= str_repeat(' ', $padding);
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= substr($welcome, 0, $welcomelen);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$this->outputBuffer .= '~';
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function drawStatusBar(): void
|
|
|
|
{
|
2022-08-26 15:44:00 -04:00
|
|
|
$this->outputBuffer .= ANSI::INVERSE_TEXT;
|
2019-10-16 16:43:15 -04:00
|
|
|
|
2021-03-17 13:14:16 -04:00
|
|
|
$statusFilename = $this->document->filename !== '' ? $this->document->filename : '[No Name]';
|
|
|
|
$syntaxType = $this->document->fileType->name;
|
2021-03-17 15:38:52 -04:00
|
|
|
$isDirty = $this->document->isDirty() ? '(modified)' : '';
|
2021-03-17 08:52:17 -04:00
|
|
|
$status = sprintf('%.20s - %d lines %s', $statusFilename, $this->document->numRows, $isDirty);
|
|
|
|
$rstatus = sprintf('%s | %d/%d', $syntaxType, $this->cursor->y + 1, $this->document->numRows);
|
2019-10-16 16:43:15 -04:00
|
|
|
$len = strlen($status);
|
|
|
|
$rlen = strlen($rstatus);
|
2021-03-11 16:56:02 -05:00
|
|
|
if ($len > $this->terminalSize->cols)
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
$len = $this->terminalSize->cols;
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= substr($status, 0, $len);
|
2021-03-11 16:56:02 -05:00
|
|
|
while ($len < $this->terminalSize->cols)
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
if ($this->terminalSize->cols - $len === $rlen)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= substr($rstatus, 0, $rlen);
|
2019-10-16 16:43:15 -04:00
|
|
|
break;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2019-10-16 16:43:15 -04:00
|
|
|
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= ' ';
|
2019-10-16 16:43:15 -04:00
|
|
|
$len++;
|
|
|
|
}
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= ANSI::RESET_TEXT;
|
|
|
|
$this->outputBuffer .= "\r\n";
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function drawMessageBar(): void
|
|
|
|
{
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= ANSI::CLEAR_LINE;
|
2021-03-17 15:38:52 -04:00
|
|
|
$len = $this->statusMessage->len;
|
2021-03-11 16:56:02 -05:00
|
|
|
if ($len > $this->terminalSize->cols)
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
$len = $this->terminalSize->cols;
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
|
|
|
|
2021-03-17 15:38:52 -04:00
|
|
|
// If there is a message, and it's been less than 5 seconds since
|
|
|
|
// last screen update, show the message
|
2021-03-11 16:56:02 -05:00
|
|
|
if ($len > 0 && (time() - $this->statusMessage->time) < 5)
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-11 16:56:02 -05:00
|
|
|
$this->outputBuffer .= substr($this->statusMessage->text, 0, $len);
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
protected function refreshScreen(): void
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-15 13:23:25 -04:00
|
|
|
$this->scroll();
|
|
|
|
|
2021-04-14 16:38:41 -04:00
|
|
|
$this->outputBuffer = ANSI::HIDE_CURSOR . ANSI::RESET_CURSOR;
|
2019-10-14 16:21:41 -04:00
|
|
|
|
|
|
|
$this->drawRows();
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->drawStatusBar();
|
|
|
|
$this->drawMessageBar();
|
2019-10-14 16:21:41 -04:00
|
|
|
|
|
|
|
// Specify the current cursor position
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= ANSI::moveCursor(
|
2021-03-09 12:46:30 -05:00
|
|
|
$this->cursor->y - $this->offset->y,
|
|
|
|
$this->renderX - $this->offset->x
|
2019-10-16 16:43:15 -04:00
|
|
|
);
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2021-03-04 12:03:51 -05:00
|
|
|
$this->outputBuffer .= ANSI::SHOW_CURSOR;
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2021-03-05 12:06:23 -05:00
|
|
|
Terminal::write($this->outputBuffer, strlen($this->outputBuffer));
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! Input
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2019-10-23 10:36:04 -04:00
|
|
|
protected function prompt(string $prompt, ?callable $callback = NULL): string
|
2019-10-22 16:16:28 -04:00
|
|
|
{
|
|
|
|
$buffer = '';
|
2020-01-27 15:11:20 -05:00
|
|
|
$modifiers = KeyType::getConstList();
|
2019-10-22 16:16:28 -04:00
|
|
|
while (TRUE)
|
|
|
|
{
|
|
|
|
$this->setStatusMessage($prompt, $buffer);
|
|
|
|
$this->refreshScreen();
|
|
|
|
|
2021-03-17 15:38:52 -04:00
|
|
|
$c = Terminal::readKey();
|
2020-02-05 16:32:17 -05:00
|
|
|
$isModifier = in_array($c, $modifiers, TRUE);
|
2019-10-22 16:16:28 -04:00
|
|
|
|
2023-10-10 15:21:24 -04:00
|
|
|
if ($c === KeyType::Escape || ($c === KeyType::Enter && $buffer !== ''))
|
2019-10-22 16:16:28 -04:00
|
|
|
{
|
|
|
|
$this->setStatusMessage('');
|
2019-10-23 10:36:04 -04:00
|
|
|
if ($callback !== NULL)
|
|
|
|
{
|
|
|
|
$callback($buffer, $c);
|
|
|
|
}
|
2023-10-10 15:21:24 -04:00
|
|
|
return ($c === KeyType::Enter) ? $buffer : '';
|
2019-10-22 16:16:28 -04:00
|
|
|
}
|
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
if ($c === KeyType::Delete || $c === KeyType::Backspace)
|
2019-10-22 16:16:28 -04:00
|
|
|
{
|
|
|
|
$buffer = substr($buffer, 0, -1);
|
|
|
|
}
|
2023-10-10 15:21:24 -04:00
|
|
|
else if (is_string($c) && is_ascii($c) && ( ! (is_ctrl($c) || $isModifier)))
|
2019-10-22 16:16:28 -04:00
|
|
|
{
|
|
|
|
$buffer .= $c;
|
|
|
|
}
|
2019-10-23 10:36:04 -04:00
|
|
|
|
|
|
|
if ($callback !== NULL)
|
|
|
|
{
|
|
|
|
$callback($buffer, $c);
|
|
|
|
}
|
2019-10-22 16:16:28 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-18 16:26:30 -04:00
|
|
|
/**
|
|
|
|
* Input processing
|
2022-08-23 14:09:00 -04:00
|
|
|
*
|
|
|
|
* Returns `false` on no keypress
|
2021-03-18 16:26:30 -04:00
|
|
|
*/
|
2022-08-23 14:09:00 -04:00
|
|
|
protected function processKeypress(): bool|null
|
2021-03-18 16:26:30 -04:00
|
|
|
{
|
|
|
|
$c = Terminal::readKey();
|
|
|
|
|
2021-04-09 13:52:01 -04:00
|
|
|
if ($c === RawKeyCode::NULL || $c === RawKeyCode::EMPTY)
|
2021-03-18 16:26:30 -04:00
|
|
|
{
|
2022-08-23 14:09:00 -04:00
|
|
|
return false;
|
2021-03-18 16:26:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
switch ($c)
|
|
|
|
{
|
2021-04-09 13:52:01 -04:00
|
|
|
case RawKeyCode::CTRL('q'):
|
2021-03-18 16:26:30 -04:00
|
|
|
$this->quitAttempt();
|
2022-08-23 14:09:00 -04:00
|
|
|
return true;
|
2021-03-18 16:26:30 -04:00
|
|
|
|
2022-08-23 14:09:00 -04:00
|
|
|
case RawKeyCode::CTRL('s'):
|
|
|
|
$this->save();
|
|
|
|
break;
|
2021-03-18 16:26:30 -04:00
|
|
|
|
2022-08-23 14:09:00 -04:00
|
|
|
case RawKeyCode::CTRL('f'):
|
|
|
|
$this->find();
|
|
|
|
break;
|
2021-03-18 16:26:30 -04:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::Delete:
|
|
|
|
case KeyType::Backspace:
|
2021-04-09 13:52:01 -04:00
|
|
|
$this->removeChar($c);
|
2021-03-18 16:26:30 -04:00
|
|
|
break;
|
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::ArrowUp:
|
|
|
|
case KeyType::ArrowDown:
|
|
|
|
case KeyType::ArrowLeft:
|
|
|
|
case KeyType::ArrowRight:
|
|
|
|
case KeyType::PageUp:
|
|
|
|
case KeyType::PageDown:
|
|
|
|
case KeyType::Home:
|
|
|
|
case KeyType::End:
|
2021-03-18 16:26:30 -04:00
|
|
|
$this->moveCursor($c);
|
|
|
|
break;
|
|
|
|
|
2021-04-09 13:52:01 -04:00
|
|
|
case RawKeyCode::CTRL('l'):
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::Escape:
|
2021-03-18 16:26:30 -04:00
|
|
|
// Do nothing
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
$this->insertChar($c);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset quit confirmation timer on different keypress
|
|
|
|
if ($this->quitTimes < KILO_QUIT_TIMES)
|
|
|
|
{
|
|
|
|
$this->quitTimes = KILO_QUIT_TIMES;
|
|
|
|
$this->setStatusMessage('');
|
|
|
|
}
|
2022-08-23 14:09:00 -04:00
|
|
|
|
|
|
|
return true;
|
2021-03-18 16:26:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! Editor operation helpers
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
protected function moveCursor(KeyType $key): void
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-09 13:37:03 -05:00
|
|
|
$x = $this->cursor->x;
|
|
|
|
$y = $this->cursor->y;
|
2021-03-17 15:38:52 -04:00
|
|
|
$row = $this->document->row($y);
|
2021-03-18 16:26:30 -04:00
|
|
|
if ( ! $row->isValid())
|
2021-03-17 15:38:52 -04:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2019-10-16 16:43:15 -04:00
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
switch ($key)
|
|
|
|
{
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::ArrowLeft:
|
2021-03-09 13:37:03 -05:00
|
|
|
if ($x !== 0)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-09 13:37:03 -05:00
|
|
|
$x--;
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
2021-03-09 13:37:03 -05:00
|
|
|
else if ($y > 0)
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-09 13:37:03 -05:00
|
|
|
// Beginning of a line, go to end of previous line
|
|
|
|
$y--;
|
2021-03-17 15:38:52 -04:00
|
|
|
$x = $row->size - 1;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2023-10-10 10:25:38 -04:00
|
|
|
break;
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::ArrowRight:
|
2021-03-17 15:38:52 -04:00
|
|
|
if ($x < $row->size)
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-09 13:37:03 -05:00
|
|
|
$x++;
|
2019-10-16 16:43:15 -04:00
|
|
|
}
|
2021-03-17 15:38:52 -04:00
|
|
|
else if ($x === $row->size)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-09 13:37:03 -05:00
|
|
|
$y++;
|
|
|
|
$x = 0;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2023-10-10 10:25:38 -04:00
|
|
|
break;
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::ArrowUp:
|
2021-03-09 13:37:03 -05:00
|
|
|
if ($y !== 0)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-09 13:37:03 -05:00
|
|
|
$y--;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2023-10-10 10:25:38 -04:00
|
|
|
break;
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::ArrowDown:
|
2021-03-17 08:52:17 -04:00
|
|
|
if ($y < $this->document->numRows)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-09 13:37:03 -05:00
|
|
|
$y++;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2023-10-10 10:25:38 -04:00
|
|
|
break;
|
2021-03-09 12:46:30 -05:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::PageUp:
|
2021-04-14 16:38:41 -04:00
|
|
|
$y = saturating_sub($y, $this->terminalSize->rows);
|
2023-10-10 10:25:38 -04:00
|
|
|
break;
|
2021-03-09 12:46:30 -05:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::PageDown:
|
2021-04-14 16:38:41 -04:00
|
|
|
$y = saturating_add($y, $this->terminalSize->rows, $this->document->numRows);
|
2023-10-10 10:25:38 -04:00
|
|
|
break;
|
2021-03-09 12:46:30 -05:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::Home:
|
2021-03-09 13:37:03 -05:00
|
|
|
$x = 0;
|
2023-10-10 10:25:38 -04:00
|
|
|
break;
|
2021-03-09 12:46:30 -05:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
case KeyType::End:
|
2021-03-17 08:52:17 -04:00
|
|
|
if ($y < $this->document->numRows)
|
2021-03-09 12:46:30 -05:00
|
|
|
{
|
2021-03-17 15:38:52 -04:00
|
|
|
$x = $row->size;
|
2021-03-09 12:46:30 -05:00
|
|
|
}
|
2023-10-10 10:25:38 -04:00
|
|
|
break;
|
2021-03-09 12:46:30 -05:00
|
|
|
|
|
|
|
default:
|
|
|
|
// Do nothing
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2019-10-16 16:43:15 -04:00
|
|
|
|
2021-03-09 17:22:49 -05:00
|
|
|
// Snap cursor to the end of a row when moving
|
|
|
|
// from a longer row to a shorter one
|
2021-03-17 15:38:52 -04:00
|
|
|
$row = $this->document->row($y);
|
2021-03-18 16:26:30 -04:00
|
|
|
if ($row->isValid())
|
2019-10-16 16:43:15 -04:00
|
|
|
{
|
2021-03-17 15:38:52 -04:00
|
|
|
if ($x > $row->size)
|
|
|
|
{
|
|
|
|
$x = $row->size;
|
|
|
|
}
|
2021-03-09 13:37:03 -05:00
|
|
|
|
2021-03-17 15:38:52 -04:00
|
|
|
$this->cursor->x = $x;
|
|
|
|
$this->cursor->y = $y;
|
|
|
|
}
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
protected function insertChar(string|KeyType $c): void
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2021-03-18 16:26:30 -04:00
|
|
|
$this->document->insert($this->cursor, $c);
|
2021-12-03 11:59:42 -05:00
|
|
|
$this->moveCursor(KeyType::ArrowRight);
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2019-10-15 13:23:25 -04:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
protected function removeChar(string|KeyType $ch): void
|
2021-04-09 13:52:01 -04:00
|
|
|
{
|
2021-12-03 11:59:42 -05:00
|
|
|
if ($ch === KeyType::Delete)
|
2021-04-09 13:52:01 -04:00
|
|
|
{
|
|
|
|
$this->document->delete($this->cursor);
|
|
|
|
}
|
2021-04-09 16:27:16 -04:00
|
|
|
|
2021-12-03 11:59:42 -05:00
|
|
|
if ($ch === KeyType::Backspace && ($this->cursor->x > 0 || $this->cursor->y > 0))
|
2021-04-09 13:52:01 -04:00
|
|
|
{
|
2021-12-03 11:59:42 -05:00
|
|
|
$this->moveCursor(KeyType::ArrowLeft);
|
2021-04-09 16:27:16 -04:00
|
|
|
$this->document->delete($this->cursor);
|
2021-04-09 13:52:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-11 16:56:02 -05:00
|
|
|
protected function quitAttempt(): void
|
2021-03-09 17:22:49 -05:00
|
|
|
{
|
2021-03-17 15:38:52 -04:00
|
|
|
if ($this->document->isDirty() && $this->quitTimes > 0)
|
2021-03-09 17:22:49 -05:00
|
|
|
{
|
2021-04-09 16:27:16 -04:00
|
|
|
if ($this->quitTimes === KILO_QUIT_TIMES)
|
|
|
|
{
|
|
|
|
Terminal::ding();
|
|
|
|
}
|
|
|
|
|
2021-03-09 17:22:49 -05:00
|
|
|
$this->setStatusMessage(
|
|
|
|
'WARNING!!! File has unsaved changes. Press Ctrl-Q %d more times to quit.',
|
|
|
|
$this->quitTimes
|
|
|
|
);
|
2021-04-09 16:27:16 -04:00
|
|
|
|
2021-03-09 17:22:49 -05:00
|
|
|
$this->quitTimes--;
|
2021-03-11 16:56:02 -05:00
|
|
|
return;
|
2021-03-09 17:22:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Terminal::clear();
|
2021-03-11 16:56:02 -05:00
|
|
|
|
|
|
|
$this->shouldQuit = true;
|
2021-03-09 17:22:49 -05:00
|
|
|
}
|
2019-10-24 20:21:44 -04:00
|
|
|
}
|