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