1 /* Copyright (c) 2006-2015 Jonas Fonseca <jonas.fonseca@gmail.com>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14 #include "tig/util.h"
15 #include "tig/parse.h"
16 #include "tig/repo.h"
17 #include "tig/prompt.h"
18 #include "tig/display.h"
19 #include "tig/view.h"
20 #include "tig/ui.h"
21
22 struct file_finder_line {
23 size_t matches;
24 char text[1];
25 };
26
27 DEFINE_ALLOCATOR(realloc_file_array, struct file_finder_line *, 256)
28
29 struct file_finder {
30 WINDOW *win;
31 int height, width;
32
33 struct file_finder_line **file;
34
35 struct file_finder_line **line;
36 size_t lines;
37 struct position pos;
38
39 struct keymap *keymap;
40 const char **search;
41 size_t searchlen;
42 };
43
44 static bool
file_finder_read(struct file_finder * finder,const char * commit)45 file_finder_read(struct file_finder *finder, const char *commit)
46 {
47 const char *tree = string_rev_is_null(commit) ? "HEAD" : commit;
48 const char *ls_tree_files_argv[] = {
49 "git", "ls-tree", "-z", "-r", "--name-only", "--full-name",
50 tree, NULL
51 };
52 struct buffer buf;
53 struct io io;
54 size_t files;
55 bool ok = true;
56
57 if (!io_run(&io, IO_RD, repo.exec_dir, NULL, ls_tree_files_argv))
58 return false;
59
60 for (files = 0; io_get(&io, &buf, 0, true); files++) {
61 /* Alloc two to ensure NULL terminated array. */
62 if (!realloc_file_array(&finder->file, files, 2)) {
63 ok = false;
64 break;
65 }
66
67 finder->file[files] = calloc(1, sizeof(struct file_finder_line) + buf.size);
68 if (!finder->file[files]) {
69 ok = false;
70 break;
71 }
72
73 strncpy(finder->file[files]->text, buf.data, buf.size);
74 }
75
76 if (io_error(&io) || !realloc_file_array(&finder->line, 0, files + 1))
77 ok = false;
78 io_done(&io);
79 return ok;
80 }
81
82 static void
file_finder_done(struct file_finder * finder)83 file_finder_done(struct file_finder *finder)
84 {
85 int i;
86
87 free(finder->line);
88 if (finder->file) {
89 for (i = 0; finder->file[i]; i++)
90 free(finder->file[i]);
91 free(finder->file);
92 }
93
94 if (finder->win)
95 delwin(finder->win);
96 }
97
98 static void
file_finder_move(struct file_finder * finder,int direction)99 file_finder_move(struct file_finder *finder, int direction)
100 {
101 if (direction < 0 && finder->pos.lineno <= -direction)
102 finder->pos.lineno = 0;
103 else
104 finder->pos.lineno += direction;
105
106 if (finder->pos.lineno >= finder->lines) {
107 if (finder->lines > 0)
108 finder->pos.lineno = finder->lines - 1;
109 else
110 finder->pos.lineno = 0;
111 }
112
113 if (finder->pos.offset + finder->height <= finder->pos.lineno)
114 finder->pos.offset = finder->pos.lineno - (finder->height / 2);
115
116 if (finder->pos.offset > finder->pos.lineno)
117 finder->pos.offset = finder->pos.lineno;
118
119 if (finder->lines <= finder->height)
120 finder->pos.offset = 0;
121 }
122
123 static void
file_finder_draw_line(struct file_finder * finder,struct file_finder_line * line)124 file_finder_draw_line(struct file_finder *finder, struct file_finder_line *line)
125 {
126 const char **search = finder->search;
127 const char *text = line->text;
128 const char *pos;
129
130 for (; *text && search && *search && (pos = strstr(text, *search)); search++) {
131 if (text < pos)
132 waddnstr(finder->win, text, pos - text);
133 wattron(finder->win, A_STANDOUT);
134 waddnstr(finder->win, pos, 1);
135 wattroff(finder->win, A_STANDOUT);
136 text = pos + 1;
137 }
138
139 if (*text)
140 waddstr(finder->win, text);
141 }
142
143 static void
file_finder_draw(struct file_finder * finder)144 file_finder_draw(struct file_finder *finder)
145 {
146 struct position *pos = &finder->pos;
147 struct file_finder_line *current_line = finder->line[pos->lineno];
148 struct file_finder_line **line_pos = &finder->line[pos->offset];
149 int column;
150
151 wbkgdset(finder->win, get_line_attr(NULL, LINE_DEFAULT));
152 werase(finder->win);
153
154 for (column = 0; *line_pos && column < finder->height - 1; line_pos++) {
155 struct file_finder_line *line = *line_pos;
156
157 if (finder->searchlen != line->matches)
158 continue;
159
160 wmove(finder->win, column++, 0);
161 if (line == current_line) {
162 wbkgdset(finder->win, get_line_attr(NULL, LINE_CURSOR));
163 }
164 file_finder_draw_line(finder, line);
165 if (line == current_line) {
166 wclrtoeol(finder->win);
167 wbkgdset(finder->win, get_line_attr(NULL, LINE_DEFAULT));
168 }
169 }
170
171 wmove(finder->win, finder->height - 1, 0);
172 wbkgdset(finder->win, get_line_attr(NULL, LINE_TITLE_FOCUS));
173 wprintw(finder->win, "[finder] file %lu of %zu", pos->lineno + 1, finder->lines);
174 wclrtoeol(finder->win);
175 wrefresh(finder->win);
176 }
177
178 static size_t
file_finder_line_matches(struct file_finder_line * line,const char ** search)179 file_finder_line_matches(struct file_finder_line *line, const char **search)
180 {
181 const char *text = line->text;
182 const char *pos;
183 size_t matches = 0;
184
185 for (; *text && *search && (pos = strstr(text, *search)); search++) {
186 text = pos + strlen(*search);
187 matches++;
188 }
189
190 return matches;
191 }
192
193 static void
file_finder_update(struct file_finder * finder)194 file_finder_update(struct file_finder *finder)
195 {
196 struct file_finder_line *current = finder->line[finder->pos.lineno];
197 size_t new_lineno = 0;
198 int i;
199
200 memset(finder->line, 0, sizeof(*finder->line) * finder->lines);
201 finder->lines = 0;
202
203 for (i = 0; finder->file && finder->file[i]; i++) {
204 struct file_finder_line *line = finder->file[i];
205
206 if (line == current)
207 current = NULL;
208
209 if (line->matches + 1 < finder->searchlen) {
210 continue;
211 }
212
213 if (line->matches >= finder->searchlen) {
214 line->matches = finder->searchlen;
215 } else {
216 line->matches = file_finder_line_matches(line, finder->search);
217 if (line->matches < finder->searchlen)
218 continue;
219 }
220
221 if (current != NULL)
222 new_lineno++;
223
224 finder->line[finder->lines++] = line;
225 }
226
227 finder->pos.lineno = new_lineno;
228 }
229
230 static enum input_status
file_finder_input_handler(struct input * input,struct key * key)231 file_finder_input_handler(struct input *input, struct key *key)
232 {
233 struct file_finder *finder = input->data;
234 enum input_status status;
235
236 status = prompt_default_handler(input, key);
237 if (status == INPUT_DELETE) {
238 if (finder->searchlen > 0) {
239 finder->searchlen--;
240 free((void *) finder->search[finder->searchlen]);
241 finder->search[finder->searchlen] = NULL;
242 }
243 file_finder_update(finder);
244 file_finder_move(finder, 0);
245 file_finder_draw(finder);
246 return status;
247 }
248
249 if (status != INPUT_SKIP)
250 return status;
251
252 switch (get_keybinding(finder->keymap, key, 1, NULL)) {
253 case REQ_FIND_PREV:
254 file_finder_move(finder, -1);
255 file_finder_draw(finder);
256 return INPUT_SKIP;
257
258 case REQ_FIND_NEXT:
259 file_finder_move(finder, +1);
260 file_finder_draw(finder);
261 return INPUT_SKIP;
262
263 case REQ_BACK:
264 case REQ_PARENT:
265 case REQ_VIEW_CLOSE:
266 case REQ_VIEW_CLOSE_NO_QUIT:
267 return INPUT_CANCEL;
268
269 default:
270 if (key_to_value(key) == 0) {
271 argv_append(&finder->search, key->data.bytes);
272 finder->searchlen++;
273 file_finder_update(finder);
274 file_finder_move(finder, 0);
275 file_finder_draw(finder);
276 return INPUT_OK;
277 }
278
279 /* Catch all non-multibyte keys. */
280 return INPUT_SKIP;
281 }
282 }
283
284 const char *
open_file_finder(const char * commit)285 open_file_finder(const char *commit)
286 {
287 struct file_finder finder = {0};
288 const char *file = NULL;
289
290 if (!file_finder_read(&finder, commit)) {
291 file_finder_done(&finder);
292 return false;
293 }
294
295 getmaxyx(stdscr, finder.height, finder.width);
296 finder.height--;
297 finder.win = newwin(finder.height, finder.width, 0, 0);
298 if (!finder.win) {
299 file_finder_done(&finder);
300 return false;
301 }
302
303 finder.keymap = get_keymap("search", STRING_SIZE("search")),
304 file_finder_update(&finder);
305 file_finder_draw(&finder);
306 if (read_prompt_incremental("Find file: ", false, true, file_finder_input_handler, &finder) && finder.pos.lineno < finder.lines)
307 file = get_path(finder.line[finder.pos.lineno]->text);
308
309 file_finder_done(&finder);
310 redraw_display(true);
311 return file;
312 }
313
314 /* vim: set ts=8 sw=8 noexpandtab: */
315