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 | |
33 | using namespace Lexilla; |
34 | |
35 | namespace { |
36 | |
37 | typedef struct { |
38 | bool start; |
39 | int len1; |
40 | int len2; |
41 | const char *name; |
42 | } MacroItem; |
43 | |
44 | static 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 | |
75 | constexpr 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 | |
82 | static bool AtTermStart(StyleContext &sc) { |
83 | return sc.currentPos == 0 || sc.chPrev == 0 || isspacechar(sc.chPrev); |
84 | } |
85 | |
86 | static 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 | |
393 | LexerModule lmAsciidoc(SCLEX_ASCIIDOC, ColorizeAsciidocDoc, "asciidoc" ); |
394 | |