git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v6 04/11] column: add columnar layout

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Feb 25, 2012, 11:41 UTC
Message-ID
<1330170078-29353-5-git-send-email-pclouds@gmail.com>
In-Reply-To
<1330170078-29353-1-git-send-email-pclouds@gmail.com>

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 to be in UTF-8. If strings contain ANSI escape strings, COL_ANSI must be specified for correct length calculation.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 column.c          |  131 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 column.h          |    3 +
 t/t9002-column.sh |   86 +++++++++++++++++++++++++++++++++++
 3 files changed, 219 insertions(+), 1 deletions(-)
diff --git a/column.c b/column.c
index e62edf7..6dca3b8 100644
--- a/column.c
+++ b/column.c
@@ -2,8 +2,66 @@
 #include "column.h"
 #include "string-list.h"
 #include "parse-options.h"
+#include "utf8.h"
 
 #define MODE(mode) ((mode) & COL_MODE)
+#define XY2LINEAR(d, x, y) (MODE((d)->mode) == COL_MODE_COLUMN ? \
+			    (x) * (d)->rows + (y) : \
+			    (y) * (d)->cols + (x))
+
+struct column_data {
+	const struct string_list *list; /* list of all cells */
+	int mode;			/* COL_MODE */
+	int total_width;		/* terminal width */
+	int padding;			/* cell padding */
+	const char *indent;		/* left most column indentation */
+	const char *nl;
+
+	int rows, cols;
+	int *len;			/* cell length */
+};
+
+/* return length of 's' in letters, ANSI escapes stripped */
+static int item_length(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(struct column_data *data, int *width)
+{
+	int i;
+
+	*width = 0;
+	for (i = 0; i < data->list->nr; i++)
+		if (*width < data->len[i])
+			*width = data->len[i];
+
+	*width += data->padding;
+
+	data->cols = (data->total_width - strlen(data->indent)) / *width;
+	if (data->cols == 0)
+		data->cols = 1;
+
+	data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
+}
 
 /* Display without layout when COL_ENABLED is not set */
 static void display_plain(const struct string_list *list,
@@ -15,6 +73,65 @@ static void display_plain(const struct string_list *list,
 		printf("%s%s%s", indent, list->items[i].string, nl);
 }
 
+/* Print a cell to stdout with all necessary leading/traling space */
+static int display_cell(struct column_data *data, int initial_width,
+			const char *empty_cell, int x, int y)
+{
+	int i, len, newline;
+
+	i = XY2LINEAR(data, x, y);
+	if (i >= data->list->nr)
+		return -1;
+	len = data->len[i];
+	if (MODE(data->mode) == COL_MODE_COLUMN)
+		newline = i + data->rows >= data->list->nr;
+	else
+		newline = x == data->cols - 1 || i == data->list->nr - 1;
+
+	printf("%s%s%s",
+			x == 0 ? data->indent : "",
+			data->list->items[i].string,
+			newline ? data->nl : 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,
+			  const char *nl)
+{
+	struct column_data data;
+	int x, y, i, initial_width;
+	char *empty_cell;
+
+	memset(&data, 0, sizeof(data));
+	data.list = list;
+	data.mode = mode;
+	data.total_width = total_width;
+	data.padding = padding;
+	data.indent = indent;
+	data.nl = nl;
+
+	data.len = xmalloc(sizeof(*data.len) * list->nr);
+	for (i = 0; i < list->nr; i++)
+		data.len[i] = item_length(mode, list->items[i].string);
+
+	layout(&data, &initial_width);
+
+	empty_cell = xmalloc(initial_width + 1);
+	memset(empty_cell, ' ', initial_width);
+	empty_cell[initial_width] = '\0';
+	for (y = 0; y < data.rows; y++) {
+		for (x = 0; x < data.cols; x++)
+			if (display_cell(&data, initial_width, empty_cell, x, y))
+				break;
+	}
+
+	free(data.len);
+	free(empty_cell);
+}
+
 void print_columns(const struct string_list *list, unsigned int mode,
 		   struct column_options *opts)
 {
@@ -36,7 +153,16 @@ void print_columns(const struct string_list *list, unsigned int mode,
 		display_plain(list, indent, nl);
 		return;
 	}
-	die("BUG: invalid mode %d", MODE(mode));
+
+	switch (MODE(mode)) {
+	case COL_MODE_ROW:
+	case COL_MODE_COLUMN:
+		display_table(list, mode, width, padding, indent, nl);
+		break;
+
+	default:
+		die("BUG: invalid mode %d", MODE(mode));
+	}
 }
 
 struct colopt {
@@ -98,6 +224,9 @@ static int parse_option(const char *arg, int len,
 		{ ENABLE, "always",  1 },
 		{ ENABLE, "never",   0 },
 		{ ENABLE, "auto",   -1 },
+		{ MODE,   "column", COL_MODE_COLUMN },
+		{ MODE,   "row",    COL_MODE_ROW },
+		{ OPTION, "color",  COL_ANSI },
 	};
 	int i;
 
diff --git a/column.h b/column.h
index 67b1c4f..d9d27c6 100644
--- a/column.h
+++ b/column.h
@@ -2,8 +2,11 @@
 #define COLUMN_H
 
 #define COL_MODE          0x000F
+#define COL_MODE_COLUMN        0   /* Fill columns before rows */
+#define COL_MODE_ROW           1   /* Fill rows before columns */
 #define COL_ENABLED      (1 << 4)
 #define COL_ENABLED_SET  (1 << 5)  /* Has COL_ENABLED been set by config? */
+#define COL_ANSI         (1 << 6)  /* Remove ANSI escapes from string length */
 #define COL_PARSEOPT     (1 << 8)  /* --column is given */
 
 #define explicitly_enable_column(c) \
diff --git a/t/t9002-column.sh b/t/t9002-column.sh
index b0b6d62..cffb029 100755
--- a/t/t9002-column.sh
+++ b/t/t9002-column.sh
@@ -24,4 +24,90 @@ test_expect_success 'never' '
 	test_cmp lista actual
 '
 
+test_expect_success '80 columns' '
+	cat >expected <<\EOF &&
+one    two    three  four   five   six    seven  eight  nine   ten    eleven
+EOF
+	COLUMNS=80 git column --mode=column <lista >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'COLUMNS = 1' '
+	cat >expected <<\EOF &&
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+	COLUMNS=1 git column --mode=column <lista >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'width = 1' '
+	git column --mode=column --width=1 <lista >actual &&
+	test_cmp expected actual
+'
+
+COLUMNS=20
+export COLUMNS
+
+test_expect_success '20 columns' '
+	cat >expected <<\EOF &&
+one    seven
+two    eight
+three  nine
+four   ten
+five   eleven
+six
+EOF
+	git column --mode=column <lista >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '20 columns, padding 2' '
+	cat >expected <<\EOF &&
+one     seven
+two     eight
+three   nine
+four    ten
+five    eleven
+six
+EOF
+	git column --mode=column --padding 2 <lista >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '20 columns, indented' '
+	cat >expected <<\EOF &&
+  one    seven
+  two    eight
+  three  nine
+  four   ten
+  five   eleven
+  six
+EOF
+	git column --mode=column --indent="  " <lista >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first' '
+	cat >expected <<\EOF &&
+one    two
+three  four
+five   six
+seven  eight
+nine   ten
+eleven
+EOF
+	git column --mode=row <lista >actual &&
+	test_cmp expected actual
+'
+
 test_done
-- 
1.7.8.36.g69ee2
Previous: Nguyễn Thái Ngọc DuyNext: Nguyễn Thái Ngọc Duy
Message 7 of 66 in “Column display”
  1. 00/11 Column displayNguyễn Thái Ngọc Duy, Feb 25, 2012
  2. 01/11 column: add API to print items in columnsNguyễn Thái Ngọc Duy, Feb 25, 2012
  3. 02/11 Add git-column and column mode parsingNguyễn Thái Ngọc Duy, Feb 25, 2012
  4. Ramsay JonesFeb 27, 2012
  5. Nguyen Thai Ngoc DuyFeb 28, 2012
  6. 03/11 Stop starting pager recursivelyNguyễn Thái Ngọc Duy, Feb 25, 2012
  7. 04/11 column: add columnar layoutNguyễn Thái Ngọc Duy, Feb 25, 2012
  8. 05/11 column: support columns with different widthsNguyễn Thái Ngọc Duy, Feb 25, 2012
  9. Junio C HamanoFeb 26, 2012
  10. 06/11 column: add column.ui for default column output settingsNguyễn Thái Ngọc Duy, Feb 25, 2012
  11. Junio C HamanoFeb 27, 2012
  12. Nguyen Thai Ngoc DuyFeb 27, 2012
  13. 07/11 help: reuse print_columns() for help -aNguyễn Thái Ngọc Duy, Feb 25, 2012
  14. 08/11 branch: add --columnNguyễn Thái Ngọc Duy, Feb 25, 2012
  15. 09/11 status: add --columnNguyễn Thái Ngọc Duy, Feb 25, 2012
  16. 10/11 column: support piping stdout to external git-column processNguyễn Thái Ngọc Duy, Feb 25, 2012
  17. 11/11 tag: add --columnNguyễn Thái Ngọc Duy, Feb 25, 2012
  18. Junio C HamanoFeb 26, 2012
  19. Nguyen Thai Ngoc DuyFeb 27, 2012
  20. Junio C HamanoFeb 27, 2012
  21. Junio C HamanoFeb 27, 2012
  22. Nguyen Thai Ngoc DuyFeb 27, 2012
  23. 00/10 Column displayNguyễn Thái Ngọc Duy, Feb 28, 2012
  24. 01/10 Add git-column for columnar displayNguyễn Thái Ngọc Duy, Feb 28, 2012
  25. Junio C HamanoFeb 28, 2012
  26. Nguyen Thai Ngoc DuyMar 2, 2012
  27. 02/10 Stop starting pager recursivelyNguyễn Thái Ngọc Duy, Feb 28, 2012
  28. Junio C HamanoFeb 28, 2012
  29. Junio C HamanoFeb 28, 2012
  30. Nguyen Thai Ngoc DuyFeb 29, 2012
  31. Junio C HamanoFeb 29, 2012
  32. Nguyen Thai Ngoc DuyFeb 29, 2012
  33. Junio C HamanoFeb 29, 2012
  34. 03/10 column: add columnar layoutNguyễn Thái Ngọc Duy, Feb 28, 2012
  35. Junio C HamanoFeb 28, 2012
  36. 04/10 column: add dense layout supportNguyễn Thái Ngọc Duy, Feb 28, 2012
  37. Junio C HamanoFeb 28, 2012
  38. Nguyen Thai Ngoc DuyMar 2, 2012
  39. Junio C HamanoMar 2, 2012
  40. 05/10 column: add column.ui for default column output settingsNguyễn Thái Ngọc Duy, Feb 28, 2012
  41. Junio C HamanoFeb 28, 2012
  42. 06/10 help: reuse print_columns() for help -aNguyễn Thái Ngọc Duy, Feb 28, 2012
  43. 07/10 branch: add --columnNguyễn Thái Ngọc Duy, Feb 28, 2012
  44. 08/10 status: add --columnNguyễn Thái Ngọc Duy, Feb 28, 2012
  45. 09/10 column: support piping stdout to external git-column processNguyễn Thái Ngọc Duy, Feb 28, 2012
  46. 10/10 tag: add --columnNguyễn Thái Ngọc Duy, Feb 28, 2012
  47. Thomas RastMar 2, 2012
  48. Nguyen Thai Ngoc DuyMar 11, 2012
  49. Junio C HamanoMar 12, 2012
  50. 00/9 Column displayNguyễn Thái Ngọc Duy, Mar 13, 2012
  51. Nguyen Thai Ngoc DuyMar 13, 2012
  52. 01/9 Add column layout skeleton and git-columnNguyễn Thái Ngọc Duy, Mar 13, 2012
  53. 02/9 Stop starting pager recursivelyNguyễn Thái Ngọc Duy, Mar 13, 2012
  54. 03/9 column: add columnar layoutNguyễn Thái Ngọc Duy, Mar 13, 2012
  55. 04/9 column: add dense layout supportNguyễn Thái Ngọc Duy, Mar 13, 2012
  56. 05/9 help: reuse print_columns() for help -aNguyễn Thái Ngọc Duy, Mar 13, 2012
  57. 06/9 branch: add --columnNguyễn Thái Ngọc Duy, Mar 13, 2012
  58. 07/9 status: add --columnNguyễn Thái Ngọc Duy, Mar 13, 2012
  59. 08/9 column: support piping stdout to external git-column processNguyễn Thái Ngọc Duy, Mar 13, 2012
  60. 09/9 tag: add --columnNguyễn Thái Ngọc Duy, Mar 13, 2012
  61. 10/9 ls-files: support --columnNguyễn Thái Ngọc Duy, Mar 13, 2012
  62. 11/9 column: support "denser" modeNguyễn Thái Ngọc Duy, Mar 13, 2012
  63. 12/9 column: support grouping entriesNguyễn Thái Ngọc Duy, Mar 13, 2012
  64. Junio C HamanoMar 13, 2012
  65. Nguyen Thai Ngoc DuyMar 14, 2012
  66. Junio C HamanoMar 14, 2012

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.