Numbers printed at the right, handle long input.
[iomenu.git] / complete.c
blob593981364aeefcab10684b103e953bc2f80c67c4
1 #include <ctype.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <sys/ioctl.h>
6 #include <termios.h>
7 #include <unistd.h>
9 #include "complete.h"
10 #include "config.h"
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
22 Buffer *
23 fill_buffer(char *separator)
25 /* fill buffer with string */
26 char string[LINE_SIZE];
27 Line *last = NULL;
28 Buffer *buffer = malloc(sizeof(Buffer));
29 FILE *fp = fopen("complete.c", "r");
31 if (!fp) {
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)) {
37 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, "test")) {
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 (line && input /* match */) {
139 return TRUE;
140 } else {
141 return FALSE;
146 * Print a line to stderr.
148 void
149 print_line(Line *line, int current, int column)
151 int chars = 0;
152 size_t i;
154 /* clean the line in case it was not empty */
155 fputs("\033[K", stderr);
157 /* line number if option set */
158 if (TRUE) {
159 if (current) {
160 fputs("\033[1m", stderr);
161 } else {
162 fputs("\033[1;30m", stderr);
165 fprintf(stderr, "%7d\033[0m ", line->number);
168 chars += 8;
171 /* highlight current line */
172 if (current) {
173 fputs("\033[1;33m", stderr);
176 /* print content without overflowing terminal width */
177 for (i = 0; i < strlen(line->content) && chars < column; chars++, i++) {
179 /* handle tab as 8 spaces for output */
180 if (line->content[i] == '\t') {
181 fputc(' ', stderr);
182 chars++;
184 for (; chars % 8 != 0; chars++) {
185 fputc(' ', stderr);
187 } else {
188 fputc(line->content[i], stderr);
192 /* print spaces without overflowing terminal width */
193 for ( ; chars <= 30 && chars < column; chars++, i++) {
194 fputc(' ', stderr);
197 /* comments in grey */
198 fputs("\033[1;30m", stderr);
200 /* print comment without overflowing terminal width */
201 for (i = 0; i < strlen(line->comment) && chars < column; chars++, i++) {
203 /* handle tab as 8 spaces for output */
204 if (line->comment[i] == '\t') {
205 fputc(' ', stderr);
206 chars++;
208 for (; chars % 8 != 7; chars++) {
209 fputc(' ', stderr);
211 } else {
212 fputc(line->comment[i], stderr);
216 fputs("\033[0m\n", stderr);
221 * Print a header title.
223 void
224 print_header()
229 * Print all the lines from an array of pointer to lines.
231 * The total number oflines printed shall not excess 'count'.
233 void
234 print_lines(Buffer *buffer, int count, int offset, int columns)
236 Line *line = buffer->current;
237 int i = 0;
238 int j = 0;
240 /* seek back from current line to the first line to print */
241 while (line && i < count - offset) {
242 i = line->matches ? i + 1 : i;
243 line = line->prev;
245 line = line ? line : buffer->first;
247 /* print up to count lines that match the input */
248 while (line && j < count) {
249 if (line->matches) {
250 print_line(line, line == buffer->current, columns);
251 j++;
254 line = line->next;
257 /* continue up to the end of the screen clearing it */
258 for (; j < count; j++) {
259 fputs("\r\033[K\n", stderr);
264 * Update the screen interface and print all candidates.
266 * This also has to clear the previous lines.
268 void
269 update_screen(Buffer *buffer, int count, int offset)
271 struct winsize w;
272 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
274 fprintf(stderr, "\n");
275 print_lines(buffer, count, offset, w.ws_col);
277 /* go up to the prompt position and update it */
278 fprintf(stderr, "\033[%dA", count + 1);
279 print_prompt(buffer->matching, buffer->total, buffer->input, w.ws_col);
282 void clear_screen(int count)
284 int i;
285 for (i = 0; i < count + 1; i++) {
286 fputs("\r\033[K\n", stderr);
289 fprintf(stderr, "\033[%dA", count + 1);
293 * Set terminal to send one char at a time for interactive mode, and return the
294 * last terminal state.
296 struct termios
297 terminal_set()
299 struct termios termio_old;
300 struct termios termio_new;
302 /* set the terminal to send one key at a time. */
304 /* get the terminal's state */
305 if (tcgetattr(0, &termio_old) < 0) {
306 die("Can not get terminal attributes with tcgetattr().");
309 /* create a new modified state by switching the binary flags */
310 termio_new = termio_old;
311 termio_new.c_lflag &= ~(ICANON | ECHO | IGNBRK);
313 /* apply this state to current terminal now (TCSANOW) */
314 tcsetattr(STDIN_FILENO, TCSANOW, &termio_new);
316 return termio_old;
320 * Listen for the user input and call the appropriate functions.
322 void
323 get_input(Buffer *buffer, int count, int offset)
325 /* receive one character at a time from the terminal */
326 struct termios termio_old = terminal_set();
328 /* get input char by char from the keyboard. */
329 while (do_key(getchar(), buffer)) {
330 filter_lines(buffer);
331 update_screen(buffer, count, offset);
334 /* resets the terminal to the previous state. */
335 tcsetattr(STDIN_FILENO, TCSANOW, &termio_old);
339 * Perform action associated with key
342 do_key(char key, Buffer *buffer)
344 size_t length;
345 int i;
347 switch (key) {
349 case CONTROL('C'):
350 return FALSE;
352 case CONTROL('U'):
353 buffer->input[0] = '\0';
354 break;
356 case CONTROL('W'):
357 length = strlen(buffer->input) - 1;
359 for (i = length; i >= 0 && isspace(buffer->input[i]); i--) {
360 buffer->input[i] = '\0';
363 length = strlen(buffer->input) - 1;
364 for (i = length; i >= 0 && !isspace(buffer->input[i]); i--) {
365 buffer->input[i] = '\0';
368 break;
370 case CONTROL('H'):
371 case 127: /* backspace */
372 buffer->input[strlen(buffer->input) - 1] = '\0';
373 break;
375 case CONTROL('N'):
376 if (buffer->current->next) {
377 buffer->current = buffer->current->next;
380 break;
382 case CONTROL('P'):
383 if (buffer->current->prev) {
384 buffer->current = buffer->current->prev;
386 break;
388 case CONTROL('M'):
389 case CONTROL('J'): /* enter */
390 fputs("\r\033[K", stderr);
391 puts(buffer->current->content);
392 return FALSE;
394 default:
395 if (isprint(key)) {
396 length = strlen(buffer->input);
397 buffer->input[length] = key;
398 buffer->input[length + 1] = '\0';
402 return TRUE;
406 * Print the prompt, before the input, with the number of candidates that
407 * match.
409 void
410 print_prompt(int matching, int total, char *input, int columns)
412 size_t i;
413 int digits = 0;
415 /* for the '/' separator between the numbers */
416 columns--;
418 /* count the number of digits */
419 for (i = matching; i; i /= 10, digits++);
420 for (i = total; i; i /= 10, digits++);
422 /* actual prompt */
423 fputs("\r\033[K> ", stderr);
424 columns -= 2;
426 for (i = 0; i < strlen(input) && columns > digits; columns--, i++) {
427 fputc(input[i], stderr);
430 /* save the cursor position at the end of the input */
431 fputs("\033[s", stderr);
433 /* go to the end of the line */
434 for (i = 0; columns > digits; columns--, i++) {
435 fputc(' ', stderr);
438 /* total match and line count at the end of the line */
439 fprintf(stderr, "%d/%d", matching, total);
441 /* restore cursor position at the end of the input */
442 fputs("\033[u", stderr);
446 * Reset the terminal state and exit with error.
448 void die(const char *s)
450 /* tcsetattr(STDIN_FILENO, TCSANOW, &termio_old); */
451 fprintf(stderr, "%s\n", s);
452 exit(EXIT_FAILURE);
457 main(int argc, char *argv[])
459 int i;
460 Buffer *buffer = NULL;
461 char *separator = "* ";
462 int count = 30;
463 int offset = 3;
466 /* command line arguments */
467 for (i = 0; i <= argc; i++) {
468 if (argv[i][1] == '-') {
473 /* command line arguments */
474 buffer = fill_buffer(separator);
476 /* set the interface */
477 filter_lines(buffer);
478 update_screen(buffer, count, offset);
480 /* listen and interact to input */
481 get_input(buffer, count, offset);
483 clear_screen(count);
485 return 0;