12 enum { FALSE
= 0, TRUE
= 1 };
14 #define LENGTH(x) (sizeof(x) / sizeof(*x))
15 #define CONTROL(char) (char ^ 0x40)
16 #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
17 #define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
20 * Fill the buffer apropriately with the lines and headers
23 fill_buffer(char *separator
)
25 /* fill buffer with string */
26 char string
[LINE_SIZE
];
28 Buffer
*buffer
= malloc(sizeof(Buffer
));
29 FILE *fp
= fopen("complete.c", "r");
32 die("Can not open file for reading.");
35 /* read the file into a doubly linked list of lines. */
36 while (fgets(string
, LINE_SIZE
, fp
)) {
39 last
= add_line(buffer
, buffer
->total
, string
, separator
, last
);
42 /* set the buffer stats. */
43 buffer
->current
= buffer
->first
;
49 * Parse the line content to determine if it is a header and identify the
53 parse_line(char *s
, char *separator
)
55 Line
*line
= malloc(sizeof(Line
));
56 char *sep
= strstr(s
, separator
);
57 int pos
= !sep
? (int) strlen(s
) - 1 : (int) (sep
- s
);
59 /* strip trailing newline */
60 s
[strlen(s
) - 1] = '\0';
62 /* fill line->content */
63 line
->content
= malloc((pos
+ 1) * sizeof(char));
64 strncpy(line
->content
, s
, pos
);
66 /* fill line->comment */
68 line
->comment
= malloc((strlen(s
) - pos
) * sizeof(char));
69 strcpy(line
->comment
, s
+ pos
);
78 * Add a line to the end of the current buffer.
80 * This requires to create a new line with a link to the previous line
81 * and to NULL as the next line.
83 * The previous line's 'next' should be relinked to this new line.
85 * The header's last line have to point to this last line
88 add_line(Buffer
*buffer
, int number
, char *string
, char *separator
, Line
*prev
)
90 /* allocate new line */
91 Line
*line
= malloc(sizeof(Line
));
92 line
= parse_line(string
, separator
);
96 line
->number
= number
;
98 /* interlink with previous line if exists */
100 buffer
->first
= line
;
110 * Set buffer->candidates to an array of lines that match and update
111 * buffer->matching to number of matching candidates.
114 filter_lines(Buffer
*buffer
)
116 Line
* line
= buffer
->first
;
118 buffer
->matching
= 0;
121 if (line_match_input(line
, "test")) {
122 line
->matches
= TRUE
;
125 line
->matches
= FALSE
;
133 * Check if line matches and return TRUE if so
136 line_match_input(Line
*line
, char *input
)
138 if (line
&& input
/* match */) {
146 * Replace tab as a multiple of 8 spaces in a line.
149 expand_tabs(char *line
)
152 char *converted
= malloc(sizeof(char) * (strlen(line
) * 8 + 1));
154 for (i
= 0, n
= 0; i
< strlen(line
); i
++, n
++) {
155 if (line
[i
] == '\t') {
159 for (; (n
) % 8 != 0; n
++) {
165 converted
[n
] = line
[i
];
175 * Print a line to stderr.
178 print_line(Line
*line
, int current
, int cols
)
182 char *content
= expand_tabs(line
->content
);
183 char *comment
= expand_tabs(line
->comment
);
185 /* clean the line in case it was not empty */
186 fputs("\033[K", stderr
);
188 /* line number if option set */
191 fputs("\033[1m", stderr
);
193 fputs("\033[1;30m", stderr
);
196 fprintf(stderr
, "%7d\033[0m ", line
->number
);
202 /* highlight current line */
204 fputs("\033[1;33m", stderr
);
207 /* print content without overflowing terminal width */
208 for (i
= 0; i
< strlen(content
) && n
< cols
; n
++, i
++) {
209 fputc(content
[i
], stderr
);
212 /* print spaces without overflowing terminal width */
213 for (i
= n
; i
<= 40 && n
< cols
; n
++, i
++) {
217 /* comments in grey */
218 fputs("\033[1;30m", stderr
);
220 /* print comment without overflowing terminal width */
221 for (i
= 0; i
< strlen(comment
) && n
< cols
; n
++, i
++) {
222 fputc(comment
[i
], stderr
);
225 fputs("\033[0m\n", stderr
);
232 * Print a header title.
240 * Print all the lines from an array of pointer to lines.
242 * The total number oflines printed shall not excess 'count'.
245 print_lines(Buffer
*buffer
, int count
, int offset
, int cols
)
247 Line
*line
= buffer
->current
;
251 /* seek back from current line to the first line to print */
252 while (line
&& i
< count
- offset
) {
253 i
= line
->matches
? i
+ 1 : i
;
256 line
= line
? line
: buffer
->first
;
258 /* print up to count lines that match the input */
259 while (line
&& j
< count
) {
261 print_line(line
, line
== buffer
->current
, cols
);
268 /* continue up to the end of the screen clearing it */
269 for (; j
< count
; j
++) {
270 fputs("\r\033[K\n", stderr
);
275 * Update the screen interface and print all candidates.
277 * This also has to clear the previous lines.
280 update_screen(Buffer
*buffer
, int count
, int offset
)
283 ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
);
286 print_lines(buffer
, count
, offset
, w
.ws_col
);
288 /* go up to the prompt position and update it */
289 fprintf(stderr
, "\033[%dA", count
+ 1);
290 print_prompt(buffer
, w
.ws_col
);
293 void clear_screen(int count
)
296 for (i
= 0; i
< count
+ 1; i
++) {
297 fputs("\r\033[K\n", stderr
);
300 fprintf(stderr
, "\033[%dA", count
+ 1);
304 * Set terminal to send one char at a time for interactive mode, and return the
305 * last terminal state.
310 struct termios termio_old
;
311 struct termios termio_new
;
313 /* set the terminal to send one key at a time. */
315 /* get the terminal's state */
316 if (tcgetattr(0, &termio_old
) < 0) {
317 die("Can not get terminal attributes with tcgetattr().");
320 /* create a new modified state by switching the binary flags */
321 termio_new
= termio_old
;
322 termio_new
.c_lflag
&= ~(ICANON
| ECHO
| IGNBRK
);
324 /* apply this state to current terminal now (TCSANOW) */
325 tcsetattr(STDIN_FILENO
, TCSANOW
, &termio_new
);
331 * Listen for the user input and call the appropriate functions.
334 get_input(Buffer
*buffer
, int count
, int offset
)
336 /* receive one character at a time from the terminal */
337 struct termios termio_old
= terminal_set();
339 /* get input char by char from the keyboard. */
340 while (do_key(getchar(), buffer
)) {
341 filter_lines(buffer
);
342 update_screen(buffer
, count
, offset
);
345 /* resets the terminal to the previous state. */
346 tcsetattr(STDIN_FILENO
, TCSANOW
, &termio_old
);
350 * Perform action associated with key
353 do_key(char key
, Buffer
*buffer
)
364 buffer
->input
[0] = '\0';
368 length
= strlen(buffer
->input
) - 1;
370 for (i
= length
; i
>= 0 && isspace(buffer
->input
[i
]); i
--) {
371 buffer
->input
[i
] = '\0';
374 length
= strlen(buffer
->input
) - 1;
375 for (i
= length
; i
>= 0 && !isspace(buffer
->input
[i
]); i
--) {
376 buffer
->input
[i
] = '\0';
382 case 127: /* backspace */
383 buffer
->input
[strlen(buffer
->input
) - 1] = '\0';
387 if (buffer
->current
->next
) {
388 buffer
->current
= buffer
->current
->next
;
394 if (buffer
->current
->prev
) {
395 buffer
->current
= buffer
->current
->prev
;
400 case CONTROL('J'): /* enter */
401 fputs("\r\033[K", stderr
);
402 puts(buffer
->current
->content
);
407 length
= strlen(buffer
->input
);
408 buffer
->input
[length
] = key
;
409 buffer
->input
[length
+ 1] = '\0';
417 * Print the prompt, before the input, with the number of candidates that
421 print_prompt(Buffer
*buffer
, int cols
)
425 int matching
= buffer
->matching
;
426 int total
= buffer
->total
;
427 char *input
= expand_tabs(buffer
->input
);
428 char *suggest
= expand_tabs(buffer
->current
->content
);
430 /* for the '/' separator between the numbers */
433 /* count the number of digits */
434 for (i
= matching
; i
; i
/= 10, digits
++);
435 for (i
= total
; i
; i
/= 10, digits
++);
438 fputs("\r\033[K> ", stderr
);
441 /* input without overflowing terminal width */
442 for (i
= 0; i
< strlen(input
) && cols
> digits
; cols
--, i
++) {
443 fputc(input
[i
], stderr
);
446 /* save the cursor position at the end of the input */
447 fputs("\033[s", stderr
);
450 fputs("\033[1;30m", stderr
);
452 /* suggest without overflowing terminal width */
453 for (i
= 0; i
< strlen(suggest
) && cols
> digits
; cols
--, i
++) {
454 fputc(suggest
[i
], stderr
);
457 /* go to the end of the line */
458 for (i
= 0; cols
> digits
; cols
--, i
++) {
462 /* total match and line count at the end of the line */
463 fprintf(stderr
, "%d/%d", matching
, total
);
465 /* restore cursor position at the end of the input */
466 fputs("\033[u", stderr
);
473 * Reset the terminal state and exit with error.
475 void die(const char *s
)
477 /* tcsetattr(STDIN_FILENO, TCSANOW, &termio_old); */
478 fprintf(stderr
, "%s\n", s
);
484 main(int argc
, char *argv
[])
487 Buffer
*buffer
= NULL
;
488 char *separator
= "* ";
493 /* command line arguments */
494 for (i
= 0; i
<= argc
; i
++) {
495 if (argv
[i
][1] == '-') {
500 /* command line arguments */
501 buffer
= fill_buffer(separator
);
503 /* set the interface */
504 filter_lines(buffer
);
505 update_screen(buffer
, count
, offset
);
507 /* listen and interact to input */
508 get_input(buffer
, count
, offset
);