13 /* add abstraction at no cost, but may add more complexity as well? */
14 enum { FALSE
= 0, TRUE
= 1 };
16 /* preprocessor macros */
17 #define LENGTH(x) (sizeof(x) / sizeof(*x))
18 #define CONTROL(char) (char ^ 0x40)
19 #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
20 #define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
23 * Fill the buffer apropriately with the lines and headers
26 fill_buffer(char *separator
)
28 /* fill buffer with string */
29 char string
[LINE_SIZE
];
31 Buffer
*buffer
= malloc(sizeof(Buffer
));
32 FILE *fp
= fopen("/dev/stdin", "r");
35 die("Can not open file for reading.");
38 /* read the file into a doubly linked list of lines */
39 while (fgets(string
, LINE_SIZE
, fp
)) {
41 last
= add_line(buffer
, buffer
->total
, string
, separator
, last
);
44 /* set the buffer stats */
45 buffer
->current
= buffer
->first
;
48 buffer
->empty
= malloc(sizeof(Line
));
49 buffer
->empty
->content
= "";
50 buffer
->empty
->comment
= "";
51 buffer
->empty
->number
= 0;
52 buffer
->empty
->matches
= 0;
53 buffer
->empty
->next
= buffer
->first
;
54 buffer
->empty
->prev
= buffer
->first
;
60 * Parse the line content to determine if it is a header and identify the
64 parse_line(char *s
, char *separator
)
66 Line
*line
= malloc(sizeof(Line
));
67 char *sep
= strstr(s
, separator
);
68 int pos
= !sep
? (int) strlen(s
) - 1 : (int) (sep
- s
);
70 /* strip trailing newline */
71 s
[strlen(s
) - 1] = '\0';
73 /* fill line->content */
74 line
->content
= malloc((pos
+ 1) * sizeof(char));
75 strncpy(line
->content
, s
, pos
);
77 /* fill line->comment */
79 line
->comment
= malloc((strlen(s
) - pos
) * sizeof(char));
80 strcpy(line
->comment
, s
+ pos
);
89 * Add a line to the end of the current buffer.
91 * This requires to create a new line with a link to the previous line
92 * and to NULL as the next line.
94 * The previous line's 'next' should be relinked to this new line.
96 * The header's last line have to point to this last line
99 add_line(Buffer
*buffer
, int number
, char *string
, char *separator
, Line
*prev
)
101 /* allocate new line */
102 Line
*line
= malloc(sizeof(Line
));
103 line
= parse_line(string
, separator
);
107 line
->number
= number
;
109 /* interlink with previous line if exists */
111 buffer
->first
= line
;
121 * Set buffer->candidates to an array of lines that match and update
122 * buffer->matching to number of matching candidates.
125 filter_lines(Buffer
*buffer
)
127 Line
* line
= buffer
->first
;
129 buffer
->matching
= 0;
132 if (line_match_input(line
, buffer
->input
)) {
133 line
->matches
= TRUE
;
136 line
->matches
= FALSE
;
144 * Check if line matches and return TRUE if so
147 line_match_input(Line
*line
, char *input
)
149 size_t /*i, j,*/ len_input
/*, len_content*/;
150 len_input
= strlen(input
);
151 /*len_content = strlen(line->content);*/
153 if (!strncmp(input
, line
->content
, len_input
)) {
160 for (i = 0; i < len_content - len_input; i++) {
162 j < len_input && i + j < len_content
163 && tolower(input[j]) == tolower(line->content[i + j]);
167 if (j == len_input) {
177 * Replace tab as a multiple of 8 spaces in a line.
180 expand_tabs(char *line
)
183 char *converted
= malloc(sizeof(char) * (strlen(line
) * 8 + 1));
185 for (i
= 0, n
= 0; i
< strlen(line
); i
++, n
++) {
186 if (line
[i
] == '\t') {
190 for (; (n
) % 8 != 0; n
++) {
196 converted
[n
] = line
[i
];
206 * Print a line to stderr.
209 print_line(Line
*line
, int current
, int cols
)
213 char *content
= expand_tabs(line
->content
);
214 char *comment
= expand_tabs(line
->comment
);
216 /* clean the line in case it was not empty */
217 fputs("\033[K", stderr
);
219 /* line number if option set */
222 fputs("\033[1m", stderr
);
224 fputs("\033[1;30m", stderr
);
227 fprintf(stderr
, "%7d\033[0m ", line
->number
);
233 /* highlight current line */
235 fputs("\033[1;33m", stderr
);
238 /* print content without overflowing terminal width */
239 for (i
= 0; i
< strlen(content
) && n
< cols
; n
++, i
++) {
240 fputc(content
[i
], stderr
);
243 /* print spaces without overflowing terminal width */
244 for (i
= n
; i
<= 40 && n
< cols
; n
++, i
++) {
248 /* comments in grey */
249 fputs("\033[1;30m", stderr
);
251 /* print comment without overflowing terminal width */
252 for (i
= 0; i
< strlen(comment
) && n
< cols
; n
++, i
++) {
253 fputc(comment
[i
], stderr
);
256 fputs("\033[0m\n", stderr
);
263 * Print a header title.
271 * Print all the lines from an array of pointer to lines.
273 * The total number oflines printed shall not excess 'count'.
276 print_lines(Buffer
*buffer
, int count
, int offset
, int cols
)
278 Line
*line
= buffer
->current
;
282 /* seek back from current line to the first line to print */
283 while (line
&& i
< count
- offset
) {
284 i
= line
->matches
? i
+ 1 : i
;
287 line
= line
? line
: buffer
->first
;
289 /* print up to count lines that match the input */
290 while (line
&& j
< count
) {
292 print_line(line
, line
== buffer
->current
, cols
);
299 /* continue up to the end of the screen clearing it */
300 for (; j
< count
; j
++) {
301 fputs("\r\033[K\n", stderr
);
306 * Update the screen interface and print all candidates.
308 * This also has to clear the previous lines.
311 update_screen(Buffer
*buffer
, int count
, int offset
, int tty
)
314 ioctl(tty
, TIOCGWINSZ
, &w
);
317 print_lines(buffer
, count
, offset
, w
.ws_col
);
319 /* go up to the prompt position and update it */
320 fprintf(stderr
, "\033[%dA", count
+ 1);
321 print_prompt(buffer
, w
.ws_col
);
324 void clear_screen(int count
)
327 for (i
= 0; i
< count
+ 1; i
++) {
328 fputs("\r\033[K\n", stderr
);
331 fprintf(stderr
, "\033[%dA", count
+ 1);
335 * Set terminal to send one char at a time for interactive mode, and return the
336 * last terminal state.
339 terminal_set(int tty
)
341 struct termios termio_old
;
342 struct termios termio_new
;
344 /* set the terminal to send one key at a time. */
346 /* get the terminal's state */
347 if (tcgetattr(tty
, &termio_old
) < 0) {
348 die("Can not get terminal attributes with tcgetattr().");
351 /* create a new modified state by switching the binary flags */
352 termio_new
= termio_old
;
353 termio_new
.c_lflag
&= ~(ICANON
| ECHO
| IGNBRK
);
355 /* apply this state to current terminal now (TCSANOW) */
356 tcsetattr(tty
, TCSANOW
, &termio_new
);
362 * Listen for the user input and call the appropriate functions.
365 get_input(Buffer
*buffer
, int count
, int offset
, int tty
)
367 FILE *tty_fd
= fopen("/dev/tty", "r");
369 /* receive one character at a time from the terminal */
370 struct termios termio_old
= terminal_set(tty
);
372 /* get input char by char from the keyboard. */
373 while (do_key(fgetc(tty_fd
), buffer
)) {
374 update_screen(buffer
, count
, offset
, tty
);
377 /* resets the terminal to the previous state. */
378 tcsetattr(tty
, TCSANOW
, &termio_old
);
384 * Perform action associated with key
387 do_key(char key
, Buffer
*buffer
)
398 buffer
->input
[0] = '\0';
399 buffer
->current
= buffer
->first
;
400 filter_lines(buffer
);
404 length
= strlen(buffer
->input
) - 1;
406 for (i
= length
; i
>= 0 && isspace(buffer
->input
[i
]); i
--) {
407 buffer
->input
[i
] = '\0';
410 length
= strlen(buffer
->input
) - 1;
411 for (i
= length
; i
>= 0 && !isspace(buffer
->input
[i
]); i
--) {
412 buffer
->input
[i
] = '\0';
415 filter_lines(buffer
);
420 case CONTROL('H'): /* backspace */
421 buffer
->input
[strlen(buffer
->input
) - 1] = '\0';
422 filter_lines(buffer
);
424 if (!buffer
->current
->matches
) {
425 do_next_line(buffer
);
426 do_prev_line(buffer
);
431 do_next_line(buffer
);
435 do_prev_line(buffer
);
438 case CONTROL('I'): /* tab */
439 strcpy(buffer
->input
, buffer
->current
->content
);
441 filter_lines(buffer
);
445 case CONTROL('J'): /* enter */
446 fputs("\r\033[K", stderr
);
447 puts(buffer
->current
->content
);
452 length
= strlen(buffer
->input
);
453 buffer
->input
[length
] = key
;
454 buffer
->input
[length
+ 1] = '\0';
457 filter_lines(buffer
);
459 if (!buffer
->current
->matches
) {
460 do_next_line(buffer
);
461 do_prev_line(buffer
);
464 if (!buffer
->current
->matches
) {
465 buffer
->current
= buffer
->empty
;
473 * Set the current line to the next matching line, if any.
476 do_prev_line(Buffer
*buffer
)
478 Line
* line
= buffer
->current
;
484 buffer
->current
= line
;
492 * Set the current line to the next matching line, if any.
495 do_next_line(Buffer
*buffer
)
497 Line
* line
= buffer
->current
;
503 buffer
->current
= line
;
511 * Print the prompt, before the input, with the number of candidates that
515 print_prompt(Buffer
*buffer
, int cols
)
519 int matching
= buffer
->matching
;
520 int total
= buffer
->total
;
521 char *input
= expand_tabs(buffer
->input
);
522 char *suggest
= expand_tabs(buffer
->current
->content
);
524 /* for the '/' separator between the numbers */
527 /* count the number of digits */
528 for (i
= matching
; i
; i
/= 10, digits
++);
529 for (i
= total
; i
; i
/= 10, digits
++);
532 fputs("\r\033[K> ", stderr
);
535 /* input without overflowing terminal width */
536 for (i
= 0; i
< strlen(input
) && cols
> digits
; cols
--, i
++) {
537 fputc(input
[i
], stderr
);
540 /* save the cursor position at the end of the input */
541 fputs("\033[s", stderr
);
544 fputs("\033[1;30m", stderr
);
546 /* suggest without overflowing terminal width */
547 for (; i
< strlen(suggest
) && cols
> digits
; cols
--, i
++) {
548 fputc(suggest
[i
], stderr
);
551 /* go to the end of the line */
552 for (i
= 0; cols
> digits
; cols
--, i
++) {
556 /* total match and line count at the end of the line */
557 fprintf(stderr
, "%d/%d", matching
, total
);
559 /* restore cursor position at the end of the input */
560 fputs("\033[u", stderr
);
567 * Reset the terminal state and exit with error.
569 void die(const char *s
)
571 /* tcsetattr(STDIN_FILENO, TCSANOW, &termio_old); */
572 fprintf(stderr
, "%s\n", s
);
578 main(int argc
, char *argv
[])
581 Buffer
*buffer
= NULL
;
582 char *separator
= "";
585 int tty
= open("/dev/tty", O_RDWR
);
587 /* command line arguments */
588 for (i
= 0; i
<= argc
; i
++) {
589 if (argv
[i
][1] == '-') {
594 /* command line arguments */
595 buffer
= fill_buffer(separator
);
597 /* set the interface */
598 filter_lines(buffer
);
599 update_screen(buffer
, count
, offset
, tty
);
601 /* listen and interact to input */
602 get_input(buffer
, count
, offset
, tty
);