Author: rinrab
Date: Sat Apr 11 19:04:23 2026
New Revision: 1932985
Log:
svnbrowse: Improve application style, adding colours and better composition of
list items and the header infobar.
- Fixed layout of list elements; we can now pad them properly and show
selection for the whole row.
- The list items now highlight directories with cyan.
- The directories now display '(dir)' instead of size.
- Show branding with title 'Apache Subversion' in the info bar.
* subversion/svnbrowse/pallete.py: Add simple script to test current palette of
the terminal; for development use only.
* subversion/svnbrowse/svnbrowse.c
(COLOR_PRIMARY, COLOR_SECONDARY, COLOR_BRANDING): New macros; branding means
a customly picked colour that represents the tool's overall look/adds some
originality. It's just something that's shown in the header, that's why.
(COLOR_PAIR_INFO_BAR, COLOR_PAIR_DIR, COLOR_PAIR_DIR_SELECTED,
COLOR_PAIR_FILE, COLOR_PAIR_FILE_SELECTED, COLOR_COUNT): New constants,
defined through a enum.
(rightpad, leftpad): New functions for string manipulation.
(format_node_size, format_node_name, get_item_style): Factor-out those
functions to simplify item rendering.
(view_draw_item): Rewrite function to use new padding system, utilise new
function for formatting texts, and paint the list.
(view_draw_info_bar): Factor-out function for displaying the info bar.
(view_draw): Call view_draw_info_bar() and update usage of view_draw_item().
(sub_main): Initialize colour pairs.
Added:
subversion/trunk/subversion/svnbrowse/palette.py (contents, props changed)
Modified:
subversion/trunk/subversion/svnbrowse/svnbrowse.c
Added: subversion/trunk/subversion/svnbrowse/palette.py
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ subversion/trunk/subversion/svnbrowse/palette.py Sat Apr 11 19:04:23
2026 (r1932985)
@@ -0,0 +1,50 @@
+#!/bin/python
+
+import curses
+
+def main(stdscr):
+ curses.start_color()
+ curses.use_default_colors()
+
+ WIDTH = 32
+
+ for i in range(0, curses.COLORS):
+ curses.init_pair(i + 1, i, -1)
+ for i in range(0, 255):
+ if (i % WIDTH == 0):
+ stdscr.addstr(i // WIDTH, 0, f"{i} - {i + WIDTH - 1}", 0)
+
+ stdscr.addstr(i // WIDTH, i % WIDTH + 12, "x", curses.color_pair(i))
+
+ stdscr.getch()
+ stdscr.clear()
+
+ for i in range(0, curses.COLORS):
+ curses.init_pair(i + 1, -1, i)
+ for i in range(0, 255):
+ if (i % WIDTH == 0):
+ stdscr.addstr(i // WIDTH, 0, f"{i} - {i + WIDTH - 1}", 0)
+
+ stdscr.addstr(i // WIDTH, i % WIDTH + 12, "x", curses.color_pair(i))
+
+ stdscr.getch()
+ stdscr.clear()
+
+ for i in range(0, 16):
+ curses.init_pair(i + 1, i, -1)
+ for i in range(0, 16):
+ curses.init_pair(i + 16 + 1, -1, i)
+
+ for i in range(0, 16):
+ stdscr.addstr(
+ f"The color number {i} of the ",
+ curses.color_pair(i)
+ )
+ stdscr.addstr(
+ f"standard terminal pallete.\n",
+ curses.color_pair(i + 16)
+ )
+
+ stdscr.getch()
+
+curses.wrapper(main)
Modified: subversion/trunk/subversion/svnbrowse/svnbrowse.c
==============================================================================
--- subversion/trunk/subversion/svnbrowse/svnbrowse.c Sat Apr 11 12:45:04
2026 (r1932984)
+++ subversion/trunk/subversion/svnbrowse/svnbrowse.c Sat Apr 11 19:04:23
2026 (r1932985)
@@ -110,6 +110,20 @@ const apr_getopt_option_t svn_browse__op
#define KEY_ESC 27
+/* handpicked */
+#define COLOR_PRIMARY 238
+#define COLOR_SECONDARY 240
+#define COLOR_BRANDING 96
+
+enum {
+ COLOR_PAIR_INFO_BAR = 1,
+ COLOR_PAIR_DIR,
+ COLOR_PAIR_DIR_SELECTED,
+ COLOR_PAIR_FILE,
+ COLOR_PAIR_FILE_SELECTED,
+ COLOR_COUNT,
+};
+
typedef struct svn_browse__view_t {
/* TODO: store information about terminal screen (a WINDOW* in curses world)
*/
svn_browse__model_t *model;
@@ -204,36 +218,129 @@ view_on_event(svn_browse__view_t *view,
return SVN_NO_ERROR;
}
-static void
-view_draw_item(const svn_browse__item_t *item, int y, svn_boolean_t selected)
+static char *
+rightpad(const char *cstr, int padding, apr_pool_t *result_pool)
+{
+ int len = strlen(cstr);
+ char *result = apr_palloc(result_pool, padding + 1);
+
+ if (len < padding)
+ {
+ memcpy(result, cstr, len);
+ memset(result + len, ' ', padding - len);
+ }
+ else
+ {
+ memcpy(result, cstr, len);
+ }
+
+ result[padding] = '\0';
+ return result;
+}
+
+static char *
+leftpad(const char *cstr, int padding, apr_pool_t *result_pool)
+{
+ int len = strlen(cstr);
+ char *result = apr_palloc(result_pool, padding + 1);
+
+ if (len < padding)
+ {
+ int spaces = padding - len;
+ memset(result, ' ', spaces);
+ memcpy(result + spaces, cstr, len);
+ }
+ else
+ {
+ memcpy(result, cstr, len);
+ }
+
+ result[padding] = '\0';
+ return result;
+}
+
+static const char *
+format_node_size(const svn_browse__item_t *item, apr_pool_t *pool)
{
- if (selected)
- standout();
+ if (item->dirent->kind == svn_node_dir)
+ return "(dir)";
+ else
+ return apr_psprintf(pool, "%ld KiB", item->dirent->size / 1024);
+}
+static const char *
+format_node_name(const svn_browse__item_t *item, apr_pool_t *pool)
+{
if (item->dirent->kind == svn_node_dir)
- mvprintw(y, 0, "%s/", item->name);
- else if (item->dirent->kind == svn_node_file)
- mvprintw(y, 0, "%s", item->name);
+ return apr_pstrcat(pool, item->name, "/", SVN_VA_NULL);
else
- abort();
+ return item->name;
+}
+
+static int
+get_item_style(svn_node_kind_t kind, svn_boolean_t selected)
+{
+ switch (kind)
+ {
+ case svn_node_dir:
+ return COLOR_PAIR((selected) ? COLOR_PAIR_DIR_SELECTED
+ : COLOR_PAIR_DIR);
+ case svn_node_file:
+ return COLOR_PAIR((selected) ? COLOR_PAIR_FILE_SELECTED
+ : COLOR_PAIR_FILE);
+ default:
+ abort();
+ }
+}
+
+static void
+view_draw_item(const svn_browse__item_t *item, int y, svn_boolean_t selected,
+ apr_pool_t *scratch_pool)
+{
+ int attrs = 0;
+ const char *prefix;
- mvprintw(y, COLS - 40, "%8ld KiB r%-8ld %s",
- item->dirent->size / 1024,
- item->dirent->created_rev,
- item->dirent->last_author);
+ move(y, 0);
+ attrset(get_item_style(item->dirent->kind, selected));
- if (selected)
- standend();
+ /* 12 + 12 + (20 + 1) + 2 = 47 */
+
+ addch(' ');
+ addstr(rightpad(format_node_name(item, scratch_pool),
+ COLS - 47, scratch_pool));
+
+ attrset(get_item_style(svn_node_file, selected));
+ addstr(leftpad(format_node_size(item, scratch_pool), 12, scratch_pool));
+ addstr(leftpad(apr_psprintf(scratch_pool, "r%ld", item->dirent->created_rev),
+ 12, scratch_pool));
+ addch(' ');
+ addstr(rightpad(item->dirent->last_author, 20, scratch_pool));
+ addch(' ');
+}
+
+static void
+view_draw_info_bar(svn_browse__view_t *view, apr_pool_t *scratch_pool)
+{
+ const char *abspath = svn_path_url_add_component2(
+ view->model->root, view->model->current->relpath, scratch_pool);
+ svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
+ const char *prefix = " ";
+ const char *suffix = "Apache Subversion ";
+
+ move(0, 0);
+ attrset(COLOR_PAIR(COLOR_PAIR_INFO_BAR));
+ addstr(prefix);
+ addstr(rightpad(apr_psprintf(scratch_pool, "URL: %s", abspath),
+ COLS - strlen(prefix) - strlen(suffix), scratch_pool));
+ addstr(suffix);
}
static void
view_draw(svn_browse__view_t *view, apr_pool_t *pool)
{
int i;
- const char *abspath = svn_path_url_add_component2(
- view->model->root, view->model->current->relpath, pool);
- mvprintw(0, 4, "Browsing: %s", abspath);
+ view_draw_info_bar(view, pool);
for (i = 0; i < view->model->current->list->nelts; i++)
{
@@ -243,7 +350,7 @@ view_draw(svn_browse__view_t *view, apr_
int y = i - view->model->current->scroller_offset;
if (0 <= y && y < LINES)
- view_draw_item(item, y + 1, selected);
+ view_draw_item(item, y + 1, selected, pool);
}
}
@@ -472,6 +579,15 @@ sub_main(int *code, int argc, const char
ESCDELAY = 0;
#endif /* NCURSES_VERSION */
+ start_color();
+ use_default_colors();
+
+ init_pair(COLOR_PAIR_INFO_BAR, COLOR_YELLOW, COLOR_BRANDING);
+ init_pair(COLOR_PAIR_DIR, COLOR_CYAN, -1);
+ init_pair(COLOR_PAIR_DIR_SELECTED, COLOR_CYAN, COLOR_PRIMARY);
+ init_pair(COLOR_PAIR_FILE, -1, -1);
+ init_pair(COLOR_PAIR_FILE_SELECTED, -1, COLOR_PRIMARY);
+
view = view_make(ctx, pool);
iterpool = svn_pool_create(pool);