1// Scintilla source code edit control 2/** @file LexYAML.cxx 3 ** Lexer for YAML. 4 **/ 5// Copyright 2003- by Sean O'Dell <sean@celsoft.com> 6// Release under the same license as Scintilla/SciTE. 7 8#include <stdlib.h> 9#include <string.h> 10#include <ctype.h> 11#include <stdio.h> 12#include <stdarg.h> 13 14#include "Platform.h" 15 16#include "PropSet.h" 17#include "Accessor.h" 18#include "StyleContext.h" 19#include "KeyWords.h" 20#include "Scintilla.h" 21#include "SciLexer.h" 22 23static const char * const yamlWordListDesc[] = { 24 "Keywords", 25 0 26}; 27 28static inline bool AtEOL(Accessor &styler, unsigned int i) { 29 return (styler[i] == '\n') || 30 ((styler[i] == '\r') && (styler.SafeGetCharAt(i + 1) != '\n')); 31} 32 33static unsigned int SpaceCount(char* lineBuffer) { 34 if (lineBuffer == NULL) 35 return 0; 36 37 char* headBuffer = lineBuffer; 38 39 while (*headBuffer == ' ') 40 headBuffer++; 41 42 return headBuffer - lineBuffer; 43} 44 45#define YAML_STATE_BITSIZE 16 46#define YAML_STATE_MASK (0xFFFF0000) 47#define YAML_STATE_DOCUMENT (1 << YAML_STATE_BITSIZE) 48#define YAML_STATE_VALUE (2 << YAML_STATE_BITSIZE) 49#define YAML_STATE_COMMENT (3 << YAML_STATE_BITSIZE) 50#define YAML_STATE_TEXT_PARENT (4 << YAML_STATE_BITSIZE) 51#define YAML_STATE_TEXT (5 << YAML_STATE_BITSIZE) 52 53static void ColouriseYAMLLine( 54 char *lineBuffer, 55 unsigned int currentLine, 56 unsigned int lengthLine, 57 unsigned int startLine, 58 unsigned int endPos, 59 WordList &keywords, 60 Accessor &styler) { 61 62 unsigned int i = 0; 63 bool bInQuotes = false; 64 unsigned int indentAmount = SpaceCount(lineBuffer); 65 66 if (currentLine > 0) { 67 int parentLineState = styler.GetLineState(currentLine - 1); 68 69 if ((parentLineState&YAML_STATE_MASK) == YAML_STATE_TEXT || (parentLineState&YAML_STATE_MASK) == YAML_STATE_TEXT_PARENT) { 70 unsigned int parentIndentAmount = parentLineState&(~YAML_STATE_MASK); 71 if (indentAmount > parentIndentAmount) { 72 styler.SetLineState(currentLine, YAML_STATE_TEXT | parentIndentAmount); 73 styler.ColourTo(endPos, SCE_YAML_TEXT); 74 return; 75 } 76 } 77 } 78 styler.SetLineState(currentLine, 0); 79 if (strncmp(lineBuffer, "---", 3) == 0) { // Document marker 80 styler.SetLineState(currentLine, YAML_STATE_DOCUMENT); 81 styler.ColourTo(endPos, SCE_YAML_DOCUMENT); 82 return; 83 } 84 // Skip initial spaces 85 while ((i < lengthLine) && lineBuffer[i] == ' ') { // YAML always uses space, never TABS or anything else 86 i++; 87 } 88 if (lineBuffer[i] == '\t') { // if we skipped all spaces, and we are NOT inside a text block, this is wrong 89 styler.ColourTo(endPos, SCE_YAML_ERROR); 90 return; 91 } 92 if (lineBuffer[i] == '#') { // Comment 93 styler.SetLineState(currentLine, YAML_STATE_COMMENT); 94 styler.ColourTo(endPos, SCE_YAML_COMMENT); 95 return; 96 } 97 while (i < lengthLine) { 98 if (lineBuffer[i] == '\'' || lineBuffer[i] == '\"') { 99 bInQuotes = !bInQuotes; 100 } else if (lineBuffer[i] == ':' && !bInQuotes) { 101 styler.ColourTo(startLine + i, SCE_YAML_IDENTIFIER); 102 // Non-folding scalar 103 i++; 104 while ((i < lengthLine) && isspacechar(lineBuffer[i])) 105 i++; 106 unsigned int endValue = lengthLine - 1; 107 while ((endValue >= i) && isspacechar(lineBuffer[endValue])) 108 endValue--; 109 lineBuffer[endValue + 1] = '\0'; 110 if (lineBuffer[i] == '|' || lineBuffer[i] == '>') { 111 i++; 112 if (lineBuffer[i] == '+' || lineBuffer[i] == '-') 113 i++; 114 while ((i < lengthLine) && isspacechar(lineBuffer[i])) 115 i++; 116 if (lineBuffer[i] == '\0') { 117 styler.SetLineState(currentLine, YAML_STATE_TEXT_PARENT | indentAmount); 118 styler.ColourTo(endPos, SCE_YAML_DEFAULT); 119 return; 120 } else if (lineBuffer[i] == '#') { 121 styler.SetLineState(currentLine, YAML_STATE_TEXT_PARENT | indentAmount); 122 styler.ColourTo(startLine + i - 1, SCE_YAML_DEFAULT); 123 styler.ColourTo(endPos, SCE_YAML_COMMENT); 124 return; 125 } else { 126 styler.ColourTo(endPos, SCE_YAML_ERROR); 127 return; 128 } 129 } 130 styler.SetLineState(currentLine, YAML_STATE_VALUE); 131 if (lineBuffer[i] == '&' || lineBuffer[i] == '*') { 132 styler.ColourTo(endPos, SCE_YAML_REFERENCE); 133 return; 134 } 135 if (keywords.InList(&lineBuffer[i])) { // Convertible value (true/false, etc.) 136 styler.ColourTo(endPos, SCE_YAML_KEYWORD); 137 return; 138 } else { 139 unsigned int i2 = i; 140 while ((i < lengthLine) && lineBuffer[i]) { 141 if (!isdigit(lineBuffer[i]) && lineBuffer[i] != '-' && lineBuffer[i] != '.' && lineBuffer[i] != ',') { 142 styler.ColourTo(endPos, SCE_YAML_DEFAULT); 143 return; 144 } 145 i++; 146 } 147 if (i > i2) { 148 styler.ColourTo(endPos, SCE_YAML_NUMBER); 149 return; 150 } 151 } 152 break; // shouldn't get here, but just in case, the rest of the line is coloured the default 153 } 154 i++; 155 } 156 styler.ColourTo(endPos, SCE_YAML_DEFAULT); 157} 158 159static void ColouriseYAMLDoc(unsigned int startPos, int length, int, WordList *keywordLists[], Accessor &styler) { 160 char lineBuffer[1024]; 161 styler.StartAt(startPos); 162 styler.StartSegment(startPos); 163 unsigned int linePos = 0; 164 unsigned int startLine = startPos; 165 unsigned int endPos = startPos + length; 166 unsigned int maxPos = styler.Length(); 167 unsigned int lineCurrent = styler.GetLine(startPos); 168 169 for (unsigned int i = startPos; i < maxPos && i < endPos; i++) { 170 lineBuffer[linePos++] = styler[i]; 171 if (AtEOL(styler, i) || (linePos >= sizeof(lineBuffer) - 1)) { 172 // End of line (or of line buffer) met, colourise it 173 lineBuffer[linePos] = '\0'; 174 ColouriseYAMLLine(lineBuffer, lineCurrent, linePos, startLine, i, *keywordLists[0], styler); 175 linePos = 0; 176 startLine = i + 1; 177 lineCurrent++; 178 } 179 } 180 if (linePos > 0) { // Last line does not have ending characters 181 ColouriseYAMLLine(lineBuffer, lineCurrent, linePos, startLine, startPos + length - 1, *keywordLists[0], styler); 182 } 183} 184 185static bool IsCommentLine(int line, Accessor &styler) { 186 int pos = styler.LineStart(line); 187 if (styler[pos] == '#') 188 return true; 189 return false; 190} 191 192static void FoldYAMLDoc(unsigned int startPos, int length, int /*initStyle - unused*/, 193 WordList *[], Accessor &styler) { 194 const int maxPos = startPos + length; 195 const int maxLines = styler.GetLine(maxPos - 1); // Requested last line 196 const int docLines = styler.GetLine(styler.Length() - 1); // Available last line 197 const bool foldComment = styler.GetPropertyInt("fold.comment.yaml") != 0; 198 199 // Backtrack to previous non-blank line so we can determine indent level 200 // for any white space lines 201 // and so we can fix any preceding fold level (which is why we go back 202 // at least one line in all cases) 203 int spaceFlags = 0; 204 int lineCurrent = styler.GetLine(startPos); 205 int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL); 206 while (lineCurrent > 0) { 207 lineCurrent--; 208 indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL); 209 if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG) && 210 (!IsCommentLine(lineCurrent, styler))) 211 break; 212 } 213 int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK; 214 215 // Set up initial loop state 216 int prevComment = 0; 217 if (lineCurrent >= 1) 218 prevComment = foldComment && IsCommentLine(lineCurrent - 1, styler); 219 220 // Process all characters to end of requested range 221 // or comment that hangs over the end of the range. Cap processing in all cases 222 // to end of document (in case of unclosed comment at end). 223 while ((lineCurrent <= docLines) && ((lineCurrent <= maxLines) || prevComment)) { 224 225 // Gather info 226 int lev = indentCurrent; 227 int lineNext = lineCurrent + 1; 228 int indentNext = indentCurrent; 229 if (lineNext <= docLines) { 230 // Information about next line is only available if not at end of document 231 indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL); 232 } 233 const int comment = foldComment && IsCommentLine(lineCurrent, styler); 234 const int comment_start = (comment && !prevComment && (lineNext <= docLines) && 235 IsCommentLine(lineNext, styler) && (lev > SC_FOLDLEVELBASE)); 236 const int comment_continue = (comment && prevComment); 237 if (!comment) 238 indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK; 239 if (indentNext & SC_FOLDLEVELWHITEFLAG) 240 indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel; 241 242 if (comment_start) { 243 // Place fold point at start of a block of comments 244 lev |= SC_FOLDLEVELHEADERFLAG; 245 } else if (comment_continue) { 246 // Add level to rest of lines in the block 247 lev = lev + 1; 248 } 249 250 // Skip past any blank lines for next indent level info; we skip also 251 // comments (all comments, not just those starting in column 0) 252 // which effectively folds them into surrounding code rather 253 // than screwing up folding. 254 255 while ((lineNext < docLines) && 256 ((indentNext & SC_FOLDLEVELWHITEFLAG) || 257 (lineNext <= docLines && IsCommentLine(lineNext, styler)))) { 258 259 lineNext++; 260 indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL); 261 } 262 263 const int levelAfterComments = indentNext & SC_FOLDLEVELNUMBERMASK; 264 const int levelBeforeComments = Platform::Maximum(indentCurrentLevel,levelAfterComments); 265 266 // Now set all the indent levels on the lines we skipped 267 // Do this from end to start. Once we encounter one line 268 // which is indented more than the line after the end of 269 // the comment-block, use the level of the block before 270 271 int skipLine = lineNext; 272 int skipLevel = levelAfterComments; 273 274 while (--skipLine > lineCurrent) { 275 int skipLineIndent = styler.IndentAmount(skipLine, &spaceFlags, NULL); 276 277 if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments) 278 skipLevel = levelBeforeComments; 279 280 int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG; 281 282 styler.SetLevel(skipLine, skipLevel | whiteFlag); 283 } 284 285 // Set fold header on non-comment line 286 if (!comment && !(indentCurrent & SC_FOLDLEVELWHITEFLAG) ) { 287 if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) 288 lev |= SC_FOLDLEVELHEADERFLAG; 289 } 290 291 // Keep track of block comment state of previous line 292 prevComment = comment_start || comment_continue; 293 294 // Set fold level for this line and move to next line 295 styler.SetLevel(lineCurrent, lev); 296 indentCurrent = indentNext; 297 lineCurrent = lineNext; 298 } 299 300 // NOTE: Cannot set level of last line here because indentCurrent doesn't have 301 // header flag set; the loop above is crafted to take care of this case! 302 //styler.SetLevel(lineCurrent, indentCurrent); 303} 304 305LexerModule lmYAML(SCLEX_YAML, ColouriseYAMLDoc, "yaml", FoldYAMLDoc, yamlWordListDesc); 306