mirror of
https://github.com/dvorka/hstr.git
synced 2024-09-20 06:46:13 +08:00
Source code
This commit is contained in:
parent
c7688fe5e7
commit
986f158088
41
README.md
41
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 -
|
||||
|
|
96
src/hashset.c
Normal file
96
src/hashset.c
Normal 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
374
src/hstr.c
Normal 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
37
src/include/hashset.h
Normal 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
|
Loading…
Reference in a new issue