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"
38 using namespace Scintilla
;
43 static inline bool IsNewline(const int ch
) {
44 return (ch
== '\n' || ch
== '\r');
47 // True if can follow ch down to the end with possibly trailing whitespace
48 static bool FollowToLineEnd(const int ch
, const int state
, const Sci_PositionU endPos
, StyleContext
&sc
) {
50 while (sc
.GetRelative(++i
) == ch
)
52 // Skip over whitespace
53 while (IsASpaceOrTab(sc
.GetRelative(i
)) && sc
.currentPos
+ i
< endPos
)
55 if (IsNewline(sc
.GetRelative(i
)) || sc
.currentPos
+ i
== endPos
) {
57 sc
.ChangeState(state
);
58 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
64 // Does the previous line have more than spaces and tabs?
65 static bool HasPrevLineContent(StyleContext
&sc
) {
67 // Go back to the previous newline
68 while ((--i
+ sc
.currentPos
) && !IsNewline(sc
.GetRelative(i
)))
70 while (--i
+ sc
.currentPos
) {
71 if (IsNewline(sc
.GetRelative(i
)))
73 if (!IsASpaceOrTab(sc
.GetRelative(i
)))
80 static bool IsValidHrule(const Sci_PositionU endPos
, StyleContext
&sc
) {
85 int c
= sc
.GetRelative(i
);
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
);
95 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
99 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
106 static void ColorizeTxt2tagsDoc(Sci_PositionU startPos
, Sci_Position length
, int initStyle
,
107 WordList
**, Accessor
&styler
) {
108 Sci_PositionU 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;
115 StyleContext
sc(startPos
, length
, initStyle
, styler
);
118 // Skip past escaped characters
124 // A blockquotes resets the line semantics
125 if (sc
.state
== SCE_TXT2TAGS_BLOCKQUOTE
){
127 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
129 // An option colors the whole line
130 if (sc
.state
== SCE_TXT2TAGS_OPTION
){
131 FollowToLineEnd('%', SCE_TXT2TAGS_OPTION
, endPos
, sc
);
133 if (sc
.state
== SCE_TXT2TAGS_POSTPROC
){
134 FollowToLineEnd('%', SCE_TXT2TAGS_POSTPROC
, endPos
, sc
);
136 if (sc
.state
== SCE_TXT2TAGS_PREPROC
){
137 FollowToLineEnd('%', SCE_TXT2TAGS_PREPROC
, endPos
, sc
);
139 // A comment colors the whole line
140 if (sc
.state
== SCE_TXT2TAGS_COMMENT
){
141 FollowToLineEnd('%', SCE_TXT2TAGS_COMMENT
, endPos
, sc
);
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) != ' ') {
149 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
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
);
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
!= ' ') {
165 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
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
!= ' ') {
174 sc
.ForwardSetState(SCE_TXT2TAGS_DEFAULT
);
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
!= ' ') {
183 sc
.ForwardSetState(SCE_TXT2TAGS_DEFAULT
);
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("```")) {
192 while (!IsNewline(sc
.GetRelative(i
)) && sc
.currentPos
+ i
< endPos
)
195 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
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
!= ' ') {
204 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
208 else if (sc
.state
== SCE_TXT2TAGS_LINE_BEGIN
) {
209 if (sc
.Match("======"))
211 sc
.SetState(SCE_TXT2TAGS_HEADER6
);
214 else if (sc
.Match("====="))
216 sc
.SetState(SCE_TXT2TAGS_HEADER5
);
219 else if (sc
.Match("===="))
221 sc
.SetState(SCE_TXT2TAGS_HEADER4
);
224 else if (sc
.Match("==="))
226 sc
.SetState(SCE_TXT2TAGS_HEADER3
);
229 //SetStateAndZoom(SCE_TXT2TAGS_HEADER3, 3, '=', sc);
230 else if (sc
.Match("==")) {
231 sc
.SetState(SCE_TXT2TAGS_HEADER2
);
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))) {
239 sc
.SetState(SCE_TXT2TAGS_PRECHAR
);
243 sc
.SetState(SCE_TXT2TAGS_HEADER1
);
246 //SetStateAndZoom(SCE_TXT2TAGS_HEADER1, 1, '=', sc);
250 else if (sc
.Match("++++++"))
252 sc
.SetState(SCE_TXT2TAGS_HEADER6
);
255 else if (sc
.Match("+++++"))
257 sc
.SetState(SCE_TXT2TAGS_HEADER5
);
260 else if (sc
.Match("++++"))
262 sc
.SetState(SCE_TXT2TAGS_HEADER4
);
265 else if (sc
.Match("+++"))
267 sc
.SetState(SCE_TXT2TAGS_HEADER3
);
270 //SetStateAndZoom(SCE_TXT2TAGS_HEADER3, 3, '+', sc);
271 else if (sc
.Match("++")) {
272 sc
.SetState(SCE_TXT2TAGS_HEADER2
);
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)) {
281 // sc.SetState(SCE_TXT2TAGS_LINE_BEGIN);
282 //sc.SetState(SCE_TXT2TAGS_PRECHAR);
286 sc
.SetState(SCE_TXT2TAGS_OLIST_ITEM
);
288 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
289 // sc.SetState(SCE_TXT2TAGS_PRECHAR);
294 sc
.SetState(SCE_TXT2TAGS_HEADER1
);
301 else if (sc
.Match("```")) {
302 if (!HasPrevLineContent(sc
))
303 // if (!FollowToLineEnd(sc))
304 sc
.SetState(SCE_TXT2TAGS_CODEBK
);
306 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
310 else if (sc
.Match("%!preproc")) {
311 sc
.SetState(SCE_TXT2TAGS_PREPROC
);
314 else if (sc
.Match("%!postproc")) {
315 sc
.SetState(SCE_TXT2TAGS_POSTPROC
);
318 else if (sc
.Match("%!")) {
319 sc
.SetState(SCE_TXT2TAGS_OPTION
);
323 else if (sc
.ch
== '%') {
324 sc
.SetState(SCE_TXT2TAGS_COMMENT
);
327 else if (sc
.ch
== '-') {
329 sc
.SetState(SCE_TXT2TAGS_PRECHAR
);
332 else if (sc
.ch
== ':') {
334 sc
.SetState(SCE_TXT2TAGS_OLIST_ITEM
);
336 sc
.SetState(SCE_TXT2TAGS_PRECHAR
);
338 else if (IsNewline(sc
.ch
))
339 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
342 sc
.SetState(SCE_TXT2TAGS_PRECHAR
);
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
);
354 // New state only within the initial whitespace
355 if (sc
.state
== SCE_TXT2TAGS_PRECHAR
) {
357 if (sc
.Match("\"\"\"") && precharCount
< 5){
359 sc
.SetState(SCE_TXT2TAGS_BLOCKQUOTE
);
363 // Begin of code block
364 else if (!HasPrevLineContent(sc) && (sc.chPrev == '\t' || precharCount >= 4))
365 sc.SetState(SCE_TXT2TAGS_CODEBK);
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
))
372 else if ((sc
.ch
== '-') && IsASpaceOrTab(sc
.chNext
)) {
373 sc
.SetState(SCE_TXT2TAGS_ULIST_ITEM
);
374 sc
.ForwardSetState(SCE_TXT2TAGS_DEFAULT
);
377 else if (IsADigit(sc
.ch
)) {
378 Sci_Position digitCount
= 0;
379 while (IsADigit(sc
.GetRelative(++digitCount
)))
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
);
388 // Alternate Ordered list
389 else if (sc
.ch
== '+' && sc
.chNext
== ' ' && IsASpaceOrTab(sc
.GetRelative(2))) {
390 // sc.SetState(SCE_TXT2TAGS_OLIST_ITEM);
392 // sc.SetState(SCE_TXT2TAGS_DEFAULT);
394 else if (sc
.ch
!= ' ' || precharCount
> 2)
395 sc
.SetState(SCE_TXT2TAGS_DEFAULT
);
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;
407 if (sc
.Match("![") || sc
.ch
== '[') {
408 Sci_Position i
= 0, j
= 0, k
= 0;
409 Sci_Position len
= endPos
- sc
.currentPos
;
410 while (i
< len
&& (sc
.GetRelative(++i
) != ']' || sc
.GetRelative(i
- 1) == '\\'))
412 if (sc
.GetRelative(i
) == ']') {
414 if (sc
.GetRelative(++i
) == '(') {
415 while (i
< len
&& (sc
.GetRelative(++i
) != '(' || sc
.GetRelative(i
- 1) == '\\'))
417 if (sc
.GetRelative(i
) == '(')
421 else if (sc
.GetRelative(i
) == '[' || sc
.GetRelative(++i
) == '[') {
422 while (i
< len
&& (sc
.GetRelative(++i
) != ']' || sc
.GetRelative(i
- 1) == '\\'))
424 if (sc
.GetRelative(i
) == ']')
428 // At least a link text
430 sc
.SetState(SCE_TXT2TAGS_LINK
);
432 // Also has a URL or reference portion
435 sc
.ForwardSetState(SCE_TXT2TAGS_DEFAULT
);
438 // Code - also a special case for alternate inside spacing
439 if (sc
.Match("``") && sc
.GetRelative(3) != ' ') {
440 sc
.SetState(SCE_TXT2TAGS_CODE2
);
443 else if (sc
.ch
== '|' && sc
.GetRelative(3) != ' ') {
444 sc
.SetState(SCE_TXT2TAGS_CODE
);
447 else if (sc
.Match("**") && sc
.GetRelative(2) != ' ') {
448 sc
.SetState(SCE_TXT2TAGS_STRONG1
);
452 else if (sc
.Match("//") && sc
.GetRelative(2) != ' ') {
453 sc
.SetState(SCE_TXT2TAGS_EM1
);
456 else if (sc
.Match("__") && sc
.GetRelative(2) != ' ') {
457 sc
.SetState(SCE_TXT2TAGS_EM2
);
461 else if (sc
.Match("--") && sc
.GetRelative(2) != ' ') {
462 sc
.SetState(SCE_TXT2TAGS_STRIKEOUT
);
467 else if (IsNewline(sc
.ch
))
468 sc
.SetState(SCE_TXT2TAGS_LINE_BEGIN
);
470 // Advance if not holding back the cursor for this iteration.
473 freezeCursor
= false;
478 LexerModule
lmTxt2tags(SCLEX_TXT2TAGS
, ColorizeTxt2tagsDoc
, "txt2tags");