From 4d7e8054b3e535244c5d2fc1046ad37df8790231 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Tue, 25 Jun 2024 11:11:46 +0200 Subject: [PATCH 01/52] SI-4830 Add API allowing loading and saving Slash history to file --- include/slash/slash.h | 10 +++++++++- src/slash.c | 26 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/include/slash/slash.h b/include/slash/slash.h index f4c69ac..7c7a3c3 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -26,6 +26,7 @@ #include #include +#include #include @@ -161,7 +162,7 @@ struct slash { char *history_head; char *history_tail; char *history_cursor; - + FILE *history_file; /* Command interface */ char **argv; int argc; @@ -239,6 +240,13 @@ void slash_command_description(struct slash *slash, struct slash_command *comman int slash_run(struct slash *slash, char * filename, int printcmd); +/** + * @brief Populate Slash history buffer with the content of the given file + * @param slash pointer to valid slash context + * @param history_filename filename to use (will be created if it doesn't exist) to read and write history + */ +void slash_init_history_from_file(struct slash *slash, const char *history_filename); + void slash_history_add(struct slash *slash, char *line); typedef struct slash_list_iterator_s { diff --git a/src/slash.c b/src/slash.c index 87930c2..5b8078f 100644 --- a/src/slash.c +++ b/src/slash.c @@ -568,8 +568,13 @@ void slash_history_add(struct slash *slash, char *line) return; /* Push including trailing zero */ - if (!slash_line_empty(line, strlen(line))) + if (!slash_line_empty(line, strlen(line))) { + if(slash->history_file) { + fprintf(slash->history_file, "%s\n", line); + fflush(slash->history_file); + } slash_history_push(slash, line, strlen(line) + 1); + } } static void slash_history_next(struct slash *slash) @@ -1056,3 +1061,22 @@ void slash_destroy(struct slash *slash) free(slash); } + +void slash_init_history_from_file(struct slash *slash, const char *history_filename) { + /* Set this to NULL to disable history, will be set if all goes well */ + slash->history_file = NULL; + FILE *history = fopen(history_filename, "a+"); + if (history) { + ssize_t read; + char *line; + size_t len = 0; + while ((read = getline(&line, &len, history)) != -1) { + if(line[read - 1] == '\n') { + line[read - 1] = '\0'; + } + slash_history_add(slash, line); + } + free(line); + slash->history_file = history; + } +} From 3619cceac53967bdf3238113d13717d5b48c62cd Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Tue, 30 Jul 2024 10:04:02 +0200 Subject: [PATCH 02/52] Rewrite slash_complete, dynamically building a list of completions, this is easier to understand and extend --- src/completer.c | 169 +++++++++++++++++++++++++----------------------- 1 file changed, 88 insertions(+), 81 deletions(-) diff --git a/src/completer.c b/src/completer.c index c422172..db93e06 100644 --- a/src/completer.c +++ b/src/completer.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -6,6 +7,7 @@ #include #include #include +#include static void ls_appended(const char* tok, const char* app) { char cmd[PATH_MAX + 3]; @@ -117,102 +119,107 @@ void slash_completer_revert_skip(struct slash *slash, char * orig_slash_buf) { int slash_prefix_length(const char *s1, const char *s2) { int len = 0; - - while (*s1 && *s2 && *s1 == *s2) { - len++; - s1++; - s2++; - } - + if (s1 && s2) { + while (*s1 && *s2 && *s1 == *s2) { + len++; + s1++; + s2++; + } + } return len; } +SLIST_HEAD( completion_list, completion_entry ); +struct completion_entry { + struct slash_command * cmd; + SLIST_ENTRY(completion_entry) list; +}; + /** * @brief For tab auto completion, calls other completion functions when matched command has them - * + * * @param slash Slash context */ void slash_complete(struct slash *slash) { int matches = 0; - size_t prefixlen = -1; - struct slash_command *prefix = NULL; - struct slash_command * cmd; + struct completion_list completions; + struct completion_entry *completion = NULL; + struct completion_entry *cur_completion; + SLIST_INIT( &completions ); slash_list_iterator i = {}; - while ((cmd = slash_list_iterate(&i)) != NULL) { - - if (strncmp(slash->buffer, cmd->name, slash_min(strlen(cmd->name), slash->length)) == 0) { - - if(strlen(cmd->name) < slash->length && slash->buffer[slash->length - 1] != ' ') { - if (NULL == prefix) { - /* Count matches */ - matches++; - slash->in_completion = true; - prefix = cmd; - if (matches == 1) - slash_printf(slash, "\n"); - } - continue; + size_t cmd_len; + int cur_prefix; + int cmd_match; + int len_to_compare_to = slash->buffer[slash->length-1] == ' '?slash->length-1:slash->length; + while ((cmd = slash_list_iterate(&i)) != NULL) { + cmd_len = strlen(cmd->name); + cmd_match = strncmp(slash->buffer, cmd->name, slash_min(len_to_compare_to, cmd_len)); + /* Do we have an exact match on the buffer ?*/ + if (cmd_match == 0) { + if((cmd_len < len_to_compare_to && cmd->completer) || (cmd_len >= len_to_compare_to)) { + matches++; + completion = malloc(sizeof(struct completion_entry)); + completion->cmd = cmd; + SLIST_INSERT_HEAD(&completions, completion, list); } - if(false == slash->in_completion) { - if(strlen(cmd->name) < (slash->length - 1) && slash->buffer[slash->length - 1] == ' ') { - continue; - } + } + } + if(matches > 1) { + /* We only print all commands over 1 match here */ + slash_printf(slash, "\n"); + } + struct completion_entry *prev_completion = NULL; + int prefix_len = INT_MAX; + + SLIST_FOREACH(cur_completion, &completions, list) { + if (prev_completion != 0) { + cur_prefix = slash_prefix_length(prev_completion->cmd->name, cur_completion->cmd->name); + if(cur_prefix < prefix_len) { + prefix_len = cur_prefix; + completion = cur_completion; } - /* Count matches */ - matches++; - slash->in_completion = true; - /* Find common prefix */ - if (prefixlen == (size_t) -1) { - prefix = cmd; - prefixlen = strlen(prefix->name); - } else { - size_t new_prefixlen = slash_prefix_length(prefix->name, cmd->name); - if (new_prefixlen < prefixlen) - prefixlen = new_prefixlen; - } - - /* Print newline on first match */ - if (matches == 1) - slash_printf(slash, "\n"); - - /* We only print all commands over 1 match here */ - if (matches > 1) - slash_command_description(slash, cmd); - - } - - } - - if (!matches) { - slash->in_completion = false; - slash_bell(slash); - } else if (matches == 1) { - if (prefixlen != -1) { - if (slash->cursor <= prefixlen) { - strncpy(slash->buffer, prefix->name, prefixlen); - slash->buffer[prefixlen] = '\0'; - strcat(slash->buffer, " "); - slash->cursor = slash->length = strlen(slash->buffer); - } else { - if (prefix->completer) { - prefix->completer(slash, slash->buffer + prefixlen + 1); - } + } + prev_completion = cur_completion; + if(matches > 1) { + slash_command_description(slash, cur_completion->cmd); + } + } + if (matches == 1) { + /* Reassign cmd_len to the current completion as it may have changed during the loop */ + cmd_len = strlen(completion->cmd->name); + if(slash->length < strlen(completion->cmd->name)) { + /* The buffer uniquely completes to a longer command */ + strncpy(slash->buffer, completion->cmd->name, cmd_len); + slash->buffer[cmd_len] = '\0'; + slash->cursor = slash->length = strlen(slash->buffer); + } + if (completion->cmd->completer) { + /* Call the matching command completer with the rest of the buffer */ + if(slash->length == strlen(completion->cmd->name)) { + slash->buffer[slash->length] = ' '; + slash->buffer[slash->length+1] = '\0'; + slash->cursor++; + slash->length++; } - } else { - if (prefix->completer) { - prefix->completer(slash, slash->buffer + strlen(prefix->name) + 1); - } - } - } else if (slash->last_char != '\t') { - /* Print the first match as well */ - slash_command_description(slash, prefix); - strncpy(slash->buffer, prefix->name, prefixlen); - slash->buffer[prefixlen] = '\0'; - slash->cursor = slash->length = strlen(slash->buffer); - slash_bell(slash); - } + slash_printf(slash, "\n"); + completion->cmd->completer(slash, slash->buffer + cmd_len + 1); + } + } else if(matches > 1) { + if(slash->buffer[slash->length-1] != ' ') { + strncpy(slash->buffer, completion->cmd->name, prefix_len); + slash->buffer[prefix_len] = '\0'; + slash->cursor = slash->length = strlen(slash->buffer); + } + } + /* Free up the completion list we built up earlier */ + while (!SLIST_EMPTY(&completions)) + { + completion = SLIST_FIRST(&completions); + SLIST_REMOVE(&completions, completion, completion_entry, list); + free(completion); + } } /** From 458ed6ca89e509a0eaaaa4eceaa7bb73579c884e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Thu, 1 Aug 2024 08:58:13 +0200 Subject: [PATCH 03/52] 0-initialize buffer to remove Valgrind warning --- src/completer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/completer.c b/src/completer.c index db93e06..83222c4 100644 --- a/src/completer.c +++ b/src/completer.c @@ -74,7 +74,7 @@ void slash_completer_skip_flagged_prefix(struct slash *slash, char * tgt_prefix) /* if slash buffer begins with tgt_prefix */ if (!strncmp(slash->buffer, tgt_prefix, prefix_len)) { - char * tmp_buf = (char *) malloc(buffer_len+1); + char * tmp_buf = (char *) calloc(1, buffer_len+1); if (tmp_buf != NULL) { strncpy(tmp_buf, slash->buffer + prefix_len, buffer_len-prefix_len); } else { From 8aef17633bff36f164757b3cf27bbc2a90243160 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 16 Aug 2024 13:31:00 +0200 Subject: [PATCH 04/52] Remove unneeded new line print when calling unique completion's completer function, the new line print is actually the completion completer function's responsibility --- src/completer.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/completer.c b/src/completer.c index 83222c4..d24458e 100644 --- a/src/completer.c +++ b/src/completer.c @@ -203,7 +203,6 @@ void slash_complete(struct slash *slash) slash->cursor++; slash->length++; } - slash_printf(slash, "\n"); completion->cmd->completer(slash, slash->buffer + cmd_len + 1); } } else if(matches > 1) { From 2dbb3ea0d0cd565cdcbb9f802258495116be118f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Mon, 19 Aug 2024 12:32:29 +0200 Subject: [PATCH 05/52] Restore completion functionality for "help" or "watch" commands --- src/completer.c | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/completer.c b/src/completer.c index d24458e..95707dd 100644 --- a/src/completer.c +++ b/src/completer.c @@ -85,26 +85,31 @@ void slash_completer_skip_flagged_prefix(struct slash *slash, char * tgt_prefix) /* start at 1 in case first word following tgt_prefix is not a flag */ int consecutive_ctr = !strcmp(tgt_prefix, "") ? 0 : 1; - - /* if flagged, search for 2nd word not starting with '-' or first word not starting with '--' */ - while (token != NULL) { - if (token[0] != '-') { - consecutive_ctr++; - - if (consecutive_ctr == 2) { - /* move buffer pointer ahead of "tgt_prefix [OPTIONS] ..." */ - slash->buffer = slash->buffer + (prefix_len+(token-tmp_buf)); - slash->length = strlen(slash->buffer); - slash->cursor = slash->length; - break; - } - } else if (token[1] == '-') { - consecutive_ctr++; - } else { - consecutive_ctr = 0; - } - token = strtok(NULL, " "); - } + if(!token) { + slash->buffer = slash->buffer + prefix_len + 1; + slash->length = strlen(slash->buffer); + slash->cursor = slash->length; + } else { + /* if flagged, search for 2nd word not starting with '-' or first word not starting with '--' */ + while (token != NULL) { + if (token[0] != '-') { + consecutive_ctr++; + + if (consecutive_ctr == 2) { + /* move buffer pointer ahead of "tgt_prefix [OPTIONS] ..." */ + slash->buffer = slash->buffer + (prefix_len+(token-tmp_buf)); + slash->length = strlen(slash->buffer); + slash->cursor = slash->length; + break; + } + } else if (token[1] == '-') { + consecutive_ctr++; + } else { + consecutive_ctr = 0; + } + token = strtok(NULL, " "); + } + } free(tmp_buf); } } @@ -152,7 +157,7 @@ void slash_complete(struct slash *slash) size_t cmd_len; int cur_prefix; int cmd_match; - int len_to_compare_to = slash->buffer[slash->length-1] == ' '?slash->length-1:slash->length; + int len_to_compare_to = slash->length>0?slash->buffer[slash->length-1] == ' '?slash->length-1:slash->length:0; while ((cmd = slash_list_iterate(&i)) != NULL) { cmd_len = strlen(cmd->name); cmd_match = strncmp(slash->buffer, cmd->name, slash_min(len_to_compare_to, cmd_len)); From 0802b777a182e9605aa75853dbff9168450a2907 Mon Sep 17 00:00:00 2001 From: kivkiv12345 Date: Mon, 19 Aug 2024 11:51:22 +0200 Subject: [PATCH 06/52] Fix conditional jump or move dependant on uninitialised value Simply used calloc() instead of malloc(), this makes Valgrind slightly happier. --- src/completer.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/completer.c b/src/completer.c index 95707dd..55db785 100644 --- a/src/completer.c +++ b/src/completer.c @@ -74,13 +74,14 @@ void slash_completer_skip_flagged_prefix(struct slash *slash, char * tgt_prefix) /* if slash buffer begins with tgt_prefix */ if (!strncmp(slash->buffer, tgt_prefix, prefix_len)) { - char * tmp_buf = (char *) calloc(1, buffer_len+1); - if (tmp_buf != NULL) { - strncpy(tmp_buf, slash->buffer + prefix_len, buffer_len-prefix_len); - } else { - printf("Memory allocation error"); + char * const tmp_buf = calloc(1, buffer_len+1); + + if (tmp_buf == NULL) { + fprintf(stderr, "Memory allocation error\n"); return; - } + } + + strncpy(tmp_buf, slash->buffer + prefix_len, buffer_len-prefix_len); char * token = strtok(tmp_buf, " "); /* start at 1 in case first word following tgt_prefix is not a flag */ From d4669a7b82484a3433ed78ac800dd298dd2590df Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Wed, 4 Sep 2024 15:13:30 +0200 Subject: [PATCH 07/52] SI-4831 handle multiple trailing blank spaces, make "complete while already compleing" (aka "watch get") configurable --- include/slash/slash.h | 8 ++++++-- src/completer.c | 42 +++++++++++++++++++++++++++++++++--------- src/slash.c | 2 +- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/include/slash/slash.h b/include/slash/slash.h index 7c7a3c3..0b88e21 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -177,8 +177,12 @@ struct slash { /* Command list */ struct slash_command * cmd_list; - /* Completions */ - bool in_completion; + /* Allow calling complete functions while already in in one of those functions. + * Use case: when completing "help", you only want to complete the *name* of commands to get help for + * BUT while completing "watch" (or watch-like commands), you want to carry on completing as much as possible, + * for instance, typing: "wgs" would result in the completed command line "watch get serial0" in 6 keystrokes + */ + bool complete_in_completion; }; /** diff --git a/src/completer.c b/src/completer.c index 55db785..1e0863c 100644 --- a/src/completer.c +++ b/src/completer.c @@ -158,13 +158,25 @@ void slash_complete(struct slash *slash) size_t cmd_len; int cur_prefix; int cmd_match; + { + /* Let's take care of multiple consecutive trailing spaces */ + int nof_trailing_spaces = 0; + while (nof_trailing_spaces < slash->length && slash->buffer[slash->length - 1 - nof_trailing_spaces] == ' ') { + nof_trailing_spaces++; + } + if(nof_trailing_spaces > 1) { + slash->length -= (nof_trailing_spaces - 1); + slash->cursor = slash->length; + slash->buffer[slash->length] = '\0'; + } + } int len_to_compare_to = slash->length>0?slash->buffer[slash->length-1] == ' '?slash->length-1:slash->length:0; while ((cmd = slash_list_iterate(&i)) != NULL) { cmd_len = strlen(cmd->name); cmd_match = strncmp(slash->buffer, cmd->name, slash_min(len_to_compare_to, cmd_len)); /* Do we have an exact match on the buffer ?*/ if (cmd_match == 0) { - if((cmd_len < len_to_compare_to && cmd->completer) || (cmd_len >= len_to_compare_to)) { + if((cmd_len < len_to_compare_to && cmd->completer) || (len_to_compare_to <= cmd_len)) { matches++; completion = malloc(sizeof(struct completion_entry)); completion->cmd = cmd; @@ -180,6 +192,7 @@ void slash_complete(struct slash *slash) int prefix_len = INT_MAX; SLIST_FOREACH(cur_completion, &completions, list) { + /* Compute the length of prefix common to all completions */ if (prev_completion != 0) { cur_prefix = slash_prefix_length(prev_completion->cmd->name, cur_completion->cmd->name); if(cur_prefix < prefix_len) { @@ -202,17 +215,24 @@ void slash_complete(struct slash *slash) slash->cursor = slash->length = strlen(slash->buffer); } if (completion->cmd->completer) { - /* Call the matching command completer with the rest of the buffer */ - if(slash->length == strlen(completion->cmd->name)) { - slash->buffer[slash->length] = ' '; - slash->buffer[slash->length+1] = '\0'; - slash->cursor++; - slash->length++; + /* Call the matching command completer with the rest of the buffer but only the current + completer allows it */ + if(slash->complete_in_completion == true) { + if(slash->length == strlen(completion->cmd->name)) { + slash->buffer[slash->length] = ' '; + slash->buffer[slash->length+1] = '\0'; + slash->cursor++; + slash->length++; + } + completion->cmd->completer(slash, slash->buffer + cmd_len + 1); } - completion->cmd->completer(slash, slash->buffer + cmd_len + 1); } } else if(matches > 1) { - if(slash->buffer[slash->length-1] != ' ') { + /* Fill the buffer with as much characters as possible: + * if what the user typed in doesn't end with a space, we might + * as well put all the common prefix in the buffer + */ + if(slash->buffer[slash->length-1] != ' ' && len_to_compare_to < prefix_len) { strncpy(slash->buffer, completion->cmd->name, prefix_len); slash->buffer[prefix_len] = '\0'; slash->cursor = slash->length = strlen(slash->buffer); @@ -383,6 +403,10 @@ void slash_watch_completer(struct slash *slash, char * token) { * @param token Slash buffer after first space */ void slash_help_completer(struct slash *slash, char * token) { + bool previous_state = slash->complete_in_completion; + /* Do not complete past the command name */ + slash->complete_in_completion = false; // skip help slash_complete_other_commands(slash, token, "help"); + slash->complete_in_completion = previous_state; } diff --git a/src/slash.c b/src/slash.c index 5b8078f..11ba679 100644 --- a/src/slash.c +++ b/src/slash.c @@ -1041,7 +1041,7 @@ void slash_create_static(struct slash *slash, char * line_buf, size_t line_size, /* Empty command list */ slash->cmd_list = 0; - slash->in_completion = false; + slash->complete_in_completion = true; tcgetattr(slash->fd_read, &slash->original); } From ff4a5c6ae1cd39417da6cf50c6aab6363acda391 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Mon, 18 Mar 2024 10:50:36 +0100 Subject: [PATCH 08/52] SI-4598 actually store "help" parameter in data structure --- include/slash/slash.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/slash/slash.h b/include/slash/slash.h index 0b88e21..0f272ac 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -61,6 +61,7 @@ .func = _func,\ .completer = _completer,\ .args = _args,\ + .help = _help, \ .next = {NULL}, /* Next pointer in case the user wants to implement custom ordering within or across APMs. It should not required by the default implementation. */\ }; From e4f8e72a070956b983f728ad471bdd0610a5a3ea Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Mon, 18 Mar 2024 12:57:27 +0100 Subject: [PATCH 09/52] Add "help" member to slash_command struct, missed in previous commit --- include/slash/slash.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/slash/slash.h b/include/slash/slash.h index 0f272ac..a22b5bd 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -123,6 +123,7 @@ struct slash_command { char *name; const slash_func_t func; const char *args; + const char *help; const slash_completer_func_t completer; /* Next pointer in case the user wants to implement custom ordering within or across APMs. It should not required by the default implementation. */ From 19214fd78d4f64fe70827715051f0b95ed252eed Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 6 Sep 2024 14:43:30 +0200 Subject: [PATCH 10/52] Add new optparse_new_ex(), handling optional help text --- include/slash/optparse.h | 22 ++++++++++++++++++++++ src/optparse.c | 13 ++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/include/slash/optparse.h b/include/slash/optparse.h index 523419d..0707c2e 100644 --- a/include/slash/optparse.h +++ b/include/slash/optparse.h @@ -13,7 +13,29 @@ typedef struct optparse optparse_t; typedef struct optparse_opt optparse_opt_t; +/** + * @brief Create a new option parser context, remeber to free it using optparse_del() when done + * @param progname name to be associated with the option parser + * @param arg_summary short list of optional command line options and arguments + * @return pointer to new context, NULL if something went wrong + * @warning the progname and arg_summary arguments, if not NULL, must be valid for the entire lifecycle of the option parser! + */ optparse_t *optparse_new(const char *progname, const char *arg_summary); + +/** + * @brief Create a new options parser context, remeber to free it using optparse_del() when done + * @param progname name to be associated with the option parser + * @param arg_summary short list of optional command line options and arguments, can be NULL if not relevant + * @param help longer description of the command, can be NULL if not relevant. + * @return pointer to new context, NULL if something went wrong + * @warning the progname, arg_summary and help arguments, if not NULL, must be valid for the entire lifecycle of the option parser! + */ +optparse_t *optparse_new_ex(const char *progname, const char *arg_summary, const char *help); + +/** + * @brief Release the memory for the given option parser object + * @param parser pointer to valid option parser obtained by calling optparse_new() or optparse_new_ex() + */ void optparse_del(optparse_t *parser); int optparse_parse(optparse_t *parser, int argc, const char *argv[]); diff --git a/src/optparse.c b/src/optparse.c index aac2bab..9d002a8 100644 --- a/src/optparse.c +++ b/src/optparse.c @@ -31,7 +31,7 @@ struct optparse { int argi; int argc; const char ** argv; - + const char *help; void * ptr; }; @@ -57,6 +57,10 @@ struct optparse_opt { optparse_t * optparse_new(const char * progname, const char * arg_summary) { + return optparse_new_ex(progname, arg_summary, NULL); +} + +optparse_t *optparse_new_ex(const char *progname, const char *arg_summary, const char *help) { optparse_t * parser; parser = malloc(sizeof(*parser)); @@ -71,6 +75,8 @@ optparse_new(const char * progname, const char * arg_summary) { if (arg_summary) parser->arg_summary = arg_summary; + parser->help = help; + return parser; } @@ -218,6 +224,11 @@ void optparse_help(optparse_t * parser, FILE * fp) { fprintf(fp, " %s", parser->arg_summary); fprintf(fp, "\n\n"); + if (parser->help) { + fprintf(fp, "%s", parser->help); + fprintf(fp, "\n\n"); + } + for (opt = parser->options; opt; opt = opt->next) { int col = 0; col += fprintf(fp, " "); From 5c087ddf1ddfcebce4c596494509b8420316e4a8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 6 Sep 2024 14:44:08 +0200 Subject: [PATCH 11/52] Print extended help if available in slash_command_usage --- src/slash.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/slash.c b/src/slash.c index 11ba679..2f8aa85 100644 --- a/src/slash.c +++ b/src/slash.c @@ -347,7 +347,12 @@ void slash_command_usage(struct slash *slash, struct slash_command *command) { const char *args = command->args ? command->args : ""; const char *type = command->func ? "usage" : "group"; - slash_printf(slash, "%s: %s %s\n", type, command->name, args); + const char *help = command->help; + if(NULL != help) { + slash_printf(slash, "%s: %s %s\n\n%s\n\n", type, command->name, args, help); + } else { + slash_printf(slash, "%s: %s %s\n", type, command->name, args); + } } void slash_command_description(struct slash *slash, struct slash_command *command) From 4551c7467fe0f4395ecf714fe555aca494606ae2 Mon Sep 17 00:00:00 2001 From: Troels Jessen Date: Fri, 23 Aug 2024 21:03:46 +0200 Subject: [PATCH 12/52] Let watch command run every X ms, no matter how long time the command takes to execute --- src/builtins.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/builtins.c b/src/builtins.c index 5b9ef2d..1706f7e 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -99,7 +100,7 @@ slash_command(confirm, slash_builtin_confirm, "", "Block until user confirmation static int slash_builtin_watch(struct slash *slash) { - unsigned int interval = slash_dfl_timeout; + unsigned int interval = 1000; unsigned int count = 0; optparse_t * parser = optparse_new("watch", ""); @@ -130,15 +131,26 @@ static int slash_builtin_watch(struct slash *slash) char cmd_exec[slash->line_size]; strncpy(cmd_exec, line, slash->line_size); + /* Read time it takes to execute command */ + struct timespec time_before, time_after; + clock_gettime(CLOCK_MONOTONIC, &time_before); + /* Execute command */ slash_execute(slash, cmd_exec); + clock_gettime(CLOCK_MONOTONIC, &time_after); + if ((count > 0) && (count-- == 1)) { break; } + /* Calculate remaining time to ensure execution is running no more often than what interval dictates */ + unsigned int elapsed = (time_after.tv_sec-time_before.tv_sec)*1000 + (time_after.tv_nsec-time_before.tv_nsec)/1000000; + unsigned int remaining = 0; + if (interval > elapsed) remaining = interval - elapsed; + /* Delay (press enter to exit) */ - if (slash_wait_interruptible(slash, interval) != 0) + if (slash_wait_interruptible(slash, remaining) != 0) break; } From e77e235f8ecf86034219f82380db5d731a1e8174 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 6 Sep 2024 19:57:11 +0200 Subject: [PATCH 13/52] Initialize complete_in_completion in slash_create() too --- src/slash.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slash.c b/src/slash.c index 2f8aa85..82a8163 100644 --- a/src/slash.c +++ b/src/slash.c @@ -1009,6 +1009,7 @@ struct slash *slash_create(size_t line_size, size_t history_size) slash->history_tail = slash->history; slash->history_cursor = slash->history; slash->history_avail = slash->history_size - 1; + slash->complete_in_completion = true; slash_list_init(); From 70d0be29b8da109a4e26bfe9624bd6b5b8999948 Mon Sep 17 00:00:00 2001 From: Troels Jessen Date: Mon, 9 Sep 2024 12:01:31 +0200 Subject: [PATCH 14/52] Feed argv/argc into autocompleter --- src/builtins.h | 5 +++++ src/completer.c | 7 +++++++ src/slash.c | 4 ---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/builtins.h b/src/builtins.h index c7c40ba..a4e69e1 100644 --- a/src/builtins.h +++ b/src/builtins.h @@ -3,12 +3,17 @@ #include +/* Configuration */ +#define SLASH_ARG_MAX 16 /* Maximum number of arguments */ +#define SLASH_SHOW_MAX 25 /* Maximum number of commands to list */ + /* Declarations for required implementation functions in slash.c */ void slash_command_usage(struct slash *slash, struct slash_command *command); char *slash_history_increment(struct slash *slash, char *ptr); int slash_putchar(struct slash *slash, char c); struct slash_command * slash_command_find(struct slash *slash, char *line, size_t linelen, char **args); void slash_command_description(struct slash *slash, struct slash_command *command); +int slash_build_args(char *args, char **argv, int *argc); /* Define and initialize section variables */ /* __attribute__((visibility("hidden"))) prevents the section symbols from linking with diff --git a/src/completer.c b/src/completer.c index 1e0863c..b4db163 100644 --- a/src/completer.c +++ b/src/completer.c @@ -9,6 +9,8 @@ #include #include +#include "builtins.h" + static void ls_appended(const char* tok, const char* app) { char cmd[PATH_MAX + 3]; @@ -224,6 +226,11 @@ void slash_complete(struct slash *slash) slash->cursor++; slash->length++; } + char *argv[SLASH_ARG_MAX]; + slash->argv = argv; + char args[slash->line_size]; + strcpy(args, slash->buffer); + slash_build_args(args, slash->argv, &slash->argc); completion->cmd->completer(slash, slash->buffer + cmd_len + 1); } } diff --git a/src/slash.c b/src/slash.c index 82a8163..ba5310f 100644 --- a/src/slash.c +++ b/src/slash.c @@ -50,10 +50,6 @@ #include "builtins.h" -/* Configuration */ -#define SLASH_ARG_MAX 16 /* Maximum number of arguments */ -#define SLASH_SHOW_MAX 25 /* Maximum number of commands to list */ - /* Terminal codes */ #define ESC '\x1b' #define DEL '\x7f' From 75ec339852a4a50d59fc7e963a3948aba74b6c45 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Tue, 10 Sep 2024 13:04:38 +0200 Subject: [PATCH 15/52] Fix typo in comment --- src/completer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/completer.c b/src/completer.c index b4db163..1773bc9 100644 --- a/src/completer.c +++ b/src/completer.c @@ -217,7 +217,7 @@ void slash_complete(struct slash *slash) slash->cursor = slash->length = strlen(slash->buffer); } if (completion->cmd->completer) { - /* Call the matching command completer with the rest of the buffer but only the current + /* Call the matching command completer with the rest of the buffer but only if the current completer allows it */ if(slash->complete_in_completion == true) { if(slash->length == strlen(completion->cmd->name)) { From 92b662c13f2668a3ac3a6b623af14b3a1eedfb47 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Tue, 10 Sep 2024 15:58:55 +0200 Subject: [PATCH 16/52] Fix a NULL dereference found while testing get/set param completion --- src/optparse.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/optparse.c b/src/optparse.c index 9d002a8..9606909 100644 --- a/src/optparse.c +++ b/src/optparse.c @@ -161,7 +161,9 @@ int optparse_parse(optparse_t * parser, int argc, const char * argv[]) { parser->argi = 0; while (parser->argi < parser->argc) { const char * s = argv[parser->argi++]; - + if(!s) { + break; + } if (s[0] == '-' && s[1] == '-') { if (!s[2]) break; From 1fa566b48c95bf5856158db5f8b1a3d323ef37a4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Tue, 10 Sep 2024 16:02:53 +0200 Subject: [PATCH 17/52] Fix greedy comparison which prevents option values to be whitespace separated. Without this fix, it is not possible to use the following: get -n135 --- src/optparse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optparse.c b/src/optparse.c index 9606909..3a873e3 100644 --- a/src/optparse.c +++ b/src/optparse.c @@ -131,7 +131,7 @@ handle_short_opt(optparse_t * parser, char c, char c2) { for (opt = parser->options; opt; opt = opt->next) { if (opt->short_opt && c == opt->short_opt) { if (opt->arg_desc) { - if (parser->argi >= parser->argc) { + if (parser->argi > parser->argc) { fprintf(stderr, "%s: \"-%c\" requires an argument\n", parser->progname, c); return 0; From dddc6a232f51e9b001f2e70f796adbb4f5e56bb0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Tue, 10 Sep 2024 16:03:46 +0200 Subject: [PATCH 18/52] Skip the found command name when building the command line --- src/completer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/completer.c b/src/completer.c index 1773bc9..7dbaded 100644 --- a/src/completer.c +++ b/src/completer.c @@ -229,7 +229,8 @@ void slash_complete(struct slash *slash) char *argv[SLASH_ARG_MAX]; slash->argv = argv; char args[slash->line_size]; - strcpy(args, slash->buffer); + /* Skip the found command name when building the command line */ + strcpy(args, slash->buffer + cmd_len + 1); slash_build_args(args, slash->argv, &slash->argc); completion->cmd->completer(slash, slash->buffer + cmd_len + 1); } From 8c8b1deec50fd2f3af4ea05682335ccaf385ab53 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 13 Sep 2024 13:43:28 +0200 Subject: [PATCH 19/52] Feature/add-environment-variables-handling (#13) * Add a configurable mechanism that applications can use to modify the actual command line about to be slash-executed. * Typo in comment * Add support for an optional "global" completion function * Initialise variable, caught by PR review --- include/slash/slash.h | 17 +++++++++++++++++ src/completer.c | 9 +++++++++ src/slash.c | 26 +++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/include/slash/slash.h b/include/slash/slash.h index a22b5bd..5fd1ea6 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -209,6 +209,23 @@ char *slash_readline(struct slash *slash); */ void slash_on_execute_hook(const char *line); + +typedef char *(*slash_process_cmd_line_hook_t)(const char *line); +/** + * @brief Set this variable to a function if you wish to modify the command line about to be executed + * + * @param line the slash line about to be executed + * @return a malloc'ed pointer to the processed command line, this will be free()'d for you, NULL is a valid + * return value and will cause the original line to be used as-is. + */ +extern slash_process_cmd_line_hook_t slash_process_cmd_line_hook; + +/** + * @brief Set this variable to a function if you wish to execute general completions after specific completions have been executed + * @see slash_completer_func_t + */ +extern slash_completer_func_t slash_global_completer; + int slash_execute(struct slash *slash, char *line); int slash_loop(struct slash *slash); diff --git a/src/completer.c b/src/completer.c index 7dbaded..8f2ead8 100644 --- a/src/completer.c +++ b/src/completer.c @@ -143,6 +143,8 @@ struct completion_entry { SLIST_ENTRY(completion_entry) list; }; +slash_completer_func_t slash_global_completer = NULL; + /** * @brief For tab auto completion, calls other completion functions when matched command has them * @@ -233,6 +235,9 @@ void slash_complete(struct slash *slash) strcpy(args, slash->buffer + cmd_len + 1); slash_build_args(args, slash->argv, &slash->argc); completion->cmd->completer(slash, slash->buffer + cmd_len + 1); + if (slash_global_completer) { + slash_global_completer(slash, slash->buffer + cmd_len + 1); + } } } } else if(matches > 1) { @@ -245,6 +250,10 @@ void slash_complete(struct slash *slash) slash->buffer[prefix_len] = '\0'; slash->cursor = slash->length = strlen(slash->buffer); } + } else { + if (slash_global_completer) { + slash_global_completer(slash, slash->buffer); + } } /* Free up the completion list we built up earlier */ while (!SLIST_EMPTY(&completions)) diff --git a/src/slash.c b/src/slash.c index ba5310f..e69e967 100644 --- a/src/slash.c +++ b/src/slash.c @@ -366,10 +366,13 @@ __attribute__((weak)) int slash_prompt(struct slash *slash) { return 0; } +slash_process_cmd_line_hook_t slash_process_cmd_line_hook = NULL; + int slash_execute(struct slash *slash, char *line) { struct slash_command *command; char *args, *argv[SLASH_ARG_MAX]; + char *processed_cmd_line = NULL, *line_to_use; int ret, argc = 0; /* Skip comments */ @@ -377,9 +380,22 @@ int slash_execute(struct slash *slash, char *line) return SLASH_SUCCESS; } - command = slash_command_find(slash, line, strlen(line), &args); + if(NULL != slash_process_cmd_line_hook) { + processed_cmd_line = slash_process_cmd_line_hook(line); + } + + if (processed_cmd_line != NULL) { + line_to_use = processed_cmd_line; + } else { + line_to_use = line; + } + + command = slash_command_find(slash, line_to_use, strlen(line_to_use), &args); if (!command) { + /* Print the original line here, not the possibly processed one */ slash_printf(slash, "No such command: %s\n", line); + /* Yes, processed_cmd_line maybe NULL, but the man page says it's ok, so we save an "if" statement */ + free(processed_cmd_line); return -ENOENT; } @@ -387,12 +403,16 @@ int slash_execute(struct slash *slash, char *line) slash_on_execute_hook(line); if (!command->func) { + /* Yes, processed_cmd_line maybe NULL, but the free() man page says it's ok, so we save an "if" statement */ + free(processed_cmd_line); return -EINVAL; } /* Build args */ if (slash_build_args(args, argv, &argc) < 0) { slash_printf(slash, "Mismatched quotes\n"); + /* Yes, processed_cmd_line maybe NULL, but the free() man page says it's ok, so we save an "if" statement */ + free(processed_cmd_line); return -EINVAL; } @@ -407,9 +427,13 @@ int slash_execute(struct slash *slash, char *line) slash->argv = argv; ret = command->func(slash); + + if (ret == SLASH_EUSAGE) slash_command_usage(slash, command); + /* Yes, processed_cmd_line maybe NULL, but the free() man page says it's ok, so we save an "if" statement */ + free(processed_cmd_line); return ret; } From ba5562160617ad493164e5d82ae45ba260c1b37d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Mon, 16 Sep 2024 11:43:44 +0200 Subject: [PATCH 20/52] Refactoring in general completion code removed initial newline when multiple completions are found, causing the slash_path_completer function to print on the same line as the cmd line, which is confusing. --- src/completer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/completer.c b/src/completer.c index 8f2ead8..f3e5ce8 100644 --- a/src/completer.c +++ b/src/completer.c @@ -281,6 +281,7 @@ void slash_path_completer(struct slash * slash, char * token) { struct dirent * entry; if (token[0]=='\0') { + printf("\n"); ls_appended(NULL, NULL); return; } @@ -375,6 +376,7 @@ void slash_path_completer(struct slash * slash, char * token) { token[subdir_idx+prefix_idx+1] = 0; slash->length = (token - slash->buffer) + strlen(token); slash->cursor = slash->length; + printf("\n"); ls_appended(token, "* -d"); break; } From dc98b771a126bd245bc88acc373611e0a954ea12 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 20 Sep 2024 12:56:55 +0200 Subject: [PATCH 21/52] Actually find an exact match, as per comment. Current code finds a *prefix* match, leading to typing things like "listttt" being interpreted the same as "list" --- src/slash.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/slash.c b/src/slash.c index e69e967..39a4b27 100644 --- a/src/slash.c +++ b/src/slash.c @@ -261,19 +261,31 @@ slash_command_find(struct slash *slash, char *line, size_t linelen, char **args) struct slash_command * cmd; slash_list_iterator i = {}; + int cmd_length; while ((cmd = slash_list_iterate(&i)) != NULL) { - + cmd_length = strlen(cmd->name); /* Find an exact match */ - if (strncmp(line, cmd->name, strlen(cmd->name)) != 0) + if (strncmp(line, cmd->name, cmd_length) == 0) { + /* Now, let's see if it is an actual exact match, or simply a prefix match + as a prefix match can list to "listttt" being interpreted as the valid "list" command + */ + if (linelen > cmd_length) { + /* If the input > match, check that the next character is a separator */ + if(line[cmd_length] != ' ') { + continue; + } + } + } else { continue; + } /* Update the max-length match */ - if (strlen(cmd->name) > max_matchlen) { + if (cmd_length > max_matchlen) { max_match_cmd = cmd; - max_matchlen = strlen(cmd->name); + max_matchlen = cmd_length; /* Calculate arguments position */ - *args = line + strlen(cmd->name); + *args = line + cmd_length; } } From 7cbffcfde213bcefcaed9c6173cbbfd6e7a45605 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 20 Sep 2024 15:20:16 +0200 Subject: [PATCH 22/52] Fix comment --- src/slash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slash.c b/src/slash.c index 39a4b27..49ce3b5 100644 --- a/src/slash.c +++ b/src/slash.c @@ -267,7 +267,7 @@ slash_command_find(struct slash *slash, char *line, size_t linelen, char **args) /* Find an exact match */ if (strncmp(line, cmd->name, cmd_length) == 0) { /* Now, let's see if it is an actual exact match, or simply a prefix match - as a prefix match can list to "listttt" being interpreted as the valid "list" command + as a prefix match can lead to "listttt" being interpreted as the valid "list" command */ if (linelen > cmd_length) { /* If the input > match, check that the next character is a separator */ From 4754166c6511539c54d288855386d3799d749a3d Mon Sep 17 00:00:00 2001 From: Johan de Claville Christiansen Date: Tue, 24 Sep 2024 09:53:46 +0200 Subject: [PATCH 23/52] Defer signals to external handler Catch the SIGINT outside of slash and then call slash_sigint to pass signals down, for the usual CTRL+C behaviour in slash. --- include/slash/slash.h | 3 +++ src/slash.c | 21 ++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/include/slash/slash.h b/include/slash/slash.h index 5fd1ea6..a5440bd 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -143,6 +143,7 @@ struct slash { int fd_read; slash_waitfunc_t waitfunc; bool use_activate; + int signal; /* Line editing */ size_t line_size; @@ -202,6 +203,8 @@ void slash_destroy(struct slash *slash); char *slash_readline(struct slash *slash); +void slash_sigint(struct slash *slash, int signum); + /** * @brief Implement this function to do something with the current line (logging, etc) * diff --git a/src/slash.c b/src/slash.c index 49ce3b5..b6b29c8 100644 --- a/src/slash.c +++ b/src/slash.c @@ -119,7 +119,7 @@ static int slash_rawmode_enable(struct slash *slash) raw = slash->original; raw.c_cflag |= (CS8); - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; @@ -759,12 +759,6 @@ static void slash_delete(struct slash *slash) } } -void slash_clear_screen(struct slash *slash) -{ - const char *esc = ESCAPE("H") ESCAPE("2J"); - slash_write(slash, esc, strlen(esc)); -} - static void slash_backspace(struct slash *slash) { if (slash->cursor > 0) { @@ -823,6 +817,19 @@ static void slash_swap(struct slash *slash) slash->cursor++; } } + + +void slash_clear_screen(struct slash *slash) { + const char *esc = ESCAPE("H") ESCAPE("2J"); + slash_write(slash, esc, strlen(esc)); +} + +void slash_sigint(struct slash *slash, int signum) { + slash->signal = signum; + slash_reset(slash); + slash_refresh(slash, 0); +} + #include char *slash_readline(struct slash *slash) { From 8f9e8d0d6bf044db446f540e54be0647e20ab26f Mon Sep 17 00:00:00 2001 From: Troels Jessen Date: Tue, 8 Oct 2024 12:58:49 +0200 Subject: [PATCH 24/52] Add indication to know if slash is currently executing a command --- include/slash/slash.h | 1 + src/slash.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/include/slash/slash.h b/include/slash/slash.h index a5440bd..b7284af 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -144,6 +144,7 @@ struct slash { slash_waitfunc_t waitfunc; bool use_activate; int signal; + int busy; /* Line editing */ size_t line_size; diff --git a/src/slash.c b/src/slash.c index b6b29c8..374e4fc 100644 --- a/src/slash.c +++ b/src/slash.c @@ -387,6 +387,8 @@ int slash_execute(struct slash *slash, char *line) char *processed_cmd_line = NULL, *line_to_use; int ret, argc = 0; + slash->busy = 1; + /* Skip comments */ if (line[0] == '#') { return SLASH_SUCCESS; @@ -446,6 +448,9 @@ int slash_execute(struct slash *slash, char *line) /* Yes, processed_cmd_line maybe NULL, but the free() man page says it's ok, so we save an "if" statement */ free(processed_cmd_line); + + slash->busy = 0; + return ret; } From 4e50e0aadbdb18a3266b0cc20d5507c5f01f2926 Mon Sep 17 00:00:00 2001 From: edvard Date: Tue, 8 Oct 2024 14:42:41 +0200 Subject: [PATCH 25/52] sigint: if slash busy set slash signal else reset and refresh slash prompt --- src/slash.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/slash.c b/src/slash.c index 374e4fc..9449b16 100644 --- a/src/slash.c +++ b/src/slash.c @@ -388,6 +388,7 @@ int slash_execute(struct slash *slash, char *line) int ret, argc = 0; slash->busy = 1; + slash->signal = 0; /* Skip comments */ if (line[0] == '#') { @@ -830,9 +831,12 @@ void slash_clear_screen(struct slash *slash) { } void slash_sigint(struct slash *slash, int signum) { - slash->signal = signum; - slash_reset(slash); - slash_refresh(slash, 0); + if (slash->busy) { + slash->signal = signum; + } else { + slash_reset(slash); + slash_refresh(slash, 0); + } } #include From 6e594270c49b958704409e43526317f9c9505637 Mon Sep 17 00:00:00 2001 From: kivkiv12345 Date: Tue, 15 Oct 2024 14:14:47 +0200 Subject: [PATCH 26/52] Fix strncpy() length from source, rather than destination --- src/completer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/completer.c b/src/completer.c index f3e5ce8..f259528 100644 --- a/src/completer.c +++ b/src/completer.c @@ -214,7 +214,7 @@ void slash_complete(struct slash *slash) cmd_len = strlen(completion->cmd->name); if(slash->length < strlen(completion->cmd->name)) { /* The buffer uniquely completes to a longer command */ - strncpy(slash->buffer, completion->cmd->name, cmd_len); + strncpy(slash->buffer, completion->cmd->name, strlen(slash->buffer)); slash->buffer[cmd_len] = '\0'; slash->cursor = slash->length = strlen(slash->buffer); } From 0eabe699bb10310bfbf5a4fca68b14dd6011a7f1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Thu, 24 Oct 2024 15:07:27 +0200 Subject: [PATCH 27/52] fix: when one and only completion is found, replace the (potentially) partial user input up to the found completion string For example: - user types in "clea" - there is only one possible completion, "clean" - then fill up the string with the rest of the completion, a.k.a "clean" --- src/completer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/completer.c b/src/completer.c index f259528..b1522a5 100644 --- a/src/completer.c +++ b/src/completer.c @@ -214,7 +214,7 @@ void slash_complete(struct slash *slash) cmd_len = strlen(completion->cmd->name); if(slash->length < strlen(completion->cmd->name)) { /* The buffer uniquely completes to a longer command */ - strncpy(slash->buffer, completion->cmd->name, strlen(slash->buffer)); + strncpy(slash->buffer, completion->cmd->name, cmd_len); slash->buffer[cmd_len] = '\0'; slash->cursor = slash->length = strlen(slash->buffer); } @@ -222,7 +222,7 @@ void slash_complete(struct slash *slash) /* Call the matching command completer with the rest of the buffer but only if the current completer allows it */ if(slash->complete_in_completion == true) { - if(slash->length == strlen(completion->cmd->name)) { + if(slash->length == cmd_len) { slash->buffer[slash->length] = ' '; slash->buffer[slash->length+1] = '\0'; slash->cursor++; From 6ea6960ddc64be288f27ba32b26c301dbed31a1d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Tue, 5 Nov 2024 14:46:44 +0100 Subject: [PATCH 28/52] Prevent crash when passing no value to short options that require one --- src/optparse.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/optparse.c b/src/optparse.c index 3a873e3..366f23c 100644 --- a/src/optparse.c +++ b/src/optparse.c @@ -132,17 +132,21 @@ handle_short_opt(optparse_t * parser, char c, char c2) { if (opt->short_opt && c == opt->short_opt) { if (opt->arg_desc) { if (parser->argi > parser->argc) { - fprintf(stderr, "%s: \"-%c\" requires an argument\n", - parser->progname, c); + fprintf(stderr, "%s: \"-%c\" requires an argument\n", parser->progname, c); return 0; } if (c2 == '\0') { + if (parser->argi >= parser->argc) { + fprintf(stderr, "%s: \"-%c\" requires an argument\n", parser->progname, c); + return 0; + } return opt->func(opt, parser->argv[parser->argi++]); + } else { + return opt->func(opt, parser->argv[parser->argi - 1] + 2); } - return opt->func(opt, parser->argv[parser->argi - 1] + 2); - - } else + } else { return opt->func(opt, NULL); + } } } From 31926bdae71509e3c0d4132700f8f92e06b9a16d Mon Sep 17 00:00:00 2001 From: kivkiv12345 Date: Sun, 8 Dec 2024 17:56:41 +0100 Subject: [PATCH 29/52] PLT-291 Added customizable hooks to slash_run() --- include/slash/slash.h | 17 +++++++++++++++++ src/run.c | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/include/slash/slash.h b/include/slash/slash.h index b7284af..f85a500 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -214,6 +214,23 @@ void slash_sigint(struct slash *slash, int signum); void slash_on_execute_hook(const char *line); +/** + * @brief Will be called before a file is executed by "run ", implement it to set environment variables for example. + * + * @param filename name of the file to be executed. + * @param ctx_for_post Assignable context pointer which will be passed to the corresponding post_hook call. If it is malloc, it should be freed in the post-hook. + */ +void slash_on_run_pre_hook(const char * const filename, void ** ctx_for_post); + +/** + * @brief Will be called after a file has been executed by "run ", implement it to set clear variables for example. + * + * @param filename name of the file which was executed. + * @param ctx customizable context from corresponding pre-hook. + */ +void slash_on_run_post_hook(const char * const filename, void * ctx); + + typedef char *(*slash_process_cmd_line_hook_t)(const char *line); /** * @brief Set this variable to a function if you wish to modify the command line about to be executed diff --git a/src/run.c b/src/run.c index 9b6a9ff..8cd197f 100644 --- a/src/run.c +++ b/src/run.c @@ -3,6 +3,16 @@ #include #include #include +#include + + +/* Implement this function to set environment variables for example */ +__attribute__((weak)) void slash_on_run_pre_hook(const char * const filename, void ** ctx_for_post) { /* Set up environemnt variables for "run" command. */ +} + +/* Implement this function to clear environment variables for example */ +__attribute__((weak)) void slash_on_run_post_hook(const char * const filename, void * ctx) { +} int slash_run(struct slash *slash, char * filename, int printcmd) { @@ -22,6 +32,10 @@ int slash_run(struct slash *slash, char * filename, int printcmd) { return SLASH_EIO; } + + void *ctx_for_post = NULL; + slash_on_run_pre_hook(filename_local, &ctx_for_post); + char line[512]; int ret = SLASH_SUCCESS; while(fgets(line, sizeof(line), stream)) { @@ -50,6 +64,8 @@ int slash_run(struct slash *slash, char * filename, int printcmd) { fclose(stream); + slash_on_run_post_hook(filename_local, ctx_for_post); + return ret; } From 337b3c8d60d7ceac4c8f5fe06daddfa4ffc568ae Mon Sep 17 00:00:00 2001 From: kivkiv12345 Date: Wed, 11 Dec 2024 15:18:07 +0100 Subject: [PATCH 30/52] Added description of __FILE__ and __FILE_DIR__ environment variables Not that slash knows anything of these, but this is the way to document them for now. --- src/run.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/run.c b/src/run.c index 8cd197f..1cfb1b1 100644 --- a/src/run.c +++ b/src/run.c @@ -97,5 +97,8 @@ static int cmd_run(struct slash *slash) { return res; } -slash_command_completer(run, cmd_run, slash_path_completer, "", "Runs commands in specified file"); +slash_command_completer(run, cmd_run, slash_path_completer, "", "Runs commands in specified file\n"\ + "Sets the following environemnt variables during execution:\n\n"\ + "- __FILE__ to the name of the executed file\n"\ + "- __FILE_DIR__ to the directory containing the executed file, useful for running other files located relative to __FILE__"); \ No newline at end of file From 8c4eb8af5c0372b6f90fc4895285eaacc57bcb3f Mon Sep 17 00:00:00 2001 From: kivkiv12345 Date: Fri, 20 Dec 2024 09:57:19 +0100 Subject: [PATCH 31/52] Improve cmd_run() documentation --- src/run.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/run.c b/src/run.c index 1cfb1b1..e5cfe31 100644 --- a/src/run.c +++ b/src/run.c @@ -97,8 +97,9 @@ static int cmd_run(struct slash *slash) { return res; } -slash_command_completer(run, cmd_run, slash_path_completer, "", "Runs commands in specified file\n"\ - "Sets the following environemnt variables during execution:\n\n"\ - "- __FILE__ to the name of the executed file\n"\ +slash_command_completer(run, cmd_run, slash_path_completer, "", "Runs commands in the specified file. \n"\ + "Sets the following environment variables during execution:\n\n"\ + "- __FILE__ to the path and name of the executed file\n"\ "- __FILE_DIR__ to the directory containing the executed file, useful for running other files located relative to __FILE__"); - \ No newline at end of file +/* TODO: Documenting __FILE__ and __FILE_DIR__ is incorrect. + They are implemented in hooks, but right now we don't have a way to document them there. */ From 5e6439ddfb9716eb73e5f5d1b6a605bccad18a11 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 20 Dec 2024 09:12:33 +0100 Subject: [PATCH 32/52] Bump slash commands maximum number of allowed arguments to 32 --- src/builtins.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builtins.h b/src/builtins.h index a4e69e1..881c9ba 100644 --- a/src/builtins.h +++ b/src/builtins.h @@ -4,7 +4,7 @@ #include /* Configuration */ -#define SLASH_ARG_MAX 16 /* Maximum number of arguments */ +#define SLASH_ARG_MAX 32 /* Maximum number of arguments */ #define SLASH_SHOW_MAX 25 /* Maximum number of commands to list */ /* Declarations for required implementation functions in slash.c */ From 4799fc2d78418f1b14e23c2a360dd34309bc9509 Mon Sep 17 00:00:00 2001 From: kivkiv12345 Date: Thu, 27 Feb 2025 11:54:31 +0100 Subject: [PATCH 33/52] Added verbosity argument to "run" --- src/run.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/run.c b/src/run.c index e5cfe31..6f2cd76 100644 --- a/src/run.c +++ b/src/run.c @@ -72,7 +72,10 @@ int slash_run(struct slash *slash, char * filename, int printcmd) { static int cmd_run(struct slash *slash) { + int verbosity = 2; + optparse_t * parser = optparse_new("run", ""); + optparse_add_int(parser, 'v', "verbosity", "NUM", 0, &verbosity, "verbosity (default = 2, max = 2)"); optparse_add_help(parser); int argi = optparse_parse(parser, slash->argc - 1, (const char **) slash->argv + 1); @@ -88,11 +91,15 @@ static int cmd_run(struct slash *slash) { return SLASH_EINVAL; } - char * name = slash->argv[argi]; + char * const name = slash->argv[argi]; + + if (verbosity >= 1) { + printf("Running %s\n", name); + } - printf("Running %s\n", name); + const bool printcmd = verbosity >= 2; - int res = slash_run(slash, name, 1); + const int res = slash_run(slash, name, printcmd); optparse_del(parser); return res; From 37a0d6a1451b497273f0b7a391b522804cbd63c6 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 21 Mar 2025 10:26:39 +0100 Subject: [PATCH 34/52] Comments are indicated by '#' character, update code to actually use that --- src/run.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run.c b/src/run.c index 6f2cd76..a645bec 100644 --- a/src/run.c +++ b/src/run.c @@ -48,7 +48,7 @@ int slash_run(struct slash *slash, char * filename, int printcmd) { continue; /* Skip comments */ - if (line[0] == '/') { + if (line[0] == '#') { continue; } From 4dda935d952633420204bb9f65f5f09fdebb87dd Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Fri, 28 Mar 2025 14:06:23 +0100 Subject: [PATCH 35/52] Move "node" cmd out of slash and into CSH (where it belongs) to allow hostname tab-completion (#17) --- meson.build | 1 - src/dflopt.c | 41 ----------------------------------------- 2 files changed, 42 deletions(-) delete mode 100644 src/dflopt.c diff --git a/meson.build b/meson.build index 3b04151..bd57fc4 100644 --- a/meson.build +++ b/meson.build @@ -10,7 +10,6 @@ slash_sources = files([ if get_option('builtins') slash_sources += files([ 'src/builtins.c', - 'src/dflopt.c', 'src/run.c', ]) endif diff --git a/src/dflopt.c b/src/dflopt.c deleted file mode 100644 index 1f1c544..0000000 --- a/src/dflopt.c +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include - -unsigned int slash_dfl_node = 0; -unsigned int slash_dfl_timeout = 1000; - -static int cmd_node(struct slash *slash) { - - if (slash->argc < 1) { - /* Only print node when explicitly asked, - as it should be shown in the prompt. - (it may be truncated when the hostname is too long) */ - printf("Default node = %d\n", slash_dfl_node); - } - - if (slash->argc == 2) { - - /* We rely on user to provide known hosts implementation */ - int known_hosts_get_node(char * find_name); - slash_dfl_node = known_hosts_get_node(slash->argv[1]); - if (slash_dfl_node == 0) - slash_dfl_node = atoi(slash->argv[1]); - } - - return SLASH_SUCCESS; -} -slash_command(node, cmd_node, "[node]", "Set global default node"); - - -static int cmd_timeout(struct slash *slash) { - - if (slash->argc == 2) { - slash_dfl_timeout = atoi(slash->argv[1]); - } - - printf("timeout = %d\n", slash_dfl_timeout); - - return SLASH_SUCCESS; -} -slash_command(timeout, cmd_timeout, "[timeout ms]", "Set global default timeout"); From 338fcc0d1db6580a67f6fd6ed00947494ffd23ef Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Tue, 1 Apr 2025 11:18:08 +0200 Subject: [PATCH 36/52] Progressive removal of CSH specifics (#18) * Remove unneed header inclusion * Add currently not active warning in anticipation of dflopt.h header deprecation --- include/slash/dflopt.h | 4 ++++ src/builtins.c | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/include/slash/dflopt.h b/include/slash/dflopt.h index 0150cdf..95e3f4c 100644 --- a/include/slash/dflopt.h +++ b/include/slash/dflopt.h @@ -1,5 +1,9 @@ #pragma once +/* TODO: determine when to enable this */ +#if 0 +#pragma GCC warning "dflopt.h is deprecated, use #include instead" +#endif #include #include diff --git a/src/builtins.c b/src/builtins.c index 1706f7e..6bb5d2e 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -5,7 +5,6 @@ #include #include -#include #include #include From 70041ee7dee05a48f75f01eedf19232964121d20 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Mon, 7 Apr 2025 14:42:01 +0200 Subject: [PATCH 37/52] Feature/CSH-21-Extend-slash-to-allow-use-of-custom-options-handling (#19) * Extend slash to allow use of custom options handling * Remove duplicate line --- include/slash/optparse.h | 23 +++++++++++++++++++++++ src/optparse.c | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/include/slash/optparse.h b/include/slash/optparse.h index 0707c2e..c09d7f7 100644 --- a/include/slash/optparse.h +++ b/include/slash/optparse.h @@ -55,4 +55,27 @@ optparse_opt_t *optparse_add_double(optparse_t *parser, int short_opt, const cha optparse_opt_t *optparse_add_string(optparse_t *parser, int short_opt, const char *long_opt, const char *arg_desc, char **ptr, char *help); +/** + * @brief interface to be implemented for custom option parsers + * + * This function will be called by the optparse framework when it processes an + * option for which this custom option value function was registered + * @param data an opaque pointer where the parsed option value must be stored + * @param arg the part of the command line containing the option value as a string to be parsed + */ +typedef int (*optparse_custom_func_t)(void *data, const char * arg); + +/** + * @brief Register an option with a custom parser (see the "func" param) + * @param parser pointer to valid optparse object the option will be attached to + * @param short_opt single character option (for example: 'c' for the corresponding "-c" cmd line option). Set to '\0' if you do not want a short option + * @param long_opt string option (for example "test" for the corresponding "--test" cmd line option). Set to NULL if you do not want a long option + * @param arg_desc string describing the argument (will be shown when help is invoked), may be NULL + * @param help string describing waht this option is/does + * @param func ptr to a cutom option value parser, see the optparse_custom_func_t typedef documentation + * @param data opaque pointer to a valid memory where the "func" above will store the parsed option value + * @return reference to newly created optparse_opt_t object + */ +optparse_opt_t *optparse_add_custom(optparse_t * parser, int short_opt, const char * long_opt, const char * arg_desc, const char * help, optparse_custom_func_t func, void * data); + #endif /* _OPTPARSE_H */ \ No newline at end of file diff --git a/src/optparse.c b/src/optparse.c index 366f23c..f24da0d 100644 --- a/src/optparse.c +++ b/src/optparse.c @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -53,6 +54,7 @@ struct optparse_opt { unsigned num_base; int set_value; } spec; + bool should_free_data; }; optparse_t * @@ -85,6 +87,9 @@ void optparse_del(optparse_t * parser) { while ((opt = parser->options)) { parser->options = opt->next; + if(opt->should_free_data) { + free(opt->data); + } free(opt); } free(parser); @@ -208,7 +213,42 @@ optparse_opt_new(optparse_t * parser, opt->help = help; opt->func = func; opt->data = data; + opt->should_free_data = false; + + *parser->last_option = opt; + parser->last_option = &opt->next; + + return opt; +} + +struct custom_opt_ctx { + optparse_custom_func_t func; + void *org_data; +}; + +static int optparse_custom_func_trampoline(optparse_opt_t * opt, const char * arg) { + struct custom_opt_ctx * custom_ctx = opt->data; + return custom_ctx->func(custom_ctx->org_data, arg); +} +optparse_opt_t *optparse_add_custom(optparse_t * parser, int short_opt, const char * long_opt, const char * arg_desc, const char * help, optparse_custom_func_t func, void * data) { + optparse_opt_t * opt; + struct custom_opt_ctx *custom_ctx = calloc(1, sizeof(*custom_ctx)); + custom_ctx->func = func; + custom_ctx->org_data = data; + opt = malloc(sizeof(*opt)); + memset(opt, 0, sizeof(*opt)); + + opt->parser = parser; + opt->short_opt = short_opt; + opt->long_opt = long_opt; + if (opt->long_opt) + opt->long_opt_len = strlen(opt->long_opt); + opt->arg_desc = arg_desc; + opt->help = help; + opt->func = optparse_custom_func_trampoline; + opt->data = custom_ctx; + opt->should_free_data = true; *parser->last_option = opt; parser->last_option = &opt->next; From cf6ae969b82a25086b11ee7acb707396684d4645 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Wed, 16 Apr 2025 13:48:55 +0200 Subject: [PATCH 38/52] Lazy fix: there is an "off-by-one" algorithmic error in slash history related functions This change configures slash's history size to what the user asks - 1 but still allocates the originally requested memory amount... Found with valgrind --- src/slash.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slash.c b/src/slash.c index 9449b16..517198c 100644 --- a/src/slash.c +++ b/src/slash.c @@ -1044,8 +1044,8 @@ struct slash *slash_create(size_t line_size, size_t history_size) return NULL; } - slash->history_size = history_size; - slash->history = calloc(1, slash->history_size); + slash->history_size = history_size - 1; + slash->history = calloc(1, history_size); if (!slash->history) { free(slash->buffer); free(slash); From 42e90ed05920bb53cf5edf779e761a818a29a32a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Wed, 30 Apr 2025 10:00:20 +0200 Subject: [PATCH 39/52] Handle "HOME" and "END" keys when in a TMUX session --- src/slash.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/slash.c b/src/slash.c index 517198c..ad04805 100644 --- a/src/slash.c +++ b/src/slash.c @@ -888,6 +888,18 @@ char *slash_readline(struct slash *slash) slash->cursor = 0; } else if (esc[0] == '[' && esc[1] == 'F') { slash->cursor = slash->length; + } else if (esc[0] == 'O') { + /* If the first escape character is 'O', we're likely in a TMUX session. + The HOME and END keys (unless remapped by the user in their tmux config) send "OH" and "OF" respetively + so we handle those too + */ + if(NULL != getenv("TMUX")) { + if (esc[1] == 'H') { + slash->cursor = 0; + } else if (esc[1] == 'F') { + slash->cursor = slash->length; + } + } } else if (esc[0] == '1' && esc[1] == '~') { slash->cursor = 0; } else if (esc[0] == '4' && esc[1] == '[') { From cdd370741bb3bed7257870a1fa13c7ce52aa0fde Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Wed, 7 May 2025 09:25:59 +0200 Subject: [PATCH 40/52] Fix warnings: - comparison of integer expressions of different signedness - unused parameter --- src/slash.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/slash.c b/src/slash.c index ad04805..27cd4bf 100644 --- a/src/slash.c +++ b/src/slash.c @@ -225,6 +225,7 @@ int slash_wait_interruptible(struct slash *slash, unsigned int ms) int slash_printf(struct slash *slash, const char *format, ...) { + (void)slash; int ret; va_list args; @@ -255,13 +256,14 @@ static bool slash_line_empty(char *line, size_t linelen) struct slash_command * slash_command_find(struct slash *slash, char *line, size_t linelen, char **args) { + (void)slash; /* Maximum length match */ size_t max_matchlen = 0; struct slash_command *max_match_cmd = NULL; struct slash_command * cmd; slash_list_iterator i = {}; - int cmd_length; + size_t cmd_length; while ((cmd = slash_list_iterate(&i)) != NULL) { cmd_length = strlen(cmd->name); /* Find an exact match */ @@ -371,10 +373,12 @@ void slash_command_description(struct slash *slash, struct slash_command *comman /* Implement this function to perform logging for example */ __attribute__((weak)) void slash_on_execute_hook(const char *line) { + (void)line; } /* A default no-prompt implementation is provided as a __attribute__((weak)) */ __attribute__((weak)) int slash_prompt(struct slash *slash) { + (void)slash; return 0; } From f2313b0cd9cdfc188cde332d95dced4d3757bf85 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Wed, 7 May 2025 09:29:49 +0200 Subject: [PATCH 41/52] Handle memory allocation failures --- src/completer.c | 56 ++++++++++++++++++++++++++++--------------------- src/optparse.c | 43 ++++++++++++++++++++----------------- 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/completer.c b/src/completer.c index b1522a5..2e53a12 100644 --- a/src/completer.c +++ b/src/completer.c @@ -181,10 +181,12 @@ void slash_complete(struct slash *slash) /* Do we have an exact match on the buffer ?*/ if (cmd_match == 0) { if((cmd_len < len_to_compare_to && cmd->completer) || (len_to_compare_to <= cmd_len)) { - matches++; completion = malloc(sizeof(struct completion_entry)); - completion->cmd = cmd; - SLIST_INSERT_HEAD(&completions, completion, list); + if (completion) { + matches++; + completion->cmd = cmd; + SLIST_INSERT_HEAD(&completions, completion, list); + } } } } @@ -214,7 +216,7 @@ void slash_complete(struct slash *slash) cmd_len = strlen(completion->cmd->name); if(slash->length < strlen(completion->cmd->name)) { /* The buffer uniquely completes to a longer command */ - strncpy(slash->buffer, completion->cmd->name, cmd_len); + strncpy(slash->buffer, completion->cmd->name, slash->line_size); slash->buffer[cmd_len] = '\0'; slash->cursor = slash->length = strlen(slash->buffer); } @@ -329,30 +331,36 @@ void slash_path_completer(struct slash * slash, char * token) { match_list_size = 16; match_count = 0; match_list = (char**) malloc(match_list_size * sizeof(char*)); - - while ((entry = readdir(cwd_ptr)) != NULL) { - - /* compare token with filename */ - char* pmatch = path_cmp(file_name_buf, entry->d_name); - - if (pmatch != NULL && strcmp(pmatch, ".")) { - /* allocate more memory if necessary */ - if (match_count+1 >= match_list_size) { - match_list_size += 16; - char** tmp = (char**) reallocarray(match_list, match_list_size, sizeof(char*)); - if (tmp == NULL) { + if(match_list) { + while ((entry = readdir(cwd_ptr)) != NULL) { + + /* compare token with filename */ + char* pmatch = path_cmp(file_name_buf, entry->d_name); + + if (pmatch != NULL && strcmp(pmatch, ".")) { + /* allocate more memory if necessary */ + if (match_count+1 >= match_list_size) { + match_list_size += 16; + char** tmp = (char**) reallocarray(match_list, match_list_size, sizeof(char*)); + if (tmp == NULL) { + printf("Unable to find all matches: No memory\n"); + break; + } + match_list = tmp; + } + char *match_tmp = (char*)malloc(strlen(pmatch) + 3); + if(match_tmp) { + match_list[match_count] = match_tmp; + strcpy(match_list[match_count], pmatch); + if (entry->d_type == DT_DIR) { + strcat(match_list[match_count], "/"); + } + match_count++; + } else { printf("Unable to find all matches: No memory\n"); break; } - match_list = tmp; - } - - match_list[match_count] = (char*)malloc(strlen(pmatch) + 3); - strcpy(match_list[match_count], pmatch); - if (entry->d_type == DT_DIR) { - strcat(match_list[match_count], "/"); } - match_count++; } } diff --git a/src/optparse.c b/src/optparse.c index f24da0d..faba802 100644 --- a/src/optparse.c +++ b/src/optparse.c @@ -232,26 +232,31 @@ static int optparse_custom_func_trampoline(optparse_opt_t * opt, const char * ar } optparse_opt_t *optparse_add_custom(optparse_t * parser, int short_opt, const char * long_opt, const char * arg_desc, const char * help, optparse_custom_func_t func, void * data) { - optparse_opt_t * opt; + optparse_opt_t * opt = NULL; struct custom_opt_ctx *custom_ctx = calloc(1, sizeof(*custom_ctx)); - custom_ctx->func = func; - custom_ctx->org_data = data; - opt = malloc(sizeof(*opt)); - memset(opt, 0, sizeof(*opt)); - - opt->parser = parser; - opt->short_opt = short_opt; - opt->long_opt = long_opt; - if (opt->long_opt) - opt->long_opt_len = strlen(opt->long_opt); - opt->arg_desc = arg_desc; - opt->help = help; - opt->func = optparse_custom_func_trampoline; - opt->data = custom_ctx; - opt->should_free_data = true; - *parser->last_option = opt; - parser->last_option = &opt->next; - + if (custom_ctx) { + custom_ctx->func = func; + custom_ctx->org_data = data; + opt = malloc(sizeof(*opt)); + if(opt) { + memset(opt, 0, sizeof(*opt)); + + opt->parser = parser; + opt->short_opt = short_opt; + opt->long_opt = long_opt; + if (opt->long_opt) + opt->long_opt_len = strlen(opt->long_opt); + opt->arg_desc = arg_desc; + opt->help = help; + opt->func = optparse_custom_func_trampoline; + opt->data = custom_ctx; + opt->should_free_data = true; + *parser->last_option = opt; + parser->last_option = &opt->next; + } else { + free(custom_ctx); + } + } return opt; } From 0cfd0169603e3168a7965bb36b107d37671f37fd Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Wed, 14 May 2025 23:49:20 +0200 Subject: [PATCH 42/52] Export a "link whole" meson dependency --- meson.build | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index bd57fc4..70dda53 100644 --- a/meson.build +++ b/meson.build @@ -39,9 +39,14 @@ slash_lib = library('slash', dependencies : dependencies, install : false, ) - + slash_dep = declare_dependency( include_directories : slash_inc, link_with : slash_lib, ) +slash_link_whole_dep = declare_dependency( + include_directories : slash_inc, + link_with : slash_lib, + link_whole: [slash_lib] +) From 9db9662caa9999779a4ad69b825828c5f9746ccc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Wed, 14 May 2025 23:50:43 +0200 Subject: [PATCH 43/52] Extend slash API to allow enabling/disabling handling of stdout/stdin by slash - useful for APM that need the "normal" stdin/stdout descriptors (for example, an embedded Python interpreter) --- include/slash/slash.h | 15 +++++++++++++++ src/slash.c | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/slash/slash.h b/include/slash/slash.h index f85a500..9d1d88f 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -303,4 +303,19 @@ int slash_list_add(struct slash_command * item); int slash_list_remove(const struct slash_command * item); int slash_list_init(); +/** + * @brief let slash handle stdout/stdin + * @param slash pointer to valid slash instance + */ +void slash_acquire_std_in_out(struct slash *slash); + +/** + * @brief let slash release handling of stdout/stdin to default. Use this function if you have for instance an APM that needs stdin/stdout control + * @warning remember to call slash_acquire_std_in_out when you are done! + * @param slash pointer to valid slash instance + * @return 0 if success, -ENOTTY in case of failure + */ +void slash_release_std_in_out(struct slash *slash); + + #endif /* _SLASH_H_ */ diff --git a/src/slash.c b/src/slash.c index 27cd4bf..56fceaa 100644 --- a/src/slash.c +++ b/src/slash.c @@ -154,6 +154,21 @@ static int slash_restore_term(struct slash *slash) return 0; } +static int slash_wait_select(void *slashp, unsigned int ms); +void slash_acquire_std_in_out(struct slash *slash) { + slash_configure_term(slash); +#ifdef SLASH_HAVE_SELECT + slash->waitfunc = slash_wait_select; +#endif +} + +void slash_release_std_in_out(struct slash *slash) { + slash_restore_term(slash); +#ifdef SLASH_HAVE_SELECT + slash->waitfunc = NULL; +#endif +} + int slash_write(struct slash *slash, const char *buf, size_t count) { return write(slash->fd_write, buf, count); From 9cdbbbd92993a0ed09b264029093dc660311457f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Wed, 28 May 2025 14:56:06 +0200 Subject: [PATCH 44/52] Trim trailing whitespaces from stdin-read line --- src/slash.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/slash.c b/src/slash.c index 56fceaa..6e26644 100644 --- a/src/slash.c +++ b/src/slash.c @@ -1018,7 +1018,14 @@ char *slash_readline(struct slash *slash) - +static void slash_trim(char *line, size_t line_len) { + while(--line_len) { + if(!isspace(line[line_len])) { + break; + } + line[line_len] = '\0'; + } +} /* Core */ @@ -1036,8 +1043,10 @@ int slash_loop(struct slash *slash) c = slash_getchar(slash); } while (c != '\n' && c != '\r'); } - + size_t line_len = 0; while ((line = slash_readline(slash))) { + line_len = strlen(line); + slash_trim(line, line_len); if (!slash_line_empty(line, strlen(line))) { /* Run command */ ret = slash_execute(slash, line); From c5bdffbfe2a6d9d19cd0f7167aa2a797b9d3bfa3 Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 11 Jun 2025 14:52:45 +0200 Subject: [PATCH 45/52] Fix valgrind warning when `slash_trim()`ing empty line --- src/slash.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slash.c b/src/slash.c index 6e26644..de1db18 100644 --- a/src/slash.c +++ b/src/slash.c @@ -1019,6 +1019,10 @@ char *slash_readline(struct slash *slash) static void slash_trim(char *line, size_t line_len) { + if (!line_len) { + return; + } + while(--line_len) { if(!isspace(line[line_len])) { break; From 439549a3e6f63c05f7fcd0ce8ddcb480a24a6a70 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Thu, 26 Jun 2025 15:44:40 +0200 Subject: [PATCH 46/52] Better (but not good yet) handling of "~" when tab-completing paths --- src/completer.c | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/completer.c b/src/completer.c index 2e53a12..7dc3315 100644 --- a/src/completer.c +++ b/src/completer.c @@ -21,7 +21,6 @@ static void ls_appended(const char* tok, const char* app) { } sprintf(cmd, "ls -p %s%s", (tok != NULL ? tok : ""), (app != NULL ? app : "")); - int ret = system(cmd); (void)ret; } @@ -274,8 +273,11 @@ void slash_complete(struct slash *slash) */ void slash_path_completer(struct slash * slash, char * token) { // TODO: Add windows support - char cwd_buf[256]; - char file_name_buf[256]; + char *cwd_buf = calloc(sizeof(char), PATH_MAX); + if(!cwd_buf) { + return; + } + char file_name_buf[FILENAME_MAX]; size_t match_list_size; uint16_t match_count; char ** match_list; @@ -302,30 +304,41 @@ void slash_path_completer(struct slash * slash, char * token) { if (res != cwd_buf) { printf("Path error\n"); slash_completer_revert_skip(slash, orig_slash_buffer); + free(cwd_buf); return; } // TODO: Add support for absolute paths /* handle home shortcut (~) and subdirectories in token */ - int subdir_idx = last_char_occ(token, '/'); + int subdir_idx; if (token[0] == '~') { - memset(cwd_buf, '\0', 256); strcpy(cwd_buf, getenv("HOME")); - strncpy(&cwd_buf[strlen(cwd_buf)], &token[1], subdir_idx); - } else if (subdir_idx != -1) { - strcat(cwd_buf, "/"); - strncat(cwd_buf, token, subdir_idx); + strcat(cwd_buf, &token[1]); + subdir_idx = last_char_occ(cwd_buf, '/'); + if (subdir_idx != -1) { + cwd_buf[subdir_idx] = '\0'; + } + } else { + subdir_idx = last_char_occ(token, '/'); + strcpy(cwd_buf, token); + if (subdir_idx != -1) { + cwd_buf[subdir_idx] = '\0'; + } } cwd_ptr = opendir(cwd_buf); + if (cwd_ptr == NULL) { + cwd_ptr = opendir("."); + } if (cwd_ptr == NULL) { printf("No such file or directory:\n"); ls_appended(NULL, NULL); slash_completer_revert_skip(slash, orig_slash_buffer); + free(cwd_buf); return; } - strcpy(file_name_buf, token+subdir_idx+1); + strcpy(file_name_buf, cwd_buf+subdir_idx+1); /* allocate memory dynamically as we don't know no. of matches */ match_list_size = 16; @@ -372,8 +385,10 @@ void slash_path_completer(struct slash * slash, char * token) { break; case 1: - strcpy(token+subdir_idx+1, match_list[0]); - slash->length = (token - slash->buffer) + strlen(token); + cwd_buf[subdir_idx] = '/'; + strcpy(cwd_buf+subdir_idx+1, match_list[0]); + strcpy(token, cwd_buf); + slash->length = strlen(cwd_buf); slash->cursor = slash->length; break; @@ -389,7 +404,7 @@ void slash_path_completer(struct slash * slash, char * token) { break; } } - + free(cwd_buf); slash_completer_revert_skip(slash, orig_slash_buffer); closedir(cwd_ptr); From 49fc78f50db542659e907e96ae66580ec65d0a30 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Mon, 30 Jun 2025 15:15:26 +0200 Subject: [PATCH 47/52] Fix parameter to getcwd() call --- src/completer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/completer.c b/src/completer.c index 7dc3315..a34282a 100644 --- a/src/completer.c +++ b/src/completer.c @@ -299,7 +299,7 @@ void slash_path_completer(struct slash * slash, char * token) { but it breaks tab-completion of sub commands ¯\_(ツ)_/¯ */ //token = slash->buffer; - char* res = getcwd(cwd_buf, sizeof(cwd_buf)); + char* res = getcwd(cwd_buf, PATH_MAX); if (res != cwd_buf) { printf("Path error\n"); From 1f09360989256069e2413e9c6434e5265fe33c6f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lab Date: Thu, 3 Jul 2025 09:21:21 +0200 Subject: [PATCH 48/52] Fix crash in slash_path_completer --- src/completer.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/completer.c b/src/completer.c index a34282a..0f9b3fb 100644 --- a/src/completer.c +++ b/src/completer.c @@ -385,7 +385,9 @@ void slash_path_completer(struct slash * slash, char * token) { break; case 1: - cwd_buf[subdir_idx] = '/'; + if(subdir_idx != -1) { + cwd_buf[subdir_idx] = '/'; + } strcpy(cwd_buf+subdir_idx+1, match_list[0]); strcpy(token, cwd_buf); slash->length = strlen(cwd_buf); From 82219751d067a4c255ff83dd21bd625d60b063ef Mon Sep 17 00:00:00 2001 From: kivkiv12345 <57446369+kivkiv12345@users.noreply.github.com> Date: Tue, 8 Jul 2025 15:58:42 +0200 Subject: [PATCH 49/52] Added customizable context pointer to `struct slash_command` (#20) Currently no function to use it, but a struct initializer will suffice. --- include/slash/slash.h | 12 +++++++++++- src/slash.c | 11 ++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/include/slash/slash.h b/include/slash/slash.h index 9d1d88f..3fbbb08 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -98,6 +98,7 @@ /* Command prototype */ struct slash; typedef int (*slash_func_t)(struct slash *slash); +typedef int (*slash_func_context_t)(struct slash *slash, void *context); /* Wait function prototype, * this function is implemented by user, so use a void* instead of struct slash* */ @@ -121,7 +122,12 @@ typedef void (*slash_completer_func_t)(struct slash *slash, char * token); struct slash_command { /* Static data */ char *name; - const slash_func_t func; + union { + /* Traditional function used by the `slash_command()` macros. */ + const slash_func_t func; + /* Function with context pointer, for advanced API usage. */ + const slash_func_context_t func_ctx; + }; const char *args; const char *help; const slash_completer_func_t completer; @@ -130,6 +136,10 @@ struct slash_command { /* single linked list: * The weird definition format comes from sys/queue.h SLINST_ENTRY() macro */ struct { struct slash_command *sle_next; } next; + + /* Optional context pointer (after `next` for ABI compatibility). + Will be supplied to `func_ctx` if specified. */ + void *context; }; /* Slash context */ diff --git a/src/slash.c b/src/slash.c index de1db18..9804c1a 100644 --- a/src/slash.c +++ b/src/slash.c @@ -459,9 +459,14 @@ int slash_execute(struct slash *slash, char *line) slash->argc = argc; slash->argv = argv; - ret = command->func(slash); - - + if (command->context) { + /* If the user has attached context to the command, + we assume they also specified a function which can accept it. */ + ret = command->func_ctx(slash, command->context); + } else { + /* Otherwise call the traditional (`slash_command()` macro) function without context. */ + ret = command->func(slash); + } if (ret == SLASH_EUSAGE) slash_command_usage(slash, command); From 79e572acf36cdcd9358de914382ac5ec3f07f89b Mon Sep 17 00:00:00 2001 From: kevin Date: Thu, 10 Jul 2025 14:18:02 +0200 Subject: [PATCH 50/52] Added `slash_on_execute_post_hook()` Not used internally, to be implemented by user. --- include/slash/slash.h | 4 ++++ src/slash.c | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/include/slash/slash.h b/include/slash/slash.h index 3fbbb08..4a7a2a7 100644 --- a/include/slash/slash.h +++ b/include/slash/slash.h @@ -223,6 +223,10 @@ void slash_sigint(struct slash *slash, int signum); */ void slash_on_execute_hook(const char *line); +/** + * @param line the slash line which was just executed + */ +void slash_on_execute_post_hook(const char *line, struct slash_command *command); /** * @brief Will be called before a file is executed by "run ", implement it to set environment variables for example. diff --git a/src/slash.c b/src/slash.c index 9804c1a..36406a6 100644 --- a/src/slash.c +++ b/src/slash.c @@ -391,6 +391,11 @@ __attribute__((weak)) void slash_on_execute_hook(const char *line) { (void)line; } +__attribute__((weak)) void slash_on_execute_post_hook(const char *line, struct slash_command *command) { + (void)line; + (void)command; +} + /* A default no-prompt implementation is provided as a __attribute__((weak)) */ __attribute__((weak)) int slash_prompt(struct slash *slash) { (void)slash; @@ -471,6 +476,8 @@ int slash_execute(struct slash *slash, char *line) if (ret == SLASH_EUSAGE) slash_command_usage(slash, command); + slash_on_execute_post_hook(line, command); + /* Yes, processed_cmd_line maybe NULL, but the free() man page says it's ok, so we save an "if" statement */ free(processed_cmd_line); From 68fef6ddeb917591ef0cc6b8d5c0ac1edc68d0ce Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 16 Jul 2025 10:02:31 +0200 Subject: [PATCH 51/52] Fix `watch` memory leak --- src/builtins.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/builtins.c b/src/builtins.c index 6bb5d2e..7c7459f 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -154,6 +154,7 @@ static int slash_builtin_watch(struct slash *slash) } + optparse_del(parser); return SLASH_SUCCESS; } slash_command_completer(watch, slash_builtin_watch, slash_watch_completer, "", "Repeat a command") \ No newline at end of file From 07d77676694fb67d56bdf81e6732d4d7d7e1ae25 Mon Sep 17 00:00:00 2001 From: kevin Date: Thu, 17 Jul 2025 15:59:00 +0200 Subject: [PATCH 52/52] Remove `rdp_opt_set()` and `rdp_opt_reset()` --- include/slash/dflopt.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/slash/dflopt.h b/include/slash/dflopt.h index 95e3f4c..73b6324 100644 --- a/include/slash/dflopt.h +++ b/include/slash/dflopt.h @@ -19,5 +19,3 @@ extern unsigned int rdp_dfl_ack_timeout; extern unsigned int rdp_dfl_ack_count; void rdp_opt_add(optparse_t * parser); -void rdp_opt_set(); -void rdp_opt_reset();