diff --git a/example/CLI/tasks/HelpTask.php b/example/CLI/tasks/HelpTask.php index b16504d..8dc8ac1 100644 --- a/example/CLI/tasks/HelpTask.php +++ b/example/CLI/tasks/HelpTask.php @@ -19,6 +19,7 @@ public function run() $this->io->info('Try above tasks:'); $this->io->info(' ./boot.php help'); $this->io->info(' ./boot.php read'); + $this->io->info(' ./boot.php menu'); $this->io->info(' ./boot.php color'); $this->io->info(' ./boot.php parse'); } diff --git a/example/CLI/tasks/MenuTask.php b/example/CLI/tasks/MenuTask.php new file mode 100644 index 0000000..d3e1619 --- /dev/null +++ b/example/CLI/tasks/MenuTask.php @@ -0,0 +1,63 @@ +io->ask('Item counts of menu? [10] ', function ($value) { + return true === (bool) preg_match('/^\d+$/', $value) || '' === $value; + }); + + if ('' !== $count) { + $count = (int) $count; + } else { + $count = 10; + } + + if (0 === $count) { + $count = 10; + } + + $lines = $this->io->ask('Display lines of menu? [3] ', function ($value) { + return true === (bool) preg_match('/^\d+$/', $value) || '' === $value; + }); + + if ('' !== $lines) { + $lines = (int) $lines; + } else { + $lines = 3; + } + + if (0 === $lines) { + $lines = 3; + } + + if ($lines > $count) { + $lines = $count; + } + + $list = []; + + for ($index = 0; $index < $count; $index++) { + $list[] = sprintf('[%3d]', $index) . ' ' . md5($index); + } + + $this->io->writeln("Select Index"); + + $index = $this->io->menuSelect($list, $lines); + + $this->io->log("You selected index is {$index}!"); + } +} diff --git a/example/CLI/tasks/ReadTask.php b/example/CLI/tasks/ReadTask.php index bced90e..f7543ae 100644 --- a/example/CLI/tasks/ReadTask.php +++ b/example/CLI/tasks/ReadTask.php @@ -29,7 +29,9 @@ public function run() // or - $name = $this->io->ask('What is your name? '); + $name = $this->io->ask('What is your name? ', function ($value) { + return '' !== $value; + }); if (0 === $gender) { $this->io->log("Hi, Mr.{$name}!"); diff --git a/src/Oni/CLI/Helper/ANSIEscapeCode.php b/src/Oni/CLI/Helper/ANSIEscapeCode.php index 6f833f6..bf27c08 100644 --- a/src/Oni/CLI/Helper/ANSIEscapeCode.php +++ b/src/Oni/CLI/Helper/ANSIEscapeCode.php @@ -21,6 +21,17 @@ final class ANSIEscapeCode const BEL = "\x07"; const SEP = ';'; + // Key Code + const KEY_CODE_ENTER = 10; + const KEY_CODE_UP = 65; + const KEY_CODE_DOWN = 66; + const KEY_CODE_LEFT = 68; + const KEY_CODE_RIGHT = 67; + const KEY_CODE_PAGE_UP = 53; + const KEY_CODE_PAGE_DOWN = 54; + const KEY_CODE_HOME = 72; + const KEY_CODE_END = 70; + /** * @var array */ diff --git a/src/Oni/CLI/IO.php b/src/Oni/CLI/IO.php index bf77e38..47ef304 100644 --- a/src/Oni/CLI/IO.php +++ b/src/Oni/CLI/IO.php @@ -240,9 +240,10 @@ public function ask(string $text, ?callable $callback = null, ?string $fgColor = * * @return int */ - public function menuSelect(array $options): int + public function menuSelect(array $options, ?int $currentDisplayLines = null): int { $totalIndex = count($options); + $skipIndex = 0; $selectedIndex = 0; $isBreakLoop = false; $isFirstLoop = true; @@ -251,6 +252,16 @@ public function menuSelect(array $options): int $wWidth = (int) exec('tput cols'); $wHeight = (int) exec('tput lines'); + $displayLines = $totalIndex <= $wHeight + ? $totalIndex : $wHeight; + + if (true === is_integer($currentDisplayLines) + && $currentDisplayLines > 0 + && $currentDisplayLines < $displayLines + ) { + $displayLines = $currentDisplayLines; + } + readline_callback_handler_install('', function() {}); // Set Cursor is Hide @@ -258,41 +269,62 @@ public function menuSelect(array $options): int do { switch (ord($char)) { - case 10: // Enter Key + case AEC::KEY_CODE_ENTER: $isBreakLoop = true; break; - case 65: // Up Key - if ($selectedIndex - 1 >= 0) { - $selectedIndex--; - } + case AEC::KEY_CODE_UP: + $selectedIndex--; break; - case 66: // Down Key - if ($selectedIndex + 1 < $totalIndex) { - $selectedIndex++; - } + case AEC::KEY_CODE_DOWN: + $selectedIndex++; + + break; + case AEC::KEY_CODE_PAGE_UP: + $selectedIndex -= $displayLines; + + break; + case AEC::KEY_CODE_PAGE_DOWN: + $selectedIndex += $displayLines; + + break; + case AEC::KEY_CODE_HOME: + $selectedIndex = 0; + + break; + case AEC::KEY_CODE_END: + $selectedIndex = $totalIndex; break; } + if ($selectedIndex < 0) { + $selectedIndex = 0; + } elseif ($selectedIndex >= $totalIndex) { + $selectedIndex = $totalIndex - 1; + } + if (true === $isBreakLoop) { break; } // Set Cursor Prev if (false === $isFirstLoop) { - $this->write(AEC::cursorPrev($totalIndex - 1)); + $this->write(AEC::cursorPrev($displayLines - 1)); } else { $isFirstLoop = false; } - // Get Skip Index - $skipIndex = $selectedIndex < $wHeight - ? 0 : $selectedIndex - $wHeight + 1; + // Set Skip Index + if ($selectedIndex < $skipIndex) { + $skipIndex = $selectedIndex; + } else if ($selectedIndex > $skipIndex + $displayLines - 1) { + $skipIndex = $selectedIndex - $displayLines + 1; + } // Get Current Options - $currentOptions = array_slice($options, $skipIndex, $wHeight); + $currentOptions = array_slice($options, $skipIndex, $displayLines); // Print Menu $this->write(implode("\n", array_map(function ($option, $currentIndex) use ($selectedIndex, $skipIndex) {