added history suggestions (finnaly)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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]", ×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);
|
||||
}
|
||||
for (int i = 0; entries[i] != NULL; i++) {
|
||||
free(entries[i]);
|
||||
}
|
||||
free(entries);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user