11 enum { FALSE
= 0, TRUE
= 1 };
13 #define LENGTH(x) (sizeof(x) / sizeof(*x))
14 #define CONTROL(char) (char ^ 0x40)
17 * Fill the buffer apropriately with the lines and headers
20 fill_buffer(char *separator
)
22 /* fill buffer with string */
23 char string
[LINE_SIZE
];
25 Buffer
*buffer
= malloc(sizeof(Buffer
));
26 FILE *fp
= fopen("complete.c", "r");
29 die("Can not open file for reading.");
32 /* read the file into a doubly linked list of lines. */
33 while (fgets(string
, LINE_SIZE
, fp
)) {
36 last
= add_line(buffer
, buffer
->total
, string
, separator
, last
);
39 /* set the buffer stats. */
40 buffer
->current
= buffer
->first
;
46 * Parse the line content to determine if it is a header and identify the
50 parse_line(char *s
, char *separator
)
52 Line
*line
= malloc(sizeof(Line
));
53 char *sep
= strstr(s
, separator
);
54 int pos
= !sep
? (int) strlen(s
) - 1 : (int) (sep
- s
);
56 /* strip trailing newline */
57 s
[strlen(s
) - 1] = '\0';
59 /* fill line->content */
60 line
->content
= malloc((pos
+ 1) * sizeof(char));
61 strncpy(line
->content
, s
, pos
);
63 /* fill line->comment */
65 line
->comment
= malloc((strlen(s
) - pos
) * sizeof(char));
66 strcpy(line
->comment
, s
+ pos
);
75 * Add a line to the end of the current buffer.
77 * This requires to create a new line with a link to the previous line
78 * and to NULL as the next line.
80 * The previous line's 'next' should be relinked to this new line.
82 * The header's last line have to point to this last line
85 add_line(Buffer
*buffer
, int number
, char *string
, char *separator
, Line
*prev
)
87 /* allocate new line */
88 Line
*line
= malloc(sizeof(Line
));
89 line
= parse_line(string
, separator
);
93 line
->number
= number
;
95 /* interlink with previous line if exists */
107 * Set buffer->candidates to an array of lines that match and update
108 * buffer->matching to number of matching candidates.
111 filter_lines(Buffer
*buffer
)
113 Line
* line
= buffer
->first
;
115 buffer
->matching
= 0;
118 if (line_match_input(line
, "test")) {
119 line
->matches
= TRUE
;
122 line
->matches
= FALSE
;
130 * Check if line matches and return TRUE if so
133 line_match_input(Line
*line
, char *input
)
135 if (line
&& input
/* match */) {
143 * Print a line to stderr
146 print_line(Line
*line
, int current
)
148 /* clean the line in case it was not empty */
149 fputs("\033[K", stderr
);
151 /* line number if option set */
154 fputs("\033[1m", stderr
);
156 fputs("\033[1;30m", stderr
);
159 fprintf(stderr
, "%7d\033[0m", line
->number
);
162 /* highlight current line */
164 fputs("\033[1;33m", stderr
);
167 fprintf(stderr
, " %-30s\033[1;30m%s", line
->content
, line
->comment
);
169 fputs("\033[0m\n", stderr
);
174 * Print a header title.
182 * Print all the lines from an array of pointer to lines.
184 * The total number oflines printed shall not excess 'count'.
187 print_lines(Buffer
*buffer
, int count
, int offset
)
189 Line
*line
= buffer
->current
;
193 /* seek back from current line to the first line to print */
194 while (line
&& i
< count
- offset
) {
195 i
= line
->matches
? i
+ 1 : i
;
198 line
= line
? line
: buffer
->first
;
200 /* print up to count lines that match the input */
201 while (line
&& j
< count
) {
203 print_line(line
, line
== buffer
->current
);
210 /* continue up to the end of the screen clearing it */
211 for (; j
< count
; j
++) {
212 fputs("\r\033[K\n", stderr
);
217 * Update the screen interface and print all candidates.
219 * This also has to clear the previous lines.
222 update_screen(Buffer
*buffer
, int count
, int offset
)
224 fprintf(stderr
, "\n");
225 print_lines(buffer
, count
, offset
);
227 /* go up to the prompt position and update it */
228 fprintf(stderr
, "\033[%dA", count
+ 1);
229 print_prompt(buffer
->matching
, buffer
->total
, buffer
->input
);
232 void clear_screen(int count
)
235 for (i
= 0; i
< count
+ 1; i
++) {
236 fputs("\r\033[K\n", stderr
);
239 fprintf(stderr
, "\033[%dA", count
+ 1);
243 * Set terminal to send one char at a time for interactive mode, and return the
244 * last terminal state.
250 struct termios termio_old
;
251 struct termios termio_new
;
253 /* set the terminal to send one key at a time. */
255 /* get the terminal's state */
256 if (tcgetattr(0, &termio_old
) < 0) {
257 die("Can not get terminal attributes with tcgetattr().");
260 /* create a new modified state by switching the binary flags */
261 termio_new
= termio_old
;
262 termio_new
.c_lflag
&= ~(ICANON
| ECHO
| IGNBRK
);
264 /* apply this state to current terminal now (TCSANOW) */
265 tcsetattr(STDIN_FILENO
, TCSANOW
, &termio_new
);
271 * Listen for the user input and call the appropriate functions.
274 get_input(Buffer
*buffer
, int count
, int offset
)
276 /* receive one character at a time from the terminal */
277 struct termios termio_old
= terminal_set();
279 /* get input char by char from the keyboard. */
280 while (do_key(getchar(), buffer
)) {
281 filter_lines(buffer
);
282 update_screen(buffer
, count
, offset
);
285 /* resets the terminal to the previous state. */
286 tcsetattr(STDIN_FILENO
, TCSANOW
, &termio_old
);
290 * Perform action associated with key
293 do_key(char key
, Buffer
*buffer
)
304 buffer
->input
[0] = '\0';
308 length
= strlen(buffer
->input
) - 1;
310 for (i
= length
; i
>= 0 && isspace(buffer
->input
[i
]); i
--) {
311 buffer
->input
[i
] = '\0';
314 length
= strlen(buffer
->input
) - 1;
315 for (i
= length
; i
>= 0 && !isspace(buffer
->input
[i
]); i
--) {
316 buffer
->input
[i
] = '\0';
322 case 127: /* backspace */
323 buffer
->input
[strlen(buffer
->input
) - 1] = '\0';
327 if (buffer
->current
->next
) {
328 buffer
->current
= buffer
->current
->next
;
334 if (buffer
->current
->prev
) {
335 buffer
->current
= buffer
->current
->prev
;
340 case CONTROL('J'): /* enter */
341 fputs("\r\033[K", stderr
);
342 puts(buffer
->current
->content
);
347 length
= strlen(buffer
->input
);
348 buffer
->input
[length
] = key
;
349 buffer
->input
[length
+ 1] = '\0';
357 * Print the prompt, before the input, with the number of candidates that
361 print_prompt(int matching
, int total
, char *input
)
363 fprintf(stderr
, "\r\033[K%5d/%-5d > %s", matching
, total
, input
);
367 * Reset the terminal state and exit with error.
369 void die(const char *s
)
371 /* tcsetattr(STDIN_FILENO, TCSANOW, &termio_old); */
372 fprintf(stderr
, "%s\n", s
);
378 main(int argc
, char *argv
[])
381 Buffer
*buffer
= NULL
;
382 char *separator
= "* ";
387 /* command line arguments */
388 for (i
= 0; i
<= argc
; i
++) {
389 if (argv
[i
][1] == '-') {
394 /* command line arguments */
395 buffer
= fill_buffer(separator
);
397 /* set the interface */
398 filter_lines(buffer
);
399 update_screen(buffer
, count
, offset
);
401 /* listen and interact to input */
402 get_input(buffer
, count
, offset
);