Fix insertion/ordering of refs in refs_by_id map.
[tig.git] / src / ui.c
blobe2c839c3c9a7e44f1720066b591c028cdaba9896
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 const char **search;
40 size_t searchlen;
43 static bool
44 file_finder_read(struct file_finder *finder, const char *commit)
46 const char *tree = string_rev_is_null(commit) ? "HEAD" : commit;
47 const char *ls_tree_files_argv[] = {
48 "git", "ls-tree", "-z", "-r", "--name-only", "--full-name",
49 tree, NULL
51 struct buffer buf;
52 struct io io;
53 size_t files;
54 bool ok = TRUE;
56 if (!io_run(&io, IO_RD, repo.cdup, NULL, ls_tree_files_argv))
57 return FALSE;
59 for (files = 0; io_get(&io, &buf, 0, TRUE); files++) {
60 /* Alloc two to ensure NULL terminated array. */
61 if (!realloc_file_array(&finder->file, files, 2)) {
62 ok = FALSE;
63 break;
66 finder->file[files] = calloc(1, sizeof(*finder->file) + buf.size);
67 if (!finder->file[files]) {
68 ok = FALSE;
69 break;
72 strncpy(finder->file[files]->text, buf.data, buf.size);
75 if (io_error(&io) || !realloc_file_array(&finder->line, 0, files + 1))
76 ok = FALSE;
77 io_done(&io);
78 return ok;
81 static void
82 file_finder_done(struct file_finder *finder)
84 int i;
86 free(finder->line);
87 if (finder->file) {
88 for (i = 0; finder->file[i]; i++)
89 free(finder->file[i]);
90 free(finder->file);
93 if (finder->win)
94 delwin(finder->win);
97 static void
98 file_finder_move(struct file_finder *finder, int direction)
100 if (direction < 0 && finder->pos.lineno <= -direction)
101 finder->pos.lineno = 0;
102 else
103 finder->pos.lineno += direction;
105 if (finder->pos.lineno >= finder->lines)
106 finder->pos.lineno = finder->lines - 1;
108 if (finder->pos.offset + finder->height <= finder->pos.lineno)
109 finder->pos.offset = finder->pos.lineno - (finder->height / 2);
111 if (finder->pos.offset > finder->pos.lineno)
112 finder->pos.offset = finder->pos.lineno;
114 if (finder->lines <= finder->height)
115 finder->pos.offset = 0;
118 static void
119 file_finder_draw_line(struct file_finder *finder, struct file_finder_line *line)
121 const char **search = finder->search;
122 const char *text = line->text;
123 const char *pos;
125 for (; *text && search && *search && (pos = strstr(text, *search)); search++) {
126 if (text < pos)
127 waddnstr(finder->win, text, pos - text);
128 wattron(finder->win, A_STANDOUT);
129 waddnstr(finder->win, pos, 1);
130 wattroff(finder->win, A_STANDOUT);
131 text = pos + 1;
134 if (*text)
135 waddstr(finder->win, text);
138 static void
139 file_finder_draw(struct file_finder *finder)
141 struct position *pos = &finder->pos;
142 struct file_finder_line *current_line = finder->line[pos->lineno];
143 struct file_finder_line **line_pos = &finder->line[pos->offset];
144 int column;
146 wbkgdset(finder->win, get_line_attr(NULL, LINE_DEFAULT));
147 wclear(finder->win);
149 for (column = 0; *line_pos && column < finder->height - 1; line_pos++) {
150 struct file_finder_line *line = *line_pos;
152 if (finder->searchlen != line->matches)
153 continue;
155 wmove(finder->win, column++, 0);
156 if (line == current_line) {
157 wbkgdset(finder->win, get_line_attr(NULL, LINE_CURSOR));
159 file_finder_draw_line(finder, line);
160 if (line == current_line) {
161 wclrtoeol(finder->win);
162 wbkgdset(finder->win, get_line_attr(NULL, LINE_DEFAULT));
166 wmove(finder->win, finder->height - 1, 0);
167 wbkgdset(finder->win, get_line_attr(NULL, LINE_TITLE_FOCUS));
168 wprintw(finder->win, "[finder] file %d of %d", pos->lineno + 1, finder->lines);
169 wclrtoeol(finder->win);
170 wrefresh(finder->win);
173 static size_t
174 file_finder_line_matches(struct file_finder_line *line, const char **search)
176 const char *text = line->text;
177 const char *pos;
178 size_t matches = 0;
180 for (; *text && *search && (pos = strstr(text, *search)); search++) {
181 text = pos + strlen(*search);
182 matches++;
185 return matches;
188 static void
189 file_finder_update(struct file_finder *finder)
191 struct file_finder_line *current = finder->line[finder->pos.lineno];
192 size_t new_lineno = 0;
193 int i;
195 memset(finder->line, 0, sizeof(finder->line) * finder->lines);
196 finder->lines = 0;
198 for (i = 0; finder->file[i]; i++) {
199 struct file_finder_line *line = finder->file[i];
201 if (line == current)
202 current = NULL;
204 if (line->matches + 1 < finder->searchlen) {
205 continue;
208 if (line->matches >= finder->searchlen) {
209 line->matches = finder->searchlen;
210 } else {
211 line->matches = file_finder_line_matches(line, finder->search);
212 if (line->matches < finder->searchlen)
213 continue;
216 if (current != NULL)
217 new_lineno++;
219 finder->line[finder->lines++] = line;
222 finder->pos.lineno = new_lineno;
225 static enum input_status
226 file_finder_input_handler(struct input *input, struct key *key)
228 struct file_finder *finder = input->data;
229 enum input_status status;
231 status = prompt_default_handler(input, key);
232 if (status == INPUT_DELETE) {
233 if (finder->searchlen > 0) {
234 finder->searchlen--;
235 free((void *) finder->search[finder->searchlen]);
236 finder->search[finder->searchlen] = NULL;
238 file_finder_update(finder);
239 file_finder_move(finder, 0);
240 file_finder_draw(finder);
241 return status;
244 if (status != INPUT_SKIP)
245 return status;
247 switch (key_to_value(key)) {
248 case 0:
249 argv_append(&finder->search, key->data.bytes);
250 finder->searchlen++;
251 file_finder_update(finder);
252 file_finder_move(finder, 0);
253 file_finder_draw(finder);
254 return INPUT_OK;
256 case KEY_UP:
257 file_finder_move(finder, -1);
258 file_finder_draw(finder);
259 return INPUT_SKIP;
261 case KEY_DOWN:
262 file_finder_move(finder, +1);
263 file_finder_draw(finder);
264 return INPUT_SKIP;
266 default:
267 /* Catch all non-multibyte keys. */
268 return INPUT_SKIP;
272 const char *
273 open_file_finder(const char *commit)
275 struct file_finder finder = {0};
276 const char *file = NULL;
278 if (!file_finder_read(&finder, commit)) {
279 file_finder_done(&finder);
280 return FALSE;
283 getmaxyx(stdscr, finder.height, finder.width);
284 finder.height--;
285 finder.win = newwin(finder.height, finder.width, 0, 0);
286 if (!finder.win) {
287 file_finder_done(&finder);
288 return FALSE;
291 file_finder_update(&finder);
292 file_finder_draw(&finder);
293 if (read_prompt_incremental("Find file: ", FALSE, file_finder_input_handler, &finder))
294 file = get_path(finder.line[finder.pos.lineno]->text);
296 file_finder_done(&finder);
297 redraw_display(TRUE);
298 return file;
301 /* vim: set ts=8 sw=8 noexpandtab: */