diff options
author | Bram Moolenaar <Bram@vim.org> | 2004-06-13 20:20:40 +0000 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2004-06-13 20:20:40 +0000 |
commit | 071d4279d6ab81b7187b48f3a0fc61e587b6db6c (patch) | |
tree | 221cbe3c40e043163c06f61c52a7ba2eb41e12ce /src/fold.c | |
parent | b4210b3bc14e2918f153a7307530fbe6eba659e1 (diff) |
updated for version 7.0001v7.0001
Diffstat (limited to 'src/fold.c')
-rw-r--r-- | src/fold.c | 3250 |
1 files changed, 3250 insertions, 0 deletions
diff --git a/src/fold.c b/src/fold.c new file mode 100644 index 0000000000..ceac00bbf6 --- /dev/null +++ b/src/fold.c @@ -0,0 +1,3250 @@ +/* vim:set ts=8 sts=4 sw=4: + * vim600:fdm=marker fdl=1 fdc=3: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * fold.c: code for folding + */ + +#include "vim.h" + +#if defined(FEAT_FOLDING) || defined(PROTO) + +/* local declarations. {{{1 */ +/* typedef fold_T {{{2 */ +/* + * The toplevel folds for each window are stored in the w_folds growarray. + * Each toplevel fold can contain an array of second level folds in the + * fd_nested growarray. + * The info stored in both growarrays is the same: An array of fold_T. + */ +typedef struct +{ + linenr_T fd_top; /* first line of fold; for nested fold + * relative to parent */ + linenr_T fd_len; /* number of lines in the fold */ + garray_T fd_nested; /* array of nested folds */ + char fd_flags; /* see below */ + char fd_small; /* TRUE, FALSE or MAYBE: fold smaller than + 'foldminlines'; MAYBE applies to nested + folds too */ +} fold_T; + +#define FD_OPEN 0 /* fold is open (nested ones can be closed) */ +#define FD_CLOSED 1 /* fold is closed */ +#define FD_LEVEL 2 /* depends on 'foldlevel' (nested folds too) */ + +#define MAX_LEVEL 20 /* maximum fold depth */ + +/* static functions {{{2 */ +static void newFoldLevelWin __ARGS((win_T *wp)); +static int checkCloseRec __ARGS((garray_T *gap, linenr_T lnum, int level)); +static int foldFind __ARGS((garray_T *gap, linenr_T lnum, fold_T **fpp)); +static int foldLevelWin __ARGS((win_T *wp, linenr_T lnum)); +static void checkupdate __ARGS((win_T *wp)); +static void setFoldRepeat __ARGS((linenr_T lnum, long count, int open)); +static linenr_T setManualFold __ARGS((linenr_T lnum, int opening, int recurse, int *donep)); +static linenr_T setManualFoldWin __ARGS((win_T *wp, linenr_T lnum, int opening, int recurse, int *donep)); +static void foldOpenNested __ARGS((fold_T *fpr)); +static void deleteFoldEntry __ARGS((garray_T *gap, int idx, int recursive)); +static void foldMarkAdjustRecurse __ARGS((garray_T *gap, linenr_T line1, linenr_T line2, long amount, long amount_after)); +static int getDeepestNestingRecurse __ARGS((garray_T *gap)); +static int check_closed __ARGS((win_T *win, fold_T *fp, int *use_levelp, int level, int *maybe_smallp, linenr_T lnum_off)); +static void checkSmall __ARGS((win_T *wp, fold_T *fp, linenr_T lnum_off)); +static void setSmallMaybe __ARGS((garray_T *gap)); +static void foldCreateMarkers __ARGS((linenr_T start, linenr_T end)); +static void foldAddMarker __ARGS((linenr_T lnum, char_u *marker, int markerlen)); +static void deleteFoldMarkers __ARGS((fold_T *fp, int recursive, linenr_T lnum_off)); +static void foldDelMarker __ARGS((linenr_T lnum, char_u *marker, int markerlen)); +static void foldUpdateIEMS __ARGS((win_T *wp, linenr_T top, linenr_T bot)); +static void parseMarker __ARGS((win_T *wp)); + +static char *e_nofold = N_("E490: No fold found"); + +/* + * While updating the folds lines between invalid_top and invalid_bot have an + * undefined fold level. Only used for the window currently being updated. + */ +static linenr_T invalid_top = (linenr_T)0; +static linenr_T invalid_bot = (linenr_T)0; + +/* + * When using 'foldexpr' we sometimes get the level of the next line, which + * calls foldlevel() to get the level of the current line, which hasn't been + * stored yet. To get around this chicken-egg problem the level of the + * previous line is stored here when available. prev_lnum is zero when the + * level is not available. + */ +static linenr_T prev_lnum = 0; +static int prev_lnum_lvl = -1; + +/* Flags used for "done" argument of setManualFold. */ +#define DONE_NOTHING 0 +#define DONE_ACTION 1 /* did close or open a fold */ +#define DONE_FOLD 2 /* did find a fold */ + +static int foldstartmarkerlen; +static char_u *foldendmarker; +static int foldendmarkerlen; + +/* Exported folding functions. {{{1 */ +/* copyFoldingState() {{{2 */ +#if defined(FEAT_WINDOWS) || defined(PROTO) +/* + * Copy that folding state from window "wp_from" to window "wp_to". + */ + void +copyFoldingState(wp_from, wp_to) + win_T *wp_from; + win_T *wp_to; +{ + wp_to->w_fold_manual = wp_from->w_fold_manual; + wp_to->w_foldinvalid = wp_from->w_foldinvalid; + cloneFoldGrowArray(&wp_from->w_folds, &wp_to->w_folds); +} +#endif + +/* hasAnyFolding() {{{2 */ +/* + * Return TRUE if there may be folded lines in the current window. + */ + int +hasAnyFolding(win) + win_T *win; +{ + /* very simple now, but can become more complex later */ + return (win->w_p_fen + && (!foldmethodIsManual(win) || win->w_folds.ga_len > 0)); +} + +/* hasFolding() {{{2 */ +/* + * Return TRUE if line "lnum" in the current window is part of a closed + * fold. + * When returning TRUE, *firstp and *lastp are set to the first and last + * lnum of the sequence of folded lines (skipped when NULL). + */ + int +hasFolding(lnum, firstp, lastp) + linenr_T lnum; + linenr_T *firstp; + linenr_T *lastp; +{ + return hasFoldingWin(curwin, lnum, firstp, lastp, TRUE, NULL); +} + +/* hasFoldingWin() {{{2 */ + int +hasFoldingWin(win, lnum, firstp, lastp, cache, infop) + win_T *win; + linenr_T lnum; + linenr_T *firstp; + linenr_T *lastp; + int cache; /* when TRUE: use cached values of window */ + foldinfo_T *infop; /* where to store fold info */ +{ + int had_folded = FALSE; + linenr_T first = 0; + linenr_T last = 0; + linenr_T lnum_rel = lnum; + int x; + fold_T *fp; + int level = 0; + int use_level = FALSE; + int maybe_small = FALSE; + garray_T *gap; + int low_level = 0;; + + checkupdate(win); + /* + * Return quickly when there is no folding at all in this window. + */ + if (!hasAnyFolding(win)) + { + if (infop != NULL) + infop->fi_level = 0; + return FALSE; + } + + if (cache) + { + /* + * First look in cached info for displayed lines. This is probably + * the fastest, but it can only be used if the entry is still valid. + */ + x = find_wl_entry(win, lnum); + if (x >= 0) + { + first = win->w_lines[x].wl_lnum; + last = win->w_lines[x].wl_lastlnum; + had_folded = win->w_lines[x].wl_folded; + } + } + + if (first == 0) + { + /* + * Recursively search for a fold that contains "lnum". + */ + gap = &win->w_folds; + for (;;) + { + if (!foldFind(gap, lnum_rel, &fp)) + break; + + /* Remember lowest level of fold that starts in "lnum". */ + if (lnum_rel == fp->fd_top && low_level == 0) + low_level = level + 1; + + first += fp->fd_top; + last += fp->fd_top; + + /* is this fold closed? */ + had_folded = check_closed(win, fp, &use_level, level, + &maybe_small, lnum - lnum_rel); + if (had_folded) + { + /* Fold closed: Set last and quit loop. */ + last += fp->fd_len - 1; + break; + } + + /* Fold found, but it's open: Check nested folds. Line number is + * relative to containing fold. */ + gap = &fp->fd_nested; + lnum_rel -= fp->fd_top; + ++level; + } + } + + if (!had_folded) + { + if (infop != NULL) + { + infop->fi_level = level; + infop->fi_lnum = lnum - lnum_rel; + infop->fi_low_level = low_level == 0 ? level : low_level; + } + return FALSE; + } + + if (lastp != NULL) + *lastp = last; + if (firstp != NULL) + *firstp = first; + if (infop != NULL) + { + infop->fi_level = level + 1; + infop->fi_lnum = first; + infop->fi_low_level = low_level == 0 ? level + 1 : low_level; + } + return TRUE; +} + +/* foldLevel() {{{2 */ +/* + * Return fold level at line number "lnum" in the current window. + */ + int +foldLevel(lnum) + linenr_T lnum; +{ + /* While updating the folds lines between invalid_top and invalid_bot have + * an undefined fold level. Otherwise update the folds first. */ + if (invalid_top == (linenr_T)0) + checkupdate(curwin); + else if (lnum == prev_lnum && prev_lnum_lvl >= 0) + return prev_lnum_lvl; + else if (lnum >= invalid_top && lnum <= invalid_bot) + return -1; + + /* Return quickly when there is no folding at all in this window. */ + if (!hasAnyFolding(curwin)) + return 0; + + return foldLevelWin(curwin, lnum); +} + +/* lineFolded() {{{2 */ +/* + * Low level function to check if a line is folded. Doesn't use any caching. + * Return TRUE if line is folded. + * Return FALSE if line is not folded. + * Return MAYBE if the line is folded when next to a folded line. + */ + int +lineFolded(win, lnum) + win_T *win; + linenr_T lnum; +{ + return foldedCount(win, lnum, NULL) != 0; +} + +/* foldedCount() {{{2 */ +/* + * Count the number of lines that are folded at line number "lnum". + * Normally "lnum" is the first line of a possible fold, and the returned + * number is the number of lines in the fold. + * Doesn't use caching from the displayed window. + * Returns number of folded lines from "lnum", or 0 if line is not folded. + * When "infop" is not NULL, fills *infop with the fold level info. + */ + long +foldedCount(win, lnum, infop) + win_T *win; + linenr_T lnum; + foldinfo_T *infop; +{ + linenr_T last; + + if (hasFoldingWin(win, lnum, NULL, &last, FALSE, infop)) + return (long)(last - lnum + 1); + return 0; +} + +/* foldmethodIsManual() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "manual" + */ + int +foldmethodIsManual(wp) + win_T *wp; +{ + return (wp->w_p_fdm[3] == 'u'); +} + +/* foldmethodIsIndent() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "indent" + */ + int +foldmethodIsIndent(wp) + win_T *wp; +{ + return (wp->w_p_fdm[0] == 'i'); +} + +/* foldmethodIsExpr() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "expr" + */ + int +foldmethodIsExpr(wp) + win_T *wp; +{ + return (wp->w_p_fdm[1] == 'x'); +} + +/* foldmethodIsMarker() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "marker" + */ + int +foldmethodIsMarker(wp) + win_T *wp; +{ + return (wp->w_p_fdm[2] == 'r'); +} + +/* foldmethodIsSyntax() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "syntax" + */ + int +foldmethodIsSyntax(wp) + win_T *wp; +{ + return (wp->w_p_fdm[0] == 's'); +} + +/* foldmethodIsDiff() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "diff" + */ + int +foldmethodIsDiff(wp) + win_T *wp; +{ + return (wp->w_p_fdm[0] == 'd'); +} + +/* closeFold() {{{2 */ +/* + * Close fold for current window at line "lnum". + * Repeat "count" times. + */ + void +closeFold(lnum, count) + linenr_T lnum; + long count; +{ + setFoldRepeat(lnum, count, FALSE); +} + +/* closeFoldRecurse() {{{2 */ +/* + * Close fold for current window at line "lnum" recursively. + */ + void +closeFoldRecurse(lnum) + linenr_T lnum; +{ + (void)setManualFold(lnum, FALSE, TRUE, NULL); +} + +/* opFoldRange() {{{2 */ +/* + * Open or Close folds for current window in lines "first" to "last". + * Used for "zo", "zO", "zc" and "zC" in Visual mode. + */ + void +opFoldRange(first, last, opening, recurse, had_visual) + linenr_T first; + linenr_T last; + int opening; /* TRUE to open, FALSE to close */ + int recurse; /* TRUE to do it recursively */ + int had_visual; /* TRUE when Visual selection used */ +{ + int done = DONE_NOTHING; /* avoid error messages */ + linenr_T lnum; + linenr_T lnum_next; + + for (lnum = first; lnum <= last; lnum = lnum_next + 1) + { + lnum_next = lnum; + /* Opening one level only: next fold to open is after the one going to + * be opened. */ + if (opening && !recurse) + (void)hasFolding(lnum, NULL, &lnum_next); + (void)setManualFold(lnum, opening, recurse, &done); + /* Closing one level only: next line to close a fold is after just + * closed fold. */ + if (!opening && !recurse) + (void)hasFolding(lnum, NULL, &lnum_next); + } + if (done == DONE_NOTHING) + EMSG(_(e_nofold)); +#ifdef FEAT_VISUAL + /* Force a redraw to remove the Visual highlighting. */ + if (had_visual) + redraw_curbuf_later(INVERTED); +#endif +} + +/* openFold() {{{2 */ +/* + * Open fold for current window at line "lnum". + * Repeat "count" times. + */ + void +openFold(lnum, count) + linenr_T lnum; + long count; +{ + setFoldRepeat(lnum, count, TRUE); +} + +/* openFoldRecurse() {{{2 */ +/* + * Open fold for current window at line "lnum" recursively. + */ + void +openFoldRecurse(lnum) + linenr_T lnum; +{ + (void)setManualFold(lnum, TRUE, TRUE, NULL); +} + +/* foldOpenCursor() {{{2 */ +/* + * Open folds until the cursor line is not in a closed fold. + */ + void +foldOpenCursor() +{ + int done; + + checkupdate(curwin); + if (hasAnyFolding(curwin)) + for (;;) + { + done = DONE_NOTHING; + (void)setManualFold(curwin->w_cursor.lnum, TRUE, FALSE, &done); + if (!(done & DONE_ACTION)) + break; + } +} + +/* newFoldLevel() {{{2 */ +/* + * Set new foldlevel for current window. + */ + void +newFoldLevel() +{ + newFoldLevelWin(curwin); + +#ifdef FEAT_DIFF + if (foldmethodIsDiff(curwin) && curwin->w_p_scb) + { + win_T *wp; + + /* + * Set the same foldlevel in other windows in diff mode. + */ + FOR_ALL_WINDOWS(wp) + { + if (wp != curwin && foldmethodIsDiff(wp) && wp->w_p_scb) + { + wp->w_p_fdl = curwin->w_p_fdl; + newFoldLevelWin(wp); + } + } + } +#endif +} + + static void +newFoldLevelWin(wp) + win_T *wp; +{ + fold_T *fp; + int i; + + checkupdate(wp); + if (wp->w_fold_manual) + { + /* Set all flags for the first level of folds to FD_LEVEL. Following + * manual open/close will then change the flags to FD_OPEN or + * FD_CLOSED for those folds that don't use 'foldlevel'. */ + fp = (fold_T *)wp->w_folds.ga_data; + for (i = 0; i < wp->w_folds.ga_len; ++i) + fp[i].fd_flags = FD_LEVEL; + wp->w_fold_manual = FALSE; + } + changed_window_setting_win(wp); +} + +/* foldCheckClose() {{{2 */ +/* + * Apply 'foldlevel' to all folds that don't contain the cursor. + */ + void +foldCheckClose() +{ + if (*p_fcl != NUL) /* can only be "all" right now */ + { + checkupdate(curwin); + if (checkCloseRec(&curwin->w_folds, curwin->w_cursor.lnum, + (int)curwin->w_p_fdl)) + changed_window_setting(); + } +} + +/* checkCloseRec() {{{2 */ + static int +checkCloseRec(gap, lnum, level) + garray_T *gap; + linenr_T lnum; + int level; +{ + fold_T *fp; + int retval = FALSE; + int i; + + fp = (fold_T *)gap->ga_data; + for (i = 0; i < gap->ga_len; ++i) + { + /* Only manually opened folds may need to be closed. */ + if (fp[i].fd_flags == FD_OPEN) + { + if (level <= 0 && (lnum < fp[i].fd_top + || lnum >= fp[i].fd_top + fp[i].fd_len)) + { + fp[i].fd_flags = FD_LEVEL; + retval = TRUE; + } + else + retval |= checkCloseRec(&fp[i].fd_nested, lnum - fp[i].fd_top, + level - 1); + } + } + return retval; +} + +/* foldCreateAllowed() {{{2 */ +/* + * Return TRUE if it's allowed to manually create or delete a fold. + * Give an error message and return FALSE if not. + */ + int +foldManualAllowed(create) + int create; +{ + if (foldmethodIsManual(curwin) || foldmethodIsMarker(curwin)) + return TRUE; + if (create) + EMSG(_("E350: Cannot create fold with current 'foldmethod'")); + else + EMSG(_("E351: Cannot delete fold with current 'foldmethod'")); + return FALSE; +} + +/* foldCreate() {{{2 */ +/* + * Create a fold from line "start" to line "end" (inclusive) in the current + * window. + */ + void +foldCreate(start, end) + linenr_T start; + linenr_T end; +{ + fold_T *fp; + garray_T *gap; + garray_T fold_ga; + int i, j; + int cont; + int use_level = FALSE; + int closed = FALSE; + int level = 0; + linenr_T start_rel = start; + linenr_T end_rel = end; + + if (start > end) + { + /* reverse the range */ + end = start_rel; + start = end_rel; + start_rel = start; + end_rel = end; + } + + /* When 'foldmethod' is "marker" add markers, which creates the folds. */ + if (foldmethodIsMarker(curwin)) + { + foldCreateMarkers(start, end); + return; + } + + checkupdate(curwin); + + /* Find the place to insert the new fold. */ + gap = &curwin->w_folds; + for (;;) + { + if (!foldFind(gap, start_rel, &fp)) + break; + if (fp->fd_top + fp->fd_len > end_rel) + { + /* New fold is completely inside this fold: Go one level deeper. */ + gap = &fp->fd_nested; + start_rel -= fp->fd_top; + end_rel -= fp->fd_top; + if (use_level || fp->fd_flags == FD_LEVEL) + { + use_level = TRUE; + if (level >= curwin->w_p_fdl) + closed = TRUE; + } + else if (fp->fd_flags == FD_CLOSED) + closed = TRUE; + ++level; + } + else + { + /* This fold and new fold overlap: Insert here and move some folds + * inside the new fold. */ + break; + } + } + + i = (int)(fp - (fold_T *)gap->ga_data); + if (ga_grow(gap, 1) == OK) + { + fp = (fold_T *)gap->ga_data + i; + ga_init2(&fold_ga, (int)sizeof(fold_T), 10); + + /* Count number of folds that will be contained in the new fold. */ + for (cont = 0; i + cont < gap->ga_len; ++cont) + if (fp[cont].fd_top > end_rel) + break; + if (cont > 0 && ga_grow(&fold_ga, cont) == OK) + { + /* If the first fold starts before the new fold, let the new fold + * start there. Otherwise the existing fold would change. */ + if (start_rel > fp->fd_top) + start_rel = fp->fd_top; + + /* When last contained fold isn't completely contained, adjust end + * of new fold. */ + if (end_rel < fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1) + end_rel = fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1; + /* Move contained folds to inside new fold. */ + mch_memmove(fold_ga.ga_data, fp, sizeof(fold_T) * cont); + fold_ga.ga_len += cont; + fold_ga.ga_room -= cont; + i += cont; + + /* Adjust line numbers in contained folds to be relative to the + * new fold. */ + for (j = 0; j < cont; ++j) + ((fold_T *)fold_ga.ga_data)[j].fd_top -= start_rel; + } + /* Move remaining entries to after the new fold. */ + if (i < gap->ga_len) + mch_memmove(fp + 1, (fold_T *)gap->ga_data + i, + sizeof(fold_T) * (gap->ga_len - i)); + gap->ga_len = gap->ga_len + 1 - cont; + gap->ga_room = gap->ga_room - 1 + cont; + + /* insert new fold */ + fp->fd_nested = fold_ga; + fp->fd_top = start_rel; + fp->fd_len = end_rel - start_rel + 1; + + /* We want the new fold to be closed. If it would remain open because + * of using 'foldlevel', need to adjust fd_flags of containing folds. + */ + if (use_level && !closed && level < curwin->w_p_fdl) + closeFold(start, 1L); + if (!use_level) + curwin->w_fold_manual = TRUE; + fp->fd_flags = FD_CLOSED; + fp->fd_small = MAYBE; + + /* redraw */ + changed_window_setting(); + } +} + +/* deleteFold() {{{2 */ +/* + * Delete a fold at line "start" in the current window. + * When "end" is not 0, delete all folds from "start" to "end". + * When "recursive" is TRUE delete recursively. + */ + void +deleteFold(start, end, recursive, had_visual) + linenr_T start; + linenr_T end; + int recursive; + int had_visual; /* TRUE when Visual selection used */ +{ + garray_T *gap; + fold_T *fp; + garray_T *found_ga; + fold_T *found_fp = NULL; + linenr_T found_off = 0; + int use_level = FALSE; + int maybe_small = FALSE; + int level = 0; + linenr_T lnum = start; + linenr_T lnum_off; + int did_one = FALSE; + linenr_T first_lnum = MAXLNUM; + linenr_T last_lnum = 0; + + checkupdate(curwin); + + while (lnum <= end) + { + /* Find the deepest fold for "start". */ + gap = &curwin->w_folds; + found_ga = NULL; + lnum_off = 0; + for (;;) + { + if (!foldFind(gap, lnum - lnum_off, &fp)) + break; + /* lnum is inside this fold, remember info */ + found_ga = gap; + found_fp = fp; + found_off = lnum_off; + + /* if "lnum" is folded, don't check nesting */ + if (check_closed(curwin, fp, &use_level, level, + &maybe_small, lnum_off)) + break; + + /* check nested folds */ + gap = &fp->fd_nested; + lnum_off += fp->fd_top; + ++level; + } + if (found_ga == NULL) + { + ++lnum; + } + else + { + lnum = found_fp->fd_top + found_fp->fd_len + found_off; + did_one = TRUE; + + if (foldmethodIsManual(curwin)) + deleteFoldEntry(found_ga, + (int)(found_fp - (fold_T *)found_ga->ga_data), recursive); + else + { + if (found_fp->fd_top + found_off < first_lnum) + first_lnum = found_fp->fd_top; + if (lnum > last_lnum) + last_lnum = lnum; + parseMarker(curwin); + deleteFoldMarkers(found_fp, recursive, found_off); + } + + /* redraw window */ + changed_window_setting(); + } + } + if (!did_one) + { + EMSG(_(e_nofold)); +#ifdef FEAT_VISUAL + /* Force a redraw to remove the Visual highlighting. */ + if (had_visual) + redraw_curbuf_later(INVERTED); +#endif + } + if (last_lnum > 0) + changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L); +} + +/* clearFolding() {{{2 */ +/* + * Remove all folding for window "win". + */ + void +clearFolding(win) + win_T *win; +{ + deleteFoldRecurse(&win->w_folds); + win->w_foldinvalid = FALSE; +} + +/* foldUpdate() {{{2 */ +/* + * Update folds for changes in the buffer of a window. + * Note that inserted/deleted lines must have already been taken care of by + * calling foldMarkAdjust(). + * The changes in lines from top to bot (inclusive). + */ + void +foldUpdate(wp, top, bot) + win_T *wp; + linenr_T top; + linenr_T bot; +{ + fold_T *fp; + + /* Mark all folds from top to bot as maybe-small. */ + (void)foldFind(&curwin->w_folds, curwin->w_cursor.lnum, &fp); + while (fp < (fold_T *)curwin->w_folds.ga_data + curwin->w_folds.ga_len + && fp->fd_top < bot) + { + fp->fd_small = MAYBE; + ++fp; + } + + if (foldmethodIsIndent(wp) + || foldmethodIsExpr(wp) + || foldmethodIsMarker(wp) +#ifdef FEAT_DIFF + || foldmethodIsDiff(wp) +#endif + || foldmethodIsSyntax(wp)) + foldUpdateIEMS(wp, top, bot); +} + +/* foldUpdateAll() {{{2 */ +/* + * Update all lines in a window for folding. + * Used when a fold setting changes or after reloading the buffer. + * The actual updating is postponed until fold info is used, to avoid doing + * every time a setting is changed or a syntax item is added. + */ + void +foldUpdateAll(win) + win_T *win; +{ + win->w_foldinvalid = TRUE; + redraw_win_later(win, NOT_VALID); +} + +/* foldMoveTo() {{{2 */ +/* + * If "updown" is FALSE: Move to the start or end of the fold. + * If "updown" is TRUE: move to fold at the same level. + * If not moved return FAIL. + */ + int +foldMoveTo(updown, dir, count) + int updown; + int dir; /* FORWARD or BACKWARD */ + long count; +{ + long n; + int retval = FAIL; + linenr_T lnum_off; + linenr_T lnum_found; + linenr_T lnum; + int use_level; + int maybe_small; + garray_T *gap; + fold_T *fp; + int level; + int last; + + /* Repeat "count" times. */ + for (n = 0; n < count; ++n) + { + /* Find nested folds. Stop when a fold is closed. The deepest fold + * that moves the cursor is used. */ + lnum_off = 0; + gap = &curwin->w_folds; + use_level = FALSE; + maybe_small = FALSE; + lnum_found = curwin->w_cursor.lnum; + level = 0; + last = FALSE; + for (;;) + { + if (!foldFind(gap, curwin->w_cursor.lnum - lnum_off, &fp)) + { + if (!updown) + break; + + /* When moving up, consider a fold above the cursor; when + * moving down consider a fold below the cursor. */ + if (dir == FORWARD) + { + if (fp - (fold_T *)gap->ga_data >= gap->ga_len) + break; + --fp; + } + else + { + if (fp == (fold_T *)gap->ga_data) + break; + } + /* don't look for contained folds, they will always move + * the cursor too far. */ + last = TRUE; + } + + if (!last) + { + /* Check if this fold is closed. */ + if (check_closed(curwin, fp, &use_level, level, + &maybe_small, lnum_off)) + last = TRUE; + + /* "[z" and "]z" stop at closed fold */ + if (last && !updown) + break; + } + + if (updown) + { + if (dir == FORWARD) + { + /* to start of next fold if there is one */ + if (fp + 1 - (fold_T *)gap->ga_data < gap->ga_len) + { + lnum = fp[1].fd_top + lnum_off; + if (lnum > curwin->w_cursor.lnum) + lnum_found = lnum; + } + } + else + { + /* to end of previous fold if there is one */ + if (fp > (fold_T *)gap->ga_data) + { + lnum = fp[-1].fd_top + lnum_off + fp[-1].fd_len - 1; + if (lnum < curwin->w_cursor.lnum) + lnum_found = lnum; + } + } + } + else + { + /* Open fold found, set cursor to its start/end and then check + * nested folds. */ + if (dir == FORWARD) + { + lnum = fp->fd_top + lnum_off + fp->fd_len - 1; + if (lnum > curwin->w_cursor.lnum) + lnum_found = lnum; + } + else + { + lnum = fp->fd_top + lnum_off; + if (lnum < curwin->w_cursor.lnum) + lnum_found = lnum; + } + } + + if (last) + break; + + /* Check nested folds (if any). */ + gap = &fp->fd_nested; + lnum_off += fp->fd_top; + ++level; + } + if (lnum_found != curwin->w_cursor.lnum) + { + if (retval == FAIL) + setpcmark(); + curwin->w_cursor.lnum = lnum_found; + curwin->w_cursor.col = 0; + retval = OK; + } + else + break; + } + + return retval; +} + +/* foldInitWin() {{{2 */ +/* + * Init the fold info in a new window. + */ + void +foldInitWin(newwin) + win_T *newwin; +{ + ga_init2(&newwin->w_folds, (int)sizeof(fold_T), 10); +} + +/* find_wl_entry() {{{2 */ +/* + * Find an entry in the win->w_lines[] array for buffer line "lnum". + * Only valid entries are considered (for entries where wl_valid is FALSE the + * line number can be wrong). + * Returns index of entry or -1 if not found. + */ + int +find_wl_entry(win, lnum) + win_T *win; + linenr_T lnum; +{ + int i; + + if (win->w_lines_valid > 0) + for (i = 0; i < win->w_lines_valid; ++i) + if (win->w_lines[i].wl_valid) + { + if (lnum < win->w_lines[i].wl_lnum) + return -1; + if (lnum <= win->w_lines[i].wl_lastlnum) + return i; + } + return -1; +} + +/* foldAdjustVisual() {{{2 */ +#ifdef FEAT_VISUAL +/* + * Adjust the Visual area to include any fold at the start or end completely. + */ + void +foldAdjustVisual() +{ + pos_T *start, *end; + char_u *ptr; + + if (!VIsual_active || !hasAnyFolding(curwin)) + return; + + if (ltoreq(VIsual, curwin->w_cursor)) + { + start = &VIsual; + end = &curwin->w_cursor; + } + else + { + start = &curwin->w_cursor; + end = &VIsual; + } + if (hasFolding(start->lnum, &start->lnum, NULL)) + start->col = 0; + if (hasFolding(end->lnum, NULL, &end->lnum)) + { + ptr = ml_get(end->lnum); + end->col = (colnr_T)STRLEN(ptr); + if (end->col > 0 && *p_sel == 'o') + --end->col; +#ifdef FEAT_MBYTE + /* prevent cursor from moving on the trail byte */ + if (has_mbyte) + mb_adjust_cursor(); +#endif + } +} +#endif + +/* cursor_foldstart() {{{2 */ +/* + * Move the cursor to the first line of a closed fold. + */ + void +foldAdjustCursor() +{ + (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); +} + +/* Internal functions for "fold_T" {{{1 */ +/* cloneFoldGrowArray() {{{2 */ +/* + * Will "clone" (i.e deep copy) a garray_T of folds. + * + * Return FAIL if the operation cannot be completed, otherwise OK. + */ + void +cloneFoldGrowArray(from, to) + garray_T *from; + garray_T *to; +{ + int i; + fold_T *from_p; + fold_T *to_p; + + ga_init2(to, from->ga_itemsize, from->ga_growsize); + if (from->ga_len == 0 || ga_grow(to, from->ga_len) == FAIL) + return; + + from_p = (fold_T *)from->ga_data; + to_p = (fold_T *)to->ga_data; + + for (i = 0; i < from->ga_len; i++) + { + to_p->fd_top = from_p->fd_top; + to_p->fd_len = from_p->fd_len; + to_p->fd_flags = from_p->fd_flags; + to_p->fd_small = from_p->fd_small; + cloneFoldGrowArray(&from_p->fd_nested, &to_p->fd_nested); + ++to->ga_len; + --to->ga_room; + ++from_p; + ++to_p; + } +} + +/* foldFind() {{{2 */ +/* + * Search for line "lnum" in folds of growarray "gap". + * Set *fpp to the fold struct for the fold that contains "lnum" or + * the first fold below it (careful: it can be beyond the end of the array!). + * Returns FALSE when there is no fold that contains "lnum". + */ + static int +foldFind(gap, lnum, fpp) + garray_T *gap; + linenr_T lnum; + fold_T **fpp; +{ + linenr_T low, high; + fold_T *fp; + int i; + + /* + * Perform a binary search. + * "low" is lowest index of possible match. + * "high" is highest index of possible match. + */ + fp = (fold_T *)gap->ga_data; + low = 0; + high = gap->ga_len - 1; + while (low <= high) + { + i = (low + high) / 2; + if (fp[i].fd_top > lnum) + /* fold below lnum, adjust high */ + high = i - 1; + else if (fp[i].fd_top + fp[i].fd_len <= lnum) + /* fold above lnum, adjust low */ + low = i + 1; + else + { + /* lnum is inside this fold */ + *fpp = fp + i; + return TRUE; + } + } + *fpp = fp + low; + return FALSE; +} + +/* foldLevelWin() {{{2 */ +/* + * Return fold level at line number "lnum" in window "wp". + */ + static int +foldLevelWin(wp, lnum) + win_T *wp; + linenr_T lnum; +{ + fold_T *fp; + linenr_T lnum_rel = lnum; + int level = 0; + garray_T *gap; + + /* Recursively search for a fold that contains "lnum". */ + gap = &wp->w_folds; + for (;;) + { + if (!foldFind(gap, lnum_rel, &fp)) + break; + /* Check nested folds. Line number is relative to containing fold. */ + gap = &fp->fd_nested; + lnum_rel -= fp->fd_top; + ++level; + } + + return level; +} + +/* checkupdate() {{{2 */ +/* + * Check if the folds in window "wp" are invalid and update them if needed. + */ + static void +checkupdate(wp) + win_T *wp; +{ + if (wp->w_foldinvalid) + { + foldUpdate(wp, (linenr_T)1, (linenr_T)MAXLNUM); /* will update all */ + wp->w_foldinvalid = FALSE; + } +} + +/* setFoldRepeat() {{{2 */ +/* + * Open or close fold for current window at line "lnum". + * Repeat "count" times. + */ + static void +setFoldRepeat(lnum, count, open |