diff --git a/README.md b/README.md index 0132b1a..db48741 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,14 @@ make search case sensitive (insensitive by default): ```bash export HH_CONFIG=casesensitive ``` +show warnings: +```bash +export HH_CONFIG=warning +``` +show debug messages: +```bash +export HH_CONFIG=warning +``` more colors and case sensitive search: ```bash export HH_CONFIG=hicolor,casesensitive diff --git a/man/hh.1 b/man/hh.1 index 1166b63..ae8a595 100644 --- a/man/hh.1 +++ b/man/hh.1 @@ -9,19 +9,24 @@ uses shell history to provide suggest box like functionality for commands used in the past. By default it parses .bash-history file that is filtered as you type a command substring. Commands are not just filtered, but also ordered by a ranking algorithm -that considers number of occurences, length and timestamp. In addition -hh allows removal of commands from history - for instance with a typo or with a sensitive content. +that considers number of occurences, length and timestamp. +Favorite and frequently used commands can be bookmarked. In addition +hh allows removal of commands from history - for instance with a typo +or with a sensitive content. .SH OPTIONS .TP -\fB--show-configuration\fR -Show configuration that can be added to .bashrc -.TP \fB--help\fR Show help .TP +\fB--favorites\fR +Show favorites view immediately +.TP +\fB--show-configuration\fR +Show configuration that can be added to ~/.bashrc +.TP \fB--version\fR Show version information -.SH COMMANDS +.SH KEYS .TP \fBpattern\fR Type to filter shell history. @@ -30,7 +35,7 @@ Type to filter shell history. Toggle case sensitive search. .TP \fBCtrl\-/\fR -Toggle history as provided by shell vs. ranked history ordered by the number of occurences, length and timestamp. +Rotate view of history as provided by BASH, ranked history ordered by the number of occurences/length/timestamp and favorites. .TP \fBCtrl\-l\fR Make search pattern lowercase or uppercase. @@ -68,14 +73,26 @@ Configuration options: Get more colors with this option (default is monochromatic). \fIcasesensitive\fR - Make the pattern-based filtering case sensitive by default. + Make the pattern-based filtering case sensitive (it's case insensitive by default). \fIrawhistory\fR - Show normal history by default (default is metric-based). + Show normal history as a default view (metric-based view is shown otherwise). + +\fIfavorites\fR + Show favorites as a default view (metric-based view is shown otherwise). + +\fIwarning\fR + Show warning. + +\fIdebug\fR + Show debug information. Example: \fBexport HH_CONFIG=casesensitive,hicolor\fR +.SH FILES +\fB~/.hh_favorites\fR bookmarked favorite commands + .SH CONFIGURATION Optionally add the following lines to ~/.bashrc: .nf diff --git a/src/hstr.c b/src/hstr.c index 321b32b..5b79e76 100644 --- a/src/hstr.c +++ b/src/hstr.c @@ -38,6 +38,7 @@ #define K_CTRL_A 1 #define K_CTRL_E 5 +#define K_CTRL_F 6 #define K_CTRL_G 7 #define K_CTRL_H 8 #define K_CTRL_L 12 @@ -60,10 +61,21 @@ #define HH_COLOR_PROMPT 3 #define HH_COLOR_DELETE 4 -#define ENV_VAR_HH_CONFIG "HH_CONFIG" +#define HH_ENV_VAR_CONFIG "HH_CONFIG" #define HH_CONFIG_HICOLOR "hicolor" #define HH_CONFIG_CASE "casesensitive" #define HH_CONFIG_SORTING "rawhistory" +#define HH_CONFIG_FAVORITES "favorites" +#define HH_CONFIG_DEBUG "debug" +#define HH_CONFIG_WARN "warning" + +#define HH_DEBUG_LEVEL_NONE 0 +#define HH_DEBUG_LEVEL_WARN 1 +#define HH_DEBUG_LEVEL_DEBUG 2 + +#define HH_VIEW_RANKING 0 +#define HH_VIEW_HISTORY 1 +#define HH_VIEW_FAVORITES 2 #define SPACE_PADDING " " @@ -79,6 +91,11 @@ #define LOGCURSOR(Y) #endif +static const char *HH_VIEW_LABELS[]={ + "ranking", + "history", + "favorites"}; + static const char *INSTALL_STRING= "\n# add this configuration to ~/.bashrc" "\nexport HH_CONFIG=hicolor # get more colors" @@ -95,7 +112,8 @@ static const char *HELP_STRING= "Usage: hh [option] [arg1] [arg2]..." "\nShell history suggest box:" "\n" - "\n --show-configuration ... show configuration to be added to .bashrc" + "\n --favorites -f ... show command favorites" + "\n --show-configuration ... show configuration to be added to ~/.bashrc" "\n --help ... display this help and exit" "\n" "\nReport bugs to martin.dvorak@mindforger.com" @@ -108,19 +126,20 @@ static const char *VERSION_STRING= "\n"; static const char *LABEL_HELP= - "Type to filter, UP/DOWN to move, DEL to remove, TAB to select, C-g to cancel"; + "Type to filter, UP/DOWN move, DEL remove, TAB select, C-f add favorite, C-g cancel"; static char **selection=NULL; static unsigned selectionSize=0; static bool caseSensitive=FALSE; -static bool defaultOrder=FALSE; +static int historyView=HH_VIEW_RANKING; static bool hicolor=FALSE; +static int debugLevel=0; static char screenLine[CMDLINE_LNG]; static char cmdline[CMDLINE_LNG]; void get_env_configuration() { - char *hhconfig=getenv(ENV_VAR_HH_CONFIG); + char *hhconfig=getenv(HH_ENV_VAR_CONFIG); if(hhconfig && strlen(hhconfig)>0) { if(strstr(hhconfig,HH_CONFIG_HICOLOR)) { hicolor=TRUE; @@ -129,7 +148,18 @@ void get_env_configuration() caseSensitive=TRUE; } if(strstr(hhconfig,HH_CONFIG_SORTING)) { - defaultOrder=TRUE; + historyView=HH_VIEW_HISTORY; + } else { + if(strstr(hhconfig,HH_CONFIG_FAVORITES)) { + historyView=HH_VIEW_FAVORITES; + } + } + if(strstr(hhconfig,HH_CONFIG_DEBUG)) { + debugLevel=HH_DEBUG_LEVEL_DEBUG; + } else { + if(strstr(hhconfig,HH_CONFIG_WARN)) { + debugLevel=HH_DEBUG_LEVEL_WARN; + } } } } @@ -181,12 +211,28 @@ void print_cmd_deleted_label(char *cmd, int occurences) refresh(); } +void print_cmd_added_favorite_label(char *cmd) +{ + snprintf(screenLine, getmaxx(stdscr), "Command '%s' added to favorites (C-/ to show favorites)", cmd); + if(hicolor) { + color_attr_on(COLOR_PAIR(4)); + color_attr_on(A_BOLD); + } + mvprintw(Y_OFFSET_HELP, 0, screenLine); + if(hicolor) { + color_attr_off(A_BOLD); + color_attr_on(COLOR_PAIR(1)); + } + clrtoeol(); + refresh(); +} + void print_history_label(HistoryItems *history) { int width=getmaxx(stdscr); - snprintf(screenLine, width, "- HISTORY - case:%s (C-t) - order:%s (C-/) - %d/%d ", + snprintf(screenLine, width, "- HISTORY - match:%s (C-t) - view:%s (C-/) - %d/%d ", (caseSensitive?"sensitive":"insensitive"), - (defaultOrder?"history":"ranking"), + HH_VIEW_LABELS[historyView], history->count, history->rawCount); width -= strlen(screenLine); @@ -241,8 +287,23 @@ unsigned make_selection(char *prefix, HistoryItems *history, int maxSelectionCou realloc_selection(sizeof(char*) * maxSelectionCount); unsigned i, selectionCount=0; - char **source=(defaultOrder?history->raw:history->items); - unsigned count=(defaultOrder?history->rawCount:history->count); + char **source; + unsigned count; + + switch(historyView) { + case HH_VIEW_RANKING: + source=history->items; + count=history->count; + break; + case HH_VIEW_HISTORY: + source=history->raw; + count=history->rawCount; + break; + case HH_VIEW_FAVORITES: + source=history->favorites->items; + count=history->favorites->count; + break; + } for(i=0; icount) { - int i, w; - for(i=0, w=0; icount; i++) { - if(strcmp(history->items[i], cmd)) { - history->items[w]=history->items[i]; - w++; + if(historyView==HH_VIEW_FAVORITES) { + favorites_remove(history->favorites, cmd); + } else { + if(history->count) { + int i, w; + for(i=0, w=0; icount; i++) { + if(strcmp(history->items[i], cmd)) { + history->items[w]=history->items[i]; + w++; + } } + history->count=w; } - history->count=w; } } @@ -396,6 +461,15 @@ void signal_callback_handler_ctrl_c(int signum) } } +int seletion_source_remove(char* delete, HistoryItems *history) +{ + if(historyView!=HH_VIEW_FAVORITES) { + return history_mgmt_remove(delete); + } else { + return favorites_remove(history->favorites, delete); + } +} + void loop_to_select(HistoryItems *history) { signal(SIGINT, signal_callback_handler_ctrl_c); @@ -417,7 +491,7 @@ void loop_to_select(HistoryItems *history) print_selection(get_max_history_items(stdscr), NULL, history); color_attr_off(COLOR_PAIR(HH_COLOR_NORMAL)); - bool done=FALSE, skip=TRUE, executeResult=FALSE, lowercase=TRUE, justDeleted=FALSE; + bool done=FALSE, skip=TRUE, executeResult=FALSE, lowercase=TRUE, printDefaultLabel=FALSE; int basex=print_prompt(stdscr); int x=basex, y=0, c, cursorX=0, cursorY=0, maxHistoryItems, deleteOccurences; int width=getmaxx(stdscr); @@ -446,9 +520,9 @@ void loop_to_select(HistoryItems *history) continue; } - if(justDeleted) { + if(printDefaultLabel) { print_help_label(); - justDeleted=FALSE; + printDefaultLabel=FALSE; } switch (c) { @@ -458,11 +532,11 @@ void loop_to_select(HistoryItems *history) msg=malloc(strlen(delete)+1); strcpy(msg,delete); selection_remove(delete, history); - deleteOccurences=history_mgmt_remove(delete); + deleteOccurences=seletion_source_remove(delete, history); result=print_selection(maxHistoryItems, pattern, history); print_cmd_deleted_label(msg, deleteOccurences); move(y, basex+strlen(pattern)); - justDeleted=TRUE; + printDefaultLabel=TRUE; } print_history_label(history); break; @@ -473,11 +547,26 @@ void loop_to_select(HistoryItems *history) selectionCursorPosition=0; break; case K_CTRL_SLASH: - defaultOrder=!defaultOrder; + historyView=(++historyView)%3; result=print_selection(maxHistoryItems, pattern, history); print_history_label(history); selectionCursorPosition=0; break; + case K_CTRL_F: + if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) { + result=selection[selectionCursorPosition]; + + if(historyView==HH_VIEW_FAVORITES) { + favorites_choose(history->favorites, result); + } else { + favorites_add(history->favorites, result); + print_cmd_added_favorite_label(result); + printDefaultLabel=TRUE; + } + result=print_selection(maxHistoryItems, pattern, history); + selectionCursorPosition=0; + } + break; case KEY_RESIZE: print_history_label(history); result=print_selection(maxHistoryItems, pattern, history); @@ -618,7 +707,6 @@ void hstr() HistoryItems *history=get_prioritized_history(); if(history) { history_mgmt_open(); - get_env_configuration(); loop_to_select(history); hstr_on_exit(); } else { @@ -628,8 +716,12 @@ void hstr() int main(int argc, char *argv[]) { + get_env_configuration(); if(argc>0) { if(argc==2) { + if(strstr(argv[1], "--favorites") || strstr(argv[1], "-f")) { + historyView=HH_VIEW_FAVORITES; + } if(strstr(argv[1], "--show-configuration")) { printf("%s", INSTALL_STRING); return EXIT_SUCCESS; diff --git a/src/hstr_favorites.c b/src/hstr_favorites.c new file mode 100644 index 0000000..bdfd272 --- /dev/null +++ b/src/hstr_favorites.c @@ -0,0 +1,178 @@ +/* + ============================================================================ + Name : hstr_favorites.c + Author : martin.dvorak@midforger.com + Copyright : Apache 2.0 + Description : Favorite commands. + ============================================================================ +*/ + +#include +#include +#include +#include +#include "include/hstr_favorites.h" + +#define FAVORITE_SEGMENT_SIZE 10 + +void favorites_init(FavoriteItems *favorites) +{ + favorites->items=NULL; + favorites->count=0; + favorites->loaded=false; +} + +char* favorites_get_filename() +{ + char *home = getenv(ENV_VAR_HOME); + char *fileName = (char*) malloc(strlen(home) + 1 + strlen(FILE_HH_RC) + 1); + strcpy(fileName, home); + strcat(fileName, "/"); + strcat(fileName, FILE_HH_RC); + return fileName; +} + +void favorites_get(FavoriteItems *favorites) +{ + if(!favorites->loaded) { + char* fileName = favorites_get_filename(); + char *file_contents=NULL; + if(access(fileName, F_OK) != -1) { + 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; + + if(file_contents && strlen(file_contents)) { + favorites->count = 0; + char *p=strchr(file_contents,'\n'); + while (p!=NULL) { + favorites->count++; + p=strchr(p+1,'\n'); + } + + favorites->items = malloc(sizeof(char*) * favorites->count); + int i = 0; + char *pb=file_contents, *pe; + pe=strchr(file_contents, '\n'); + while(pe!=NULL) { + favorites->items[i]=pb; + *pe=0; + favorites->items[i]=strdup(pb); + pb=pe+1; + pe=strchr(pb, '\n'); + i++; + } + free(file_contents); + } + } else { + // favorites file not found > favorites don't exist yet + favorites->loaded=true; + return; + } + free(fileName); + } +} + +void favorites_save(FavoriteItems *favorites) +{ + char *fileName=favorites_get_filename(); + + if(favorites->count) { + FILE *output_file = fopen(fileName, "wb"); + rewind(output_file); + int i; + for(i=0; icount; i++) { + if(fwrite(favorites->items[i], sizeof(char), strlen(favorites->items[i]), output_file)==-1) { + exit(EXIT_FAILURE); + } + if(fwrite("\n", sizeof(char), strlen("\n"), output_file)==-1) { + exit(EXIT_FAILURE); + } + } + fclose(output_file); + } else { + if(access(fileName, F_OK) != -1) { + FILE *output_file = fopen(fileName, "wb"); + fclose(output_file); + } + } + + free(fileName); +} + +void favorites_add(FavoriteItems *favorites, char *newFavorite) +{ + if(favorites->count) { + favorites->items=realloc(favorites->items, sizeof(char *) * ++favorites->count); + favorites->items[favorites->count-1]=strdup(newFavorite); + favorites_choose(favorites, newFavorite); + } else { + favorites->items=malloc(sizeof(char*)); + favorites->items[0]=strdup(newFavorite); + favorites->count=1; + } + + favorites_save(favorites); +} + +void favorites_choose(FavoriteItems *favorites, char *choice) +{ + if(favorites->count && choice) { + int i; + char *b=0, *next; + for(i=0; icount; i++) { + if(!strcmp(favorites->items[i],choice)) { + favorites->items[0]=favorites->items[i]; + if(b) { + favorites->items[i]=b; + } + return; + } + next=favorites->items[i]; + favorites->items[i]=b; + b=next; + } + } + favorites_save(favorites); +} + +bool favorites_remove(FavoriteItems *favorites, char *almostDead) +{ + if(favorites->count) { + int i, j; + for(i=0, j=0; icount && jcount; i++, j++) { + if(!strcmp(favorites->items[i], almostDead)) { + j=i+1; + favorites->count--; + } else { + if(j>i) { + favorites->items[i]=favorites->items[j]; + } + } + } + favorites_save(favorites); + return true; + } else { + return false; + } +} + +void favorites_destroy(FavoriteItems *favorites) +{ + if(favorites) { + int i; + for(i=0; icount; i++) { + free(favorites->items[i]); + } + free(favorites); + } +} diff --git a/src/hstr_history.c b/src/hstr_history.c index 0bee434..7176df4 100644 --- a/src/hstr_history.c +++ b/src/hstr_history.c @@ -47,8 +47,8 @@ char *get_history_file_name() char *historyFile=getenv(ENV_VAR_HISTFILE); if(!historyFile || strlen(historyFile)==0) { char *home = getenv(ENV_VAR_HOME); - historyFile = malloc(strlen(home) + 1 + strlen(DEFAULT_HISTORY_FILE) + 1); - strcat(strcat(strcpy(historyFile, home), "/"), DEFAULT_HISTORY_FILE); + historyFile = malloc(strlen(home) + 1 + strlen(FILE_DEFAULT_HISTORY) + 1); + strcat(strcat(strcpy(historyFile, home), "/"), FILE_DEFAULT_HISTORY); } return historyFile; } @@ -150,6 +150,13 @@ HistoryItems *get_prioritized_history() radixsort_destroy(&rs); + + FavoriteItems *favoriteItems=malloc(sizeof(FavoriteItems)); + favorites_init(favoriteItems); + // TODO make favorites loading lazy > github issue + favorites_get(favoriteItems); + prioritizedHistory->favorites=favoriteItems; + return prioritizedHistory; } else { return NULL; @@ -159,6 +166,7 @@ HistoryItems *get_prioritized_history() void free_prioritized_history() { free(prioritizedHistory->items); + favorites_destroy(prioritizedHistory->favorites); free(prioritizedHistory); } diff --git a/src/include/hstr_favorites.h b/src/include/hstr_favorites.h new file mode 100644 index 0000000..9140d88 --- /dev/null +++ b/src/include/hstr_favorites.h @@ -0,0 +1,33 @@ +/* + ============================================================================ + Name : hstr_favorites.h + Author : martin.dvorak@midforger.com + Copyright : Apache 2.0 + Description : Favorite commands. + ============================================================================ +*/ + +#ifndef _HSTR_FAVORITES_H_ +#define _HSTR_FAVORITES_H_ + +#include + +#define ENV_VAR_USER "USER" +#define ENV_VAR_HOME "HOME" + +#define FILE_HH_RC ".hh_favorites" + +typedef struct { + char **items; + unsigned count; + bool loaded; +} FavoriteItems; + +void favorites_init(FavoriteItems *favorites); +void favorites_get(FavoriteItems *favorites); +void favorites_add(FavoriteItems *favorites, char *favorite); +void favorites_choose(FavoriteItems *favorites, char *choice); +bool favorites_remove(FavoriteItems *favorites, char *almostDead); +void favorites_destroy(FavoriteItems *favorites); + +#endif diff --git a/src/include/hstr_history.h b/src/include/hstr_history.h index a8ce3c2..b0ce1f4 100644 --- a/src/include/hstr_history.h +++ b/src/include/hstr_history.h @@ -17,21 +17,21 @@ #include #include #include +#include "hstr_favorites.h" #include "hstr_utils.h" #include "hashset.h" #include "radixsort.h" -#define ENV_VAR_USER "USER" -#define ENV_VAR_HOME "HOME" #define ENV_VAR_HISTFILE "HISTFILE" -#define DEFAULT_HISTORY_FILE ".bash_history" +#define FILE_DEFAULT_HISTORY ".bash_history" typedef struct { char **items; - char **raw; unsigned count; + char **raw; unsigned rawCount; + FavoriteItems *favorites; } HistoryItems; HistoryItems *get_prioritized_history(); diff --git a/src/include/radixsort.h b/src/include/radixsort.h index ce926b0..fa5d864 100644 --- a/src/include/radixsort.h +++ b/src/include/radixsort.h @@ -17,7 +17,11 @@ #include #include "hstr_utils.h" -#define SLOT_SIZE 1000 +#define RADIX_SLOT_SIZE 1000 + +#define RADIX_DEBUG_LEVEL_NONE 0 +#define RADIX_DEBUG_LEVEL_WARN 1 +#define RADIX_DEBUG_LEVEL_DEBUG 2 typedef struct radixitem { unsigned key; @@ -43,9 +47,11 @@ typedef struct { RadixSlot **_slotDescriptors; unsigned _slotsCount; unsigned _topIndexLimit; + unsigned _debug; } RadixSorter; void radixsort_init(RadixSorter *rs, unsigned keyLimit); +void radixsort_set_debug_level(RadixSorter *rs, unsigned debugLevel); void radixsort_add(RadixSorter *rs, RadixItem *item); RadixItem *radix_cut(RadixSorter *rs, unsigned key, void *data); RadixItem **radixsort_dump(RadixSorter *rs); diff --git a/src/radixsort.c b/src/radixsort.c index 1d06e3c..6c4cbc5 100644 --- a/src/radixsort.c +++ b/src/radixsort.c @@ -9,10 +9,8 @@ #include "include/radixsort.h" -#define GET_TOP_INDEX(KEY) KEY/SLOT_SIZE -#define GET_LOW_INDEX(KEY) KEY%SLOT_SIZE - -#define RADIX_DEBUG 1 +#define GET_TOP_INDEX(KEY) KEY/RADIX_SLOT_SIZE +#define GET_LOW_INDEX(KEY) KEY%RADIX_SLOT_SIZE void radixsort_init(RadixSorter *rs, unsigned keyLimit) { @@ -30,10 +28,15 @@ void radixsort_init(RadixSorter *rs, unsigned keyLimit) rs->_slotsCount=0; } +void radixsort_set_debug_level(RadixSorter *rs, unsigned debugLevel) +{ + rs->_debug=debugLevel; +} + RadixItem **radixsort_get_slot(RadixSorter *rs, unsigned topIndex) { - RadixItem **slot=malloc(SLOT_SIZE * sizeof(RadixItem *)); - memset(slot, 0, SLOT_SIZE * sizeof(RadixItem *)); + RadixItem **slot=malloc(RADIX_SLOT_SIZE * sizeof(RadixItem *)); + memset(slot, 0, RADIX_SLOT_SIZE * sizeof(RadixItem *)); RadixSlot *descriptor=malloc(sizeof(RadixSlot)); descriptor->min=rs->keyLimit; @@ -48,7 +51,9 @@ RadixItem **radixsort_get_slot(RadixSorter *rs, unsigned topIndex) void radixsort_add(RadixSorter *rs, RadixItem *item) { if(item->key > rs->keyLimit) { - if(RADIX_DEBUG) fprintf(stderr, "ERROR: Radix sort overflow - inserted key is bigger than limit (%u): %u\n", rs->keyLimit, item->key); + if(rs->_debug > RADIX_DEBUG_LEVEL_NONE) { + fprintf(stderr, "WARNING: Radix sort overflow - inserted key is bigger than limit (%u): %u\n", rs->keyLimit, item->key); + } if(rs->optFloorAndInsertBigKeys) { item->key = rs->keyLimit-1; } else { @@ -174,7 +179,7 @@ void radixsort_stat(RadixSorter *rs, bool listing) printf("\n Radixsort (size/max/limit/slot count): %u %u %u %u", rs->size, rs->maxKey, rs->keyLimit, rs->_slotsCount); unsigned memory=rs->_topIndexLimit * sizeof(RadixItem ***); memory+=memory; - memory+=rs->_slotsCount*(SLOT_SIZE * sizeof(RadixItem *)); + memory+=rs->_slotsCount*(RADIX_SLOT_SIZE * sizeof(RadixItem *)); printf("\n Memory: %u\n", memory); if(listing && rs->size>0) { int t = GET_TOP_INDEX(rs->maxKey);