Merge pull request #530 from jjlin/master
[tig.git] / src / ui.c
blob40cc7b178385cbdda501febcb0578a6b42214673
1 /* Copyright (c) 2006-2015 Jonas Fonseca <jonas.fonseca@gmail.com>
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.
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.
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"
22 struct file_finder_line {
23 size_t matches;
24 char text[1];
27 DEFINE_ALLOCATOR(realloc_file_array, struct file_finder_line *, 256)
29 struct file_finder {
30 WINDOW *win;
31 int height, width;
33 struct file_finder_line **file;
35 struct file_finder_line **line;
36 size_t lines;
37 struct position pos;
39 struct keymap *keymap;
40 const char **search;
41 size_t searchlen;
44 static bool
45 file_finder_read(struct file_finder *finder, const char *commit)
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
52 struct buffer buf;
53 struct io io;
54 size_t files;
55 bool ok = true;
57 if (!io_run(&io, IO_RD, repo.cdup, NULL, ls_tree_files_argv))
58 return false;
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;
67 finder->file[files] = calloc(1, sizeof(*finder->file) + buf.size);
68 if (!finder->file[files]) {
69 ok = false;
70 break;
73 strncpy(finder->file[files]->text, buf.data, buf.size);
76 if (io_error(&io) || !realloc_file_array(&finder->line, 0, files + 1))
77 ok = false;
78 io_done(&io);
79 return ok;
82 static void
83 file_finder_done(struct file_finder *finder)
85 int i;
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);
94 if (finder->win)
95 delwin(finder->win);
98 static void
99 file_finder_move(struct file_finder *finder, int direction)
101 if (direction < 0 && finder->pos.lineno <= -direction)
102 finder->pos.lineno = 0;
103 else
104 finder->pos.lineno += direction;
106 if (finder->pos.lineno >= finder->lines)
107 finder->pos.lineno = finder->lines - 1;
109 if (finder->pos.offset + finder->height <= finder->pos.lineno)
110 finder->pos.offset = finder->pos.lineno - (finder->height / 2);
112 if (finder->pos.offset > finder->pos.lineno)
113 finder->pos.offset = finder->pos.lineno;
115 if (finder->lines <= finder->height)
116 finder->pos.offset = 0;
119 static void
120 file_finder_draw_line(struct file_finder *finder, struct file_finder_line *line)
122 const char **search = finder->search;
123 const char *text = line->text;
124 const char *pos;
126 for (; *text && search && *search && (pos = strstr(text, *search)); search++) {
127 if (text < pos)
128 waddnstr(finder->win, text, pos - text);
129 wattron(finder->win, A_STANDOUT);
130 waddnstr(finder->win, pos, 1);
131 wattroff(finder->win, A_STANDOUT);
132 text = pos + 1;
135 if (*text)
136 waddstr(finder->win, text);
139 static void
140 file_finder_draw(struct file_finder *finder)
142 struct position *pos = &finder->pos;
143 struct file_finder_line *current_line = finder->line[pos->lineno];
144 struct file_finder_line **line_pos = &finder->line[pos->offset];
145 int column;
147 wbkgdset(finder->win, get_line_attr(NULL, LINE_DEFAULT));
148 wclear(finder->win);
150 for (column = 0; *line_pos && column < finder->height - 1; line_pos++) {
151 struct file_finder_line *line = *line_pos;
153 if (finder->searchlen != line->matches)
154 continue;
156 wmove(finder->win, column++, 0);
157 if (line == current_line) {
158 wbkgdset(finder->win, get_line_attr(NULL, LINE_CURSOR));
160 file_finder_draw_line(finder, line);
161 if (line == current_line) {
162 wclrtoeol(finder->win);
163 wbkgdset(finder->win, get_line_attr(NULL, LINE_DEFAULT));
167 wmove(finder->win, finder->height - 1, 0);
168 wbkgdset(finder->win, get_line_attr(NULL, LINE_TITLE_FOCUS));
169 wprintw(finder->win, "[finder] file %d of %d", pos->lineno + 1, finder->lines);
170 wclrtoeol(finder->win);
171 wrefresh(finder->win);
174 static size_t
175 file_finder_line_matches(struct file_finder_line *line, const char **search)
177 const char *text = line->text;
178 const char *pos;
179 size_t matches = 0;
181 for (; *text && *search && (pos = strstr(text, *search)); search++) {
182 text = pos + strlen(*search);
183 matches++;
186 return matches;
189 static void
190 file_finder_update(struct file_finder *finder)
192 struct file_finder_line *current = finder->line[finder->pos.lineno];
193 size_t new_lineno = 0;
194 int i;
196 memset(finder->line, 0, sizeof(finder->line) * finder->lines);
197 finder->lines = 0;
199 for (i = 0; finder->file && finder->file[i]; i++) {
200 struct file_finder_line *line = finder->file[i];
202 if (line == current)
203 current = NULL;
205 if (line->matches + 1 < finder->searchlen) {
206 continue;
209 if (line->matches >= finder->searchlen) {
210 line->matches = finder->searchlen;
211 } else {
212 line->matches = file_finder_line_matches(line, finder->search);
213 if (line->matches < finder->searchlen)
214 continue;
217 if (current != NULL)
218 new_lineno++;
220 finder->line[finder->lines++] = line;
223 finder->pos.lineno = new_lineno;
226 static enum input_status
227 file_finder_input_handler(struct input *input, struct key *key)
229 struct file_finder *finder = input->data;
230 enum input_status status;
232 status = prompt_default_handler(input, key);
233 if (status == INPUT_DELETE) {
234 if (finder->searchlen > 0) {
235 finder->searchlen--;
236 free((void *) finder->search[finder->searchlen]);
237 finder->search[finder->searchlen] = NULL;
239 file_finder_update(finder);
240 file_finder_move(finder, 0);
241 file_finder_draw(finder);
242 return status;
245 if (status != INPUT_SKIP)
246 return status;
248 switch (get_keybinding(finder->keymap, key, 1, NULL)) {
249 case REQ_FIND_PREV:
250 file_finder_move(finder, -1);
251 file_finder_draw(finder);
252 return INPUT_SKIP;
254 case REQ_FIND_NEXT:
255 file_finder_move(finder, +1);
256 file_finder_draw(finder);
257 return INPUT_SKIP;
259 default:
260 if (key_to_value(key) == 0) {
261 argv_append(&finder->search, key->data.bytes);
262 finder->searchlen++;
263 file_finder_update(finder);
264 file_finder_move(finder, 0);
265 file_finder_draw(finder);
266 return INPUT_OK;
269 /* Catch all non-multibyte keys. */
270 return INPUT_SKIP;
274 const char *
275 open_file_finder(const char *commit)
277 struct file_finder finder = {0};
278 const char *file = NULL;
280 if (!file_finder_read(&finder, commit)) {
281 file_finder_done(&finder);
282 return false;
285 getmaxyx(stdscr, finder.height, finder.width);
286 finder.height--;
287 finder.win = newwin(finder.height, finder.width, 0, 0);
288 if (!finder.win) {
289 file_finder_done(&finder);
290 return false;
293 finder.keymap = get_keymap("search", STRING_SIZE("search")),
294 file_finder_update(&finder);
295 file_finder_draw(&finder);
296 if (read_prompt_incremental("Find file: ", false, true, file_finder_input_handler, &finder) && finder.pos.lineno < finder.lines)
297 file = get_path(finder.line[finder.pos.lineno]->text);
299 file_finder_done(&finder);
300 redraw_display(true);
301 return file;
304 /* vim: set ts=8 sw=8 noexpandtab: */