column: add dense layout support

Normally all cells (and in turn columns) share the same width. This layout mode can waste space because one long item can stretch our all columns. With COL_DENSE enabled, column width is calculated indepdendently. All columns are shrunk to minimum, then it attempts to push cells of the last row over to the next column with hope that everything still fits even there's one row less. The process is repeated until the new layout cannot fit in given width any more, or there's only one row left (perfect!). Apparently, this mode consumes more cpu than the old one, but it makes better use of terminal space. For layouting one or two screens, cpu usage should not be detectable. This patch introduces option handling code besides layout modes and enable/disable to expose this feature as "dense". The feature can be turned off by specifying "nodense". Thanks-to: Ramsay Jones <ramsay@ramsay1.demon.co.uk> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Apr 13, 2012 at 17:54 UTC 3f8eccbe166ecff79a986b7ce87df4963cc873b2
4 files changed +137 -1
Documentation/config.txt
+4
@@ -854,6 +854,10 @@ column.ui::
854 fill rows before columns
855 `plain`;;
856 show in one column
857 +`dense`;;
858 + make unequal size columns to utilize more space
859 +`nodense`;;
860 + make equal size columns
861 --
862 +
863 This option defaults to 'never'.
column.c
+83 -1
@@ -15,6 +15,7 @@ struct column_data {
15
16 int rows, cols;
17 int *len; /* cell length */
18 + int *width; /* index to the longest row in column */
19 };
20
21 /* return length of 's' in letters, ANSI escapes stripped */
@@ -56,6 +57,57 @@ static void layout(struct column_data *data, int *width)
57 data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
58 }
59
60 +static void compute_column_width(struct column_data *data)
61 +{
62 + int i, x, y;
63 + for (x = 0; x < data->cols; x++) {
64 + data->width[x] = XY2LINEAR(data, x, 0);
65 + for (y = 0; y < data->rows; y++) {
66 + i = XY2LINEAR(data, x, y);
67 + if (i < data->list->nr &&
68 + data->len[data->width[x]] < data->len[i])
69 + data->width[x] = i;
70 + }
71 + }
72 +}
73 +
74 +/*
75 + * Shrink all columns by shortening them one row each time (and adding
76 + * more columns along the way). Hopefully the longest cell will be
77 + * moved to the next column, column is shrunk so we have more space
78 + * for new columns. The process ends when the whole thing no longer
79 + * fits in data->total_width.
80 + */
81 +static void shrink_columns(struct column_data *data)
82 +{
83 + data->width = xrealloc(data->width,
84 + sizeof(*data->width) * data->cols);
85 + while (data->rows > 1) {
86 + int x, total_width, cols, rows;
87 + rows = data->rows;
88 + cols = data->cols;
89 +
90 + data->rows--;
91 + data->cols = DIV_ROUND_UP(data->list->nr, data->rows);
92 + if (data->cols != cols)
93 + data->width = xrealloc(data->width,
94 + sizeof(*data->width) * data->cols);
95 + compute_column_width(data);
96 +
97 + total_width = strlen(data->opts.indent);
98 + for (x = 0; x < data->cols; x++) {
99 + total_width += data->len[data->width[x]];
100 + total_width += data->opts.padding;
101 + }
102 + if (total_width > data->opts.width) {
103 + data->rows = rows;
104 + data->cols = cols;
105 + break;
106 + }
107 + }
108 + compute_column_width(data);
109 +}
110 +
111 /* Display without layout when not enabled */
112 static void display_plain(const struct string_list *list,
113 const char *indent, const char *nl)
@@ -75,7 +127,18 @@ static int display_cell(struct column_data *data, int initial_width,
127 i = XY2LINEAR(data, x, y);
128 if (i >= data->list->nr)
129 return -1;
130 +
131 len = data->len[i];
132 + if (data->width && data->len[data->width[x]] < initial_width) {
133 + /*
134 + * empty_cell has initial_width chars, if real column
135 + * is narrower, increase len a bit so we fill less
136 + * space.
137 + */
138 + len += initial_width - data->len[data->width[x]];
139 + len -= data->opts.padding;
140 + }
141 +
142 if (COL_LAYOUT(data->colopts) == COL_COLUMN)
143 newline = i + data->rows >= data->list->nr;
144 else
@@ -108,6 +171,9 @@ static void display_table(const struct string_list *list,
171
172 layout(&data, &initial_width);
173
174 + if (colopts & COL_DENSE)
175 + shrink_columns(&data);
176 +
177 empty_cell = xmalloc(initial_width + 1);
178 memset(empty_cell, ' ', initial_width);
179 empty_cell[initial_width] = '\0';
@@ -118,6 +184,7 @@ static void display_table(const struct string_list *list,
184 }
185
186 free(data.len);
187 + free(data.width);
188 free(empty_cell);
189 }
190
@@ -183,13 +250,22 @@ static int parse_option(const char *arg, int len, unsigned int *colopts,
250 { "plain", COL_PLAIN, COL_LAYOUT_MASK },
251 { "column", COL_COLUMN, COL_LAYOUT_MASK },
252 { "row", COL_ROW, COL_LAYOUT_MASK },
253 + { "dense", COL_DENSE, 0 },
254 };
255 int i;
256
257 for (i = 0; i < ARRAY_SIZE(opts); i++) {
190 - int arg_len = len, name_len;
258 + int set = 1, arg_len = len, name_len;
259 const char *arg_str = arg;
260
261 + if (!opts[i].mask) {
262 + if (arg_len > 2 && !strncmp(arg_str, "no", 2)) {
263 + arg_str += 2;
264 + arg_len -= 2;
265 + set = 0;
266 + }
267 + }
268 +
269 name_len = strlen(opts[i].name);
270 if (arg_len != name_len ||
271 strncmp(arg_str, opts[i].name, name_len))
@@ -206,6 +282,12 @@ static int parse_option(const char *arg, int len, unsigned int *colopts,
282
283 if (opts[i].mask)
284 *colopts = (*colopts & ~opts[i].mask) | opts[i].value;
285 + else {
286 + if (set)
287 + *colopts |= opts[i].value;
288 + else
289 + *colopts &= ~opts[i].value;
290 + }
291 return 0;
292 }
293
column.h
+2
@@ -4,6 +4,8 @@
4 #define COL_LAYOUT_MASK 0x000F
5 #define COL_ENABLE_MASK 0x0030 /* always, never or auto */
6 #define COL_PARSEOPT 0x0040 /* --column is given from cmdline */
7 +#define COL_DENSE 0x0080 /* Shrink columns when possible,
8 + making space for more columns */
9
10 #define COL_DISABLED 0x0000 /* must be zero */
11 #define COL_ENABLED 0x0010
t/t9002-column.sh
+48
@@ -90,6 +90,30 @@ EOF
90 test_cmp expected actual
91 '
92
93 +test_expect_success '20 columns, nodense' '
94 + cat >expected <<\EOF &&
95 +one seven
96 +two eight
97 +three nine
98 +four ten
99 +five eleven
100 +six
101 +EOF
102 + git column --mode=column,nodense < lista > actual &&
103 + test_cmp expected actual
104 +'
105 +
106 +test_expect_success '20 columns, dense' '
107 + cat >expected <<\EOF &&
108 +one five nine
109 +two six ten
110 +three seven eleven
111 +four eight
112 +EOF
113 + git column --mode=column,dense < lista > actual &&
114 + test_cmp expected actual
115 +'
116 +
117 test_expect_success '20 columns, padding 2' '
118 cat >expected <<\EOF &&
119 one seven
@@ -129,4 +153,28 @@ EOF
153 test_cmp expected actual
154 '
155
156 +test_expect_success '20 columns, row first, nodense' '
157 + cat >expected <<\EOF &&
158 +one two
159 +three four
160 +five six
161 +seven eight
162 +nine ten
163 +eleven
164 +EOF
165 + git column --mode=row,nodense <lista >actual &&
166 + test_cmp expected actual
167 +'
168 +
169 +test_expect_success '20 columns, row first, dense' '
170 + cat >expected <<\EOF &&
171 +one two three
172 +four five six
173 +seven eight nine
174 +ten eleven
175 +EOF
176 + git column --mode=row,dense <lista >actual &&
177 + test_cmp expected actual
178 +'
179 +
180 test_done