diff --git a/README.md b/README.md index d68580c..c9c473e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,43 @@ hstr ==== -BASH History Suggest Box +BASH History Suggest Box. + +DESCRIPTION +----------- +A command line utility that brings improved BASH command completion +from the history. It aims to make completion easier to use and faster +than Ctrl-R. + + +DOWNLOAD +-------- +https://bitbucket.org/dvorka/hstr/downloads + + +INSTALLATION +------------ +* add hh to $PATH +* you may also bind hh to a BASH key (e.g. F12) by adding bind to .bashrc: + bind '"\e[24~":"hh"' + To determine the character sequence emitted by a pressed key in terminal, + type CTRL-v and then press the key. For example, F12 gives "^[[24~" (no quotes). + Replace the "^[" with \e. To clear the line first, add ā€œ\C-k \C-uā€ in front of + the actual command to clear the line first. + bind '"\e[24~":"\C-kX\C-uhh\n"' + Binding using Ctrl-F12: + bind '"\e[24;5~":"\C-kX\C-uhh\n"' + Check: + bind -S + + +BUGS +---- +https://bitbucket.org/dvorka/hstr/issues/new + + +AUTHOR +------ +martin.dvorak@mindforger.com + +- eof - diff --git a/src/hashset.c b/src/hashset.c new file mode 100644 index 0000000..67054b2 --- /dev/null +++ b/src/hashset.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include "include/hashset.h" + +unsigned int hash( const char *str ) { + int i; + unsigned int result = 0; + + for( i = 0; str[ i ] != '\0'; i++ ) + result = result * 31 + str[ i ]; + + return result % TABLE_SIZE; +} + +void hs_init( HashSet * hs ) { + int i; + hs->currentSize = 0; + for( i = 0; i < TABLE_SIZE; i++ ) + hs->lists[ i ] = NULL; +} + +int hs_contains( const HashSet * hs, const char *key ) { + int listNum = hash( key ); + struct HashNode *ptr = hs->lists[ listNum ]; + + while( ptr != NULL && strcmp( ptr->key, key ) != 0 ) + ptr = ptr->next; + + return ptr != NULL; +} + +int hs_add( HashSet * hs, const char *key ) { + struct HashNode *newNode; + int listNum; + + if( hs_contains( hs, key ) ) + return 0; + + listNum = hash( key ); + + + newNode = (struct HashNode *) malloc( sizeof ( struct HashNode ) ); + if( newNode == NULL ) { + fprintf( stderr, "Error allocating node" ); + return 0; + } + + newNode->key = strdup( key ); + newNode->next = hs->lists[ listNum ]; + hs->lists[ listNum ] = newNode; + hs->currentSize++; + + return 1; +} + +int hs_remove( HashSet * hs, const char *key ) { + struct HashNode *curr; + struct HashNode *prev = NULL; + int listNum; + + if( !hs_contains( hs, key ) ) + return 0; + + listNum = hash( key ); + curr = hs->lists[ listNum ]; + while( strcmp( curr->key, key ) != 0 ) { + prev = curr; + curr = curr->next; + } + + if( prev == NULL ) // first item + hs->lists[ listNum ] = curr->next; + else // middle of list + prev->next = curr->next; + + // cleanup node curr + free( curr->key ); + free( curr ); + + hs->currentSize--; + return 1; +} + +int hs_size(const HashSet * hs) { + return hs->currentSize; +} + +void hs_print( const HashSet * hs ) { + int i; + struct HashNode *ptr; + + for( i = 0; i < TABLE_SIZE; i++ ) + for( ptr = hs->lists[ i ]; ptr != NULL; ptr = ptr->next ) + printf( "%s\n", ptr->key ); +} diff --git a/src/hstr.c b/src/hstr.c new file mode 100644 index 0000000..9455594 --- /dev/null +++ b/src/hstr.c @@ -0,0 +1,374 @@ +/* + ============================================================================ + Name : hstr.c + Author : Martin Dvorak + Version : 0.2 + Copyright : Apache 2.0 + Description : Shell history completion utility + ============================================================================ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/hashset.h" + +#define HSTR_VERSION "0.2" + +#define LABEL_HISTORY " HISTORY " +#define LABEL_HELP "Type to filter history, use UP and DOWN arrow keys to choose an item using ENTER" +#define ENV_VAR_USER "USER" +#define ENV_VAR_HOME "HOME" +#define FILE_HISTORY ".bash_history" +#define SELECTION_CURSOR_IN_PROMPT -1 + +#define Y_OFFSET_PROMPT 0 +#define Y_OFFSET_HELP 2 +#define Y_OFFSET_HISTORY 3 +#define Y_OFFSET_ITEMS 4 + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + +static char ** selection=NULL; +static int selectionSize=0; + +int printPrompt(WINDOW *win) { + char hostname[128]; + char *user = getenv(ENV_VAR_USER); + int xoffset = 1; + + gethostname(hostname, sizeof hostname); + mvwprintw(win, xoffset, Y_OFFSET_PROMPT, "%s@%s$ ", user, hostname); + refresh(); + + return xoffset+strlen(user)+1+strlen(hostname)+1; +} + +void printHelpLabel(WINDOW *win) { + mvwprintw(win, Y_OFFSET_HELP, 0, LABEL_HELP); + refresh(); +} + +void printHistoryLabel(WINDOW *win) { + char message[512]; + + int width=getmaxx(win); + + strcpy(message, LABEL_HISTORY); + width -= strlen(LABEL_HISTORY); + int i; + for (i=0; i < width; i++) { + strcat(message, " "); + } + + wattron(win, A_REVERSE); + mvwprintw(win, Y_OFFSET_HISTORY, 0, message); + wattroff(win, A_REVERSE); + + refresh(); +} + +int getMaxHistoryItems(WINDOW *win) { + return (getmaxy(win)-(Y_OFFSET_ITEMS+2)); +} + +char *loadHistoryFile() { + char *home = getenv(ENV_VAR_HOME); + char *fileName=(char*)malloc(strlen(home)+1+strlen(FILE_HISTORY)+1); + strcpy(fileName,home); + strcat(fileName,"/"); + strcat(fileName,FILE_HISTORY); + + if(access(fileName, F_OK) != -1) { + char *file_contents; + long input_file_size; + + FILE *input_file = fopen(fileName, "rb"); + fseek(input_file, 0, SEEK_END); + input_file_size = ftell(input_file); + rewind(input_file); + file_contents = malloc((input_file_size + 1) * (sizeof(char))); + if(fread(file_contents, sizeof(char), input_file_size, input_file)==-1) { + exit(EXIT_FAILURE); + } + fclose(input_file); + file_contents[input_file_size] = 0; + + return file_contents; + } else { + fprintf(stderr,"\nHistory file not found: %s\n",fileName); + exit(EXIT_FAILURE); + } +} + +int countHistoryLines(char *history) { + int i = 0; + char *p=strchr(history,'\n'); + while (p!=NULL) { + i++; + p=strchr(p+1,'\n'); + } + return i; +} + +char **tokenizeHistory(char *history, int lines) { + char **tokens = malloc(sizeof(char*) * lines); + + int i = 0; + char *pb=history, *pe; + pe=strchr(history, '\n'); + while(pe!=NULL) { + tokens[i]=pb; + *pe=0; + + pb=pe+1; + pe=strchr(pb, '\n'); + i++; + } + + return tokens; +} + +void allocSelection(int size) { + selectionSize=size; + if(selection!=NULL) { + free(selection); + selection=NULL; + } + if(size>0) { + selection = malloc(size); + } +} + +int makeSelection(char* prefix, char **historyFileItems, int historyFileItemsCount, int maxSelectionCount) { + allocSelection(sizeof(char*) * maxSelectionCount); // TODO realloc + int i, selectionCount=0; + + HashSet set; + hs_init(&set); + + for(i=0; i 0) { + result = selection[0]; + } + + int height=getMaxHistoryItems(win); + int i; + int y=Y_OFFSET_ITEMS; + for (i = 0; i"); + } +} + +char* selectionLoop(char **historyFileItems, int historyFileItemsCount) { + initscr(); + if (has_colors() == FALSE) { + endwin(); + printf("Your terminal does not support color\n"); + exit(1); + } + + start_color(); + init_pair(1, COLOR_WHITE, COLOR_BLACK); + attron(COLOR_PAIR(1)); + printHistoryLabel(stdscr); + printHelpLabel(stdscr); + printSelection(stdscr, getMaxHistoryItems(stdscr), NULL, historyFileItemsCount, historyFileItems); + int basex = printPrompt(stdscr); + int x = basex; + attroff(COLOR_PAIR(1)); + + int selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT; + int previousSelectionCursorPosition=SELECTION_CURSOR_IN_PROMPT; + + int y = 1, c, maxHistoryItems; + bool done = FALSE; + char prefix[500]=""; + char* result=""; + while (!done) { + maxHistoryItems=getMaxHistoryItems(stdscr); + + noecho(); + c = wgetch(stdscr); + //mvprintw(Y_OFFSET_HELP, 0, "Key pressed is = %4d Hopefully it can be printed as '%c'", c, c); + echo(); + + switch (c) { + case 91: + // TODO 91 killed > debug to determine how to distinguish \e and [ + //mvprintw(Y_OFFSET_HELP, 0, "91 killed"); + break; + case KEY_BACKSPACE: + case 127: + if(strlen(prefix)>0) { + prefix[strlen(prefix)-1]=0; + x--; + wattron(stdscr,A_BOLD); + mvprintw(y, basex, "%s", prefix); + wattroff(stdscr,A_BOLD); + clrtoeol(); + } + + if(strlen(prefix)>0) { + makeSelection(prefix, historyFileItems, historyFileItemsCount, maxHistoryItems); + } else { + makeSelection(NULL, historyFileItems, historyFileItemsCount, maxHistoryItems); + } + result = printSelection(stdscr, maxHistoryItems, prefix, historyFileItemsCount, historyFileItems); + break; + case KEY_UP: + case 65: + if(selectionCursorPosition>SELECTION_CURSOR_IN_PROMPT) { + previousSelectionCursorPosition=selectionCursorPosition; + selectionCursorPosition--; + } else { + previousSelectionCursorPosition=SELECTION_CURSOR_IN_PROMPT; + } + highlightSelection(selectionCursorPosition, previousSelectionCursorPosition); + break; + case KEY_DOWN: + case 66: + previousSelectionCursorPosition=selectionCursorPosition; + if((selectionCursorPosition+1) + +#ifdef __cplusplus +extern "C" { +#endif + + +#define TABLE_SIZE 10007 + +struct HashNode { + char *key; + struct HashNode *next; +}; + +struct HashSetStruct { + struct HashNode * lists[TABLE_SIZE]; + int currentSize; +}; + +typedef struct HashSetStruct HashSet; + +void hs_init( HashSet * hs ); +int hs_contains( const HashSet * hs, const char *key ); +int hs_add( HashSet * hs, const char *key ); +int hs_remove( HashSet * hs, const char *key ); +int hs_size( const HashSet * hs ); +void hs_print( const HashSet * hs ); + + +#ifdef __cplusplus +} +#endif + +#endif