|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * Created by PhpStorm. |
| 4 | + * User: inhere |
| 5 | + * Date: 2017-03-08 |
| 6 | + * Time: 9:35 |
| 7 | + */ |
| 8 | + |
| 9 | +namespace Toolkit\Cli; |
| 10 | + |
| 11 | +/** |
| 12 | + * Class Terminal - terminal control by ansiCode |
| 13 | + * @package Toolkit\Cli |
| 14 | + * |
| 15 | + * 2K 清除本行 |
| 16 | + * \x0D = \r = 13 回车,回到行首 |
| 17 | + * ESC = \x1B = \033 = 27 |
| 18 | + */ |
| 19 | +final class Terminal |
| 20 | +{ |
| 21 | + const BEGIN_CHAR = "\033["; |
| 22 | + const END_CHAR = "\033[0m"; |
| 23 | + |
| 24 | + // Control cursor code name list. more @see [[self::$ctrlCursorCodes]] |
| 25 | + const CUR_HIDE = 'hide'; |
| 26 | + const CUR_SHOW = 'show'; |
| 27 | + const CUR_SAVE_POSITION = 'savePosition'; |
| 28 | + const CUR_RESTORE_POSITION = 'restorePosition'; |
| 29 | + const CUR_UP = 'up'; |
| 30 | + const CUR_DOWN = 'down'; |
| 31 | + const CUR_FORWARD = 'forward'; |
| 32 | + const CUR_BACKWARD = 'backward'; |
| 33 | + const CUR_NEXT_LINE = 'nextLine'; |
| 34 | + const CUR_PREV_LINE = 'prevLine'; |
| 35 | + const CUR_COORDINATE = 'coordinate'; |
| 36 | + |
| 37 | + // Control screen code name list. more @see [[self::$ctrlScreenCodes]] |
| 38 | + const CLEAR = 'clear'; |
| 39 | + const CLEAR_BEFORE_CURSOR = 'clearBeforeCursor'; |
| 40 | + const CLEAR_LINE = 'clearLine'; |
| 41 | + const CLEAR_LINE_BEFORE_CURSOR = 'clearLineBeforeCursor'; |
| 42 | + const CLEAR_LINE_AFTER_CURSOR = 'clearLineAfterCursor'; |
| 43 | + |
| 44 | + const SCROLL_UP = 'scrollUp'; |
| 45 | + const SCROLL_DOWN = 'scrollDown'; |
| 46 | + |
| 47 | + /** |
| 48 | + * current class's instance |
| 49 | + * @var self |
| 50 | + */ |
| 51 | + private static $instance; |
| 52 | + |
| 53 | + /** |
| 54 | + * Control cursor code list |
| 55 | + * @var array |
| 56 | + */ |
| 57 | + private static $ctrlCursorCodes = [ |
| 58 | + // Hides the cursor. Use [show] to bring it back. |
| 59 | + 'hide' => '?25l', |
| 60 | + |
| 61 | + // Will show a cursor again when it has been hidden by [hide] |
| 62 | + 'show' => '?25h', |
| 63 | + |
| 64 | + // Saves the current cursor position, Position can then be restored with [restorePosition]. |
| 65 | + // - 保存当前光标位置,然后可以使用[restorePosition]恢复位置 |
| 66 | + 'savePosition' => 's', |
| 67 | + |
| 68 | + // Restores the cursor position saved with [savePosition] - 恢复[savePosition]保存的光标位置 |
| 69 | + 'restorePosition' => 'u', |
| 70 | + |
| 71 | + // Moves the terminal cursor up |
| 72 | + 'up' => '%dA', |
| 73 | + |
| 74 | + // Moves the terminal cursor down |
| 75 | + 'down' => '%B', |
| 76 | + |
| 77 | + // Moves the terminal cursor forward - 移动终端光标前进多远 |
| 78 | + 'forward' => '%dC', |
| 79 | + |
| 80 | + // Moves the terminal cursor backward - 移动终端光标后退多远 |
| 81 | + 'backward' => '%dD', |
| 82 | + |
| 83 | + // Moves the terminal cursor to the beginning of the previous line - 移动终端光标到前一行的开始 |
| 84 | + 'prevLine' => '%dF', |
| 85 | + |
| 86 | + // Moves the terminal cursor to the beginning of the next line - 移动终端光标到下一行的开始 |
| 87 | + 'nextLine' => '%dE', |
| 88 | + |
| 89 | + // Moves the cursor to an absolute position given as column and row |
| 90 | + // $column 1-based column number, 1 is the left edge of the screen. |
| 91 | + // $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. |
| 92 | + 'coordinate' => '%dG|%d;%dH' // only column: '%dG', column and row: '%d;%dH'. |
| 93 | + ]; |
| 94 | + |
| 95 | + /** |
| 96 | + * Control screen code list |
| 97 | + * @var array |
| 98 | + */ |
| 99 | + private static $ctrlScreenCodes = [ |
| 100 | + // Clears entire screen content - 清除整个屏幕内容 |
| 101 | + 'clear' => '2J', // "\033[2J" |
| 102 | + |
| 103 | + // Clears text from cursor to the beginning of the screen - 从光标清除文本到屏幕的开头 |
| 104 | + 'clearBeforeCursor' => '1J', |
| 105 | + |
| 106 | + // Clears the line - 清除此行 |
| 107 | + 'clearLine' => '2K', |
| 108 | + |
| 109 | + // Clears text from cursor position to the beginning of the line - 清除此行从光标位置开始到开始的字符 |
| 110 | + 'clearLineBeforeCursor' => '1K', |
| 111 | + |
| 112 | + // Clears text from cursor position to the end of the line - 清除此行从光标位置开始到结束的字符 |
| 113 | + 'clearLineAfterCursor' => '0K', |
| 114 | + |
| 115 | + // Scrolls whole page up. e.g "\033[2S" scroll up 2 line. - 上移多少行 |
| 116 | + 'scrollUp' => '%dS', |
| 117 | + |
| 118 | + // Scrolls whole page down.e.g "\033[2T" scroll down 2 line. - 下移多少行 |
| 119 | + 'scrollDown' => '%dT', |
| 120 | + ]; |
| 121 | + |
| 122 | + public static function make(): Terminal |
| 123 | + { |
| 124 | + if (!self::$instance) { |
| 125 | + self::$instance = new self; |
| 126 | + } |
| 127 | + |
| 128 | + return self::$instance; |
| 129 | + } |
| 130 | + |
| 131 | + /** |
| 132 | + * build ansi code string |
| 133 | + * |
| 134 | + * ``` |
| 135 | + * Terminal::build(null, 'u'); // "\033[s" Saves the current cursor position |
| 136 | + * Terminal::build(0); // "\033[0m" Build end char, Resets any ANSI format |
| 137 | + * ``` |
| 138 | + * |
| 139 | + * @param mixed $format |
| 140 | + * @param string $type |
| 141 | + * @return string |
| 142 | + */ |
| 143 | + public static function build($format, $type = 'm'): string |
| 144 | + { |
| 145 | + $format = null === $format ? '' : implode(';', (array)$format); |
| 146 | + |
| 147 | + return "\033[" . implode(';', (array)$format) . $type; |
| 148 | + } |
| 149 | + |
| 150 | + /** |
| 151 | + * control cursor |
| 152 | + * @param string $typeName |
| 153 | + * @param int $arg1 |
| 154 | + * @param null $arg2 |
| 155 | + * @return $this |
| 156 | + */ |
| 157 | + public function cursor($typeName, $arg1 = 1, $arg2 = null): self |
| 158 | + { |
| 159 | + if (!isset(self::$ctrlCursorCodes[$typeName])) { |
| 160 | + Cli::stderr("The [$typeName] is not supported cursor control.", true); |
| 161 | + } |
| 162 | + |
| 163 | + $code = self::$ctrlCursorCodes[$typeName]; |
| 164 | + |
| 165 | + // allow argument |
| 166 | + if (false !== strpos($code, '%')) { |
| 167 | + // The special code: ` 'coordinate' => '%dG|%d;%dH' ` |
| 168 | + if ($typeName === self::CUR_COORDINATE) { |
| 169 | + $codes = explode('|', $code); |
| 170 | + |
| 171 | + if (null === $arg2) { |
| 172 | + $code = sprintf($codes[0], $arg1); |
| 173 | + } else { |
| 174 | + $code = sprintf($codes[1], $arg1, $arg2); |
| 175 | + } |
| 176 | + |
| 177 | + } else { |
| 178 | + $code = sprintf($code, $arg1); |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + echo self::build($code, ''); |
| 183 | + |
| 184 | + return $this; |
| 185 | + } |
| 186 | + |
| 187 | + /** |
| 188 | + * control screen |
| 189 | + * @param $typeName |
| 190 | + * @param null $arg |
| 191 | + * @return $this |
| 192 | + */ |
| 193 | + public function screen(string $typeName, $arg = null): self |
| 194 | + { |
| 195 | + if (!isset(self::$ctrlScreenCodes[$typeName])) { |
| 196 | + Cli::stderr("The [$typeName] is not supported cursor control."); |
| 197 | + } |
| 198 | + |
| 199 | + $code = self::$ctrlScreenCodes[$typeName]; |
| 200 | + |
| 201 | + // allow argument |
| 202 | + if (false !== strpos($code, '%')) { |
| 203 | + $code = sprintf($code, $arg); |
| 204 | + } |
| 205 | + |
| 206 | + echo self::build($code, ''); |
| 207 | + |
| 208 | + return $this; |
| 209 | + } |
| 210 | + |
| 211 | + public function reset() |
| 212 | + { |
| 213 | + echo self::END_CHAR; |
| 214 | + } |
| 215 | + |
| 216 | + /** |
| 217 | + * @return array |
| 218 | + */ |
| 219 | + public static function supportedCursorCtrl(): array |
| 220 | + { |
| 221 | + return \array_keys(self::$ctrlCursorCodes); |
| 222 | + } |
| 223 | + |
| 224 | + /** |
| 225 | + * @return array |
| 226 | + */ |
| 227 | + public static function supportedScreenCtrl(): array |
| 228 | + { |
| 229 | + return \array_keys(self::$ctrlScreenCodes); |
| 230 | + } |
| 231 | +} |
0 commit comments