diff options
Diffstat (limited to '')
-rw-r--r-- | src/window.c | 409 |
1 files changed, 294 insertions, 115 deletions
diff --git a/src/window.c b/src/window.c index 54e17be..9ffca77 100644 --- a/src/window.c +++ b/src/window.c @@ -17,8 +17,8 @@ static void frame_setheight(frame_T *curfrp, int height); static void frame_setwidth(frame_T *curfrp, int width); static void win_exchange(long); static void win_rotate(int, int); -static void win_totop(int size, int flags); static void win_equal_rec(win_T *next_curwin, int current, frame_T *topfr, int dir, int col, int row, int width, int height); +static void trigger_winnewpre(void); static void trigger_winclosed(win_T *win); static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp); static frame_T *win_altframe(win_T *win, tabpage_T *tp); @@ -53,8 +53,10 @@ static void win_goto_ver(int up, long count); static void win_goto_hor(int left, long count); static void frame_add_height(frame_T *frp, int n); static void last_status_rec(frame_T *fr, int statusline); +static void frame_flatten(frame_T *frp); +static void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr); -static void make_snapshot_rec(frame_T *fr, frame_T **frp); +static int make_snapshot_rec(frame_T *fr, frame_T **frp); static void clear_snapshot(tabpage_T *tp, int idx); static void clear_snapshot_rec(frame_T *fr); static int check_snapshot_rec(frame_T *sn, frame_T *fr); @@ -157,6 +159,37 @@ log_frame_layout(frame_T *frame) #endif /* + * Check if the current window is allowed to move to a different buffer. + * If the window has 'winfixbuf', this function will return FALSE. + */ + int +check_can_set_curbuf_disabled(void) +{ + if (curwin->w_p_wfb) + { + emsg(_(e_winfixbuf_cannot_go_to_buffer)); + return FALSE; + } + return TRUE; +} + +/* + * Check if the current window is allowed to move to a different buffer. + * If the window has 'winfixbuf', then forceit must be TRUE or this function + * will return FALSE. + */ + int +check_can_set_curbuf_forceit(int forceit) +{ + if (!forceit && curwin->w_p_wfb) + { + emsg(_(e_winfixbuf_cannot_go_to_buffer)); + return FALSE; + } + return TRUE; +} + +/* * Return the current window, unless in the cmdline window and "prevwin" is * set, then return "prevwin". */ @@ -493,9 +526,15 @@ newwindow: case 'H': case 'L': CHECK_CMDWIN; - win_totop((int)Prenum, - ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0) - | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT)); + if (ONE_WINDOW) + beep_flush(); + else + { + int dir = ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0) + | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT); + + (void)win_splitmove(curwin, (int)Prenum, dir); + } break; // make all windows the same width and/or height @@ -659,7 +698,7 @@ wingotofile: find_pattern_in_path(ptr, 0, len, TRUE, Prenum == 0 ? TRUE : FALSE, type, - Prenum1, ACTION_SPLIT, (linenr_T)1, (linenr_T)MAXLNUM); + Prenum1, ACTION_SPLIT, (linenr_T)1, (linenr_T)MAXLNUM, FALSE); vim_free(ptr); curwin->w_set_curswant = TRUE; break; @@ -857,18 +896,18 @@ cmd_with_count( } /* - * If "split_disallowed" is set give an error and return FAIL. - * Otherwise return OK. + * If "split_disallowed" is set, or "wp"'s buffer is closing, give an error and + * return FAIL. Otherwise return OK. */ - static int -check_split_disallowed(void) + int +check_split_disallowed(win_T *wp) { if (split_disallowed > 0) { emsg(_(e_cant_split_window_while_closing_another)); return FAIL; } - if (curwin->w_buffer->b_locked_split) + if (wp->w_buffer->b_locked_split) { emsg(_(e_cannot_split_window_when_closing_buffer)); return FAIL; @@ -897,7 +936,7 @@ win_split(int size, int flags) if (ERROR_IF_ANY_POPUP_WINDOW) return FAIL; - if (check_split_disallowed() == FAIL) + if (check_split_disallowed(curwin) == FAIL) return FAIL; // When the ":tab" modifier was used open a new tab page instead. @@ -919,13 +958,18 @@ win_split(int size, int flags) else clear_snapshot(curtab, SNAP_HELP_IDX); - return win_split_ins(size, flags, NULL, 0); + return win_split_ins(size, flags, NULL, 0, NULL); } /* * When "new_wp" is NULL: split the current window in two. * When "new_wp" is not NULL: insert this window at the far * top/left/right/bottom. + * When "to_flatten" is not NULL: flatten this frame before reorganising frames; + * remains unflattened on failure. + * + * On failure, if "new_wp" was not NULL, no changes will have been made to the + * window layout or sizes. * Return FAIL for failure, OK otherwise. */ int @@ -933,7 +977,8 @@ win_split_ins( int size, int flags, win_T *new_wp, - int dir) + int dir, + frame_T *to_flatten) { win_T *wp = new_wp; win_T *oldwin; @@ -955,6 +1000,9 @@ win_split_ins( // Do not redraw here, curwin->w_buffer may be invalid. ++RedrawingDisabled; + if (new_wp == NULL) + trigger_winnewpre(); + if (flags & WSP_TOP) oldwin = firstwin; else if (flags & WSP_BOT) @@ -965,7 +1013,7 @@ win_split_ins( // add a status line when p_ls == 1 and splitting the first window if (ONE_WINDOW && p_ls == 1 && oldwin->w_status_height == 0) { - if (VISIBLE_HEIGHT(oldwin) <= p_wmh && new_wp == NULL) + if (!(flags & WSP_FORCE_ROOM) && VISIBLE_HEIGHT(oldwin) <= p_wmh) { emsg(_(e_not_enough_room)); goto theend; @@ -1023,7 +1071,7 @@ win_split_ins( available = oldwin->w_frame->fr_width; needed += minwidth; } - if (available < needed && new_wp == NULL) + if (!(flags & WSP_FORCE_ROOM) && available < needed) { emsg(_(e_not_enough_room)); goto theend; @@ -1106,7 +1154,7 @@ win_split_ins( available = oldwin->w_frame->fr_height; needed += minheight; } - if (available < needed && new_wp == NULL) + if (!(flags & WSP_FORCE_ROOM) && available < needed) { emsg(_(e_not_enough_room)); goto theend; @@ -1205,6 +1253,10 @@ win_split_ins( win_init(wp, curwin, flags); } + // Going to reorganize frames now, make sure they're flat. + if (to_flatten != NULL) + frame_flatten(to_flatten); + /* * Reorganise the tree of frames to insert the new window. */ @@ -1433,7 +1485,7 @@ win_split_ins( /* * make the new window the current window */ - (void)win_enter_ext(wp, WEE_TRIGGER_NEW_AUTOCMDS + (void)win_enter_ext(wp, (new_wp == NULL ? WEE_TRIGGER_NEW_AUTOCMDS : 0) | WEE_TRIGGER_ENTER_AUTOCMDS | WEE_TRIGGER_LEAVE_AUTOCMDS); if (flags & WSP_VERT) p_wiw = i; @@ -1897,35 +1949,51 @@ win_rotate(int upwards, int count) } /* - * Move the current window to the very top/bottom/left/right of the screen. + * Move "wp" into a new split in a given direction, possibly relative to the + * current window. + * "wp" must be valid in the current tabpage. + * Returns FAIL for failure, OK otherwise. */ - static void -win_totop(int size, int flags) + int +win_splitmove(win_T *wp, int size, int flags) { int dir; - int height = curwin->w_height; + int height = wp->w_height; + frame_T *unflat_altfr; if (ONE_WINDOW) - { - beep_flush(); - return; - } - if (check_split_disallowed() == FAIL) - return; + return OK; // nothing to do + if (check_split_disallowed(wp) == FAIL) + return FAIL; - // Remove the window and frame from the tree of frames. - (void)winframe_remove(curwin, &dir, NULL); - win_remove(curwin, NULL); + // Remove the window and frame from the tree of frames. Don't flatten any + // frames yet so we can restore things if win_split_ins fails. + winframe_remove(wp, &dir, NULL, &unflat_altfr); + win_remove(wp, NULL); last_status(FALSE); // may need to remove last status line (void)win_comp_pos(); // recompute window positions - // Split a window on the desired side and put the window there. - (void)win_split_ins(size, flags, curwin, dir); - if (!(flags & WSP_VERT)) + // Split a window on the desired side and put "wp" there. + if (win_split_ins(size, flags, wp, dir, unflat_altfr) == FAIL) + { + // win_split_ins doesn't change sizes or layout if it fails to insert an + // existing window, so just undo winframe_remove. + winframe_restore(wp, dir, unflat_altfr); + win_append(wp->w_prev, wp); + return FAIL; + } + + // If splitting horizontally, try to preserve height. + // Note that win_split_ins autocommands may have immediately closed "wp"! + if (size == 0 && !(flags & WSP_VERT) && win_valid(wp)) { - win_setheight(height); + win_setheight_win(height, wp); if (p_ea) - win_equal(curwin, TRUE, 'v'); + { + // Equalize windows. Note that win_split_ins autocommands may have + // made a window other than "wp" current. + win_equal(curwin, curwin == wp, 'v'); + } } #if defined(FEAT_GUI) @@ -1933,6 +2001,7 @@ win_totop(int size, int flags) // scrollbars. Have to update them anyway. gui_may_update_scrollbars(); #endif + return OK; } /* @@ -2362,7 +2431,7 @@ leaving_window(win_T *win) // When leaving the window (or closing the window) was done from a // callback we need to break out of the Insert mode loop and restart Insert // mode when entering the window again. - if (State & MODE_INSERT) + if ((State & MODE_INSERT) && !stop_insert_mode) { stop_insert_mode = TRUE; if (win->w_buffer->b_prompt_insert == NUL) @@ -2485,7 +2554,7 @@ close_windows( * "aucmd_win[]"). * Returns FALSE if there is a window, possibly in another tab page. */ - static int + int last_window(void) { return (one_window() && first_tabpage->tp_next == NULL); @@ -2515,7 +2584,7 @@ one_window(void) /* * Close the possibly last window in a tab page. - * Returns TRUE when the window was closed already. + * Return FALSE if there are other windows and nothing is done, TRUE otherwise. */ static int close_last_window_tabpage( @@ -2653,7 +2722,7 @@ win_close(win_T *win, int free_buf) // and then close the window and the tab page to avoid that curwin and // curtab are invalid while we are freeing memory. if (close_last_window_tabpage(win, free_buf, prev_curtab)) - return FAIL; + return FAIL; // When closing the help window, try restoring a snapshot after closing // the window. Otherwise clear the snapshot, it's now invalid. @@ -2726,9 +2795,11 @@ win_close(win_T *win, int free_buf) win_close_buffer(win, free_buf ? DOBUF_UNLOAD : 0, TRUE); - if (only_one_window() && win_valid(win) && win->w_buffer == NULL - && (last_window() || curtab != prev_curtab - || close_last_window_tabpage(win, free_buf, prev_curtab))) + if (win_valid(win) && win->w_buffer == NULL +#if defined(FEAT_PROP_POPUP) + && !popup_is_popup(win) +#endif + && last_window()) { // Autocommands have closed all windows, quit now. Restore // curwin->w_buffer, otherwise writing viminfo may fail. @@ -2742,10 +2813,7 @@ win_close(win_T *win, int free_buf) && win->w_buffer == NULL) { // Need to close the window anyway, since the buffer is NULL. - // Don't trigger autocmds with a NULL buffer. - block_autocmds(); win_close_othertab(win, FALSE, prev_curtab); - unblock_autocmds(); return FAIL; } @@ -2887,6 +2955,14 @@ win_close(win_T *win, int free_buf) } static void +trigger_winnewpre(void) +{ + window_layout_lock(); + apply_autocmds(EVENT_WINNEWPRE, NULL, NULL, FALSE, NULL); + window_layout_unlock(); +} + + static void trigger_winclosed(win_T *win) { static int recursive = FALSE; @@ -3274,10 +3350,15 @@ win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) return; // window is already being closed // Trigger WinClosed just before starting to free window-related resources. - trigger_winclosed(win); - // autocmd may have freed the window already. - if (!win_valid_any_tab(win)) - return; + // If the buffer is NULL, it isn't safe to trigger autocommands, + // and win_close() should have already triggered WinClosed. + if (win->w_buffer != NULL) + { + trigger_winclosed(win); + // autocmd may have freed the window already. + if (!win_valid_any_tab(win)) + return; + } if (win->w_buffer != NULL) // Close the link to the buffer. @@ -3355,7 +3436,7 @@ win_free_mem( // Remove the window and its frame from the tree of frames. frp = win->w_frame; - wp = winframe_remove(win, dirp, tp); + wp = winframe_remove(win, dirp, tp, NULL); vim_free(frp); win_free(win, tp); @@ -3375,6 +3456,8 @@ win_free_all(void) // avoid an error for switching tabpage with the cmdline window open cmdwin_type = 0; + cmdwin_buf = NULL; + cmdwin_win = NULL; while (first_tabpage->tp_next != NULL) tabpage_close(TRUE); @@ -3403,11 +3486,14 @@ win_free_all(void) winframe_remove( win_T *win, int *dirp UNUSED, // set to 'v' or 'h' for direction if 'ea' - tabpage_T *tp) // tab page "win" is in, NULL for current + tabpage_T *tp, // tab page "win" is in, NULL for current + frame_T **unflat_altfr) // if not NULL, set to pointer of frame that got + // the space, and it is not flattened { frame_T *frp, *frp2, *frp3; frame_T *frp_close = win->w_frame; win_T *wp; + int row, col; /* * If there is only one window there is nothing to remove. @@ -3415,6 +3501,12 @@ winframe_remove( if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) return NULL; + // Save the position of the containing frame (which will also contain the + // altframe) before we remove anything, to recompute window positions later. + wp = frame2win(frp_close->fr_parent); + row = wp->w_winrow; + col = wp->w_wincol; + /* * Remove the window from its frame. */ @@ -3458,7 +3550,7 @@ winframe_remove( } } frame_new_height(frp2, frp2->fr_height + frp_close->fr_height, - frp2 == frp_close->fr_next ? TRUE : FALSE, FALSE); + frp2 == frp_close->fr_next, FALSE); *dirp = 'v'; } else @@ -3495,64 +3587,127 @@ winframe_remove( } } frame_new_width(frp2, frp2->fr_width + frp_close->fr_width, - frp2 == frp_close->fr_next ? TRUE : FALSE, FALSE); + frp2 == frp_close->fr_next, FALSE); *dirp = 'h'; } - // If rows/columns go to a window below/right its positions need to be - // updated. Can only be done after the sizes have been updated. - if (frp2 == frp_close->fr_next) - { - int row = win->w_winrow; - int col = win->w_wincol; + // If the altframe wasn't adjacent and left/above, resizing it will have + // changed window positions within the parent frame. Recompute them. + if (frp2 != frp_close->fr_prev) + frame_comp_pos(frp_close->fr_parent, &row, &col); - frame_comp_pos(frp2, &row, &col); - } + if (unflat_altfr == NULL) + frame_flatten(frp2); + else + *unflat_altfr = frp2; - if (frp2->fr_next == NULL && frp2->fr_prev == NULL) - { - // There is no other frame in this list, move its info to the parent - // and remove it. - frp2->fr_parent->fr_layout = frp2->fr_layout; - frp2->fr_parent->fr_child = frp2->fr_child; - FOR_ALL_FRAMES(frp, frp2->fr_child) - frp->fr_parent = frp2->fr_parent; - frp2->fr_parent->fr_win = frp2->fr_win; - if (frp2->fr_win != NULL) - frp2->fr_win->w_frame = frp2->fr_parent; - frp = frp2->fr_parent; - if (topframe->fr_child == frp2) - topframe->fr_child = frp; - vim_free(frp2); + return wp; +} + +/* + * Flatten "frp" into its parent frame if it's the only child, also merging its + * list with the grandparent if they share the same layout. + * Frees "frp" if flattened; also "frp->fr_parent" if it has the same layout. + */ + static void +frame_flatten(frame_T *frp) +{ + frame_T *frp2, *frp3; + + if (frp->fr_next != NULL || frp->fr_prev != NULL) + return; - frp2 = frp->fr_parent; - if (frp2 != NULL && frp2->fr_layout == frp->fr_layout) + // There is no other frame in this list, move its info to the parent + // and remove it. + frp->fr_parent->fr_layout = frp->fr_layout; + frp->fr_parent->fr_child = frp->fr_child; + FOR_ALL_FRAMES(frp2, frp->fr_child) + frp2->fr_parent = frp->fr_parent; + frp->fr_parent->fr_win = frp->fr_win; + if (frp->fr_win != NULL) + frp->fr_win->w_frame = frp->fr_parent; + frp2 = frp->fr_parent; + if (topframe->fr_child == frp) + topframe->fr_child = frp2; + vim_free(frp); + + frp = frp2->fr_parent; + if (frp != NULL && frp->fr_layout == frp2->fr_layout) + { + // The frame above the parent has the same layout, have to merge + // the frames into this list. + if (frp->fr_child == frp2) + frp->fr_child = frp2->fr_child; + frp2->fr_child->fr_prev = frp2->fr_prev; + if (frp2->fr_prev != NULL) + frp2->fr_prev->fr_next = frp2->fr_child; + for (frp3 = frp2->fr_child; ; frp3 = frp3->fr_next) { - // The frame above the parent has the same layout, have to merge - // the frames into this list. - if (frp2->fr_child == frp) - frp2->fr_child = frp->fr_child; - frp->fr_child->fr_prev = frp->fr_prev; - if (frp->fr_prev != NULL) - frp->fr_prev->fr_next = frp->fr_child; - for (frp3 = frp->fr_child; ; frp3 = frp3->fr_next) + frp3->fr_parent = frp; + if (frp3->fr_next == NULL) { - frp3->fr_parent = frp2; - if (frp3->fr_next == NULL) - { - frp3->fr_next = frp->fr_next; - if (frp->fr_next != NULL) - frp->fr_next->fr_prev = frp3; - break; - } + frp3->fr_next = frp2->fr_next; + if (frp2->fr_next != NULL) + frp2->fr_next->fr_prev = frp3; + break; } - if (topframe->fr_child == frp) - topframe->fr_child = frp2; - vim_free(frp); } + if (topframe->fr_child == frp2) + topframe->fr_child = frp; + vim_free(frp2); } +} - return wp; +/* + * Undo changes from a prior call to winframe_remove, also restoring lost + * vertical separators and statuslines, and changed window positions for + * windows within "unflat_altfr". + * Caller must ensure no other changes were made to the layout or window sizes! + */ + static void +winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) +{ + frame_T *frp = wp->w_frame; + + // Put "wp"'s frame back where it was. + if (frp->fr_prev != NULL) + frame_append(frp->fr_prev, frp); + else + frame_insert(frp->fr_next, frp); + + // Vertical separators to the left may have been lost. Restore them. + if (wp->w_vsep_width == 0 + && frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) + frame_add_vsep(frp->fr_prev); + + // Statuslines above may have been lost. Restore them. + if (wp->w_status_height == 0 + && frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) + frame_add_statusline(frp->fr_prev); + + // Restore the lost room that was redistributed to the altframe. Also + // adjusts window sizes to fit restored statuslines/separators, if needed. + if (dir == 'v') + { + frame_new_height(unflat_altfr, unflat_altfr->fr_height - frp->fr_height, + unflat_altfr == frp->fr_next, FALSE); + } + else if (dir == 'h') + { + frame_new_width(unflat_altfr, unflat_altfr->fr_width - frp->fr_width, + unflat_altfr == frp->fr_next, FALSE); + } + + // Recompute window positions within the parent frame to restore them. + // Positions were unchanged if the altframe was adjacent and left/above. + if (unflat_altfr != frp->fr_prev) + { + win_T *topleft = frame2win(frp->fr_parent); + int row = topleft->w_winrow; + int col = topleft->w_wincol; + + frame_comp_pos(frp->fr_parent, &row, &col); + } } /* @@ -4475,6 +4630,9 @@ win_new_tabpage(int after) newtp->tp_localdir = (tp->tp_localdir == NULL) ? NULL : vim_strsave(tp->tp_localdir); + + trigger_winnewpre(); + // Create a new empty window. if (win_alloc_firstwin(tp->tp_curwin) == OK) { @@ -5381,15 +5539,11 @@ win_enter_ext(win_T *wp, int flags) // may have to copy the buffer options when 'cpo' contains 'S' if (wp->w_buffer != curbuf) buf_copy_options(wp->w_buffer, BCO_ENTER | BCO_NOHELP); - if (curwin_invalid == 0) { prevwin = curwin; // remember for CTRL-W p curwin->w_redr_status = TRUE; } - else if (wp == prevwin) - prevwin = NULL; // don't want it to be the new curwin - curwin = wp; curbuf = wp->w_buffer; check_cursor(); @@ -7131,17 +7285,17 @@ command_height(void) // If the space for the command line is already more than 'cmdheight' there // is nothing to do (window size must have decreased). + // Note: this makes curtab->tp_ch_used unreliable if (p_ch > old_p_ch && cmdline_row <= Rows - p_ch) return; // Update cmdline_row to what it should be: just below the last window. cmdline_row = topframe->fr_height + tabline_height(); - // If cmdline_row is smaller than what it is supposed to be for 'cmdheight' - // then set old_p_ch to what it would be, so that the windows get resized + // old_p_ch may be unreliable, because of the early return above, so + // set old_p_ch to what it would be, so that the windows get resized // properly for the new value. - if (cmdline_row < Rows - p_ch) - old_p_ch = Rows - cmdline_row; + old_p_ch = Rows - cmdline_row; // Find bottom frame with width of screen. frp = lastwin->w_frame; @@ -7482,29 +7636,42 @@ reset_lnums(void) /* * Create a snapshot of the current frame sizes. * "idx" is SNAP_HELP_IDX or SNAP_AUCMD_IDX. + * Return FAIL if out of memory, OK otherwise. */ - void + int make_snapshot(int idx) { clear_snapshot(curtab, idx); - make_snapshot_rec(topframe, &curtab->tp_snapshot[idx]); + if (make_snapshot_rec(topframe, &curtab->tp_snapshot[idx]) == FAIL) + { + clear_snapshot(curtab, idx); + return FAIL; + } + return OK; } - static void + static int make_snapshot_rec(frame_T *fr, frame_T **frp) { *frp = ALLOC_CLEAR_ONE(frame_T); if (*frp == NULL) - return; + return FAIL; (*frp)->fr_layout = fr->fr_layout; (*frp)->fr_width = fr->fr_width; (*frp)->fr_height = fr->fr_height; if (fr->fr_next != NULL) - make_snapshot_rec(fr->fr_next, &((*frp)->fr_next)); + { + if (make_snapshot_rec(fr->fr_next, &((*frp)->fr_next)) == FAIL) + return FAIL; + } if (fr->fr_child != NULL) - make_snapshot_rec(fr->fr_child, &((*frp)->fr_child)); + { + if (make_snapshot_rec(fr->fr_child, &((*frp)->fr_child)) == FAIL) + return FAIL; + } if (fr->fr_layout == FR_LEAF && fr->fr_win == curwin) (*frp)->fr_win = curwin; + return OK; } /* @@ -7737,9 +7904,15 @@ frame_check_width(frame_T *topfrp, int width) * Simple int comparison function for use with qsort() */ static int -int_cmp(const void *a, const void *b) +int_cmp(const void *pa, const void *pb) { - return *(const int *)a - *(const int *)b; + const int a = *(const int *)pa; + const int b = *(const int *)pb; + if (a > b) + return 1; + if (a < b) + return -1; + return 0; } /* @@ -7812,3 +7985,9 @@ skip: return NULL; // no error } #endif + + int +get_last_winid(void) +{ + return last_win_id; +} |