1+ <?php
2+
3+
4+ namespace Ubiquity \devtools \cmd ;
5+
6+ /**
7+ * Class Screen
8+ * Forked from https://github.com/symfony/console/blob/5.3/Terminal.php
9+ * @package Ubiquity\devtools\cmd
10+ */
11+ class Screen {
12+ private static $ width ;
13+ private static $ height ;
14+ private static $ stty ;
15+
16+ /**
17+ * Gets the terminal width.
18+ *
19+ * @return int
20+ */
21+ public static function getWidth () {
22+ $ width = \getenv ('COLUMNS ' );
23+ if (false !== $ width ) {
24+ return (int ) \trim ($ width );
25+ }
26+
27+ if (null === self ::$ width ) {
28+ self ::initDimensions ();
29+ }
30+
31+ return self ::$ width ??80 ;
32+ }
33+
34+ /**
35+ * Gets the terminal height.
36+ *
37+ * @return int
38+ */
39+ public static function getHeight () {
40+ $ height = \getenv ('LINES ' );
41+ if (false !== $ height ) {
42+ return (int ) trim ($ height );
43+ }
44+
45+ if (null === self ::$ height ) {
46+ self ::initDimensions ();
47+ }
48+
49+ return self ::$ height ??50 ;
50+ }
51+
52+ /**
53+ * @internal
54+ *
55+ * @return bool
56+ */
57+ public static function hasSttyAvailable () {
58+ if (null !== self ::$ stty ) {
59+ return self ::$ stty ;
60+ }
61+
62+ // skip check if exec function is disabled
63+ if (!\function_exists ('exec ' )) {
64+ return false ;
65+ }
66+
67+ \exec ('stty 2>&1 ' , $ output , $ exitcode );
68+
69+ return self ::$ stty = 0 === $ exitcode ;
70+ }
71+
72+ private static function initDimensions () {
73+ if ('\\' === \DIRECTORY_SEPARATOR ) {
74+ if (\preg_match ('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/ ' , \trim (\getenv ('ANSICON ' )), $ matches )) {
75+ // extract [w, H] from "wxh (WxH)"
76+ // or [w, h] from "wxh"
77+ self ::$ width = (int ) $ matches [1 ];
78+ self ::$ height = isset ($ matches [4 ]) ? (int ) $ matches [4 ] : (int ) $ matches [2 ];
79+ } elseif (!self ::hasVt100Support () && self ::hasSttyAvailable ()) {
80+ // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
81+ // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
82+ self ::initDimensionsUsingStty ();
83+ } elseif (null !== $ dimensions = self ::getConsoleMode ()) {
84+ // extract [w, h] from "wxh"
85+ self ::$ width = (int ) $ dimensions [0 ];
86+ self ::$ height = (int ) $ dimensions [1 ];
87+ }
88+ } else {
89+ self ::initDimensionsUsingStty ();
90+ }
91+ }
92+
93+ /**
94+ * Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
95+ */
96+ private static function hasVt100Support (): bool {
97+ return \function_exists ('sapi_windows_vt100_support ' ) && \sapi_windows_vt100_support (\fopen ('php://stdout ' , 'w ' ));
98+ }
99+
100+ /**
101+ * Initializes dimensions using the output of an stty columns line.
102+ */
103+ private static function initDimensionsUsingStty () {
104+ if ($ sttyString = self ::getSttyColumns ()) {
105+ if (\preg_match ('/rows.(\d+);.columns.(\d+);/i ' , $ sttyString , $ matches )) {
106+ // extract [w, h] from "rows h; columns w;"
107+ self ::$ width = (int ) $ matches [2 ];
108+ self ::$ height = (int ) $ matches [1 ];
109+ } elseif (\preg_match ('/;.(\d+).rows;.(\d+).columns/i ' , $ sttyString , $ matches )) {
110+ // extract [w, h] from "; h rows; w columns"
111+ self ::$ width = (int ) $ matches [2 ];
112+ self ::$ height = (int ) $ matches [1 ];
113+ }
114+ }
115+ }
116+
117+ /**
118+ * Runs and parses mode CON if it's available, suppressing any error output.
119+ *
120+ * @return int[]|null An array composed of the width and the height or null if it could not be parsed
121+ */
122+ private static function getConsoleMode (): ?array {
123+ $ info = self ::readFromProcess ('mode CON ' );
124+
125+ if (null === $ info || !preg_match ('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/ ' , $ info , $ matches )) {
126+ return null ;
127+ }
128+
129+ return [(int ) $ matches [2 ], (int ) $ matches [1 ]];
130+ }
131+
132+ /**
133+ * Runs and parses stty -a if it's available, suppressing any error output.
134+ */
135+ private static function getSttyColumns (): ?string {
136+ return self ::readFromProcess ('stty -a | grep columns ' );
137+ }
138+
139+ private static function readFromProcess (string $ command ): ?string {
140+ if (!\function_exists ('proc_open ' )) {
141+ return null ;
142+ }
143+
144+ $ descriptorspec = [
145+ 1 => ['pipe ' , 'w ' ],
146+ 2 => ['pipe ' , 'w ' ],
147+ ];
148+
149+ $ process = \proc_open ($ command , $ descriptorspec , $ pipes , null , null , ['suppress_errors ' => true ]);
150+ if (!\is_resource ($ process )) {
151+ return null ;
152+ }
153+
154+ $ info = \stream_get_contents ($ pipes [1 ]);
155+ \fclose ($ pipes [1 ]);
156+ \fclose ($ pipes [2 ]);
157+ \proc_close ($ process );
158+
159+ return $ info ;
160+ }
161+ }
0 commit comments