]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libsmartcols: add scols_table_calculate(), pager: add less --header support
authorkurok <22548029+kurok@users.noreply.github.com>
Fri, 13 Mar 2026 11:31:33 +0000 (11:31 +0000)
committerkurok <22548029+kurok@users.noreply.github.com>
Fri, 13 Mar 2026 11:32:30 +0000 (11:32 +0000)
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

include/pager.h
lib/pager.c
libsmartcols/src/libsmartcols.h.in
libsmartcols/src/libsmartcols.sym
libsmartcols/src/print-api.c
libsmartcols/src/print.c
libsmartcols/src/smartcolsP.h

index f935ddce1baeeeb6c3a5988ac05a91725bcd0c1b..950f97836e6121c45f0e842f7e94022a0eaae261 100644 (file)
@@ -7,7 +7,10 @@
 #ifndef UTIL_LINUX_PAGER
 #define UTIL_LINUX_PAGER
 
+#include <stddef.h>
+
 void pager_open(void);
+void pager_open_header(int header_lines, size_t first_col_width);
 void pager_close(void);
 
 #endif
index 094716c7a127fda40c555b14be338850a945035e..12defcab1f5ec88d2a2a9038f6694e4100922a95 100644 (file)
@@ -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)
index f36401d9d290de008a082021849909b09662dca8..722ade8bb1b2044be993e5bc55220cb784877657 100644 (file)
@@ -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);
 
index c510f63ba8a9da055b56cbaf30e9a75b551f8f50..64fd12daa0496f7ad7b01023aff6d06b5475eaab 100644 (file)
@@ -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;
 
index 15841b2bc6e9f81f0e5e2dfd8f5ef352535f2e73..3d03ca09ef72ec532420963e3018059c4c56695b 100644 (file)
@@ -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;
 }
 
index 488bb86d68211b365b042e6bba2804b1e82ff2e2..bdc38120ebad72ec4474b943099ba75fb335aac5 100644 (file)
@@ -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;
index a0f02e0750484fa0e1917ab1c009c58de0fe4aee..2c70c336612464524621dbfe3a99de1dd1061a78 100644 (file)
@@ -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)