From: kurok <22548029+kurok@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:31:33 +0000 (+0000) Subject: libsmartcols: add scols_table_calculate(), pager: add less --header support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=049aef854ecf80879d9ef6f2f484383cf16fa16e;p=thirdparty%2Futil-linux.git libsmartcols: add scols_table_calculate(), pager: add less --header support Add scols_table_calculate() public API to allow column width calculation without printing. This enables callers to query column widths (via scols_column_get_width()) before starting output, which is needed to set up "less --header" for freezing table headers and the first column. The new function runs __scols_initialize_printing() (which includes __scols_calculate()) and sets an is_calculated flag so that subsequent scols_print_table() calls skip redundant recalculation. Add pager_open_header() to lib/pager.c that sets the LESS environment variable with "--header N,M" options before spawning the pager process. This freezes the first N lines (header row) and first M character columns (first table column) when scrolling in less. Addresses: https://github.com/util-linux/util-linux/issues/3542 --- diff --git a/include/pager.h b/include/pager.h index f935ddce1..950f97836 100644 --- a/include/pager.h +++ b/include/pager.h @@ -7,7 +7,10 @@ #ifndef UTIL_LINUX_PAGER #define UTIL_LINUX_PAGER +#include + void pager_open(void); +void pager_open_header(int header_lines, size_t first_col_width); void pager_close(void); #endif diff --git a/lib/pager.c b/lib/pager.c index 094716c7a..12defcab1 100644 --- a/lib/pager.c +++ b/lib/pager.c @@ -53,10 +53,33 @@ static inline void close_pair(int fd[2]) close(fd[1]); } +static int pager_header_lines; +static size_t pager_header_width; + static void pager_preexec(void) { - if (getenv("LESS") == NULL && setenv("LESS", "FRSX", 0) != 0) - warn(_("failed to set the %s environment variable"), "LESS"); + if (getenv("LESS") == NULL) { + if (pager_header_lines > 0) { + char less_env[256]; + + if (pager_header_width > 0) + snprintf(less_env, sizeof(less_env), + "FRSX --header %d,%zu", + pager_header_lines, + pager_header_width); + else + snprintf(less_env, sizeof(less_env), + "FRSX --header %d", + pager_header_lines); + + if (setenv("LESS", less_env, 0) != 0) + warn(_("failed to set the %s environment variable"), "LESS"); + } else { + if (setenv("LESS", "FRSX", 0) != 0) + warn(_("failed to set the %s environment variable"), "LESS"); + } + } + if (getenv("LV") == NULL && setenv("LV", "-c", 0) != 0) warn(_("failed to set the %s environment variable"), "LV"); } @@ -255,6 +278,22 @@ void pager_open(void) } } +/* Setup pager with "less --header" support to freeze header lines and + * optionally freeze the first column. The @header_lines specifies the + * number of header lines to freeze (typically 1 for table header). + * The @first_col_width specifies the number of character columns to + * freeze (width of first column including separator), or 0 to not + * freeze any column. + */ +void pager_open_header(int header_lines, size_t first_col_width) +{ + pager_header_lines = header_lines; + pager_header_width = first_col_width; + pager_open(); + pager_header_lines = 0; + pager_header_width = 0; +} + /* Close pager and restore original std{out,err}. */ void pager_close(void) diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in index f36401d9d..722ade8bb 100644 --- a/libsmartcols/src/libsmartcols.h.in +++ b/libsmartcols/src/libsmartcols.h.in @@ -430,6 +430,7 @@ extern size_t scols_table_get_termheight(const struct libscols_table *tb); /* table_print.c */ +extern int scols_table_calculate(struct libscols_table *tb); extern int scols_print_table(struct libscols_table *tb); extern int scols_print_table_to_string(struct libscols_table *tb, char **data); diff --git a/libsmartcols/src/libsmartcols.sym b/libsmartcols/src/libsmartcols.sym index c510f63ba..64fd12daa 100644 --- a/libsmartcols/src/libsmartcols.sym +++ b/libsmartcols/src/libsmartcols.sym @@ -263,5 +263,6 @@ SMARTCOLS_2.42 { scols_column_get_headercolor; scols_column_refer_annotation; scols_column_get_annotation; + scols_table_calculate; } SMARTCOLS_2.41; diff --git a/libsmartcols/src/print-api.c b/libsmartcols/src/print-api.c index 15841b2bc..3d03ca09e 100644 --- a/libsmartcols/src/print-api.c +++ b/libsmartcols/src/print-api.c @@ -98,6 +98,46 @@ int scols_table_print_range_to_string( } #endif +/** + * scols_table_calculate: + * @tb: table + * + * Force column width calculation without printing. After this call, + * scols_column_get_width() returns valid column widths. This is useful + * when you need to know column widths before printing, for example to + * set up a pager with "less --header" to freeze the header row and + * first column. + * + * Note that scols_print_table() will skip recalculation if this + * function has already been called. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.42 + */ +int scols_table_calculate(struct libscols_table *tb) +{ + struct ul_buffer buf = UL_INIT_BUFFER; + int rc; + + if (!tb) + return -EINVAL; + if (list_empty(&tb->tb_columns)) + return -EINVAL; + if (list_empty(&tb->tb_lines)) + return 0; + + DBG(TAB, ul_debugobj(tb, "pre-calculate")); + + rc = __scols_initialize_printing(tb, &buf); + __scols_cleanup_printing(tb, &buf); + + if (rc == 0) + tb->is_calculated = 1; + + return rc; +} + static int do_print_table(struct libscols_table *tb, int *is_empty) { int rc = 0; @@ -161,6 +201,7 @@ static int do_print_table(struct libscols_table *tb, int *is_empty) } done: __scols_cleanup_printing(tb, &buf); + tb->is_calculated = 0; return rc; } diff --git a/libsmartcols/src/print.c b/libsmartcols/src/print.c index 488bb86d6..bdc38120e 100644 --- a/libsmartcols/src/print.c +++ b/libsmartcols/src/print.c @@ -1334,7 +1334,7 @@ int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf if (has_groups(tb) && scols_table_is_tree(tb)) scols_groups_fix_members_order(tb); - if (tb->format == SCOLS_FMT_HUMAN) { + if (tb->format == SCOLS_FMT_HUMAN && !tb->is_calculated) { rc = __scols_calculate(tb, buf); if (rc != 0) goto err; diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h index a0f02e075..2c70c3366 100644 --- a/libsmartcols/src/smartcolsP.h +++ b/libsmartcols/src/smartcolsP.h @@ -288,7 +288,8 @@ struct libscols_table { no_headings , /* don't print header */ no_encode , /* don't care about control and non-printable chars */ no_linesep , /* don't print line separator */ - no_wrap ; /* never wrap lines */ + no_wrap , /* never wrap lines */ + is_calculated ; /* column widths already calculated */ }; #define IS_ITER_FORWARD(_i) ((_i)->direction == SCOLS_ITER_FORWARD)