added history suggestions (finnaly)

This commit is contained in:
2025-09-23 23:39:13 +02:00
parent 5b55672a08
commit 29fa585018
4 changed files with 491 additions and 377 deletions

View File

@@ -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
#endif // PERSISTENT_HISTORY_H

BIN
shell

Binary file not shown.

View File

@@ -4,15 +4,15 @@
*/
#include "persistent_history.h"
#include <sys/stat.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
// 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]", &timestamp, 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]", &timestamp, 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);
}
for (int i = 0; entries[i] != NULL; i++) {
free(entries[i]);
}
free(entries);
}

View File

@@ -16,6 +16,8 @@
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <strings.h>
#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
@@ -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;
}