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