diff --git a/include/history/persistent_history.h b/include/history/persistent_history.h index 2c1b873..00d1fd0 100644 --- a/include/history/persistent_history.h +++ b/include/history/persistent_history.h @@ -82,10 +82,13 @@ char **get_matching_history_entries(const char *prefix); // Free matching entries void free_matching_entries(char **entries); +// get the most recent command that starts with the given prefix +char *get_most_recent_history_match(const char *prefix); + // 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 +#endif // PERSISTENT_HISTORY_H diff --git a/shell b/shell index 3380097..6ae20ee 100755 Binary files a/shell and b/shell differ diff --git a/src/history/persistent_history.c b/src/history/persistent_history.c index 240ddd8..05c94b0 100644 --- a/src/history/persistent_history.c +++ b/src/history/persistent_history.c @@ -4,15 +4,15 @@ */ #include "persistent_history.h" -#include +#include #include #include #include #include #include +#include #include #include -#include // Global variables for persistent history PersistentHistoryEntry *history_entries = NULL; @@ -33,471 +33,519 @@ static char frequency_file_path[PATH_MAX]; * Initialize the persistent history system */ void init_persistent_history(void) { - // Allocate initial history capacity - history_capacity = PERSISTENT_HISTORY_SIZE; - history_entries = (PersistentHistoryEntry *)malloc(history_capacity * sizeof(PersistentHistoryEntry)); - if (!history_entries) { - fprintf(stderr, "Failed to allocate memory for history entries\n"); - return; + // Allocate initial history capacity + history_capacity = PERSISTENT_HISTORY_SIZE; + history_entries = (PersistentHistoryEntry *)malloc( + history_capacity * sizeof(PersistentHistoryEntry)); + if (!history_entries) { + fprintf(stderr, "Failed to allocate memory for history entries\n"); + return; + } + + // Allocate initial frequency capacity + frequency_capacity = 100; + command_frequencies = + (CommandFrequency *)malloc(frequency_capacity * sizeof(CommandFrequency)); + if (!command_frequencies) { + fprintf(stderr, "Failed to allocate memory for command frequencies\n"); + free(history_entries); + history_entries = NULL; + return; + } + + // Initialize history and frequency counters + history_size = 0; + history_position = -1; + frequency_count = 0; + + // Set up file paths + char *home_dir = getenv("HOME"); + if (home_dir) { + // Create .lsh directory if it doesn't exist + char lsh_dir[PATH_MAX]; + snprintf(lsh_dir, sizeof(lsh_dir), "%s/.lsh", home_dir); + + struct stat st = {0}; + if (stat(lsh_dir, &st) == -1) { + mkdir(lsh_dir, 0700); } - // Allocate initial frequency capacity - frequency_capacity = 100; - command_frequencies = (CommandFrequency *)malloc(frequency_capacity * sizeof(CommandFrequency)); - if (!command_frequencies) { - fprintf(stderr, "Failed to allocate memory for command frequencies\n"); - free(history_entries); - history_entries = NULL; - return; + snprintf(history_file_path, sizeof(history_file_path), "%s/.lsh/history", + home_dir); + snprintf(frequency_file_path, sizeof(frequency_file_path), + "%s/.lsh/frequency", home_dir); + } else { + // Fallback to current directory + strcpy(history_file_path, ".lsh_history"); + strcpy(frequency_file_path, ".lsh_frequency"); + } + + // Load history and frequency data + load_history_from_file(); + load_frequencies_from_file(); +} + +/** + * Get the most recent command that starts with the given prefix + * returns NULL if no match found + */ +char *get_most_recent_history_match(const char *prefix) { + if (!prefix || !history_entries || strlen(prefix) == 0) { + return NULL; + } + + size_t prefix_len = strlen(prefix); + + // Search from most recent to oldest (reverse order) + // Find the first (most recent) command that starts with the exact prefix + for (int i = history_size - 1; i >= 0; i--) { + if (history_entries[i].command && + strncasecmp(history_entries[i].command, prefix, prefix_len) == 0 && + strlen(history_entries[i].command) > prefix_len) { + + // Make sure it's an exact prefix match (not a substring) + // For "git s", we want "git status", not "git branch" where "git s" is not a real prefix + const char *cmd = history_entries[i].command; + + // If prefix ends with a space, any command starting with prefix is good + if (prefix_len > 0 && prefix[prefix_len - 1] == ' ') { + return strdup(cmd); + } + + // If prefix doesn't end with space, make sure the next char in command + // is either a space or continues the word we're typing + if (cmd[prefix_len] == ' ' || isalnum(cmd[prefix_len]) || cmd[prefix_len] == '-') { + return strdup(cmd); + } } - - // Initialize history and frequency counters - history_size = 0; - history_position = -1; - frequency_count = 0; - - // Set up file paths - char *home_dir = getenv("HOME"); - if (home_dir) { - // Create .lsh directory if it doesn't exist - char lsh_dir[PATH_MAX]; - snprintf(lsh_dir, sizeof(lsh_dir), "%s/.lsh", home_dir); - - struct stat st = {0}; - if (stat(lsh_dir, &st) == -1) { - mkdir(lsh_dir, 0700); - } - - snprintf(history_file_path, sizeof(history_file_path), "%s/.lsh/history", home_dir); - snprintf(frequency_file_path, sizeof(frequency_file_path), "%s/.lsh/frequency", home_dir); - } else { - // Fallback to current directory - strcpy(history_file_path, ".lsh_history"); - strcpy(frequency_file_path, ".lsh_frequency"); - } - - // Load history and frequency data - load_history_from_file(); - load_frequencies_from_file(); + } + return NULL; } /** * Clean up persistent history resources */ void cleanup_persistent_history(void) { - if (history_entries) { - for (int i = 0; i < history_size; i++) { - free(history_entries[i].command); - } - free(history_entries); - history_entries = NULL; + if (history_entries) { + for (int i = 0; i < history_size; i++) { + free(history_entries[i].command); } + free(history_entries); + history_entries = NULL; + } - if (command_frequencies) { - for (int i = 0; i < frequency_count; i++) { - free(command_frequencies[i].command); - } - free(command_frequencies); - command_frequencies = NULL; + if (command_frequencies) { + for (int i = 0; i < frequency_count; i++) { + free(command_frequencies[i].command); } + free(command_frequencies); + command_frequencies = NULL; + } - history_size = 0; - history_capacity = 0; - frequency_count = 0; - frequency_capacity = 0; + history_size = 0; + history_capacity = 0; + frequency_count = 0; + frequency_capacity = 0; } /** * Shutdown persistent history */ void shutdown_persistent_history(void) { - save_history_to_file(); - save_frequencies_to_file(); - cleanup_persistent_history(); + save_history_to_file(); + save_frequencies_to_file(); + cleanup_persistent_history(); } /** * Add a command to the persistent history */ void add_to_history(const char *command) { - if (!command || !*command || !history_entries) { - return; // Skip empty commands - } + if (!command || !*command || !history_entries) { + return; // Skip empty commands + } - // Check for duplicates (don't add the same command twice in a row) - if (history_size > 0 && strcmp(history_entries[history_size - 1].command, command) == 0) { - return; - } + // Check for duplicates (don't add the same command twice in a row) + if (history_size > 0 && + strcmp(history_entries[history_size - 1].command, command) == 0) { + return; + } - // Update command frequency - update_command_frequency(command); + // Update command frequency + update_command_frequency(command); - // Add to history - if (history_size >= history_capacity) { - // History is full, shift entries - free(history_entries[0].command); - memmove(&history_entries[0], &history_entries[1], - (history_capacity - 1) * sizeof(PersistentHistoryEntry)); - history_size = history_capacity - 1; - } + // Add to history + if (history_size >= history_capacity) { + // History is full, shift entries + free(history_entries[0].command); + memmove(&history_entries[0], &history_entries[1], + (history_capacity - 1) * sizeof(PersistentHistoryEntry)); + history_size = history_capacity - 1; + } - // Add new entry - history_entries[history_size].command = strdup(command); - history_entries[history_size].timestamp = time(NULL); - history_size++; + // Add new entry + history_entries[history_size].command = strdup(command); + history_entries[history_size].timestamp = time(NULL); + history_size++; - // Reset history position for navigation - history_position = -1; + // Reset history position for navigation + history_position = -1; } /** * Update command frequency statistics */ void update_command_frequency(const char *command) { - if (!command || !*command || !command_frequencies) { - return; - } + if (!command || !*command || !command_frequencies) { + return; + } - // Check if command exists in frequencies - for (int i = 0; i < frequency_count; i++) { - if (strcmp(command_frequencies[i].command, command) == 0) { - // Update existing entry - command_frequencies[i].count++; - return; - } + // Check if command exists in frequencies + for (int i = 0; i < frequency_count; i++) { + if (strcmp(command_frequencies[i].command, command) == 0) { + // Update existing entry + command_frequencies[i].count++; + return; } + } - // Add new frequency entry - if (frequency_count >= frequency_capacity) { - // Expand capacity - frequency_capacity *= 2; - CommandFrequency *new_freq = (CommandFrequency *)realloc( - command_frequencies, frequency_capacity * sizeof(CommandFrequency)); - if (!new_freq) { - fprintf(stderr, "Failed to allocate memory for command frequencies\n"); - return; - } - command_frequencies = new_freq; + // Add new frequency entry + if (frequency_count >= frequency_capacity) { + // Expand capacity + frequency_capacity *= 2; + CommandFrequency *new_freq = (CommandFrequency *)realloc( + command_frequencies, frequency_capacity * sizeof(CommandFrequency)); + if (!new_freq) { + fprintf(stderr, "Failed to allocate memory for command frequencies\n"); + return; } + command_frequencies = new_freq; + } - // Add new entry - command_frequencies[frequency_count].command = strdup(command); - command_frequencies[frequency_count].count = 1; - frequency_count++; + // Add new entry + command_frequencies[frequency_count].command = strdup(command); + command_frequencies[frequency_count].count = 1; + frequency_count++; } /** * Save history to file */ void save_history_to_file(void) { - if (!history_entries || history_size == 0) { - return; - } + if (!history_entries || history_size == 0) { + return; + } - FILE *fp = fopen(history_file_path, "w"); - if (!fp) { - fprintf(stderr, "Failed to open history file for writing: %s\n", history_file_path); - return; - } + FILE *fp = fopen(history_file_path, "w"); + if (!fp) { + fprintf(stderr, "Failed to open history file for writing: %s\n", + history_file_path); + return; + } - // Write version and metadata - fprintf(fp, "# LSH Persistent History\n"); - fprintf(fp, "# Version: 1.0\n"); - fprintf(fp, "# Format: timestamp command\n\n"); + // Write version and metadata + fprintf(fp, "# LSH Persistent History\n"); + fprintf(fp, "# Version: 1.0\n"); + fprintf(fp, "# Format: timestamp command\n\n"); - // Write entries - for (int i = 0; i < history_size; i++) { - fprintf(fp, "%ld %s\n", (long)history_entries[i].timestamp, history_entries[i].command); - } + // Write entries + for (int i = 0; i < history_size; i++) { + fprintf(fp, "%ld %s\n", (long)history_entries[i].timestamp, + history_entries[i].command); + } - fclose(fp); + fclose(fp); } /** * Load history from file */ void load_history_from_file(void) { - FILE *fp = fopen(history_file_path, "r"); - if (!fp) { - return; // File doesn't exist or can't be opened + FILE *fp = fopen(history_file_path, "r"); + if (!fp) { + return; // File doesn't exist or can't be opened + } + + char line[1024]; + time_t timestamp; + char command[900]; // Allow space for timestamp and whitespace + + // Skip header lines + while (fgets(line, sizeof(line), fp) && line[0] == '#') { + // Skip header line + } + + // Clear history + for (int i = 0; i < history_size; i++) { + free(history_entries[i].command); + } + history_size = 0; + + // Read entries + while (fgets(line, sizeof(line), fp)) { + // Parse line: timestamp command + if (sscanf(line, "%ld %[^\n]", ×tamp, command) == 2) { + if (history_size < history_capacity) { + history_entries[history_size].command = strdup(command); + history_entries[history_size].timestamp = timestamp; + history_size++; + } } + } - char line[1024]; - time_t timestamp; - char command[900]; // Allow space for timestamp and whitespace - - // Skip header lines - while (fgets(line, sizeof(line), fp) && line[0] == '#') { - // Skip header line - } - - // Clear history - for (int i = 0; i < history_size; i++) { - free(history_entries[i].command); - } - history_size = 0; - - // Read entries - while (fgets(line, sizeof(line), fp)) { - // Parse line: timestamp command - if (sscanf(line, "%ld %[^\n]", ×tamp, command) == 2) { - if (history_size < history_capacity) { - history_entries[history_size].command = strdup(command); - history_entries[history_size].timestamp = timestamp; - history_size++; - } - } - } - - fclose(fp); + fclose(fp); } /** * Save command frequencies to file */ void save_frequencies_to_file(void) { - if (!command_frequencies || frequency_count == 0) { - return; - } + if (!command_frequencies || frequency_count == 0) { + return; + } - FILE *fp = fopen(frequency_file_path, "w"); - if (!fp) { - fprintf(stderr, "Failed to open frequency file for writing: %s\n", frequency_file_path); - return; - } + FILE *fp = fopen(frequency_file_path, "w"); + if (!fp) { + fprintf(stderr, "Failed to open frequency file for writing: %s\n", + frequency_file_path); + return; + } - // Write version and metadata - fprintf(fp, "# LSH Command Frequencies\n"); - fprintf(fp, "# Version: 1.0\n"); - fprintf(fp, "# Format: count command\n\n"); + // Write version and metadata + fprintf(fp, "# LSH Command Frequencies\n"); + fprintf(fp, "# Version: 1.0\n"); + fprintf(fp, "# Format: count command\n\n"); - // Write entries - for (int i = 0; i < frequency_count; i++) { - fprintf(fp, "%d %s\n", command_frequencies[i].count, command_frequencies[i].command); - } + // Write entries + for (int i = 0; i < frequency_count; i++) { + fprintf(fp, "%d %s\n", command_frequencies[i].count, + command_frequencies[i].command); + } - fclose(fp); + fclose(fp); } /** * Load command frequencies from file */ void load_frequencies_from_file(void) { - FILE *fp = fopen(frequency_file_path, "r"); - if (!fp) { - return; // File doesn't exist or can't be opened + FILE *fp = fopen(frequency_file_path, "r"); + if (!fp) { + return; // File doesn't exist or can't be opened + } + + char line[1024]; + int count; + char command[900]; // Allow space for count and whitespace + + // Skip header lines + while (fgets(line, sizeof(line), fp) && line[0] == '#') { + // Skip header line + } + + // Clear frequencies + for (int i = 0; i < frequency_count; i++) { + free(command_frequencies[i].command); + } + frequency_count = 0; + + // Read entries + while (fgets(line, sizeof(line), fp)) { + // Parse line: count command + if (sscanf(line, "%d %[^\n]", &count, command) == 2) { + if (frequency_count < frequency_capacity) { + command_frequencies[frequency_count].command = strdup(command); + command_frequencies[frequency_count].count = count; + frequency_count++; + } } + } - char line[1024]; - int count; - char command[900]; // Allow space for count and whitespace - - // Skip header lines - while (fgets(line, sizeof(line), fp) && line[0] == '#') { - // Skip header line - } - - // Clear frequencies - for (int i = 0; i < frequency_count; i++) { - free(command_frequencies[i].command); - } - frequency_count = 0; - - // Read entries - while (fgets(line, sizeof(line), fp)) { - // Parse line: count command - if (sscanf(line, "%d %[^\n]", &count, command) == 2) { - if (frequency_count < frequency_capacity) { - command_frequencies[frequency_count].command = strdup(command); - command_frequencies[frequency_count].count = count; - frequency_count++; - } - } - } - - fclose(fp); + fclose(fp); } /** * Get the history entry at the specified index */ PersistentHistoryEntry *get_history_entry(int index) { - if (index < 0 || index >= history_size) { - return NULL; - } - return &history_entries[index]; + if (index < 0 || index >= history_size) { + return NULL; + } + return &history_entries[index]; } /** * Get the total number of history entries */ -int get_history_count(void) { - return history_size; -} +int get_history_count(void) { return history_size; } /** * Find the best matching command based on frequency */ char *find_best_frequency_match(const char *prefix) { - if (!prefix || !*prefix || !command_frequencies) { - return NULL; - } - - int best_freq = 0; - int best_index = -1; - - // Find the most frequent command that starts with the prefix - for (int i = 0; i < frequency_count; i++) { - if (strncasecmp(command_frequencies[i].command, prefix, strlen(prefix)) == 0) { - if (command_frequencies[i].count > best_freq) { - best_freq = command_frequencies[i].count; - best_index = i; - } - } - } - - if (best_index >= 0) { - return strdup(command_frequencies[best_index].command); - } - + if (!prefix || !*prefix || !command_frequencies) { return NULL; + } + + int best_freq = 0; + int best_index = -1; + + // Find the most frequent command that starts with the prefix + for (int i = 0; i < frequency_count; i++) { + if (strncasecmp(command_frequencies[i].command, prefix, strlen(prefix)) == + 0) { + if (command_frequencies[i].count > best_freq) { + best_freq = command_frequencies[i].count; + best_index = i; + } + } + } + + if (best_index >= 0) { + return strdup(command_frequencies[best_index].command); + } + + return NULL; } /** * Debug function to print frequency data */ void debug_print_frequencies(void) { - printf("Command Frequencies:\n"); - for (int i = 0; i < frequency_count; i++) { - printf("%3d: %s (%d)\n", i + 1, command_frequencies[i].command, command_frequencies[i].count); - } + printf("Command Frequencies:\n"); + for (int i = 0; i < frequency_count; i++) { + printf("%3d: %s (%d)\n", i + 1, command_frequencies[i].command, + command_frequencies[i].count); + } } /** * Case-insensitive substring search */ char *_stristr(const char *haystack, const char *needle) { - if (!haystack || !needle) - return NULL; + if (!haystack || !needle) + return NULL; - char *h = strdup(haystack); - char *n = strdup(needle); - - if (!h || !n) { - if (h) free(h); - if (n) free(n); - return NULL; - } + char *h = strdup(haystack); + char *n = strdup(needle); - // Convert both strings to lowercase - for (char *p = h; *p; p++) - *p = tolower(*p); - for (char *p = n; *p; p++) - *p = tolower(*p); + if (!h || !n) { + if (h) + free(h); + if (n) + free(n); + return NULL; + } - char *result = strstr(h, n); - char *original = NULL; + // Convert both strings to lowercase + for (char *p = h; *p; p++) + *p = tolower(*p); + for (char *p = n; *p; p++) + *p = tolower(*p); - // If found, map back to original string position - if (result) { - original = (char *)haystack + (result - h); - } + char *result = strstr(h, n); + char *original = NULL; - free(h); - free(n); - return original; + // If found, map back to original string position + if (result) { + original = (char *)haystack + (result - h); + } + + free(h); + free(n); + return original; } /** * Get previous history entry for up-arrow key navigation */ char *get_previous_history_entry(int *position) { - if (!history_entries || history_size == 0) { - return NULL; - } + if (!history_entries || history_size == 0) { + return NULL; + } - if (*position < 0) { - // First time accessing history, start from the most recent entry - *position = history_size - 1; - } else if (*position > 0) { - // Move to previous entry - (*position)--; - } else { - // Already at the oldest entry, can't go back further - return history_entries[0].command; - } + if (*position < 0) { + // First time accessing history, start from the most recent entry + *position = history_size - 1; + } else if (*position > 0) { + // Move to previous entry + (*position)--; + } else { + // Already at the oldest entry, can't go back further + return history_entries[0].command; + } - return history_entries[*position].command; + return history_entries[*position].command; } /** * Get next history entry for down-arrow key navigation */ char *get_next_history_entry(int *position) { - if (!history_entries || history_size == 0 || *position < 0) { - return NULL; - } + if (!history_entries || history_size == 0 || *position < 0) { + return NULL; + } - if (*position < history_size - 1) { - // Move to next (more recent) entry - (*position)++; - return history_entries[*position].command; - } else { - // We've reached the end of history, return NULL to indicate - // user should get an empty prompt - *position = -1; - return NULL; - } + if (*position < history_size - 1) { + // Move to next (more recent) entry + (*position)++; + return history_entries[*position].command; + } else { + // We've reached the end of history, return NULL to indicate + // user should get an empty prompt + *position = -1; + return NULL; + } } /** * Get matching history entries for tab completion */ char **get_matching_history_entries(const char *prefix) { - if (!prefix || !history_entries) { - return NULL; - } + if (!prefix || !history_entries) { + return NULL; + } - // Count matching entries - int matches = 0; - for (int i = 0; i < history_size; i++) { - if (strncasecmp(history_entries[i].command, prefix, strlen(prefix)) == 0) { - matches++; - } + // Count matching entries + int matches = 0; + for (int i = 0; i < history_size; i++) { + if (strncasecmp(history_entries[i].command, prefix, strlen(prefix)) == 0) { + matches++; } + } - if (matches == 0) { - return NULL; + if (matches == 0) { + return NULL; + } + + // Allocate result array + char **result = (char **)malloc((matches + 1) * sizeof(char *)); + if (!result) { + return NULL; + } + + // Fill the array with matching commands + int count = 0; + for (int i = 0; i < history_size && count < matches; i++) { + if (strncasecmp(history_entries[i].command, prefix, strlen(prefix)) == 0) { + result[count++] = strdup(history_entries[i].command); } + } + result[count] = NULL; - // Allocate result array - char **result = (char **)malloc((matches + 1) * sizeof(char *)); - if (!result) { - return NULL; - } - - // Fill the array with matching commands - int count = 0; - for (int i = 0; i < history_size && count < matches; i++) { - if (strncasecmp(history_entries[i].command, prefix, strlen(prefix)) == 0) { - result[count++] = strdup(history_entries[i].command); - } - } - result[count] = NULL; - - return result; + return result; } /** * Free matching entries array */ void free_matching_entries(char **entries) { - if (!entries) { - return; - } + if (!entries) { + return; + } - for (int i = 0; entries[i] != NULL; i++) { - free(entries[i]); - } - free(entries); -} \ No newline at end of file + for (int i = 0; entries[i] != NULL; i++) { + free(entries[i]); + } + free(entries); +} diff --git a/src/input/line_reader.c b/src/input/line_reader.c index 6fd628a..7c9d07b 100644 --- a/src/input/line_reader.c +++ b/src/input/line_reader.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -37,6 +39,10 @@ static int max_menu_lines = 0; static int cycling_mode = 0; static char cycle_prefix[LSH_RL_BUFSIZE] = {0}; +// history suggestion state +static char *history_suggestion = NULL; +static int has_history_suggestion = 0; + // Define colors for suggestions #define SUGGESTION_COLOR "\033[2;37m" // Dim white color for suggestions #define HIGHLIGHT_COLOR "\033[7;36m" // Highlighted background for selected item @@ -270,7 +276,14 @@ void update_suggestions(const char *buffer, int position) { suggestion_count = 0; } + // Free previous history suggestion + if (history_suggestion) { + free(history_suggestion); + history_suggestion = NULL; + } + has_suggestion = 0; + has_history_suggestion = 0; // Parse command line prefix_start = 0; @@ -297,6 +310,19 @@ void update_suggestions(const char *buffer, int position) { current_token[position - prefix_start] = '\0'; } + // get history suggestion for the entire command line typed so far + if (position > 0) { + // Use the entire buffer typed so far as the search prefix + char search_prefix[LSH_RL_BUFSIZE]; + strncpy(search_prefix, buffer, position); + search_prefix[position] = '\0'; + + history_suggestion = get_most_recent_history_match(search_prefix); + if (history_suggestion) { + has_history_suggestion = 1; + } + } + // Get suggestions from the tab completion engine SuggestionList *suggestion_list = get_suggestion_list(buffer, cycling_mode ? cycle_prefix : current_token); @@ -351,7 +377,11 @@ void update_suggestions(const char *buffer, int position) { */ void display_inline_suggestion(const char *prompt_buffer, const char *buffer, int position) { - if (has_suggestion && suggestion_count > 0) { + // Prioritize history suggestions when available, tab suggestions as fallback + int show_history = has_history_suggestion; + int show_tab = has_suggestion && suggestion_count > 0 && !show_history; + + if (show_history || show_tab) { // Determine what part should be in normal text and what part in suggestion // color char current_text[LSH_RL_BUFSIZE] = ""; @@ -360,56 +390,62 @@ void display_inline_suggestion(const char *prompt_buffer, const char *buffer, strncpy(current_text, buffer, position); current_text[position] = '\0'; - // Extract just the suggestion part (after what user typed) - int current_len = 0; - - if (prefix_start > 0) { - char current_arg[LSH_RL_BUFSIZE] = ""; - strncpy(current_arg, &buffer[prefix_start], position - prefix_start); - current_arg[position - prefix_start] = '\0'; - current_len = strlen(current_arg); - - if (current_len > 0) { - // Handle nested paths with subdirectories - char *last_slash = strrchr(current_arg, '/'); - if (last_slash) { - // We're in a subdirectory path - char *filename_part = last_slash + 1; - int filename_len = strlen(filename_part); - - // When dealing with a path like "cd shelltest/ha", we need to match - // "ha" against the entry in the suggestions array (which would be - // e.g. "halla") - - // Check if the filename part is a prefix of the suggestions entry - if (strncasecmp(suggestions[suggestion_index], filename_part, - filename_len) == 0) { - // Copy the suggestion text starting from where the user input ends - strncpy(suggestion_text, - suggestions[suggestion_index] + filename_len, - sizeof(suggestion_text) - 1); - } - } else { - // Regular path (no subdirectories) - // Find where the current argument ends in the suggestion - char *suggestion_part = suggestions[suggestion_index]; - if (strncasecmp(suggestion_part, current_arg, current_len) == 0) { - strncpy(suggestion_text, suggestion_part + current_len, - sizeof(suggestion_text) - 1); - } - } - } else { - // Use the entire suggestion - strncpy(suggestion_text, suggestions[suggestion_index], + if (show_history) { + // Show history suggestion + if (strlen(buffer) < strlen(history_suggestion) && + strncasecmp(history_suggestion, buffer, strlen(buffer)) == 0) { + strncpy(suggestion_text, history_suggestion + strlen(buffer), sizeof(suggestion_text) - 1); } - } else { - // Completing a command - if (strlen(buffer) <= strlen(suggestions[suggestion_index]) && - strncasecmp(suggestions[suggestion_index], buffer, strlen(buffer)) == - 0) { - strncpy(suggestion_text, suggestions[suggestion_index] + strlen(buffer), - sizeof(suggestion_text) - 1); + } else if (show_tab) { + // Show tab completion suggestion + // Extract just the suggestion part (after what user typed) + int current_len = 0; + + if (prefix_start > 0) { + char current_arg[LSH_RL_BUFSIZE] = ""; + strncpy(current_arg, &buffer[prefix_start], position - prefix_start); + current_arg[position - prefix_start] = '\0'; + current_len = strlen(current_arg); + + if (current_len > 0) { + // Handle nested paths with subdirectories + char *last_slash = strrchr(current_arg, '/'); + if (last_slash) { + // We're in a subdirectory path + char *filename_part = last_slash + 1; + int filename_len = strlen(filename_part); + + // Check if the filename part is a prefix of the suggestions entry + if (strncasecmp(suggestions[suggestion_index], filename_part, + filename_len) == 0) { + // Copy the suggestion text starting from where the user input ends + strncpy(suggestion_text, + suggestions[suggestion_index] + filename_len, + sizeof(suggestion_text) - 1); + } + } else { + // Regular path (no subdirectories) + // Find where the current argument ends in the suggestion + char *suggestion_part = suggestions[suggestion_index]; + if (strncasecmp(suggestion_part, current_arg, current_len) == 0) { + strncpy(suggestion_text, suggestion_part + current_len, + sizeof(suggestion_text) - 1); + } + } + } else { + // Use the entire suggestion + strncpy(suggestion_text, suggestions[suggestion_index], + sizeof(suggestion_text) - 1); + } + } else { + // Completing a command + if (strlen(buffer) <= strlen(suggestions[suggestion_index]) && + strncasecmp(suggestions[suggestion_index], buffer, strlen(buffer)) == + 0) { + strncpy(suggestion_text, suggestions[suggestion_index] + strlen(buffer), + sizeof(suggestion_text) - 1); + } } } @@ -1142,7 +1178,27 @@ char *lsh_read_line(void) { } } else if (c == KEY_RIGHT && !menu_mode) { // Accept inline suggestion with right arrow - if (has_suggestion && suggestion_count > 0) { + if (has_history_suggestion) { + // Accept history suggestion + strncpy(buffer, history_suggestion, bufsize - 1); + buffer[bufsize - 1] = '\0'; + position = strlen(buffer); + + // Redraw the line with accepted suggestion + printf("\r\033[K%s%s", prompt_buffer, buffer); + fflush(stdout); + + // Clear history suggestion after accepting + if (history_suggestion) { + free(history_suggestion); + history_suggestion = NULL; + } + has_history_suggestion = 0; + + // Update suggestions after accepting history + update_suggestions(buffer, position); + display_inline_suggestion(prompt_buffer, buffer, position); + } else if (has_suggestion && suggestion_count > 0) { // Update buffer with the inline suggestion if (prefix_start > 0) { // We're completing an argument @@ -1301,7 +1357,14 @@ char *lsh_read_line(void) { suggestions = NULL; suggestion_count = 0; } + + if (history_suggestion) { + free(history_suggestion); + history_suggestion = NULL; + } + has_suggestion = 0; + has_history_suggestion = 0; return buffer; }