On 10/17/2016 04:45 PM, Bram Moolenaar wrote: >> :syntax foldlevel [start | minimum] > > Thanks. I suppose that's the best way to do it.
Here is a revised patch series that now includes tests for the new command. It also updates runtime/syntax/vim.vim to highlight the command syntax. Thanks, -Brad -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
>From 5892ea62008d11ad24c07d875c8c4cefa5503c64 Mon Sep 17 00:00:00 2001 Message-Id: <5892ea62008d11ad24c07d875c8c4cefa5503c64.1485803991.git.brad.k...@kitware.com> From: Brad King <brad.k...@kitware.com> Date: Sun, 22 Jan 2017 22:31:35 -0500 Subject: [PATCH v2 1/3] syntax: Add command to control how foldlevel is computed for a line With `foldmethod=syntax` the foldlevel of a line is computed based on syntax items on the line. Previously we always used the level of the syntax item containing the start of the line. This works well in cases such as: if (...) { ... } else if (...) { ... } else { ... } which folds like this: +--- 3 lines: if (...) {--------------------------- +--- 3 lines: else if (...) {---------------------- +--- 3 lines: else {------------------------------- However, the code: if (...) { ... } else if (...) { ... } else { ... } folds like this: +--- 7 lines: if (...) {--------------------------- We can make the latter case fold like this: +--- 2 lines: if (...) {--------------------------- +--- 2 lines: } else if (...) {-------------------- +--- 3 lines: } else {----------------------------- by choosing on each line the lowest fold level that is followed by a higher fold level. Add a syntax command :syntax foldlevel [start | minimum] to choose between these two methods of computing the foldlevel of a line. --- runtime/doc/syntax.txt | 19 +++++++++++ src/structs.h | 5 +++ src/syntax.c | 64 ++++++++++++++++++++++++++++++++++--- src/testdir/test_syntax.vim | 77 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 160 insertions(+), 5 deletions(-) diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index e300983f0..c2804ecd9 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -3482,6 +3482,23 @@ DEFINING CASE *:syn-case* *E390* :sy[ntax] case Show either "syntax case match" or "syntax case ignore" (translated). +DEFINING FOLDLEVEL *:syn-foldlevel* + +:sy[ntax] foldlevel [start | minimum] + This defines how the foldlevel of a line is computed when using + foldmethod=syntax (see |fold-syntax| and |:syn-fold|): + + start: Use level of item containing start of line. + minimum: Use lowest local-minimum level of items on line. + + The default is 'start'. Use 'minimum' to search a line horizontally + for the lowest level contained on the line that is followed by a + higher level. This produces more natural folds when syntax items + may close and open horizontally within a line. + + {not meaningful when Vim was compiled without |+folding| feature} + + SPELL CHECKING *:syn-spell* :sy[ntax] spell [toplevel | notoplevel | default] @@ -3945,6 +3962,8 @@ This will make each {} block form one fold. The fold will start on the line where the item starts, and end where the item ends. If the start and end are within the same line, there is no fold. The 'foldnestmax' option limits the nesting of syntax folds. +See |:syn-foldlevel| to control how the foldlevel of a line is computed +from its syntax items. {not available when Vim was compiled without |+folding| feature} diff --git a/src/structs.h b/src/structs.h index af0a6fd2b..97f30bb56 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1736,6 +1736,10 @@ typedef struct list_stack_S #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) @@ -1787,6 +1791,7 @@ typedef struct { hashtab_T b_keywtab_ic; /* idem, ignore case */ int b_syn_error; /* TRUE when error occurred in HL */ 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 dbadb70fd..3df1a7810 100644 --- a/src/syntax.c +++ b/src/syntax.c @@ -436,6 +436,7 @@ static char_u *syn_getcurline(void); static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st); static int check_keyword_id(char_u *line, int startcol, int *endcol, long *flags, short **next_list, stateitem_T *cur_si, int *ccharp); static void syn_cmd_case(exarg_T *eap, int syncing); +static void syn_cmd_foldlevel(exarg_T *eap, int syncing); static void syn_cmd_spell(exarg_T *eap, int syncing); static void syntax_sync_clear(void); static void syn_remove_pattern(synblock_T *block, int idx); @@ -3481,6 +3482,31 @@ syn_cmd_case(exarg_T *eap, int syncing UNUSED) } /* + * Handle ":syntax foldlevel" command. + */ + static void +syn_cmd_foldlevel(exarg_T *eap, int syncing UNUSED) +{ + char_u *arg = eap->arg; + char_u *next; + + eap->nextcmd = find_nextcmd(arg); + if (eap->skip) + return; + + next = skiptowhite(arg); + if (STRNICMP(arg, "start", 5) == 0 && next - arg == 5) + curwin->w_s->b_syn_foldlevel = SYNFLD_START; + else if (STRNICMP(arg, "minimum", 7) == 0 && next - arg == 7) + curwin->w_s->b_syn_foldlevel = SYNFLD_MINIMUM; + else + { + EMSG2(_("E390: Illegal argument: %s"), arg); + return; + } +} + +/* * Handle ":syntax spell" command. */ static void @@ -3578,6 +3604,7 @@ syntax_clear(synblock_T *block) block->b_syn_error = FALSE; /* clear previous error */ 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 @@ -6302,6 +6329,7 @@ static struct subcommand subcommands[] = {"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}, @@ -6603,6 +6631,17 @@ syn_get_stack_item(int i) #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". */ @@ -6610,16 +6649,33 @@ syn_get_stack_item(int i) 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) { 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 8a00f992f..603b8e3d5 100644 --- a/src/testdir/test_syntax.vim +++ b/src/testdir/test_syntax.vim @@ -145,7 +145,7 @@ endfunc 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', @:) @@ -346,3 +346,78 @@ func Test_invalid_name() hi clear Nop hi clear @Wrong 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', 'E390') + call assert_fails('syn foldlevel not_an_option', 'E390') + + set foldlevel=1 + + syn foldlevel start + 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 + 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 -- 2.11.0
>From 27a031a243993fd002f5eac2316c1bad6bc4b8d8 Mon Sep 17 00:00:00 2001 Message-Id: <27a031a243993fd002f5eac2316c1bad6bc4b8d8.1485803991.git.brad.k...@kitware.com> In-Reply-To: <5892ea62008d11ad24c07d875c8c4cefa5503c64.1485803991.git.brad.k...@kitware.com> References: <5892ea62008d11ad24c07d875c8c4cefa5503c64.1485803991.git.brad.k...@kitware.com> From: Brad King <brad.k...@kitware.com> Date: Sun, 22 Jan 2017 22:31:56 -0500 Subject: [PATCH v2 2/3] runtime/syntax/vim.vim: Extend for `syntax foldlevel` command --- runtime/syntax/vim.vim | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 52df919f1..9dcd5b9f9 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -480,6 +480,13 @@ syn match vimGroupAdd contained "add=" nextgroup=vimGroupList syn match vimGroupRem contained "remove=" nextgroup=vimGroupList syn cluster vimFuncBodyList add=vimSynType,vimGroupAdd,vimGroupRem +" Syntax: foldlevel {{{2 +syn keyword vimSynType contained foldlevel skipwhite nextgroup=vimSynFoldMethod,vimSynFoldMethodError +if !exists("g:vimsyn_noerror") && !exists("g:vimsyn_novimsynfoldmethoderror") + syn match vimSynFoldMethodError contained "\i\+" +endif +syn keyword vimSynFoldMethod contained start minimum + " Syntax: iskeyword {{{2 syn keyword vimSynType contained iskeyword skipwhite nextgroup=vimIskList syn match vimIskList contained '\S\+' contains=vimIskSep @@ -811,6 +818,7 @@ if !exists("skip_vim_syntax_inits") hi def link vimMapModErr vimError hi def link vimSubstFlagErr vimError hi def link vimSynCaseError vimError + hi def link vimSynFoldMethodError vimError hi def link vimBufnrWarn vimWarn endif @@ -932,6 +940,8 @@ if !exists("skip_vim_syntax_inits") hi def link vimSyncNone Type hi def link vimSynContains vimSynOption hi def link vimSynError Error + hi def link vimSynFoldMethodError Error + hi def link vimSynFoldMethod Type hi def link vimSynKeyContainedin vimSynContains hi def link vimSynKeyOpt vimSynOption hi def link vimSynMtchGrp vimSynOption -- 2.11.0
>From b9fed56b3f22fd02bf49fc955eed9433ca1a84be Mon Sep 17 00:00:00 2001 Message-Id: <b9fed56b3f22fd02bf49fc955eed9433ca1a84be.1485803991.git.brad.k...@kitware.com> In-Reply-To: <5892ea62008d11ad24c07d875c8c4cefa5503c64.1485803991.git.brad.k...@kitware.com> References: <5892ea62008d11ad24c07d875c8c4cefa5503c64.1485803991.git.brad.k...@kitware.com> From: Brad King <brad.k...@kitware.com> Date: Sun, 22 Jan 2017 22:31:59 -0500 Subject: [PATCH v2 3/3] runtime/syntax/c.vim: Start new folds on '} ... {' Update the C syntax rules so that the code if (...) { ... } else if (...) { ... } else { ... } folds like this: +--- 2 lines: if (...) {--------------------------- +--- 2 lines: } else if (...) {-------------------- +--- 3 lines: } else {----------------------------- even though the nesting level at the start of the `else` lines does not change. This makes folding of code using attached braces consistent with the same code using braces on their own line. --- runtime/syntax/c.vim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/syntax/c.vim b/runtime/syntax/c.vim index 16c7ce4d9..8cc1f76b6 100644 --- a/runtime/syntax/c.vim +++ b/runtime/syntax/c.vim @@ -13,6 +13,9 @@ set cpo&vim let s:ft = matchstr(&ft, '^\([^.]\)\+') +" Start new folds on lines of the form '} ... {' +syn foldlevel minimum + " A bunch of useful C keywords syn keyword cStatement goto break return continue asm syn keyword cLabel case default -- 2.11.0