diff --git a/.idea/blade.xml b/.idea/blade.xml index 93870c7..fca757d 100644 --- a/.idea/blade.xml +++ b/.idea/blade.xml @@ -86,6 +86,7 @@ + @@ -99,7 +100,7 @@ - + diff --git a/src/AnsiChar.php b/src/AnsiChar.php index 6a02b5b..0a22c7c 100644 --- a/src/AnsiChar.php +++ b/src/AnsiChar.php @@ -2,12 +2,14 @@ namespace Glhd\AnsiPants; -class AnsiChar +use Stringable; + +class AnsiChar implements Stringable { public function __construct( public string $value, /** @var \Glhd\AnsiPants\Flag[] */ - public array $flags, + public array $flags = [], ) { } @@ -20,4 +22,9 @@ public function hasFlag(Flag $flag): bool { return in_array($flag, $this->flags, true); } + + public function __toString(): string + { + return $this->value; + } } diff --git a/src/AnsiString.php b/src/AnsiString.php index 76ccea8..b6bb1a5 100644 --- a/src/AnsiString.php +++ b/src/AnsiString.php @@ -2,6 +2,7 @@ namespace Glhd\AnsiPants; +use Generator; use Glhd\AnsiPants\Support\Parsing\EscapeSequence; use Glhd\AnsiPants\Support\Parsing\Text; use Glhd\AnsiPants\Support\Parsing\Tokenizer; @@ -18,8 +19,12 @@ public static function make(AnsiString|Collection|string $input): static return new static($input); } - public function __construct(AnsiString|Collection|string $input) + public function __construct(AnsiString|AnsiChar|Collection|string $input) { + if ($input instanceof AnsiChar) { + $input = new Collection([$input]); + } + if ($input instanceof Collection) { $input->ensure(AnsiChar::class); $this->chars = clone $input; @@ -35,18 +40,14 @@ public function withFlags(Flag ...$flags): static return new static($this->chars->map(fn(AnsiChar $char) => $char->withFlags(...$flags))); } - public function prepend(AnsiString|string $string): static + public function prepend(AnsiString|AnsiChar|string $string): static { - $string = new AnsiString($string); - - return new AnsiString($string->chars->merge($this->chars)); + return new AnsiString(AnsiString::make($string)->chars->merge($this->chars)); } - public function append(AnsiString|string $string): static + public function append(AnsiString|AnsiChar|string $string): static { - $string = new AnsiString($string); - - return new AnsiString($this->chars->merge($string->chars)); + return new AnsiString($this->chars->merge(AnsiString::make($string)->chars)); } public function padLeft(int $length, AnsiString|string $pad = ' '): static @@ -83,6 +84,34 @@ public function padBoth(int $length, AnsiString|string $pad = ' '): static ->append($right_padding); } + public function wordwrap(int $width = 75, AnsiString|string $break = "\e[0m\n", bool $cut_long_words = false): static + { + $break = new AnsiString($break); + $buffer = new AnsiString(''); + $wrapped = new AnsiString(''); + + foreach ($this->words() as $word) { + [$sep, $word] = $word; + + if (($buffer->length() + $sep->length() + $word->length()) > $width) { + if ($wrapped->length()) { + $wrapped = $wrapped->append($break); + } + + $wrapped = $wrapped->append($buffer); + $buffer = new AnsiString($word); + } else { + $buffer = $buffer->append($sep)->append($word); + } + } + + if ($buffer->length() > 0) { + $wrapped = $wrapped->append($break)->append($buffer); + } + + return $wrapped; + } + public function length(): int { return count($this->chars); @@ -125,6 +154,28 @@ public function __toString(): string return $result; } + /** @return Generator */ + protected function words(): Generator + { + $sep = new static(''); + $word = new static(''); + + foreach ($this->chars as $char) { + if ($word->length() > 0 && in_array($char->value, [' ', "\n"])) { + yield [$sep, $word]; + $word = new static(''); + $sep = new static($char); + continue; + } + + $word->chars->push($char); + } + + if ($word->length() > 0 || $sep->length() > 0) { + yield [$sep, $word]; + } + } + /** @return \Glhd\AnsiPants\Flag[][] */ protected function diffFlags($a, $b): array { diff --git a/tests/Unit/AnsiStringTest.php b/tests/Unit/AnsiStringTest.php index 41772f8..ef25754 100644 --- a/tests/Unit/AnsiStringTest.php +++ b/tests/Unit/AnsiStringTest.php @@ -38,4 +38,21 @@ public function test_pad_both(): void $this->assertEquals($expected, (string) $parsed->padBoth(15)); } + + public function test_word_wrap(): void + { + $input = "The \e[1mquick\e[0m \e[33mbrown fox \e[3mjumps\e[0m over the lazy dog"; + + $expected = <<assertEquals($expected, (string) $parsed->wordwrap(10)); + } }