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.

417 lines
9.7KB

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