Author: rinrab
Date: Sat May 16 15:32:17 2026
New Revision: 1934261

Log:
svnbrowse: Display file information if a file is opened. This prevents it from
crashing when each time when we face a file. Yet it's not perfect but at least
we have state handling logic and the looks can be improved later.

* subversion/svnbrowse/model.c
  (state_from_file, state_from_dir): New functions; state_from_dir does the
   same thing as svn_browse__state_create() was before.
  (svn_browse__state_create): Forward logic to either state_from_file or
   state_from_dir. Currently try directory first and in case of an error treat
   it as a file.
* subversion/svnbrowse/svnbrowse.c
  (#include): Add svn_time.h.
  (view_on_event): Check state before proceeding to handle keystrokes.
  (view_get_list_height): New function.
  (view_draw_footer): Use view_get_list_height() to measure size of the
   current list.
  (dir_draw, file_draw): New functions. 
  (view_draw): Call either dir_draw() or file_draw() based on the current
   state.
* subversion/svnbrowse/svnbrowse.h
  (svn_browse__state_type_e): New enum.
  (svn_browse__state_t): Add info for node if it was a file and update some
   comments.

Modified:
   subversion/trunk/subversion/svnbrowse/model.c
   subversion/trunk/subversion/svnbrowse/svnbrowse.c
   subversion/trunk/subversion/svnbrowse/svnbrowse.h

Modified: subversion/trunk/subversion/svnbrowse/model.c
==============================================================================
--- subversion/trunk/subversion/svnbrowse/model.c       Sat May 16 14:27:11 
2026        (r1934260)
+++ subversion/trunk/subversion/svnbrowse/model.c       Sat May 16 15:32:17 
2026        (r1934261)
@@ -85,12 +85,12 @@ sort_item_comparison_func(const void *le
 }
 
 svn_error_t *
-svn_browse__state_create(svn_browse__state_t **state_p,
-                         svn_ra_session_t *session,
-                         const char *relpath,
-                         svn_revnum_t revision,
-                         apr_pool_t *result_pool,
-                         apr_pool_t *scratch_pool)
+state_from_dir(svn_browse__state_t **state_p,
+               svn_ra_session_t *session,
+               const char *relpath,
+               svn_revnum_t revision,
+               apr_pool_t *result_pool,
+               apr_pool_t *scratch_pool)
 {
   svn_browse__state_t *state = apr_pcalloc(result_pool, sizeof(*state));
   svn_revnum_t fetched_revnum;
@@ -100,6 +100,7 @@ svn_browse__state_create(svn_browse__sta
   SVN_ERR(svn_ra_get_dir2(session, &dirents, &fetched_revnum, NULL, relpath,
                           revision, SVN_DIRENT_ALL, scratch_pool));
 
+  state->type = svn_browse__state_dir;
   state->relpath = apr_pstrdup(result_pool, relpath);
   state->revision = fetched_revnum;
   state->selection = 0;
@@ -129,6 +130,51 @@ svn_browse__state_create(svn_browse__sta
 }
 
 svn_error_t *
+state_from_file(svn_browse__state_t **state_p,
+                svn_ra_session_t *session,
+                const char *relpath,
+                svn_revnum_t revision,
+                apr_pool_t *result_pool,
+                apr_pool_t *scratch_pool)
+{
+  svn_browse__state_t *state = apr_pcalloc(result_pool, sizeof(*state));
+  svn_revnum_t fetched_revnum;
+  apr_hash_index_t *hi;
+  svn_dirent_t *dirent;
+
+  SVN_ERR(svn_ra_stat(session, relpath, revision, &dirent, scratch_pool));
+
+  state->type = svn_browse__state_file;
+  state->relpath = apr_pstrdup(result_pool, relpath);
+  state->revision = fetched_revnum;
+  state->this_dirent = svn_dirent_dup(dirent, result_pool);
+  state->pool = result_pool;
+
+  *state_p = state;
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_browse__state_create(svn_browse__state_t **state_p,
+                         svn_ra_session_t *session,
+                         const char *relpath,
+                         svn_revnum_t revision,
+                         apr_pool_t *result_pool,
+                         apr_pool_t *scratch_pool)
+{
+  svn_error_t *err;
+
+  err = state_from_dir(state_p, session, relpath, revision, result_pool,
+                       scratch_pool);
+
+  if (err && err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)
+    return svn_error_trace(state_from_file(state_p, session, relpath, revision,
+                                           result_pool, scratch_pool));
+  else
+    return svn_error_trace(err);
+}
+
+svn_error_t *
 svn_browse__model_enter_path(svn_browse__model_t *ctx, const char *relpath,
                  apr_pool_t *scratch_pool)
 {

Modified: subversion/trunk/subversion/svnbrowse/svnbrowse.c
==============================================================================
--- subversion/trunk/subversion/svnbrowse/svnbrowse.c   Sat May 16 14:27:11 
2026        (r1934260)
+++ subversion/trunk/subversion/svnbrowse/svnbrowse.c   Sat May 16 15:32:17 
2026        (r1934261)
@@ -37,6 +37,7 @@
 #include <curses.h>
 
 #include "svn_private_config.h"
+#include "svn_time.h"
 #include "svnbrowse.h"
 
 /* Option codes and descriptions for the command line client.
@@ -263,22 +264,9 @@ view_on_event(svn_browse__view_t *view,
    * 3. The rest of keys remain as their equivalents on the current layout.
    * 4. If shift is held, they just become uppercased.
    */
+
   switch (ch)
     {
-      case KEY_UP:
-      case 'k':
-      case CTRL('p'):
-        SVN_ERR(svn_browse__model_move_selection(view->model, -1));
-        break;
-      case KEY_DOWN:
-      case 'j':
-      case CTRL('n'):
-        SVN_ERR(svn_browse__model_move_selection(view->model, 1));
-        break;
-      case '\n':
-      case '\r':
-        SVN_ERR(svn_browse__model_go_enter(view->model, scratch_pool));
-        break;
       case KEY_BACKSPACE:
       case '-':
       case 'u':
@@ -286,45 +274,72 @@ view_on_event(svn_browse__view_t *view,
         view->model->current->scroller_offset =
             view->model->current->selection - scrollsize / 2;
         break;
-      case CTRL('e'):
-        view->model->current->scroller_offset += 1;
-        break;
-      case CTRL('y'):
-        view->model->current->scroller_offset -= 1;
-        break;
-      case CTRL('d'):
-        SVN_ERR(svn_browse__model_move_selection(view->model, scrollsize / 2));
-        break;
-      case CTRL('f'):
-      case KEY_NPAGE:
-        SVN_ERR(svn_browse__model_move_selection(view->model, scrollsize));
-        break;
-      case CTRL('u'):
-        SVN_ERR(svn_browse__model_move_selection(view->model, -scrollsize / 
2));
-        break;
-      case CTRL('b'):
-      case KEY_PPAGE:
-        SVN_ERR(svn_browse__model_move_selection(view->model, -scrollsize));
-        break;
-      case 'g':
-      case KEY_HOME:
-        view->model->current->selection = 0;
-        break;
-      case 'G':
-      case KEY_END:
-        view->model->current->selection =
-            view->model->current->list->nelts - 1;
-        break;
-      case 'z':
-        view->model->current->scroller_offset =
-            view->model->current->selection - scrollsize / 2;
-        break;
       case 'q':
       case KEY_ESC:
         return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
+      default:
+        break;
     }
 
-  SVN_ERR(svn_browse__model_scroll_in_view(view->model, scrollsize));
+  if (view->model->current->type == svn_browse__state_dir)
+    {
+      switch (ch)
+        {
+          case KEY_UP:
+          case 'k':
+          case CTRL('p'):
+            SVN_ERR(svn_browse__model_move_selection(view->model, -1));
+            break;
+          case KEY_DOWN:
+          case 'j':
+          case CTRL('n'):
+            SVN_ERR(svn_browse__model_move_selection(view->model, 1));
+            break;
+          case '\n':
+          case '\r':
+            SVN_ERR(svn_browse__model_go_enter(view->model, scratch_pool));
+            break;
+          case CTRL('e'):
+            view->model->current->scroller_offset += 1;
+            break;
+          case CTRL('y'):
+            view->model->current->scroller_offset -= 1;
+            break;
+          case CTRL('d'):
+            SVN_ERR(svn_browse__model_move_selection(view->model,
+                                                     scrollsize / 2));
+            break;
+          case CTRL('f'):
+          case KEY_NPAGE:
+            SVN_ERR(svn_browse__model_move_selection(view->model,
+                                                     scrollsize));
+            break;
+          case CTRL('u'):
+            SVN_ERR(svn_browse__model_move_selection(view->model,
+                                                     -scrollsize / 2));
+            break;
+          case CTRL('b'):
+          case KEY_PPAGE:
+            SVN_ERR(svn_browse__model_move_selection(view->model,
+                                                     -scrollsize));
+            break;
+          case 'g':
+          case KEY_HOME:
+            view->model->current->selection = 0;
+            break;
+          case 'G':
+          case KEY_END:
+            view->model->current->selection =
+                view->model->current->list->nelts - 1;
+            break;
+          case 'z':
+            view->model->current->scroller_offset =
+                view->model->current->selection - scrollsize / 2;
+            break;
+        }
+
+      SVN_ERR(svn_browse__model_scroll_in_view(view->model, scrollsize));
+    }
 
   return SVN_NO_ERROR;
 }
@@ -494,6 +509,15 @@ format_percentage_scroll(int scroll, int
     }
 }
 
+static int
+view_get_list_height(const svn_browse__state_t *state)
+{
+  if (state->type == svn_browse__state_dir)
+    return state->list->nelts;
+  else
+    return 0;
+}
+
 static void
 view_draw_footer(svn_browse__view_t *view, WINDOW *win,
                  apr_pool_t *scratch_pool)
@@ -508,26 +532,24 @@ view_draw_footer(svn_browse__view_t *vie
                         getmaxx(win) - 4 - strlen(brand) - 16,
                         scratch_pool));
   waddstr(win, brand);
+
   waddstr(win, leftpad(apr_psprintf(scratch_pool, "%d/%d",
-                                    state->selection + 1, state->list->nelts),
+                                    state->selection + 1,
+                                    view_get_list_height(state)),
                        8, scratch_pool));
   waddstr(win, leftpad(format_percentage_scroll(state->scroller_offset,
-                                                state->list->nelts,
+                                                view_get_list_height(state),
                                                 getmaxy(view->list),
                                                 scratch_pool),
                        8, scratch_pool));
   waddstr(win, "  ");
 }
 
-
 static void
-view_draw(svn_browse__view_t *view, apr_pool_t *pool)
+dir_draw(svn_browse__view_t *view, apr_pool_t *pool)
 {
   int i;
 
-  view_draw_header(view, view->header, pool);
-  view_draw_footer(view, view->footer, pool);
-
   for (i = 0; i < view->model->current->list->nelts; i++)
     {
       svn_browse__item_t *item = APR_ARRAY_IDX(view->model->current->list, i,
@@ -540,6 +562,39 @@ view_draw(svn_browse__view_t *view, apr_
     }
 }
 
+static void
+file_draw(svn_browse__view_t *view, apr_pool_t *pool)
+{
+  const svn_browse__state_t *state = view->model->current;
+
+  mvwprintw(view->list, 0, 0, " File: %s",
+            svn_relpath_basename(state->relpath, pool));
+
+  mvwprintw(view->list, 1, 0, " Last Changed Rev: %ld",
+            state->this_dirent->created_rev);
+  mvwprintw(view->list, 2, 0, " Last Changed Author: %s",
+            state->this_dirent->last_author);
+  mvwprintw(view->list, 3, 0, " Last Changed Date: %s",
+            svn_time_to_human_cstring(state->this_dirent->time, pool));
+}
+
+static void
+view_draw(svn_browse__view_t *view, apr_pool_t *pool)
+{
+  view_draw_header(view, view->header, pool);
+  view_draw_footer(view, view->footer, pool);
+
+  switch (view->model->current->type)
+    {
+      case svn_browse__state_dir:
+        dir_draw(view, pool);
+        break;
+      case svn_browse__state_file:
+        file_draw(view, pool);
+        break;
+    }
+}
+
 static svn_error_t *
 show_usage(apr_pool_t *scratch_pool)
 {

Modified: subversion/trunk/subversion/svnbrowse/svnbrowse.h
==============================================================================
--- subversion/trunk/subversion/svnbrowse/svnbrowse.h   Sat May 16 14:27:11 
2026        (r1934260)
+++ subversion/trunk/subversion/svnbrowse/svnbrowse.h   Sat May 16 15:32:17 
2026        (r1934261)
@@ -68,15 +68,26 @@ typedef struct svn_browse__item_t {
   const svn_dirent_t *dirent;
 } svn_browse__item_t;
 
+typedef enum svn_browse__state_type_e {
+  svn_browse__state_dir,
+  svn_browse__state_file,
+} svn_browse__state_type_e;
+
 /* a state of a single directory */
 typedef struct svn_browse__state_t {
+  svn_browse__state_type_e type;
+
   /* information about this node */
   const char *relpath;
   svn_revnum_t revision;
 
-  /* stores the list of nodes in this state; an array of svn_browse__item_t */
+  /* stores the list of nodes in this state; an array of svn_browse__item_t or
+   * NULL if 'type' is set to svn_browse__state_file */
   apr_array_header_t *list;
 
+  /* only available for files */
+  const svn_dirent_t *this_dirent;
+
   /* the index of hovered item */
   int selection;
   int scroller_offset;

Reply via email to