| 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 | |