1 /******************************************************************
2  *  LexTxt2tags.cxx
3  *
4  *  A simple Txt2tags lexer for scintilla.
5  *
6  *
7  *  Adapted by Eric Forgeot
8  *  Based on the LexMarkdown.cxx by Jon Strait - jstrait@moonloop.net
9  *
10  *  What could be improved:
11  *   - Verbatim lines could be like for raw lines : when there is no space between the ``` and the following text, the first letter should be colored so the user would understand there must be a space for a valid tag.
12  *   - marks such as bold, italic, strikeout, underline should begin to be highlighted only when they are closed and valid.
13  *   - verbatim and raw area should be highlighted too.
14  *
15  *  The License.txt file describes the conditions under which this
16  *  software may be distributed.
17  *
18  *****************************************************************/
19 
20 #include <stdlib.h>
21 #include <string.h>
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <assert.h>
25 
26 #include "ILexer.h"
27 #include "Scintilla.h"
28 #include "SciLexer.h"
29 
30 #include "WordList.h"
31 #include "LexAccessor.h"
32 #include "Accessor.h"
33 #include "StyleContext.h"
34 #include "CharacterSet.h"
35 #include "LexerModule.h"
36 
37 #ifdef SCI_NAMESPACE
38 using namespace Scintilla;
39 #endif
40 
41 
42 
IsNewline(const int ch)43 static inline bool IsNewline(const int ch) {
44     return (ch == '\n' || ch == '\r');
45 }
46 
47 // True if can follow ch down to the end with possibly trailing whitespace
FollowToLineEnd(const int ch,const int state,const unsigned int endPos,StyleContext & sc)48 static bool FollowToLineEnd(const int ch, const int state, const unsigned int endPos, StyleContext &sc) {
49     unsigned int i = 0;
50     while (sc.GetRelative(++i) == ch)
51         ;
52     // Skip over whitespace
53     while (IsASpaceOrTab(sc.GetRelative(i)) && sc.currentPos + i < endPos)
54         ++i;
55     if (IsNewline(sc.GetRelative(i)) || sc.currentPos + i == endPos) {
56         sc.Forward(i);
57         sc.ChangeState(state);
58         sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
59         return true;
60     }
61     else return false;
62 }
63 
64 // Does the previous line have more than spaces and tabs?
HasPrevLineContent(StyleContext & sc)65 static bool HasPrevLineContent(StyleContext &sc) {
66     int i = 0;
67     // Go back to the previous newline
68     while ((--i + sc.currentPos) && !IsNewline(sc.GetRelative(i)))
69         ;
70     while (--i + sc.currentPos) {
71         if (IsNewline(sc.GetRelative(i)))
72             break;
73         if (!IsASpaceOrTab(sc.GetRelative(i)))
74             return true;
75     }
76     return false;
77 }
78 
79 // Separator line
IsValidHrule(const unsigned int endPos,StyleContext & sc)80 static bool IsValidHrule(const unsigned int endPos, StyleContext &sc) {
81     int count = 1;
82     unsigned int i = 0;
83     for (;;) {
84         ++i;
85         int c = sc.GetRelative(i);
86         if (c == sc.ch)
87             ++count;
88         // hit a terminating character
89         else if (!IsASpaceOrTab(c) || sc.currentPos + i == endPos) {
90             // Are we a valid HRULE
91             if ((IsNewline(c) || sc.currentPos + i == endPos) &&
92                     count >= 20 && !HasPrevLineContent(sc)) {
93                 sc.SetState(SCE_TXT2TAGS_HRULE);
94                 sc.Forward(i);
95                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
96                 return true;
97             }
98             else {
99                 sc.SetState(SCE_TXT2TAGS_DEFAULT);
100 		return false;
101             }
102         }
103     }
104 }
105 
ColorizeTxt2tagsDoc(unsigned int startPos,int length,int initStyle,WordList **,Accessor & styler)106 static void ColorizeTxt2tagsDoc(unsigned int startPos, int length, int initStyle,
107                                WordList **, Accessor &styler) {
108     unsigned int endPos = startPos + length;
109     int precharCount = 0;
110     // Don't advance on a new loop iteration and retry at the same position.
111     // Useful in the corner case of having to start at the beginning file position
112     // in the default state.
113     bool freezeCursor = false;
114 
115     StyleContext sc(startPos, length, initStyle, styler);
116 
117     while (sc.More()) {
118         // Skip past escaped characters
119         if (sc.ch == '\\') {
120             sc.Forward();
121             continue;
122         }
123 
124         // A blockquotes resets the line semantics
125         if (sc.state == SCE_TXT2TAGS_BLOCKQUOTE){
126             sc.Forward(2);
127             sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
128         }
129         // An option colors the whole line
130         if (sc.state == SCE_TXT2TAGS_OPTION){
131             FollowToLineEnd('%', SCE_TXT2TAGS_OPTION, endPos, sc);
132         }
133         if (sc.state == SCE_TXT2TAGS_POSTPROC){
134             FollowToLineEnd('%', SCE_TXT2TAGS_POSTPROC, endPos, sc);
135         }
136         if (sc.state == SCE_TXT2TAGS_PREPROC){
137             FollowToLineEnd('%', SCE_TXT2TAGS_PREPROC, endPos, sc);
138         }
139         // A comment colors the whole line
140         if (sc.state == SCE_TXT2TAGS_COMMENT){
141             FollowToLineEnd('%', SCE_TXT2TAGS_COMMENT, endPos, sc);
142         }
143         // Conditional state-based actions
144         if (sc.state == SCE_TXT2TAGS_CODE2) {
145         if (IsNewline(sc.ch))
146                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
147             if (sc.Match("``") && sc.GetRelative(-2) != ' ') {
148                 sc.Forward(2);
149                 sc.SetState(SCE_TXT2TAGS_DEFAULT);
150             }
151         }
152         // Table
153         else if (sc.state == SCE_TXT2TAGS_CODE) {
154         if (IsNewline(sc.ch))
155                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
156             if (sc.ch == '|' && sc.chPrev != ' ')
157                 sc.ForwardSetState(SCE_TXT2TAGS_DEFAULT);
158         }
159         // Strong
160         else if (sc.state == SCE_TXT2TAGS_STRONG1) {
161         if (IsNewline(sc.ch))
162                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
163             if (sc.Match("**") && sc.chPrev != ' ') {
164                 sc.Forward(2);
165                 sc.SetState(SCE_TXT2TAGS_DEFAULT);
166             }
167         }
168         // Emphasis
169         else if (sc.state == SCE_TXT2TAGS_EM1) {
170         if (IsNewline(sc.ch))
171                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
172             if (sc.Match("//") && sc.chPrev != ' ') {
173                 sc.Forward(2);
174                 sc.ForwardSetState(SCE_TXT2TAGS_DEFAULT);
175            }
176         }
177         // Underline
178         else if (sc.state == SCE_TXT2TAGS_EM2) {
179         if (IsNewline(sc.ch))
180                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
181             if (sc.Match("__") && sc.chPrev != ' ') {
182                 sc.Forward(2);
183                 sc.ForwardSetState(SCE_TXT2TAGS_DEFAULT);
184            }
185         }
186         // codeblock
187         else if (sc.state == SCE_TXT2TAGS_CODEBK) {
188                 if (IsNewline(sc.ch))
189                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
190             if (sc.atLineStart && sc.Match("```")) {
191                 int i = 1;
192                 while (!IsNewline(sc.GetRelative(i)) && sc.currentPos + i < endPos)
193                     i++;
194                 sc.Forward(i);
195                 sc.SetState(SCE_TXT2TAGS_DEFAULT);
196             }
197         }
198         // strikeout
199         else if (sc.state == SCE_TXT2TAGS_STRIKEOUT) {
200         if (IsNewline(sc.ch))
201                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
202             if (sc.Match("--") && sc.chPrev != ' ') {
203                 sc.Forward(2);
204                 sc.SetState(SCE_TXT2TAGS_DEFAULT);
205             }
206         }
207         // Headers
208         else if (sc.state == SCE_TXT2TAGS_LINE_BEGIN) {
209             if (sc.Match("======"))
210                 {
211                 sc.SetState(SCE_TXT2TAGS_HEADER6);
212                 sc.Forward();
213                 }
214             else if (sc.Match("====="))
215                 {
216                 sc.SetState(SCE_TXT2TAGS_HEADER5);
217                 sc.Forward();
218                 }
219             else if (sc.Match("===="))
220                 {
221                 sc.SetState(SCE_TXT2TAGS_HEADER4);
222                 sc.Forward();
223                 }
224             else if (sc.Match("==="))
225                 {
226                 sc.SetState(SCE_TXT2TAGS_HEADER3);
227                 sc.Forward();
228                 }
229                 //SetStateAndZoom(SCE_TXT2TAGS_HEADER3, 3, '=', sc);
230             else if (sc.Match("==")) {
231                 sc.SetState(SCE_TXT2TAGS_HEADER2);
232                 sc.Forward();
233                 }
234                 //SetStateAndZoom(SCE_TXT2TAGS_HEADER2, 2, '=', sc);
235             else if (sc.Match("=")) {
236                 // Catch the special case of an unordered list
237                 if (sc.chNext == '.' && IsASpaceOrTab(sc.GetRelative(2))) {
238                     precharCount = 0;
239                     sc.SetState(SCE_TXT2TAGS_PRECHAR);
240                 }
241                 else
242                     {
243                     sc.SetState(SCE_TXT2TAGS_HEADER1);
244                     sc.Forward();
245                     }
246                     //SetStateAndZoom(SCE_TXT2TAGS_HEADER1, 1, '=', sc);
247             }
248 
249             // Numbered title
250             else if (sc.Match("++++++"))
251                 {
252                 sc.SetState(SCE_TXT2TAGS_HEADER6);
253                 sc.Forward();
254                 }
255             else if (sc.Match("+++++"))
256                 {
257                 sc.SetState(SCE_TXT2TAGS_HEADER5);
258                 sc.Forward();
259                 }
260             else if (sc.Match("++++"))
261                 {
262                 sc.SetState(SCE_TXT2TAGS_HEADER4);
263                 sc.Forward();
264                 }
265             else if (sc.Match("+++"))
266                 {
267                 sc.SetState(SCE_TXT2TAGS_HEADER3);
268                 sc.Forward();
269                 }
270                 //SetStateAndZoom(SCE_TXT2TAGS_HEADER3, 3, '+', sc);
271             else if (sc.Match("++")) {
272                 sc.SetState(SCE_TXT2TAGS_HEADER2);
273                 sc.Forward();
274                 }
275                 //SetStateAndZoom(SCE_TXT2TAGS_HEADER2, 2, '+', sc);
276             else if (sc.Match("+")) {
277                 // Catch the special case of an unordered list
278                 if (sc.chNext == ' ' && IsASpaceOrTab(sc.GetRelative(1))) {
279                  //    if (IsNewline(sc.ch)) {
280                      	//precharCount = 0;
281                 //		sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
282                 		//sc.SetState(SCE_TXT2TAGS_PRECHAR);
283 				//	}
284                 //    else {
285                 //    precharCount = 0;
286                     sc.SetState(SCE_TXT2TAGS_OLIST_ITEM);
287                     sc.Forward(2);
288                     sc.SetState(SCE_TXT2TAGS_DEFAULT);
289                //     sc.SetState(SCE_TXT2TAGS_PRECHAR);
290 				//	}
291                 }
292                 else
293                     {
294                     sc.SetState(SCE_TXT2TAGS_HEADER1);
295                     sc.Forward();
296                     }
297             }
298 
299 
300             // Codeblock
301             else if (sc.Match("```")) {
302                 if (!HasPrevLineContent(sc))
303               //  if (!FollowToLineEnd(sc))
304                     sc.SetState(SCE_TXT2TAGS_CODEBK);
305                 else
306                     sc.SetState(SCE_TXT2TAGS_DEFAULT);
307             }
308 
309             // Preproc
310             else if (sc.Match("%!preproc")) {
311                 sc.SetState(SCE_TXT2TAGS_PREPROC);
312             }
313             // Postproc
314             else if (sc.Match("%!postproc")) {
315                 sc.SetState(SCE_TXT2TAGS_POSTPROC);
316             }
317             // Option
318             else if (sc.Match("%!")) {
319                 sc.SetState(SCE_TXT2TAGS_OPTION);
320             }
321 
322              // Comment
323             else if (sc.ch == '%') {
324                 sc.SetState(SCE_TXT2TAGS_COMMENT);
325             }
326             // list
327             else if (sc.ch == '-') {
328                     precharCount = 0;
329                     sc.SetState(SCE_TXT2TAGS_PRECHAR);
330             }
331             // def list
332             else if (sc.ch == ':') {
333                     precharCount = 0;
334                    sc.SetState(SCE_TXT2TAGS_OLIST_ITEM);
335                    sc.Forward(1);
336                    sc.SetState(SCE_TXT2TAGS_PRECHAR);
337             }
338             else if (IsNewline(sc.ch))
339                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
340             else {
341                 precharCount = 0;
342                 sc.SetState(SCE_TXT2TAGS_PRECHAR);
343             }
344         }
345 
346         // The header lasts until the newline
347         else if (sc.state == SCE_TXT2TAGS_HEADER1 || sc.state == SCE_TXT2TAGS_HEADER2 ||
348                 sc.state == SCE_TXT2TAGS_HEADER3 || sc.state == SCE_TXT2TAGS_HEADER4 ||
349                 sc.state == SCE_TXT2TAGS_HEADER5 || sc.state == SCE_TXT2TAGS_HEADER6) {
350             if (IsNewline(sc.ch))
351                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
352         }
353 
354         // New state only within the initial whitespace
355         if (sc.state == SCE_TXT2TAGS_PRECHAR) {
356             // Blockquote
357             if (sc.Match("\"\"\"") && precharCount < 5){
358 
359                 sc.SetState(SCE_TXT2TAGS_BLOCKQUOTE);
360                 sc.Forward(1);
361                 }
362             /*
363             // Begin of code block
364             else if (!HasPrevLineContent(sc) && (sc.chPrev == '\t' || precharCount >= 4))
365                 sc.SetState(SCE_TXT2TAGS_CODEBK);
366             */
367             // HRule - Total of 20 or more hyphens, asterisks, or underscores
368             // on a line by themselves
369             else if ((sc.ch == '-' ) && IsValidHrule(endPos, sc))
370                 ;
371             // Unordered list
372             else if ((sc.ch == '-') && IsASpaceOrTab(sc.chNext)) {
373                 sc.SetState(SCE_TXT2TAGS_ULIST_ITEM);
374                 sc.ForwardSetState(SCE_TXT2TAGS_DEFAULT);
375             }
376             // Ordered list
377             else if (IsADigit(sc.ch)) {
378                 int digitCount = 0;
379                 while (IsADigit(sc.GetRelative(++digitCount)))
380                     ;
381                 if (sc.GetRelative(digitCount) == '.' &&
382                         IsASpaceOrTab(sc.GetRelative(digitCount + 1))) {
383                     sc.SetState(SCE_TXT2TAGS_OLIST_ITEM);
384                     sc.Forward(digitCount + 1);
385                     sc.SetState(SCE_TXT2TAGS_DEFAULT);
386                 }
387             }
388             // Alternate Ordered list
389             else if (sc.ch == '+' && sc.chNext == ' ' && IsASpaceOrTab(sc.GetRelative(2))) {
390             //    sc.SetState(SCE_TXT2TAGS_OLIST_ITEM);
391             //    sc.Forward(2);
392              //   sc.SetState(SCE_TXT2TAGS_DEFAULT);
393             }
394             else if (sc.ch != ' ' || precharCount > 2)
395                 sc.SetState(SCE_TXT2TAGS_DEFAULT);
396             else
397                 ++precharCount;
398         }
399 
400         // New state anywhere in doc
401         if (sc.state == SCE_TXT2TAGS_DEFAULT) {
402          //   if (sc.atLineStart && sc.ch == '#') {
403          //       sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
404          //       freezeCursor = true;
405          //   }
406             // Links and Images
407             if (sc.Match("![") || sc.ch == '[') {
408                 int i = 0, j = 0, k = 0;
409                 int len = endPos - sc.currentPos;
410                 while (i < len && (sc.GetRelative(++i) != ']' || sc.GetRelative(i - 1) == '\\'))
411                     ;
412                 if (sc.GetRelative(i) == ']') {
413                     j = i;
414                     if (sc.GetRelative(++i) == '(') {
415                         while (i < len && (sc.GetRelative(++i) != '(' || sc.GetRelative(i - 1) == '\\'))
416                             ;
417                         if (sc.GetRelative(i) == '(')
418                             k = i;
419                     }
420 
421                     else if (sc.GetRelative(i) == '[' || sc.GetRelative(++i) == '[') {
422                         while (i < len && (sc.GetRelative(++i) != ']' || sc.GetRelative(i - 1) == '\\'))
423                             ;
424                         if (sc.GetRelative(i) == ']')
425                             k = i;
426                     }
427                 }
428                 // At least a link text
429                 if (j) {
430                     sc.SetState(SCE_TXT2TAGS_LINK);
431                     sc.Forward(j);
432                     // Also has a URL or reference portion
433                     if (k)
434                         sc.Forward(k - j);
435                     sc.ForwardSetState(SCE_TXT2TAGS_DEFAULT);
436                 }
437             }
438             // Code - also a special case for alternate inside spacing
439             if (sc.Match("``") && sc.GetRelative(3) != ' ') {
440                 sc.SetState(SCE_TXT2TAGS_CODE2);
441                 sc.Forward();
442             }
443             else if (sc.ch == '|' && sc.GetRelative(3) != ' ') {
444                 sc.SetState(SCE_TXT2TAGS_CODE);
445             }
446             // Strong
447             else if (sc.Match("**") && sc.GetRelative(2) != ' ') {
448                 sc.SetState(SCE_TXT2TAGS_STRONG1);
449                 sc.Forward();
450            }
451             // Emphasis
452             else if (sc.Match("//") && sc.GetRelative(2) != ' ') {
453                 sc.SetState(SCE_TXT2TAGS_EM1);
454                 sc.Forward();
455             }
456             else if (sc.Match("__") && sc.GetRelative(2) != ' ') {
457                 sc.SetState(SCE_TXT2TAGS_EM2);
458                 sc.Forward();
459             }
460             // Strikeout
461             else if (sc.Match("--") && sc.GetRelative(2) != ' ') {
462                 sc.SetState(SCE_TXT2TAGS_STRIKEOUT);
463                 sc.Forward();
464             }
465 
466             // Beginning of line
467             else if (IsNewline(sc.ch))
468                 sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
469         }
470         // Advance if not holding back the cursor for this iteration.
471         if (!freezeCursor)
472             sc.Forward();
473         freezeCursor = false;
474     }
475     sc.Complete();
476 }
477 
478 LexerModule lmTxt2tags(SCLEX_TXT2TAGS, ColorizeTxt2tagsDoc, "txt2tags");
479 
480 
481