4
4
5
5
use PhpSchool \CliMenu \Action \ExitAction ;
6
6
use PhpSchool \CliMenu \Action \GoBackAction ;
7
+ use PhpSchool \CliMenu \Exception \InvalidShortcutException ;
7
8
use PhpSchool \CliMenu \MenuItem \AsciiArtItem ;
8
9
use PhpSchool \CliMenu \MenuItem \LineBreakItem ;
9
10
use PhpSchool \CliMenu \MenuItem \MenuItemInterface ;
10
11
use PhpSchool \CliMenu \MenuItem \MenuMenuItem ;
11
12
use PhpSchool \CliMenu \MenuItem \SelectableItem ;
12
13
use PhpSchool \CliMenu \CliMenu ;
14
+ use PhpSchool \CliMenu \MenuItem \SplitItem ;
13
15
use PhpSchool \CliMenu \MenuItem \StaticItem ;
14
16
use PhpSchool \CliMenu \MenuStyle ;
15
17
use PhpSchool \CliMenu \Terminal \TerminalFactory ;
@@ -56,6 +58,22 @@ class CliMenuBuilder
56
58
*/
57
59
private $ disabled = false ;
58
60
61
+ /**
62
+ * Whether or not to auto create keyboard shortcuts for items
63
+ * when they contain square brackets. Eg: [M]y item
64
+ *
65
+ * @var bool
66
+ */
67
+ private $ autoShortcuts = false ;
68
+
69
+ /**
70
+ * Regex to auto match for shortcuts defaults to looking
71
+ * for a single character encased in square brackets
72
+ *
73
+ * @var string
74
+ */
75
+ private $ autoShortcutsRegex = '/\[(.)\]/ ' ;
76
+
59
77
/**
60
78
* @var bool
61
79
*/
@@ -87,6 +105,8 @@ public function addMenuItem(MenuItemInterface $item) : self
87
105
{
88
106
$ this ->menu ->addItem ($ item );
89
107
108
+ $ this ->processItemShortcut ($ item );
109
+
90
110
return $ this ;
91
111
}
92
112
@@ -135,6 +155,10 @@ public function addSubMenu(string $text, \Closure $callback) : self
135
155
{
136
156
$ builder = self ::newSubMenu ($ this ->terminal );
137
157
158
+ if ($ this ->autoShortcuts ) {
159
+ $ builder ->enableAutoShortcuts ($ this ->autoShortcutsRegex );
160
+ }
161
+
138
162
$ callback = $ callback ->bindTo ($ builder );
139
163
$ callback ($ builder );
140
164
@@ -147,12 +171,14 @@ public function addSubMenu(string $text, \Closure $callback) : self
147
171
$ menu ->setStyle ($ this ->menu ->getStyle ());
148
172
}
149
173
150
- $ this ->menu ->addItem (new MenuMenuItem (
174
+ $ this ->menu ->addItem ($ item = new MenuMenuItem (
151
175
$ text ,
152
176
$ menu ,
153
177
$ builder ->isMenuDisabled ()
154
178
));
155
-
179
+
180
+ $ this ->processItemShortcut ($ item );
181
+
156
182
return $ this ;
157
183
}
158
184
@@ -167,24 +193,98 @@ public function addSubMenuFromBuilder(string $text, CliMenuBuilder $builder) : s
167
193
$ menu ->setStyle ($ this ->menu ->getStyle ());
168
194
}
169
195
170
- $ this ->menu ->addItem (new MenuMenuItem (
196
+ $ this ->menu ->addItem ($ item = new MenuMenuItem (
171
197
$ text ,
172
198
$ menu ,
173
199
$ builder ->isMenuDisabled ()
174
200
));
175
201
202
+ $ this ->processItemShortcut ($ item );
203
+
176
204
return $ this ;
177
205
}
178
206
207
+ public function enableAutoShortcuts (string $ regex = null ) : self
208
+ {
209
+ $ this ->autoShortcuts = true ;
210
+
211
+ if (null !== $ regex ) {
212
+ $ this ->autoShortcutsRegex = $ regex ;
213
+ }
214
+
215
+ return $ this ;
216
+ }
217
+
218
+ private function extractShortcut (string $ title ) : ?string
219
+ {
220
+ preg_match ($ this ->autoShortcutsRegex , $ title , $ match );
221
+
222
+ if (!isset ($ match [1 ])) {
223
+ return null ;
224
+ }
225
+
226
+ if (mb_strlen ($ match [1 ]) > 1 ) {
227
+ throw InvalidShortcutException::fromShortcut ($ match [1 ]);
228
+ }
229
+
230
+ return isset ($ match [1 ]) ? strtolower ($ match [1 ]) : null ;
231
+ }
232
+
233
+ private function processItemShortcut (MenuItemInterface $ item ) : void
234
+ {
235
+ $ this ->processIndividualShortcut ($ item , function (CliMenu $ menu ) use ($ item ) {
236
+ $ menu ->executeAsSelected ($ item );
237
+ });
238
+ }
239
+
240
+ private function processSplitItemShortcuts (SplitItem $ splitItem ) : void
241
+ {
242
+ foreach ($ splitItem ->getItems () as $ item ) {
243
+ $ this ->processIndividualShortcut ($ item , function (CliMenu $ menu ) use ($ splitItem , $ item ) {
244
+ $ current = $ splitItem ->getSelectedItemIndex ();
245
+
246
+ $ splitItem ->setSelectedItemIndex (
247
+ array_search ($ item , $ splitItem ->getItems (), true )
248
+ );
249
+
250
+ $ menu ->executeAsSelected ($ splitItem );
251
+
252
+ if ($ current !== null ) {
253
+ $ splitItem ->setSelectedItemIndex ($ current );
254
+ }
255
+ });
256
+ }
257
+ }
258
+
259
+ private function processIndividualShortcut (MenuItemInterface $ item , callable $ callback ) : void
260
+ {
261
+ if (!$ this ->autoShortcuts ) {
262
+ return ;
263
+ }
264
+
265
+ if ($ shortcut = $ this ->extractShortcut ($ item ->getText ())) {
266
+ $ this ->menu ->addCustomControlMapping (
267
+ $ shortcut ,
268
+ $ callback
269
+ );
270
+ }
271
+ }
272
+
179
273
public function addSplitItem (\Closure $ callback ) : self
180
274
{
181
275
$ builder = new SplitItemBuilder ($ this ->menu );
182
276
277
+ if ($ this ->autoShortcuts ) {
278
+ $ builder ->enableAutoShortcuts ($ this ->autoShortcutsRegex );
279
+ }
280
+
183
281
$ callback = $ callback ->bindTo ($ builder );
184
282
$ callback ($ builder );
185
283
186
- $ this ->menu ->addItem ($ builder ->build ());
187
-
284
+ $ this ->menu ->addItem ($ splitItem = $ builder ->build ());
285
+
286
+ $ this ->processSplitItemShortcuts ($ splitItem );
287
+
188
288
return $ this ;
189
289
}
190
290
0 commit comments