mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-13 05:06:30 +04:00
[FL-3956] CLI autocomplete and other sugar (#4115)
* feat: FuriThread stdin * ci: fix f18 * feat: stdio callback context * feat: FuriPipe * POTENTIALLY EXPLOSIVE pipe welding * fix: non-explosive welding * Revert welding * docs: furi_pipe * feat: pipe event loop integration * update f18 sdk * f18 * docs: make doxygen happy * fix: event loop not triggering when pipe attached to stdio * fix: partial stdout in pipe * allow simultaneous in and out subscription in event loop * feat: vcp i/o * feat: cli ansi stuffs and history * feat: more line editing * working but slow cli rewrite * restore previous speed after 4 days of debugging 🥲 * fix: cli_app_should_stop * fix: cli and event_loop memory leaks * style: remove commented out code * ci: fix pvs warnings * fix: unit tests, event_loop crash * ci: fix build * ci: silence pvs warning * feat: cli gpio * ci: fix formatting * Fix memory leak during event loop unsubscription * Event better memory leak fix * feat: cli completions * Merge remote-tracking branch 'origin/dev' into portasynthinca3/3928-cli-threads * merge fixups * temporarily exclude speaker_debug app * pvs and unit tests fixups * feat: commands in fals * move commands out of flash, code cleanup * ci: fix errors * fix: run commands in buffer when stopping session * speedup cli file transfer * fix f18 * separate cli_shell into modules * fix pvs warning * fix qflipper refusing to connect * remove temp debug logs * remove erroneous conclusion * Fix memory leak during event loop unsubscription * Event better memory leak fix * unit test for the fix * improve thread stdio callback signatures * pipe stdout timeout * update api symbols * fix f18, formatting * fix pvs warnings * increase stack size, hope to fix unit tests * cli completions * more key combos * cli: revert flag changes * cli: fix formatting * cli, fbt: loopback perf benchmark * thread, event_loop: subscribing to thread flags * cli: signal internal events using thread flags, improve performance * fix f18, formatting * event_loop: fix crash * storage_cli: increase write_chunk buffer size again * cli: explanation for order=0 * thread, event_loop: thread flags callback refactor * cli: increase stack size * cli: rename cli_app_should_stop -> cli_is_pipe_broken_or_is_etx_next_char * cli: use plain array instead of mlib for history * cli: prepend file name to static fns * cli: fix formatting * cli_shell: increase stack size * fix merge * fix merge * cli_shell: fix autocomplete up/down logic * cli_shell: don't add empty line to history * cli_shell: fix history recall * cli_shell: find manually typed command in history * cli_shell: different up/down completions navigation * fix formatting * cli_shell: fix memory leak * cli_shell: silence pvs warning * test_pipe: fix race condition * storage_cli: terminate on pipe broken --------- Co-authored-by: Georgii Surkov <georgii.surkov@outlook.com> Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
@@ -65,6 +65,64 @@ void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t cli_shell_line_get_line_position(CliShellLine* line) {
|
||||
return line->line_position;
|
||||
}
|
||||
|
||||
void cli_shell_line_set_line_position(CliShellLine* line, size_t position) {
|
||||
line->line_position = position;
|
||||
}
|
||||
|
||||
// =======
|
||||
// Helpers
|
||||
// =======
|
||||
|
||||
typedef enum {
|
||||
CliCharClassWord,
|
||||
CliCharClassSpace,
|
||||
CliCharClassOther,
|
||||
} CliCharClass;
|
||||
|
||||
typedef enum {
|
||||
CliSkipDirectionLeft,
|
||||
CliSkipDirectionRight,
|
||||
} CliSkipDirection;
|
||||
|
||||
CliCharClass cli_shell_line_char_class(char c) {
|
||||
if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {
|
||||
return CliCharClassWord;
|
||||
} else if(c == ' ') {
|
||||
return CliCharClassSpace;
|
||||
} else {
|
||||
return CliCharClassOther;
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
cli_shell_line_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) {
|
||||
if(furi_string_size(string) == 0) return original_pos;
|
||||
if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos;
|
||||
if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string))
|
||||
return original_pos;
|
||||
|
||||
int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0;
|
||||
int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1;
|
||||
int32_t position = original_pos;
|
||||
CliCharClass start_class =
|
||||
cli_shell_line_char_class(furi_string_get_char(string, position + look_offset));
|
||||
|
||||
while(true) {
|
||||
position += increment;
|
||||
if(position < 0) break;
|
||||
if(position >= (int32_t)furi_string_size(string)) break;
|
||||
if(cli_shell_line_char_class(furi_string_get_char(string, position + look_offset)) !=
|
||||
start_class)
|
||||
break;
|
||||
}
|
||||
|
||||
return MAX(0, position);
|
||||
}
|
||||
|
||||
// ==============
|
||||
// Input handlers
|
||||
// ==============
|
||||
@@ -87,13 +145,32 @@ static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) {
|
||||
|
||||
FuriString* command = cli_shell_line_get_selected(line);
|
||||
furi_string_trim(command);
|
||||
if(furi_string_empty(command)) {
|
||||
cli_shell_line_prompt(line);
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriString* command_copy = furi_string_alloc_set(command);
|
||||
|
||||
if(line->history_position == 0) {
|
||||
for(size_t i = 1; i < line->history_entries; i++) {
|
||||
if(furi_string_cmp(line->history[i], command) == 0) {
|
||||
line->history_position = i;
|
||||
command = cli_shell_line_get_selected(line);
|
||||
furi_string_trim(command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move selected command to the front
|
||||
if(line->history_position > 0) {
|
||||
// move selected command to the front
|
||||
size_t pos = line->history_position;
|
||||
size_t len = line->history_entries;
|
||||
memmove(
|
||||
&line->history[1], &line->history[0], line->history_position * sizeof(FuriString*));
|
||||
line->history[0] = command;
|
||||
&line->history[pos], &line->history[pos + 1], (len - pos - 1) * sizeof(FuriString*));
|
||||
furi_string_move(line->history[0], command);
|
||||
line->history_entries--;
|
||||
}
|
||||
|
||||
// insert empty command
|
||||
@@ -109,7 +186,7 @@ static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) {
|
||||
|
||||
// execute command
|
||||
printf("\r\n");
|
||||
if(!furi_string_empty(command_copy)) cli_shell_execute_command(line->shell, command_copy);
|
||||
cli_shell_execute_command(line->shell, command_copy);
|
||||
furi_string_free(command_copy);
|
||||
|
||||
cli_shell_line_prompt(line);
|
||||
@@ -199,7 +276,57 @@ static bool cli_shell_line_input_bksp(CliKeyCombo combo, void* context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_fallback(CliKeyCombo combo, void* context) {
|
||||
static bool cli_shell_line_input_ctrl_l(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellLine* line = context;
|
||||
// clear screen
|
||||
FuriString* command = cli_shell_line_get_selected(line);
|
||||
char prompt[64];
|
||||
cli_shell_line_format_prompt(line, prompt, sizeof(prompt));
|
||||
printf(
|
||||
ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS(
|
||||
"1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%zu"),
|
||||
prompt,
|
||||
furi_string_get_cstr(command),
|
||||
strlen(prompt) + line->line_position + 1 /* 1-based column indexing */);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_ctrl_left_right(CliKeyCombo combo, void* context) {
|
||||
CliShellLine* line = context;
|
||||
// skip run of similar chars to the left or right
|
||||
FuriString* selected_line = cli_shell_line_get_selected(line);
|
||||
CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft :
|
||||
CliSkipDirectionRight;
|
||||
line->line_position = cli_shell_line_skip_run(selected_line, line->line_position, direction);
|
||||
printf(
|
||||
ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_ctrl_bksp(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellLine* line = context;
|
||||
// delete run of similar chars to the left
|
||||
cli_shell_line_ensure_not_overwriting_history(line);
|
||||
FuriString* selected_line = cli_shell_line_get_selected(line);
|
||||
size_t run_start =
|
||||
cli_shell_line_skip_run(selected_line, line->line_position, CliSkipDirectionLeft);
|
||||
furi_string_replace_at(selected_line, run_start, line->line_position - run_start, "");
|
||||
line->line_position = run_start;
|
||||
printf(
|
||||
ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END)
|
||||
ANSI_CURSOR_HOR_POS("%zu"),
|
||||
cli_shell_line_prompt_length(line) + line->line_position + 1,
|
||||
furi_string_get_cstr(selected_line) + run_start,
|
||||
cli_shell_line_prompt_length(line) + run_start + 1);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_normal(CliKeyCombo combo, void* context) {
|
||||
CliShellLine* line = context;
|
||||
if(combo.modifiers != CliModKeyNo) return false;
|
||||
if(combo.key < CliKeySpace || combo.key >= CliKeyDEL) return false;
|
||||
@@ -220,8 +347,8 @@ static bool cli_shell_line_input_fallback(CliKeyCombo combo, void* context) {
|
||||
}
|
||||
|
||||
CliShellKeyComboSet cli_shell_line_key_combo_set = {
|
||||
.fallback = cli_shell_line_input_fallback,
|
||||
.count = 10,
|
||||
.fallback = cli_shell_line_input_normal,
|
||||
.count = 14,
|
||||
.records =
|
||||
{
|
||||
{{CliModKeyNo, CliKeyETX}, cli_shell_line_input_ctrl_c},
|
||||
@@ -234,5 +361,9 @@ CliShellKeyComboSet cli_shell_line_key_combo_set = {
|
||||
{{CliModKeyNo, CliKeyEnd}, cli_shell_line_input_end},
|
||||
{{CliModKeyNo, CliKeyBackspace}, cli_shell_line_input_bksp},
|
||||
{{CliModKeyNo, CliKeyDEL}, cli_shell_line_input_bksp},
|
||||
{{CliModKeyNo, CliKeyFF}, cli_shell_line_input_ctrl_l},
|
||||
{{CliModKeyCtrl, CliKeyLeft}, cli_shell_line_input_ctrl_left_right},
|
||||
{{CliModKeyCtrl, CliKeyRight}, cli_shell_line_input_ctrl_left_right},
|
||||
{{CliModKeyNo, CliKeyETB}, cli_shell_line_input_ctrl_bksp},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user