Skip to content

Commit b7872dd

Browse files
authored
Merge pull request #220 from php-school/width
Fix various width issues
2 parents 1316f37 + 587fd8b commit b7872dd

33 files changed

+793
-355
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ before_script:
1919
script:
2020
- ./vendor/bin/phpunit --coverage-clover ./build/logs/clover.xml
2121
- composer cs
22-
- composer static
2322

2423
after_script:
2524
- bash <(curl -s https://codecov.io/bash)

src/MenuStyle.php

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpSchool\CliMenu\Terminal\TerminalFactory;
66
use PhpSchool\CliMenu\Util\ColourUtil;
7+
use PhpSchool\CliMenu\Util\StringUtil as s;
78
use PhpSchool\Terminal\Terminal;
89
use Assert\Assertion;
910

@@ -30,14 +31,31 @@ class MenuStyle
3031
protected $bg;
3132

3233
/**
34+
* The width of the menu. Including borders and padding.
35+
* Does not include margin.
36+
*
37+
* May not be the value that was requested in the
38+
* circumstance that the terminal is smaller then the
39+
* requested width.
40+
*
3341
* @var int
3442
*/
3543
protected $width;
3644

3745
/**
46+
* In case the requested width is wider than the terminal
47+
* then we shrink the width to fit the terminal. We keep
48+
* the requested size in case the margins are changed and
49+
* we need to recalculate the width.
50+
*
3851
* @var int
3952
*/
40-
protected $margin;
53+
private $requestedWidth;
54+
55+
/**
56+
* @var int
57+
*/
58+
protected $margin = 0;
4159

4260
/**
4361
* @var int
@@ -134,6 +152,11 @@ class MenuStyle
134152
*/
135153
private $marginAuto = false;
136154

155+
/**
156+
* @var bool
157+
*/
158+
private $debugMode = false;
159+
137160
/**
138161
* Default Values
139162
*
@@ -244,8 +267,13 @@ public function hasChangedFromDefaults() : bool
244267
$this->borderColour,
245268
$this->marginAuto,
246269
];
247-
248-
return $currentValues !== array_values(self::$defaultStyleValues);
270+
271+
$defaultStyleValues = self::$defaultStyleValues;
272+
if ($this->width !== $this->requestedWidth) {
273+
$defaultStyleValues['width'] = $this->width;
274+
}
275+
276+
return $currentValues !== array_values($defaultStyleValues);
249277
}
250278

251279
public function getDisabledItemText(string $text) : string
@@ -312,6 +340,8 @@ public function getColoursResetCode() : string
312340

313341
/**
314342
* Calculate the contents width
343+
*
344+
* The content width is menu width minus borders and padding.
315345
*/
316346
protected function calculateContentWidth() : void
317347
{
@@ -369,13 +399,12 @@ public function setWidth(int $width) : self
369399
{
370400
Assertion::greaterOrEqualThan($width, 0);
371401

372-
if ($width >= $this->terminal->getWidth()) {
373-
$width = $this->terminal->getWidth();
374-
}
402+
$this->requestedWidth = $width;
403+
$width = $this->maybeShrinkWidth($this->margin, $width);
375404

376405
$this->width = $width;
377406
if ($this->marginAuto) {
378-
$this->setMarginAuto();
407+
$this->calculateMarginAuto($width);
379408
}
380409

381410
$this->calculateContentWidth();
@@ -385,6 +414,15 @@ public function setWidth(int $width) : self
385414
return $this;
386415
}
387416

417+
private function maybeShrinkWidth(int $margin, int $width) : int
418+
{
419+
if ($width + $margin >= $this->terminal->getWidth()) {
420+
$width = $this->terminal->getWidth() - $margin;
421+
}
422+
423+
return $width;
424+
}
425+
388426
public function getPaddingTopBottom() : int
389427
{
390428
return $this->paddingTopBottom;
@@ -405,7 +443,7 @@ private function generatePaddingTopBottomRows() : void
405443

406444
$paddingRow = sprintf(
407445
"%s%s%s%s%s%s%s%s%s%s\n",
408-
str_repeat(' ', $this->margin),
446+
$this->debugMode ? $this->getDebugString($this->margin) : str_repeat(' ', $this->margin),
409447
$borderColour,
410448
str_repeat(' ', $this->borderLeftWidth),
411449
$this->getColoursSetCode(),
@@ -417,6 +455,15 @@ private function generatePaddingTopBottomRows() : void
417455
$this->coloursResetCode
418456
);
419457

458+
459+
if ($this->debugMode && s::length($paddingRow) <= $this->terminal->getWidth()) {
460+
$paddingRow = substr_replace(
461+
$paddingRow,
462+
sprintf("%s\n", $this->getDebugString($this->terminal->getWidth() - (s::length($paddingRow) - 1))),
463+
-1
464+
);
465+
}
466+
420467
$this->paddingTopBottomRows = array_fill(0, $this->paddingTopBottom, $paddingRow);
421468
}
422469

@@ -469,23 +516,28 @@ public function getMargin() : int
469516
public function setMarginAuto() : self
470517
{
471518
$this->marginAuto = true;
472-
$this->margin = (int) floor(($this->terminal->getWidth() - $this->width) / 2);
519+
$this->margin = 0;
473520

474-
$this->generateBorderRows();
475-
$this->generatePaddingTopBottomRows();
521+
$this->setWidth($this->requestedWidth);
476522

477523
return $this;
478524
}
479525

526+
private function calculateMarginAuto(int $width) : void
527+
{
528+
$this->margin = (int) floor(($this->terminal->getWidth() - ($width)) / 2);
529+
}
530+
480531
public function setMargin(int $margin) : self
481532
{
482533
Assertion::greaterOrEqualThan($margin, 0);
483534

484535
$this->marginAuto = false;
485536
$this->margin = $margin;
486537

487-
$this->generateBorderRows();
488-
$this->generatePaddingTopBottomRows();
538+
//margin + width may now exceed terminal size
539+
//so set width again to trigger width check + maybe resize
540+
$this->setWidth($this->requestedWidth);
489541

490542
return $this;
491543
}
@@ -549,12 +601,20 @@ private function generateBorderRows() : void
549601
{
550602
$borderRow = sprintf(
551603
"%s%s%s%s\n",
552-
str_repeat(' ', $this->margin),
604+
$this->debugMode ? $this->getDebugString($this->margin) : str_repeat(' ', $this->margin),
553605
$this->getBorderColourCode(),
554606
str_repeat(' ', $this->width),
555-
$this->coloursResetCode
607+
$this->getColoursResetCode()
556608
);
557609

610+
if ($this->debugMode && s::length($borderRow) <= $this->terminal->getWidth()) {
611+
$borderRow = substr_replace(
612+
$borderRow,
613+
sprintf("%s\n", $this->getDebugString($this->terminal->getWidth() - (s::length($borderRow) - 1))),
614+
-1
615+
);
616+
}
617+
558618
$this->borderTopRows = array_fill(0, $this->borderTopWidth, $borderRow);
559619
$this->borderBottomRows = array_fill(0, $this->borderBottomWidth, $borderRow);
560620
}
@@ -696,4 +756,22 @@ public function getBorderColourCode() : string
696756

697757
return sprintf("\033[%sm", $borderColourCode);
698758
}
759+
760+
/**
761+
* Get a string of given length consisting of 0-9
762+
* eg $length = 15 : 012345678901234
763+
*/
764+
private function getDebugString(int $length) : string
765+
{
766+
$nums = [];
767+
for ($i = 0, $j = 0; $i < $length; $i++, $j++) {
768+
if ($j === 10) {
769+
$j = 0;
770+
}
771+
772+
$nums[] = $j;
773+
}
774+
775+
return implode('', $nums);
776+
}
699777
}

src/Util/ArrayUtil.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\CliMenu\Util;
6+
7+
class ArrayUtil
8+
{
9+
public static function mapWithKeys(array $array, callable $callback) : array
10+
{
11+
return array_combine(
12+
array_keys($array),
13+
array_map($callback, array_keys($array), $array)
14+
);
15+
}
16+
}

src/Util/StringUtil.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ public static function stripAnsiEscapeSequence(string $str) : string
4343
{
4444
return (string) preg_replace('/\x1b[^m]*m/', '', $str);
4545
}
46+
47+
public static function length(string $str, bool $ignoreAnsiEscapeSequence = true) : int
48+
{
49+
return mb_strlen($ignoreAnsiEscapeSequence ? self::stripAnsiEscapeSequence($str) : $str);
50+
}
4651
}

test/Builder/CliMenuBuilderTest.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,12 @@ public function test256ColoursCodes() : void
233233
{
234234
$terminal = static::createMock(Terminal::class);
235235
$terminal
236-
->expects($this->any())
236+
->method('getWidth')
237+
->willReturn(100);
238+
239+
$terminal
237240
->method('getColourSupport')
238-
->will($this->returnValue(256));
241+
->willReturn(256);
239242

240243
$builder = new CliMenuBuilder($terminal);
241244
$builder->setBackgroundColour(16, 'white');
@@ -248,9 +251,12 @@ public function test256ColoursCodes() : void
248251

249252
$terminal = static::createMock(Terminal::class);
250253
$terminal
251-
->expects($this->any())
254+
->method('getWidth')
255+
->willReturn(100);
256+
257+
$terminal
252258
->method('getColourSupport')
253-
->will($this->returnValue(8));
259+
->willReturn(8);
254260

255261
$builder = new CliMenuBuilder($terminal);
256262
$builder->setBackgroundColour(16, 'white');
@@ -269,9 +275,11 @@ public function testSetFgThrowsExceptionWhenColourCodeIsNotInRange() : void
269275

270276
$terminal = static::createMock(Terminal::class);
271277
$terminal
272-
->expects($this->any())
278+
->method('getWidth')
279+
->willReturn(100);
280+
$terminal
273281
->method('getColourSupport')
274-
->will($this->returnValue(256));
282+
->willReturn(256);
275283

276284
$builder = new CliMenuBuilder($terminal);
277285
$builder->setForegroundColour(512, 'white');
@@ -284,9 +292,11 @@ public function testSetBgThrowsExceptionWhenColourCodeIsNotInRange() : void
284292

285293
$terminal = static::createMock(Terminal::class);
286294
$terminal
287-
->expects($this->any())
295+
->method('getWidth')
296+
->willReturn(100);
297+
$terminal
288298
->method('getColourSupport')
289-
->will($this->returnValue(256));
299+
->willReturn(256);
290300

291301
$builder = new CliMenuBuilder($terminal);
292302
$builder->setBackgroundColour(257, 'white');

test/CliMenuTest.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,7 @@ public function testGetItems() : void
283283
$item2 = new LineBreakItem();
284284

285285

286-
$terminal = $this->createMock(Terminal::class);
287-
$style = $this->getStyle($terminal);
286+
$style = $this->getStyle($terminal = new MockTerminal);
288287

289288
$menu = new CliMenu(
290289
'PHP School FTW',
@@ -304,8 +303,7 @@ public function testRemoveItem() : void
304303
$item1 = new LineBreakItem();
305304
$item2 = new LineBreakItem();
306305

307-
$terminal = $this->createMock(Terminal::class);
308-
$style = $this->getStyle($terminal);
306+
$style = $this->getStyle($terminal = new MockTerminal);
309307

310308
$menu = new CliMenu(
311309
'PHP School FTW',
@@ -355,6 +353,10 @@ public function testThrowsExceptionIfTerminalIsNotValidTTY() : void
355353
$this->expectException(\PhpSchool\CliMenu\Exception\InvalidTerminalException::class);
356354

357355
$terminal = $this->createMock(Terminal::class);
356+
$terminal
357+
->method('getWidth')
358+
->willReturn(100);
359+
358360
$terminal->expects($this->once())
359361
->method('isInteractive')
360362
->willReturn(false);

test/Input/InputIOTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ class InputIOTest extends TestCase
4444
public function setUp() : void
4545
{
4646
$this->terminal = $this->createMock(Terminal::class);
47+
$this->terminal
48+
->method('getWidth')
49+
->willReturn(100);
50+
4751
$this->output = new BufferedOutput;
4852
$this->menu = $this->createMock(CliMenu::class);
4953
$this->style = new MenuStyle($this->terminal);
@@ -52,10 +56,6 @@ public function setUp() : void
5256
$this->style->setBg('yellow');
5357
$this->style->setFg('red');
5458

55-
$this->terminal
56-
->method('getWidth')
57-
->willReturn(100);
58-
5959
$parentStyle = new MenuStyle($this->terminal);
6060
$parentStyle->setBg('blue');
6161

0 commit comments

Comments
 (0)