]>
Commit | Line | Data |
---|---|---|
06a8decd KZ |
1 | #include "smartcolsP.h" |
2 | #include "mbsalign.h" | |
3 | ||
4 | static void dbg_column(struct libscols_table *tb, struct libscols_column *cl) | |
5 | { | |
6 | if (scols_column_is_hidden(cl)) { | |
7 | DBG(COL, ul_debugobj(cl, "%s (hidden) ignored", cl->header.data)); | |
8 | return; | |
9 | } | |
10 | ||
11 | DBG(COL, ul_debugobj(cl, "%15s seq=%zu, width=%zd, " | |
12 | "hint=%d, avg=%zu, max=%zu, min=%zu, " | |
13 | "extreme=%s %s", | |
14 | ||
15 | cl->header.data, cl->seqnum, cl->width, | |
16 | cl->width_hint > 1 ? (int) cl->width_hint : | |
17 | (int) (cl->width_hint * tb->termwidth), | |
18 | cl->width_avg, | |
19 | cl->width_max, | |
20 | cl->width_min, | |
21 | cl->is_extreme ? "yes" : "not", | |
22 | cl->flags & SCOLS_FL_TRUNC ? "trunc" : "")); | |
23 | } | |
24 | ||
25 | static void dbg_columns(struct libscols_table *tb) | |
26 | { | |
27 | struct libscols_iter itr; | |
28 | struct libscols_column *cl; | |
29 | ||
30 | scols_reset_iter(&itr, SCOLS_ITER_FORWARD); | |
31 | while (scols_table_next_column(tb, &itr, &cl) == 0) | |
32 | dbg_column(tb, cl); | |
33 | } | |
34 | ||
281a2f32 KZ |
35 | static int count_cell_width(struct libscols_table *tb, |
36 | struct libscols_line *ln, | |
37 | struct libscols_column *cl, | |
38 | struct libscols_buffer *buf) | |
39 | { | |
40 | size_t len; | |
41 | char *data; | |
42 | int rc; | |
43 | ||
44 | rc = __cell_to_buffer(tb, ln, cl, buf); | |
45 | if (rc) | |
46 | return rc; | |
47 | ||
48 | data = buffer_get_data(buf); | |
49 | if (!data) | |
50 | len = 0; | |
51 | else if (scols_column_is_customwrap(cl)) | |
52 | len = cl->wrap_chunksize(cl, data, cl->wrapfunc_data); | |
53 | else | |
54 | len = mbs_safe_width(data); | |
55 | ||
56 | if (len == (size_t) -1) /* ignore broken multibyte strings */ | |
57 | len = 0; | |
58 | cl->width_max = max(len, cl->width_max); | |
59 | ||
60 | if (cl->is_extreme && cl->width_avg && len > cl->width_avg * 2) | |
61 | return 0; | |
62 | ||
042f62df | 63 | if (scols_column_is_noextremes(cl)) { |
281a2f32 KZ |
64 | cl->extreme_sum += len; |
65 | cl->extreme_count++; | |
66 | } | |
67 | cl->width = max(len, cl->width); | |
68 | if (scols_column_is_tree(cl)) { | |
69 | size_t treewidth = buffer_get_safe_art_size(buf); | |
70 | cl->width_treeart = max(cl->width_treeart, treewidth); | |
71 | } | |
72 | return 0; | |
73 | } | |
74 | ||
47b6eccc KZ |
75 | |
76 | static int walk_count_cell_width(struct libscols_table *tb, | |
77 | struct libscols_line *ln, | |
78 | struct libscols_column *cl, | |
79 | void *data) | |
80 | { | |
81 | return count_cell_width(tb, ln, cl, (struct libscols_buffer *) data); | |
82 | } | |
83 | ||
06a8decd KZ |
84 | /* |
85 | * This function counts column width. | |
86 | * | |
87 | * For the SCOLS_FL_NOEXTREMES columns it is possible to call this function | |
88 | * two times. The first pass counts the width and average width. If the column | |
89 | * contains fields that are too large (a width greater than 2 * average) then | |
90 | * the column is marked as "extreme". In the second pass all extreme fields | |
91 | * are ignored and the column width is counted from non-extreme fields only. | |
92 | */ | |
93 | static int count_column_width(struct libscols_table *tb, | |
94 | struct libscols_column *cl, | |
95 | struct libscols_buffer *buf) | |
96 | { | |
281a2f32 | 97 | int rc = 0, no_header = 0; |
06a8decd KZ |
98 | |
99 | assert(tb); | |
100 | assert(cl); | |
101 | ||
102 | cl->width = 0; | |
103 | if (!cl->width_min) { | |
104 | if (cl->width_hint < 1 && scols_table_is_maxout(tb) && tb->is_term) { | |
105 | cl->width_min = (size_t) (cl->width_hint * tb->termwidth); | |
106 | if (cl->width_min && !is_last_column(cl)) | |
107 | cl->width_min--; | |
108 | } | |
109 | if (scols_cell_get_data(&cl->header)) { | |
110 | size_t len = mbs_safe_width(scols_cell_get_data(&cl->header)); | |
111 | cl->width_min = max(cl->width_min, len); | |
112 | } else | |
113 | no_header = 1; | |
114 | ||
115 | if (!cl->width_min) | |
116 | cl->width_min = 1; | |
117 | } | |
118 | ||
47b6eccc KZ |
119 | if (scols_table_is_tree(tb)) { |
120 | /* Count width for tree */ | |
121 | rc = scols_walk_tree(tb, cl, walk_count_cell_width, (void *) buf); | |
06a8decd KZ |
122 | if (rc) |
123 | goto done; | |
47b6eccc KZ |
124 | } else { |
125 | /* Count width for list */ | |
126 | struct libscols_iter itr; | |
127 | struct libscols_line *ln; | |
128 | ||
129 | scols_reset_iter(&itr, SCOLS_ITER_FORWARD); | |
130 | while (scols_table_next_line(tb, &itr, &ln) == 0) { | |
131 | rc = count_cell_width(tb, ln, cl, buf); | |
132 | if (rc) | |
133 | goto done; | |
134 | } | |
06a8decd KZ |
135 | } |
136 | ||
ea7fb72e KZ |
137 | if (scols_column_is_tree(cl) && has_groups(tb)) { |
138 | /* We don't fill buffer with groups tree ascii art during width | |
218b1dd6 | 139 | * calculation. The print function only enlarge grpset[] and we |
ea7fb72e KZ |
140 | * calculate final width from grpset_size. |
141 | */ | |
142 | size_t gprwidth = tb->grpset_size + 1; | |
143 | cl->width_treeart += gprwidth; | |
144 | cl->width_max += gprwidth; | |
145 | cl->width += gprwidth; | |
281a2f32 KZ |
146 | if (cl->extreme_count) |
147 | cl->extreme_sum += gprwidth; | |
ea7fb72e KZ |
148 | } |
149 | ||
281a2f32 KZ |
150 | if (cl->extreme_count && cl->width_avg == 0) { |
151 | cl->width_avg = cl->extreme_sum / cl->extreme_count; | |
06a8decd KZ |
152 | if (cl->width_avg && cl->width_max > cl->width_avg * 2) |
153 | cl->is_extreme = 1; | |
154 | } | |
155 | ||
156 | /* enlarge to minimal width */ | |
157 | if (cl->width < cl->width_min && !scols_column_is_strict_width(cl)) | |
158 | cl->width = cl->width_min; | |
159 | ||
160 | /* use absolute size for large columns */ | |
161 | else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint | |
162 | && cl->width_min < (size_t) cl->width_hint) | |
163 | ||
164 | cl->width = (size_t) cl->width_hint; | |
165 | ||
166 | ||
167 | /* Column without header and data, set minimal size to zero (default is 1) */ | |
168 | if (cl->width_max == 0 && no_header && cl->width_min == 1 && cl->width <= 1) | |
169 | cl->width = cl->width_min = 0; | |
170 | ||
171 | done: | |
172 | ON_DBG(COL, dbg_column(tb, cl)); | |
173 | return rc; | |
174 | } | |
175 | ||
176 | /* | |
177 | * This is core of the scols_* voodoo... | |
178 | */ | |
179 | int __scols_calculate(struct libscols_table *tb, struct libscols_buffer *buf) | |
180 | { | |
181 | struct libscols_column *cl; | |
182 | struct libscols_iter itr; | |
183 | size_t width = 0, width_min = 0; /* output width */ | |
184 | int stage, rc = 0; | |
d52f5542 | 185 | int extremes = 0, group_ncolumns = 0; |
06a8decd KZ |
186 | size_t colsepsz; |
187 | ||
188 | ||
d52f5542 | 189 | DBG(TAB, ul_debugobj(tb, "-----calculate-(termwidth=%zu)-----", tb->termwidth)); |
ea7fb72e | 190 | tb->is_dummy_print = 1; |
06a8decd KZ |
191 | |
192 | colsepsz = mbs_safe_width(colsep(tb)); | |
193 | ||
ea7fb72e | 194 | if (has_groups(tb)) |
d52f5542 | 195 | group_ncolumns = 1; |
d52f5542 | 196 | |
06a8decd KZ |
197 | /* set basic columns width |
198 | */ | |
199 | scols_reset_iter(&itr, SCOLS_ITER_FORWARD); | |
200 | while (scols_table_next_column(tb, &itr, &cl) == 0) { | |
201 | int is_last; | |
202 | ||
203 | if (scols_column_is_hidden(cl)) | |
204 | continue; | |
d52f5542 KZ |
205 | |
206 | /* we print groups chart only for the for the first tree column */ | |
207 | if (scols_column_is_tree(cl) && group_ncolumns == 1) { | |
208 | cl->is_groups = 1; | |
209 | group_ncolumns++; | |
210 | } | |
211 | ||
06a8decd KZ |
212 | rc = count_column_width(tb, cl, buf); |
213 | if (rc) | |
214 | goto done; | |
215 | ||
216 | is_last = is_last_column(cl); | |
217 | ||
218 | width += cl->width + (is_last ? 0 : colsepsz); /* separator for non-last column */ | |
219 | width_min += cl->width_min + (is_last ? 0 : colsepsz); | |
d52f5542 KZ |
220 | if (cl->is_extreme) |
221 | extremes++; | |
06a8decd KZ |
222 | } |
223 | ||
224 | if (!tb->is_term) { | |
225 | DBG(TAB, ul_debugobj(tb, " non-terminal output")); | |
226 | goto done; | |
227 | } | |
228 | ||
229 | /* be paranoid */ | |
230 | if (width_min > tb->termwidth && scols_table_is_maxout(tb)) { | |
231 | DBG(TAB, ul_debugobj(tb, " min width larger than terminal! [width=%zu, term=%zu]", width_min, tb->termwidth)); | |
232 | ||
233 | scols_reset_iter(&itr, SCOLS_ITER_FORWARD); | |
234 | while (width_min > tb->termwidth | |
235 | && scols_table_next_column(tb, &itr, &cl) == 0) { | |
236 | if (scols_column_is_hidden(cl)) | |
237 | continue; | |
238 | width_min--; | |
239 | cl->width_min--; | |
240 | } | |
241 | DBG(TAB, ul_debugobj(tb, " min width reduced to %zu", width_min)); | |
242 | } | |
243 | ||
244 | /* reduce columns with extreme fields */ | |
245 | if (width > tb->termwidth && extremes) { | |
246 | DBG(TAB, ul_debugobj(tb, " reduce width (extreme columns)")); | |
247 | ||
248 | scols_reset_iter(&itr, SCOLS_ITER_FORWARD); | |
249 | while (scols_table_next_column(tb, &itr, &cl) == 0) { | |
250 | size_t org_width; | |
251 | ||
252 | if (!cl->is_extreme || scols_column_is_hidden(cl)) | |
253 | continue; | |
254 | ||
255 | org_width = cl->width; | |
256 | rc = count_column_width(tb, cl, buf); | |
257 | if (rc) | |
258 | goto done; | |
259 | ||
260 | if (org_width > cl->width) | |
261 | width -= org_width - cl->width; | |
262 | else | |
263 | extremes--; /* hmm... nothing reduced */ | |
264 | } | |
265 | } | |
266 | ||
267 | if (width < tb->termwidth) { | |
268 | if (extremes) { | |
269 | DBG(TAB, ul_debugobj(tb, " enlarge width (extreme columns)")); | |
270 | ||
271 | /* enlarge the first extreme column */ | |
272 | scols_reset_iter(&itr, SCOLS_ITER_FORWARD); | |
273 | while (scols_table_next_column(tb, &itr, &cl) == 0) { | |
274 | size_t add; | |
275 | ||
276 | if (!cl->is_extreme || scols_column_is_hidden(cl)) | |
277 | continue; | |
278 | ||
279 | /* this column is too large, ignore? | |
280 | if (cl->width_max - cl->width > | |
281 | (tb->termwidth - width)) | |
282 | continue; | |
283 | */ | |
284 | ||
285 | add = tb->termwidth - width; | |
286 | if (add && cl->width + add > cl->width_max) | |
287 | add = cl->width_max - cl->width; | |
288 | ||
289 | cl->width += add; | |
290 | width += add; | |
291 | ||
292 | if (width == tb->termwidth) | |
293 | break; | |
294 | } | |
295 | } | |
296 | ||
297 | if (width < tb->termwidth && scols_table_is_maxout(tb)) { | |
298 | DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); | |
299 | ||
300 | /* try enlarging all columns */ | |
301 | while (width < tb->termwidth) { | |
302 | scols_reset_iter(&itr, SCOLS_ITER_FORWARD); | |
303 | while (scols_table_next_column(tb, &itr, &cl) == 0) { | |
304 | if (scols_column_is_hidden(cl)) | |
305 | continue; | |
306 | cl->width++; | |
307 | width++; | |
308 | if (width == tb->termwidth) | |
309 | break; | |
310 | } | |
311 | } | |
312 | } else if (width < tb->termwidth) { | |
313 | /* enlarge the last column */ | |
314 | struct libscols_column *col = list_entry( | |
315 | tb->tb_columns.prev, struct libscols_column, cl_columns); | |
316 | ||
317 | DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); | |
318 | ||
319 | if (!scols_column_is_right(col) && tb->termwidth - width > 0) { | |
320 | col->width += tb->termwidth - width; | |
321 | width = tb->termwidth; | |
322 | } | |
323 | } | |
324 | } | |
325 | ||
326 | /* bad, we have to reduce output width, this is done in three stages: | |
327 | * | |
328 | * 1) trunc relative with trunc flag if the column width is greater than | |
329 | * expected column width (it means "width_hint * terminal_width"). | |
330 | * | |
331 | * 2) trunc all with trunc flag | |
332 | * | |
333 | * 3) trunc relative without trunc flag | |
334 | * | |
335 | * Note that SCOLS_FL_WRAP (if no custom wrap function is specified) is | |
336 | * interpreted as SCOLS_FL_TRUNC. | |
337 | */ | |
338 | for (stage = 1; width > tb->termwidth && stage <= 3; ) { | |
339 | size_t org_width = width; | |
340 | ||
341 | DBG(TAB, ul_debugobj(tb, " reduce width - #%d stage (current=%zu, wanted=%zu)", | |
342 | stage, width, tb->termwidth)); | |
343 | ||
344 | scols_reset_iter(&itr, SCOLS_ITER_FORWARD); | |
345 | while (scols_table_next_column(tb, &itr, &cl) == 0) { | |
346 | ||
347 | int trunc_flag = 0; | |
348 | ||
349 | DBG(TAB, ul_debugobj(cl, " checking %s (width=%zu, treeart=%zu)", | |
350 | cl->header.data, cl->width, cl->width_treeart)); | |
351 | if (scols_column_is_hidden(cl)) | |
352 | continue; | |
353 | if (width <= tb->termwidth) | |
354 | break; | |
355 | ||
356 | /* never truncate if already minimal width */ | |
357 | if (cl->width == cl->width_min) | |
358 | continue; | |
359 | ||
360 | /* never truncate the tree */ | |
361 | if (scols_column_is_tree(cl) && width <= cl->width_treeart) | |
362 | continue; | |
363 | ||
364 | /* nothing to truncate */ | |
f6b6beaf | 365 | if (cl->width == 0) |
06a8decd KZ |
366 | continue; |
367 | ||
368 | trunc_flag = scols_column_is_trunc(cl) | |
369 | || (scols_column_is_wrap(cl) && !scols_column_is_customwrap(cl)); | |
370 | ||
371 | switch (stage) { | |
372 | /* #1 stage - trunc relative with TRUNC flag */ | |
373 | case 1: | |
374 | if (!trunc_flag) /* ignore: missing flag */ | |
375 | break; | |
376 | if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ | |
377 | break; | |
378 | if (cl->width < (size_t) (cl->width_hint * tb->termwidth)) /* ignore: smaller than expected width */ | |
379 | break; | |
380 | ||
381 | DBG(TAB, ul_debugobj(tb, " reducing (relative with flag)")); | |
382 | cl->width--; | |
383 | width--; | |
384 | break; | |
385 | ||
386 | /* #2 stage - trunc all with TRUNC flag */ | |
387 | case 2: | |
388 | if (!trunc_flag) /* ignore: missing flag */ | |
389 | break; | |
390 | ||
391 | DBG(TAB, ul_debugobj(tb, " reducing (all with flag)")); | |
392 | cl->width--; | |
393 | width--; | |
394 | break; | |
395 | ||
396 | /* #3 stage - trunc relative without flag */ | |
397 | case 3: | |
398 | if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ | |
399 | break; | |
400 | ||
401 | DBG(TAB, ul_debugobj(tb, " reducing (relative without flag)")); | |
402 | cl->width--; | |
403 | width--; | |
404 | break; | |
405 | } | |
406 | ||
407 | /* hide zero width columns */ | |
408 | if (cl->width == 0) | |
409 | cl->flags |= SCOLS_FL_HIDDEN; | |
410 | } | |
411 | ||
412 | /* the current stage is without effect, go to the next */ | |
413 | if (org_width == width) | |
414 | stage++; | |
415 | } | |
416 | ||
417 | /* ignore last column(s) or force last column to be truncated if | |
418 | * nowrap mode enabled */ | |
419 | if (tb->no_wrap && width > tb->termwidth) { | |
420 | scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); | |
421 | while (scols_table_next_column(tb, &itr, &cl) == 0) { | |
422 | ||
423 | if (scols_column_is_hidden(cl)) | |
424 | continue; | |
425 | if (width <= tb->termwidth) | |
426 | break; | |
427 | if (width - cl->width < tb->termwidth) { | |
428 | size_t r = width - tb->termwidth; | |
429 | ||
430 | cl->flags |= SCOLS_FL_TRUNC; | |
431 | cl->width -= r; | |
432 | width -= r; | |
433 | } else { | |
434 | cl->flags |= SCOLS_FL_HIDDEN; | |
435 | width -= cl->width + colsepsz; | |
436 | } | |
437 | } | |
438 | } | |
439 | done: | |
ea7fb72e | 440 | tb->is_dummy_print = 0; |
883246a1 | 441 | DBG(TAB, ul_debugobj(tb, "-----final width: %zu (rc=%d)-----", width, rc)); |
06a8decd KZ |
442 | ON_DBG(TAB, dbg_columns(tb)); |
443 | ||
444 | return rc; | |
445 | } |