Fix infinite loop when parsing view columns
[tig.git] / src / pager.c
blob6e0d8864887cfeeb5a4a4b08f506d6e933066f2d
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/tig.h"
15 #include "tig/options.h"
16 #include "tig/request.h"
17 #include "tig/line.h"
18 #include "tig/keys.h"
19 #include "tig/display.h"
20 #include "tig/view.h"
21 #include "tig/draw.h"
22 #include "tig/diff.h"
25 * Pager backend
28 bool
29 pager_get_column_data(struct view *view, const struct line *line, struct view_column_data *column_data)
31 column_data->text = line->data;
32 return TRUE;
35 static bool
36 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
38 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
39 char ref[SIZEOF_STR];
41 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
42 return TRUE;
44 /* This is the only fatal call, since it can "corrupt" the buffer. */
45 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
46 return FALSE;
48 return TRUE;
51 static void
52 add_pager_refs(struct view *view, const char *commit_id)
54 char buf[SIZEOF_STR];
55 struct ref_list *list;
56 size_t bufpos = 0, i;
57 const char *sep = "Refs: ";
58 bool is_tag = FALSE;
60 list = get_ref_list(commit_id);
61 if (!list) {
62 if (view_has_flags(view, VIEW_ADD_DESCRIBE_REF))
63 goto try_add_describe_ref;
64 return;
67 for (i = 0; i < list->size; i++) {
68 struct ref *ref = list->refs[i];
69 const struct ref_format *fmt = get_ref_format(ref);
71 if (!string_format_from(buf, &bufpos, "%s%s%s%s", sep,
72 fmt->start, ref->name, fmt->end))
73 return;
74 sep = ", ";
75 if (ref_is_tag(ref))
76 is_tag = TRUE;
79 if (!is_tag && view_has_flags(view, VIEW_ADD_DESCRIBE_REF)) {
80 try_add_describe_ref:
81 /* Add <tag>-g<commit_id> "fake" reference. */
82 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
83 return;
86 if (bufpos == 0)
87 return;
89 add_line_text(view, buf, LINE_PP_REFS);
92 static struct line *
93 pager_wrap_line(struct view *view, const char *data, enum line_type type)
95 size_t first_line = 0;
96 bool has_first_line = FALSE;
97 size_t datalen = strlen(data);
98 size_t lineno = 0;
100 while (datalen > 0 || !has_first_line) {
101 bool wrapped = !!first_line;
102 size_t linelen = string_expanded_length(data, datalen, opt_tab_size, view->width - !!wrapped);
103 struct line *line;
104 char *text;
106 line = add_line(view, NULL, type, linelen + 1, wrapped);
107 if (!line)
108 break;
109 if (!has_first_line) {
110 first_line = view->lines - 1;
111 has_first_line = TRUE;
114 if (!wrapped)
115 lineno = line->lineno;
117 line->wrapped = wrapped;
118 line->lineno = lineno;
119 text = line->data;
120 if (linelen)
121 strncpy(text, data, linelen);
122 text[linelen] = 0;
124 datalen -= linelen;
125 data += linelen;
128 return has_first_line ? &view->line[first_line] : NULL;
131 bool
132 pager_common_read(struct view *view, const char *data, enum line_type type, struct line **line_ptr)
134 struct line *line;
136 if (!data)
137 return TRUE;
139 if (opt_wrap_lines) {
140 line = pager_wrap_line(view, data, type);
141 } else {
142 line = add_line_text(view, data, type);
145 if (!line)
146 return FALSE;
148 if (line_ptr)
149 *line_ptr = line;
151 if (line->type == LINE_COMMIT && view_has_flags(view, VIEW_ADD_PAGER_REFS))
152 add_pager_refs(view, data + STRING_SIZE("commit "));
154 return TRUE;
157 bool
158 pager_read(struct view *view, char *data)
160 if (!data)
161 return TRUE;
163 return pager_common_read(view, data, get_line_type(data), NULL);
166 enum request
167 pager_request(struct view *view, enum request request, struct line *line)
169 int split = 0;
171 if (request != REQ_ENTER)
172 return request;
174 if (line->type == LINE_COMMIT && view_has_flags(view, VIEW_OPEN_DIFF)) {
175 open_diff_view(view, OPEN_SPLIT);
176 split = 1;
179 /* Always scroll the view even if it was split. That way
180 * you can use Enter to scroll through the log view and
181 * split open each commit diff. */
182 scroll_view(view, REQ_SCROLL_LINE_DOWN);
184 /* FIXME: A minor workaround. Scrolling the view will call report_clear()
185 * but if we are scrolling a non-current view this won't properly
186 * update the view title. */
187 if (split)
188 update_view_title(view);
190 return REQ_NONE;
193 void
194 pager_select(struct view *view, struct line *line)
196 if (line->type == LINE_COMMIT) {
197 string_copy_rev_from_commit_line(view->env->commit, line->data);
198 if (!view_has_flags(view, VIEW_NO_REF))
199 string_copy_rev(view->ref, view->env->commit);
203 static bool
204 pager_open(struct view *view, enum open_flags flags)
206 if (!open_from_stdin(flags) && !view->lines && !(flags & OPEN_PREPARED)) {
207 report("No pager content, press %s to run command from prompt",
208 get_view_key(view, REQ_PROMPT));
209 return FALSE;
212 return begin_update(view, NULL, NULL, flags);
215 static struct view_ops pager_ops = {
216 "line",
218 VIEW_OPEN_DIFF | VIEW_NO_REF | VIEW_NO_GIT_DIR,
220 pager_open,
221 pager_read,
222 view_column_draw,
223 pager_request,
224 view_column_grep,
225 pager_select,
226 NULL,
227 view_column_bit(LINE_NUMBER) | view_column_bit(TEXT),
228 pager_get_column_data,
231 DEFINE_VIEW(pager);
233 /* vim: set ts=8 sw=8 noexpandtab: */