xaizek / hstr (License: Apachev2) (since 2018-12-07)
Bash and Zsh shell history suggest box - easily view, navigate, search and manage your command history.
Commit 986f1580886ce17c4b2a90c5c9dea350f8208681

Source code
Author: Martin Dvorak
Author date (UTC): 2013-12-02 22:55
Committer name: Martin Dvorak
Committer date (UTC): 2013-12-02 22:55
Parent(s): c7688fe5e7252c7be97e39594bba31b177d06baf
Signing key:
Tree: ccb556231730ec6bd728e5a21f9cdccaaec0ff99
File Lines added Lines deleted
README.md 40 1
src/hashset.c 96 0
src/hstr.c 374 0
src/include/hashset.h 37 0
File README.md changed (mode: 100644) (index d68580c..c9c473e)
1 1 hstr hstr
2 2 ==== ====
3 3
4 BASH History Suggest Box
4 BASH History Suggest Box.
5
6 DESCRIPTION
7 -----------
8 A command line utility that brings improved BASH command completion
9 from the history. It aims to make completion easier to use and faster
10 than Ctrl-R.
11
12
13 DOWNLOAD
14 --------
15 https://bitbucket.org/dvorka/hstr/downloads
16
17
18 INSTALLATION
19 ------------
20 * add hh to $PATH
21 * you may also bind hh to a BASH key (e.g. F12) by adding bind to .bashrc:
22 bind '"\e[24~":"hh"'
23 To determine the character sequence emitted by a pressed key in terminal,
24 type CTRL-v and then press the key. For example, F12 gives "^[[24~" (no quotes).
25 Replace the "^[" with \e. To clear the line first, add ā€œ\C-k \C-uā€ in front of
26 the actual command to clear the line first.
27 bind '"\e[24~":"\C-kX\C-uhh\n"'
28 Binding using Ctrl-F12:
29 bind '"\e[24;5~":"\C-kX\C-uhh\n"'
30 Check:
31 bind -S
32
33
34 BUGS
35 ----
36 https://bitbucket.org/dvorka/hstr/issues/new
37
38
39 AUTHOR
40 ------
41 martin.dvorak@mindforger.com
42
43 - eof -
File src/hashset.c added (mode: 100644) (index 0000000..67054b2)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include "include/hashset.h"
5
6 unsigned int hash( const char *str ) {
7 int i;
8 unsigned int result = 0;
9
10 for( i = 0; str[ i ] != '\0'; i++ )
11 result = result * 31 + str[ i ];
12
13 return result % TABLE_SIZE;
14 }
15
16 void hs_init( HashSet * hs ) {
17 int i;
18 hs->currentSize = 0;
19 for( i = 0; i < TABLE_SIZE; i++ )
20 hs->lists[ i ] = NULL;
21 }
22
23 int hs_contains( const HashSet * hs, const char *key ) {
24 int listNum = hash( key );
25 struct HashNode *ptr = hs->lists[ listNum ];
26
27 while( ptr != NULL && strcmp( ptr->key, key ) != 0 )
28 ptr = ptr->next;
29
30 return ptr != NULL;
31 }
32
33 int hs_add( HashSet * hs, const char *key ) {
34 struct HashNode *newNode;
35 int listNum;
36
37 if( hs_contains( hs, key ) )
38 return 0;
39
40 listNum = hash( key );
41
42
43 newNode = (struct HashNode *) malloc( sizeof ( struct HashNode ) );
44 if( newNode == NULL ) {
45 fprintf( stderr, "Error allocating node" );
46 return 0;
47 }
48
49 newNode->key = strdup( key );
50 newNode->next = hs->lists[ listNum ];
51 hs->lists[ listNum ] = newNode;
52 hs->currentSize++;
53
54 return 1;
55 }
56
57 int hs_remove( HashSet * hs, const char *key ) {
58 struct HashNode *curr;
59 struct HashNode *prev = NULL;
60 int listNum;
61
62 if( !hs_contains( hs, key ) )
63 return 0;
64
65 listNum = hash( key );
66 curr = hs->lists[ listNum ];
67 while( strcmp( curr->key, key ) != 0 ) {
68 prev = curr;
69 curr = curr->next;
70 }
71
72 if( prev == NULL ) // first item
73 hs->lists[ listNum ] = curr->next;
74 else // middle of list
75 prev->next = curr->next;
76
77 // cleanup node curr
78 free( curr->key );
79 free( curr );
80
81 hs->currentSize--;
82 return 1;
83 }
84
85 int hs_size(const HashSet * hs) {
86 return hs->currentSize;
87 }
88
89 void hs_print( const HashSet * hs ) {
90 int i;
91 struct HashNode *ptr;
92
93 for( i = 0; i < TABLE_SIZE; i++ )
94 for( ptr = hs->lists[ i ]; ptr != NULL; ptr = ptr->next )
95 printf( "%s\n", ptr->key );
96 }
File src/hstr.c added (mode: 100644) (index 0000000..9455594)
1 /*
2 ============================================================================
3 Name : hstr.c
4 Author : Martin Dvorak
5 Version : 0.2
6 Copyright : Apache 2.0
7 Description : Shell history completion utility
8 ============================================================================
9 */
10
11 #include <curses.h>
12 #include <fcntl.h>
13 #include <stdbool.h>
14 #include <stddef.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/ioctl.h>
19 #include <termios.h>
20 #include <unistd.h>
21 #include "include/hashset.h"
22
23 #define HSTR_VERSION "0.2"
24
25 #define LABEL_HISTORY " HISTORY "
26 #define LABEL_HELP "Type to filter history, use UP and DOWN arrow keys to choose an item using ENTER"
27 #define ENV_VAR_USER "USER"
28 #define ENV_VAR_HOME "HOME"
29 #define FILE_HISTORY ".bash_history"
30 #define SELECTION_CURSOR_IN_PROMPT -1
31
32 #define Y_OFFSET_PROMPT 0
33 #define Y_OFFSET_HELP 2
34 #define Y_OFFSET_HISTORY 3
35 #define Y_OFFSET_ITEMS 4
36
37 #define MIN(a,b) (((a)<(b))?(a):(b))
38 #define MAX(a,b) (((a)>(b))?(a):(b))
39
40 static char ** selection=NULL;
41 static int selectionSize=0;
42
43 int printPrompt(WINDOW *win) {
44 char hostname[128];
45 char *user = getenv(ENV_VAR_USER);
46 int xoffset = 1;
47
48 gethostname(hostname, sizeof hostname);
49 mvwprintw(win, xoffset, Y_OFFSET_PROMPT, "%s@%s$ ", user, hostname);
50 refresh();
51
52 return xoffset+strlen(user)+1+strlen(hostname)+1;
53 }
54
55 void printHelpLabel(WINDOW *win) {
56 mvwprintw(win, Y_OFFSET_HELP, 0, LABEL_HELP);
57 refresh();
58 }
59
60 void printHistoryLabel(WINDOW *win) {
61 char message[512];
62
63 int width=getmaxx(win);
64
65 strcpy(message, LABEL_HISTORY);
66 width -= strlen(LABEL_HISTORY);
67 int i;
68 for (i=0; i < width; i++) {
69 strcat(message, " ");
70 }
71
72 wattron(win, A_REVERSE);
73 mvwprintw(win, Y_OFFSET_HISTORY, 0, message);
74 wattroff(win, A_REVERSE);
75
76 refresh();
77 }
78
79 int getMaxHistoryItems(WINDOW *win) {
80 return (getmaxy(win)-(Y_OFFSET_ITEMS+2));
81 }
82
83 char *loadHistoryFile() {
84 char *home = getenv(ENV_VAR_HOME);
85 char *fileName=(char*)malloc(strlen(home)+1+strlen(FILE_HISTORY)+1);
86 strcpy(fileName,home);
87 strcat(fileName,"/");
88 strcat(fileName,FILE_HISTORY);
89
90 if(access(fileName, F_OK) != -1) {
91 char *file_contents;
92 long input_file_size;
93
94 FILE *input_file = fopen(fileName, "rb");
95 fseek(input_file, 0, SEEK_END);
96 input_file_size = ftell(input_file);
97 rewind(input_file);
98 file_contents = malloc((input_file_size + 1) * (sizeof(char)));
99 if(fread(file_contents, sizeof(char), input_file_size, input_file)==-1) {
100 exit(EXIT_FAILURE);
101 }
102 fclose(input_file);
103 file_contents[input_file_size] = 0;
104
105 return file_contents;
106 } else {
107 fprintf(stderr,"\nHistory file not found: %s\n",fileName);
108 exit(EXIT_FAILURE);
109 }
110 }
111
112 int countHistoryLines(char *history) {
113 int i = 0;
114 char *p=strchr(history,'\n');
115 while (p!=NULL) {
116 i++;
117 p=strchr(p+1,'\n');
118 }
119 return i;
120 }
121
122 char **tokenizeHistory(char *history, int lines) {
123 char **tokens = malloc(sizeof(char*) * lines);
124
125 int i = 0;
126 char *pb=history, *pe;
127 pe=strchr(history, '\n');
128 while(pe!=NULL) {
129 tokens[i]=pb;
130 *pe=0;
131
132 pb=pe+1;
133 pe=strchr(pb, '\n');
134 i++;
135 }
136
137 return tokens;
138 }
139
140 void allocSelection(int size) {
141 selectionSize=size;
142 if(selection!=NULL) {
143 free(selection);
144 selection=NULL;
145 }
146 if(size>0) {
147 selection = malloc(size);
148 }
149 }
150
151 int makeSelection(char* prefix, char **historyFileItems, int historyFileItemsCount, int maxSelectionCount) {
152 allocSelection(sizeof(char*) * maxSelectionCount); // TODO realloc
153 int i, selectionCount=0;
154
155 HashSet set;
156 hs_init(&set);
157
158 for(i=0; i<historyFileItemsCount && selectionCount<maxSelectionCount; i++) {
159 if(!hs_contains(&set, historyFileItems[i])) {
160 if(prefix==NULL) {
161 selection[selectionCount++]=historyFileItems[i];
162 hs_add(&set, historyFileItems[i]);
163 } else {
164 if(historyFileItems[i]==strstr(historyFileItems[i], prefix)) {
165 selection[selectionCount++]=historyFileItems[i];
166 hs_add(&set, historyFileItems[i]);
167 }
168 }
169 }
170 }
171
172 if(prefix!=NULL && selectionCount<maxSelectionCount) {
173 for(i=0; i<historyFileItemsCount && selectionCount<maxSelectionCount; i++) {
174 if(!hs_contains(&set, historyFileItems[i])) {
175 char* substring = strstr(historyFileItems[i], prefix);
176 if (substring != NULL && substring!=historyFileItems[i]) {
177 selection[selectionCount++]=historyFileItems[i];
178 hs_add(&set, historyFileItems[i]);
179 }
180 }
181 }
182 }
183
184 selectionSize=selectionCount;
185 return selectionCount;
186 }
187
188 char* printSelection(WINDOW *win, int maxHistoryItems, char *prefix, int historyFileItemsCount, char** historyFileItems) {
189 char* result="";
190 int selectionCount=makeSelection(prefix, historyFileItems, historyFileItemsCount, maxHistoryItems);
191 if (selectionCount > 0) {
192 result = selection[0];
193 }
194
195 int height=getMaxHistoryItems(win);
196 int i;
197 int y=Y_OFFSET_ITEMS;
198 for (i = 0; i<height; ++i) {
199 if(i<selectionSize) {
200 mvwprintw(win, y++, 1, "%s", selection[i]);
201 clrtoeol();
202 if(prefix!=NULL) {
203 wattron(win,A_BOLD);
204 char *p=strstr(selection[i], prefix);
205 mvwprintw(win, (y-1), 1+(p-selection[i]), "%s", prefix);
206 wattroff(win,A_BOLD);
207 }
208 } else {
209 mvwprintw(win, y++, 0, " ");
210 clrtoeol();
211 }
212 }
213 refresh();
214
215 return result;
216 }
217
218 void highlightSelection(int selectionCursorPosition, int previousSelectionCursorPosition) {
219 if(previousSelectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
220 mvprintw(Y_OFFSET_ITEMS+previousSelectionCursorPosition, 0, " ");
221 }
222 if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
223 mvprintw(Y_OFFSET_ITEMS+selectionCursorPosition, 0, ">");
224 }
225 }
226
227 char* selectionLoop(char **historyFileItems, int historyFileItemsCount) {
228 initscr();
229 if (has_colors() == FALSE) {
230 endwin();
231 printf("Your terminal does not support color\n");
232 exit(1);
233 }
234
235 start_color();
236 init_pair(1, COLOR_WHITE, COLOR_BLACK);
237 attron(COLOR_PAIR(1));
238 printHistoryLabel(stdscr);
239 printHelpLabel(stdscr);
240 printSelection(stdscr, getMaxHistoryItems(stdscr), NULL, historyFileItemsCount, historyFileItems);
241 int basex = printPrompt(stdscr);
242 int x = basex;
243 attroff(COLOR_PAIR(1));
244
245 int selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
246 int previousSelectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
247
248 int y = 1, c, maxHistoryItems;
249 bool done = FALSE;
250 char prefix[500]="";
251 char* result="";
252 while (!done) {
253 maxHistoryItems=getMaxHistoryItems(stdscr);
254
255 noecho();
256 c = wgetch(stdscr);
257 //mvprintw(Y_OFFSET_HELP, 0, "Key pressed is = %4d Hopefully it can be printed as '%c'", c, c);
258 echo();
259
260 switch (c) {
261 case 91:
262 // TODO 91 killed > debug to determine how to distinguish \e and [
263 //mvprintw(Y_OFFSET_HELP, 0, "91 killed");
264 break;
265 case KEY_BACKSPACE:
266 case 127:
267 if(strlen(prefix)>0) {
268 prefix[strlen(prefix)-1]=0;
269 x--;
270 wattron(stdscr,A_BOLD);
271 mvprintw(y, basex, "%s", prefix);
272 wattroff(stdscr,A_BOLD);
273 clrtoeol();
274 }
275
276 if(strlen(prefix)>0) {
277 makeSelection(prefix, historyFileItems, historyFileItemsCount, maxHistoryItems);
278 } else {
279 makeSelection(NULL, historyFileItems, historyFileItemsCount, maxHistoryItems);
280 }
281 result = printSelection(stdscr, maxHistoryItems, prefix, historyFileItemsCount, historyFileItems);
282 break;
283 case KEY_UP:
284 case 65:
285 if(selectionCursorPosition>SELECTION_CURSOR_IN_PROMPT) {
286 previousSelectionCursorPosition=selectionCursorPosition;
287 selectionCursorPosition--;
288 } else {
289 previousSelectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
290 }
291 highlightSelection(selectionCursorPosition, previousSelectionCursorPosition);
292 break;
293 case KEY_DOWN:
294 case 66:
295 previousSelectionCursorPosition=selectionCursorPosition;
296 if((selectionCursorPosition+1)<selectionSize) {
297 selectionCursorPosition++;
298 } else {
299 selectionCursorPosition=0;
300 }
301 highlightSelection(selectionCursorPosition, previousSelectionCursorPosition);
302 break;
303 case 10:
304 if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
305 mvprintw(Y_OFFSET_HELP, 0, "EXIT: %d %d ",selectionCursorPosition, selectionSize);
306 result=selection[selectionCursorPosition];
307 allocSelection(0);
308 }
309 done = TRUE;
310 break;
311 default:
312 if(c!=27) {
313 strcat(prefix, (char*)(&c));
314 wattron(stdscr,A_BOLD);
315 mvprintw(y, basex, "%s", prefix);
316 wattroff(stdscr,A_BOLD);
317 clrtoeol();
318
319 result = printSelection(stdscr, maxHistoryItems, prefix, historyFileItemsCount, historyFileItems);
320 }
321 break;
322 }
323 }
324 endwin();
325
326 return result;
327 }
328
329 void tiocsti() {
330 char buf[] = "cmd";
331 int i;
332 for (i = 0; i < sizeof buf - 1; i++) {
333 ioctl(0, TIOCSTI, &buf[i]);
334 }
335 }
336
337 void fillTerminalInput(char* cmd){
338 size_t size = strlen(cmd);
339 int i;
340 char *c;
341 for (i = 0; i < size; i++) {
342 // terminal I/O control, simulate terminal input
343 c=(cmd+i);
344 ioctl(0, TIOCSTI, c);
345 }
346 printf("\n");
347 }
348
349 void reverseCharPointerArray(char **array, int length) {
350 int i;
351 char * temp;
352 for (i=0; i<length/2; i++) {
353 temp = array[i];
354 array[i] = array[length-i-1];
355 array[length-i-1] = temp;
356 }
357 }
358
359 void hstr() {
360 char *historyAsString = loadHistoryFile(FILE_HISTORY);
361 int itemsCount = countHistoryLines(historyAsString);
362 char** items = tokenizeHistory(historyAsString, itemsCount);
363 reverseCharPointerArray(items, itemsCount);
364 char* command = selectionLoop(items, itemsCount);
365 fillTerminalInput(command);
366 free(historyAsString);
367 free(items);
368 }
369
370 int main(int argc, char *argv[]) {
371 hstr();
372 return EXIT_SUCCESS;
373 }
374
File src/include/hashset.h added (mode: 100644) (index 0000000..7c2ec2a)
1 #ifndef HASHSET_H
2 #define HASHSET_H 1
3
4 #include <stdlib.h>
5
6 #ifdef __cplusplus
7 extern "C" {
8 #endif
9
10
11 #define TABLE_SIZE 10007
12
13 struct HashNode {
14 char *key;
15 struct HashNode *next;
16 };
17
18 struct HashSetStruct {
19 struct HashNode * lists[TABLE_SIZE];
20 int currentSize;
21 };
22
23 typedef struct HashSetStruct HashSet;
24
25 void hs_init( HashSet * hs );
26 int hs_contains( const HashSet * hs, const char *key );
27 int hs_add( HashSet * hs, const char *key );
28 int hs_remove( HashSet * hs, const char *key );
29 int hs_size( const HashSet * hs );
30 void hs_print( const HashSet * hs );
31
32
33 #ifdef __cplusplus
34 }
35 #endif
36
37 #endif
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/hstr

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/hstr

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master