Unify view refresh checking
[tig.git] / src / main.c
blob8b2485e445f4cd99e459af26e582ba03c39b2c44
1 /* Copyright (c) 2006-2014 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/repo.h"
15 #include "tig/options.h"
16 #include "tig/parse.h"
17 #include "tig/graph.h"
18 #include "tig/display.h"
19 #include "tig/view.h"
20 #include "tig/draw.h"
21 #include "tig/git.h"
22 #include "tig/status.h"
23 #include "tig/stage.h"
24 #include "tig/main.h"
25 #include "tig/diff.h"
28 * Main view backend
31 DEFINE_ALLOCATOR(realloc_reflogs, char *, 32)
33 static void
34 main_register_commit(struct view *view, struct commit *commit, const char *ids, bool is_boundary)
36 struct main_state *state = view->private;
38 string_copy_rev(commit->id, ids);
39 if (state->with_graph)
40 graph_add_commit(&state->graph, &commit->graph, commit->id, ids, is_boundary);
43 static struct commit *
44 main_add_commit(struct view *view, enum line_type type, struct commit *template,
45 const char *title, bool custom)
47 struct main_state *state = view->private;
48 size_t titlelen;
49 struct commit *commit;
50 char buf[SIZEOF_STR / 2];
51 struct line *line;
53 /* FIXME: More graceful handling of titles; append "..." to
54 * shortened titles, etc. */
55 string_expand(buf, sizeof(buf), title, 1);
56 title = buf;
57 titlelen = strlen(title);
59 line = add_line_alloc(view, &commit, type, titlelen, custom);
60 if (!line)
61 return NULL;
63 *commit = *template;
64 strncpy(commit->title, title, titlelen);
65 state->graph.canvas = &commit->graph;
66 memset(template, 0, sizeof(*template));
67 state->reflogmsg[0] = 0;
69 view_column_info_update(view, line);
70 return commit;
73 static inline void
74 main_flush_commit(struct view *view, struct commit *commit)
76 if (*commit->id)
77 main_add_commit(view, LINE_MAIN_COMMIT, commit, "", FALSE);
80 static bool
81 main_add_changes_commit(struct view *view, enum line_type type, const char *parent, const char *title)
83 char ids[SIZEOF_STR] = NULL_ID " ";
84 struct main_state *state = view->private;
85 struct commit commit = {};
86 struct timeval now;
87 struct timezone tz;
89 if (!parent)
90 return TRUE;
92 if (*parent)
93 string_copy_rev(ids + STRING_SIZE(NULL_ID " "), parent);
94 else
95 ids[STRING_SIZE(NULL_ID)] = 0;
97 if (!gettimeofday(&now, &tz)) {
98 commit.time.tz = tz.tz_minuteswest * 60;
99 commit.time.sec = now.tv_sec - commit.time.tz;
102 commit.author = &unknown_ident;
103 main_register_commit(view, &commit, ids, FALSE);
104 if (!main_add_commit(view, type, &commit, title, TRUE))
105 return FALSE;
107 if (state->with_graph) {
108 if (*parent)
109 return graph_render_parents(&state->graph);
110 state->add_changes_parents = TRUE;
113 return TRUE;
116 static bool
117 main_add_changes_commits(struct view *view, struct main_state *state, const char *parent)
119 const char *staged_parent = NULL_ID;
120 const char *unstaged_parent = parent;
122 update_index();
124 if (!index_diff_unstaged()) {
125 unstaged_parent = NULL;
126 staged_parent = parent;
129 if (!index_diff_staged()) {
130 staged_parent = NULL;
133 return main_add_changes_commit(view, LINE_STAT_STAGED, staged_parent, "Staged changes")
134 && main_add_changes_commit(view, LINE_STAT_UNSTAGED, unstaged_parent, "Unstaged changes");
137 static bool
138 main_check_argv(struct view *view, const char *argv[])
140 struct main_state *state = view->private;
141 bool with_reflog = FALSE;
142 int i;
144 for (i = 0; argv[i]; i++) {
145 const char *arg = argv[i];
146 struct rev_flags rev_flags = {};
148 if (!strcmp(arg, "--graph")) {
149 struct view_column *column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE);
151 if (column) {
152 column->opt.commit_title.graph = TRUE;
153 if (opt_commit_order != COMMIT_ORDER_REVERSE)
154 state->with_graph = TRUE;
156 argv[i] = "";
157 continue;
160 if (!argv_parse_rev_flag(arg, &rev_flags))
161 continue;
163 if (rev_flags.with_reflog)
164 with_reflog = TRUE;
165 if (!rev_flags.with_graph)
166 state->with_graph = FALSE;
167 arg += rev_flags.search_offset;
168 if (*arg && !*view->env->search)
169 string_ncopy(view->env->search, arg, strlen(arg));
172 return with_reflog;
175 static bool
176 main_open(struct view *view, enum open_flags flags)
178 const char *pretty_custom_argv[] = {
179 GIT_MAIN_LOG_CUSTOM(encoding_arg, commit_order_arg(), "%(cmdlineargs)", "%(revargs)", "%(fileargs)")
181 const char *pretty_raw_argv[] = {
182 GIT_MAIN_LOG_RAW(encoding_arg, commit_order_arg(), "%(cmdlineargs)", "%(revargs)", "%(fileargs)")
184 struct main_state *state = view->private;
185 const char **main_argv = pretty_custom_argv;
186 struct view_column *column;
187 bool added_changes_commits = FALSE;
189 column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE);
190 state->with_graph = column && column->opt.commit_title.graph &&
191 opt_commit_order != COMMIT_ORDER_REVERSE;
193 if (opt_rev_argv && main_check_argv(view, opt_rev_argv))
194 main_argv = pretty_raw_argv;
196 if (open_in_pager_mode(flags)) {
197 added_changes_commits = TRUE;
198 state->with_graph = FALSE;
201 /* This calls reset_view() so must be before adding changes commits. */
202 if (!begin_update(view, NULL, main_argv, flags))
203 return FALSE;
205 if (!added_changes_commits && opt_show_changes && repo.is_inside_work_tree)
206 main_add_changes_commits(view, state, "");
208 return TRUE;
211 void
212 main_done(struct view *view)
214 struct main_state *state = view->private;
215 int i;
217 for (i = 0; i < view->lines; i++) {
218 struct commit *commit = view->line[i].data;
220 free(commit->graph.symbols);
223 for (i = 0; i < state->reflogs; i++)
224 free(state->reflog[i]);
225 free(state->reflog);
228 #define main_check_commit_refs(line) !((line)->no_commit_refs)
229 #define main_mark_no_commit_refs(line) (((struct line *) (line))->no_commit_refs = 1)
231 static inline struct ref_list *
232 main_get_commit_refs(const struct line *line, struct commit *commit)
234 struct ref_list *refs = NULL;
236 if (main_check_commit_refs(line) && !(refs = get_ref_list(commit->id)))
237 main_mark_no_commit_refs(line);
239 return refs;
242 bool
243 main_get_column_data(struct view *view, const struct line *line, struct view_column_data *column_data)
245 struct main_state *state = view->private;
246 struct commit *commit = line->data;
247 struct ref_list *refs = NULL;
249 column_data->author = commit->author;
250 column_data->date = &commit->time;
251 column_data->id = commit->id;
252 if (state->reflogs)
253 column_data->reflog = state->reflog[line->lineno - 1];
255 column_data->commit_title = commit->title;
256 if (state->with_graph)
257 column_data->graph = &commit->graph;
259 if ((refs = main_get_commit_refs(line, commit)))
260 column_data->refs = refs;
262 return TRUE;
265 static bool
266 main_add_reflog(struct view *view, struct main_state *state, char *reflog)
268 char *end = strchr(reflog, ' ');
269 int id_width;
271 if (!end)
272 return FALSE;
273 *end = 0;
275 if (!realloc_reflogs(&state->reflog, state->reflogs, 1)
276 || !(reflog = strdup(reflog)))
277 return FALSE;
279 state->reflog[state->reflogs++] = reflog;
280 id_width = strlen(reflog);
281 if (state->reflog_width < id_width) {
282 struct view_column *column = get_view_column(view, VIEW_COLUMN_ID);
284 state->reflog_width = id_width;
285 if (column && column->opt.id.display)
286 view->force_redraw = TRUE;
289 return TRUE;
292 /* Reads git log --pretty=raw output and parses it into the commit struct. */
293 bool
294 main_read(struct view *view, char *line)
296 struct main_state *state = view->private;
297 struct graph *graph = &state->graph;
298 enum line_type type;
299 struct commit *commit = &state->current;
301 if (!line) {
302 main_flush_commit(view, commit);
304 if (failed_to_load_initial_view(view))
305 die("No revisions match the given arguments.");
306 if (view->lines > 0) {
307 struct commit *last = view->line[view->lines - 1].data;
309 view->line[view->lines - 1].dirty = 1;
310 if (!last->author) {
311 view->lines--;
312 free(last);
316 if (state->with_graph)
317 done_graph(graph);
318 return TRUE;
321 type = get_line_type(line);
322 if (type == LINE_COMMIT) {
323 bool is_boundary;
324 char *author;
326 state->in_header = TRUE;
327 line += STRING_SIZE("commit ");
328 is_boundary = *line == '-';
329 while (*line && !isalnum(*line))
330 line++;
332 if (state->add_changes_parents) {
333 state->add_changes_parents = FALSE;
334 if (!graph_add_parent(graph, line))
335 return FALSE;
336 graph->has_parents = TRUE;
337 graph_render_parents(graph);
340 main_flush_commit(view, commit);
341 main_register_commit(view, &state->current, line, is_boundary);
343 author = io_memchr(&view->io, line, 0);
344 if (author) {
345 char *title = io_memchr(&view->io, author, 0);
347 parse_author_line(author, &commit->author, &commit->time);
348 if (state->with_graph)
349 graph_render_parents(graph);
350 if (title)
351 main_add_commit(view, LINE_MAIN_COMMIT, commit, title, FALSE);
354 return TRUE;
357 if (!*commit->id)
358 return TRUE;
360 /* Empty line separates the commit header from the log itself. */
361 if (*line == '\0')
362 state->in_header = FALSE;
364 switch (type) {
365 case LINE_PP_REFLOG:
366 if (!main_add_reflog(view, state, line + STRING_SIZE("Reflog: ")))
367 return FALSE;
368 break;
370 case LINE_PP_REFLOGMSG:
371 line += STRING_SIZE("Reflog message: ");
372 string_ncopy(state->reflogmsg, line, strlen(line));
373 break;
375 case LINE_PARENT:
376 if (state->with_graph && !graph->has_parents)
377 graph_add_parent(graph, line + STRING_SIZE("parent "));
378 break;
380 case LINE_AUTHOR:
381 parse_author_line(line + STRING_SIZE("author "),
382 &commit->author, &commit->time);
383 if (state->with_graph)
384 graph_render_parents(graph);
385 break;
387 default:
388 /* Fill in the commit title if it has not already been set. */
389 if (*commit->title)
390 break;
392 /* Skip lines in the commit header. */
393 if (state->in_header)
394 break;
396 /* Require titles to start with a non-space character at the
397 * offset used by git log. */
398 if (strncmp(line, " ", 4))
399 break;
400 line += 4;
401 /* Well, if the title starts with a whitespace character,
402 * try to be forgiving. Otherwise we end up with no title. */
403 while (isspace(*line))
404 line++;
405 if (*line == '\0')
406 break;
407 if (*state->reflogmsg)
408 line = state->reflogmsg;
409 main_add_commit(view, LINE_MAIN_COMMIT, commit, line, FALSE);
412 return TRUE;
415 enum request
416 main_request(struct view *view, enum request request, struct line *line)
418 enum open_flags flags = (view_is_displayed(view) && request != REQ_VIEW_DIFF)
419 ? OPEN_SPLIT : OPEN_DEFAULT;
421 switch (request) {
422 case REQ_NEXT:
423 case REQ_PREVIOUS:
424 if (view_is_displayed(view) && display[0] != view)
425 return request;
426 /* Do not pass navigation requests to the branch view
427 * when the main view is maximized. (GH #38) */
428 return request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
430 case REQ_VIEW_DIFF:
431 case REQ_ENTER:
432 if (view_is_displayed(view) && display[0] != view)
433 maximize_view(view, TRUE);
435 if (line->type == LINE_STAT_UNSTAGED
436 || line->type == LINE_STAT_STAGED)
437 open_stage_view(view, NULL, line->type, flags);
438 else
439 open_diff_view(view, flags);
440 break;
442 case REQ_REFRESH:
443 load_refs(TRUE);
444 refresh_view(view);
445 break;
447 default:
448 return request;
451 return REQ_NONE;
454 static void
455 main_update_env(struct view *view, struct line *line, struct commit *commit)
457 struct ref_list *list = main_get_commit_refs(line, commit);
458 size_t i;
460 for (i = 0; list && i < list->size; i++)
461 ref_update_env(view->env, list->refs[list->size - i - 1], !i);
464 void
465 main_select(struct view *view, struct line *line)
467 struct commit *commit = line->data;
469 if (line->type == LINE_STAT_STAGED || line->type == LINE_STAT_UNSTAGED) {
470 string_ncopy(view->ref, commit->title, strlen(commit->title));
471 status_stage_info(view->env->status, line->type, NULL);
472 } else {
473 string_copy_rev(view->ref, commit->id);
474 main_update_env(view, line, commit);
476 string_copy_rev(view->env->commit, commit->id);
479 static struct view_ops main_ops = {
480 "commit",
481 argv_env.head,
482 VIEW_SEND_CHILD_ENTER | VIEW_FILE_FILTER | VIEW_LOG_LIKE | VIEW_REFRESH,
483 sizeof(struct main_state),
484 main_open,
485 main_read,
486 view_column_draw,
487 main_request,
488 view_column_grep,
489 main_select,
490 main_done,
491 view_column_bit(AUTHOR) | view_column_bit(COMMIT_TITLE) |
492 view_column_bit(DATE) | view_column_bit(ID) |
493 view_column_bit(LINE_NUMBER),
494 main_get_column_data,
497 DEFINE_VIEW(main);
499 /* vim: set ts=8 sw=8 noexpandtab: */