1/******************************************************************
2 * LexAsciidoc.cxx
3 *
4 * A simple Asciidoc lexer for scintilla.
5 *
6 * Based on the LexMarkdown.cxx by Jon Strait - jstrait@moonloop.net
7 *
8 * The License.txt file describes the conditions under which this
9 * software may be distributed.
10 *
11 *****************************************************************/
12
13#include <stdlib.h>
14#include <string.h>
15#include <stdio.h>
16#include <stdarg.h>
17#include <assert.h>
18
19#include <string>
20#include <string_view>
21
22#include "ILexer.h"
23#include "Scintilla.h"
24#include "SciLexer.h"
25
26#include "WordList.h"
27#include "LexAccessor.h"
28#include "Accessor.h"
29#include "StyleContext.h"
30#include "CharacterSet.h"
31#include "LexerModule.h"
32
33using namespace Lexilla;
34
35namespace {
36
37typedef struct {
38 bool start;
39 int len1;
40 int len2;
41 const char *name;
42} MacroItem;
43
44static const MacroItem MacroList[] = {
45 // Directives
46 {true, 5, 2, "ifdef::"},
47 {true, 6, 2, "ifeval::"},
48 {true, 6, 2, "ifndef::"},
49 {true, 5, 2, "endif::"},
50 // Macros
51 {true, 5, 2, "audio::"},
52 {true, 7, 2, "include::"},
53 {true, 5, 2, "image::"},
54 {true, 5, 2, "video::"},
55 {false, 8, 1, "asciimath:"},
56 {false, 3, 1, "btn:"},
57 {false, 5, 1, "image:"},
58 {false, 3, 1, "kbd:"},
59 {false, 9, 1, "latexmath:"},
60 {false, 4, 1, "link:"},
61 {false, 6, 1, "mailto:"},
62 {false, 4, 1, "menu:"},
63 {false, 4, 1, "pass:"},
64 {false, 4, 1, "stem:"},
65 {false, 4, 1, "xref:"},
66 // Admonitions
67 {true, 7, 1, "CAUTION:"},
68 {true, 9, 1, "IMPORTANT:"},
69 {true, 4, 1, "NOTE:"},
70 {true, 3, 1, "TIP:"},
71 {true, 7, 1, "WARNING:"},
72 {false, 0, 0, NULL}
73};
74
75constexpr bool IsNewline(const int ch) {
76 // sc.GetRelative(i) returns '\0' if out of range
77 return (ch == '\n' || ch == '\r' || ch == '\0');
78}
79
80}
81
82static bool AtTermStart(StyleContext &sc) {
83 return sc.currentPos == 0 || sc.chPrev == 0 || isspacechar(sc.chPrev);
84}
85
86static void ColorizeAsciidocDoc(Sci_PositionU startPos, Sci_Position length, int initStyle,
87 WordList **, Accessor &styler) {
88 bool freezeCursor = false;
89
90 StyleContext sc(startPos, static_cast<Sci_PositionU>(length), initStyle, styler);
91
92 while (sc.More()) {
93 // Skip past escaped characters
94 if (sc.ch == '\\') {
95 sc.Forward();
96 continue;
97 }
98
99 // Skip newline.
100 if (IsNewline(sc.ch)) {
101 // Newline doesn't end blocks
102 if (sc.state != SCE_ASCIIDOC_CODEBK && \
103 sc.state != SCE_ASCIIDOC_PASSBK && \
104 sc.state != SCE_ASCIIDOC_COMMENTBK && \
105 sc.state != SCE_ASCIIDOC_LITERALBK) {
106 sc.SetState(SCE_ASCIIDOC_DEFAULT);
107 }
108 sc.Forward();
109 continue;
110 }
111
112 // Conditional state-based actions
113 switch (sc.state) {
114
115 // Strong
116 case SCE_ASCIIDOC_STRONG1:
117 if (sc.ch == '*' && sc.chPrev != ' ') {
118 sc.Forward();
119 sc.SetState(SCE_ASCIIDOC_DEFAULT);
120 freezeCursor = true;
121 }
122 break;
123 case SCE_ASCIIDOC_STRONG2:
124 if (sc.Match("**") && sc.chPrev != ' ') {
125 sc.Forward(2);
126 sc.SetState(SCE_ASCIIDOC_DEFAULT);
127 freezeCursor = true;
128 }
129 break;
130
131 // Emphasis
132 case SCE_ASCIIDOC_EM1:
133 if (sc.ch == '_' && sc.chPrev != ' ') {
134 sc.Forward();
135 sc.SetState(SCE_ASCIIDOC_DEFAULT);
136 freezeCursor = true;
137 }
138 break;
139 case SCE_ASCIIDOC_EM2:
140 if (sc.Match("__") && sc.chPrev != ' ') {
141 sc.Forward(2);
142 sc.SetState(SCE_ASCIIDOC_DEFAULT);
143 freezeCursor = true;
144 }
145 break;
146
147 // Link
148 case SCE_ASCIIDOC_LINK:
149 if (sc.ch == ']' && sc.chPrev != '\\') {
150 sc.SetState(SCE_ASCIIDOC_DEFAULT);
151 }
152 break;
153
154 // Code block
155 case SCE_ASCIIDOC_CODEBK:
156 if (sc.atLineStart && sc.Match("----") && IsNewline(sc.GetRelative(4))) {
157 sc.Forward(4);
158 sc.SetState(SCE_ASCIIDOC_DEFAULT);
159 }
160 break;
161
162 // Passthrough block
163 case SCE_ASCIIDOC_PASSBK:
164 if (sc.atLineStart && sc.Match("++++") && IsNewline(sc.GetRelative(4))) {
165 sc.Forward(4);
166 sc.SetState(SCE_ASCIIDOC_DEFAULT);
167 }
168 break;
169
170 // Comment block
171 case SCE_ASCIIDOC_COMMENTBK:
172 if (sc.atLineStart && sc.Match("////") && IsNewline(sc.GetRelative(4))) {
173 sc.Forward(4);
174 sc.SetState(SCE_ASCIIDOC_DEFAULT);
175 }
176 break;
177
178 // Literal
179 case SCE_ASCIIDOC_LITERAL:
180 if (sc.ch == '+' && sc.chPrev != '\\') {
181 sc.SetState(SCE_ASCIIDOC_DEFAULT);
182 }
183 break;
184
185 // Literal block
186 case SCE_ASCIIDOC_LITERALBK:
187 if (sc.atLineStart && sc.Match("....") && IsNewline(sc.GetRelative(4))) {
188 sc.Forward(4);
189 sc.SetState(SCE_ASCIIDOC_DEFAULT);
190 }
191 break;
192
193 // Attribute
194 case SCE_ASCIIDOC_ATTRIB:
195 if (sc.ch == ':' && sc.chPrev != ' ' && sc.chNext == ' ') {
196 sc.Forward();
197 sc.SetState(SCE_ASCIIDOC_ATTRIBVAL);
198 }
199 break;
200
201 // Macro
202 case SCE_ASCIIDOC_MACRO:
203 if (sc.ch == ']' && sc.chPrev != '\\') {
204 sc.SetState(SCE_ASCIIDOC_DEFAULT);
205 }
206 break;
207
208 // Default
209 case SCE_ASCIIDOC_DEFAULT:
210 // Headers
211 if (sc.atLineStart && sc.Match("====== ")) {
212 sc.SetState(SCE_ASCIIDOC_HEADER6);
213 sc.Forward(6);
214 }
215 else if (sc.atLineStart && sc.Match("===== ")) {
216 sc.SetState(SCE_ASCIIDOC_HEADER5);
217 sc.Forward(5);
218 }
219 else if (sc.atLineStart && sc.Match("==== ")) {
220 sc.SetState(SCE_ASCIIDOC_HEADER4);
221 sc.Forward(4);
222 }
223 else if (sc.atLineStart && sc.Match("=== ")) {
224 sc.SetState(SCE_ASCIIDOC_HEADER3);
225 sc.Forward(3);
226 }
227 else if (sc.atLineStart && sc.Match("== ")) {
228 sc.SetState(SCE_ASCIIDOC_HEADER2);
229 sc.Forward(2);
230 }
231 else if (sc.atLineStart && sc.Match("= ")) {
232 sc.SetState(SCE_ASCIIDOC_HEADER1);
233 sc.Forward(1);
234 }
235 // Unordered list item
236 else if (sc.atLineStart && sc.Match("****** ")) {
237 sc.SetState(SCE_ASCIIDOC_ULIST_ITEM);
238 sc.Forward(6);
239 sc.SetState(SCE_ASCIIDOC_DEFAULT);
240 }
241 else if (sc.atLineStart && sc.Match("***** ")) {
242 sc.SetState(SCE_ASCIIDOC_ULIST_ITEM);
243 sc.Forward(5);
244 sc.SetState(SCE_ASCIIDOC_DEFAULT);
245 }
246 else if (sc.atLineStart && sc.Match("**** ")) {
247 sc.SetState(SCE_ASCIIDOC_ULIST_ITEM);
248 sc.Forward(4);
249 sc.SetState(SCE_ASCIIDOC_DEFAULT);
250 }
251 else if (sc.atLineStart && sc.Match("*** ")) {
252 sc.SetState(SCE_ASCIIDOC_ULIST_ITEM);
253 sc.Forward(3);
254 sc.SetState(SCE_ASCIIDOC_DEFAULT);
255 }
256 else if (sc.atLineStart && sc.Match("** ")) {
257 sc.SetState(SCE_ASCIIDOC_ULIST_ITEM);
258 sc.Forward(2);
259 sc.SetState(SCE_ASCIIDOC_DEFAULT);
260 }
261 else if (sc.atLineStart && sc.Match("* ")) {
262 sc.SetState(SCE_ASCIIDOC_ULIST_ITEM);
263 sc.Forward(1);
264 sc.SetState(SCE_ASCIIDOC_DEFAULT);
265 }
266 // Ordered list item
267 else if (sc.atLineStart && sc.Match("...... ")) {
268 sc.SetState(SCE_ASCIIDOC_OLIST_ITEM);
269 sc.Forward(6);
270 sc.SetState(SCE_ASCIIDOC_DEFAULT);
271 }
272 else if (sc.atLineStart && sc.Match("..... ")) {
273 sc.SetState(SCE_ASCIIDOC_OLIST_ITEM);
274 sc.Forward(5);
275 sc.SetState(SCE_ASCIIDOC_DEFAULT);
276 }
277 else if (sc.atLineStart && sc.Match(".... ")) {
278 sc.SetState(SCE_ASCIIDOC_OLIST_ITEM);
279 sc.Forward(4);
280 sc.SetState(SCE_ASCIIDOC_DEFAULT);
281 }
282 else if (sc.atLineStart && sc.Match("... ")) {
283 sc.SetState(SCE_ASCIIDOC_OLIST_ITEM);
284 sc.Forward(3);
285 sc.SetState(SCE_ASCIIDOC_DEFAULT);
286 }
287 else if (sc.atLineStart && sc.Match(".. ")) {
288 sc.SetState(SCE_ASCIIDOC_OLIST_ITEM);
289 sc.Forward(2);
290 sc.SetState(SCE_ASCIIDOC_DEFAULT);
291 }
292 else if (sc.atLineStart && sc.Match(". ")) {
293 sc.SetState(SCE_ASCIIDOC_OLIST_ITEM);
294 sc.Forward(1);
295 sc.SetState(SCE_ASCIIDOC_DEFAULT);
296 }
297 // Blockquote
298 else if (sc.atLineStart && sc.Match("> ")) {
299 sc.SetState(SCE_ASCIIDOC_BLOCKQUOTE);
300 sc.Forward();
301 }
302 // Link
303 else if (!sc.atLineStart && sc.ch == '[' && sc.chPrev != '\\' && sc.chNext != ' ') {
304 sc.Forward();
305 sc.SetState(SCE_ASCIIDOC_LINK);
306 freezeCursor = true;
307 }
308 // Code block
309 else if (sc.atLineStart && sc.Match("----") && IsNewline(sc.GetRelative(4))) {
310 sc.SetState(SCE_ASCIIDOC_CODEBK);
311 sc.Forward(4);
312 }
313 // Passthrough block
314 else if (sc.atLineStart && sc.Match("++++") && IsNewline(sc.GetRelative(4))) {
315 sc.SetState(SCE_ASCIIDOC_PASSBK);
316 sc.Forward(4);
317 }
318 // Comment block
319 else if (sc.atLineStart && sc.Match("////") && IsNewline(sc.GetRelative(4))) {
320 sc.SetState(SCE_ASCIIDOC_COMMENTBK);
321 sc.Forward(4);
322 }
323 // Comment
324 else if (sc.atLineStart && sc.Match("//")) {
325 sc.SetState(SCE_ASCIIDOC_COMMENT);
326 sc.Forward();
327 }
328 // Literal
329 else if (sc.ch == '+' && sc.chPrev != '\\' && sc.chNext != ' ' && AtTermStart(sc)) {
330 sc.Forward();
331 sc.SetState(SCE_ASCIIDOC_LITERAL);
332 freezeCursor = true;
333 }
334 // Literal block
335 else if (sc.atLineStart && sc.Match("....") && IsNewline(sc.GetRelative(4))) {
336 sc.SetState(SCE_ASCIIDOC_LITERALBK);
337 sc.Forward(4);
338 }
339 // Attribute
340 else if (sc.atLineStart && sc.ch == ':' && sc.chNext != ' ') {
341 sc.SetState(SCE_ASCIIDOC_ATTRIB);
342 }
343 // Strong
344 else if (sc.Match("**") && sc.GetRelative(2) != ' ') {
345 sc.SetState(SCE_ASCIIDOC_STRONG2);
346 sc.Forward();
347 }
348 else if (sc.ch == '*' && sc.chNext != ' ' && AtTermStart(sc)) {
349 sc.SetState(SCE_ASCIIDOC_STRONG1);
350 }
351 // Emphasis
352 else if (sc.Match("__") && sc.GetRelative(2) != ' ') {
353 sc.SetState(SCE_ASCIIDOC_EM2);
354 sc.Forward();
355 }
356 else if (sc.ch == '_' && sc.chNext != ' ' && AtTermStart(sc)) {
357 sc.SetState(SCE_ASCIIDOC_EM1);
358 }
359 // Macro
360 else if (sc.atLineStart && sc.ch == '[' && sc.chNext != ' ') {
361 sc.Forward();
362 sc.SetState(SCE_ASCIIDOC_MACRO);
363 freezeCursor = true;
364 }
365 else {
366 int i = 0;
367 bool found = false;
368 while (!found && MacroList[i].name != NULL) {
369 if (MacroList[i].start)
370 found = sc.atLineStart && sc.Match(MacroList[i].name);
371 else
372 found = sc.Match(MacroList[i].name);
373 if (found) {
374 sc.SetState(SCE_ASCIIDOC_MACRO);
375 sc.Forward(MacroList[i].len1);
376 sc.SetState(SCE_ASCIIDOC_DEFAULT);
377 if (MacroList[i].len2 > 1)
378 sc.Forward(MacroList[i].len2 - 1);
379 }
380 i++;
381 }
382 }
383 break;
384 }
385 // Advance if not holding back the cursor for this iteration.
386 if (!freezeCursor)
387 sc.Forward();
388 freezeCursor = false;
389 }
390 sc.Complete();
391}
392
393LexerModule lmAsciidoc(SCLEX_ASCIIDOC, ColorizeAsciidocDoc, "asciidoc");
394