commit 9b058f456d15d60a89334ce3e7f0a7c22e182c55 from: Mark Jamsek date: Thu Jun 30 05:02:23 2022 UTC tog: implement support for horizontal splitscreens Enable all main views to open primary child views in a horizontal split: log -> diff tree -> blame blame -> diff ref -> log Support is yet to be expanded to nested child views and key maps for manipulating split type and size. ok op@ and stsp@ commit - b880cc75ed8a91d3ed8ddb051aa4ea4e2b7314c0 commit + 9b058f456d15d60a89334ce3e7f0a7c22e182c55 blob - d66402a503cc8cbf5bc900f559fae4373b39eca6 blob + 6a0c6dbff39ee1a8db173cbc3fa1fbf0918eecab --- tog/tog.c +++ tog/tog.c @@ -105,6 +105,14 @@ enum tog_view_type { TOG_VIEW_REF, }; +enum tog_view_mode { + TOG_VIEW_SPLIT_NONE, + TOG_VIEW_SPLIT_VERT, + TOG_VIEW_SPLIT_HRZN +}; + +#define HSPLIT_SCALE 0.3 /* default horizontal split scale */ + #define TOG_EOF_STRING "(END)" struct commit_queue_entry { @@ -500,9 +508,10 @@ struct tog_view { TAILQ_ENTRY(tog_view) entry; WINDOW *window; PANEL *panel; - int nlines, ncols, begin_y, begin_x; + int nlines, ncols, begin_y, begin_x; /* based on split height/width */ int maxx, x; /* max column and current start column */ int lines, cols; /* copies of LINES and COLS */ + int nscrolled, offset; /* lines scrolled and hsplit line offset */ int ch, count; /* current keymap and count prefix */ int focussed; /* Only set on one parent or child view at a time. */ int dying; @@ -520,6 +529,7 @@ struct tog_view { */ int focus_child; + enum tog_view_mode mode; /* type-specific state */ enum tog_view_type type; union { @@ -669,8 +679,6 @@ view_open(int nlines, int ncols, int begin_y, int begi if (view == NULL) return NULL; - view->ch = 0; - view->count = 0; view->type = type; view->lines = LINES; view->cols = COLS; @@ -702,6 +710,13 @@ view_split_begin_x(int begin_x) return (COLS - MAX(COLS / 2, 80)); } +/* XXX Stub till we decide what to do. */ +static int +view_split_begin_y(int lines) +{ + return lines * HSPLIT_SCALE; +} + static const struct got_error *view_resize(struct tog_view *); static const struct got_error * @@ -709,9 +724,14 @@ view_splitscreen(struct tog_view *view) { const struct got_error *err = NULL; - view->begin_y = 0; - view->begin_x = view_split_begin_x(0); - view->nlines = LINES; + if (view->mode == TOG_VIEW_SPLIT_HRZN) { + view->begin_y = view_split_begin_y(view->nlines); + view->begin_x = 0; + } else { + view->begin_x = view_split_begin_x(0); + view->begin_y = 0; + } + view->nlines = LINES - view->begin_y; view->ncols = COLS - view->begin_x; view->lines = LINES; view->cols = COLS; @@ -719,6 +739,9 @@ view_splitscreen(struct tog_view *view) if (err) return err; + if (view->parent && view->mode == TOG_VIEW_SPLIT_HRZN) + view->parent->nlines = view->begin_y; + if (mvwin(view->window, view->begin_y, view->begin_x) == ERR) return got_error_from_errno("mvwin"); @@ -755,29 +778,60 @@ view_is_parent_view(struct tog_view *view) static int view_is_splitscreen(struct tog_view *view) { - return view->begin_x > 0; + return view->begin_x > 0 || view->begin_y > 0; } +static void +view_border(struct tog_view *view) +{ + PANEL *panel; + const struct tog_view *view_above; + if (view->parent) + return view_border(view->parent); + + panel = panel_above(view->panel); + if (panel == NULL) + return; + + view_above = panel_userptr(panel); + if (view->mode == TOG_VIEW_SPLIT_HRZN) + mvwhline(view->window, view_above->begin_y - 1, + view->begin_x, got_locale_is_utf8() ? + ACS_HLINE : '-', view->ncols); + else + mvwvline(view->window, view->begin_y, view_above->begin_x - 1, + got_locale_is_utf8() ? ACS_VLINE : '|', view->nlines); +} + +static const struct got_error *request_log_commits(struct tog_view *); +static const struct got_error *offset_selection_down(struct tog_view *); +static void offset_selection_up(struct tog_view *); + static const struct got_error * view_resize(struct tog_view *view) { - int nlines, ncols; + const struct got_error *err = NULL; + int dif, nlines, ncols; + dif = LINES - view->lines; /* line difference */ + if (view->lines > LINES) nlines = view->nlines - (view->lines - LINES); else nlines = view->nlines + (LINES - view->lines); - if (view->cols > COLS) ncols = view->ncols - (view->cols - COLS); else ncols = view->ncols + (COLS - view->cols); if (view->child) { + int hs = view->child->begin_y; + if (view->child->focussed) view->child->begin_x = view_split_begin_x(view->begin_x); - if (view->child->begin_x == 0) { + if (view->mode == TOG_VIEW_SPLIT_HRZN || + view->child->begin_x == 0) { ncols = COLS; view_fullscreen(view->child); @@ -791,6 +845,44 @@ view_resize(struct tog_view *view) view_splitscreen(view->child); show_panel(view->child->panel); } + /* + * Request commits if terminal height was increased in a log + * view so we have enough commits loaded to populate the view. + */ + if (view->type == TOG_VIEW_LOG && dif > 0) { + struct tog_log_view_state *ts = &view->state.log; + + if (ts->commits.ncommits < ts->selected_entry->idx + + view->lines - ts->selected) { + view->nscrolled = ts->selected_entry->idx + + view->lines - ts->selected - + ts->commits.ncommits + dif; + err = request_log_commits(view); + if (err) + return err; + } + } + + /* + * XXX This is ugly and needs to be moved into the above + * logic but "works" for now and my attempts at moving it + * break either 'tab' or 'F' key maps in horizontal splits. + */ + if (hs) { + err = view_splitscreen(view->child); + if (err) + return err; + if (dif < 0) { /* top split decreased */ + err = offset_selection_down(view); + if (err) + return err; + } + view_border(view); + update_panels(); + doupdate(); + show_panel(view->child->panel); + nlines = view->nlines; + } } else if (view->parent == NULL) ncols = COLS; @@ -850,6 +942,7 @@ static const struct got_error * view_search_start(struct tog_view *view) { const struct got_error *err = NULL; + struct tog_view *v = view; char pattern[1024]; int ret; @@ -863,12 +956,17 @@ view_search_start(struct tog_view *view) if (view->nlines < 1) return NULL; - mvwaddstr(view->window, view->begin_y + view->nlines - 1, 0, "/"); - wclrtoeol(view->window); + if (view->mode == TOG_VIEW_SPLIT_HRZN && view->child && + view_is_splitscreen(view->child)) + v = view->child; + mvwaddstr(v->window, v->nlines - 1, 0, "/"); + wclrtoeol(v->window); + nocbreak(); echo(); - ret = wgetnstr(view->window, pattern, sizeof(pattern)); + ret = wgetnstr(v->window, pattern, sizeof(pattern)); + wrefresh(v->window); cbreak(); noecho(); if (ret == ERR) @@ -898,19 +996,29 @@ view_search_start(struct tog_view *view) static int get_compound_key(struct tog_view *view, int c) { - int x, n = 0; + struct tog_view *v = view; + int x, n = 0; + if (view->mode == TOG_VIEW_SPLIT_HRZN && view->child && + view_is_splitscreen(view->child)) + v = view->child; + else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent) + v = view->parent; + view->count = 0; halfdelay(5); /* block for half a second */ - wattron(view->window, A_BOLD); - wmove(view->window, view->nlines - 1, 0); - wclrtoeol(view->window); - waddch(view->window, ':'); + wattron(v->window, A_BOLD); + wmove(v->window, v->nlines - 1, 0); + wclrtoeol(v->window); + waddch(v->window, ':'); do { - x = getcurx(view->window); - if (x != ERR && x < view->ncols) - waddch(view->window, c); + x = getcurx(v->window); + if (x != ERR && x < view->ncols) { + waddch(v->window, c); + wrefresh(v->window); + } + /* * Don't overflow. Max valid request should be the greatest * between the longest and total lines; cap at 10 million. @@ -924,7 +1032,7 @@ get_compound_key(struct tog_view *view, int c) /* Massage excessive or inapplicable values at the input handler. */ view->count = n; - wattroff(view->window, A_BOLD); + wattroff(v->window, A_BOLD); cbreak(); /* return to blocking */ return c; } @@ -1011,11 +1119,31 @@ view_input(struct tog_view **new, int *done, struct to view->focussed = 0; view->parent->focussed = 1; view->parent->focus_child = 0; - if (!view_is_splitscreen(view)) + if (!view_is_splitscreen(view)) { + if (view->mode == TOG_VIEW_SPLIT_HRZN && + view->parent->type == TOG_VIEW_LOG) { + err = request_log_commits(view->parent); + if (err) + return err; + } + offset_selection_up(view->parent); err = view_fullscreen(view->parent); + if (err) + return err; + } } break; case 'q': + if (view->parent && view->mode == TOG_VIEW_SPLIT_HRZN) { + if (view->parent->type == TOG_VIEW_LOG) { + /* might need more commits to fill fullscreen */ + err = request_log_commits(view->parent); + if (err) + break; + } + offset_selection_up(view->parent); + view->parent->mode = TOG_VIEW_SPLIT_NONE; + } err = view->input(new, view, ch); view->dying = 1; break; @@ -1044,13 +1172,24 @@ view_input(struct tog_view **new, int *done, struct to err = view_fullscreen(view); } else { err = view_splitscreen(view); - if (!err) + if (!err && view->mode != TOG_VIEW_SPLIT_HRZN) err = view_resize(view->parent); } if (err) break; err = view->input(new, view, KEY_RESIZE); } + if (err) + break; + if (view->type == TOG_VIEW_LOG) { + err = request_log_commits(view); + if (err) + break; + } + if (view->parent) + err = offset_selection_down(view->parent); + if (!err) + err = offset_selection_down(view); break; case KEY_RESIZE: break; @@ -1079,24 +1218,6 @@ view_input(struct tog_view **new, int *done, struct to return err; } -static void -view_vborder(struct tog_view *view) -{ - PANEL *panel; - const struct tog_view *view_above; - - if (view->parent) - return view_vborder(view->parent); - - panel = panel_above(view->panel); - if (panel == NULL) - return; - - view_above = panel_userptr(panel); - mvwvline(view->window, view->begin_y, view_above->begin_x - 1, - got_locale_is_utf8() ? ACS_VLINE : '|', view->nlines); -} - static int view_needs_focus_indication(struct tog_view *view) { @@ -1153,7 +1274,8 @@ view_loop(struct tog_view *view) if (view->parent) { view->parent->child = NULL; view->parent->focus_child = 0; - + /* Restore fullscreen line height. */ + view->parent->nlines = view->parent->lines; err = view_resize(view->parent); if (err) break; @@ -1932,7 +2054,7 @@ draw_commits(struct tog_view *view) entry = TAILQ_NEXT(entry, entry); } - view_vborder(view); + view_border(view); done: free(id_str); free(refs_str); @@ -2007,6 +2129,19 @@ trigger_log_thread(struct tog_view *view, int wait) } static const struct got_error * +request_log_commits(struct tog_view *view) +{ + struct tog_log_view_state *state = &view->state.log; + const struct got_error *err = NULL; + + state->thread_args.commits_needed = view->nscrolled; + err = trigger_log_thread(view, 1); + view->nscrolled = 0; + + return err; +} + +static const struct got_error * log_scroll_down(struct tog_view *view, int maxscroll) { struct tog_log_view_state *s = &view->state.log; @@ -2031,10 +2166,11 @@ log_scroll_down(struct tog_view *view, int maxscroll) do { pentry = TAILQ_NEXT(s->last_displayed_entry, entry); - if (pentry == NULL) + if (pentry == NULL && view->mode != TOG_VIEW_SPLIT_HRZN) break; - s->last_displayed_entry = pentry; + s->last_displayed_entry = pentry ? + pentry : s->last_displayed_entry;; pentry = TAILQ_NEXT(s->first_displayed_entry, entry); if (pentry == NULL) @@ -2042,11 +2178,16 @@ log_scroll_down(struct tog_view *view, int maxscroll) s->first_displayed_entry = pentry; } while (++nscrolled < maxscroll); + if (view->mode == TOG_VIEW_SPLIT_HRZN) + view->nscrolled += nscrolled; + else + view->nscrolled = 0; + return err; } static const struct got_error * -open_diff_view_for_commit(struct tog_view **new_view, int begin_x, +open_diff_view_for_commit(struct tog_view **new_view, int begin_y, int begin_x, struct got_commit_object *commit, struct got_object_id *commit_id, struct tog_view *log_view, struct got_repository *repo) { @@ -2054,7 +2195,7 @@ open_diff_view_for_commit(struct tog_view **new_view, struct got_object_qid *parent_id; struct tog_view *diff_view; - diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF); + diff_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_DIFF); if (diff_view == NULL) return got_error_from_errno("view_open"); @@ -2662,6 +2803,9 @@ log_move_cursor_down(struct tog_view *view, int page) if (!page) { int eos = view->nlines - 2; + if (view->mode == TOG_VIEW_SPLIT_HRZN && view->child && + view_is_splitscreen(view->child)) + --eos; /* border consumes the last line */ if (s->selected < MIN(eos, s->commits.ncommits - 1)) ++s->selected; else @@ -2686,6 +2830,13 @@ log_move_cursor_down(struct tog_view *view, int page) if (err) return err; + /* + * We might necessarily overshoot in horizontal + * splits; if so, select the last displayed commit. + */ + s->selected = MIN(s->selected, + s->last_displayed_entry->idx - s->first_displayed_entry->idx); + select_commit(s); if (s->thread_args.log_complete && @@ -2695,7 +2846,46 @@ log_move_cursor_down(struct tog_view *view, int page) return NULL; } +/* + * Get splitscreen dimensions based on TOG_VIEW_SPLIT_MODE: + * TOG_VIEW_SPLIT_VERT vertical split if COLS > 119 (default) + * TOG_VIEW_SPLIT_HRZN horizontal split + * Assign start column and line of the new split to *x and *y, respectively, + * and assign view mode to view->mode. + */ +static void +view_get_split(struct tog_view *view, int *y, int *x) +{ + char *mode; + + mode = getenv("TOG_VIEW_SPLIT_MODE"); + + if (!mode || mode[0] != 'h') { + view->mode = TOG_VIEW_SPLIT_VERT; + *x = view_split_begin_x(view->begin_x); + } else if (mode && mode[0] == 'h') { + view->mode = TOG_VIEW_SPLIT_HRZN; + *y = view_split_begin_y(view->lines); + } +} + +/* Split view horizontally at y and offset view->state->selected line. */ static const struct got_error * +view_init_hsplit(struct tog_view *view, int y) +{ + const struct got_error *err = NULL; + + view->nlines = y; + err = view_resize(view); + if (err) + return err; + + err = offset_selection_down(view); + + return err; +} + +static const struct got_error * input_log_view(struct tog_view **new_view, struct tog_view *view, int ch) { const struct got_error *err = NULL; @@ -2703,7 +2893,7 @@ input_log_view(struct tog_view **new_view, struct tog_ struct tog_view *diff_view = NULL, *tree_view = NULL; struct tog_view *ref_view = NULL; struct commit_queue_entry *entry; - int begin_x = 0, n, nscroll = view->nlines - 1; + int begin_x = 0, begin_y = 0, n, nscroll = view->nlines - 1; if (s->thread_args.load_all) { if (ch == KEY_BACKSPACE) @@ -2814,19 +3004,32 @@ input_log_view(struct tog_view **new_view, struct tog_ } break; case KEY_ENTER: - case '\r': + case '\r': { view->count = 0; if (s->selected_entry == NULL) break; + + /* get dimensions--don't split till initialisation succeeds */ if (view_is_parent_view(view)) - begin_x = view_split_begin_x(view->begin_x); - err = open_diff_view_for_commit(&diff_view, begin_x, + view_get_split(view, &begin_y, &begin_x); + + err = open_diff_view_for_commit(&diff_view, begin_y, begin_x, s->selected_entry->commit, s->selected_entry->id, view, s->repo); if (err) break; + + if (view->mode == TOG_VIEW_SPLIT_HRZN) { /* safe to split */ + err = view_init_hsplit(view, begin_y); + if (err) + break; + } + view->focussed = 0; diff_view->focussed = 1; + diff_view->mode = view->mode; + diff_view->nlines = view->lines - begin_y; + if (view_is_parent_view(view)) { err = view_close_child(view); if (err) @@ -2838,6 +3041,7 @@ input_log_view(struct tog_view **new_view, struct tog_ } else *new_view = diff_view; break; + } case 't': view->count = 0; if (s->selected_entry == NULL) @@ -2922,7 +3126,7 @@ input_log_view(struct tog_view **new_view, struct tog_ s->selected = 0; s->thread_args.log_complete = 0; s->quit = 0; - s->thread_args.commits_needed = view->nlines; + s->thread_args.commits_needed = view->lines; s->matched_entry = NULL; s->search_entry = NULL; break; @@ -3448,7 +3652,7 @@ draw_file(struct tog_view *view, const char *header) else s->last_displayed_line = s->first_displayed_line; - view_vborder(view); + view_border(view); if (s->eof) { while (nprinted < view->nlines) { @@ -4650,7 +4854,7 @@ draw_blame(struct tog_view *view) free(line); s->last_displayed_line = lineno; - view_vborder(view); + view_border(view); return NULL; } @@ -5111,7 +5315,7 @@ show_blame_view(struct tog_view *view) err = draw_blame(view); - view_vborder(view); + view_border(view); return err; } @@ -5121,7 +5325,12 @@ input_blame_view(struct tog_view **new_view, struct to const struct got_error *err = NULL, *thread_err = NULL; struct tog_view *diff_view; struct tog_blame_view_state *s = &view->state.blame; - int begin_x = 0, nscroll = view->nlines - 2; + int eos, nscroll, begin_y = 0, begin_x = 0; + + eos = nscroll = view->nlines - 2; + if (view->mode == TOG_VIEW_SPLIT_HRZN && view->child && + view_is_splitscreen(view->child)) + --eos; /* border */ switch (ch) { case '0': @@ -5155,13 +5364,12 @@ input_blame_view(struct tog_view **new_view, struct to break; case 'G': case KEY_END: - if (s->blame.nlines < view->nlines - 2) { + if (s->blame.nlines < eos) { s->selected_line = s->blame.nlines; s->first_displayed_line = 1; } else { - s->selected_line = view->nlines - 2; - s->first_displayed_line = s->blame.nlines - - (view->nlines - 3); + s->selected_line = eos; + s->first_displayed_line = s->blame.nlines - (eos - 1); } view->count = 0; break; @@ -5198,12 +5406,10 @@ input_blame_view(struct tog_view **new_view, struct to case 'j': case KEY_DOWN: case CTRL('n'): - if (s->selected_line < view->nlines - 2 && - s->first_displayed_line + + if (s->selected_line < eos && s->first_displayed_line + s->selected_line <= s->blame.nlines) s->selected_line++; - else if (s->last_displayed_line < - s->blame.nlines) + else if (s->first_displayed_line < s->blame.nlines - (eos - 1)) s->first_displayed_line++; else view->count = 0; @@ -5313,11 +5519,11 @@ input_blame_view(struct tog_view **new_view, struct to err = got_object_open_as_commit(&commit, s->repo, id); if (err) break; - pid = STAILQ_FIRST( - got_object_commit_get_parent_ids(commit)); + pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit)); if (view_is_parent_view(view)) - begin_x = view_split_begin_x(view->begin_x); - diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF); + view_get_split(view, &begin_y, &begin_x); + + diff_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_DIFF); if (diff_view == NULL) { got_object_commit_close(commit); err = got_error_from_errno("view_open"); @@ -5330,8 +5536,16 @@ input_blame_view(struct tog_view **new_view, struct to view_close(diff_view); break; } + if (view->mode == TOG_VIEW_SPLIT_HRZN) { + err = view_init_hsplit(view, begin_y); + if (err) + break; + } + view->focussed = 0; diff_view->focussed = 1; + diff_view->mode = view->mode; + diff_view->nlines = view->lines - begin_y; if (view_is_parent_view(view)) { err = view_close_child(view); if (err) @@ -5541,6 +5755,9 @@ draw_tree_entries(struct tog_view *view, const char *p int limit = view->nlines; s->ndisplayed = 0; + if (view->mode == TOG_VIEW_SPLIT_HRZN && view->child && + view_is_splitscreen(view->child)) + --limit; /* border */ werase(view->window); @@ -5708,9 +5925,10 @@ tree_scroll_up(struct tog_tree_view_state *s, int maxs } } -static void -tree_scroll_down(struct tog_tree_view_state *s, int maxscroll) +static const struct got_error * +tree_scroll_down(struct tog_view *view, int maxscroll) { + struct tog_tree_view_state *s = &view->state.tree; struct got_tree_entry *next, *last; int n = 0; @@ -5721,13 +5939,16 @@ tree_scroll_down(struct tog_tree_view_state *s, int ma next = got_object_tree_get_first_entry(s->tree); last = s->last_displayed_entry; - while (next && last && n++ < maxscroll) { - last = got_tree_entry_get_next(s->tree, last); - if (last) { + while (next && n++ < maxscroll) { + if (last) + last = got_tree_entry_get_next(s->tree, last); + if (last || (view->mode == TOG_VIEW_SPLIT_HRZN && next)) { s->first_displayed_entry = next; next = got_tree_entry_get_next(s->tree, next); } } + + return NULL; } static const struct got_error * @@ -5777,7 +5998,7 @@ done: } static const struct got_error * -blame_tree_entry(struct tog_view **new_view, int begin_x, +blame_tree_entry(struct tog_view **new_view, int begin_y, int begin_x, struct got_tree_entry *te, struct tog_parent_trees *parents, struct got_object_id *commit_id, struct got_repository *repo) { @@ -5791,7 +6012,7 @@ blame_tree_entry(struct tog_view **new_view, int begin if (err) return err; - blame_view = view_open(0, 0, 0, begin_x, TOG_VIEW_BLAME); + blame_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_BLAME); if (blame_view == NULL) { err = got_error_from_errno("view_open"); goto done; @@ -6056,7 +6277,7 @@ show_tree_view(struct tog_view *view) err = draw_tree_entries(view, parent_path); free(parent_path); - view_vborder(view); + view_border(view); return err; } @@ -6131,11 +6352,15 @@ input_tree_view(struct tog_view **new_view, struct tog s->first_displayed_entry = NULL; break; case 'G': - case KEY_END: + case KEY_END: { + int eos = view->nlines - 3; + + if (view->mode == TOG_VIEW_SPLIT_HRZN) + --eos; /* border */ s->selected = 0; view->count = 0; te = got_object_tree_get_last_entry(s->tree); - for (n = 0; n < view->nlines - 3; n++) { + for (n = 0; n < eos; n++) { if (te == NULL) { if(s->tree != s->root) { s->first_displayed_entry = NULL; @@ -6149,6 +6374,7 @@ input_tree_view(struct tog_view **new_view, struct tog if (n > 0) s->selected = n - 1; break; + } case 'k': case KEY_UP: case CTRL('p'): @@ -6196,7 +6422,7 @@ input_tree_view(struct tog_view **new_view, struct tog view->count = 0; break; } - tree_scroll_down(s, 1); + tree_scroll_down(view, 1); break; case CTRL('d'): case 'd': @@ -6216,7 +6442,7 @@ input_tree_view(struct tog_view **new_view, struct tog view->count = 0; break; } - tree_scroll_down(s, nscroll); + tree_scroll_down(view, nscroll); break; case KEY_ENTER: case '\r': @@ -6238,6 +6464,11 @@ input_tree_view(struct tog_view **new_view, struct tog s->selected_entry = parent->selected_entry; s->selected = parent->selected; + if (s->selected > view->nlines - 3) { + err = offset_selection_down(view); + if (err) + break; + } free(parent); } else if (S_ISDIR(got_tree_entry_get_mode( s->selected_entry))) { @@ -6255,17 +6486,28 @@ input_tree_view(struct tog_view **new_view, struct tog } else if (S_ISREG(got_tree_entry_get_mode( s->selected_entry))) { struct tog_view *blame_view; - int begin_x = view_is_parent_view(view) ? - view_split_begin_x(view->begin_x) : 0; + int begin_x = 0, begin_y = 0; - err = blame_tree_entry(&blame_view, begin_x, + if (view_is_parent_view(view)) + view_get_split(view, &begin_y, &begin_x); + + err = blame_tree_entry(&blame_view, begin_y, begin_x, s->selected_entry, &s->parents, s->commit_id, s->repo); if (err) break; + + if (view->mode == TOG_VIEW_SPLIT_HRZN) { + err = view_init_hsplit(view, begin_y); + if (err) + break; + } + view->count = 0; view->focussed = 0; blame_view->focussed = 1; + blame_view->mode = view->mode; + blame_view->nlines = view->lines - begin_y; if (view_is_parent_view(view)) { err = view_close_child(view); if (err) @@ -6613,7 +6855,7 @@ done: } static const struct got_error * -log_ref_entry(struct tog_view **new_view, int begin_x, +log_ref_entry(struct tog_view **new_view, int begin_y, int begin_x, struct tog_reflist_entry *re, struct got_repository *repo) { struct tog_view *log_view; @@ -6630,7 +6872,7 @@ log_ref_entry(struct tog_view **new_view, int begin_x, return NULL; } - log_view = view_open(0, 0, 0, begin_x, TOG_VIEW_LOG); + log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG); if (log_view == NULL) { err = got_error_from_errno("view_open"); goto done; @@ -6665,9 +6907,10 @@ ref_scroll_up(struct tog_ref_view_state *s, int maxscr } } -static void -ref_scroll_down(struct tog_ref_view_state *s, int maxscroll) +static const struct got_error * +ref_scroll_down(struct tog_view *view, int maxscroll) { + struct tog_ref_view_state *s = &view->state.ref; struct tog_reflist_entry *next, *last; int n = 0; @@ -6677,13 +6920,16 @@ ref_scroll_down(struct tog_ref_view_state *s, int maxs next = TAILQ_FIRST(&s->refs); last = s->last_displayed_entry; - while (next && last && n++ < maxscroll) { - last = TAILQ_NEXT(last, entry); - if (last) { + while (next && n++ < maxscroll) { + if (last) + last = TAILQ_NEXT(last, entry); + if (last || (view->mode == TOG_VIEW_SPLIT_HRZN)) { s->first_displayed_entry = next; next = TAILQ_NEXT(next, entry); } } + + return NULL; } static const struct got_error * @@ -6785,6 +7031,9 @@ show_ref_view(struct tog_view *view) werase(view->window); s->ndisplayed = 0; + if (view->mode == TOG_VIEW_SPLIT_HRZN && view->child && + view_is_splitscreen(view->child)) + --limit; /* border */ if (limit == 0) return NULL; @@ -6916,7 +7165,7 @@ show_ref_view(struct tog_view *view) re = TAILQ_NEXT(re, entry); } - view_vborder(view); + view_border(view); return err; } @@ -6962,7 +7211,7 @@ input_ref_view(struct tog_view **new_view, struct tog_ struct tog_ref_view_state *s = &view->state.ref; struct tog_view *log_view, *tree_view; struct tog_reflist_entry *re; - int begin_x = 0, n, nscroll = view->nlines - 1; + int begin_y = 0, begin_x = 0, n, nscroll = view->nlines - 1; switch (ch) { case 'i': @@ -6995,11 +7244,23 @@ input_ref_view(struct tog_view **new_view, struct tog_ if (!s->selected_entry) break; if (view_is_parent_view(view)) - begin_x = view_split_begin_x(view->begin_x); - err = log_ref_entry(&log_view, begin_x, s->selected_entry, - s->repo); + view_get_split(view, &begin_y, &begin_x); + + err = log_ref_entry(&log_view, begin_y, begin_x, + s->selected_entry, s->repo); + if (err) + break; + + if (view->mode == TOG_VIEW_SPLIT_HRZN) { + err = view_init_hsplit(view, begin_y); + if (err) + break; + } + view->focussed = 0; log_view->focussed = 1; + log_view->mode = view->mode; + log_view->nlines = view->lines - begin_y; if (view_is_parent_view(view)) { err = view_close_child(view); if (err) @@ -7041,11 +7302,15 @@ input_ref_view(struct tog_view **new_view, struct tog_ s->first_displayed_entry = TAILQ_FIRST(&s->refs); break; case 'G': - case KEY_END: + case KEY_END: { + int eos = view->nlines - 1; + + if (view->mode == TOG_VIEW_SPLIT_HRZN) + --eos; /* border */ s->selected = 0; view->count = 0; re = TAILQ_LAST(&s->refs, tog_reflist_head); - for (n = 0; n < view->nlines - 1; n++) { + for (n = 0; n < eos; n++) { if (re == NULL) break; s->first_displayed_entry = re; @@ -7054,6 +7319,7 @@ input_ref_view(struct tog_view **new_view, struct tog_ if (n > 0) s->selected = n - 1; break; + } case 'k': case KEY_UP: case CTRL('p'): @@ -7090,7 +7356,7 @@ input_ref_view(struct tog_view **new_view, struct tog_ view->count = 0; break; } - ref_scroll_down(s, 1); + ref_scroll_down(view, 1); break; case CTRL('d'): case 'd': @@ -7110,7 +7376,7 @@ input_ref_view(struct tog_view **new_view, struct tog_ view->count = 0; break; } - ref_scroll_down(s, nscroll); + ref_scroll_down(view, nscroll); break; case CTRL('l'): view->count = 0; @@ -7243,7 +7509,113 @@ done: return error; } +/* + * If view was scrolled down to move the selected line into view when opening a + * horizontal split, scroll back up when closing the split/toggling fullscreen. + */ static void +offset_selection_up(struct tog_view *view) +{ + switch (view->type) { + case TOG_VIEW_BLAME: { + struct tog_blame_view_state *s = &view->state.blame; + if (s->first_displayed_line == 1) { + s->selected_line = MAX(s->selected_line - view->offset, + 1); + break; + } + if (s->first_displayed_line > view->offset) + s->first_displayed_line -= view->offset; + else + s->first_displayed_line = 1; + s->selected_line += view->offset; + break; + } + case TOG_VIEW_LOG: + log_scroll_up(&view->state.log, view->offset); + view->state.log.selected += view->offset; + break; + case TOG_VIEW_REF: + ref_scroll_up(&view->state.ref, view->offset); + view->state.ref.selected += view->offset; + break; + case TOG_VIEW_TREE: + tree_scroll_up(&view->state.tree, view->offset); + view->state.tree.selected += view->offset; + break; + default: + break; + } + + view->offset = 0; +} + +/* + * If the selected line is in the section of screen covered by the bottom split, + * scroll down offset lines to move it into view and index its new position. + */ +static const struct got_error * +offset_selection_down(struct tog_view *view) +{ + const struct got_error *err = NULL; + const struct got_error *(*scrolld)(struct tog_view *, int); + int *selected = NULL; + int header, offset; + + switch (view->type) { + case TOG_VIEW_BLAME: { + struct tog_blame_view_state *s = &view->state.blame; + header = 3; + scrolld = NULL; + if (s->selected_line > view->nlines - header) { + offset = abs(view->nlines - s->selected_line - header); + s->first_displayed_line += offset; + s->selected_line -= offset; + view->offset = offset; + } + break; + } + case TOG_VIEW_LOG: { + struct tog_log_view_state *s = &view->state.log; + scrolld = &log_scroll_down; + header = 3; + selected = &s->selected; + break; + } + case TOG_VIEW_REF: { + struct tog_ref_view_state *s = &view->state.ref; + scrolld = &ref_scroll_down; + header = 3; + selected = &s->selected; + break; + } + case TOG_VIEW_TREE: { + struct tog_tree_view_state *s = &view->state.tree; + scrolld = &tree_scroll_down; + header = 5; + selected = &s->selected; + break; + } + default: + selected = NULL; + scrolld = NULL; + header = 0; + break; + } + + if (selected && *selected > view->nlines - header) { + offset = abs(view->nlines - *selected - header); + view->offset = offset; + if (scrolld && offset) { + err = scrolld(view, offset); + *selected -= offset; + } + } + + return err; +} + +static void list_commands(FILE *fp) { size_t i;