Git fork
at reftables-rust 412 lines 9.8 kB view raw
1#define DISABLE_SIGN_COMPARE_WARNINGS 2 3#include "git-compat-util.h" 4#include "config.h" 5#include "column.h" 6#include "string-list.h" 7#include "pager.h" 8#include "parse-options.h" 9#include "run-command.h" 10#include "utf8.h" 11 12#define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \ 13 (x) * (d)->rows + (y) : \ 14 (y) * (d)->cols + (x)) 15 16struct column_data { 17 const struct string_list *list; 18 unsigned int colopts; 19 struct column_options opts; 20 21 int rows, cols; 22 int *len; /* cell length */ 23 int *width; /* index to the longest row in column */ 24}; 25 26/* return length of 's' in letters, ANSI escapes stripped */ 27static int item_length(const char *s) 28{ 29 return utf8_strnwidth(s, strlen(s), 1); 30} 31 32/* 33 * Calculate cell width, rows and cols for a table of equal cells, given 34 * table width and how many spaces between cells. 35 */ 36static void layout(struct column_data *data, int *width) 37{ 38 int i; 39 40 *width = 0; 41 for (i = 0; i < data->list->nr; i++) 42 if (*width < data->len[i]) 43 *width = data->len[i]; 44 45 *width += data->opts.padding; 46 47 data->cols = (data->opts.width - strlen(data->opts.indent)) / *width; 48 if (data->cols == 0) 49 data->cols = 1; 50 51 data->rows = DIV_ROUND_UP(data->list->nr, data->cols); 52} 53 54static void compute_column_width(struct column_data *data) 55{ 56 int i, x, y; 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]) 63 data->width[x] = i; 64 } 65 } 66} 67 68/* 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. 74 */ 75static void shrink_columns(struct column_data *data) 76{ 77 REALLOC_ARRAY(data->width, data->cols); 78 while (data->rows > 1) { 79 int x, total_width, cols, rows; 80 rows = data->rows; 81 cols = data->cols; 82 83 data->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); 88 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; 93 } 94 if (total_width > data->opts.width) { 95 data->rows = rows; 96 data->cols = cols; 97 break; 98 } 99 } 100 compute_column_width(data); 101} 102 103/* Display without layout when not enabled */ 104static void display_plain(const struct string_list *list, 105 const char *indent, const char *nl) 106{ 107 int i; 108 109 for (i = 0; i < list->nr; i++) 110 printf("%s%s%s", indent, list->items[i].string, nl); 111} 112 113/* Print a cell to stdout with all necessary leading/trailing space */ 114static int display_cell(struct column_data *data, int initial_width, 115 const char *empty_cell, int x, int y) 116{ 117 int i, len, newline; 118 119 i = XY2LINEAR(data, x, y); 120 if (i >= data->list->nr) 121 return -1; 122 123 len = data->len[i]; 124 if (data->width && data->len[data->width[x]] < initial_width) { 125 /* 126 * empty_cell has initial_width chars, if real column 127 * is narrower, increase len a bit so we fill less 128 * space. 129 */ 130 len += initial_width - data->len[data->width[x]]; 131 len -= data->opts.padding; 132 } 133 134 if (COL_LAYOUT(data->colopts) == COL_COLUMN) 135 newline = i + data->rows >= data->list->nr; 136 else 137 newline = x == data->cols - 1 || i == data->list->nr - 1; 138 139 printf("%s%s%s", 140 x == 0 ? data->opts.indent : "", 141 data->list->items[i].string, 142 newline ? data->opts.nl : empty_cell + len); 143 return 0; 144} 145 146/* Display COL_COLUMN or COL_ROW */ 147static void display_table(const struct string_list *list, 148 unsigned int colopts, 149 const struct column_options *opts) 150{ 151 struct column_data data; 152 int x, y, i, initial_width; 153 char *empty_cell; 154 155 memset(&data, 0, sizeof(data)); 156 data.list = list; 157 data.colopts = colopts; 158 data.opts = *opts; 159 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); 163 164 layout(&data, &initial_width); 165 166 if (colopts & COL_DENSE) 167 shrink_columns(&data); 168 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)) 174 break; 175 } 176 177 free(data.len); 178 free(data.width); 179 free(empty_cell); 180} 181 182void print_columns(const struct string_list *list, unsigned int colopts, 183 const struct column_options *opts) 184{ 185 struct column_options nopts; 186 187 if (opts && (0 > opts->padding)) 188 BUG("padding must be non-negative"); 189 if (!list->nr) 190 return; 191 assert((colopts & COL_ENABLE_MASK) != COL_AUTO); 192 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"); 200 return; 201 } 202 switch (COL_LAYOUT(colopts)) { 203 case COL_PLAIN: 204 display_plain(list, nopts.indent, nopts.nl); 205 break; 206 case COL_ROW: 207 case COL_COLUMN: 208 display_table(list, colopts, &nopts); 209 break; 210 default: 211 BUG("invalid layout mode %d", COL_LAYOUT(colopts)); 212 } 213} 214 215int finalize_colopts(unsigned int *colopts, int stdout_is_tty) 216{ 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; 223 } 224 return 0; 225} 226 227struct colopt { 228 const char *name; 229 unsigned int value; 230 unsigned int mask; 231}; 232 233#define LAYOUT_SET 1 234#define ENABLE_SET 2 235 236static int parse_option(const char *arg, int len, unsigned int *colopts, 237 int *group_set) 238{ 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 }, 247 }; 248 int i; 249 250 for (i = 0; i < ARRAY_SIZE(opts); i++) { 251 int set = 1, arg_len = len, name_len; 252 const char *arg_str = arg; 253 254 if (!opts[i].mask) { 255 if (arg_len > 2 && !strncmp(arg_str, "no", 2)) { 256 arg_str += 2; 257 arg_len -= 2; 258 set = 0; 259 } 260 } 261 262 name_len = strlen(opts[i].name); 263 if (arg_len != name_len || 264 strncmp(arg_str, opts[i].name, name_len)) 265 continue; 266 267 switch (opts[i].mask) { 268 case COL_ENABLE_MASK: 269 *group_set |= ENABLE_SET; 270 break; 271 case COL_LAYOUT_MASK: 272 *group_set |= LAYOUT_SET; 273 break; 274 } 275 276 if (opts[i].mask) 277 *colopts = (*colopts & ~opts[i].mask) | opts[i].value; 278 else { 279 if (set) 280 *colopts |= opts[i].value; 281 else 282 *colopts &= ~opts[i].value; 283 } 284 return 0; 285 } 286 287 return error("unsupported option '%s'", arg); 288} 289 290static int parse_config(unsigned int *colopts, const char *value) 291{ 292 const char *sep = " ,"; 293 int group_set = 0; 294 295 while (*value) { 296 int len = strcspn(value, sep); 297 if (len) { 298 if (parse_option(value, len, colopts, &group_set)) 299 return -1; 300 301 value += len; 302 } 303 value += strspn(value, sep); 304 } 305 /* 306 * If none of "always", "never", and "auto" is specified, then setting 307 * layout implies "always". 308 * 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". 312 */ 313 if ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET)) 314 *colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED; 315 return 0; 316} 317 318static int column_config(const char *var, const char *value, 319 const char *key, unsigned int *colopts) 320{ 321 if (!value) 322 return config_error_nonbool(var); 323 if (parse_config(colopts, value)) 324 return error("invalid column.%s mode %s", key, value); 325 return 0; 326} 327 328int git_column_config(const char *var, const char *value, 329 const char *command, unsigned int *colopts) 330{ 331 const char *it; 332 333 if (!skip_prefix(var, "column.", &it)) 334 return 0; 335 336 if (!strcmp(it, "ui")) 337 return column_config(var, value, "ui", colopts); 338 339 if (command && !strcmp(it, command)) 340 return column_config(var, value, it, colopts); 341 342 return 0; 343} 344 345int parseopt_column_callback(const struct option *opt, 346 const char *arg, int unset) 347{ 348 unsigned int *colopts = opt->value; 349 *colopts |= COL_PARSEOPT; 350 *colopts &= ~COL_ENABLE_MASK; 351 if (unset) /* --no-column == never */ 352 return 0; 353 /* --column == always unless "arg" states otherwise */ 354 *colopts |= COL_ENABLED; 355 if (arg) 356 return parse_config(colopts, arg); 357 358 return 0; 359} 360 361static int fd_out = -1; 362static struct child_process column_process = CHILD_PROCESS_INIT; 363 364int run_column_filter(int colopts, const struct column_options *opts) 365{ 366 struct strvec *argv; 367 368 if (opts && (0 > opts->padding)) 369 BUG("padding must be non-negative"); 370 if (fd_out != -1) 371 return -1; 372 373 child_process_init(&column_process); 374 argv = &column_process.args; 375 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); 384 385 fflush(stdout); 386 column_process.in = -1; 387 column_process.out = dup(1); 388 column_process.git_cmd = 1; 389 390 if (start_command(&column_process)) 391 return -2; 392 393 fd_out = dup(1); 394 close(1); 395 dup2(column_process.in, 1); 396 close(column_process.in); 397 return 0; 398} 399 400int stop_column_filter(void) 401{ 402 if (fd_out == -1) 403 return -1; 404 405 fflush(stdout); 406 close(1); 407 finish_command(&column_process); 408 dup2(fd_out, 1); 409 close(fd_out); 410 fd_out = -1; 411 return 0; 412}