Started implementing completion.
[iomenu.git] / complete.c
blob95a19b653125140e98a60ac774b632843bc2241a
1 #include <ctype.h>
2 #include <fcntl.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <sys/ioctl.h>
7 #include <termios.h>
8 #include <unistd.h>
10 #include "complete.h"
11 #include "config.h"
13 enum { FALSE = 0, TRUE = 1 };
15 #define LENGTH(x) (sizeof(x) / sizeof(*x))
16 #define CONTROL(char) (char ^ 0x40)
17 #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
18 #define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
21 * Fill the buffer apropriately with the lines and headers
23 Buffer *
24 fill_buffer(char *separator)
26 /* fill buffer with string */
27 char string[LINE_SIZE];
28 Line *last = NULL;
29 Buffer *buffer = malloc(sizeof(Buffer));
30 FILE *fp = fopen("complete.c", "r");
32 if (!fp) {
33 die("Can not open file for reading.");
36 /* read the file into a doubly linked list of lines. */
37 while (fgets(string, LINE_SIZE, fp)) {
38 buffer->total++;
39 last = add_line(buffer, buffer->total, string, separator, last);
42 /* set the buffer stats. */
43 buffer->current = buffer->first;
45 return buffer;
49 * Parse the line content to determine if it is a header and identify the
50 * separator if any.
52 Line *
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 */
67 if (sep) {
68 line->comment = malloc((strlen(s) - pos) * sizeof(char));
69 strcpy(line->comment, s + pos);
70 } else {
71 line->comment = "";
74 return line;
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
87 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);
93 line->next = NULL;
94 line->prev = NULL;
95 buffer->last = line;
96 line->number = number;
98 /* interlink with previous line if exists */
99 if (number == 1) {
100 buffer->first = line;
101 } else {
102 prev->next = line;
103 line->prev = prev;
106 return line;
110 * Set buffer->candidates to an array of lines that match and update
111 * buffer->matching to number of matching candidates.
113 void
114 filter_lines(Buffer *buffer)
116 Line * line = buffer->first;
118 buffer->matching = 0;
120 while (line) {
121 if (line_match_input(line, buffer->input)) {
122 line->matches = TRUE;
123 buffer->matching++;
124 } else {
125 line->matches = FALSE;
128 line = line->next;
133 * Check if line matches and return TRUE if so
136 line_match_input(Line *line, char *input)
138 if (strncmp(input, line->content, strlen(input)) == 0) {
139 return TRUE;
140 } else {
141 return FALSE;
146 * Replace tab as a multiple of 8 spaces in a line.
148 char *
149 expand_tabs(char *line)
151 size_t i, n;
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') {
156 converted[n] = ' ';
157 n++;
159 for (; (n) % 8 != 0; n++) {
160 converted[n] = ' ';
163 n--;
164 } else {
165 converted[n] = line[i];
169 converted[n] = '\0';
171 return converted;
175 * Print a line to stderr.
177 void
178 print_line(Line *line, int current, int cols)
180 size_t i;
181 int n = 0;
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 */
189 if (TRUE) {
190 if (current) {
191 fputs("\033[1m", stderr);
192 } else {
193 fputs("\033[1;30m", stderr);
196 fprintf(stderr, "%7d\033[0m ", line->number);
199 n += 8;
202 /* highlight current line */
203 if (current) {
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++) {
214 fputc(' ', stderr);
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);
227 free(content);
228 free(comment);
232 * Print a header title.
234 void
235 print_header()
240 * Print all the lines from an array of pointer to lines.
242 * The total number oflines printed shall not excess 'count'.
244 void
245 print_lines(Buffer *buffer, int count, int offset, int cols)
247 Line *line = buffer->current;
248 int i = 0;
249 int j = 0;
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;
254 line = line->prev;
256 line = line ? line : buffer->first;
258 /* print up to count lines that match the input */
259 while (line && j < count) {
260 if (line->matches) {
261 print_line(line, line == buffer->current, cols);
262 j++;
265 line = line->next;
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.
279 void
280 update_screen(Buffer *buffer, int count, int offset, int tty)
282 struct winsize w;
283 ioctl(tty, TIOCGWINSZ, &w);
285 fputs("\n", stderr);
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)
295 int i;
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.
307 struct termios
308 terminal_set(int tty)
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(tty, &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(tty, TCSANOW, &termio_new);
327 return termio_old;
331 * Listen for the user input and call the appropriate functions.
333 void
334 get_input(Buffer *buffer, int count, int offset, int tty)
336 FILE *tty_fd = fopen("/dev/tty", "r");
338 /* receive one character at a time from the terminal */
339 struct termios termio_old = terminal_set(tty);
341 /* get input char by char from the keyboard. */
342 while (do_key(fgetc(tty_fd), buffer)) {
343 filter_lines(buffer);
344 update_screen(buffer, count, offset, tty);
347 /* resets the terminal to the previous state. */
348 tcsetattr(tty, TCSANOW, &termio_old);
350 fclose(tty_fd);
354 * Perform action associated with key
357 do_key(char key, Buffer *buffer)
359 size_t length;
360 int i;
361 Line *line = NULL;
363 switch (key) {
365 case CONTROL('C'):
366 return FALSE;
368 case CONTROL('U'):
369 buffer->input[0] = '\0';
370 break;
372 case CONTROL('W'):
373 length = strlen(buffer->input) - 1;
375 for (i = length; i >= 0 && isspace(buffer->input[i]); i--) {
376 buffer->input[i] = '\0';
379 length = strlen(buffer->input) - 1;
380 for (i = length; i >= 0 && !isspace(buffer->input[i]); i--) {
381 buffer->input[i] = '\0';
384 break;
386 case CONTROL('H'):
387 case 127: /* backspace */
388 buffer->input[strlen(buffer->input) - 1] = '\0';
389 break;
391 case CONTROL('N'):
392 line = buffer->current;
393 while (line->next) {
394 line = line->next;
396 if (line->matches) {
397 buffer->current = line;
398 break;
402 break;
404 case CONTROL('P'):
405 if (buffer->current->prev) {
406 buffer->current = buffer->current->prev;
408 break;
410 case CONTROL('M'):
411 case CONTROL('J'): /* enter */
412 fputs("\r\033[K", stderr);
413 puts(buffer->current->content);
414 return FALSE;
416 default:
417 if (isprint(key)) {
418 length = strlen(buffer->input);
419 buffer->input[length] = key;
420 buffer->input[length + 1] = '\0';
424 return TRUE;
428 * Print the prompt, before the input, with the number of candidates that
429 * match.
431 void
432 print_prompt(Buffer *buffer, int cols)
434 size_t i;
435 int digits = 0;
436 int matching = buffer->matching;
437 int total = buffer->total;
438 char *input = expand_tabs(buffer->input);
439 char *suggest = expand_tabs(buffer->current->content);
441 /* for the '/' separator between the numbers */
442 cols--;
444 /* count the number of digits */
445 for (i = matching; i; i /= 10, digits++);
446 for (i = total; i; i /= 10, digits++);
448 /* actual prompt */
449 fputs("\r\033[K> ", stderr);
450 cols -= 2;
452 /* input without overflowing terminal width */
453 for (i = 0; i < strlen(input) && cols > digits; cols--, i++) {
454 fputc(input[i], stderr);
457 /* save the cursor position at the end of the input */
458 fputs("\033[s", stderr);
460 /* grey */
461 fputs("\033[1;30m", stderr);
463 /* suggest without overflowing terminal width */
464 for (i = 0; i < strlen(suggest) && cols > digits; cols--, i++) {
465 fputc(suggest[i], stderr);
468 /* go to the end of the line */
469 for (i = 0; cols > digits; cols--, i++) {
470 fputc(' ', stderr);
473 /* total match and line count at the end of the line */
474 fprintf(stderr, "%d/%d", matching, total);
476 /* restore cursor position at the end of the input */
477 fputs("\033[u", stderr);
479 free(input);
480 free(suggest);
484 * Reset the terminal state and exit with error.
486 void die(const char *s)
488 /* tcsetattr(STDIN_FILENO, TCSANOW, &termio_old); */
489 fprintf(stderr, "%s\n", s);
490 exit(EXIT_FAILURE);
495 main(int argc, char *argv[])
497 int i;
498 Buffer *buffer = NULL;
499 char *separator = "* ";
500 int count = 30;
501 int offset = 3;
502 int tty = open("/dev/tty", O_RDWR);
504 /* command line arguments */
505 for (i = 0; i <= argc; i++) {
506 if (argv[i][1] == '-') {
511 /* command line arguments */
512 buffer = fill_buffer(separator);
514 /* set the interface */
515 filter_lines(buffer);
516 update_screen(buffer, count, offset, tty);
518 /* listen and interact to input */
519 get_input(buffer, count, offset, tty);
521 clear_screen(count);
523 return 0;