1
1
#include "cli_i.h"
2
2
#include "cli_commands.h"
3
3
#include "cli_vcp.h"
4
+ #include "cli_ansi.h"
4
5
#include <furi_hal_version.h>
5
6
#include <loader/loader.h>
6
7
7
8
#define TAG "CliSrv"
8
9
9
10
#define CLI_INPUT_LEN_LIMIT 256
11
+ #define CLI_PROMPT ">: " // qFlipper does not recognize us if we use escape sequences :(
12
+ #define CLI_PROMPT_LENGTH 3 // printable characters
10
13
11
14
Cli * cli_alloc (void ) {
12
15
Cli * cli = malloc (sizeof (Cli ));
@@ -85,7 +88,7 @@ bool cli_cmd_interrupt_received(Cli* cli) {
85
88
char c = '\0' ;
86
89
if (cli_is_connected (cli )) {
87
90
if (cli -> session -> rx ((uint8_t * )& c , 1 , 0 ) == 1 ) {
88
- return c == CliSymbolAsciiETX ;
91
+ return c == CliKeyETX ;
89
92
}
90
93
} else {
91
94
return true;
@@ -102,7 +105,8 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
102
105
}
103
106
104
107
void cli_motd (void ) {
105
- printf ("\r\n"
108
+ printf (ANSI_FLIPPER_BRAND_ORANGE
109
+ "\r\n"
106
110
" _.-------.._ -,\r\n"
107
111
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
108
112
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
@@ -116,12 +120,11 @@ void cli_motd(void) {
116
120
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
117
121
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
118
122
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
119
- "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
120
- "\r\n"
121
- "Welcome to Flipper Zero Command Line Interface!\r\n"
123
+ "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" ANSI_RESET
124
+ "\r\n" ANSI_FG_BR_WHITE "Welcome to " ANSI_FLIPPER_BRAND_ORANGE
125
+ "Flipper Zero" ANSI_FG_BR_WHITE " Command Line Interface!\r\n"
122
126
"Read the manual: https://docs.flipper.net/development/cli\r\n"
123
- "Run `help` or `?` to list available commands\r\n"
124
- "\r\n" );
127
+ "Run `help` or `?` to list available commands\r\n" ANSI_RESET "\r\n" );
125
128
126
129
const Version * firmware_version = furi_hal_version_get_firmware_version ();
127
130
if (firmware_version ) {
@@ -142,7 +145,7 @@ void cli_nl(Cli* cli) {
142
145
143
146
void cli_prompt (Cli * cli ) {
144
147
UNUSED (cli );
145
- printf ("\r\n>: %s" , furi_string_get_cstr (cli -> line ));
148
+ printf ("\r\n" CLI_PROMPT " %s" , furi_string_get_cstr (cli -> line ));
146
149
fflush (stdout );
147
150
}
148
151
@@ -165,7 +168,7 @@ static void cli_handle_backspace(Cli* cli) {
165
168
166
169
cli -> cursor_position -- ;
167
170
} else {
168
- cli_putc (cli , CliSymbolAsciiBell );
171
+ cli_putc (cli , CliKeyBell );
169
172
}
170
173
}
171
174
@@ -241,7 +244,7 @@ static void cli_handle_enter(Cli* cli) {
241
244
printf (
242
245
"`%s` command not found, use `help` or `?` to list all available commands" ,
243
246
furi_string_get_cstr (command ));
244
- cli_putc (cli , CliSymbolAsciiBell );
247
+ cli_putc (cli , CliKeyBell );
245
248
}
246
249
247
250
cli_reset (cli );
@@ -305,8 +308,85 @@ static void cli_handle_autocomplete(Cli* cli) {
305
308
cli_prompt (cli );
306
309
}
307
310
308
- static void cli_handle_escape (Cli * cli , char c ) {
309
- if (c == 'A' ) {
311
+ typedef enum {
312
+ CliCharClassWord ,
313
+ CliCharClassSpace ,
314
+ CliCharClassOther ,
315
+ } CliCharClass ;
316
+
317
+ /**
318
+ * @brief Determines the class that a character belongs to
319
+ *
320
+ * The return value of this function should not be used on its own; it should
321
+ * only be used for comparing it with other values returned by this function.
322
+ * This function is used internally in `cli_skip_run`.
323
+ */
324
+ static CliCharClass cli_char_class (char c ) {
325
+ if ((c >= 'A' && c <= 'Z' ) || (c >= 'a' && c <= 'z' ) || (c >= '0' && c <= '9' ) || c == '_' ) {
326
+ return CliCharClassWord ;
327
+ } else if (c == ' ' ) {
328
+ return CliCharClassSpace ;
329
+ } else {
330
+ return CliCharClassOther ;
331
+ }
332
+ }
333
+
334
+ typedef enum {
335
+ CliSkipDirectionLeft ,
336
+ CliSkipDirectionRight ,
337
+ } CliSkipDirection ;
338
+
339
+ /**
340
+ * @brief Skips a run of a class of characters
341
+ *
342
+ * @param string Input string
343
+ * @param original_pos Position to start the search at
344
+ * @param direction Direction in which to perform the search
345
+ * @returns The position at which the run ends
346
+ */
347
+ static size_t cli_skip_run (FuriString * string , size_t original_pos , CliSkipDirection direction ) {
348
+ if (furi_string_size (string ) == 0 ) return original_pos ;
349
+ if (direction == CliSkipDirectionLeft && original_pos == 0 ) return original_pos ;
350
+ if (direction == CliSkipDirectionRight && original_pos == furi_string_size (string ))
351
+ return original_pos ;
352
+
353
+ int8_t look_offset = (direction == CliSkipDirectionLeft ) ? -1 : 0 ;
354
+ int8_t increment = (direction == CliSkipDirectionLeft ) ? -1 : 1 ;
355
+ int32_t position = original_pos ;
356
+ CliCharClass start_class =
357
+ cli_char_class (furi_string_get_char (string , position + look_offset ));
358
+
359
+ while (true) {
360
+ position += increment ;
361
+ if (position < 0 ) break ;
362
+ if (position >= (int32_t )furi_string_size (string )) break ;
363
+ if (cli_char_class (furi_string_get_char (string , position + look_offset )) != start_class )
364
+ break ;
365
+ }
366
+
367
+ return MAX (0 , position );
368
+ }
369
+
370
+ void cli_process_input (Cli * cli ) {
371
+ CliKeyCombo combo = cli_read_ansi_key_combo (cli );
372
+ FURI_LOG_T (TAG , "code=0x%02x, mod=0x%x\r\n" , combo .key , combo .modifiers );
373
+
374
+ if (combo .key == CliKeyTab ) {
375
+ cli_handle_autocomplete (cli );
376
+
377
+ } else if (combo .key == CliKeySOH ) {
378
+ furi_delay_ms (33 ); // We are too fast, Minicom is not ready yet
379
+ cli_motd ();
380
+ cli_prompt (cli );
381
+
382
+ } else if (combo .key == CliKeyETX ) {
383
+ cli_reset (cli );
384
+ cli_prompt (cli );
385
+
386
+ } else if (combo .key == CliKeyEOT ) {
387
+ cli_reset (cli );
388
+
389
+ } else if (combo .key == CliKeyUp && combo .modifiers == CliModKeyNo ) {
310
390
// Use previous command if line buffer is empty
311
391
if (furi_string_size (cli -> line ) == 0 && furi_string_cmp (cli -> line , cli -> last_line ) != 0 ) {
312
392
// Set line buffer and cursor position
@@ -315,67 +395,85 @@ static void cli_handle_escape(Cli* cli, char c) {
315
395
// Show new line to user
316
396
printf ("%s" , furi_string_get_cstr (cli -> line ));
317
397
}
318
- } else if (c == 'B' ) {
319
- } else if (c == 'C' ) {
398
+
399
+ } else if (combo .key == CliKeyDown && combo .modifiers == CliModKeyNo ) {
400
+ // Clear input buffer
401
+ furi_string_reset (cli -> line );
402
+ cli -> cursor_position = 0 ;
403
+ printf ("\r" CLI_PROMPT "\e[0K" );
404
+
405
+ } else if (combo .key == CliKeyRight && combo .modifiers == CliModKeyNo ) {
406
+ // Move right
320
407
if (cli -> cursor_position < furi_string_size (cli -> line )) {
321
408
cli -> cursor_position ++ ;
322
409
printf ("\e[C" );
323
410
}
324
- } else if (c == 'D' ) {
411
+
412
+ } else if (combo .key == CliKeyLeft && combo .modifiers == CliModKeyNo ) {
413
+ // Move left
325
414
if (cli -> cursor_position > 0 ) {
326
415
cli -> cursor_position -- ;
327
416
printf ("\e[D" );
328
417
}
329
- }
330
- fflush (stdout );
331
- }
332
418
333
- void cli_process_input (Cli * cli ) {
334
- char in_chr = cli_getc (cli );
335
- size_t rx_len ;
419
+ } else if (combo .key == CliKeyHome && combo .modifiers == CliModKeyNo ) {
420
+ // Move to beginning of line
421
+ cli -> cursor_position = 0 ;
422
+ printf ("\e[%uG" , CLI_PROMPT_LENGTH + 1 ); // columns start at 1 \(-_-)/
336
423
337
- if (in_chr == CliSymbolAsciiTab ) {
338
- cli_handle_autocomplete (cli );
339
- } else if (in_chr == CliSymbolAsciiSOH ) {
340
- furi_delay_ms (33 ); // We are too fast, Minicom is not ready yet
341
- cli_motd ();
342
- cli_prompt (cli );
343
- } else if (in_chr == CliSymbolAsciiETX ) {
344
- cli_reset (cli );
345
- cli_prompt (cli );
346
- } else if (in_chr == CliSymbolAsciiEOT ) {
347
- cli_reset (cli );
348
- } else if (in_chr == CliSymbolAsciiEsc ) {
349
- rx_len = cli_read (cli , (uint8_t * )& in_chr , 1 );
350
- if ((rx_len > 0 ) && (in_chr == '[' )) {
351
- cli_read (cli , (uint8_t * )& in_chr , 1 );
352
- cli_handle_escape (cli , in_chr );
353
- } else {
354
- cli_putc (cli , CliSymbolAsciiBell );
355
- }
356
- } else if (in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel ) {
424
+ } else if (combo .key == CliKeyEnd && combo .modifiers == CliModKeyNo ) {
425
+ // Move to end of line
426
+ cli -> cursor_position = furi_string_size (cli -> line );
427
+ printf ("\e[%zuG" , CLI_PROMPT_LENGTH + cli -> cursor_position + 1 );
428
+
429
+ } else if (
430
+ combo .modifiers == CliModKeyCtrl &&
431
+ (combo .key == CliKeyLeft || combo .key == CliKeyRight )) {
432
+ // Skip run of similar chars to the left or right
433
+ CliSkipDirection direction = (combo .key == CliKeyLeft ) ? CliSkipDirectionLeft :
434
+ CliSkipDirectionRight ;
435
+ cli -> cursor_position = cli_skip_run (cli -> line , cli -> cursor_position , direction );
436
+ printf ("\e[%zuG" , CLI_PROMPT_LENGTH + cli -> cursor_position + 1 );
437
+
438
+ } else if (combo .key == CliKeyBackspace || combo .key == CliKeyDEL ) {
357
439
cli_handle_backspace (cli );
358
- } else if (in_chr == CliSymbolAsciiCR ) {
440
+
441
+ } else if (combo .key == CliKeyETB ) { // Ctrl + Backspace
442
+ // Delete run of similar chars to the left
443
+ size_t run_start = cli_skip_run (cli -> line , cli -> cursor_position , CliSkipDirectionLeft );
444
+ furi_string_replace_at (cli -> line , run_start , cli -> cursor_position - run_start , "" );
445
+ cli -> cursor_position = run_start ;
446
+ printf (
447
+ "\e[%zuG%s\e[0K\e[%zuG" , // move cursor, print second half of line, erase remains, move cursor again
448
+ CLI_PROMPT_LENGTH + cli -> cursor_position + 1 ,
449
+ furi_string_get_cstr (cli -> line ) + run_start ,
450
+ CLI_PROMPT_LENGTH + run_start + 1 );
451
+
452
+ } else if (combo .key == CliKeyCR ) {
359
453
cli_handle_enter (cli );
454
+
360
455
} else if (
361
- (in_chr >= 0x20 && in_chr < 0x7F ) && //-V560
456
+ (combo . key >= 0x20 && combo . key < 0x7F ) && //-V560
362
457
(furi_string_size (cli -> line ) < CLI_INPUT_LEN_LIMIT )) {
363
458
if (cli -> cursor_position == furi_string_size (cli -> line )) {
364
- furi_string_push_back (cli -> line , in_chr );
365
- cli_putc (cli , in_chr );
459
+ furi_string_push_back (cli -> line , combo . key );
460
+ cli_putc (cli , combo . key );
366
461
} else {
367
462
// Insert character to line buffer
368
- const char in_str [2 ] = {in_chr , 0 };
463
+ const char in_str [2 ] = {combo . key , 0 };
369
464
furi_string_replace_at (cli -> line , cli -> cursor_position , 0 , in_str );
370
465
371
466
// Print character in replace mode
372
- printf ("\e[4h%c\e[4l" , in_chr );
467
+ printf ("\e[4h%c\e[4l" , combo . key );
373
468
fflush (stdout );
374
469
}
375
470
cli -> cursor_position ++ ;
471
+
376
472
} else {
377
- cli_putc (cli , CliSymbolAsciiBell );
473
+ cli_putc (cli , CliKeyBell );
378
474
}
475
+
476
+ fflush (stdout );
379
477
}
380
478
381
479
void cli_add_command (
0 commit comments