Use termio.c/h as a new base for pretty printing in pacman. It collects a lot of the low-level code that pacman uses to output tables. Some key changes: - string_length -> grapheme_count This better reflects the intent of the function: to return the number of grapheme's that'll be printed, its not returning the actual string's length. - indentprint -> indentprint_r indentprint_r is a renterant version of indentprint. This allows for indentprint_r to be used in a loop easily to output the contents of a alpm_list_t without special duplicated logic. This version of indentprint should also be a bit cleaner to read and maintain. - Add indent_string/indent_list/indent_list_linebreak These functions print either a string or a list of strings out on the terminal correctly with no fuss. These are intended to be used by pacman to aid in correctly formatting a table. These new functions should replace string_display/list_display/etc eventually. How pacman prints tables needs to be overhauled first. In the meanwhile, however, they still offer some value as they improve and unify unicode support. Signed-off-by: Simon Gomizelj <simongmzlj@gmail.com> --- src/pacman/Makefile.am | 1 + src/pacman/callback.c | 1 + src/pacman/package.c | 11 +-- src/pacman/termio.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++ src/pacman/termio.h | 17 +++++ src/pacman/util.c | 158 +++----------------------------------- src/pacman/util.h | 2 - 7 files changed, 240 insertions(+), 154 deletions(-) create mode 100644 src/pacman/termio.c create mode 100644 src/pacman/termio.h diff --git a/src/pacman/Makefile.am b/src/pacman/Makefile.am index ed51573..b94e578 100644 --- a/src/pacman/Makefile.am +++ b/src/pacman/Makefile.am @@ -38,6 +38,7 @@ pacman_SOURCES = \ sync.c \ callback.h callback.c \ upgrade.c \ + termio.h termio.c \ util.h util.c \ util-common.h util-common.c diff --git a/src/pacman/callback.c b/src/pacman/callback.c index 71d9d04..e93ccd2 100644 --- a/src/pacman/callback.c +++ b/src/pacman/callback.c @@ -32,6 +32,7 @@ /* pacman */ #include "pacman.h" +#include "termio.h" #include "callback.h" #include "util.h" #include "conf.h" diff --git a/src/pacman/package.c b/src/pacman/package.c index 403896a..5af7586 100644 --- a/src/pacman/package.c +++ b/src/pacman/package.c @@ -31,6 +31,7 @@ /* pacman */ #include "package.h" +#include "termio.h" #include "util.h" #include "conf.h" @@ -42,14 +43,14 @@ static void string_display(const char *title, const char *string, unsigned short len = 0; if(title) { - len = (unsigned short)string_length(title) + 1; + len = (unsigned short)grapheme_count(title) + 1; printf("%s%s%s ", config->colstr.title, title, config->colstr.nocolor); } if(string == NULL || string[0] == '\0') { printf(_("None")); } else { - indentprint(string, (unsigned short)len, maxcols); + indent_string(string, (unsigned short)len, maxcols); } printf("\n"); } @@ -97,7 +98,7 @@ void signature_display(const char *title, alpm_siglist_t *siglist, unsigned short len = 0; if(title) { - len = (unsigned short)string_length(title) + 1; + len = (unsigned short)grapheme_count(title) + 1; printf("%s%s%s ", config->colstr.title, title, config->colstr.nocolor); } @@ -158,7 +159,7 @@ void signature_display(const char *title, alpm_siglist_t *siglist, if(ret == -1) { continue; } - indentprint(sigline, len, maxcols); + indent_string(sigline, len, maxcols); printf("\n"); free(sigline); } @@ -513,7 +514,7 @@ int dump_pkg_search(alpm_db_t *db, alpm_list_t *targets, int show_status) /* we need a newline and initial indent first */ fputs("\n ", stdout); - indentprint(alpm_pkg_get_desc(pkg), 4, cols); + indent_string(alpm_pkg_get_desc(pkg), 4, cols); } fputc('\n', stdout); } diff --git a/src/pacman/termio.c b/src/pacman/termio.c new file mode 100644 index 0000000..11d564d --- /dev/null +++ b/src/pacman/termio.c @@ -0,0 +1,204 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <wchar.h> +#include <unistd.h> +#include <sys/ioctl.h> + +/* pacman */ +#include "termio.h" + +/* gets the current screen column width */ +unsigned short getcols(int fd) +{ + const unsigned short default_tty = 80; + const unsigned short default_notty = 0; + unsigned short termwidth = 0; + + if(!isatty(fd)) { + return default_notty; + } + +#if defined(TIOCGSIZE) + struct ttysize win; + if(ioctl(fd, TIOCGSIZE, &win) == 0) { + termwidth = win.ts_cols; + } +#elif defined(TIOCGWINSZ) + struct winsize win; + if(ioctl(fd, TIOCGWINSZ, &win) == 0) { + termwidth = win.ws_col; + } +#endif + return termwidth == 0 ? default_tty : termwidth; +} + +static wchar_t *wide_string(const char *str, size_t *wclen, size_t *graphemes) +{ + wchar_t *wcstr = NULL; + size_t len = 0; + + if(str && str[0] != '\0') { + len = strlen(str) + 1; + wcstr = calloc(len, sizeof(wchar_t)); + len = mbstowcs(wcstr, str, len); + } + + if(wclen) { + *wclen = wcstr ? len : 0; + } + + if(graphemes) { + *graphemes = len ? wcswidth(wcstr, len) : 0; + } + + return wcstr; +} + +size_t grapheme_count(const char *str) +{ + wchar_t *wcstr; + size_t graphemes; + + wcstr = wide_string(str, NULL, &graphemes); + + free(wcstr); + return graphemes; +} + +static wchar_t *indentword_r(wchar_t *wcstr, unsigned short indent, + unsigned short maxcols, unsigned short *cidx) +{ + size_t len; + wchar_t *next; + + /* find the first space, set it to \0 */ + next = wcschr(wcstr, L' '); + if(next != NULL) { + *next++ = L'\0'; + } + + /* calculate the number of columns needed to print the current word */ + len = wcslen(wcstr); + len = wcswidth(wcstr, len); + + /* line is going to be too long, don't even bother trying to wrap it */ + if(len + 1 > maxcols - indent) { + if(*cidx > indent) + printf("\n%-*s", (int)indent, ""); + + printf("%ls", wcstr); + *cidx = maxcols - 1; + return next; + } + + /* if the message is long enough, wrap to a newline and re-indent */ + if(len + 1 > maxcols - *cidx) { + printf("\n%-*s", (int)indent, ""); + *cidx = indent; + } + + /* print the word */ + if(next) { + printf("%ls ", wcstr); + *cidx += len + 1; + } else { + printf("%ls" , wcstr); + *cidx += len; + } + + return next; +} + +static unsigned short indentprint_r(const char *str, unsigned short indent, + unsigned short maxcols, unsigned short cidx) +{ + wchar_t *wcstr; + size_t len; + + if(!str) { + return cidx; + } + + if(cidx < indent) { + cidx = indent; + } + + /* if we're not a tty, or our tty is not wide enough that wrapping even makes + * sense, print without indenting */ + if(maxcols == 0 || indent > maxcols) { + fputs(str, stdout); + return cidx; + } + + /* convert to a wide string */ + wcstr = wide_string(str, NULL, &len); + + /* if it turns out the string will fit, just print it */ + if(len < maxcols - cidx) { + printf("%s", str); + cidx += len; + } else { + wchar_t *buf = wcstr; + while(buf) { + buf = indentword_r(buf, indent, maxcols, &cidx); + } + } + + free(wcstr); + return cidx; +} + +static unsigned short indentpad_r(int pad, unsigned short maxcols, unsigned short cidx) +{ + /* add as many spaces as we can until we hit the right edge of the + * screen */ + if(maxcols) { + while(pad-- && cidx++ < maxcols - 1) { + putchar(' '); + } + } else { + printf("%-*s", pad, ""); + cidx += pad; + } + + return cidx; +} + +void indent_string(const char *str, unsigned short indent, unsigned short maxcols) +{ + indentprint_r(str, indent, maxcols, 0); +} + +void indent_list(const alpm_list_t *list, unsigned short indent, + unsigned short maxcols) +{ + const alpm_list_t *i; + unsigned short cidx = 0; + + for(i = list; i; i = alpm_list_next(i)) { + const char *entry = i->data; + cidx = indentprint_r(entry, indent, maxcols, cidx); + + if(i->next) { + cidx = indentpad_r(2, maxcols, cidx); + } + } +} + +void indent_list_linebreak(const alpm_list_t *list, unsigned short indent, + unsigned short maxcols) +{ + const alpm_list_t *i; + + for(i = list; i; i = alpm_list_next(i)) { + const char *entry = i->data; + indent_string(entry, indent, maxcols); + + if(i->next) { + printf("\n%-*s", (int)indent, ""); + } + } +} + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/pacman/termio.h b/src/pacman/termio.h new file mode 100644 index 0000000..6d0490a --- /dev/null +++ b/src/pacman/termio.h @@ -0,0 +1,17 @@ +#ifndef _PM_TERMIO_H +#define _PM_TERMIO_H + +#include <stddef.h> +#include <alpm_list.h> + +unsigned short getcols(int fd); + +size_t grapheme_count(const char *s); + +void indent_string(const char *str, unsigned short indent, unsigned short maxcols); +void indent_list(const alpm_list_t *list, unsigned short indent, unsigned short maxcols); +void indent_list_linebreak(const alpm_list_t *list, unsigned short indent, unsigned short maxcols); + +#endif /* _PM_TERMIO_H */ + +/* vim: set ts=2 sw=2 noet: */ diff --git a/src/pacman/util.c b/src/pacman/util.c index c2f0669..062c019 100644 --- a/src/pacman/util.c +++ b/src/pacman/util.c @@ -43,10 +43,10 @@ /* pacman */ #include "util.h" +#include "termio.h" #include "conf.h" #include "callback.h" - struct table_row_t { const char *label; int size; @@ -147,31 +147,6 @@ static int flush_term_input(int fd) return 0; } -/* gets the current screen column width */ -unsigned short getcols(int fd) -{ - const unsigned short default_tty = 80; - const unsigned short default_notty = 0; - unsigned short termwidth = 0; - - if(!isatty(fd)) { - return default_notty; - } - -#if defined(TIOCGSIZE) - struct ttysize win; - if(ioctl(fd, TIOCGSIZE, &win) == 0) { - termwidth = win.ts_cols; - } -#elif defined(TIOCGWINSZ) - struct winsize win; - if(ioctl(fd, TIOCGWINSZ, &win) == 0) { - termwidth = win.ws_col; - } -#endif - return termwidth == 0 ? default_tty : termwidth; -} - /* does the same thing as 'rm -rf' */ int rmrf(const char *path) { @@ -216,67 +191,6 @@ int rmrf(const char *path) } } -/* output a string, but wrap words properly with a specified indentation - */ -void indentprint(const char *str, unsigned short indent, unsigned short cols) -{ - wchar_t *wcstr; - const wchar_t *p; - size_t len, cidx; - - if(!str) { - return; - } - - /* if we're not a tty, or our tty is not wide enough that wrapping even makes - * sense, print without indenting */ - if(cols == 0 || indent > cols) { - fputs(str, stdout); - return; - } - - len = strlen(str) + 1; - wcstr = calloc(len, sizeof(wchar_t)); - len = mbstowcs(wcstr, str, len); - p = wcstr; - cidx = indent; - - if(!p || !len) { - return; - } - - while(*p) { - if(*p == L' ') { - const wchar_t *q, *next; - p++; - if(p == NULL || *p == L' ') continue; - next = wcschr(p, L' '); - if(next == NULL) { - next = p + wcslen(p); - } - /* len captures # cols */ - len = 0; - q = p; - while(q < next) { - len += wcwidth(*q++); - } - if((len + 1) > (cols - cidx)) { - /* wrap to a newline and reindent */ - printf("\n%-*s", (int)indent, ""); - cidx = indent; - } else { - printf(" "); - cidx++; - } - continue; - } - printf("%lc", (wint_t)*p); - cidx += wcwidth(*p); - p++; - } - free(wcstr); -} - /* Trim whitespace and newlines from a string */ size_t strtrim(char *str) @@ -406,24 +320,6 @@ alpm_list_t *strsplit(const char *str, const char splitchar) return list; } -size_t string_length(const char *s) -{ - int len; - wchar_t *wcstr; - - if(!s || s[0] == '\0') { - return 0; - } - /* len goes from # bytes -> # chars -> # cols */ - len = strlen(s) + 1; - wcstr = calloc(len, sizeof(wchar_t)); - len = mbstowcs(wcstr, s, len); - len = wcswidth(wcstr, len); - free(wcstr); - - return len; -} - static void table_print_line(const alpm_list_t *line, short col_padding, size_t colcount, size_t *widths, int *has_data) { @@ -452,7 +348,7 @@ static void table_print_line(const alpm_list_t *line, short col_padding, value = ""; } /* silly printf requires padding size to be an int */ - cell_padding = (int)widths[i] - (int)string_length(value); + cell_padding = (int)widths[i] - (int)grapheme_count(value); if(cell_padding < 0) { cell_padding = 0; } @@ -505,7 +401,7 @@ static size_t table_calc_widths(const alpm_list_t *header, } /* header determines column count and initial values of longest_strs */ for(i = header, curcol = 0; i; i = alpm_list_next(i), curcol++) { - colwidths[curcol] = string_length(i->data); + colwidths[curcol] = grapheme_count(i->data); /* note: header does not determine whether column has data */ } @@ -515,7 +411,7 @@ static size_t table_calc_widths(const alpm_list_t *header, const alpm_list_t *j = i->data; for(curcol = 0; j; j = alpm_list_next(j), curcol++) { const char *str = j->data; - size_t str_len = string_length(str); + size_t str_len = grapheme_count(str); if(str_len > colwidths[curcol]) { colwidths[curcol] = str_len; @@ -601,38 +497,14 @@ void list_display(const char *title, const alpm_list_t *list, unsigned short len = 0; if(title) { - len = (unsigned short)string_length(title) + 1; + len = (unsigned short)grapheme_count(title) + 1; printf("%s%s%s ", config->colstr.title, title, config->colstr.nocolor); } if(!list) { printf("%s\n", _("None")); } else { - const alpm_list_t *i; - const char *str = list->data; - size_t cols = len; - - fputs(str, stdout); - cols += string_length(str); - for(i = alpm_list_next(list); i; i = alpm_list_next(i)) { - str = i->data; - size_t s = string_length(str); - /* wrap only if we have enough usable column space */ - if(maxcols > len && cols + s + 2 >= maxcols) { - size_t j; - cols = len; - printf("\n"); - for(j = 1; j <= len; j++) { - printf(" "); - } - } else if(cols != len) { - /* 2 spaces are added if this is not the first element on a line. */ - printf(" "); - cols += 2; - } - fputs(str, stdout); - cols += s; - } + indent_list(list, len, maxcols); putchar('\n'); } } @@ -643,25 +515,17 @@ void list_display_linebreak(const char *title, const alpm_list_t *list, unsigned short len = 0; if(title) { - len = (unsigned short)string_length(title) + 1; + len = (unsigned short)grapheme_count(title) + 1; printf("%s%s%s ", config->colstr.title, title, config->colstr.nocolor); } if(!list) { printf("%s\n", _("None")); } else { - const alpm_list_t *i; - - /* Print the first element */ - indentprint((const char *)list->data, len, maxcols); - printf("\n"); - /* Print the rest */ - for(i = alpm_list_next(list); i; i = alpm_list_next(i)) { - printf("%-*s", (int)len, ""); - indentprint((const char *)i->data, len, maxcols); - printf("\n"); - } + indent_list_linebreak(list, len, maxcols); + putchar('\n'); } + } static alpm_list_t *create_verbose_header(void) @@ -750,7 +614,7 @@ static void display_transaction_sizes(alpm_list_t *table) for(i = table; i; i = alpm_list_next(i)) { struct table_row_t *row = i->data; - int len = string_length(row->label); + int len = grapheme_count(row->label); if(len > max_len) max_len = len; diff --git a/src/pacman/util.h b/src/pacman/util.h index 2566913..ff405c4 100644 --- a/src/pacman/util.h +++ b/src/pacman/util.h @@ -49,9 +49,7 @@ int trans_init(alpm_transflag_t flags, int check_valid); int trans_release(void); int needs_root(void); int check_syncdbs(size_t need_repos, int check_valid); -unsigned short getcols(int fd); int rmrf(const char *path); -void indentprint(const char *str, unsigned short indent, unsigned short cols); size_t strtrim(char *str); char *strreplace(const char *str, const char *needle, const char *replace); alpm_list_t *strsplit(const char *str, const char splitchar); -- 1.8.2