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);

Reply via email to