Automatic commit
[selector.git] / selector.cc
1
2 ///////////////////////////////////////////////////////////////////////////
3 // START_IP_HEADER                                                       //
4 //                                                                       //
5 // This program is free software: you can redistribute it and/or modify  //
6 // it under the terms of the version 3 of the GNU General Public License //
7 // as published by the Free Software Foundation.                         //
8 //                                                                       //
9 // This program is distributed in the hope that it will be useful, but   //
10 // WITHOUT ANY WARRANTY; without even the implied warranty of            //
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      //
12 // General Public License for more details.                              //
13 //                                                                       //
14 // You should have received a copy of the GNU General Public License     //
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.  //
16 //                                                                       //
17 // Written by and Copyright (C) Francois Fleuret                         //
18 // Contact <francois.fleuret@idiap.ch> for comments & bug reports        //
19 //                                                                       //
20 // END_IP_HEADER                                                         //
21 ///////////////////////////////////////////////////////////////////////////
22
23 #include <stdio.h>
24 #include <ncurses.h>
25 #include <iostream>
26 #include <fstream>
27 #include <string.h>
28
29 using namespace std;
30
31 int buffer_size = 1024;
32 int nb_lines_max = 100000;
33
34 int match(char *string, char *regexp) {
35   return strstr(string, regexp) != 0;
36 }
37
38 void check(int condition, const char *message) {
39   if(!condition) {
40     echo();
41     curs_set(1);
42     endwin();
43     cout << message << endl;
44   }
45 }
46
47 int previous_visible(int current_line, int nb_lines, char **lines, char *regexp) {
48   int line = current_line - 1;
49   while(line >= 0 && !match(lines[line], regexp)) line--;
50   return line;
51 }
52
53 int next_visible(int current_line, int nb_lines, char **lines, char *regexp) {
54   int line = current_line + 1;
55   while(line < nb_lines && !match(lines[line], regexp)) line++;
56
57   if(line < nb_lines)
58     return line;
59   else
60     return -1;
61 }
62
63 void update_screen(int *current_line, int motion,
64                    int nb_lines, char **lines,
65                    char *regexp, int noblink) {
66
67   char buffer[buffer_size];
68
69   int console_width = getmaxx(stdscr);
70   int console_height = getmaxy(stdscr);
71
72   int nb_printed_lines = 1, last_printer_line = -1;
73
74   // First, we find a visible line. In priority: The current, or the
75   // first visible after it, or the first visible before it.
76
77   int new_line;
78   if(match(lines[*current_line], regexp)) {
79     new_line = *current_line;
80   } else {
81     new_line = next_visible(*current_line, nb_lines, lines, regexp);
82     if(new_line < 0) {
83       new_line = previous_visible(*current_line, nb_lines, lines, regexp);
84     }
85   }
86
87   // If we found a visible line and we should move, let's move
88
89   if(new_line >= 0 && motion != 0) {
90     int l = new_line;
91     l += motion;
92
93     if(motion > 0) {
94       // We want to go down, let's find the first visible line below
95       l = next_visible(new_line, nb_lines, lines, regexp);
96       if(l >= 0) {
97         new_line = l;
98       }
99     } else {
100       // We want to go up, let's find the first visible line above
101       l = previous_visible(new_line, nb_lines, lines, regexp);
102       if(l >= 0) {
103         new_line = l;
104       }
105     }
106   }
107
108   if(!noblink) {
109     clear();
110   }
111
112   use_default_colors();
113
114   addstr("\n");
115
116   check(new_line < nb_lines, "Ouch!");
117
118   // Here new_line is either a line number matching the regexp, or -1
119
120   if(new_line >= 0) {
121
122     int first_line = new_line, last_line = new_line, nb_match = 1;
123
124     while(nb_match < console_height-1 && (first_line > 0 || last_line < nb_lines - 1)) {
125
126       if(first_line > 0) {
127         first_line--;
128         while(first_line > 0 && !match(lines[first_line], regexp)) {
129           first_line--;
130         }
131         if(match(lines[first_line], regexp)) {
132           nb_match++;
133         }
134       }
135
136       if(last_line < nb_lines - 1) {
137         last_line++;
138         while(last_line < nb_lines - 1 && !match(lines[last_line], regexp)) {
139           last_line++;
140         }
141
142         if(match(lines[last_line], regexp)) {
143           nb_match++;
144         }
145       }
146     }
147
148     check(first_line >= 0 && last_line < nb_lines, "ouch2");
149
150     for(int l = first_line; l <= last_line; l++) {
151       if(match(lines[l], regexp)) {
152         int k = 0;
153
154         while(lines[l][k] && k < buffer_size - 2 && k < console_width - 1) {
155           buffer[k] = lines[l][k];
156           k++;
157         }
158
159         if(noblink) {
160           while(k < console_width - 1) {
161             buffer[k++] = ' ';
162           }
163         }
164
165         buffer[k++] = '\n';
166         buffer[k++] = '\0';
167
168         if(l == new_line) {
169           attron(COLOR_PAIR(2));
170           addstr(buffer);
171           attroff(COLOR_PAIR(2));
172         } else {
173           addstr(buffer);
174         }
175
176         last_printer_line = l;
177         nb_printed_lines++;
178       }
179     }
180
181     check(nb_printed_lines != nb_match, "ouch3");
182
183     if(motion != 0) {
184       *current_line = new_line;
185     }
186   }
187
188   if(noblink) { // Erase the rest of the window. That's slightly ugly.
189     int k = 0;
190     while(k < console_width - 1) {
191       buffer[k++] = ' ';
192     }
193     buffer[k++] = '\n';
194     buffer[k++] = '\0';
195     for(int l = nb_printed_lines; l < console_height; l++) {
196       addstr(buffer);
197     }
198   }
199
200   // Draw the modeline
201
202   move(0, 0);
203   attron(COLOR_PAIR(1));
204   sprintf(buffer, "%d/%d pattern: %s", nb_printed_lines - 1, nb_lines, regexp);
205   for(int k = strlen(buffer); k < console_width - 1; k++) buffer[k] = ' ';
206   buffer[console_width-1] = '\0';
207   addstr(buffer);
208   attroff(COLOR_PAIR(1));
209
210   refresh();       // After doing something on the display, we refresh it
211 }
212
213 int main(int argc, char **argv) {
214   char buffer[buffer_size];
215   char *lines[nb_lines_max];
216   int noblink = 1;
217
218   char *file_name;
219   char stdin_name[] = "/dev/stdin";
220
221   if(argc == 2 && strcmp(argv[1], "-")) {
222     file_name = argv[1];
223   } else {
224     file_name = stdin_name;
225   }
226
227   ifstream file(file_name);
228
229   if(file.fail()) {
230     cerr << "Can not open \"" << file_name << "\"" << endl;
231     return 1;
232   }
233
234   int nb_lines = 0;
235   while(nb_lines < nb_lines_max && !file.eof()) {
236     file.getline(buffer, buffer_size);
237     lines[nb_lines] = new char[strlen(buffer) + 1];
238     strcpy(lines[nb_lines], buffer);
239     nb_lines++;
240   }
241
242   char regexp[buffer_size];
243   regexp[0] = '\0';
244   int regexp_point;
245   regexp_point = 0;
246
247   initscr();
248
249   if(!has_colors()) {
250     cerr << "No colors." << endl;
251     return 1;
252   }
253
254   noecho();
255   curs_set(0);
256   keypad(stdscr, TRUE);
257
258   start_color();
259   init_pair(1, COLOR_WHITE, COLOR_BLACK);
260   init_pair(2, COLOR_BLACK, COLOR_YELLOW);
261
262   int key;
263
264   int line = 0;
265
266   update_screen(&line, 0, nb_lines, lines, regexp, noblink);
267
268   do {
269
270     key = getch();
271
272     int motion = 0;
273
274     if(key >= ' ' && key <= 'z') {
275       regexp[regexp_point++] = key;
276       regexp[regexp_point] = '\0';
277     }
278
279     else if(key == KEY_BACKSPACE || key == KEY_DC || key == '\b') {
280       if(regexp_point > 0) {
281         regexp_point--;
282         regexp[regexp_point] = '\0';
283       }
284     }
285
286     else if(key == KEY_UP || key == '\10') {
287       motion = -1;
288     }
289
290     else if(key == KEY_DOWN || key == '\ e') {
291       motion = 1;
292     }
293
294     update_screen(&line, motion, nb_lines, lines, regexp, noblink);
295   } while(key != '\n' && key != KEY_ENTER && key != '\a');
296
297   echo();
298   curs_set(1);
299   endwin();
300
301   ofstream out("/tmp/selector.out");
302   if((key == KEY_ENTER || key == '\n') && line >= 0 && line < nb_lines) {
303     out << lines[line] << endl;
304   } else {
305     out << endl;
306   }
307   out.flush();
308
309   for(int l = 0; l < nb_lines; l++) {
310     delete[] lines[l];
311   }
312
313   return 0;
314 }