diff --git a/builtins.c b/builtins.c index c1759c1..18f665b 100644 --- a/builtins.c +++ b/builtins.c @@ -38,7 +38,7 @@ char *builtin_str[] = { "bookmark", "bookmarks", "goto", "unbookmark", "focus_timer", "weather", "grep", "grep-text", "ripgrep", "fzf", "clip", "echo", "theme", "loc", "git_status", - "gg", "ls", + "gg", "ls", "stats", }; // Array of function pointers to built-in command implementations @@ -53,6 +53,7 @@ int (*builtin_func[])(char **) = { &lsh_ripgrep, &lsh_fzf_native, &lsh_clip, &lsh_echo, &lsh_theme, &lsh_loc, &lsh_git_status, &lsh_gg, lsh_dir, + &lsh_stats, }; /** @@ -1055,3 +1056,70 @@ int lsh_gg(char **args) { return 1; } + +int lsh_stats(char **args) { + printf("Command Statistics\n"); + printf("=================\n\n"); + + if (frequency_count == 0) { + printf("No command history available\n"); + return 1; + } + + typedef struct { + char *command; + int count; + double score; + } CommandStat; + + CommandStat *stats = malloc(frequency_count * sizeof(CommandStat)); + if (!stats) + return 1; + + time_t current_time = time(NULL); + for (int i = 0; i < frequency_count; i++) { + stats[i].command = command_frequencies[i].command; + stats[i].count = command_frequencies[i].count; + + time_t most_recent = 0; + for (int j = history_size - 1; j >= 0; j--) { + if (strcmp(history_entries[j].command, stats[i].command) == 0) { + most_recent = history_entries[j].timestamp; + break; + } + } + + double hours_ago = (current_time - most_recent) / 3600.0; + double recency_weight = hours_ago > 0 ? 1.0 / (1.0 + hours_ago * 0.1) : 1.0; + + stats[i].score = stats[i].count * recency_weight; + + } + + for (int i = 0; i < frequency_count - 1; i++) { + for (int j = i + 1; j < frequency_count; j++) { + if (stats[j].score > stats[i].score) { + CommandStat temp = stats[i]; + stats[i] = stats[j]; + stats[j] = temp; + } + } + } + + printf("Top Commands (by frequency + recency):\n"); + printf("Rank Command Count Score\n"); + printf("---- -------------------- ----- -----\n"); + + int display_count = frequency_count < 10 ? frequency_count : 10; + for (int i = 0; i < display_count; i++) { + printf("%-4d %-20s %-5d %.2f\n", i + 1, stats[i].command, + stats[i].count, stats[i].score); + } + + printf("\nNext Command Prediction: \n"); + if (frequency_count > 0) { + printf("Most likely: %s (score: %.2f)\n", stats[0].command, stats[0].score); + } + free(stats); + return 1; +} diff --git a/builtins.h b/builtins.h index 23d01fb..8b2ed4e 100644 --- a/builtins.h +++ b/builtins.h @@ -1,5 +1,5 @@ /** - * builtins.h + * builtins.h * Declarations for all built-in shell commands */ @@ -77,6 +77,7 @@ int lsh_theme(char **args); int lsh_loc(char **args); int lsh_git_status(char **args); int lsh_gg(char **args); +int lsh_stats(char **args); // Add command to history void lsh_add_to_history(const char *command); @@ -84,8 +85,9 @@ void lsh_add_to_history(const char *command); // Get number of builtin commands int lsh_num_builtins(void); -// Expose the builtin command strings and function pointers -extern char *builtin_str[]; + + // Expose the builtin command strings and function pointers + extern char *builtin_str[]; extern int (*builtin_func[])(char **); /** diff --git a/builtins.o b/builtins.o index 146c6b2..2248deb 100644 Binary files a/builtins.o and b/builtins.o differ diff --git a/line_reader.c b/line_reader.c index 4aa9f22..83d5e12 100644 --- a/line_reader.c +++ b/line_reader.c @@ -461,7 +461,7 @@ void clear_menu() { // Clear a generous number of lines to ensure we get everything // Add extra buffer to handle spacing and indicators int lines_to_clear = max_menu_lines + 5; // Extra buffer for safety - + for (int i = 0; i < lines_to_clear; i++) { printf("\033[K"); // Clear entire line if (i < lines_to_clear - 1) { @@ -476,6 +476,7 @@ void clear_menu() { menu_start_line = 0; } } +// this is a test /** * Function to display menu of suggestions @@ -542,26 +543,27 @@ void display_menu(const char *prompt_buffer, const char *buffer, int position) { // Store number of menu lines displayed menu_start_line = show_count; - - // Calculate total lines used by the menu (including all spacing and indicators) + + // Calculate total lines used by the menu (including all spacing and + // indicators) int total_lines = 0; - + // Initial spacing (2 lines) total_lines += 2; - + // "Above" indicator and its spacing if (suggestion_count > max_display && start_idx > 0) { total_lines += 2; // indicator line + spacing line } - + // Menu items total_lines += show_count; - - // "Below" indicator and its spacing + + // "Below" indicator and its spacing if (suggestion_count > max_display && end_idx < suggestion_count) { total_lines += 2; // spacing line + indicator line } - + // Update max_menu_lines if this is larger if (total_lines > max_menu_lines) { max_menu_lines = total_lines; @@ -779,7 +781,7 @@ char *lsh_read_line(void) { // Update suggestions after backspace update_suggestions(buffer, position); - + // If we were in menu mode and still have suggestions, stay in menu mode if (was_in_menu_mode && has_suggestion && suggestion_count > 1) { menu_mode = 1; @@ -961,11 +963,13 @@ char *lsh_read_line(void) { } } } else if (c == KEY_SHIFT_TAB) { - // Shift+Tab: cycle backwards in menu mode, or enter menu mode and go to last item + // Shift+Tab: cycle backwards in menu mode, or enter menu mode and go to + // last item if (menu_mode) { // Already in menu mode: cycle to previous suggestion if (suggestion_count > 0) { - suggestion_index = (suggestion_index + suggestion_count - 1) % suggestion_count; + suggestion_index = + (suggestion_index + suggestion_count - 1) % suggestion_count; // Update full_suggestion with the newly selected suggestion if (prefix_start > 0) { @@ -980,13 +984,15 @@ char *lsh_read_line(void) { char *last_slash = strrchr(path_part, '/'); if (last_slash) { - // Get the directory part up to the last slash (including the slash) + // Get the directory part up to the last slash (including the + // slash) int dir_part_len = (last_slash - path_part) + 1; char dir_part[LSH_RL_BUFSIZE] = ""; strncpy(dir_part, path_part, dir_part_len); dir_part[dir_part_len] = '\0'; - // Construct the complete path by keeping the command and directory part + // Construct the complete path by keeping the command and + // directory part strncpy(full_suggestion, buffer, prefix_start); full_suggestion[prefix_start] = '\0'; strncat(full_suggestion, dir_part, @@ -1155,16 +1161,19 @@ char *lsh_read_line(void) { strncpy(dir_part, path_part, dir_part_len); dir_part[dir_part_len] = '\0'; - // Construct the complete path by keeping the command and directory part + // Construct the complete path by keeping the command and directory + // part strncpy(temp, buffer, prefix_start); temp[prefix_start] = '\0'; strncat(temp, dir_part, LSH_RL_BUFSIZE - strlen(temp) - 1); - strncat(temp, suggestions[suggestion_index], LSH_RL_BUFSIZE - strlen(temp) - 1); + strncat(temp, suggestions[suggestion_index], + LSH_RL_BUFSIZE - strlen(temp) - 1); } else { // No slash in current argument, normal behavior strncpy(temp, buffer, prefix_start); temp[prefix_start] = '\0'; - strncat(temp, suggestions[suggestion_index], LSH_RL_BUFSIZE - strlen(temp) - 1); + strncat(temp, suggestions[suggestion_index], + LSH_RL_BUFSIZE - strlen(temp) - 1); } strncpy(buffer, temp, bufsize - 1); @@ -1188,7 +1197,8 @@ char *lsh_read_line(void) { // Clear suggestions after accepting non-directory if (suggestions) { for (int i = 0; i < suggestion_count; i++) { - if (suggestions[i]) free(suggestions[i]); + if (suggestions[i]) + free(suggestions[i]); } free(suggestions); suggestions = NULL; @@ -1261,7 +1271,7 @@ char *lsh_read_line(void) { // Update with the new character and show suggestions update_suggestions(buffer, position); - + // If we were in menu mode and still have suggestions, stay in menu mode if (was_in_menu_mode && has_suggestion && suggestion_count > 1) { menu_mode = 1; diff --git a/line_reader.o b/line_reader.o index 1dd6f04..06ef888 100644 Binary files a/line_reader.o and b/line_reader.o differ diff --git a/persistent_history.c b/persistent_history.c index fda5608..240ddd8 100644 --- a/persistent_history.c +++ b/persistent_history.c @@ -12,16 +12,17 @@ #include #include #include +#include // Global variables for persistent history -static PersistentHistoryEntry *history_entries = NULL; -static int history_size = 0; +PersistentHistoryEntry *history_entries = NULL; +int history_size = 0; static int history_capacity = 0; static int history_position = 0; // Global variables for frequency tracking -static CommandFrequency *command_frequencies = NULL; -static int frequency_count = 0; +CommandFrequency *command_frequencies = NULL; +int frequency_count = 0; static int frequency_capacity = 0; // Paths for history and frequency files diff --git a/persistent_history.h b/persistent_history.h index 1043095..2c1b873 100644 --- a/persistent_history.h +++ b/persistent_history.h @@ -82,4 +82,10 @@ char **get_matching_history_entries(const char *prefix); // Free matching entries void free_matching_entries(char **entries); +// Expose global variables for stats command +extern CommandFrequency *command_frequencies; +extern int frequency_count; +extern PersistentHistoryEntry *history_entries; +extern int history_size; + #endif // PERSISTENT_HISTORY_H \ No newline at end of file diff --git a/persistent_history.o b/persistent_history.o index 0557738..a1e16fb 100644 Binary files a/persistent_history.o and b/persistent_history.o differ diff --git a/shell b/shell index 1176ec8..b136d6f 100755 Binary files a/shell and b/shell differ