1 #define DISABLE_SIGN_COMPARE_WARNINGS
3 #include "git-compat-util.h"
6 #include "string-list.h"
8 #include "parse-options.h"
9 #include "run-command.h"
12 #define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \
13 (x) * (d)->rows + (y) : \
14 (y) * (d)->cols + (x))
17 const struct string_list
*list
;
19 struct column_options opts
;
22 int *len
; /* cell length */
23 int *width
; /* index to the longest row in column */
26 /* return length of 's' in letters, ANSI escapes stripped */
27 static int item_length(const char *s
)
29 return utf8_strnwidth(s
, strlen(s
), 1);
33 * Calculate cell width, rows and cols for a table of equal cells, given
34 * table width and how many spaces between cells.
36 static void layout(struct column_data
*data
, int *width
)
41 for (i
= 0; i
< data
->list
->nr
; i
++)
42 if (*width
< data
->len
[i
])
43 *width
= data
->len
[i
];
45 *width
+= data
->opts
.padding
;
47 data
->cols
= (data
->opts
.width
- strlen(data
->opts
.indent
)) / *width
;
51 data
->rows
= DIV_ROUND_UP(data
->list
->nr
, data
->cols
);
54 static void compute_column_width(struct column_data
*data
)
57 for (x
= 0; x
< data
->cols
; x
++) {
58 data
->width
[x
] = XY2LINEAR(data
, x
, 0);
59 for (y
= 0; y
< data
->rows
; y
++) {
60 i
= XY2LINEAR(data
, x
, y
);
61 if (i
< data
->list
->nr
&&
62 data
->len
[data
->width
[x
]] < data
->len
[i
])
69 * Shrink all columns by shortening them one row each time (and adding
70 * more columns along the way). Hopefully the longest cell will be
71 * moved to the next column, column is shrunk so we have more space
72 * for new columns. The process ends when the whole thing no longer
73 * fits in data->total_width.
75 static void shrink_columns(struct column_data
*data
)
77 REALLOC_ARRAY(data
->width
, data
->cols
);
78 while (data
->rows
> 1) {
79 int x
, total_width
, cols
, rows
;
84 data
->cols
= DIV_ROUND_UP(data
->list
->nr
, data
->rows
);
85 if (data
->cols
!= cols
)
86 REALLOC_ARRAY(data
->width
, data
->cols
);
87 compute_column_width(data
);
89 total_width
= strlen(data
->opts
.indent
);
90 for (x
= 0; x
< data
->cols
; x
++) {
91 total_width
+= data
->len
[data
->width
[x
]];
92 total_width
+= data
->opts
.padding
;
94 if (total_width
> data
->opts
.width
) {
100 compute_column_width(data
);
103 /* Display without layout when not enabled */
104 static void display_plain(const struct string_list
*list
,
105 const char *indent
, const char *nl
)
109 for (i
= 0; i
< list
->nr
; i
++)
110 printf("%s%s%s", indent
, list
->items
[i
].string
, nl
);
113 /* Print a cell to stdout with all necessary leading/trailing space */
114 static int display_cell(struct column_data
*data
, int initial_width
,
115 const char *empty_cell
, int x
, int y
)
119 i
= XY2LINEAR(data
, x
, y
);
120 if (i
>= data
->list
->nr
)
124 if (data
->width
&& data
->len
[data
->width
[x
]] < initial_width
) {
126 * empty_cell has initial_width chars, if real column
127 * is narrower, increase len a bit so we fill less
130 len
+= initial_width
- data
->len
[data
->width
[x
]];
131 len
-= data
->opts
.padding
;
134 if (COL_LAYOUT(data
->colopts
) == COL_COLUMN
)
135 newline
= i
+ data
->rows
>= data
->list
->nr
;
137 newline
= x
== data
->cols
- 1 || i
== data
->list
->nr
- 1;
140 x
== 0 ? data
->opts
.indent
: "",
141 data
->list
->items
[i
].string
,
142 newline
? data
->opts
.nl
: empty_cell
+ len
);
146 /* Display COL_COLUMN or COL_ROW */
147 static void display_table(const struct string_list
*list
,
148 unsigned int colopts
,
149 const struct column_options
*opts
)
151 struct column_data data
;
152 int x
, y
, i
, initial_width
;
155 memset(&data
, 0, sizeof(data
));
157 data
.colopts
= colopts
;
160 ALLOC_ARRAY(data
.len
, list
->nr
);
161 for (i
= 0; i
< list
->nr
; i
++)
162 data
.len
[i
] = item_length(list
->items
[i
].string
);
164 layout(&data
, &initial_width
);
166 if (colopts
& COL_DENSE
)
167 shrink_columns(&data
);
169 empty_cell
= xmallocz(initial_width
);
170 memset(empty_cell
, ' ', initial_width
);
171 for (y
= 0; y
< data
.rows
; y
++) {
172 for (x
= 0; x
< data
.cols
; x
++)
173 if (display_cell(&data
, initial_width
, empty_cell
, x
, y
))
182 void print_columns(const struct string_list
*list
, unsigned int colopts
,
183 const struct column_options
*opts
)
185 struct column_options nopts
;
187 if (opts
&& (0 > opts
->padding
))
188 BUG("padding must be non-negative");
191 assert((colopts
& COL_ENABLE_MASK
) != COL_AUTO
);
193 memset(&nopts
, 0, sizeof(nopts
));
194 nopts
.indent
= opts
&& opts
->indent
? opts
->indent
: "";
195 nopts
.nl
= opts
&& opts
->nl
? opts
->nl
: "\n";
196 nopts
.padding
= opts
? opts
->padding
: 1;
197 nopts
.width
= opts
&& opts
->width
? opts
->width
: term_columns() - 1;
198 if (!column_active(colopts
)) {
199 display_plain(list
, "", "\n");
202 switch (COL_LAYOUT(colopts
)) {
204 display_plain(list
, nopts
.indent
, nopts
.nl
);
208 display_table(list
, colopts
, &nopts
);
211 BUG("invalid layout mode %d", COL_LAYOUT(colopts
));
215 int finalize_colopts(unsigned int *colopts
, int stdout_is_tty
)
217 if ((*colopts
& COL_ENABLE_MASK
) == COL_AUTO
) {
218 if (stdout_is_tty
< 0)
219 stdout_is_tty
= isatty(1);
220 *colopts
&= ~COL_ENABLE_MASK
;
221 if (stdout_is_tty
|| pager_in_use())
222 *colopts
|= COL_ENABLED
;
236 static int parse_option(const char *arg
, int len
, unsigned int *colopts
,
239 struct colopt opts
[] = {
240 { "always", COL_ENABLED
, COL_ENABLE_MASK
},
241 { "never", COL_DISABLED
, COL_ENABLE_MASK
},
242 { "auto", COL_AUTO
, COL_ENABLE_MASK
},
243 { "plain", COL_PLAIN
, COL_LAYOUT_MASK
},
244 { "column", COL_COLUMN
, COL_LAYOUT_MASK
},
245 { "row", COL_ROW
, COL_LAYOUT_MASK
},
246 { "dense", COL_DENSE
, 0 },
250 for (i
= 0; i
< ARRAY_SIZE(opts
); i
++) {
251 int set
= 1, arg_len
= len
, name_len
;
252 const char *arg_str
= arg
;
255 if (arg_len
> 2 && !strncmp(arg_str
, "no", 2)) {
262 name_len
= strlen(opts
[i
].name
);
263 if (arg_len
!= name_len
||
264 strncmp(arg_str
, opts
[i
].name
, name_len
))
267 switch (opts
[i
].mask
) {
268 case COL_ENABLE_MASK
:
269 *group_set
|= ENABLE_SET
;
271 case COL_LAYOUT_MASK
:
272 *group_set
|= LAYOUT_SET
;
277 *colopts
= (*colopts
& ~opts
[i
].mask
) | opts
[i
].value
;
280 *colopts
|= opts
[i
].value
;
282 *colopts
&= ~opts
[i
].value
;
287 return error("unsupported option '%s'", arg
);
290 static int parse_config(unsigned int *colopts
, const char *value
)
292 const char *sep
= " ,";
296 int len
= strcspn(value
, sep
);
298 if (parse_option(value
, len
, colopts
, &group_set
))
303 value
+= strspn(value
, sep
);
306 * If none of "always", "never", and "auto" is specified, then setting
307 * layout implies "always".
309 * Current value in COL_ENABLE_MASK is disregarded. This means if
310 * you set column.ui = auto and pass --column=row, then "auto"
311 * will become "always".
313 if ((group_set
& LAYOUT_SET
) && !(group_set
& ENABLE_SET
))
314 *colopts
= (*colopts
& ~COL_ENABLE_MASK
) | COL_ENABLED
;
318 static int column_config(const char *var
, const char *value
,
319 const char *key
, unsigned int *colopts
)
322 return config_error_nonbool(var
);
323 if (parse_config(colopts
, value
))
324 return error("invalid column.%s mode %s", key
, value
);
328 int git_column_config(const char *var
, const char *value
,
329 const char *command
, unsigned int *colopts
)
333 if (!skip_prefix(var
, "column.", &it
))
336 if (!strcmp(it
, "ui"))
337 return column_config(var
, value
, "ui", colopts
);
339 if (command
&& !strcmp(it
, command
))
340 return column_config(var
, value
, it
, colopts
);
345 int parseopt_column_callback(const struct option
*opt
,
346 const char *arg
, int unset
)
348 unsigned int *colopts
= opt
->value
;
349 *colopts
|= COL_PARSEOPT
;
350 *colopts
&= ~COL_ENABLE_MASK
;
351 if (unset
) /* --no-column == never */
353 /* --column == always unless "arg" states otherwise */
354 *colopts
|= COL_ENABLED
;
356 return parse_config(colopts
, arg
);
361 static int fd_out
= -1;
362 static struct child_process column_process
= CHILD_PROCESS_INIT
;
364 int run_column_filter(int colopts
, const struct column_options
*opts
)
368 if (opts
&& (0 > opts
->padding
))
369 BUG("padding must be non-negative");
373 child_process_init(&column_process
);
374 argv
= &column_process
.args
;
376 strvec_push(argv
, "column");
377 strvec_pushf(argv
, "--raw-mode=%d", colopts
);
378 if (opts
&& opts
->width
)
379 strvec_pushf(argv
, "--width=%d", opts
->width
);
380 if (opts
&& opts
->indent
)
381 strvec_pushf(argv
, "--indent=%s", opts
->indent
);
382 if (opts
&& opts
->padding
)
383 strvec_pushf(argv
, "--padding=%d", opts
->padding
);
386 column_process
.in
= -1;
387 column_process
.out
= dup(1);
388 column_process
.git_cmd
= 1;
390 if (start_command(&column_process
))
395 dup2(column_process
.in
, 1);
396 close(column_process
.in
);
400 int stop_column_filter(void)
407 finish_command(&column_process
);