THIS IS A TEST INSTANCE ONLY! REPOSITORIES CAN BE DELETED AT ANY TIME!

Git Source Code Mirror - This is a publish-only repository and all pull requests are ignored. Please follow Documentation/SubmittingPatches procedure for any of your improvements.
git
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

406 lines
9.4KB

  1. #include "cache.h"
  2. #include "config.h"
  3. #include "column.h"
  4. #include "string-list.h"
  5. #include "parse-options.h"
  6. #include "run-command.h"
  7. #include "utf8.h"
  8. #define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \
  9. (x) * (d)->rows + (y) : \
  10. (y) * (d)->cols + (x))
  11. struct column_data {
  12. const struct string_list *list;
  13. unsigned int colopts;
  14. struct column_options opts;
  15. int rows, cols;
  16. int *len; /* cell length */
  17. int *width; /* index to the longest row in column */
  18. };
  19. /* return length of 's' in letters, ANSI escapes stripped */
  20. static int item_length(const char *s)
  21. {
  22. return utf8_strnwidth(s, -1, 1);
  23. }
  24. /*
  25. * Calculate cell width, rows and cols for a table of equal cells, given
  26. * table width and how many spaces between cells.
  27. */
  28. static void layout(struct column_data *data, int *width)
  29. {
  30. int i;
  31. *width = 0;
  32. for (i = 0; i < data->list->nr; i++)
  33. if (*width < data->len[i])
  34. *width = data->len[i];
  35. *width += data->opts.padding;
  36. data->cols = (data->opts.width - strlen(data->opts.indent)) / *width;
  37. if (data->cols == 0)
  38. data->cols = 1;
  39. data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
  40. }
  41. static void compute_column_width(struct column_data *data)
  42. {
  43. int i, x, y;
  44. for (x = 0; x < data->cols; x++) {
  45. data->width[x] = XY2LINEAR(data, x, 0);
  46. for (y = 0; y < data->rows; y++) {
  47. i = XY2LINEAR(data, x, y);
  48. if (i < data->list->nr &&
  49. data->len[data->width[x]] < data->len[i])
  50. data->width[x] = i;
  51. }
  52. }
  53. }
  54. /*
  55. * Shrink all columns by shortening them one row each time (and adding
  56. * more columns along the way). Hopefully the longest cell will be
  57. * moved to the next column, column is shrunk so we have more space
  58. * for new columns. The process ends when the whole thing no longer
  59. * fits in data->total_width.
  60. */
  61. static void shrink_columns(struct column_data *data)
  62. {
  63. REALLOC_ARRAY(data->width, data->cols);
  64. while (data->rows > 1) {
  65. int x, total_width, cols, rows;
  66. rows = data->rows;
  67. cols = data->cols;
  68. data->rows--;
  69. data->cols = DIV_ROUND_UP(data->list->nr, data->rows);
  70. if (data->cols != cols)
  71. REALLOC_ARRAY(data->width, data->cols);
  72. compute_column_width(data);
  73. total_width = strlen(data->opts.indent);
  74. for (x = 0; x < data->cols; x++) {
  75. total_width += data->len[data->width[x]];
  76. total_width += data->opts.padding;
  77. }
  78. if (total_width > data->opts.width) {
  79. data->rows = rows;
  80. data->cols = cols;
  81. break;
  82. }
  83. }
  84. compute_column_width(data);
  85. }
  86. /* Display without layout when not enabled */
  87. static void display_plain(const struct string_list *list,
  88. const char *indent, const char *nl)
  89. {
  90. int i;
  91. for (i = 0; i < list->nr; i++)
  92. printf("%s%s%s", indent, list->items[i].string, nl);
  93. }
  94. /* Print a cell to stdout with all necessary leading/traling space */
  95. static int display_cell(struct column_data *data, int initial_width,
  96. const char *empty_cell, int x, int y)
  97. {
  98. int i, len, newline;
  99. i = XY2LINEAR(data, x, y);
  100. if (i >= data->list->nr)
  101. return -1;
  102. len = data->len[i];
  103. if (data->width && data->len[data->width[x]] < initial_width) {
  104. /*
  105. * empty_cell has initial_width chars, if real column
  106. * is narrower, increase len a bit so we fill less
  107. * space.
  108. */
  109. len += initial_width - data->len[data->width[x]];
  110. len -= data->opts.padding;
  111. }
  112. if (COL_LAYOUT(data->colopts) == COL_COLUMN)
  113. newline = i + data->rows >= data->list->nr;
  114. else
  115. newline = x == data->cols - 1 || i == data->list->nr - 1;
  116. printf("%s%s%s",
  117. x == 0 ? data->opts.indent : "",
  118. data->list->items[i].string,
  119. newline ? data->opts.nl : empty_cell + len);
  120. return 0;
  121. }
  122. /* Display COL_COLUMN or COL_ROW */
  123. static void display_table(const struct string_list *list,
  124. unsigned int colopts,
  125. const struct column_options *opts)
  126. {
  127. struct column_data data;
  128. int x, y, i, initial_width;
  129. char *empty_cell;
  130. memset(&data, 0, sizeof(data));
  131. data.list = list;
  132. data.colopts = colopts;
  133. data.opts = *opts;
  134. ALLOC_ARRAY(data.len, list->nr);
  135. for (i = 0; i < list->nr; i++)
  136. data.len[i] = item_length(list->items[i].string);
  137. layout(&data, &initial_width);
  138. if (colopts & COL_DENSE)
  139. shrink_columns(&data);
  140. empty_cell = xmallocz(initial_width);
  141. memset(empty_cell, ' ', initial_width);
  142. for (y = 0; y < data.rows; y++) {
  143. for (x = 0; x < data.cols; x++)
  144. if (display_cell(&data, initial_width, empty_cell, x, y))
  145. break;
  146. }
  147. free(data.len);
  148. free(data.width);
  149. free(empty_cell);
  150. }
  151. void print_columns(const struct string_list *list, unsigned int colopts,
  152. const struct column_options *opts)
  153. {
  154. struct column_options nopts;
  155. if (!list->nr)
  156. return;
  157. assert((colopts & COL_ENABLE_MASK) != COL_AUTO);
  158. memset(&nopts, 0, sizeof(nopts));
  159. nopts.indent = opts && opts->indent ? opts->indent : "";
  160. nopts.nl = opts && opts->nl ? opts->nl : "\n";
  161. nopts.padding = opts ? opts->padding : 1;
  162. nopts.width = opts && opts->width ? opts->width : term_columns() - 1;
  163. if (!column_active(colopts)) {
  164. display_plain(list, "", "\n");
  165. return;
  166. }
  167. switch (COL_LAYOUT(colopts)) {
  168. case COL_PLAIN:
  169. display_plain(list, nopts.indent, nopts.nl);
  170. break;
  171. case COL_ROW:
  172. case COL_COLUMN:
  173. display_table(list, colopts, &nopts);
  174. break;
  175. default:
  176. BUG("invalid layout mode %d", COL_LAYOUT(colopts));
  177. }
  178. }
  179. int finalize_colopts(unsigned int *colopts, int stdout_is_tty)
  180. {
  181. if ((*colopts & COL_ENABLE_MASK) == COL_AUTO) {
  182. if (stdout_is_tty < 0)
  183. stdout_is_tty = isatty(1);
  184. *colopts &= ~COL_ENABLE_MASK;
  185. if (stdout_is_tty || pager_in_use())
  186. *colopts |= COL_ENABLED;
  187. }
  188. return 0;
  189. }
  190. struct colopt {
  191. const char *name;
  192. unsigned int value;
  193. unsigned int mask;
  194. };
  195. #define LAYOUT_SET 1
  196. #define ENABLE_SET 2
  197. static int parse_option(const char *arg, int len, unsigned int *colopts,
  198. int *group_set)
  199. {
  200. struct colopt opts[] = {
  201. { "always", COL_ENABLED, COL_ENABLE_MASK },
  202. { "never", COL_DISABLED, COL_ENABLE_MASK },
  203. { "auto", COL_AUTO, COL_ENABLE_MASK },
  204. { "plain", COL_PLAIN, COL_LAYOUT_MASK },
  205. { "column", COL_COLUMN, COL_LAYOUT_MASK },
  206. { "row", COL_ROW, COL_LAYOUT_MASK },
  207. { "dense", COL_DENSE, 0 },
  208. };
  209. int i;
  210. for (i = 0; i < ARRAY_SIZE(opts); i++) {
  211. int set = 1, arg_len = len, name_len;
  212. const char *arg_str = arg;
  213. if (!opts[i].mask) {
  214. if (arg_len > 2 && !strncmp(arg_str, "no", 2)) {
  215. arg_str += 2;
  216. arg_len -= 2;
  217. set = 0;
  218. }
  219. }
  220. name_len = strlen(opts[i].name);
  221. if (arg_len != name_len ||
  222. strncmp(arg_str, opts[i].name, name_len))
  223. continue;
  224. switch (opts[i].mask) {
  225. case COL_ENABLE_MASK:
  226. *group_set |= ENABLE_SET;
  227. break;
  228. case COL_LAYOUT_MASK:
  229. *group_set |= LAYOUT_SET;
  230. break;
  231. }
  232. if (opts[i].mask)
  233. *colopts = (*colopts & ~opts[i].mask) | opts[i].value;
  234. else {
  235. if (set)
  236. *colopts |= opts[i].value;
  237. else
  238. *colopts &= ~opts[i].value;
  239. }
  240. return 0;
  241. }
  242. return error("unsupported option '%s'", arg);
  243. }
  244. static int parse_config(unsigned int *colopts, const char *value)
  245. {
  246. const char *sep = " ,";
  247. int group_set = 0;
  248. while (*value) {
  249. int len = strcspn(value, sep);
  250. if (len) {
  251. if (parse_option(value, len, colopts, &group_set))
  252. return -1;
  253. value += len;
  254. }
  255. value += strspn(value, sep);
  256. }
  257. /*
  258. * If none of "always", "never", and "auto" is specified, then setting
  259. * layout implies "always".
  260. *
  261. * Current value in COL_ENABLE_MASK is disregarded. This means if
  262. * you set column.ui = auto and pass --column=row, then "auto"
  263. * will become "always".
  264. */
  265. if ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))
  266. *colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
  267. return 0;
  268. }
  269. static int column_config(const char *var, const char *value,
  270. const char *key, unsigned int *colopts)
  271. {
  272. if (!value)
  273. return config_error_nonbool(var);
  274. if (parse_config(colopts, value))
  275. return error("invalid column.%s mode %s", key, value);
  276. return 0;
  277. }
  278. int git_column_config(const char *var, const char *value,
  279. const char *command, unsigned int *colopts)
  280. {
  281. const char *it;
  282. if (!skip_prefix(var, "column.", &it))
  283. return 0;
  284. if (!strcmp(it, "ui"))
  285. return column_config(var, value, "ui", colopts);
  286. if (command && !strcmp(it, command))
  287. return column_config(var, value, it, colopts);
  288. return 0;
  289. }
  290. int parseopt_column_callback(const struct option *opt,
  291. const char *arg, int unset)
  292. {
  293. unsigned int *colopts = opt->value;
  294. *colopts |= COL_PARSEOPT;
  295. *colopts &= ~COL_ENABLE_MASK;
  296. if (unset) /* --no-column == never */
  297. return 0;
  298. /* --column == always unless "arg" states otherwise */
  299. *colopts |= COL_ENABLED;
  300. if (arg)
  301. return parse_config(colopts, arg);
  302. return 0;
  303. }
  304. static int fd_out = -1;
  305. static struct child_process column_process = CHILD_PROCESS_INIT;
  306. int run_column_filter(int colopts, const struct column_options *opts)
  307. {
  308. struct argv_array *argv;
  309. if (fd_out != -1)
  310. return -1;
  311. child_process_init(&column_process);
  312. argv = &column_process.args;
  313. argv_array_push(argv, "column");
  314. argv_array_pushf(argv, "--raw-mode=%d", colopts);
  315. if (opts && opts->width)
  316. argv_array_pushf(argv, "--width=%d", opts->width);
  317. if (opts && opts->indent)
  318. argv_array_pushf(argv, "--indent=%s", opts->indent);
  319. if (opts && opts->padding)
  320. argv_array_pushf(argv, "--padding=%d", opts->padding);
  321. fflush(stdout);
  322. column_process.in = -1;
  323. column_process.out = dup(1);
  324. column_process.git_cmd = 1;
  325. if (start_command(&column_process))
  326. return -2;
  327. fd_out = dup(1);
  328. close(1);
  329. dup2(column_process.in, 1);
  330. close(column_process.in);
  331. return 0;
  332. }
  333. int stop_column_filter(void)
  334. {
  335. if (fd_out == -1)
  336. return -1;
  337. fflush(stdout);
  338. close(1);
  339. finish_command(&column_process);
  340. dup2(fd_out, 1);
  341. close(fd_out);
  342. fd_out = -1;
  343. return 0;
  344. }