├── img ├── week_count.png ├── single_day_dot.png ├── single_day_count.png ├── single_day_date.png ├── single_day_list.png ├── LaravelScheduleCalendar.png ├── single_day_24hourPerLine.png └── single_day_6hourPerLine.png ├── .gitignore ├── src ├── LaravelScheduleCalendarServiceProvider.php └── Console │ └── Commands │ └── ScheduleCalendarCommand.php ├── LICENSE.md ├── composer.json └── README.md /img/week_count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inDeev/Laravel-Schedule-Calendar/HEAD/img/week_count.png -------------------------------------------------------------------------------- /img/single_day_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inDeev/Laravel-Schedule-Calendar/HEAD/img/single_day_dot.png -------------------------------------------------------------------------------- /img/single_day_count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inDeev/Laravel-Schedule-Calendar/HEAD/img/single_day_count.png -------------------------------------------------------------------------------- /img/single_day_date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inDeev/Laravel-Schedule-Calendar/HEAD/img/single_day_date.png -------------------------------------------------------------------------------- /img/single_day_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inDeev/Laravel-Schedule-Calendar/HEAD/img/single_day_list.png -------------------------------------------------------------------------------- /img/LaravelScheduleCalendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inDeev/Laravel-Schedule-Calendar/HEAD/img/LaravelScheduleCalendar.png -------------------------------------------------------------------------------- /img/single_day_24hourPerLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inDeev/Laravel-Schedule-Calendar/HEAD/img/single_day_24hourPerLine.png -------------------------------------------------------------------------------- /img/single_day_6hourPerLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inDeev/Laravel-Schedule-Calendar/HEAD/img/single_day_6hourPerLine.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | node_modules/ 3 | npm-debug.log 4 | yarn-error.log 5 | 6 | # Laravel 4 specific 7 | bootstrap/compiled.php 8 | app/storage/ 9 | 10 | # Laravel 5 & Lumen specific 11 | public/storage 12 | public/hot 13 | storage/*.key 14 | .env 15 | Homestead.yaml 16 | Homestead.json 17 | /.vagrant 18 | .phpunit.result.cache 19 | 20 | .idea/ 21 | 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /src/LaravelScheduleCalendarServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 16 | $this->commands([ 17 | ScheduleCalendarCommand::class, 18 | ]); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2023 Petr Katerinak 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "indeev/laravel-schedule-calendar", 3 | "description": "Laravel Schedule Calendar - a package providing developers with a concise and visual representation of scheduled tasks, enabling easy analysis of load distribution throughout the day or week for optimized task scheduling.", 4 | "keywords": [ 5 | "indeev", 6 | "laravel-schedule-calendar", 7 | "laravel", 8 | "schedule", 9 | "calendar" 10 | ], 11 | "homepage": "https://github.com/indeev/laravel-schedule-calendar", 12 | "license": "MIT", 13 | "type": "library", 14 | "authors": [ 15 | { 16 | "name": "Petr Kateřiňák", 17 | "email": "katerinak@indeev.eu", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": { 22 | "php": "^7.3 || ^8.0" 23 | }, 24 | "require-dev": { 25 | "orchestra/testbench": "^v6.40.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Indeev\\LaravelScheduleCalendar\\": "src" 30 | } 31 | }, 32 | "config": { 33 | "sort-packages": true 34 | }, 35 | "extra": { 36 | "laravel": { 37 | "providers": [ 38 | "Indeev\\LaravelScheduleCalendar\\LaravelScheduleCalendarServiceProvider" 39 | ] 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Schedule Calendar 2 | 3 | [![Latest Stable Version](http://poser.pugx.org/indeev/laravel-schedule-calendar/v)](https://packagist.org/packages/indeev/laravel-schedule-calendar) 4 | [![Total Downloads](http://poser.pugx.org/indeev/laravel-schedule-calendar/downloads)](https://packagist.org/packages/indeev/laravel-schedule-calendar) 5 | [![Latest Unstable Version](http://poser.pugx.org/indeev/laravel-schedule-calendar/v/unstable)](https://packagist.org/packages/indeev/laravel-rapid-db-anonymizer) 6 | [![License](http://poser.pugx.org/indeev/laravel-schedule-calendar/license)](https://packagist.org/packages/indeev/laravel-schedule-calendar) 7 | 8 | ![Laravel Remote DB Sync](https://github.com/inDeev/Laravel-Schedule-Calendar/blob/main/img/LaravelScheduleCalendar.png) 9 | 10 | ## Overview 11 | 12 | The Schedule Calendar command has been introduced to provide developers with a clear and insightful view of scheduled tasks within the Laravel application. This new functionality allows for a visual representation of task distribution throughout the day and week, offering a valuable perspective on load distribution. 13 | 14 | ## Requirements 15 | 16 | - PHP 7.3 or higher 17 | - Laravel 8+ 18 | 19 | ## Key Features 20 | 21 | - **Day and Week View:** Easily switch between day and week views to analyze scheduled tasks over different time frames. 22 | 23 | - **Load Distribution:** Gain insights into the distribution of scheduled tasks throughout the day, helping identify peak load periods and optimize task scheduling. 24 | 25 | - **Enhanced Debugging:** Use the calendar view as a debugging tool to identify potential conflicts or overlaps in scheduled tasks. 26 | 27 | ## Installation 28 | 29 | The Schedule Calendar command is available as a package on [Packagist](https://packagist.org/packages/indeev/laravel-schedule-calendar) and can be installed using [Composer](https://getcomposer.org/). 30 | 31 | ```bash 32 | composer require indeev/laravel-schedule-calendar 33 | ``` 34 | 35 | ## How to Use 36 | 37 | To leverage the power of Schedule Calendar, simply run the command in your Laravel application: 38 | 39 | ```bash 40 | php artisan schedule:calendar 41 | ``` 42 | 43 | This will generate a visual representation of your scheduled tasks, providing a comprehensive overview of your application's task schedule. 44 | 45 | ![Single day with counts](https://github.com/inDeev/Laravel-Schedule-Calendar/blob/main/img/single_day_count.png) 46 | 47 | ## Display Option: `--display=dot` 48 | 49 | The `--display=dot` option provides a visual representation of your scheduled tasks using dots, offering a clear and concise overview. Each dot represents all scheduled tasks in time piece, making it easy to identify the distribution of tasks throughout the specified time range. 50 | 51 | ### Usage: 52 | 53 | ```bash 54 | php artisan schedule:calendar --display=dot 55 | ``` 56 | 57 | ![Single day with dots](https://github.com/inDeev/Laravel-Schedule-Calendar/blob/main/img/single_day_dot.png) 58 | 59 | ## Display Option: `--display=list` 60 | 61 | The `--display=list` option provides a detailed list of concrete commands for each time piece, offering a comprehensive view of your scheduled activities. 62 | 63 | ### Usage: 64 | 65 | ```bash 66 | php artisan schedule:calendar --display=list 67 | ``` 68 | 69 | ![Single day list](https://github.com/inDeev/Laravel-Schedule-Calendar/blob/main/img/single_day_list.png) 70 | 71 | ## Range Option: `--range=week` 72 | 73 | The `--range=week` option allows you to view scheduled tasks for the week around a specified day (or current day as default), providing a broader context of your upcoming activities. 74 | 75 | ### Usage: 76 | 77 | ```bash 78 | php artisan schedule:calendar --range=week 79 | ``` 80 | 81 | ![Week count](https://github.com/inDeev/Laravel-Schedule-Calendar/blob/main/img/week_count.png) 82 | 83 | ## Date Selection Option: `--day=YYYY-MM-DD` 84 | 85 | The `--day=YYYY-MM-DD` option allows you to specify a particular date for viewing the scheduled tasks, providing detailed insights into the tasks for that specific day. 86 | 87 | ### Usage: 88 | 89 | ```bash 90 | php artisan schedule:calendar --day=yyyy-mm-dd 91 | ``` 92 | 93 | ![Single day date](https://github.com/inDeev/Laravel-Schedule-Calendar/blob/main/img/single_day_date.png) 94 | 95 | ## Hours per line Option: `--hoursPerLine` 96 | 97 | The `--hoursPerLine` option allows you to specify how many hours will be displayed per one output line. This parameter provides flexibility in tailoring the visual representation based on your preferences. 98 | 99 | ### Usage: 100 | 101 | ```bash 102 | php artisan schedule:calendar --hoursPerLine=6 103 | ``` 104 | 105 | ![Single day 6 hours](https://github.com/inDeev/Laravel-Schedule-Calendar/blob/main/img/single_day_6hourPerLine.png) 106 | 107 | ```bash 108 | php artisan schedule:calendar --hoursPerLine=24 109 | ``` 110 | 111 | ![Single day 24 hours](https://github.com/inDeev/Laravel-Schedule-Calendar/blob/main/img/single_day_24hourPerLine.png) 112 | 113 | ## Contribution 114 | 115 | 👋 Thank you for considering contributing to our project! We welcome contributions from the community to help make this project even better. Whether you're fixing a bug, improving documentation, or adding a new feature, your efforts are highly appreciated and will be credited. 116 | 117 | ## Credits 118 | 119 | - [Petr Kateřiňák](https://github.com/indeev) 120 | - [Jarand](https://github.com/lokeland) 121 | 122 | ## License 123 | 124 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 125 | -------------------------------------------------------------------------------- /src/Console/Commands/ScheduleCalendarCommand.php: -------------------------------------------------------------------------------- 1 | 0, 87 | 'max_lines' => 1, 88 | 'used_symbols' => [], 89 | ]; 90 | 91 | /** 92 | * Execute the console command. 93 | * 94 | * @param Schedule $schedule 95 | * 96 | * @throws Exception 97 | */ 98 | public function handle(Schedule $schedule): void 99 | { 100 | $date = $this->option('date'); 101 | if ($date === 'today') { 102 | $date = today(); 103 | } elseif (preg_match('#^\d{4}-\d{2}-\d{2}$#', $date)) { 104 | $date = Carbon::parse($date); 105 | } else { 106 | $this->error('Date must be "today" or date in format "yyyy-mm-dd".'); 107 | return; 108 | } 109 | 110 | $range = $this->option('range'); 111 | if (!in_array($range, ['day', 'week'], true)) { 112 | $this->error('Range must be one of "day" or "week".'); 113 | return; 114 | } 115 | 116 | $hoursPerLine = $this->option('hoursPerLine'); 117 | if (!in_array($hoursPerLine, $this->allowedHoursPerLine, false)) { 118 | $this->error('Hours per line must be one of '.implode(', ', $this->allowedHoursPerLine).'.'); 119 | return; 120 | } 121 | $this->hoursPerLine = (int) $hoursPerLine; 122 | 123 | $display = $this->option('display'); 124 | if (!in_array($display, ['dot', 'count', 'list'], true)) { 125 | $this->error('Display must be one of "dot", "count", "list".'); 126 | return; 127 | } 128 | $this->display = $display; 129 | 130 | $terminalWidth = self::getTerminalWidth(); 131 | 132 | $this->hourWidth = (int) (($terminalWidth - 1) / $this->hoursPerLine); 133 | $this->minutesPerField = 60 / ($this->hourWidth - 1); 134 | $selectedIndex = array_search($this->hoursPerLine, $this->allowedHoursPerLine, true); 135 | while ($this->hourWidth < 7 && $selectedIndex > 0) { 136 | $this->hoursPerLine = $this->allowedHoursPerLine[$selectedIndex - 1]; 137 | $this->hourWidth = (int) (($terminalWidth - 1) / $this->hoursPerLine); 138 | $this->minutesPerField = 60 / ($this->hourWidth - 1); 139 | $selectedIndex = array_search($this->hoursPerLine, $this->allowedHoursPerLine, true); 140 | } 141 | if ($this->hoursPerLine !== (int) $this->option('hoursPerLine')) { 142 | $this->error('Terminal width is too small. Hours per line adjusted to '.$this->hoursPerLine.'.'); 143 | } 144 | 145 | if ($range === 'week') { 146 | $start = $date->copy()->startOfWeek(); 147 | $end = $date->copy()->endOfWeek(); 148 | } else { 149 | $start = $date->copy()->startOfDay(); 150 | $end = $date->copy()->endOfDay(); 151 | } 152 | 153 | $period = new CarbonPeriod($start, '1 day', $end); 154 | 155 | $this->prepareDatetimeArray($period); 156 | 157 | $this->mapTasks($schedule, $start, $end); 158 | 159 | $this->printCalendar($period); 160 | } 161 | 162 | /** 163 | * Prepare array of datetime for calendar. 164 | */ 165 | private function prepareDatetimeArray(CarbonPeriod $period): void 166 | { 167 | /** @var Carbon $day */ 168 | foreach ($period as $day) { 169 | for ($hour = 0; $hour < 24; $hour++) { 170 | for ($minutes = 0; $minutes < $this->hourWidth - 1; $minutes++) { 171 | $fieldStart = $day->copy() 172 | ->addHours($hour) 173 | ->addMinutes($minutes * (int) $this->minutesPerField) 174 | ->addSeconds($minutes * (int) (60 * ($this->minutesPerField - (int) $this->minutesPerField))); 175 | $this->scheduledTasks[$day->toDateString()][$hour][$fieldStart->toDateTimeString()] = []; 176 | } 177 | } 178 | } 179 | } 180 | 181 | /** 182 | * Map scheduled tasks to datetime array. 183 | * @throws Exception 184 | */ 185 | private function mapTasks(Schedule $schedule, CarbonInterface $start, CarbonInterface $end): void 186 | { 187 | $events = collect($schedule->events()); 188 | 189 | foreach ($events as $i => $event) { 190 | $commandSymbol = $this->generateSymbol($i); 191 | $command = str_replace([Application::phpBinary(), Application::artisanBinary()], [ 192 | 'php', 193 | preg_replace("#['\"]#", '', Application::artisanBinary()), 194 | ], $event->command); 195 | $this->commands[$commandSymbol] = $command; 196 | 197 | $cronExpression = new CronExpression($event->expression); 198 | 199 | $nextRunDate = $cronExpression->getNextRunDate($start, 0, true); 200 | while ($nextRunDate <= $end) { 201 | $dateString = $nextRunDate->format('Y-m-d'); 202 | $hourString = $nextRunDate->format('G'); 203 | $prevKey = key($this->scheduledTasks[$dateString][$hourString]); 204 | end($this->scheduledTasks[$dateString][$hourString]); 205 | $lastKey = key($this->scheduledTasks[$dateString][$hourString]); 206 | foreach ($this->scheduledTasks[$dateString][$hourString] as $key => $value) { 207 | if (new DateTime($key) > $nextRunDate) { 208 | $this->attachCommandToDatetime($dateString, $hourString, $prevKey, $commandSymbol); 209 | break; 210 | } 211 | $prevKey = $key; 212 | } 213 | if ($prevKey === $lastKey) { 214 | $this->attachCommandToDatetime($dateString, $hourString, $prevKey, $commandSymbol); 215 | } 216 | $nextRunDate = Carbon::parse($cronExpression->getNextRunDate($nextRunDate, 1, true)); 217 | } 218 | } 219 | $this->printInfo['used_symbols'] = array_unique($this->printInfo['used_symbols']); 220 | } 221 | 222 | /** 223 | * Attach command symbol to datetime. 224 | */ 225 | private function attachCommandToDatetime(string $dateString, string $hourString, string $key, string $commandSymbol): void 226 | { 227 | $this->scheduledTasks[$dateString][$hourString][$key]['symbols'][] = $commandSymbol; 228 | $symbolsCount = count($this->scheduledTasks[$dateString][$hourString][$key]['symbols']); 229 | $this->printInfo['max_commands'] = max($this->printInfo['max_commands'], $symbolsCount); 230 | $this->printInfo['used_symbols'][] = $commandSymbol; 231 | if ($this->display !== 'dot') { 232 | $this->printInfo['max_lines'] = $this->display === 'count' 233 | ? max($this->printInfo['max_lines'], strlen((string) $symbolsCount)) 234 | : max($this->printInfo['max_lines'], $symbolsCount); 235 | } 236 | } 237 | 238 | /** 239 | * Generate symbol for command by index. 240 | */ 241 | private function generateSymbol(int $index): string 242 | { 243 | $numLowercase = 26; 244 | $numUppercase = 26; 245 | $numDigits = 10; 246 | 247 | if ($index < $numLowercase) { 248 | return chr(ord('a') + $index); 249 | } 250 | 251 | if ($index < $numLowercase + $numUppercase) { 252 | return chr(ord('A') + ($index - $numLowercase)); 253 | } 254 | 255 | if ($index < $numLowercase + $numUppercase + $numDigits) { 256 | return (string) ($index - $numLowercase - $numUppercase); 257 | } 258 | 259 | $start = 0x2460; // Unicode point for ① 260 | 261 | return html_entity_decode('&#'.($start + $index - 62).';', ENT_COMPAT, 'UTF-8'); 262 | } 263 | 264 | /** 265 | * Print calendar. 266 | */ 267 | private function printCalendar(CarbonPeriod $days): void 268 | { 269 | $this->line(str_pad('Legend', ($this->hourWidth * $this->hoursPerLine + 1), ' ', STR_PAD_BOTH), 'bg=blue;fg=bright-white'); 270 | if ($this->printInfo['max_commands'] === 0) { 271 | $this->line('Your Kernel.php looks empty, let\'s add some scheduled tasks!'); 272 | } else { 273 | $colorStep = $this->printInfo['max_commands'] / 3; 274 | $this->line('● - <= '.floor($colorStep).' tasks'); 275 | $this->line('● - <= '.floor($colorStep * 2).' tasks'); 276 | $this->line('● - <= '.floor($colorStep * 3).' tasks'); 277 | if ($this->display === 'list') { 278 | foreach ($this->commands as $symbol => $command) { 279 | if (in_array($symbol, $this->printInfo['used_symbols'], true)) { 280 | $this->line(''.$symbol.' - '.$command); 281 | } 282 | } 283 | $this->line(''); 284 | } 285 | } 286 | 287 | /** @var Carbon $day */ 288 | foreach ($days as $day) { 289 | $dayString = $day->toDateString(); 290 | $this->line(str_pad($day->format('l Y-m-d'), ($this->hourWidth * $this->hoursPerLine + 1), ' ', STR_PAD_BOTH), 'bg=blue;fg=bright-white'); 291 | $hour = today(); 292 | for ($lines = 0, $totalLines = 24 / $this->hoursPerLine; $lines < $totalLines; $lines++) { 293 | $this->printHourLine($hour); 294 | $this->output->newLine(); 295 | $linesArray = []; 296 | for ($hours = $lines * $this->hoursPerLine, $maxHour = $lines * $this->hoursPerLine + $this->hoursPerLine; $hours < $maxHour; $hours++) { 297 | $linesArray[0][] = ['value' => '|']; 298 | for ($minutes = 0; $minutes < $this->hourWidth - 1; $minutes++) { 299 | $fieldStart = $day->copy() 300 | ->addHours($hours) 301 | ->addMinutes($minutes * (int) $this->minutesPerField) 302 | ->addSeconds($minutes * (int) (60 * ($this->minutesPerField - (int) $this->minutesPerField))) 303 | ->toDateTimeString(); 304 | $scheduledTask = $this->scheduledTasks[$dayString][$hours][$fieldStart] ?? []; 305 | $style = $this->getColorBasedOnMaxTasks($scheduledTask); 306 | 307 | if ($this->display === 'count') { 308 | $minutesFieldCharacter = !empty($scheduledTask['symbols']) ? ['value' => count($scheduledTask['symbols']), 'style' => $style] : ['value' => '⎯']; 309 | } elseif ($this->display === 'list') { 310 | $minutesFieldCharacter = !empty($scheduledTask['symbols']) ? ['value' => implode('', $scheduledTask['symbols']), 'style' => $style] : ['value' => '⎯']; 311 | } else { 312 | $minutesFieldCharacter = !empty($scheduledTask['symbols']) ? ['value' => '●', 'style' => $style] : ['value' => '⎯']; 313 | } 314 | 315 | $linesArray[0][] = $minutesFieldCharacter; 316 | } 317 | } 318 | $linesArray[0][] = ['value' => '|']; 319 | 320 | foreach ($linesArray[0] as $key => $record) { 321 | for ($i = 0; $i < $this->printInfo['max_lines']; $i++) { 322 | $styleStart = $record['style'] ?? ''; 323 | $styleEnd = $record['style'] ?? null ? '' : ''; 324 | $character = mb_strlen((string) $record['value']) > $i ? mb_substr((string) $record['value'], $i, 1) : ' '; 325 | $linesArray[$i][$key] = $styleStart.$character.$styleEnd; 326 | } 327 | } 328 | foreach ($linesArray as $lineValue) { 329 | $this->output->writeln(implode('', (array) $lineValue)); 330 | } 331 | } 332 | } 333 | } 334 | 335 | /** 336 | * Print hour line. 337 | */ 338 | private function printHourLine(CarbonInterface $startHour): void 339 | { 340 | for ($hours = 0; $hours < $this->hoursPerLine; $hours++) { 341 | $this->output->write($startHour->format('H:i')); 342 | $this->output->write(str_repeat(' ', $this->hourWidth - ($hours === 0 ? 7 : 5))); 343 | $startHour = $startHour->addHour(); 344 | } 345 | } 346 | 347 | /** 348 | * Get color based on max tasks. 349 | */ 350 | private function getColorBasedOnMaxTasks(array $scheduledTask): string 351 | { 352 | $colorStep = $this->printInfo['max_commands'] / 3; 353 | $scheduledTaskSymbolsCount = count($scheduledTask['symbols'] ?? []); 354 | 355 | if ($scheduledTaskSymbolsCount <= $colorStep) { 356 | return ''; 357 | } 358 | 359 | if ($scheduledTaskSymbolsCount <= $colorStep * 2) { 360 | return ''; 361 | } 362 | 363 | if ($scheduledTaskSymbolsCount <= $colorStep * 3) { 364 | return ''; 365 | } 366 | 367 | return 'white'; 368 | } 369 | 370 | /** 371 | * Get the terminal width. 372 | */ 373 | public static function getTerminalWidth(): int 374 | { 375 | return is_null(static::$terminalWidthResolver) 376 | ? (new Terminal)->getWidth() 377 | : call_user_func(static::$terminalWidthResolver); 378 | } 379 | } 380 | --------------------------------------------------------------------------------