/
syntax.c
8565 lines (7813 loc) · 268 KB
/
syntax.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
/*
* syntax.c: code for syntax highlighting
*/
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/api/private/helpers.h"
#include "nvim/syntax.h"
#include "nvim/charset.h"
#include "nvim/cursor_shape.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/hashtab.h"
#include "nvim/highlight.h"
#include "nvim/indent_c.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
#include "nvim/keymap.h"
#include "nvim/garray.h"
#include "nvim/option.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
#include "nvim/macros.h"
#include "nvim/regexp.h"
#include "nvim/screen.h"
#include "nvim/sign.h"
#include "nvim/strings.h"
#include "nvim/syntax_defs.h"
#include "nvim/terminal.h"
#include "nvim/ui.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/buffer.h"
static bool did_syntax_onoff = false;
/// Structure that stores information about a highlight group.
/// The ID of a highlight group is also called group ID. It is the index in
/// the highlight_ga array PLUS ONE.
struct hl_group {
char_u *sg_name; ///< highlight group name
char_u *sg_name_u; ///< uppercase of sg_name
bool sg_cleared; ///< "hi clear" was used
int sg_attr; ///< Screen attr @see ATTR_ENTRY
int sg_link; ///< link to this highlight group ID
int sg_set; ///< combination of flags in \ref SG_SET
sctx_T sg_script_ctx; ///< script in which the group was last set
// for terminal UIs
int sg_cterm; ///< "cterm=" highlighting attr
///< (combination of \ref HlAttrFlags)
int sg_cterm_fg; ///< terminal fg color number + 1
int sg_cterm_bg; ///< terminal bg color number + 1
bool sg_cterm_bold; ///< bold attr was set for light color
// for RGB UIs
int sg_gui; ///< "gui=" highlighting attributes
///< (combination of \ref HlAttrFlags)
RgbValue sg_rgb_fg; ///< RGB foreground color
RgbValue sg_rgb_bg; ///< RGB background color
RgbValue sg_rgb_sp; ///< RGB special color
char *sg_rgb_fg_name; ///< RGB foreground color name
char *sg_rgb_bg_name; ///< RGB background color name
char *sg_rgb_sp_name; ///< RGB special color name
int sg_blend; ///< blend level (0-100 inclusive), -1 if unset
};
/// \addtogroup SG_SET
/// @{
#define SG_CTERM 2 // cterm has been set
#define SG_GUI 4 // gui has been set
#define SG_LINK 8 // link has been set
/// @}
// builtin |highlight-groups|
static garray_T highlight_ga = GA_EMPTY_INIT_VALUE;
static inline struct hl_group * HL_TABLE(void)
{
return ((struct hl_group *)((highlight_ga.ga_data)));
}
#define MAX_HL_ID 20000 /* maximum value for a highlight ID. */
/* different types of offsets that are possible */
#define SPO_MS_OFF 0 /* match start offset */
#define SPO_ME_OFF 1 /* match end offset */
#define SPO_HS_OFF 2 /* highl. start offset */
#define SPO_HE_OFF 3 /* highl. end offset */
#define SPO_RS_OFF 4 /* region start offset */
#define SPO_RE_OFF 5 /* region end offset */
#define SPO_LC_OFF 6 /* leading context offset */
#define SPO_COUNT 7
/* Flags to indicate an additional string for highlight name completion. */
static int include_none = 0; /* when 1 include "nvim/None" */
static int include_default = 0; /* when 1 include "nvim/default" */
static int include_link = 0; /* when 2 include "nvim/link" and "clear" */
/// The "term", "cterm" and "gui" arguments can be any combination of the
/// following names, separated by commas (but no spaces!).
static char *(hl_name_table[]) =
{ "bold", "standout", "underline", "undercurl",
"italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" };
static int hl_attr_table[] =
{ HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE,
HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 };
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.
// A start/end item consists of n start patterns and m end patterns.
// A start/skip/end item consists of n start patterns, one skip pattern and m
// end patterns.
// For the latter two, the patterns are always consecutive: start-skip-end.
//
// A character offset can be given for the matched text (_m_start and _m_end)
// and for the actually highlighted text (_h_start and _h_end).
//
// Note that ordering of members is optimized to reduce padding.
typedef struct syn_pattern {
char sp_type; // see SPTYPE_ defines below
bool sp_syncing; // this item used for syncing
int16_t sp_syn_match_id; // highlight group ID of pattern
int16_t sp_off_flags; // see below
int sp_offsets[SPO_COUNT]; // offsets
int sp_flags; // see HL_ defines below
int sp_cchar; // conceal substitute character
int sp_ic; // ignore-case flag for sp_prog
int sp_sync_idx; // sync item index (syncing only)
int sp_line_id; // ID of last line where tried
int sp_startcol; // next match in sp_line_id line
int16_t *sp_cont_list; // cont. group IDs, if non-zero
int16_t *sp_next_list; // next group IDs, if non-zero
struct sp_syn sp_syn; // struct passed to in_id_list()
char_u *sp_pattern; // regexp to match, pattern
regprog_T *sp_prog; // regexp to match, program
syn_time_T sp_time;
} synpat_T;
typedef struct syn_cluster_S {
char_u *scl_name; // syntax cluster name
char_u *scl_name_u; // uppercase of scl_name
int16_t *scl_list; // IDs in this syntax cluster
} syn_cluster_T;
/*
* For the current state we need to remember more than just the idx.
* When si_m_endpos.lnum is 0, the items other than si_idx are unknown.
* (The end positions have the column number of the next char)
*/
typedef struct state_item {
int si_idx; // index of syntax pattern or
// KEYWORD_IDX
int si_id; // highlight group ID for keywords
int si_trans_id; // idem, transparency removed
int si_m_lnum; // lnum of the match
int si_m_startcol; // starting column of the match
lpos_T si_m_endpos; // just after end posn of the match
lpos_T si_h_startpos; // start position of the highlighting
lpos_T si_h_endpos; // end position of the highlighting
lpos_T si_eoe_pos; // end position of end pattern
int si_end_idx; // group ID for end pattern or zero
int si_ends; // if match ends before si_m_endpos
int si_attr; // attributes in this state
long si_flags; // HL_HAS_EOL flag in this state, and
// HL_SKIP* for si_next_list
int si_seqnr; // sequence number
int si_cchar; // substitution character for conceal
int16_t *si_cont_list; // list of contained groups
int16_t *si_next_list; // nextgroup IDs after this item ends
reg_extmatch_T *si_extmatch; // \z(...\) matches from start
// pattern
} stateitem_T;
/*
* Struct to reduce the number of arguments to get_syn_options(), it's used
* very often.
*/
typedef struct {
int flags; // flags for contained and transparent
bool keyword; // true for ":syn keyword"
int *sync_idx; // syntax item for "grouphere" argument, NULL
// if not allowed
bool has_cont_list; // true if "cont_list" can be used
int16_t *cont_list; // group IDs for "contains" argument
int16_t *cont_in_list; // group IDs for "containedin" argument
int16_t *next_list; // group IDs for "nextgroup" argument
} syn_opt_arg_T;
typedef struct {
proftime_T total;
int count;
int match;
proftime_T slowest;
proftime_T average;
int id;
char_u *pattern;
} time_entry_T;
struct name_list {
int flag;
char *name;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "syntax.c.generated.h"
#endif
static char *(spo_name_tab[SPO_COUNT]) =
{"ms=", "me=", "hs=", "he=", "rs=", "re=", "lc="};
/* The sp_off_flags are computed like this:
* offset from the start of the matched text: (1 << SPO_XX_OFF)
* offset from the end of the matched text: (1 << (SPO_XX_OFF + SPO_COUNT))
* When both are present, only one is used.
*/
#define SPTYPE_MATCH 1 /* match keyword with this group ID */
#define SPTYPE_START 2 /* match a regexp, start of item */
#define SPTYPE_END 3 /* match a regexp, end of item */
#define SPTYPE_SKIP 4 /* match a regexp, skip within item */
#define SYN_ITEMS(buf) ((synpat_T *)((buf)->b_syn_patterns.ga_data))
#define NONE_IDX -2 /* value of sp_sync_idx for "NONE" */
/*
* Flags for b_syn_sync_flags:
*/
#define SF_CCOMMENT 0x01 /* sync on a C-style comment */
#define SF_MATCH 0x02 /* sync by matching a pattern */
#define SYN_STATE_P(ssp) ((bufstate_T *)((ssp)->ga_data))
#define MAXKEYWLEN 80 /* maximum length of a keyword */
/*
* The attributes of the syntax item that has been recognized.
*/
static int current_attr = 0; /* attr of current syntax word */
static int current_id = 0; /* ID of current char for syn_get_id() */
static int current_trans_id = 0; /* idem, transparency removed */
static int current_flags = 0;
static int current_seqnr = 0;
static int current_sub_char = 0;
/*
* Methods of combining two clusters
*/
#define CLUSTER_REPLACE 1 /* replace first list with second */
#define CLUSTER_ADD 2 /* add second list to first */
#define CLUSTER_SUBTRACT 3 /* subtract second list from first */
#define SYN_CLSTR(buf) ((syn_cluster_T *)((buf)->b_syn_clusters.ga_data))
/*
* Syntax group IDs have different types:
* 0 - 19999 normal syntax groups
* 20000 - 20999 ALLBUT indicator (current_syn_inc_tag added)
* 21000 - 21999 TOP indicator (current_syn_inc_tag added)
* 22000 - 22999 CONTAINED indicator (current_syn_inc_tag added)
* 23000 - 32767 cluster IDs (subtract SYNID_CLUSTER for the cluster ID)
*/
#define SYNID_ALLBUT MAX_HL_ID /* syntax group ID for contains=ALLBUT */
#define SYNID_TOP 21000 /* syntax group ID for contains=TOP */
#define SYNID_CONTAINED 22000 /* syntax group ID for contains=CONTAINED */
#define SYNID_CLUSTER 23000 /* first syntax group ID for clusters */
#define MAX_SYN_INC_TAG 999 /* maximum before the above overflow */
#define MAX_CLUSTER_ID (32767 - SYNID_CLUSTER)
/*
* Annoying Hack(TM): ":syn include" needs this pointer to pass to
* expand_filename(). Most of the other syntax commands don't need it, so
* instead of passing it to them, we stow it here.
*/
static char_u **syn_cmdlinep;
/*
* Another Annoying Hack(TM): To prevent rules from other ":syn include"'d
* files from leaking into ALLBUT lists, we assign a unique ID to the
* rules in each ":syn include"'d file.
*/
static int current_syn_inc_tag = 0;
static int running_syn_inc_tag = 0;
/*
* In a hashtable item "hi_key" points to "keyword" in a keyentry.
* This avoids adding a pointer to the hashtable item.
* KE2HIKEY() converts a var pointer to a hashitem key pointer.
* HIKEY2KE() converts a hashitem key pointer to a var pointer.
* HI2KE() converts a hashitem pointer to a var pointer.
*/
static keyentry_T dumkey;
#define KE2HIKEY(kp) ((kp)->keyword)
#define HIKEY2KE(p) ((keyentry_T *)((p) - (dumkey.keyword - (char_u *)&dumkey)))
#define HI2KE(hi) HIKEY2KE((hi)->hi_key)
// -V:HI2KE:782
/*
* To reduce the time spent in keepend(), remember at which level in the state
* stack the first item with "keepend" is present. When "-1", there is no
* "keepend" on the stack.
*/
static int keepend_level = -1;
static char msg_no_items[] = N_("No Syntax items defined for this buffer");
// value of si_idx for keywords
#define KEYWORD_IDX -1
// valid of si_cont_list for containing all but contained groups
#define ID_LIST_ALL (int16_t *)-1
static int next_seqnr = 1; /* value to use for si_seqnr */
/*
* The next possible match in the current line for any pattern is remembered,
* to avoid having to try for a match in each column.
* If next_match_idx == -1, not tried (in this line) yet.
* If next_match_col == MAXCOL, no match found in this line.
* (All end positions have the column of the char after the end)
*/
static int next_match_col; /* column for start of next match */
static lpos_T next_match_m_endpos; /* position for end of next match */
static lpos_T next_match_h_startpos; /* pos. for highl. start of next match */
static lpos_T next_match_h_endpos; /* pos. for highl. end of next match */
static int next_match_idx; /* index of matched item */
static long next_match_flags; /* flags for next match */
static lpos_T next_match_eos_pos; /* end of start pattn (start region) */
static lpos_T next_match_eoe_pos; /* pos. for end of end pattern */
static int next_match_end_idx; /* ID of group for end pattn or zero */
static reg_extmatch_T *next_match_extmatch = NULL;
/*
* A state stack is an array of integers or stateitem_T, stored in a
* garray_T. A state stack is invalid if its itemsize entry is zero.
*/
#define INVALID_STATE(ssp) ((ssp)->ga_itemsize == 0)
#define VALID_STATE(ssp) ((ssp)->ga_itemsize != 0)
/*
* The current state (within the line) of the recognition engine.
* When current_state.ga_itemsize is 0 the current state is invalid.
*/
static win_T *syn_win; // current window for highlighting
static buf_T *syn_buf; // current buffer for highlighting
static synblock_T *syn_block; // current buffer for highlighting
static proftime_T *syn_tm; // timeout limit
static linenr_T current_lnum = 0; // lnum of current state
static colnr_T current_col = 0; // column of current state
static int current_state_stored = 0; // TRUE if stored current state
// after setting current_finished
static int current_finished = 0; // current line has been finished
static garray_T current_state // current stack of state_items
= GA_EMPTY_INIT_VALUE;
static int16_t *current_next_list = NULL; // when non-zero, nextgroup list
static int current_next_flags = 0; // flags for current_next_list
static int current_line_id = 0; // unique number for current line
#define CUR_STATE(idx) ((stateitem_T *)(current_state.ga_data))[idx]
static int syn_time_on = FALSE;
# define IF_SYN_TIME(p) (p)
// Set the timeout used for syntax highlighting.
// Use NULL to reset, no timeout.
void syn_set_timeout(proftime_T *tm)
{
syn_tm = tm;
}
/*
* Start the syntax recognition for a line. This function is normally called
* from the screen updating, once for each displayed line.
* The buffer is remembered in syn_buf, because get_syntax_attr() doesn't get
* it. Careful: curbuf and curwin are likely to point to another buffer and
* window.
*/
void syntax_start(win_T *wp, linenr_T lnum)
{
synstate_T *p;
synstate_T *last_valid = NULL;
synstate_T *last_min_valid = NULL;
synstate_T *sp, *prev = NULL;
linenr_T parsed_lnum;
linenr_T first_stored;
int dist;
static int changedtick = 0; /* remember the last change ID */
current_sub_char = NUL;
/*
* After switching buffers, invalidate current_state.
* Also do this when a change was made, the current state may be invalid
* then.
*/
if (syn_block != wp->w_s
|| syn_buf != wp->w_buffer
|| changedtick != buf_get_changedtick(syn_buf)) {
invalidate_current_state();
syn_buf = wp->w_buffer;
syn_block = wp->w_s;
}
changedtick = buf_get_changedtick(syn_buf);
syn_win = wp;
/*
* Allocate syntax stack when needed.
*/
syn_stack_alloc();
if (syn_block->b_sst_array == NULL)
return; /* out of memory */
syn_block->b_sst_lasttick = display_tick;
/*
* If the state of the end of the previous line is useful, store it.
*/
if (VALID_STATE(¤t_state)
&& current_lnum < lnum
&& current_lnum < syn_buf->b_ml.ml_line_count) {
(void)syn_finish_line(false);
if (!current_state_stored) {
++current_lnum;
(void)store_current_state();
}
/*
* If the current_lnum is now the same as "lnum", keep the current
* state (this happens very often!). Otherwise invalidate
* current_state and figure it out below.
*/
if (current_lnum != lnum)
invalidate_current_state();
} else
invalidate_current_state();
/*
* Try to synchronize from a saved state in b_sst_array[].
* Only do this if lnum is not before and not to far beyond a saved state.
*/
if (INVALID_STATE(¤t_state) && syn_block->b_sst_array != NULL) {
/* Find last valid saved state before start_lnum. */
for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next) {
if (p->sst_lnum > lnum) {
break;
}
if (p->sst_change_lnum == 0) {
last_valid = p;
if (p->sst_lnum >= lnum - syn_block->b_syn_sync_minlines)
last_min_valid = p;
}
}
if (last_min_valid != NULL)
load_current_state(last_min_valid);
}
/*
* If "lnum" is before or far beyond a line with a saved state, need to
* re-synchronize.
*/
if (INVALID_STATE(¤t_state)) {
syn_sync(wp, lnum, last_valid);
if (current_lnum == 1)
/* First line is always valid, no matter "minlines". */
first_stored = 1;
else
/* Need to parse "minlines" lines before state can be considered
* valid to store. */
first_stored = current_lnum + syn_block->b_syn_sync_minlines;
} else
first_stored = current_lnum;
/*
* Advance from the sync point or saved state until the current line.
* Save some entries for syncing with later on.
*/
if (syn_block->b_sst_len <= Rows)
dist = 999999;
else
dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
while (current_lnum < lnum) {
syn_start_line();
(void)syn_finish_line(false);
current_lnum++;
/* If we parsed at least "minlines" lines or started at a valid
* state, the current state is considered valid. */
if (current_lnum >= first_stored) {
/* Check if the saved state entry is for the current line and is
* equal to the current state. If so, then validate all saved
* states that depended on a change before the parsed line. */
if (prev == NULL)
prev = syn_stack_find_entry(current_lnum - 1);
if (prev == NULL)
sp = syn_block->b_sst_first;
else
sp = prev;
while (sp != NULL && sp->sst_lnum < current_lnum)
sp = sp->sst_next;
if (sp != NULL
&& sp->sst_lnum == current_lnum
&& syn_stack_equal(sp)) {
parsed_lnum = current_lnum;
prev = sp;
while (sp != NULL && sp->sst_change_lnum <= parsed_lnum) {
if (sp->sst_lnum <= lnum)
/* valid state before desired line, use this one */
prev = sp;
else if (sp->sst_change_lnum == 0)
/* past saved states depending on change, break here. */
break;
sp->sst_change_lnum = 0;
sp = sp->sst_next;
}
load_current_state(prev);
}
/* Store the state at this line when it's the first one, the line
* where we start parsing, or some distance from the previously
* saved state. But only when parsed at least 'minlines'. */
else if (prev == NULL
|| current_lnum == lnum
|| current_lnum >= prev->sst_lnum + dist)
prev = store_current_state();
}
/* This can take a long time: break when CTRL-C pressed. The current
* state will be wrong then. */
line_breakcheck();
if (got_int) {
current_lnum = lnum;
break;
}
}
syn_start_line();
}
/*
* We cannot simply discard growarrays full of state_items or buf_states; we
* have to manually release their extmatch pointers first.
*/
static void clear_syn_state(synstate_T *p)
{
if (p->sst_stacksize > SST_FIX_STATES) {
# define UNREF_BUFSTATE_EXTMATCH(bs) unref_extmatch((bs)->bs_extmatch)
GA_DEEP_CLEAR(&(p->sst_union.sst_ga), bufstate_T, UNREF_BUFSTATE_EXTMATCH);
} else {
for (int i = 0; i < p->sst_stacksize; i++) {
unref_extmatch(p->sst_union.sst_stack[i].bs_extmatch);
}
}
}
/*
* Cleanup the current_state stack.
*/
static void clear_current_state(void)
{
# define UNREF_STATEITEM_EXTMATCH(si) unref_extmatch((si)->si_extmatch)
GA_DEEP_CLEAR(¤t_state, stateitem_T, UNREF_STATEITEM_EXTMATCH);
}
/*
* Try to find a synchronisation point for line "lnum".
*
* This sets current_lnum and the current state. One of three methods is
* used:
* 1. Search backwards for the end of a C-comment.
* 2. Search backwards for given sync patterns.
* 3. Simply start on a given number of lines above "lnum".
*/
static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
{
buf_T *curbuf_save;
win_T *curwin_save;
pos_T cursor_save;
int idx;
linenr_T lnum;
linenr_T end_lnum;
linenr_T break_lnum;
bool had_sync_point;
stateitem_T *cur_si;
synpat_T *spp;
char_u *line;
int found_flags = 0;
int found_match_idx = 0;
linenr_T found_current_lnum = 0;
int found_current_col= 0;
lpos_T found_m_endpos;
colnr_T prev_current_col;
/*
* Clear any current state that might be hanging around.
*/
invalidate_current_state();
/*
* Start at least "minlines" back. Default starting point for parsing is
* there.
* Start further back, to avoid that scrolling backwards will result in
* resyncing for every line. Now it resyncs only one out of N lines,
* where N is minlines * 1.5, or minlines * 2 if minlines is small.
* Watch out for overflow when minlines is MAXLNUM.
*/
if (syn_block->b_syn_sync_minlines > start_lnum)
start_lnum = 1;
else {
if (syn_block->b_syn_sync_minlines == 1)
lnum = 1;
else if (syn_block->b_syn_sync_minlines < 10)
lnum = syn_block->b_syn_sync_minlines * 2;
else
lnum = syn_block->b_syn_sync_minlines * 3 / 2;
if (syn_block->b_syn_sync_maxlines != 0
&& lnum > syn_block->b_syn_sync_maxlines)
lnum = syn_block->b_syn_sync_maxlines;
if (lnum >= start_lnum)
start_lnum = 1;
else
start_lnum -= lnum;
}
current_lnum = start_lnum;
/*
* 1. Search backwards for the end of a C-style comment.
*/
if (syn_block->b_syn_sync_flags & SF_CCOMMENT) {
/* Need to make syn_buf the current buffer for a moment, to be able to
* use find_start_comment(). */
curwin_save = curwin;
curwin = wp;
curbuf_save = curbuf;
curbuf = syn_buf;
/*
* Skip lines that end in a backslash.
*/
for (; start_lnum > 1; --start_lnum) {
line = ml_get(start_lnum - 1);
if (*line == NUL || *(line + STRLEN(line) - 1) != '\\')
break;
}
current_lnum = start_lnum;
/* set cursor to start of search */
cursor_save = wp->w_cursor;
wp->w_cursor.lnum = start_lnum;
wp->w_cursor.col = 0;
/*
* If the line is inside a comment, need to find the syntax item that
* defines the comment.
* Restrict the search for the end of a comment to b_syn_sync_maxlines.
*/
if (find_start_comment((int)syn_block->b_syn_sync_maxlines) != NULL) {
for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; )
if (SYN_ITEMS(syn_block)[idx].sp_syn.id
== syn_block->b_syn_sync_id
&& SYN_ITEMS(syn_block)[idx].sp_type == SPTYPE_START) {
validate_current_state();
push_current_state(idx);
update_si_attr(current_state.ga_len - 1);
break;
}
}
/* restore cursor and buffer */
wp->w_cursor = cursor_save;
curwin = curwin_save;
curbuf = curbuf_save;
}
/*
* 2. Search backwards for given sync patterns.
*/
else if (syn_block->b_syn_sync_flags & SF_MATCH) {
if (syn_block->b_syn_sync_maxlines != 0
&& start_lnum > syn_block->b_syn_sync_maxlines)
break_lnum = start_lnum - syn_block->b_syn_sync_maxlines;
else
break_lnum = 0;
found_m_endpos.lnum = 0;
found_m_endpos.col = 0;
end_lnum = start_lnum;
lnum = start_lnum;
while (--lnum > break_lnum) {
/* This can take a long time: break when CTRL-C pressed. */
line_breakcheck();
if (got_int) {
invalidate_current_state();
current_lnum = start_lnum;
break;
}
/* Check if we have run into a valid saved state stack now. */
if (last_valid != NULL && lnum == last_valid->sst_lnum) {
load_current_state(last_valid);
break;
}
/*
* Check if the previous line has the line-continuation pattern.
*/
if (lnum > 1 && syn_match_linecont(lnum - 1))
continue;
/*
* Start with nothing on the state stack
*/
validate_current_state();
for (current_lnum = lnum; current_lnum < end_lnum; ++current_lnum) {
syn_start_line();
for (;; ) {
had_sync_point = syn_finish_line(true);
// When a sync point has been found, remember where, and
// continue to look for another one, further on in the line.
if (had_sync_point && current_state.ga_len) {
cur_si = &CUR_STATE(current_state.ga_len - 1);
if (cur_si->si_m_endpos.lnum > start_lnum) {
/* ignore match that goes to after where started */
current_lnum = end_lnum;
break;
}
if (cur_si->si_idx < 0) {
/* Cannot happen? */
found_flags = 0;
found_match_idx = KEYWORD_IDX;
} else {
spp = &(SYN_ITEMS(syn_block)[cur_si->si_idx]);
found_flags = spp->sp_flags;
found_match_idx = spp->sp_sync_idx;
}
found_current_lnum = current_lnum;
found_current_col = current_col;
found_m_endpos = cur_si->si_m_endpos;
/*
* Continue after the match (be aware of a zero-length
* match).
*/
if (found_m_endpos.lnum > current_lnum) {
current_lnum = found_m_endpos.lnum;
current_col = found_m_endpos.col;
if (current_lnum >= end_lnum)
break;
} else if (found_m_endpos.col > current_col)
current_col = found_m_endpos.col;
else
++current_col;
/* syn_current_attr() will have skipped the check for
* an item that ends here, need to do that now. Be
* careful not to go past the NUL. */
prev_current_col = current_col;
if (syn_getcurline()[current_col] != NUL)
++current_col;
check_state_ends();
current_col = prev_current_col;
} else
break;
}
}
/*
* If a sync point was encountered, break here.
*/
if (found_flags) {
/*
* Put the item that was specified by the sync point on the
* state stack. If there was no item specified, make the
* state stack empty.
*/
clear_current_state();
if (found_match_idx >= 0) {
push_current_state(found_match_idx);
update_si_attr(current_state.ga_len - 1);
}
/*
* When using "grouphere", continue from the sync point
* match, until the end of the line. Parsing starts at
* the next line.
* For "groupthere" the parsing starts at start_lnum.
*/
if (found_flags & HL_SYNC_HERE) {
if (!GA_EMPTY(¤t_state)) {
cur_si = &CUR_STATE(current_state.ga_len - 1);
cur_si->si_h_startpos.lnum = found_current_lnum;
cur_si->si_h_startpos.col = found_current_col;
update_si_end(cur_si, (int)current_col, TRUE);
check_keepend();
}
current_col = found_m_endpos.col;
current_lnum = found_m_endpos.lnum;
(void)syn_finish_line(false);
current_lnum++;
} else {
current_lnum = start_lnum;
}
break;
}
end_lnum = lnum;
invalidate_current_state();
}
/* Ran into start of the file or exceeded maximum number of lines */
if (lnum <= break_lnum) {
invalidate_current_state();
current_lnum = break_lnum + 1;
}
}
validate_current_state();
}
static void save_chartab(char_u *chartab)
{
if (syn_block->b_syn_isk != empty_option) {
memmove(chartab, syn_buf->b_chartab, (size_t)32);
memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab, (size_t)32);
}
}
static void restore_chartab(char_u *chartab)
{
if (syn_win->w_s->b_syn_isk != empty_option) {
memmove(syn_buf->b_chartab, chartab, (size_t)32);
}
}
/*
* Return TRUE if the line-continuation pattern matches in line "lnum".
*/
static int syn_match_linecont(linenr_T lnum)
{
if (syn_block->b_syn_linecont_prog != NULL) {
regmmatch_T regmatch;
// chartab array for syn iskeyword
char_u buf_chartab[32];
save_chartab(buf_chartab);
regmatch.rmm_ic = syn_block->b_syn_linecont_ic;
regmatch.regprog = syn_block->b_syn_linecont_prog;
int r = syn_regexec(®match, lnum, (colnr_T)0,
IF_SYN_TIME(&syn_block->b_syn_linecont_time));
syn_block->b_syn_linecont_prog = regmatch.regprog;
restore_chartab(buf_chartab);
return r;
}
return FALSE;
}
/*
* Prepare the current state for the start of a line.
*/
static void syn_start_line(void)
{
current_finished = FALSE;
current_col = 0;
/*
* Need to update the end of a start/skip/end that continues from the
* previous line and regions that have "keepend".
*/
if (!GA_EMPTY(¤t_state)) {
syn_update_ends(TRUE);
check_state_ends();
}
next_match_idx = -1;
current_line_id++;
next_seqnr = 1;
}
/*
* Check for items in the stack that need their end updated.
* When "startofline" is TRUE the last item is always updated.
* When "startofline" is FALSE the item with "keepend" is forcefully updated.
*/
static void syn_update_ends(int startofline)
{
stateitem_T *cur_si;
int seen_keepend;
if (startofline) {
/* Check for a match carried over from a previous line with a
* contained region. The match ends as soon as the region ends. */
for (int i = 0; i < current_state.ga_len; ++i) {
cur_si = &CUR_STATE(i);
if (cur_si->si_idx >= 0
&& (SYN_ITEMS(syn_block)[cur_si->si_idx]).sp_type
== SPTYPE_MATCH
&& cur_si->si_m_endpos.lnum < current_lnum) {
cur_si->si_flags |= HL_MATCHCONT;
cur_si->si_m_endpos.lnum = 0;
cur_si->si_m_endpos.col = 0;
cur_si->si_h_endpos = cur_si->si_m_endpos;
cur_si->si_ends = TRUE;
}
}
}
/*
* Need to update the end of a start/skip/end that continues from the
* previous line. And regions that have "keepend", because they may
* influence contained items. If we've just removed "extend"
* (startofline == 0) then we should update ends of normal regions
* contained inside "keepend" because "extend" could have extended
* these "keepend" regions as well as contained normal regions.
* Then check for items ending in column 0.
*/
int i = current_state.ga_len - 1;
if (keepend_level >= 0)
for (; i > keepend_level; --i)
if (CUR_STATE(i).si_flags & HL_EXTEND)
break;
seen_keepend = FALSE;
for (; i < current_state.ga_len; ++i) {
cur_si = &CUR_STATE(i);
if ((cur_si->si_flags & HL_KEEPEND)
|| (seen_keepend && !startofline)
|| (i == current_state.ga_len - 1 && startofline)) {
cur_si->si_h_startpos.col = 0; /* start highl. in col 0 */
cur_si->si_h_startpos.lnum = current_lnum;
if (!(cur_si->si_flags & HL_MATCHCONT))
update_si_end(cur_si, (int)current_col, !startofline);
if (!startofline && (cur_si->si_flags & HL_KEEPEND))
seen_keepend = TRUE;
}
}
check_keepend();
}
/****************************************
* Handling of the state stack cache.
*/
/*
* EXPLANATION OF THE SYNTAX STATE STACK CACHE
*
* To speed up syntax highlighting, the state stack for the start of some
* lines is cached. These entries can be used to start parsing at that point.
*
* The stack is kept in b_sst_array[] for each buffer. There is a list of
* valid entries. b_sst_first points to the first one, then follow sst_next.
* The entries are sorted on line number. The first entry is often for line 2
* (line 1 always starts with an empty stack).
* There is also a list for free entries. This construction is used to avoid
* having to allocate and free memory blocks too often.
*
* When making changes to the buffer, this is logged in b_mod_*. When calling
* update_screen() to update the display, it will call
* syn_stack_apply_changes() for each displayed buffer to adjust the cached
* entries. The entries which are inside the changed area are removed,
* because they must be recomputed. Entries below the changed have their line
* number adjusted for deleted/inserted lines, and have their sst_change_lnum
* set to indicate that a check must be made if the changed lines would change
* the cached entry.
*
* When later displaying lines, an entry is stored for each line. Displayed
* lines are likely to be displayed again, in which case the state at the
* start of the line is needed.
* For not displayed lines, an entry is stored for every so many lines. These
* entries will be used e.g., when scrolling backwards. The distance between
* entries depends on the number of lines in the buffer. For small buffers
* the distance is fixed at SST_DIST, for large buffers there is a fixed
* number of entries SST_MAX_ENTRIES, and the distance is computed.
*/
static void syn_stack_free_block(synblock_T *block)
{
synstate_T *p;
if (block->b_sst_array != NULL) {