Source code

This commit is contained in:
Martin Dvorak 2013-12-02 23:55:33 +01:00
parent c7688fe5e7
commit 986f158088
4 changed files with 547 additions and 1 deletions

View file

@ -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 -

96
src/hashset.c Normal file
View file

@ -0,0 +1,96 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 );
}

374
src/hstr.c Normal file
View file

@ -0,0 +1,374 @@
/*
============================================================================
Name : hstr.c
Author : Martin Dvorak
Version : 0.2
Copyright : Apache 2.0
Description : Shell history completion utility
============================================================================
*/
#include <curses.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#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<historyFileItemsCount && selectionCount<maxSelectionCount; i++) {
if(!hs_contains(&set, historyFileItems[i])) {
if(prefix==NULL) {
selection[selectionCount++]=historyFileItems[i];
hs_add(&set, historyFileItems[i]);
} else {
if(historyFileItems[i]==strstr(historyFileItems[i], prefix)) {
selection[selectionCount++]=historyFileItems[i];
hs_add(&set, historyFileItems[i]);
}
}
}
}
if(prefix!=NULL && selectionCount<maxSelectionCount) {
for(i=0; i<historyFileItemsCount && selectionCount<maxSelectionCount; i++) {
if(!hs_contains(&set, historyFileItems[i])) {
char* substring = strstr(historyFileItems[i], prefix);
if (substring != NULL && substring!=historyFileItems[i]) {
selection[selectionCount++]=historyFileItems[i];
hs_add(&set, historyFileItems[i]);
}
}
}
}
selectionSize=selectionCount;
return selectionCount;
}
char* printSelection(WINDOW *win, int maxHistoryItems, char *prefix, int historyFileItemsCount, char** historyFileItems) {
char* result="";
int selectionCount=makeSelection(prefix, historyFileItems, historyFileItemsCount, maxHistoryItems);
if (selectionCount > 0) {
result = selection[0];
}
int height=getMaxHistoryItems(win);
int i;
int y=Y_OFFSET_ITEMS;
for (i = 0; i<height; ++i) {
if(i<selectionSize) {
mvwprintw(win, y++, 1, "%s", selection[i]);
clrtoeol();
if(prefix!=NULL) {
wattron(win,A_BOLD);
char *p=strstr(selection[i], prefix);
mvwprintw(win, (y-1), 1+(p-selection[i]), "%s", prefix);
wattroff(win,A_BOLD);
}
} else {
mvwprintw(win, y++, 0, " ");
clrtoeol();
}
}
refresh();
return result;
}
void highlightSelection(int selectionCursorPosition, int previousSelectionCursorPosition) {
if(previousSelectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
mvprintw(Y_OFFSET_ITEMS+previousSelectionCursorPosition, 0, " ");
}
if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
mvprintw(Y_OFFSET_ITEMS+selectionCursorPosition, 0, ">");
}
}
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)<selectionSize) {
selectionCursorPosition++;
} else {
selectionCursorPosition=0;
}
highlightSelection(selectionCursorPosition, previousSelectionCursorPosition);
break;
case 10:
if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
mvprintw(Y_OFFSET_HELP, 0, "EXIT: %d %d ",selectionCursorPosition, selectionSize);
result=selection[selectionCursorPosition];
allocSelection(0);
}
done = TRUE;
break;
default:
if(c!=27) {
strcat(prefix, (char*)(&c));
wattron(stdscr,A_BOLD);
mvprintw(y, basex, "%s", prefix);
wattroff(stdscr,A_BOLD);
clrtoeol();
result = printSelection(stdscr, maxHistoryItems, prefix, historyFileItemsCount, historyFileItems);
}
break;
}
}
endwin();
return result;
}
void tiocsti() {
char buf[] = "cmd";
int i;
for (i = 0; i < sizeof buf - 1; i++) {
ioctl(0, TIOCSTI, &buf[i]);
}
}
void fillTerminalInput(char* cmd){
size_t size = strlen(cmd);
int i;
char *c;
for (i = 0; i < size; i++) {
// terminal I/O control, simulate terminal input
c=(cmd+i);
ioctl(0, TIOCSTI, c);
}
printf("\n");
}
void reverseCharPointerArray(char **array, int length) {
int i;
char * temp;
for (i=0; i<length/2; i++) {
temp = array[i];
array[i] = array[length-i-1];
array[length-i-1] = temp;
}
}
void hstr() {
char *historyAsString = loadHistoryFile(FILE_HISTORY);
int itemsCount = countHistoryLines(historyAsString);
char** items = tokenizeHistory(historyAsString, itemsCount);
reverseCharPointerArray(items, itemsCount);
char* command = selectionLoop(items, itemsCount);
fillTerminalInput(command);
free(historyAsString);
free(items);
}
int main(int argc, char *argv[]) {
hstr();
return EXIT_SUCCESS;
}

37
src/include/hashset.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef HASHSET_H
#define HASHSET_H 1
#include <stdlib.h>
#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