From: "Nguyễn Thái Ngọc Duy" <pclouds@gmail.com>
To: git@vger.kernel.org
Cc: "Nguyễn Thái Ngọc Duy" <pclouds@gmail.com>
Subject: [PATCH v7 12/9] column: support grouping entries
Date: Tue, 13 Mar 2012 19:11:36 +0700 [thread overview]
Message-ID: <c3c9422bcfccad34e7b97c39a267207cfb41c0b7.1331638664.git.pclouds@gmail.com> (raw)
In-Reply-To: <25ba0cdc7d4898e65af12b584c0e9ebf4e412557.1331638664.git.pclouds@gmail.com>
If many entries share the same prefix (e.g. "git ls-files Documentation/"),
cutting out the prefix helps put more information on the same space.
If "group" is specified, the list of entries will be searched for
largest non-overlapping rectangles of text. Estimation is done on each
rectangle to see if there are any savings in rows if we group them.
Groups are printed first, then the remaining as the last group.
Handling the remaining part this way may not be ideal but I don't want
to split all directories like "ls -R". That takes too many lines.
Maybe I should prepend ".../" to all grouped items to make it clear
they are grouped. There's also problem with ansi escape codes that
I'll need to handle if this sounds like a good way to go.
This code may be used for diffstat too (e.g. when most of the diff is
in Documentation/).
For demonstration, this is what
"COLUMNS=80 git ls-files --column=group -- '*.[ch]'" looks like
builtin/:
add.c gc.c read-tree.c
annotate.c grep.c receive-pack.c
<snip>
for-each-ref.c prune.c verify-tag.c
fsck.c push.c write-tree.c
compat/:
basename.c regex/regexec.c
bswap.h setenv.c
<snip>
regex/regex_internal.c win32mmap.c
regex/regex_internal.h winansi.c
contrib/:
convert-objects/convert-objects.c
credential/osxkeychain/git-credential-osxkeychain.c
examples/builtin-fetch--tool.c
svn-fe/svn-fe.c
vcs-svn/:
fast_export.c line_buffer.h sliding_window.c svndiff.h
fast_export.h repo_tree.c sliding_window.h svndump.c
line_buffer.c repo_tree.h svndiff.c svndump.h
xdiff/:
xdiff.h xemit.c xinclude.h xpatience.c xtypes.h
xdiffi.c xemit.h xmacros.h xprepare.c xutils.c
xdiffi.h xhistogram.c xmerge.c xprepare.h xutils.h
...:
abspath.c pack-check.c
advice.c pack-refs.c
<snip>
object.c xdiff-interface.h
object.h zlib.c
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
column.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
column.h | 1 +
2 files changed, 227 insertions(+), 3 deletions(-)
diff --git a/column.c b/column.c
index 80eefa0..8214a9b 100644
--- a/column.c
+++ b/column.c
@@ -19,6 +19,11 @@ struct column_data {
int *width; /* index to the longest row in column */
};
+struct area {
+ int start, end; /* in string_list */
+ int size;
+};
+
/* return length of 's' in letters, ANSI escapes stripped */
static int item_length(unsigned int colopts, const char *s)
{
@@ -223,9 +228,9 @@ static int display_table(const struct string_list *list,
i = break_long_line(&data);
if (i != -1) {
printf("%s%s" "%s%s%s" "%s%s",
- indent, nl,
- indent, list->items[i].string, nl,
- indent, nl);
+ opts->indent, opts->nl,
+ opts->indent, list->items[i].string, opts->nl,
+ opts->indent, opts->nl);
free(data.len);
free(data.width);
return i + 1;
@@ -248,6 +253,218 @@ static int display_table(const struct string_list *list,
return list->nr;
}
+/*
+ * Find out the contiguous list of entries sharing the same directory
+ * prefix that nr * (prefix_len - skip) is largest, where nr is the
+ * number of entries and prefix_len is the shared directory prefix's
+ * length.
+ */
+static int largest_block(const struct string_list *list, int start, int skip, int *len)
+{
+ const char *str = list->items[start].string;
+ const char *slash;
+ int largest_area = 0;
+
+ for (slash = str + strlen(str) - 1; slash > str + skip; slash--) {
+ int i, area;
+ if (*slash != '/')
+ continue;
+ for (i = start; i < list->nr; i++) {
+ const char *s = list->items[i].string;
+ if (strlen(s) < slash + 1 - str ||
+ memcmp(str + skip, s + skip, slash + 1 - (str + skip)))
+ break;
+ }
+ area = (i - start) * (slash + 1 - str - skip);
+ if (area > largest_area) {
+ largest_area = area;
+ *len = i - start;
+ }
+ }
+ return largest_area;
+}
+
+static int area_size_cmp(const void *a, const void *b)
+{
+ const struct area *area1 = a;
+ const struct area *area2 = b;
+ return area2->size - area1->size;
+}
+
+/*
+ * Make a sorted list of non-overlapping blocks, largest ones first
+ */
+static struct area *find_large_blocks(const struct string_list *list, int *nr_p)
+{
+ int i, nr = 0, alloc = 16;
+ struct area *areas = xmalloc(sizeof(*areas) * alloc);
+ struct area last;
+ memset(&last, 0, sizeof(last));
+
+ for (i = 0; i < list->nr; i++) {
+ int len, size = largest_block(list, i, 0, &len);
+ if (!size || len == 1)
+ continue;
+ /* the new found area is overlapped with the old one,
+ but smaller, skip it */
+ if (i < last.end) {
+ if (size < last.size)
+ continue;
+ last.start = i;
+ last.end = i + len;
+ last.size = size;
+ continue;
+ }
+ if (last.size) {
+ if (nr + 1 < alloc)
+ ALLOC_GROW(areas, nr + 1, alloc);
+ areas[nr++] = last;
+ }
+ last.start = i;
+ last.end = i + len;
+ last.size = size;
+ }
+ if (last.size) {
+ if (nr + 1 >= alloc)
+ ALLOC_GROW(areas, nr + 1, alloc);
+ areas[nr++] = last;
+ }
+ qsort(areas, nr, sizeof(*areas), area_size_cmp);
+ *nr_p = nr;
+ return areas;
+}
+
+static int area_start_cmp(const void *a, const void *b)
+{
+ const struct area *area1 = a;
+ const struct area *area2 = b;
+ return area1->start - area2->start;
+}
+
+/*
+ * Assume list is split into two tables: one from "start" to "stop",
+ * where all strings are truncated "skip" bytes, the other the rest of
+ * the strings. Calculate how many rows required if all cells of each
+ * table are of the same width.
+ */
+static int split_layout_gain(const struct string_list *list, int *lengths,
+ const struct column_options *opts,
+ int start, int stop, int skip)
+{
+ int i, width0, width1, width2, cols, rows0, rows1;
+ int indent = strlen(opts->indent);
+
+ width0 = width1 = width2 = 0;
+ for (i = 0; i < list->nr; i++) {
+ int len = lengths[i];
+ if (width0 < len)
+ width0 = len;
+ if (i >= start && i < stop) {
+ len -= skip;
+ if (width2 < len)
+ width2 = len;
+ } else {
+ if (width1 < len)
+ width1 = len;
+ }
+ }
+
+ width0 += opts->padding;
+ cols = (opts->width - indent) / width0;
+ if (cols == 0)
+ cols = 1;
+ rows0 = DIV_ROUND_UP(list->nr, cols);
+
+ width1 += opts->padding;
+ cols = (opts->width - indent) / width1;
+ if (cols == 0)
+ cols = 1;
+ rows1 = DIV_ROUND_UP(list->nr - (stop - start), cols);
+
+ width2 += opts->padding;
+ cols = (opts->width - indent) / width2;
+ if (cols == 0)
+ cols = 1;
+ rows1 += DIV_ROUND_UP(stop - start, cols);
+ return rows0 - rows1;
+}
+
+static void group_by_prefix(const struct string_list *list, unsigned int colopts,
+ const struct column_options *opts)
+{
+ int i, nr;
+ struct area *areas = find_large_blocks(list, &nr);
+ struct string_list new_list = STRING_LIST_INIT_NODUP;
+ struct area *dst;
+ int *len;
+
+ assert(colopts & COL_GROUP);
+ /* avoid inifinite loop when calling print_columns again */
+ colopts &= ~COL_GROUP;
+
+ len = xmalloc(sizeof(*len) * list->nr);
+ for (i = 0; i < list->nr; i++)
+ len[i] = item_length(colopts, list->items[i].string);
+
+ /*
+ * Calculate and see if there is any saving when print this as
+ * a group. Base our calculation on non-dense mode even if
+ * users want dense output because the calculation would be
+ * less expensive.
+ */
+ dst = areas;
+ for (i = 0; i < nr; i++) {
+ struct area *area = areas + i;
+ int rows, skip = area->size / (area->end - area->start);
+ rows = split_layout_gain(list, len, opts,
+ area->start, area->end, skip);
+
+ if (rows > 3) {
+ if (areas + i != dst)
+ *dst = *area;
+ dst++;
+ }
+ }
+ free(len);
+
+ nr = dst - areas;
+ if (!nr) {
+ print_columns(list, colopts, opts);
+ return;
+ }
+ qsort(areas, nr, sizeof(*areas), area_start_cmp);
+
+ /*
+ * We now have list of worthy groups, sorted by offset. Print
+ * group by group, then the rest.
+ */
+ for (i = 0; i < nr; i++) {
+ struct area *area = areas + i;
+ int j, skip = area->size / (area->end - area->start);
+
+ for (j = area->start; j < area->end; j++)
+ string_list_append(&new_list,
+ list->items[j].string + skip);
+ printf("\n%.*s:\n", skip, list->items[area->start].string);
+ print_columns(&new_list, colopts, opts);
+ string_list_clear(&new_list, 0);
+ }
+
+ printf("\n%s:\n", "...");
+ for (i = 0; i < nr; i++) {
+ struct area *area = areas + i;
+ int j;
+ for (j = i ? area[-1].end : 0; j < area->start; j++)
+ string_list_append(&new_list, list->items[j].string);
+ }
+ for (i = areas[nr-1].end; i < list->nr; i++)
+ string_list_append(&new_list, list->items[i].string);
+ print_columns(&new_list, colopts, opts);
+ string_list_clear(&new_list, 0);
+
+ free(areas);
+}
+
void print_columns(const struct string_list *list, unsigned int colopts,
const struct column_options *opts)
{
@@ -264,6 +481,11 @@ void print_columns(const struct string_list *list, unsigned int colopts,
nopts.nl = opts && opts->nl ? opts->nl : "\n";
nopts.padding = opts ? opts->padding : 1;
nopts.width = opts && opts->width ? opts->width : term_columns() - 1;
+
+ if (colopts & COL_GROUP) {
+ group_by_prefix(list, colopts, &nopts);
+ return;
+ }
if (!COL_ENABLE(colopts)) {
display_plain(list, "", "\n");
return;
@@ -318,6 +540,7 @@ static int parse_option(const char *arg, int len, unsigned int *colopts,
{ "row", COL_ROW, COL_LAYOUT_MASK },
{ "dense", COL_DENSE, 0 },
{ "denser", COL_DENSER, 0 },
+ { "group", COL_GROUP, 0 },
};
int i;
diff --git a/column.h b/column.h
index dbc5da2..f3934cf 100644
--- a/column.h
+++ b/column.h
@@ -7,6 +7,7 @@
#define COL_DENSE 0x0080 /* Shrink columns when possible,
making space for more columns */
#define COL_DENSER 0x0100
+#define COL_GROUP 0x0200
#define COL_ENABLE(c) ((c) & COL_ENABLE_MASK)
#define COL_DISABLED 0x0000 /* must be zero */
--
1.7.8.36.g69ee2
next prev parent reply other threads:[~2012-03-13 12:09 UTC|newest]
Thread overview: 66+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-02-25 11:41 [PATCH v6 00/11] Column display Nguyễn Thái Ngọc Duy
2012-02-25 11:41 ` [PATCH v6 01/11] column: add API to print items in columns Nguyễn Thái Ngọc Duy
2012-02-25 11:41 ` [PATCH v6 02/11] Add git-column and column mode parsing Nguyễn Thái Ngọc Duy
2012-02-27 20:09 ` Ramsay Jones
2012-02-28 11:00 ` Nguyen Thai Ngoc Duy
2012-02-25 11:41 ` [PATCH v6 03/11] Stop starting pager recursively Nguyễn Thái Ngọc Duy
2012-02-25 11:41 ` [PATCH v6 04/11] column: add columnar layout Nguyễn Thái Ngọc Duy
2012-02-25 11:41 ` [PATCH v6 05/11] column: support columns with different widths Nguyễn Thái Ngọc Duy
2012-02-26 23:12 ` Junio C Hamano
2012-02-25 11:41 ` [PATCH v6 06/11] column: add column.ui for default column output settings Nguyễn Thái Ngọc Duy
2012-02-27 6:20 ` Junio C Hamano
2012-02-27 7:04 ` Nguyen Thai Ngoc Duy
2012-02-25 11:41 ` [PATCH v6 07/11] help: reuse print_columns() for help -a Nguyễn Thái Ngọc Duy
2012-02-25 11:41 ` [PATCH v6 08/11] branch: add --column Nguyễn Thái Ngọc Duy
2012-02-25 11:41 ` [PATCH v6 09/11] status: " Nguyễn Thái Ngọc Duy
2012-02-25 11:41 ` [PATCH v6 10/11] column: support piping stdout to external git-column process Nguyễn Thái Ngọc Duy
2012-02-25 11:41 ` [PATCH v6 11/11] tag: add --column Nguyễn Thái Ngọc Duy
2012-02-26 23:02 ` [PATCH v6 00/11] Column display Junio C Hamano
2012-02-27 0:40 ` Nguyen Thai Ngoc Duy
2012-02-27 1:37 ` Junio C Hamano
2012-02-27 7:46 ` Junio C Hamano
2012-02-27 8:14 ` Nguyen Thai Ngoc Duy
2012-02-28 11:58 ` [PATCH v7 00/10] " Nguyễn Thái Ngọc Duy
2012-02-28 11:58 ` [PATCH v7 01/10] Add git-column for columnar display Nguyễn Thái Ngọc Duy
2012-02-28 18:10 ` Junio C Hamano
2012-03-02 12:36 ` Nguyen Thai Ngoc Duy
2012-02-28 11:58 ` [PATCH v7 02/10] Stop starting pager recursively Nguyễn Thái Ngọc Duy
2012-02-28 18:13 ` Junio C Hamano
2012-02-28 19:10 ` Junio C Hamano
2012-02-29 1:54 ` Nguyen Thai Ngoc Duy
2012-02-29 3:37 ` Junio C Hamano
2012-02-29 3:40 ` Nguyen Thai Ngoc Duy
2012-02-29 4:51 ` Junio C Hamano
2012-02-28 11:58 ` [PATCH v7 03/10] column: add columnar layout Nguyễn Thái Ngọc Duy
2012-02-28 18:22 ` Junio C Hamano
2012-02-28 11:58 ` [PATCH v7 04/10] column: add dense layout support Nguyễn Thái Ngọc Duy
2012-02-28 18:27 ` Junio C Hamano
2012-03-02 12:47 ` Nguyen Thai Ngoc Duy
2012-03-02 17:37 ` Junio C Hamano
2012-02-28 11:58 ` [PATCH v7 05/10] column: add column.ui for default column output settings Nguyễn Thái Ngọc Duy
2012-02-28 18:44 ` Junio C Hamano
2012-02-28 11:58 ` [PATCH v7 06/10] help: reuse print_columns() for help -a Nguyễn Thái Ngọc Duy
2012-02-28 11:58 ` [PATCH v7 07/10] branch: add --column Nguyễn Thái Ngọc Duy
2012-02-28 11:58 ` [PATCH v7 08/10] status: " Nguyễn Thái Ngọc Duy
2012-02-28 11:58 ` [PATCH v7 09/10] column: support piping stdout to external git-column process Nguyễn Thái Ngọc Duy
2012-02-28 11:58 ` [PATCH v7 10/10] tag: add --column Nguyễn Thái Ngọc Duy
2012-03-02 11:25 ` [PATCH v7 00/10] Column display Thomas Rast
2012-03-11 7:02 ` Nguyen Thai Ngoc Duy
2012-03-12 6:02 ` Junio C Hamano
2012-03-13 12:09 ` [PATCH v7 00/9] " Nguyễn Thái Ngọc Duy
2012-03-13 12:08 ` Nguyen Thai Ngoc Duy
2012-03-13 12:09 ` [PATCH v7 01/9] Add column layout skeleton and git-column Nguyễn Thái Ngọc Duy
2012-03-13 12:09 ` [PATCH v7 02/9] Stop starting pager recursively Nguyễn Thái Ngọc Duy
2012-03-13 12:09 ` [PATCH v7 03/9] column: add columnar layout Nguyễn Thái Ngọc Duy
2012-03-13 12:09 ` [PATCH v7 04/9] column: add dense layout support Nguyễn Thái Ngọc Duy
2012-03-13 12:09 ` [PATCH v7 05/9] help: reuse print_columns() for help -a Nguyễn Thái Ngọc Duy
2012-03-13 12:09 ` [PATCH v7 06/9] branch: add --column Nguyễn Thái Ngọc Duy
2012-03-13 12:09 ` [PATCH v7 07/9] status: " Nguyễn Thái Ngọc Duy
2012-03-13 12:09 ` [PATCH v7 08/9] column: support piping stdout to external git-column process Nguyễn Thái Ngọc Duy
2012-03-13 12:09 ` [PATCH v7 09/9] tag: add --column Nguyễn Thái Ngọc Duy
2012-03-13 12:11 ` [PATCH v7 10/9] ls-files: support --column Nguyễn Thái Ngọc Duy
2012-03-13 12:11 ` [PATCH v7 11/9] column: support "denser" mode Nguyễn Thái Ngọc Duy
2012-03-13 12:11 ` Nguyễn Thái Ngọc Duy [this message]
2012-03-13 22:24 ` [PATCH v7 01/9] Add column layout skeleton and git-column Junio C Hamano
2012-03-14 11:17 ` Nguyen Thai Ngoc Duy
2012-03-14 18:29 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=c3c9422bcfccad34e7b97c39a267207cfb41c0b7.1331638664.git.pclouds@gmail.com \
--to=pclouds@gmail.com \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).