/*------------------------------------------------------------------------------------ this file is part of notepad++ Copyright (C)2003 Don HO < donho@altern.org > This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ----------------------------------------------------------------------------------------*/ // #include #include #include #include // #include // #include // #include #include #include #include "Platform.h" #include "ILexer.h" #include "LexAccessor.h" #include "Accessor.h" #include "StyleContext.h" #include "WordList.h" #include "Scintilla.h" #include "SciLexer.h" #include "CharClassify.h" #include "LexerModule.h" #include "PropSetSimple.h" #include "SplitVector.h" #include "Partitioning.h" #include "RunStyles.h" #include "CellBuffer.h" // #include "PerLine.h" #include "Decoration.h" #include "Document.h" #ifdef SCI_NAMESPACE using namespace Scintilla; #endif #define CL_CURRENT 0x1 #define CL_PREV 0x2 #define CL_PREVPREV 0x4 #define FOLD_NONE 0 #define FOLD_OPEN 1 #define FOLD_MIDDLE 2 #define FOLD_CLOSE 3 #define COMMENTLINE_NO 0 #define COMMENTLINE_YES 1 #define COMMENTLINE_SKIP_TESTING 2 #define NI_OPEN 0 #define NI_CLOSE 1 #define NBR_DECIMAL 0 #define NBR_EXTRA_CHAR 1 #define NBR_RANGE_CHAR 2 #define NBR_PREFIX_CHAR 3 #define NBR_SUFFIX_CHAR 4 #define NBR_WHITESPACE 5 #define NBR_FOLLOWED_BY_FORWARD_KEYWORD 6 #define NBR_NOT_A_NUMBER 7 #define NO_DELIMITER 0 #define FORWARD_WHITESPACE_FOUND 1 #define FORWARD_KEYWORD_FOUND 2 #define SC_ISCOMMENTLINE 0x8000 #define MULTI_PART_LIMIT 100 #define MAPPER_TOTAL 15 #define FW_VECTORS_TOTAL SCE_USER_TOTAL_DELIMITERS + 6 const int maskMapper[MAPPER_TOTAL] = { SCE_USER_MASK_NESTING_OPERATORS2, SCE_USER_MASK_NESTING_FOLDERS_IN_CODE2_OPEN, SCE_USER_MASK_NESTING_FOLDERS_IN_CODE2_MIDDLE, SCE_USER_MASK_NESTING_FOLDERS_IN_CODE2_CLOSE, SCE_USER_MASK_NESTING_FOLDERS_IN_COMMENT_OPEN, SCE_USER_MASK_NESTING_FOLDERS_IN_COMMENT_MIDDLE, SCE_USER_MASK_NESTING_FOLDERS_IN_COMMENT_CLOSE, SCE_USER_MASK_NESTING_KEYWORD1, SCE_USER_MASK_NESTING_KEYWORD2, SCE_USER_MASK_NESTING_KEYWORD3, SCE_USER_MASK_NESTING_KEYWORD4, SCE_USER_MASK_NESTING_KEYWORD5, SCE_USER_MASK_NESTING_KEYWORD6, SCE_USER_MASK_NESTING_KEYWORD7, SCE_USER_MASK_NESTING_KEYWORD8 }; const int styleMapper[MAPPER_TOTAL] = { SCE_USER_STYLE_OPERATOR, SCE_USER_STYLE_FOLDER_IN_CODE2, SCE_USER_STYLE_FOLDER_IN_CODE2, SCE_USER_STYLE_FOLDER_IN_CODE2, SCE_USER_STYLE_FOLDER_IN_COMMENT, SCE_USER_STYLE_FOLDER_IN_COMMENT, SCE_USER_STYLE_FOLDER_IN_COMMENT, SCE_USER_STYLE_KEYWORD1, SCE_USER_STYLE_KEYWORD2, SCE_USER_STYLE_KEYWORD3, SCE_USER_STYLE_KEYWORD4, SCE_USER_STYLE_KEYWORD5, SCE_USER_STYLE_KEYWORD6, SCE_USER_STYLE_KEYWORD7, SCE_USER_STYLE_KEYWORD8 }; const int foldingtMapper[MAPPER_TOTAL] = { FOLD_NONE, FOLD_OPEN, FOLD_MIDDLE, FOLD_CLOSE, FOLD_OPEN, FOLD_MIDDLE, FOLD_CLOSE, FOLD_NONE, FOLD_NONE, FOLD_NONE, FOLD_NONE, FOLD_NONE, FOLD_NONE, FOLD_NONE, FOLD_NONE }; using namespace std; typedef vector> vvstring; // static vector * foldVectorStatic; // foldVectorStatic is used for debugging only, it should be commented out in production code ! struct forwardStruct { vvstring * vec; int sceID; int maskID; forwardStruct(): vec(0), sceID(0), maskID(0) {}; // constructor, useless but obligatory forwardStruct * Set (vvstring * vec, int sceID, int maskID) { this->vec = vec; this->sceID = sceID; this->maskID = maskID; return this; } }FWS; // just one instance struct nestedInfo { unsigned int position; int nestedLevel; int index; int state; int opener; // constructor, useless but obligatory nestedInfo():position(0), nestedLevel(0), index(0), state(0), opener(0) {}; nestedInfo * Set (unsigned int position, int nestedLevel, int index, int state, int opener) { this->position = position; this->nestedLevel = nestedLevel; this->index = index; this->state = state; this->opener = opener; return this; } }; static nestedInfo NI; // also just one instance struct udlKeywordsMapStruct { vvstring commentLineOpen, commentLineContinue, commentLineClose; vvstring commentOpen, commentClose; vvstring delim1Open, delim1Escape, delim1Close; vvstring delim2Open, delim2Escape, delim2Close; vvstring delim3Open, delim3Escape, delim3Close; vvstring delim4Open, delim4Escape, delim4Close; vvstring delim5Open, delim5Escape, delim5Close; vvstring delim6Open, delim6Escape, delim6Close; vvstring delim7Open, delim7Escape, delim7Close; vvstring delim8Open, delim8Escape, delim8Close; vvstring operators1; vvstring foldersInCode1Open, foldersInCode1Middle, foldersInCode1Close; vvstring foldersInCode2Open, foldersInCode2Middle, foldersInCode2Close; vector suffixTokens; vector prefixTokens; vector negativePrefixTokens; vector extrasInPrefixedTokens; vector rangeTokens; }; // key value is of type "int" so it could receive pointer value !! // UDL name is defined as "const char *" in UserLangContainer class // so, map will use pointer value (not value pointed to!) as the key typedef map udlMapType; static udlMapType udlKeywordsMap; // key value is of type "int" so it could receive pointer value !! // currentBufferID is defined as "Buffer *" in ScintillaEditView class // so, map will use pointer value (not value pointed to!) as the key typedef map > nestedMapType; static nestedMapType nestedMap; static inline bool isWhiteSpace(const int ch) { return (ch > 0 && ch < 0x21); } static inline bool isWhiteSpace2(unsigned char ch, int & nlCount, unsigned char excludeNewLine=0, unsigned char chNext=0) { // multi-part keywords come in two flavors: // 1. "else if" (internally mapped to "else\vif") where '\v' can be replaced by spaces, tabs and new lines // 2. 'else if" (internally mapped to "else\bif") where '\b' can be replaced by spaces, tabs but not new lines // 'excludeNewLine' parameter is used to differentiate the two if ((ch == '\n') || (ch == '\r' && chNext != '\n')) ++nlCount; if (excludeNewLine == '\b') return (ch == ' ') || (ch == '\t'); else return isWhiteSpace(ch); } static bool isInListForward2(vvstring * fwEndVectors[], StyleContext & sc, bool ignoreCase, int forward) { // forward check for multi-part keywords and numbers // this is differnt from 'isInListForward' function because // search for keyword is not performed at sc.currentPos but rather // at some position forward of sc.currentPos vvstring::iterator iter1;// = openVector.begin(); vector::iterator iter2; string::iterator iter3; int index = 0; int a = 0; int b = 0; for (int i=0; iempty()) { // vvstring::iterator iter1 = fwEndVectors[i].begin(); // vector::iterator iter2; // string::iterator iter3; index = 0; a = 0; b = 0; for (iter1 = fwEndVectors[i]->begin(); iter1 != fwEndVectors[i]->end(); ++iter1) { iter2 = iter1->begin(); for (; iter2 != iter1->end(); ++iter2) { iter3 = iter2->begin(); index = 0; for (; ; ++iter3) { a = ignoreCase?toupper(*iter3):*iter3; b = ignoreCase?toupper(sc.GetRelative(forward + index++)):sc.GetRelative(forward + index++); if (a != b) break; if (iter3 != iter2->end()) return true; } } } } } return false; } static inline bool IsADigit(char ch) { return isascii(ch) && isdigit(ch); } static inline int isADigit(StyleContext & sc, bool ignoreCase, vector & rangeTokens, bool hasPrefix, vector & extrasInPrefixedTokens, vector & prefixTokens, vector & suffixTokens, int & moveForward, char & chNext, vvstring * fwEndVectors[]) { moveForward = 0; chNext = 0; vector::iterator iter; if (hasPrefix) { iter = extrasInPrefixedTokens.begin(); for (; iter != extrasInPrefixedTokens.end(); ++iter) if (ignoreCase ? sc.MatchIgnoreCase2(iter->c_str()) : sc.Match(iter->c_str())) return NBR_EXTRA_CHAR; } iter = rangeTokens.begin(); for (; iter != rangeTokens.end(); ++iter) { if (ignoreCase ? sc.MatchIgnoreCase2(iter->c_str()) : sc.Match(iter->c_str())) { moveForward = iter->length(); chNext = sc.GetRelative(moveForward); return NBR_RANGE_CHAR; } } iter = suffixTokens.begin(); for (; iter != suffixTokens.end(); ++iter) { if (ignoreCase ? sc.MatchIgnoreCase2(iter->c_str()) : sc.Match(iter->c_str())) { moveForward = iter->length(); chNext = sc.GetRelative(moveForward); return NBR_SUFFIX_CHAR; } } iter = prefixTokens.begin(); for (; iter != prefixTokens.end(); ++iter) { if (ignoreCase ? sc.MatchIgnoreCase2(iter->c_str()) : sc.Match(iter->c_str())) { moveForward = iter->length(); chNext = sc.GetRelative(moveForward); return NBR_PREFIX_CHAR; } } if (true == isInListForward2(fwEndVectors, sc, ignoreCase, 0)) return NBR_FOLLOWED_BY_FORWARD_KEYWORD; if (IsADigit(sc.ch)) return NBR_DECIMAL; if (true == isWhiteSpace(sc.ch)) return NBR_WHITESPACE; return NBR_NOT_A_NUMBER; } static bool IsNumber(vector & prefixTokens, vector & negativePrefixTokens, StyleContext & sc, bool ignoreCase, bool & hasPrefix, bool & dontMove, bool & hasDot) { // is it a prefixed number? vector::iterator iter = prefixTokens.begin(); vector::iterator last = prefixTokens.end(); if (sc.ch == '-') { iter = negativePrefixTokens.begin(); last = negativePrefixTokens.end(); } for (; iter != last; ++iter) { if (ignoreCase?sc.MatchIgnoreCase2(iter->c_str()) : sc.Match(iter->c_str())) break; } if (iter != last) { sc.SetState(SCE_USER_STYLE_NUMBER); // prefix is styled as number only if followed by an actual number // sc.ChangeState(SCE_USER_STYLE_NUMBER); sc.Forward(iter->length()); hasPrefix = true; dontMove = true; return true; } // or is it a simple digit? if (IsADigit(sc.ch) || ((sc.ch == '-' || sc.ch == '+') && IsADigit(sc.chNext) && !IsADigit(sc.chPrev))) { sc.SetState(SCE_USER_STYLE_NUMBER); hasPrefix = true; // prefix in the middle is an error case, treat any number as it it had a prefix return true; } else if (sc.ch == '.' && IsADigit(sc.chNext)) { sc.SetState(SCE_USER_STYLE_NUMBER); hasPrefix = true; hasDot = true; return true; } return false; } static inline void SubGroup(const char * s, vvstring & vec, bool group=false) { unsigned int length = strlen(s); char * temp = new char[length+1]; unsigned int index = 0; vector subvector; unsigned int i = 0; for (unsigned int j=0; j 0) i += 1; // skip space i += 2; // skip prefix copy = true; if (s[i] == ' ') continue; if (s[i] == '(' && s[i+1] == '(') inGroup = true; } if (inGroup && s[i] == ')' && s[i+1] == ')') inGroup = false; if (copy) temp[index++] = s[i]; } if (length) SubGroup(temp, vec, inGroup); vector emptyVector; for (int i = vec.size(); i < minLength; ++i) { vec.push_back(emptyVector); } delete [] temp; } static inline void StringToVector(char * original, vector & tokenVector, bool negative=false) { // this is rarely used, so I chose std::string for simplicity reasons // for better performance C-strings could be used string temp = ""; char * pch = original; while (*pch != NULL) { if (*pch != ' ') temp += *pch; // else if (temp.size() > 0) { if (negative) tokenVector.push_back("-" + temp); else tokenVector.push_back(temp); temp = ""; } ++pch; } if (temp.size() > 0) { if (negative) tokenVector.push_back("-" + temp); else tokenVector.push_back(temp); } } static inline void ReColoringCheck(unsigned int & startPos, unsigned int & nestedLevel, int & initStyle, int & openIndex, int & isCommentLine, bool & isInComment, Accessor & styler, vector & lastNestedGroup, vector & nestedVector, /* vector & foldVector, */ int & continueCommentBlock) { // re-coloring always starts at line beginning !! // special exception for multipart keywords initStyle = styler.StyleAt(startPos-1); // check style of previous new line character if ( (initStyle >= SCE_USER_STYLE_KEYWORD1 && initStyle < (SCE_USER_STYLE_KEYWORD1+SCE_USER_TOTAL_KEYWORD_GROUPS)) // keywords1-8 || initStyle == SCE_USER_STYLE_FOLDER_IN_COMMENT || initStyle == SCE_USER_STYLE_FOLDER_IN_CODE2 ) { // we are in middle of multi-part keyword that contains newline characters, go back until current style ends while (startPos >= 0 && styler.StyleAt(--startPos) == initStyle); } if (static_cast(startPos) < 0) startPos = 0; if (startPos > 0) { // go back until first EOL char char ch = 0; do { ch = styler.SafeGetCharAt(--startPos); if (startPos == -1) startPos = 0; } while(ch != '\r' && ch != '\n' && startPos > 0); if (startPos > 0) startPos += 1; // compensate for decrement operation } if (startPos == 0) { // foldVector.clear(); nestedVector.clear(); lastNestedGroup.clear(); initStyle = SCE_USER_STYLE_IDENTIFIER; return; } // clear all data on positions forward of 'startPos' as we // are about to re-color that part of the document. vector::iterator iter = nestedVector.begin(); for (; iter != nestedVector.end(); ++iter) { if (iter->position >= startPos) { nestedVector.erase(iter, nestedVector.end()); break; } } if (!nestedVector.empty()) { // go back to last nesting level '1' (or beginning of vector if no level '1' is found) iter = --nestedVector.end(); lastNestedGroup.clear(); while (iter->nestedLevel > 1 && iter != nestedVector.begin()) --iter; } else { iter = nestedVector.end(); } // recreate lastNestedGroup, skip adjecent OPEN/CLOSE pairs // nesting group is something like: // "first delimiter 'nested delimiter 1 /*nested delimiter 2*/ delimiter 1 again' first delimiter again" // if user is editing somewhere inside this group, than 'lastNestedGroup' provides info about nesting // this is much more convinient that trying to obtain the same info from 'nestedVector' vector::iterator last; while (iter != nestedVector.end()) { if (iter->opener == NI_OPEN) lastNestedGroup.push_back(*iter); else if (iter->opener == NI_CLOSE && !lastNestedGroup.empty()) { last = --lastNestedGroup.end(); if (last->opener == NI_OPEN) if (last->nestedLevel == iter->nestedLevel) if (last->state == iter->state) if (last->index == iter->index) lastNestedGroup.erase(last); } ++iter; } if (!lastNestedGroup.empty()) { last = --lastNestedGroup.end(); initStyle = last->state; openIndex = last->index; nestedLevel = last->nestedLevel; // are we nested somewhere in comment? for (; ; --last) { if (last->state == SCE_USER_STYLE_COMMENT) { isInComment = true; isCommentLine = COMMENTLINE_YES; } if (last->state == SCE_USER_STYLE_COMMENTLINE) { isCommentLine = COMMENTLINE_YES; } if (last == lastNestedGroup.begin()) break; } } else { initStyle = SCE_USER_STYLE_IDENTIFIER; openIndex = -1; nestedLevel = 0; } // are we in fold block of comment lines? int lineCurrent = styler.GetLine(startPos); if ((styler.LevelAt(lineCurrent) & SC_ISCOMMENTLINE) != 0) continueCommentBlock |= CL_CURRENT; if (lineCurrent >= 1) if ((styler.LevelAt(lineCurrent - 1) & SC_ISCOMMENTLINE) != 0) continueCommentBlock |= CL_PREV; if (lineCurrent >= 2) if ((styler.LevelAt(lineCurrent - 2) & SC_ISCOMMENTLINE) != 0) if (continueCommentBlock & CL_PREV) continueCommentBlock |= CL_PREVPREV; // foldVector.erase(foldVector.begin() + lineCurrent, foldVector.end()); } static bool isInListForward(vvstring & openVector, StyleContext & sc, bool ignoreCase, int & openIndex, int & skipForward) { // forward check for standard (sigle part) keywords skipForward = 0; vector>::iterator iter1 = openVector.begin(); vector::iterator iter2; for (; iter1 != openVector.end(); ++iter1) { iter2 = iter1->begin(); for (; iter2 != iter1->end(); ++iter2) { if (ignoreCase?sc.MatchIgnoreCase2(iter2->c_str()):sc.Match(iter2->c_str())) { openIndex = iter1 - openVector.begin(); skipForward = iter2->length(); return true; } } } return false; } static bool isInListBackward(WordList & list, StyleContext & sc, bool specialMode, bool ignoreCase, int & moveForward, vvstring * fwEndVectors[], int & nlCount, unsigned int docLength) { // backward search // this function compares last identified 'word' (text surrounded by spaces or other forward keywords) // with all keywords within 'WordList' object // 'isInListBackward' can search for multi-part keywords too. Such keywords have variable length, // in case 'isInListBackward' finds such keywords it will set 'moveForward' parameter so algorythm could adjust position if (!list.words) return false; moveForward = 0; int offset = -1 * sc.LengthCurrent(); // length of 'word' that needs to be investigated unsigned char a = 0; // iterator for user defined keywords unsigned char b = 0; // iterator for text in the file unsigned char bNext = 0; unsigned char wsChar = 0; unsigned char firstChar = sc.GetRelative(offset); int fwDelimiterFound = NO_DELIMITER; int nlCountTemp = 0; int indexa = 0; int indexb = 0; int i = list.starts[firstChar]; bool doUpperLoop = ignoreCase; if (ignoreCase) { i = list.starts[tolower(firstChar)]; if (i == -1) { i = list.starts[toupper(firstChar)]; if (i == -1) return false; doUpperLoop = false; } } while (i >= 0) { while (static_cast(ignoreCase?toupper(list.words[i][0]):list.words[i][0]) == (ignoreCase?toupper(firstChar):firstChar)) { a = 0; b = 0; bNext = 0; indexa = 0; indexb = 0; wsChar = 0; fwDelimiterFound = NO_DELIMITER; do { a = static_cast(ignoreCase?toupper(list.words[i][indexa++]):list.words[i][indexa++]); if (a == '\v' || a == '\b') { wsChar = a; b = sc.GetRelative(offset + indexb++); bNext = sc.GetRelative(offset + indexb); if (isWhiteSpace2(b, nlCountTemp, wsChar, bNext)) { do { b = sc.GetRelative(offset + indexb++); bNext = sc.GetRelative(offset + indexb); } while((sc.currentPos + offset + indexb) <= docLength && isWhiteSpace2(b, nlCountTemp, wsChar, bNext)); a = static_cast(ignoreCase?toupper(list.words[i][indexa++]):list.words[i][indexa++]); } } else b = ignoreCase?toupper(sc.GetRelative(offset + indexb++)):sc.GetRelative(offset + indexb++); } while (a && (a == b)); if (!a) { --indexb; // decrement indexb to compensate for comparing with '\0' in previous loop if (wsChar) { // multi-part keyword is found, // but it must be followed by whitespace (or 'forward' keyword) // otherwise "else if" might wrongly match "else iff" bNext = sc.GetRelative(indexb + offset + 1); if (isWhiteSpace2(b, nlCountTemp, wsChar, bNext)) fwDelimiterFound = FORWARD_WHITESPACE_FOUND; if (fwDelimiterFound == NO_DELIMITER) { if (isInListForward2(fwEndVectors, sc, ignoreCase, indexb + offset)) { fwDelimiterFound = FORWARD_KEYWORD_FOUND; } } // special case when multi-part keywords have 'prefix' option enabled // then the next word in the text file must be treated as part of multi-part keyword // e.g. prefixed "else if" matches "else if nextWord", but not "else iffy" if (specialMode) { if (fwDelimiterFound == FORWARD_WHITESPACE_FOUND) // there must be a white space !! { // skip whitespace (all of it) int savedPosition = indexb; // return here if whitespace is not followed by another word for (;;) { if ((sc.currentPos + offset + indexb) > docLength) break; if (!isWhiteSpace2(sc.GetRelative(offset + indexb), nlCountTemp, wsChar, sc.GetRelative(offset + indexb + 1))) break; ++indexb; } // skip next "word" (if next word is not found, go back to end of multi-part keyword) // it is not necessary to check EOF position here, because sc.GetRelative returns ' ' beyond EOF bool nextWordFound = false; while (!isWhiteSpace2(sc.GetRelative(indexb + offset), nlCountTemp, wsChar, sc.GetRelative(offset + indexb + 1))) { if (isInListForward2(fwEndVectors, sc, ignoreCase, indexb + offset)) { break; } ++indexb; nextWordFound = true; } if (nextWordFound == false) indexb = savedPosition; } } } // keyword is read fully, decide if we can leave this function nlCount += nlCountTemp; moveForward = indexb + offset; // offset is already negative if (wsChar) { if (fwDelimiterFound != NO_DELIMITER) return true; // multi part keyword found } else if (moveForward == 0) return true; // single part keyword found else if (specialMode) return true; // prefixed single part keyword found } nlCountTemp = 0; ++i; } // run one more time for capital letter version if (doUpperLoop) { i = list.starts[toupper(firstChar)]; doUpperLoop = false; } else break; } return false; } static void setBackwards(WordList * kwLists[], StyleContext & sc, bool prefixes[], bool ignoreCase, int nestedKey, vvstring * fwEndVectors[], int & levelMinCurrent, int & levelNext, int & nlCount, bool & dontMove, unsigned int docLength) { if (sc.LengthCurrent() == 0) return; int folding = FOLD_NONE; int moveForward = 0; for (int i=0; i<=MAPPER_TOTAL; ++i) { if (nestedKey & maskMapper[i]) { if (isInListBackward(*kwLists[i], sc, prefixes[i], ignoreCase, moveForward, fwEndVectors, nlCount, docLength)) { folding = foldingtMapper[i]; if (moveForward > 0) { sc.Forward(moveForward); dontMove = true; } sc.ChangeState(styleMapper[i]); break; } } } if (folding == FOLD_MIDDLE) { // treat middle point as a sequence of: FOLD_CLOSE followed by FOLD_OPEN levelNext--; folding = FOLD_OPEN; } if (folding == FOLD_OPEN) { if (levelMinCurrent > levelNext) levelMinCurrent = levelNext; levelNext++; } else if (folding == FOLD_CLOSE) { levelNext--; } } static bool isInListNested(int nestedKey, vector & forwards, StyleContext & sc, bool ignoreCase, int & openIndex, int & skipForward, int & newState, bool lineCommentAtBOL ) { // check if some other delimiter is nested within current delimiter // all delimiters are freely checked but line comments must be synched with property 'lineCommentAtBOL' int backup = openIndex; vector::iterator iter = forwards.begin(); for (; iter != forwards.end(); ++iter) { if (nestedKey & iter->maskID) { if ((iter->maskID != SCE_USER_MASK_NESTING_COMMENT_LINE) || (iter->maskID == SCE_USER_MASK_NESTING_COMMENT_LINE && !(lineCommentAtBOL && !sc.atLineStart))) { if (isInListForward(*(iter->vec), sc, ignoreCase, openIndex, skipForward)) { newState = iter->sceID; return true; } } } } openIndex = backup; return false; } static void readLastNested(vector & lastNestedGroup, int & newState, int & openIndex) { // after delimiter ends we need to determine whether we are entering some other delimiter (in case of nesting) // or do we simply start over from default style. newState = SCE_USER_STYLE_IDENTIFIER; openIndex = -1; if (!lastNestedGroup.empty()) { lastNestedGroup.erase(lastNestedGroup.end()-1); if (!lastNestedGroup.empty()) { newState = (--lastNestedGroup.end())->state; openIndex = (--lastNestedGroup.end())->index; } } } static void ColouriseUserDoc(unsigned int startPos, int length, int initStyle, WordList *kwLists[], Accessor &styler) { bool lineCommentAtBOL = styler.GetPropertyInt("userDefine.forceLineCommentsAtBOL", 0) != 0; bool foldComments = styler.GetPropertyInt("userDefine.allowFoldOfComments", 0) != 0; bool ignoreCase = styler.GetPropertyInt("userDefine.isCaseIgnored", 0) != 0; bool foldCompact = styler.GetPropertyInt("userDefine.foldCompact", 0) != 0; bool prefixes[MAPPER_TOTAL]; for (int i=0; i>) objects is expensive, // therefore these objects are created only at beginning of file, and saved to // global std::map objects udlKeywordsMap and nestedMap int currentBufferID = styler.GetPropertyInt("userDefine.currentBufferID", 0); if (nestedMap.find(currentBufferID) == nestedMap.end()) { nestedMap[currentBufferID] = vector(); } vector & nestedVector = nestedMap[currentBufferID]; int sUdlName = styler.GetPropertyInt("userDefine.udlName", 0); if (udlKeywordsMap.find(sUdlName) == udlKeywordsMap.end()) { udlKeywordsMap[sUdlName] = udlKeywordsMapStruct(); } vvstring & commentLineOpen = udlKeywordsMap[sUdlName].commentLineOpen; vvstring & commentLineContinue = udlKeywordsMap[sUdlName].commentLineContinue; vvstring & commentLineClose = udlKeywordsMap[sUdlName].commentLineClose; vvstring & commentOpen = udlKeywordsMap[sUdlName].commentOpen; vvstring & commentClose = udlKeywordsMap[sUdlName].commentClose; vvstring & delim1Open = udlKeywordsMap[sUdlName].delim1Open; vvstring & delim1Escape = udlKeywordsMap[sUdlName].delim1Escape; vvstring & delim1Close = udlKeywordsMap[sUdlName].delim1Close; vvstring & delim2Open = udlKeywordsMap[sUdlName].delim2Open; vvstring & delim2Escape = udlKeywordsMap[sUdlName].delim2Escape; vvstring & delim2Close = udlKeywordsMap[sUdlName].delim2Close; vvstring & delim3Open = udlKeywordsMap[sUdlName].delim3Open; vvstring & delim3Escape = udlKeywordsMap[sUdlName].delim3Escape; vvstring & delim3Close = udlKeywordsMap[sUdlName].delim3Close; vvstring & delim4Open = udlKeywordsMap[sUdlName].delim4Open; vvstring & delim4Escape = udlKeywordsMap[sUdlName].delim4Escape; vvstring & delim4Close = udlKeywordsMap[sUdlName].delim4Close; vvstring & delim5Open = udlKeywordsMap[sUdlName].delim5Open; vvstring & delim5Escape = udlKeywordsMap[sUdlName].delim5Escape; vvstring & delim5Close = udlKeywordsMap[sUdlName].delim5Close; vvstring & delim6Open = udlKeywordsMap[sUdlName].delim6Open; vvstring & delim6Escape = udlKeywordsMap[sUdlName].delim6Escape; vvstring & delim6Close = udlKeywordsMap[sUdlName].delim6Close; vvstring & delim7Open = udlKeywordsMap[sUdlName].delim7Open; vvstring & delim7Escape = udlKeywordsMap[sUdlName].delim7Escape; vvstring & delim7Close = udlKeywordsMap[sUdlName].delim7Close; vvstring & delim8Open = udlKeywordsMap[sUdlName].delim8Open; vvstring & delim8Escape = udlKeywordsMap[sUdlName].delim8Escape; vvstring & delim8Close = udlKeywordsMap[sUdlName].delim8Close; vvstring & operators1 = udlKeywordsMap[sUdlName].operators1; vvstring & foldersInCode1Open = udlKeywordsMap[sUdlName].foldersInCode1Open; vvstring & foldersInCode1Middle = udlKeywordsMap[sUdlName].foldersInCode1Middle; vvstring & foldersInCode1Close = udlKeywordsMap[sUdlName].foldersInCode1Close; vector & extrasInPrefixedTokens = udlKeywordsMap[sUdlName].extrasInPrefixedTokens; vector & rangeTokens = udlKeywordsMap[sUdlName].rangeTokens; vector & negativePrefixTokens = udlKeywordsMap[sUdlName].negativePrefixTokens; vector & prefixTokens = udlKeywordsMap[sUdlName].prefixTokens; vector & suffixTokens = udlKeywordsMap[sUdlName].suffixTokens; if (startPos == 0) { // in keyword list objects, put longer multi-part string first, // e.g. "else if" should go in front of "else" bool equal = true; bool isMultiPart = false; bool switchPerformed = true; while (switchPerformed) { switchPerformed = false; for (int i=0; ilen; ++j) // for each keyword within object { equal = true; int z = 0; for (; kwLists[i]->words[j][z]; ++z) // for each letter within keyword { if (kwLists[i]->words[j+1][z] == '\v' || kwLists[i]->words[j+1][z] == '\b') isMultiPart = true; if (kwLists[i]->words[j][z] != kwLists[i]->words[j+1][z]) { equal = false; break; } } if (!isMultiPart) // is next word multi part keyword? { for (int k=0; kwLists[i]->words[j+1][k]; ++k) { if (kwLists[i]->words[j+1][k] == '\v' || kwLists[i]->words[j+1][k] == '\b') { isMultiPart = true; break; } } } if (equal && isMultiPart && kwLists[i]->words[j+1][z]) // perform switch only if next word is longer ! { char * temp = kwLists[i]->words[j]; kwLists[i]->words[j] = kwLists[i]->words[j+1]; kwLists[i]->words[j+1] = temp; switchPerformed = true; } } } } // if this is BOF, re-generate stuff in global map objects (udlKeywordsMap and nestedMap) const char * sFoldersInCode1Open = styler.pprops->Get("userDefine.foldersInCode1Open"); const char * sFoldersInCode1Middle = styler.pprops->Get("userDefine.foldersInCode1Middle"); const char * sFoldersInCode1Close = styler.pprops->Get("userDefine.foldersInCode1Close"); const char * sDelimiters = styler.pprops->Get("userDefine.delimiters"); const char * sOperators1 = styler.pprops->Get("userDefine.operators1"); const char * sComments = styler.pprops->Get("userDefine.comments"); // 'GenerateVector' converts strings into vvstring objects GenerateVector(commentLineOpen, sComments, TEXT("00"), 0); GenerateVector(commentLineContinue, sComments, TEXT("01"), commentLineOpen.size()); GenerateVector(commentLineClose, sComments, TEXT("02"), commentLineOpen.size()); GenerateVector(commentOpen, sComments, TEXT("03"), 0); GenerateVector(commentClose, sComments, TEXT("04"), commentOpen.size()); GenerateVector(delim1Open, sDelimiters, TEXT("00"), 0); GenerateVector(delim1Escape, sDelimiters, TEXT("01"), delim1Open.size()); GenerateVector(delim1Close, sDelimiters, TEXT("02"), delim1Open.size()); GenerateVector(delim2Open, sDelimiters, TEXT("03"), 0); GenerateVector(delim2Escape, sDelimiters, TEXT("04"), delim2Open.size()); GenerateVector(delim2Close, sDelimiters, TEXT("05"), delim2Open.size()); GenerateVector(delim3Open, sDelimiters, TEXT("06"), 0); GenerateVector(delim3Escape, sDelimiters, TEXT("07"), delim3Open.size()); GenerateVector(delim3Close, sDelimiters, TEXT("08"), delim3Open.size()); GenerateVector(delim4Open, sDelimiters, TEXT("09"), 0); GenerateVector(delim4Escape, sDelimiters, TEXT("10"), delim4Open.size()); GenerateVector(delim4Close, sDelimiters, TEXT("11"), delim4Open.size()); GenerateVector(delim5Open, sDelimiters, TEXT("12"), 0); GenerateVector(delim5Escape, sDelimiters, TEXT("13"), delim5Open.size()); GenerateVector(delim5Close, sDelimiters, TEXT("14"), delim5Open.size()); GenerateVector(delim6Open, sDelimiters, TEXT("15"), 0); GenerateVector(delim6Escape, sDelimiters, TEXT("16"), delim6Open.size()); GenerateVector(delim6Close, sDelimiters, TEXT("17"), delim6Open.size()); GenerateVector(delim7Open, sDelimiters, TEXT("18"), 0); GenerateVector(delim7Escape, sDelimiters, TEXT("19"), delim7Open.size()); GenerateVector(delim7Close, sDelimiters, TEXT("20"), delim7Open.size()); GenerateVector(delim8Open, sDelimiters, TEXT("21"), 0); GenerateVector(delim8Escape, sDelimiters, TEXT("22"), delim8Open.size()); GenerateVector(delim8Close, sDelimiters, TEXT("23"), delim8Open.size()); operators1.clear(); foldersInCode1Open.clear(); foldersInCode1Middle.clear(); foldersInCode1Close.clear(); SubGroup(sFoldersInCode1Open, foldersInCode1Open, true); SubGroup(sFoldersInCode1Middle, foldersInCode1Middle, true); SubGroup(sFoldersInCode1Close, foldersInCode1Close, true); SubGroup(sOperators1, operators1, true); char * numberRanges = (char *)styler.pprops->Get("userDefine.numberRanges"); char * extraCharsInPrefixed = (char *)styler.pprops->Get("userDefine.extraCharsInPrefixed"); char * numberPrefixes = (char *)styler.pprops->Get("userDefine.numberPrefixes"); char * numberSuffixes = (char *)styler.pprops->Get("userDefine.numberSuffixes"); negativePrefixTokens.clear(); prefixTokens.clear(); extrasInPrefixedTokens.clear(); rangeTokens.clear(); suffixTokens.clear(); // 'StringToVector' converts strings into vector objects StringToVector(numberPrefixes, prefixTokens); StringToVector(numberPrefixes, negativePrefixTokens, true); StringToVector(numberSuffixes, suffixTokens); StringToVector(extraCharsInPrefixed, extrasInPrefixedTokens); StringToVector(numberRanges, rangeTokens); } // forward strings are actually kept in forwardStruct's, this allows easy access to ScintillaID and MaskID // FWS is a single global object used only to create temporary forwardStruct objects that are copied into vector vector forwards; forwards.push_back(*FWS.Set(&delim1Open, SCE_USER_STYLE_DELIMITER1, SCE_USER_MASK_NESTING_DELIMITER1)); forwards.push_back(*FWS.Set(&delim2Open, SCE_USER_STYLE_DELIMITER2, SCE_USER_MASK_NESTING_DELIMITER2)); forwards.push_back(*FWS.Set(&delim3Open, SCE_USER_STYLE_DELIMITER3, SCE_USER_MASK_NESTING_DELIMITER3)); forwards.push_back(*FWS.Set(&delim4Open, SCE_USER_STYLE_DELIMITER4, SCE_USER_MASK_NESTING_DELIMITER4)); forwards.push_back(*FWS.Set(&delim5Open, SCE_USER_STYLE_DELIMITER5, SCE_USER_MASK_NESTING_DELIMITER5)); forwards.push_back(*FWS.Set(&delim6Open, SCE_USER_STYLE_DELIMITER6, SCE_USER_MASK_NESTING_DELIMITER6)); forwards.push_back(*FWS.Set(&delim7Open, SCE_USER_STYLE_DELIMITER7, SCE_USER_MASK_NESTING_DELIMITER7)); forwards.push_back(*FWS.Set(&delim8Open, SCE_USER_STYLE_DELIMITER8, SCE_USER_MASK_NESTING_DELIMITER8)); forwards.push_back(*FWS.Set(&commentOpen, SCE_USER_STYLE_COMMENT, SCE_USER_MASK_NESTING_COMMENT)); forwards.push_back(*FWS.Set(&commentLineOpen, SCE_USER_STYLE_COMMENTLINE, SCE_USER_MASK_NESTING_COMMENT_LINE)); forwards.push_back(*FWS.Set(&operators1, SCE_USER_STYLE_OPERATOR, SCE_USER_MASK_NESTING_OPERATORS1)); // keep delimiter open strings in an array for easier looping vvstring * delimStart[SCE_USER_TOTAL_DELIMITERS]; delimStart[0] = &delim1Open; delimStart[1] = &delim2Open; delimStart[2] = &delim3Open; delimStart[3] = &delim4Open; delimStart[4] = &delim5Open; delimStart[5] = &delim6Open; delimStart[6] = &delim7Open; delimStart[7] = &delim8Open; vvstring * fwEndVectors[FW_VECTORS_TOTAL]; // array of forward end vectors for multi-part forward search fwEndVectors[0] = &operators1; fwEndVectors[1] = &commentLineOpen; fwEndVectors[2] = &commentLineContinue; fwEndVectors[3] = &commentLineClose; fwEndVectors[4] = &commentOpen; fwEndVectors[5] = &commentClose; fwEndVectors[6] = &delim1Close; fwEndVectors[7] = &delim2Close; fwEndVectors[8] = &delim3Close; fwEndVectors[9] = &delim4Close; fwEndVectors[10] = &delim5Close; fwEndVectors[11] = &delim6Close; fwEndVectors[12] = &delim7Close; fwEndVectors[13] = &delim8Close; // keep delimiter escape/close strings in an array for easier looping vvstring * delimVectors[SCE_USER_TOTAL_DELIMITERS * 2]; delimVectors[0] = &delim1Escape; delimVectors[1] = &delim1Close; delimVectors[2] = &delim2Escape; delimVectors[3] = &delim2Close; delimVectors[4] = &delim3Escape; delimVectors[5] = &delim3Close; delimVectors[6] = &delim4Escape; delimVectors[7] = &delim4Close; delimVectors[8] = &delim5Escape; delimVectors[9] = &delim5Close; delimVectors[10] = &delim6Escape; delimVectors[11] = &delim6Close; delimVectors[12] = &delim7Escape; delimVectors[13] = &delim7Close; delimVectors[14] = &delim8Escape; delimVectors[15] = &delim8Close; // again, loops make our lifes easier int delimNestings[SCE_USER_TOTAL_DELIMITERS]; delimNestings[0] = delim1Nesting; delimNestings[1] = delim2Nesting; delimNestings[2] = delim3Nesting; delimNestings[3] = delim4Nesting; delimNestings[4] = delim5Nesting; delimNestings[5] = delim6Nesting; delimNestings[6] = delim7Nesting; delimNestings[7] = delim8Nesting; int levelCurrent = SC_FOLDLEVELBASE; int lineCurrent = 0; int levelMinCurrent = 0; int levelNext = 0; int levelPrev = 0; int lev = 0; bool visibleChars = false; bool hasDot = false; bool hasExp = false; bool hasPrefix = false; bool hasSuffix = false; bool hasRange = false; bool notNumber = false; bool previousWasRange = false; bool dontMove = false; bool finished = true; unsigned int nestedLevel = 0; int openIndex = 0; int skipForward = 0; int prevState = 0; int isCommentLine = COMMENTLINE_NO; int isPrevLineComment = COMMENTLINE_NO; bool isInCommentBlock = false; bool isInComment = false; int newState = 0; int nlCount = 0; int continueCommentBlock = 0; bool startOfDelimiter = false; vector lastNestedGroup; vvstring * delimEscape = NULL; vvstring * delimClose = NULL; int delimNesting = 0; unsigned int docLength = startPos + length; int numberParentState = SCE_USER_STYLE_IDENTIFIER; if (startPos == 0) { // foldVector.clear(); nestedVector.clear(); lastNestedGroup.clear(); initStyle = SCE_USER_STYLE_IDENTIFIER; } else { int oldStartPos = startPos; ReColoringCheck(startPos, nestedLevel, initStyle, openIndex, isCommentLine, isInComment, styler, lastNestedGroup, nestedVector, /* foldVector, */ continueCommentBlock); // offset move to previous line length += (oldStartPos - startPos); docLength = startPos + length; } lineCurrent = styler.GetLine(startPos); if (lineCurrent > 0) levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16; levelMinCurrent = levelCurrent; levelNext = levelCurrent; StyleContext sc(startPos, length, initStyle, styler); for (; finished; dontMove?true:sc.Forward()) { dontMove = false; if (sc.More() == false) finished = false; // colorize last word, even if file does not end with whitespace char if (foldComments) if (isInComment == false) if (isCommentLine == COMMENTLINE_NO) if (sc.state != SCE_USER_STYLE_COMMENTLINE) if (sc.state != SCE_USER_STYLE_IDENTIFIER) if (sc.state != SCE_USER_STYLE_DEFAULT) if (!isWhiteSpace(sc.ch)) isCommentLine = COMMENTLINE_SKIP_TESTING; if (foldCompact == true && visibleChars == false && !isWhiteSpace(sc.ch)) visibleChars = true; if (sc.atLineEnd) { if (foldComments == true) { if (levelCurrent != levelNext) isCommentLine = COMMENTLINE_SKIP_TESTING; if (continueCommentBlock > 0) { if (continueCommentBlock & CL_PREVPREV) { isInCommentBlock = true; isPrevLineComment = COMMENTLINE_YES; if (!(continueCommentBlock & CL_CURRENT)) { levelNext++; levelMinCurrent++; levelCurrent++; levelPrev = (levelMinCurrent | levelNext << 16) | SC_ISCOMMENTLINE; } } else if (continueCommentBlock & CL_PREV) { isPrevLineComment = COMMENTLINE_YES; if (continueCommentBlock & CL_CURRENT) { levelMinCurrent--; levelNext--; levelCurrent--; levelPrev = (levelMinCurrent | levelNext << 16) | SC_ISCOMMENTLINE; } } continueCommentBlock = 0; } if (isInCommentBlock && isCommentLine != COMMENTLINE_YES && isPrevLineComment == COMMENTLINE_YES) { levelNext--; levelPrev = (levelMinCurrent | levelNext << 16) | SC_ISCOMMENTLINE; levelMinCurrent--; isInCommentBlock = false; } if (!isInCommentBlock && isCommentLine == COMMENTLINE_YES && isPrevLineComment == COMMENTLINE_YES) { levelNext++; levelPrev = (levelMinCurrent | levelNext << 16) | SC_FOLDLEVELHEADERFLAG | SC_ISCOMMENTLINE; levelMinCurrent = levelNext; isInCommentBlock = true; } if (levelPrev != 0) { // foldVector[lineCurrent - 1] = levelPrev; styler.SetLevel(lineCurrent - 1, levelPrev); levelPrev = 0; } } lev = levelMinCurrent | levelNext << 16; if (foldComments && isCommentLine == COMMENTLINE_YES) lev |= SC_ISCOMMENTLINE; if (visibleChars == false && foldCompact) lev |= SC_FOLDLEVELWHITEFLAG; if (levelMinCurrent < levelNext) lev |= SC_FOLDLEVELHEADERFLAG; // foldVector.push_back(lev); styler.SetLevel(lineCurrent, lev); for (int i=0; i::iterator iter = (*delimEscape)[openIndex].begin(); for (; iter != (*delimEscape)[openIndex].end(); ++iter) { if (ignoreCase?sc.MatchIgnoreCase2(iter->c_str()):sc.Match(iter->c_str())) { sc.Forward(iter->length() + 1); // escape is found, skip escape string and one char after it. break; } } // second, check end of delimiter sequence iter = (*delimClose)[openIndex].begin(); for (; iter != (*delimClose)[openIndex].end(); ++iter) { if (ignoreCase ? sc.MatchIgnoreCase2(iter->c_str()):sc.Match(iter->c_str())) { // record end of delimiter sequence (NI_CLOSE) nestedVector.push_back(*NI.Set(sc.currentPos + iter->length() - 1, nestedLevel--, openIndex, sc.state, NI_CLOSE)); // is there anything on the left side? (any backward keyword 'glued' with end of delimiter sequence) setBackwards(kwLists, sc, prefixes, ignoreCase, delimNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); // paint backward keyword sc.SetState(prevState); // was current delimiter sequence nested, or do we start over from SCE_USER_STYLE_IDENTIFIER? readLastNested(lastNestedGroup, newState, openIndex); // for delimiters that end with ((EOL)) if (newState != SCE_USER_STYLE_COMMENTLINE || (sc.ch != '\r' && sc.ch != '\n')) sc.Forward(iter->length()); // paint end of delimiter sequence sc.SetState(newState); dontMove = true; break; // break out of 'for', not 'case' } } // out of current state? if (prevState != newState) break; // quick replacement for SCE_USER_STYLE_DEFAULT (important for nested keywords) if (isWhiteSpace(sc.ch) && !isWhiteSpace(sc.chPrev)) { setBackwards(kwLists, sc, prefixes, ignoreCase, delimNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); sc.SetState(prevState); } else if (!isWhiteSpace(sc.ch) && isWhiteSpace(sc.chPrev)) { // create new 'compare point' (AKA beginning of nested keyword) before checking for numbers sc.SetState(prevState); if (delimNesting & SCE_USER_MASK_NESTING_NUMBERS) { if (true == IsNumber(prefixTokens, negativePrefixTokens, sc, ignoreCase, hasPrefix, dontMove, hasDot)) { numberParentState = prevState; break; } } } // special case when number follows opening sequence if (startOfDelimiter == true) { startOfDelimiter = false; if (delimNesting & SCE_USER_MASK_NESTING_NUMBERS) { if (true == IsNumber(prefixTokens, negativePrefixTokens, sc, ignoreCase, hasPrefix, dontMove, hasDot)) { numberParentState = prevState; break; } } } // third, check nested delimiter sequence if (isInListNested(delimNesting, forwards, sc, ignoreCase, openIndex, skipForward, newState, lineCommentAtBOL)) { // any backward keyword 'glued' on the left side? setBackwards(kwLists, sc, prefixes, ignoreCase, delimNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); if (newState != SCE_USER_STYLE_OPERATOR) { // record delimiter sequence in BOTH vectors nestedVector.push_back(*NI.Set(sc.currentPos, ++nestedLevel, openIndex, newState, NI_OPEN)); lastNestedGroup.push_back(NI); } sc.SetState(newState); // yes, both 'SetState' calls are needed sc.Forward(skipForward); sc.SetState(newState); if (newState == SCE_USER_STYLE_OPERATOR) { // generally, nested keywords must be surrounded by spaces (or forward keywords) // one exception is that operators can be followed by numbers sc.ChangeState(prevState); if (delimNesting & SCE_USER_MASK_NESTING_NUMBERS) { if (true == IsNumber(prefixTokens, negativePrefixTokens, sc, ignoreCase, hasPrefix, dontMove, hasDot)) { numberParentState = prevState; break; } } } dontMove = true; break; } break; } case SCE_USER_STYLE_COMMENT: { // first, check end of comment sequence vector::iterator iter = commentClose[openIndex].begin(); for (; iter != commentClose[openIndex].end(); ++iter) { if (ignoreCase?sc.MatchIgnoreCase2(iter->c_str()):sc.Match(iter->c_str())) { // record end of comment sequence (NI_CLOSE) nestedVector.push_back(*NI.Set(sc.currentPos + iter->length() - 1, nestedLevel--, openIndex, SCE_USER_STYLE_COMMENT, NI_CLOSE)); // is there anything on the left side? (any backward keyword 'glued' with end of comment sequence) setBackwards(kwLists, sc, prefixes, ignoreCase, commentNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); // paint backward keyword and move on sc.SetState(SCE_USER_STYLE_COMMENT); sc.Forward(iter->length()); // was current comment sequence nested, or do we start over from SCE_USER_STYLE_IDENTIFIER? readLastNested(lastNestedGroup, newState, openIndex); // paint end of comment sequence sc.SetState(newState); isInComment = false; dontMove = true; break; } } if (sc.state != SCE_USER_STYLE_COMMENT) break; // quick replacement for SCE_USER_STYLE_DEFAULT (important for nested keywords) if (isWhiteSpace(sc.ch) && !isWhiteSpace(sc.chPrev)) { setBackwards(kwLists, sc, prefixes, ignoreCase, commentNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); sc.SetState(SCE_USER_STYLE_COMMENT); } else if (!isWhiteSpace(sc.ch) && isWhiteSpace(sc.chPrev)) { // create new 'compare point' (AKA beginning of nested keyword) before checking for numbers sc.SetState(SCE_USER_STYLE_COMMENT); if (commentNesting & SCE_USER_MASK_NESTING_NUMBERS) { if (true == IsNumber(prefixTokens, negativePrefixTokens, sc, ignoreCase, hasPrefix, dontMove, hasDot)) { numberParentState = SCE_USER_STYLE_COMMENT; break; } } } // special case when number follows opening sequence if (startOfDelimiter == true) { startOfDelimiter = false; if (commentNesting & SCE_USER_MASK_NESTING_NUMBERS) { if (true == IsNumber(prefixTokens, negativePrefixTokens, sc, ignoreCase, hasPrefix, dontMove, hasDot)) { numberParentState = SCE_USER_STYLE_COMMENT; break; } } } // third, check nested delimiter sequence if (isInListNested(commentNesting, forwards, sc, ignoreCase, openIndex, skipForward, newState, lineCommentAtBOL)) { // any backward keyword 'glued' on the left side? setBackwards(kwLists, sc, prefixes, ignoreCase, commentNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); if (newState != SCE_USER_STYLE_OPERATOR) { // record delimiter sequence in BOTH vectors nestedVector.push_back(*NI.Set(sc.currentPos, ++nestedLevel, openIndex, newState, NI_OPEN)); lastNestedGroup.push_back(NI); } sc.SetState(newState); // yes, both 'SetState' calls are needed sc.Forward(skipForward); sc.SetState(newState); if (newState == SCE_USER_STYLE_OPERATOR) { // generally, nested keywords must be surrounded by spaces (or forward keywords) // one exception is that operators can be followed by numbers sc.ChangeState(SCE_USER_STYLE_COMMENT); if (commentNesting & SCE_USER_MASK_NESTING_NUMBERS) { if (true == IsNumber(prefixTokens, negativePrefixTokens, sc, ignoreCase, hasPrefix, dontMove, hasDot)) { numberParentState = SCE_USER_STYLE_COMMENT; break; } } } dontMove = true; break; } break; } case SCE_USER_STYLE_COMMENTLINE: { // first, check end of line comment sequence (in rare cases when line comments can end before new line char) vector::iterator iter = commentLineClose[openIndex].begin(); for (; iter != commentLineClose[openIndex].end(); ++iter) { if (ignoreCase?sc.MatchIgnoreCase2(iter->c_str()):sc.Match(iter->c_str())) { // record end of line comment sequence (NI_CLOSE) nestedVector.push_back(*NI.Set(sc.currentPos + iter->length() - 1, nestedLevel--, openIndex, SCE_USER_STYLE_COMMENTLINE, NI_CLOSE)); // is there anything on the left side? (any backward keyword 'glued' with end of line comment sequence) setBackwards(kwLists, sc, prefixes, ignoreCase, lineCommentNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); // paint backward keyword and move on sc.SetState(SCE_USER_STYLE_COMMENTLINE); sc.Forward(iter->length()); // was current line comment sequence nested, or do we start over from SCE_USER_STYLE_IDENTIFIER? readLastNested(lastNestedGroup, newState, openIndex); // paint end of line comment sequence sc.SetState(newState); dontMove = true; break; } } if (sc.state != SCE_USER_STYLE_COMMENTLINE) break; // quick replacement for SCE_USER_STYLE_DEFAULT (important for nested keywords) if (isWhiteSpace(sc.ch) && !isWhiteSpace(sc.chPrev)) { setBackwards(kwLists, sc, prefixes, ignoreCase, lineCommentNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); sc.SetState(SCE_USER_STYLE_COMMENTLINE); } else if (!isWhiteSpace(sc.ch) && isWhiteSpace(sc.chPrev)) { // create new 'compare point' (AKA beginning of nested keyword) before checking for numbers sc.SetState(SCE_USER_STYLE_COMMENTLINE); if (lineCommentNesting & SCE_USER_MASK_NESTING_NUMBERS) { if (true == IsNumber(prefixTokens, negativePrefixTokens, sc, ignoreCase, hasPrefix, dontMove, hasDot)) { numberParentState = SCE_USER_STYLE_COMMENTLINE; break; } } } // special case when number follows opening sequence if (startOfDelimiter == true) { startOfDelimiter = false; if (lineCommentNesting & SCE_USER_MASK_NESTING_NUMBERS) { if (true == IsNumber(prefixTokens, negativePrefixTokens, sc, ignoreCase, hasPrefix, dontMove, hasDot)) { numberParentState = SCE_USER_STYLE_COMMENTLINE; break; } } } // second, check line comment continuation if (sc.atLineEnd) { bool lineContinuation = false; int offset = 0; if (sc.chPrev == '\r') offset = 1; vector::iterator iter = commentLineContinue[openIndex].begin(); for (; iter != commentLineContinue[openIndex].end(); ++iter) { int length = iter->length(); if (length == 0) continue; lineContinuation = true; for (int i=0; iempty()) { if (isInListForward(*delimStart[i], sc, ignoreCase, openIndex, skipForward)) { // any backward keyword 'glued' on the left side? setBackwards(kwLists, sc, prefixes, ignoreCase, bwNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); // paint up to start of delimiter sequence sc.SetState(i+SCE_USER_STYLE_DELIMITER1); // record start of delimiter sequence (NI_OPEN) in BOTH nesting vectors nestedVector.push_back(*NI.Set(sc.currentPos, ++nestedLevel, openIndex, i+SCE_USER_STYLE_DELIMITER1, NI_OPEN)); lastNestedGroup.push_back(NI); // paint start of delimiter sequence sc.Forward(skipForward); sc.SetState(i+SCE_USER_STYLE_DELIMITER1); dontMove = true; if (delimNestings[i] & SCE_USER_MASK_NESTING_NUMBERS) startOfDelimiter = true; break; // break from nested 'for' loop, not 'case' statement } } } if (dontMove == true) break; // delimiter start found, break from case SCE_USER_STYLE_DEFAULT if (!operators1.empty()) { if (isInListForward(operators1, sc, ignoreCase, openIndex, skipForward)) { // any backward keyword 'glued' on the left side? setBackwards(kwLists, sc, prefixes, ignoreCase, bwNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); // paint up to start of sequence sc.SetState(SCE_USER_STYLE_OPERATOR); // paint sequence sc.Forward(skipForward); sc.ChangeState(SCE_USER_STYLE_OPERATOR); // no closing sequence, start over from default sc.SetState(SCE_USER_STYLE_IDENTIFIER); dontMove = true; break; } } if (!foldersInCode1Open.empty()) { if (isInListForward(foldersInCode1Open, sc, ignoreCase, openIndex, skipForward)) { // any backward keyword 'glued' on the left side? setBackwards(kwLists, sc, prefixes, ignoreCase, bwNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); // paint up to start of sequence sc.SetState(SCE_USER_STYLE_FOLDER_IN_CODE1); // paint sequence sc.Forward(skipForward); sc.ChangeState(SCE_USER_STYLE_FOLDER_IN_CODE1); // no closing sequence, start over from default sc.SetState(SCE_USER_STYLE_IDENTIFIER); dontMove = true; if (levelMinCurrent > levelNext) levelMinCurrent = levelNext; levelNext++; break; } } if (!foldersInCode1Middle.empty()) { if (isInListForward(foldersInCode1Middle, sc, ignoreCase, openIndex, skipForward)) { // any backward keyword 'glued' on the left side? setBackwards(kwLists, sc, prefixes, ignoreCase, bwNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); // paint up to start of sequence sc.SetState(SCE_USER_STYLE_FOLDER_IN_CODE1); // paint sequence sc.Forward(skipForward); sc.ChangeState(SCE_USER_STYLE_FOLDER_IN_CODE1); // no closing sequence, start over from default sc.SetState(SCE_USER_STYLE_IDENTIFIER); dontMove = true; levelNext--; if (levelMinCurrent > levelNext) levelMinCurrent = levelNext; levelNext++; break; } } if (!foldersInCode1Close.empty()) { if (isInListForward(foldersInCode1Close, sc, ignoreCase, openIndex, skipForward)) { // any backward keyword 'glued' on the left side? setBackwards(kwLists, sc, prefixes, ignoreCase, bwNesting, fwEndVectors, levelMinCurrent, levelNext, nlCount, dontMove, docLength); // paint up to start of sequence sc.SetState(SCE_USER_STYLE_FOLDER_IN_CODE1); // paint sequence sc.Forward(skipForward); sc.ChangeState(SCE_USER_STYLE_FOLDER_IN_CODE1); // no closing sequence, start over from default sc.SetState(SCE_USER_STYLE_IDENTIFIER); dontMove = true; levelNext--; break; } } if (foldComments && isCommentLine != COMMENTLINE_SKIP_TESTING) isCommentLine = COMMENTLINE_SKIP_TESTING; break; } // determine if a new state should be entered. case SCE_USER_STYLE_IDENTIFIER: { if (isWhiteSpace(sc.ch))// && isWhiteSpace(sc.chPrev)) { break; } if (true == IsNumber(prefixTokens, negativePrefixTokens, sc, ignoreCase, hasPrefix, dontMove, hasDot)) { numberParentState = SCE_USER_STYLE_IDENTIFIER; break; } if (!isWhiteSpace(sc.ch))// && isWhiteSpace(sc.chPrev)) // word start { sc.SetState(SCE_USER_STYLE_DEFAULT); dontMove = true; break; } break; } default: break; } } sc.Complete(); } static void FoldUserDoc(unsigned int /* startPos */, int /* length */, int /*initStyle*/, WordList *[], Accessor & /* styler */) { // this function will not be used in final version of the code. // it should remain commented out as it is useful for debugging purposes !!! // perhaps ifdef block would be a wiser choice, but commenting out works just fine for the time being // int lineCurrent = styler.GetLine(startPos); // vector::iterator iter = foldVectorStatic->begin() + lineCurrent; // for (; iter != foldVectorStatic->end(); ++iter) // { // styler.SetLevel(lineCurrent++, *iter); // } } static const char * const userDefineWordLists[] = { "Primary keywords and identifiers", "Secondary keywords and identifiers", "Documentation comment keywords", "Fold header keywords", 0, }; LexerModule lmUserDefine(SCLEX_USER, ColouriseUserDoc, "user", FoldUserDoc, userDefineWordLists);