From: Mark Jamsek Subject: Re: tog: horizontal split To: gameoftrees@openbsd.org Date: Thu, 30 Jun 2022 00:17:59 +1000 On 22-06-26 11:08pm, Mark Jamsek wrote: > The below diff adds support for horizontal splits in tog. > > There are a couple bugs that need squishing: > - resizing with an open hsplit sometimes kills the border and there > seems to be an OB1 that makes the top split misrender a line > - the double-width bug in the log view has returned where we overwrite > the vertical border by one column; strangely enough if you open the > commit in ja.git in a vsplit then press F, tab, tab, F, it renders > correctly > Thanks to Omar, the above bugs (plus the double-width char bug in vsplit that the diff broke) have been squished. In short, the main problem was that we were trying to account for the border when initialising the split, which left us a line short when later reszing because we'd deducted the line. The fix was to account for the border when scrolling rather than try to account for it in sizing. This also makes the diff a lot cleaner imo. op's and stsp's suggestions have been applied and the split config removed. The scale is set with HSPLIT_SCALE. To test hsplit: `$ TOG_VIEW_SPLIT_MODE=h tog` Also in the diff, echo count prefix to the bottom left of the _terminal_ screen irrespective of the split or active window (rather than the bottom left of the top horizontal or right vertical split. I haven't changed the location of the search "/" prompt except for in hsplits; I thought I'd see what the consensus is first. At present, we draw the search prompt in the bottom left of the active view when in a vsplit. So we can either continue this behaviour with hsplits, or always echo to the absolute bottom left of the screen. I wanted to get a feel for what they both look like, but I don't feel strongly one way or the other. I suppose it feels a little surprsing to have it in different locations but that's subjective. diff refs/heads/main refs/heads/dev/hsplit blob - 3ec7da1db803f36693a814f77125861f3ac2095e blob + 2726f4aea0dc4d15a8e2f909279deb2d9986e17c --- 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,28 +778,59 @@ 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 && view_is_splitscreen(view->child)) { + int hs = view->child->begin_y; + 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); @@ -790,6 +844,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; @@ -849,6 +941,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; @@ -862,12 +955,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) @@ -897,19 +995,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. @@ -923,7 +1031,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; } @@ -1010,11 +1118,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; @@ -1043,13 +1171,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; @@ -1078,24 +1217,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) { @@ -1152,7 +1273,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; @@ -1931,7 +2053,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); @@ -2006,6 +2128,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; @@ -2030,10 +2165,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) @@ -2041,11 +2177,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) { @@ -2053,7 +2194,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"); @@ -2618,7 +2759,132 @@ show_log_view(struct tog_view *view) return draw_commits(view); } +static void +log_move_cursor_up(struct tog_view *view, int page, int home) +{ + struct tog_log_view_state *s = &view->state.log; + + if (s->selected_entry->idx == 0) + view->count = 0; + if (s->first_displayed_entry == NULL) + return; + + if ((page && TAILQ_FIRST(&s->commits.head) == s->first_displayed_entry) + || home) + s->selected = home ? 0 : MAX(0, s->selected - page - 1); + + if (!page && !home && s->selected > 0) + --s->selected; + else + log_scroll_up(s, home ? s->commits.ncommits : MAX(page, 1)); + + select_commit(s); + return; +} + static const struct got_error * +log_move_cursor_down(struct tog_view *view, int page) +{ + struct tog_log_view_state *s = &view->state.log; + struct commit_queue_entry *first; + const struct got_error *err = NULL; + + first = s->first_displayed_entry; + if (first == NULL) { + view->count = 0; + return NULL; + } + + if (s->thread_args.log_complete && + s->selected_entry->idx >= s->commits.ncommits - 1) + return NULL; + + 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 + err = log_scroll_down(view, 1); + } else if (s->thread_args.log_complete) { + if (s->last_displayed_entry->idx == s->commits.ncommits - 1) + s->selected += MIN(s->last_displayed_entry->idx - + s->selected_entry->idx, page + 1); + else + err = log_scroll_down(view, MIN(page, + s->commits.ncommits - s->selected_entry->idx - 1)); + s->selected = MIN(view->nlines - 2, s->commits.ncommits - 1); + } else { + err = log_scroll_down(view, page); + if (err) + return err; + if (first == s->first_displayed_entry && s->selected < + MIN(view->nlines - 2, s->commits.ncommits - 1)) { + s->selected = MIN(s->commits.ncommits - 1, 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 && + s->selected_entry->idx == s->commits.ncommits - 1) + view->count = 0; + + 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; @@ -2626,19 +2892,16 @@ 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) s->thread_args.load_all = 0; else if (s->thread_args.log_complete) { s->thread_args.load_all = 0; - log_scroll_down(view, s->commits.ncommits); - s->selected = MIN(view->nlines - 2, - s->commits.ncommits - 1); - select_commit(s); + err = log_move_cursor_down(view, s->commits.ncommits); } - return NULL; + return err; } switch (ch) { @@ -2670,21 +2933,11 @@ input_log_view(struct tog_view **new_view, struct tog_ case '<': case ',': case CTRL('p'): - if (s->selected_entry->idx == 0) - view->count = 0; - if (s->first_displayed_entry == NULL) - break; - if (s->selected > 0) - s->selected--; - else - log_scroll_up(s, 1); - select_commit(s); + log_move_cursor_up(view, 0, 0); break; case 'g': case KEY_HOME: - s->selected = 0; - s->first_displayed_entry = TAILQ_FIRST(&s->commits.head); - select_commit(s); + log_move_cursor_up(view, 0, 1); view->count = 0; break; case CTRL('u'): @@ -2694,35 +2947,14 @@ input_log_view(struct tog_view **new_view, struct tog_ case KEY_PPAGE: case CTRL('b'): case 'b': - if (s->first_displayed_entry == NULL) - break; - if (TAILQ_FIRST(&s->commits.head) == s->first_displayed_entry) - s->selected = MAX(0, s->selected - nscroll - 1); - else - log_scroll_up(s, nscroll); - select_commit(s); - if (s->selected_entry->idx == 0) - view->count = 0; + log_move_cursor_up(view, nscroll, 0); break; case 'j': case KEY_DOWN: case '>': case '.': case CTRL('n'): - if (s->first_displayed_entry == NULL) - break; - if (s->selected < MIN(view->nlines - 2, - s->commits.ncommits - 1)) - s->selected++; - else { - err = log_scroll_down(view, 1); - if (err) - break; - } - select_commit(s); - if (s->thread_args.log_complete && - s->selected_entry->idx == s->commits.ncommits - 1) - view->count = 0; + err = log_move_cursor_down(view, 0); break; case 'G': case KEY_END: { @@ -2754,29 +2986,9 @@ input_log_view(struct tog_view **new_view, struct tog_ case KEY_NPAGE: case CTRL('f'): case 'f': - case ' ': { - struct commit_queue_entry *first; - first = s->first_displayed_entry; - if (first == NULL) { - view->count = 0; - break; - } - err = log_scroll_down(view, nscroll); - if (err) - break; - if (first == s->first_displayed_entry && - s->selected < MIN(view->nlines - 2, - s->commits.ncommits - 1)) { - /* can't scroll further down */ - s->selected += MIN(s->last_displayed_entry->idx - - s->selected_entry->idx, nscroll + 1); - } - select_commit(s); - if (s->thread_args.log_complete && - s->selected_entry->idx == s->commits.ncommits - 1) - view->count = 0; + case ' ': + err = log_move_cursor_down(view, nscroll); break; - } case KEY_RESIZE: if (s->selected > view->nlines - 2) s->selected = view->nlines - 2; @@ -2791,19 +3003,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) @@ -2815,6 +3040,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) @@ -2899,7 +3125,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; @@ -3425,7 +3651,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) { @@ -4627,7 +4853,7 @@ draw_blame(struct tog_view *view) free(line); s->last_displayed_line = lineno; - view_vborder(view); + view_border(view); return NULL; } @@ -5088,7 +5314,7 @@ show_blame_view(struct tog_view *view) err = draw_blame(view); - view_vborder(view); + view_border(view); return err; } @@ -5098,8 +5324,13 @@ 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': view->x = 0; @@ -5132,13 +5363,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; @@ -5175,12 +5405,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; @@ -5290,11 +5518,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"); @@ -5307,8 +5535,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) @@ -5518,6 +5754,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); @@ -5685,9 +5924,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; @@ -5698,13 +5938,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 * @@ -5754,7 +5997,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) { @@ -5768,7 +6011,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; @@ -6033,7 +6276,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; } @@ -6108,11 +6351,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; @@ -6126,6 +6373,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'): @@ -6173,7 +6421,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': @@ -6193,7 +6441,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': @@ -6215,6 +6463,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))) { @@ -6232,17 +6485,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) @@ -6590,7 +6854,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; @@ -6607,7 +6871,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; @@ -6642,9 +6906,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; @@ -6654,13 +6919,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 * @@ -6762,6 +7030,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; @@ -6893,7 +7164,7 @@ show_ref_view(struct tog_view *view) re = TAILQ_NEXT(re, entry); } - view_vborder(view); + view_border(view); return err; } @@ -6939,7 +7210,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': @@ -6972,11 +7243,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) @@ -7018,11 +7301,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; @@ -7031,6 +7318,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'): @@ -7067,7 +7355,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': @@ -7087,7 +7375,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; @@ -7220,7 +7508,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; -- Mark Jamsek GPG: F2FF 13DE 6A06 C471 CA80 E6E2 2930 DC66 86EE CF68