1 /******************************************************************
4 * A simple Txt2tags lexer for scintilla.
7 * Adapted by Eric Forgeot
8 * Based on the LexMarkdown.cxx by Jon Strait - jstrait@moonloop.net
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.
15 * The License.txt file describes the conditions under which this
16 * software may be distributed.
18 *****************************************************************/
27 #include "Scintilla.h"
31 #include "LexAccessor.h"
33 #include "StyleContext.h"
34 #include "CharacterSet.h"
35 #include "LexerModule.h"
37 using namespace Scintilla
;
41 static inline bool IsNewline(const int ch
) {
42 return (ch
== '\n' || ch
== '\r');
45 // True if can follow ch down to the end with possibly trailing whitespace
46 static bool FollowToLineEnd(const int ch
, const int state
, const Sci_PositionU endPos
, StyleContext
&sc
) {
48 while (sc
.GetRelative(++i
) == ch
)
50 // Skip over whitespace
51 while (IsASpaceOrTab(sc
.GetRelative(i
)) && sc
.currentPos
+ i
< endPos
)
53 if (IsNewline(sc
.GetRelative(i
)) || sc
.currentPos
+ i
== endPos
) {
55 sc
.ChangeState(state
);
56 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
62 // Does the previous line have more than spaces and tabs?
63 static bool HasPrevLineContent(StyleContext
&sc
) {
65 // Go back to the previous newline
66 while ((--i
+ sc
.currentPos
) && !IsNewline(sc
.GetRelative(i
)))
68 while (--i
+ sc
.currentPos
) {
69 if (IsNewline(sc
.GetRelative(i
)))
71 if (!IsASpaceOrTab(sc
.GetRelative(i
)))
78 static bool IsValidHrule(const Sci_PositionU endPos
, StyleContext
&sc
) {
83 int c
= sc
.GetRelative(i
);
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
);
93 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
97 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
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;
113 StyleContext
sc(startPos
, length
, initStyle
, styler
);
116 // Skip past escaped characters
122 // A blockquotes resets the line semantics
123 if (sc
.state
== SCE_TXT2TAGS_BLOCKQUOTE
){
125 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
127 // An option colors the whole line
128 if (sc
.state
== SCE_TXT2TAGS_OPTION
){
129 FollowToLineEnd('%', SCE_TXT2TAGS_OPTION
, endPos
, sc
);
131 if (sc
.state
== SCE_TXT2TAGS_POSTPROC
){
132 FollowToLineEnd('%', SCE_TXT2TAGS_POSTPROC
, endPos
, sc
);
134 if (sc
.state
== SCE_TXT2TAGS_PREPROC
){
135 FollowToLineEnd('%', SCE_TXT2TAGS_PREPROC
, endPos
, sc
);
137 // A comment colors the whole line
138 if (sc
.state
== SCE_TXT2TAGS_COMMENT
){
139 FollowToLineEnd('%', SCE_TXT2TAGS_COMMENT
, endPos
, sc
);
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) != ' ') {
147 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
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
);
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
!= ' ') {
163 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
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
!= ' ') {
172 sc
.ForwardSetState(SCE_TXT2TAGS_DEFAULT
);
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
!= ' ') {
181 sc
.ForwardSetState(SCE_TXT2TAGS_DEFAULT
);
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("```")) {
190 while (!IsNewline(sc
.GetRelative(i
)) && sc
.currentPos
+ i
< endPos
)
193 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
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
!= ' ') {
202 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
206 else if (sc
.state
== SCE_TXT2TAGS_LINE_BEGIN
) {
207 if (sc
.Match("======"))
209 sc
.SetState(SCE_TXT2TAGS_HEADER6
);
212 else if (sc
.Match("====="))
214 sc
.SetState(SCE_TXT2TAGS_HEADER5
);
217 else if (sc
.Match("===="))
219 sc
.SetState(SCE_TXT2TAGS_HEADER4
);
222 else if (sc
.Match("==="))
224 sc
.SetState(SCE_TXT2TAGS_HEADER3
);
227 //SetStateAndZoom(SCE_TXT2TAGS_HEADER3, 3, '=', sc);
228 else if (sc
.Match("==")) {
229 sc
.SetState(SCE_TXT2TAGS_HEADER2
);
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))) {
237 sc
.SetState(SCE_TXT2TAGS_PRECHAR
);
241 sc
.SetState(SCE_TXT2TAGS_HEADER1
);
244 //SetStateAndZoom(SCE_TXT2TAGS_HEADER1, 1, '=', sc);
248 else if (sc
.Match("++++++"))
250 sc
.SetState(SCE_TXT2TAGS_HEADER6
);
253 else if (sc
.Match("+++++"))
255 sc
.SetState(SCE_TXT2TAGS_HEADER5
);
258 else if (sc
.Match("++++"))
260 sc
.SetState(SCE_TXT2TAGS_HEADER4
);
263 else if (sc
.Match("+++"))
265 sc
.SetState(SCE_TXT2TAGS_HEADER3
);
268 //SetStateAndZoom(SCE_TXT2TAGS_HEADER3, 3, '+', sc);
269 else if (sc
.Match("++")) {
270 sc
.SetState(SCE_TXT2TAGS_HEADER2
);
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)) {
279 // sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
280 //sc.SetState(SCE_TXT2TAGS_PRECHAR);
284 sc
.SetState(SCE_TXT2TAGS_OLIST_ITEM
);
286 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
287 // sc.SetState(SCE_TXT2TAGS_PRECHAR);
292 sc
.SetState(SCE_TXT2TAGS_HEADER1
);
299 else if (sc
.Match("```")) {
300 if (!HasPrevLineContent(sc
))
301 // if (!FollowToLineEnd(sc))
302 sc
.SetState(SCE_TXT2TAGS_CODEBK
);
304 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
308 else if (sc
.Match("%!preproc")) {
309 sc
.SetState(SCE_TXT2TAGS_PREPROC
);
312 else if (sc
.Match("%!postproc")) {
313 sc
.SetState(SCE_TXT2TAGS_POSTPROC
);
316 else if (sc
.Match("%!")) {
317 sc
.SetState(SCE_TXT2TAGS_OPTION
);
321 else if (sc
.ch
== '%') {
322 sc
.SetState(SCE_TXT2TAGS_COMMENT
);
325 else if (sc
.ch
== '-') {
327 sc
.SetState(SCE_TXT2TAGS_PRECHAR
);
330 else if (sc
.ch
== ':') {
332 sc
.SetState(SCE_TXT2TAGS_OLIST_ITEM
);
334 sc
.SetState(SCE_TXT2TAGS_PRECHAR
);
336 else if (IsNewline(sc
.ch
))
337 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
340 sc
.SetState(SCE_TXT2TAGS_PRECHAR
);
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
);
352 // New state only within the initial whitespace
353 if (sc
.state
== SCE_TXT2TAGS_PRECHAR
) {
355 if (sc
.Match("\"\"\"") && precharCount
< 5){
357 sc
.SetState(SCE_TXT2TAGS_BLOCKQUOTE
);
361 // Begin of code block
362 else if (!HasPrevLineContent(sc) && (sc.chPrev == '\t' || precharCount >= 4))
363 sc.SetState(SCE_TXT2TAGS_CODEBK);
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
))
370 else if ((sc
.ch
== '-') && IsASpaceOrTab(sc
.chNext
)) {
371 sc
.SetState(SCE_TXT2TAGS_ULIST_ITEM
);
372 sc
.ForwardSetState(SCE_TXT2TAGS_DEFAULT
);
375 else if (IsADigit(sc
.ch
)) {
376 Sci_Position digitCount
= 0;
377 while (IsADigit(sc
.GetRelative(++digitCount
)))
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
);
386 // Alternate Ordered list
387 else if (sc
.ch
== '+' && sc
.chNext
== ' ' && IsASpaceOrTab(sc
.GetRelative(2))) {
388 // sc.SetState(SCE_TXT2TAGS_OLIST_ITEM);
390 // sc.SetState(SCE_TXT2TAGS_DEFAULT);
392 else if (sc
.ch
!= ' ' || precharCount
> 2)
393 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
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;
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) == '\\'))
410 if (sc
.GetRelative(i
) == ']') {
412 if (sc
.GetRelative(++i
) == '(') {
413 while (i
< len
&& (sc
.GetRelative(++i
) != '(' || sc
.GetRelative(i
- 1) == '\\'))
415 if (sc
.GetRelative(i
) == '(')
419 else if (sc
.GetRelative(i
) == '[' || sc
.GetRelative(++i
) == '[') {
420 while (i
< len
&& (sc
.GetRelative(++i
) != ']' || sc
.GetRelative(i
- 1) == '\\'))
422 if (sc
.GetRelative(i
) == ']')
426 // At least a link text
428 sc
.SetState(SCE_TXT2TAGS_LINK
);
430 // Also has a URL or reference portion
433 sc
.ForwardSetState(SCE_TXT2TAGS_DEFAULT
);
436 // Code - also a special case for alternate inside spacing
437 if (sc
.Match("``") && sc
.GetRelative(3) != ' ') {
438 sc
.SetState(SCE_TXT2TAGS_CODE2
);
441 else if (sc
.ch
== '|' && sc
.GetRelative(3) != ' ') {
442 sc
.SetState(SCE_TXT2TAGS_CODE
);
445 else if (sc
.Match("**") && sc
.GetRelative(2) != ' ') {
446 sc
.SetState(SCE_TXT2TAGS_STRONG1
);
450 else if (sc
.Match("//") && sc
.GetRelative(2) != ' ') {
451 sc
.SetState(SCE_TXT2TAGS_EM1
);
454 else if (sc
.Match("__") && sc
.GetRelative(2) != ' ') {
455 sc
.SetState(SCE_TXT2TAGS_EM2
);
459 else if (sc
.Match("--") && sc
.GetRelative(2) != ' ') {
460 sc
.SetState(SCE_TXT2TAGS_STRIKEOUT
);
465 else if (IsNewline(sc
.ch
))
466 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
468 // Advance if not holding back the cursor for this iteration.
471 freezeCursor
= false;
476 LexerModule
lmTxt2tags(SCLEX_TXT2TAGS
, ColorizeTxt2tagsDoc
, "txt2tags");