summaryrefslogtreecommitdiffstats
path: root/src/fold.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2004-06-13 20:20:40 +0000
committerBram Moolenaar <Bram@vim.org>2004-06-13 20:20:40 +0000
commit071d4279d6ab81b7187b48f3a0fc61e587b6db6c (patch)
tree221cbe3c40e043163c06f61c52a7ba2eb41e12ce /src/fold.c
parentb4210b3bc14e2918f153a7307530fbe6eba659e1 (diff)
updated for version 7.0001v7.0001
Diffstat (limited to 'src/fold.c')
-rw-r--r--src/fold.c3250
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)
+ linenr_T lnum;
+ long count;
+ int open;
+{
+ int done;
+ long n;
+
+ for (n = 0; n < count; ++n)
+ {
+ done = DONE_NOT