From 9146e2061f8f60afe5283f4fbae2e8b56f54680d Mon Sep 17 00:00:00 2001 From: Willian de Souza Date: Wed, 23 Oct 2024 13:22:03 -0300 Subject: [PATCH 1/2] Update CliMenu.php allow usage of special chars with custom control mappings --- src/CliMenu.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/CliMenu.php b/src/CliMenu.php index f3f9de7..e1ea2c1 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -296,7 +296,9 @@ private function display() : void continue; } - switch ($char->getControl()) { + $controlChar = $char->getControl(); + + switch ($controlChar) { case InputCharacter::UP: case InputCharacter::DOWN: $this->moveSelectionVertically($char->getControl()); @@ -310,6 +312,11 @@ private function display() : void case InputCharacter::ENTER: $this->executeCurrentItem(); break; + default: + if (isset($this->customControlMappings[$controlChar])) { + $this->customControlMappings[$controlChar]($this); + } + break; } } unset($reader); @@ -374,7 +381,7 @@ protected function moveSelectionHorizontally(string $direction) : void : (int) reset($itemKeys); } } while (!$item->canSelectIndex($selectedItemIndex)); - + $item->setSelectedItemIndex($selectedItemIndex); } @@ -541,7 +548,7 @@ protected function draw() : void protected function drawMenuItem(MenuItemInterface $item, bool $selected = false) : array { $rows = $item->getRows($this->style, $selected); - + if ($item instanceof SplitItem) { $selected = false; } @@ -586,7 +593,7 @@ public function open() : void if ($this->isOpen()) { return; } - + if (count($this->items) === 0) { throw new \RuntimeException('Menu must have at least 1 item before it can be opened'); } From 5a6cec93dedd7c7882b364f8cb0ae0662bd1d2df Mon Sep 17 00:00:00 2001 From: Willian de Souza Date: Wed, 23 Oct 2024 16:23:32 -0300 Subject: [PATCH 2/2] test: add test to ensure control mapping works with control char --- test/CliMenuTest.php | 45 ++++++++++++++----- ...AddCustomControlMappingWithControlChar.txt | 9 ++++ 2 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 test/res/testAddCustomControlMappingWithControlChar.txt diff --git a/test/CliMenuTest.php b/test/CliMenuTest.php index 66a70c5..11b861b 100644 --- a/test/CliMenuTest.php +++ b/test/CliMenuTest.php @@ -205,7 +205,7 @@ public function testSimpleOpenCloseWithDifferentXAndYPadding() : void self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch()); } - + public function testReDrawReDrawsImmediately() : void { $this->terminal->expects($this->once()) @@ -242,11 +242,11 @@ public function testRedrawClearsTerminalFirstIfOptionIsPassed() : void ->will($this->returnCallback(function ($buffer) { $this->output->write($buffer); })); - + $terminal->expects($this->exactly(3)) ->method('read') ->willReturn("\n", "\n", "\n"); - + $terminal->expects($this->atLeast(2)) ->method('clear'); @@ -264,11 +264,11 @@ public function testRedrawClearsTerminalFirstIfOptionIsPassed() : void $menu->getStyle()->setWidth(70); $menu->redraw(true); } - + if ($hits === 2) { $menu->close(); } - + $hits++; }); @@ -371,7 +371,7 @@ public function testOpenThrowsExceptionIfNoItemsInMenu() : void { $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Menu must have at least 1 item before it can be opened'); - + (new CliMenu('PHP School FTW', [], $this->terminal))->open(); } @@ -440,7 +440,7 @@ public function testSetItems() : void $menu->addItems([$item1, $item2]); $this->assertCount(2, $menu->getItems()); - + $menu->setItems([$item3, $item4]); $this->assertCount(2, $menu->getItems()); @@ -603,6 +603,27 @@ public function testAddCustomControlMapping() : void self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch()); } + + public function testAddCustomControlMappingWithControlChar() : void + { + $this->terminal->expects($this->once()) + ->method('read') + ->willReturn("\e"); + + $style = $this->getStyle($this->terminal); + + $action = function (CliMenu $menu) { + $menu->close(); + }; + $item = new SelectableItem('Item 1', $action); + + $menu = new CliMenu('PHP School FTW', [$item], $this->terminal, $style); + $menu->addCustomControlMapping('ESC', $action); + $menu->open(); + + self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch()); + } + public function testAddCustomControlMappingsThrowsExceptionWhenOverwritingExistingDefaultControls() : void { $this->expectException(\InvalidArgumentException::class); @@ -675,7 +696,7 @@ public function testRemoveCustomControlMapping() : void $menu = new CliMenu('PHP School FTW', [], $this->terminal); $menu->addCustomControlMapping('c', $action); self::assertSame(['c' => $action], $menu->getCustomControlMappings()); - + $menu->removeCustomControlMapping('c'); self::assertSame([], $menu->getCustomControlMappings()); } @@ -685,16 +706,16 @@ public function testSplitItemWithNoSelectableItemsScrollingVertically() : void $this->terminal->expects($this->exactly(3)) ->method('read') ->willReturn("\033[B", "\033[B", "\n"); - + $action = function (CliMenu $menu) { $menu->close(); }; - + $menu = new CliMenu('PHP School FTW', [], $this->terminal); $menu->addItem(new SelectableItem('One', $action)); $menu->addItem(new SplitItem([new StaticItem('Two'), new StaticItem('Three')])); $menu->addItem(new SelectableItem('Four', $action)); - + $menu->open(); self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch()); @@ -841,7 +862,7 @@ public function testSelectableCallableReceivesSelectableAndNotSplitItem() : void ) ); $menu->open(); - + self::assertSame($expectedSelectedItem, $actualSelectedItem); } diff --git a/test/res/testAddCustomControlMappingWithControlChar.txt b/test/res/testAddCustomControlMappingWithControlChar.txt new file mode 100644 index 0000000..b931076 --- /dev/null +++ b/test/res/testAddCustomControlMappingWithControlChar.txt @@ -0,0 +1,9 @@ + + +   +  PHP School FTW  +  ========================================  +  ● Item 1  +   + +