vim-patch:9.1.1077: included syntax items do not understand contains=TOP (#32343)

Problem:  Syntax engine interpreted contains=TOP as matching nothing
          inside included files, since :syn-include forces HL_CONTAINED
          on for every included item. After 8.2.2761, interprets
          contains=TOP as contains=@INCLUDED, which is also not correct
          since it doesn't respect exclusions, and doesn't work if there
          is no @INCLUDED cluster.
Solution: revert patch 8.2.2761, instead track groups that have had
          HL_CONTAINED forced, and interpret contains=TOP and
          contains=CONTAINED using this. (Theodore Dubois)

fixes: vim/vim#11277
closes: vim/vim#16571

f50d5364d7

Co-authored-by: Theodore Dubois <tblodt@icloud.com>
This commit is contained in:
zeertzjq 2025-02-06 08:04:42 +08:00 committed by GitHub
parent 44740e561f
commit 878b3b89c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 38 deletions

View File

@ -1644,7 +1644,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con
? !(spp->sp_flags & HL_CONTAINED) ? !(spp->sp_flags & HL_CONTAINED)
: in_id_list(cur_si, : in_id_list(cur_si,
cur_si->si_cont_list, &spp->sp_syn, cur_si->si_cont_list, &spp->sp_syn,
spp->sp_flags & HL_CONTAINED)))) { spp->sp_flags)))) {
// If we already tried matching in this line, and // If we already tried matching in this line, and
// there isn't a match before next_match_col, skip // there isn't a match before next_match_col, skip
// this item. // this item.
@ -2775,7 +2775,7 @@ static keyentry_T *match_keyword(char *keyword, hashtab_T *ht, stateitem_T *cur_
: (cur_si == NULL : (cur_si == NULL
? !(kp->flags & HL_CONTAINED) ? !(kp->flags & HL_CONTAINED)
: in_id_list(cur_si, cur_si->si_cont_list, : in_id_list(cur_si, cur_si->si_cont_list,
&kp->k_syn, kp->flags & HL_CONTAINED))) { &kp->k_syn, kp->flags))) {
return kp; return kp;
} }
} }
@ -3931,7 +3931,7 @@ static void syn_incl_toplevel(int id, int *flagsp)
if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0) { if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0) {
return; return;
} }
*flagsp |= HL_CONTAINED; *flagsp |= HL_CONTAINED | HL_INCLUDED_TOPLEVEL;
if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) { if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) {
// We have to alloc this, because syn_combine_list() will free it. // We have to alloc this, because syn_combine_list() will free it.
int16_t *grp_list = xmalloc(2 * sizeof(*grp_list)); int16_t *grp_list = xmalloc(2 * sizeof(*grp_list));
@ -4977,16 +4977,13 @@ static int get_id_list(char **const arg, const int keylen, int16_t **const list,
break; break;
} }
if (name[1] == 'A') { if (name[1] == 'A') {
id = SYNID_ALLBUT + current_syn_inc_tag; id = SYNID_ALLBUT;
} else if (name[1] == 'T') { } else if (name[1] == 'T') {
if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) { id = SYNID_TOP;
id = curwin->w_s->b_syn_topgrp;
} else { } else {
id = SYNID_TOP + current_syn_inc_tag; id = SYNID_CONTAINED;
}
} else {
id = SYNID_CONTAINED + current_syn_inc_tag;
} }
id += current_syn_inc_tag;
} else if (name[1] == '@') { } else if (name[1] == '@') {
if (skip) { if (skip) {
id = -1; id = -1;
@ -5104,8 +5101,8 @@ static int16_t *copy_id_list(const int16_t *const list)
/// @param cur_si current item or NULL /// @param cur_si current item or NULL
/// @param list id list /// @param list id list
/// @param ssp group id and ":syn include" tag of group /// @param ssp group id and ":syn include" tag of group
/// @param contained group id is contained /// @param flags group flags
static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, int contained) static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, int flags)
{ {
int retval; int retval;
int16_t id = ssp->id; int16_t id = ssp->id;
@ -5123,8 +5120,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// cur_si->si_idx is -1 for keywords, these never contain anything. // cur_si->si_idx is -1 for keywords, these never contain anything.
if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list, if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
&(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn), &(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags)) {
HL_CONTAINED)) {
return true; return true;
} }
} }
@ -5136,9 +5132,14 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// If list is ID_LIST_ALL, we are in a transparent item that isn't // If list is ID_LIST_ALL, we are in a transparent item that isn't
// inside anything. Only allow not-contained groups. // inside anything. Only allow not-contained groups.
if (list == ID_LIST_ALL) { if (list == ID_LIST_ALL) {
return !contained; return !(flags & HL_CONTAINED);
} }
// Is this top-level (i.e. not 'contained') in the file it was declared in?
// For included files, this is different from HL_CONTAINED, which is set
// unconditionally.
bool toplevel = !(flags & HL_CONTAINED) || (flags & HL_INCLUDED_TOPLEVEL);
// If the first item is "ALLBUT", return true if "id" is NOT in the // If the first item is "ALLBUT", return true if "id" is NOT in the
// contains list. We also require that "id" is at the same ":syn include" // contains list. We also require that "id" is at the same ":syn include"
// level as the list. // level as the list.
@ -5151,12 +5152,12 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
} }
} else if (item < SYNID_CONTAINED) { } else if (item < SYNID_CONTAINED) {
// TOP: accept all not-contained groups in the same file // TOP: accept all not-contained groups in the same file
if (item - SYNID_TOP != ssp->inc_tag || contained) { if (item - SYNID_TOP != ssp->inc_tag || !toplevel) {
return false; return false;
} }
} else { } else {
// CONTAINED: accept all contained groups in the same file // CONTAINED: accept all contained groups in the same file
if (item - SYNID_CONTAINED != ssp->inc_tag || !contained) { if (item - SYNID_CONTAINED != ssp->inc_tag || toplevel) {
return false; return false;
} }
} }
@ -5177,7 +5178,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// cluster that includes itself (indirectly) // cluster that includes itself (indirectly)
if (scl_list != NULL && depth < 30) { if (scl_list != NULL && depth < 30) {
depth++; depth++;
int r = in_id_list(NULL, scl_list, ssp, contained); int r = in_id_list(NULL, scl_list, ssp, flags);
depth--; depth--;
if (r) { if (r) {
return retval; return retval;

View File

@ -26,6 +26,7 @@ enum {
HL_TRANS_CONT = 0x10000, ///< transparent item without contains arg HL_TRANS_CONT = 0x10000, ///< transparent item without contains arg
HL_CONCEAL = 0x20000, ///< can be concealed HL_CONCEAL = 0x20000, ///< can be concealed
HL_CONCEALENDS = 0x40000, ///< can be concealed HL_CONCEALENDS = 0x40000, ///< can be concealed
HL_INCLUDED_TOPLEVEL = 0x80000, ///< toplevel item in included syntax, allowed by contains=TOP
}; };
#define SYN_GROUP_STATIC(s) syn_check_group(S_LEN(s)) #define SYN_GROUP_STATIC(s) syn_check_group(S_LEN(s))

View File

@ -944,7 +944,7 @@ func Test_syn_contained_transparent()
endfunc endfunc
func Test_syn_include_contains_TOP() func Test_syn_include_contains_TOP()
let l:case = "TOP in included syntax means its group list name" let l:case = "TOP in included syntax refers to top level of that included syntax"
new new
syntax include @INCLUDED syntax/c.vim syntax include @INCLUDED syntax/c.vim
syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED
@ -959,6 +959,18 @@ func Test_syn_include_contains_TOP()
bw! bw!
endfunc endfunc
func Test_syn_include_contains_TOP_excluding()
new
syntax include @INCLUDED syntax/c.vim
syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED
call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#if', '#endif', '```' ])
let l:expected = ["cCppOutElse", "cConditional"]
eval AssertHighlightGroups(6, 1, l:expected, 1)
syntax clear
bw!
endfunc
" This was using freed memory " This was using freed memory
func Test_WinEnter_synstack_synID() func Test_WinEnter_synstack_synID()
autocmd WinEnter * call synstack(line("."), col(".")) autocmd WinEnter * call synstack(line("."), col("."))