patch 8.2.0865: syntax foldlevel is taken from the start of the line

Problem:    Syntax foldlevel is taken from the start of the line.
Solution:   Add ":syn foldlevel" to be able to use the minimal foldlevel in
            the line. (Brad King, closes #6087)
diff --git a/src/structs.h b/src/structs.h
index 9968e92..8fb14d7 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -2266,6 +2266,10 @@
 #define SYNSPL_TOP	1	// spell check toplevel text
 #define SYNSPL_NOTOP	2	// don't spell check toplevel text
 
+// values for b_syn_foldlevel: how to compute foldlevel on a line
+#define SYNFLD_START	0	// use level of item at start of line
+#define SYNFLD_MINIMUM	1	// use lowest local minimum level on line
+
 // avoid #ifdefs for when b_spell is not available
 #ifdef FEAT_SPELL
 # define B_SPELL(buf)  ((buf)->b_spell)
@@ -2360,6 +2364,7 @@
     int		b_syn_slow;		// TRUE when 'redrawtime' reached
 # endif
     int		b_syn_ic;		// ignore case for :syn cmds
+    int		b_syn_foldlevel;	// how to compute foldlevel on a line
     int		b_syn_spell;		// SYNSPL_ values
     garray_T	b_syn_patterns;		// table for syntax patterns
     garray_T	b_syn_clusters;		// table for syntax clusters
diff --git a/src/syntax.c b/src/syntax.c
index cda1f95..f2f74a1 100644
--- a/src/syntax.c
+++ b/src/syntax.c
@@ -30,6 +30,8 @@
 static char *(spo_name_tab[SPO_COUNT]) =
 	    {"ms=", "me=", "hs=", "he=", "rs=", "re=", "lc="};
 
+static char e_illegal_arg[] = N_("E390: Illegal argument: %s");
+
 /*
  * The patterns that are being searched for are stored in a syn_pattern.
  * A match item consists of one pattern.
@@ -3340,7 +3342,7 @@
     else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3)
 	curwin->w_s->b_syn_conceal = FALSE;
     else
-	semsg(_("E390: Illegal argument: %s"), arg);
+	semsg(_(e_illegal_arg), arg);
 #endif
 }
 
@@ -3370,7 +3372,49 @@
     else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6)
 	curwin->w_s->b_syn_ic = TRUE;
     else
-	semsg(_("E390: Illegal argument: %s"), arg);
+	semsg(_(e_illegal_arg), arg);
+}
+
+/*
+ * Handle ":syntax foldlevel" command.
+ */
+    static void
+syn_cmd_foldlevel(exarg_T *eap, int syncing UNUSED)
+{
+    char_u *arg = eap->arg;
+    char_u *arg_end;
+
+    eap->nextcmd = find_nextcmd(arg);
+    if (eap->skip)
+	return;
+
+    if (*arg == NUL)
+    {
+	switch (curwin->w_s->b_syn_foldlevel)
+	{
+	    case SYNFLD_START:   msg(_("syntax foldlevel start"));   break;
+	    case SYNFLD_MINIMUM: msg(_("syntax foldlevel minimum")); break;
+	    default: break;
+	}
+	return;
+    }
+
+    arg_end = skiptowhite(arg);
+    if (STRNICMP(arg, "start", 5) == 0 && arg_end - arg == 5)
+	curwin->w_s->b_syn_foldlevel = SYNFLD_START;
+    else if (STRNICMP(arg, "minimum", 7) == 0 && arg_end - arg == 7)
+	curwin->w_s->b_syn_foldlevel = SYNFLD_MINIMUM;
+    else
+    {
+	semsg(_(e_illegal_arg), arg);
+	return;
+    }
+
+    arg = skipwhite(arg_end);
+    if (*arg != NUL)
+    {
+	semsg(_(e_illegal_arg), arg);
+    }
 }
 
 /*
@@ -3404,7 +3448,7 @@
 	curwin->w_s->b_syn_spell = SYNSPL_DEFAULT;
     else
     {
-	semsg(_("E390: Illegal argument: %s"), arg);
+	semsg(_(e_illegal_arg), arg);
 	return;
     }
 
@@ -3476,6 +3520,7 @@
     block->b_syn_slow = FALSE;	    // clear previous timeout
 #endif
     block->b_syn_ic = FALSE;	    // Use case, by default
+    block->b_syn_foldlevel = SYNFLD_START;
     block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking
     block->b_syn_containedin = FALSE;
 #ifdef FEAT_CONCEAL
@@ -6192,6 +6237,7 @@
     {"cluster",		syn_cmd_cluster},
     {"conceal",		syn_cmd_conceal},
     {"enable",		syn_cmd_enable},
+    {"foldlevel",	syn_cmd_foldlevel},
     {"include",		syn_cmd_include},
     {"iskeyword",	syn_cmd_iskeyword},
     {"keyword",		syn_cmd_keyword},
@@ -6489,6 +6535,18 @@
 #endif
 
 #if defined(FEAT_FOLDING) || defined(PROTO)
+    static int
+syn_cur_foldlevel(void)
+{
+    int		level = 0;
+    int		i;
+
+    for (i = 0; i < current_state.ga_len; ++i)
+	if (CUR_STATE(i).si_flags & HL_FOLD)
+	    ++level;
+    return level;
+}
+
 /*
  * Function called to get folding level for line "lnum" in window "wp".
  */
@@ -6496,7 +6554,8 @@
 syn_get_foldlevel(win_T *wp, long lnum)
 {
     int		level = 0;
-    int		i;
+    int		low_level;
+    int		cur_level;
 
     // Return quickly when there are no fold items at all.
     if (wp->w_s->b_syn_folditems != 0
@@ -6508,9 +6567,25 @@
     {
 	syntax_start(wp, lnum);
 
-	for (i = 0; i < current_state.ga_len; ++i)
-	    if (CUR_STATE(i).si_flags & HL_FOLD)
-		++level;
+	// Start with the fold level at the start of the line.
+	level = syn_cur_foldlevel();
+
+	if (wp->w_s->b_syn_foldlevel == SYNFLD_MINIMUM)
+	{
+	    // Find the lowest fold level that is followed by a higher one.
+	    cur_level = level;
+	    low_level = cur_level;
+	    while (!current_finished)
+	    {
+		(void)syn_current_attr(FALSE, FALSE, NULL, FALSE);
+		cur_level = syn_cur_foldlevel();
+		if (cur_level < low_level)
+		    low_level = cur_level;
+		else if (cur_level > low_level)
+		    level = low_level;
+		++current_col;
+	    }
+	}
     }
     if (level > wp->w_p_fdn)
     {
diff --git a/src/testdir/test_syntax.vim b/src/testdir/test_syntax.vim
index 024ba59..bbcd0d8 100644
--- a/src/testdir/test_syntax.vim
+++ b/src/testdir/test_syntax.vim
@@ -160,7 +160,7 @@
 
 func Test_syntax_completion()
   call feedkeys(":syn \<C-A>\<C-B>\"\<CR>", 'tx')
-  call assert_equal('"syn case clear cluster conceal enable include iskeyword keyword list manual match off on region reset spell sync', @:)
+  call assert_equal('"syn case clear cluster conceal enable foldlevel include iskeyword keyword list manual match off on region reset spell sync', @:)
 
   call feedkeys(":syn case \<C-A>\<C-B>\"\<CR>", 'tx')
   call assert_equal('"syn case ignore match', @:)
@@ -691,4 +691,87 @@
   call delete('Xddd.c')
 endfunc
 
+func Test_syntax_foldlevel()
+  new
+  call setline(1, [
+   \ 'void f(int a)',
+   \ '{',
+   \ '    if (a == 1) {',
+   \ '        a = 0;',
+   \ '    } else if (a == 2) {',
+   \ '        a = 1;',
+   \ '    } else {',
+   \ '        a = 2;',
+   \ '    }',
+   \ '    if (a > 0) {',
+   \ '        if (a == 1) {',
+   \ '            a = 0;',
+   \ '        } /* missing newline */ } /* end of outer if */ else {',
+   \ '        a = 1;',
+   \ '    }',
+   \ '    if (a == 1)',
+   \ '    {',
+   \ '        a = 0;',
+   \ '    }',
+   \ '    else if (a == 2)',
+   \ '    {',
+   \ '        a = 1;',
+   \ '    }',
+   \ '    else',
+   \ '    {',
+   \ '        a = 2;',
+   \ '    }',
+   \ '}',
+   \ ])
+  setfiletype c
+  syntax on
+  set foldmethod=syntax
+
+  call assert_fails('syn foldlevel start start', 'E390')
+  call assert_fails('syn foldlevel not_an_option', 'E390')
+
+  set foldlevel=1
+
+  syn foldlevel start
+  redir @c
+  syn foldlevel
+  redir END
+  call assert_equal("\nsyntax foldlevel start", @c)
+  syn sync fromstart
+  let a = map(range(3,9), 'foldclosed(v:val)')
+  call assert_equal([3,3,3,3,3,3,3], a) " attached cascade folds together
+  let a = map(range(10,15), 'foldclosed(v:val)')
+  call assert_equal([10,10,10,10,10,10], a) " over-attached 'else' hidden
+  let a = map(range(16,27), 'foldclosed(v:val)')
+  let unattached_results = [-1,17,17,17,-1,21,21,21,-1,25,25,25]
+  call assert_equal(unattached_results, a) " unattached cascade folds separately
+
+  syn foldlevel minimum
+  redir @c
+  syn foldlevel
+  redir END
+  call assert_equal("\nsyntax foldlevel minimum", @c)
+  syn sync fromstart
+  let a = map(range(3,9), 'foldclosed(v:val)')
+  call assert_equal([3,3,5,5,7,7,7], a) " attached cascade folds separately
+  let a = map(range(10,15), 'foldclosed(v:val)')
+  call assert_equal([10,10,10,13,13,13], a) " over-attached 'else' visible
+  let a = map(range(16,27), 'foldclosed(v:val)')
+  call assert_equal(unattached_results, a) " unattached cascade folds separately
+
+  set foldlevel=2
+
+  syn foldlevel start
+  syn sync fromstart
+  let a = map(range(11,14), 'foldclosed(v:val)')
+  call assert_equal([11,11,11,-1], a) " over-attached 'else' hidden
+
+  syn foldlevel minimum
+  syn sync fromstart
+  let a = map(range(11,14), 'foldclosed(v:val)')
+  call assert_equal([11,11,-1,-1], a) " over-attached 'else' visible
+
+  quit!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 6f77139..4fc7c3c 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    865,
+/**/
     864,
 /**/
     863,