Subject: [PATCH 03/16] display_columns: add COL_MODE_{COLUMN,ROW} mode

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Wed, 9 Feb 2011 19:24:31 +0700

COL_MODE_COLUMN and COL_MODE_ROW fill column by column (or row by row
respectively), given the terminal width and how many space between
columns.

Strings are supposed in UTF-8. If strings contain ANSI escape strings,
COL_ANSI must be specified.

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 column.c |  107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 column.h |    3 ++
 2 files changed, 110 insertions(+), 0 deletions(-)

diff --git a/column.c b/column.c
index c7d9a84..d6e2d70 100644
--- a/column.c
+++ b/column.c
@@ -1,8 +1,58 @@
 #include "cache.h"
 #include "column.h"
+#include "parse-options.h"
 #include "string-list.h"
+#include "utf8.h"
 
 #define MODE(mode) ((mode) & COL_MODE)
+#define XY2SCALAR(mode,x,y,cols,rows) (MODE(mode) == COL_MODE_COLUMN ? (x) * (rows) + (y) : (y) * (cols) + (x))
+
+/* return length of 's' in letters, ANSI escapes stripped */
+static int item_length(const struct string_list *list, int mode, const char *s)
+{
+	int len, i = 0;
+	struct strbuf str = STRBUF_INIT;
+
+	if (!(mode & COL_ANSI))
+		return utf8_strwidth(s);
+
+	strbuf_addstr(&str, s);
+	while ((s = strstr(str.buf + i, "\033[")) != NULL) {
+		int len = strspn(s + 2, "0123456789;");
+		i = s - str.buf;
+		strbuf_remove(&str, i, len + 3); /* \033[<len><func char> */
+	}
+	len = utf8_strwidth(str.buf);
+	strbuf_release(&str);
+	return len;
+}
+
+/*
+ * Calculate cell width, rows and cols for a table of equal cells, given
+ * table width and how many spaces between cells.
+ */
+static void layout(const struct string_list *list,
+		   int mode, int total_width, int padding,
+		   int *width, int *rows, int *cols)
+{
+	int i;
+
+	*width = 0;
+	/* Find maximum column width */
+	for (i = 0; i < list->nr; i++) {
+		const char *s = list->items[i].string;
+		int len = item_length(list, mode, s);
+		if (*width < len)
+			*width = len;
+	}
+	*width += padding;
+
+	*cols = total_width / *width;
+	if (*cols == 0)
+		*cols = 1;
+
+	*rows = DIV_ROUND_UP(list->nr, *cols);
+}
 
 /* Display without layout, COL_MODE_PLAIN */
 static void display_plain(const struct string_list *list, const char *indent)
@@ -13,6 +63,58 @@ static void display_plain(const struct string_list *list, const char *indent)
 		printf("%s%s\n", indent, list->items[i].string);
 }
 
+static int display_cell(const struct string_list *list, int mode,
+			int *width, int initial_width,
+			const char *indent, const char *empty_cell,
+			int x, int y, int cols, int rows)
+{
+	const char *s;
+	int i, len, newline;
+
+	i = XY2SCALAR(mode, x, y, cols, rows);
+	if (i >= list->nr)
+		return -1;
+	s = list->items[i].string;
+	len = item_length(list, mode, s);
+	if (width[x] < initial_width)
+		len += initial_width - width[x];
+	if (MODE(mode) == COL_MODE_COLUMN)
+		newline = i + rows >= list->nr;
+	else
+		newline = x == cols - 1 || i == list->nr - 1;
+
+	printf("%s%s%s",
+			x == 0 ? indent : "",
+			list->items[i].string,
+			newline ? "\n" : empty_cell + len);
+	return 0;
+}
+
+/* Display COL_MODE_COLUMN or COL_MODE_ROW */
+static void display_table(const struct string_list *list,
+			  int mode, int total_width,
+			  int padding, const char *indent)
+{
+	int x, y, i, cols, rows, initial_width, *width;
+	char *empty_cell;
+
+	layout(list, mode, total_width, padding, &initial_width, &rows, &cols);
+	width = xmalloc(sizeof(*width) * cols);
+	for (i = 0; i < cols; i++)
+		width[i] = initial_width;
+
+	empty_cell = xmalloc(initial_width + 1);
+	memset(empty_cell, ' ', initial_width);
+	empty_cell[initial_width] = '\0';
+	for (y = 0; y < rows; y++) {
+		for (x = 0; x < cols; x++)
+			if (display_cell(list, mode, width,
+					 initial_width, indent, empty_cell,
+					 x, y, cols, rows))
+				break;
+	}
+}
+
 void display_columns(const struct string_list *list, int mode, int width, int padding, const char *indent)
 {
 	int real_mode = MODE(mode);
@@ -25,6 +127,11 @@ void display_columns(const struct string_list *list, int mode, int width, int pa
 		display_plain(list, indent);
 		break;
 
+	case COL_MODE_ROW:
+	case COL_MODE_COLUMN:
+		display_table(list, mode, width, padding, indent);
+		break;
+
 	default:
 		die("BUG: invalid mode %d", MODE(mode));
 	}
diff --git a/column.h b/column.h
index da0bf9a..3ed5731 100644
--- a/column.h
+++ b/column.h
@@ -3,6 +3,9 @@
 
 #define COL_MODE          0x000F
 #define COL_MODE_PLAIN         0   /* Single column */
+#define COL_MODE_COLUMN        1   /* Fill columns before rows */
+#define COL_MODE_ROW           2   /* Fill rows before columns */
+#define COL_ANSI         (1 << 4) /* Remove ANSI escapes from string length */
 
 extern int term_columns(void);
 extern void display_columns(const struct string_list *list, int mode, int width, int padding, const char *indent);
-- 
1.7.2.2