// Scintilla source code edit control // @file LexPowerPro.cxx // PowerPro utility, written by Bruce Switzer, is available from http://powerpro.webeddie.com // PowerPro lexer is written by Christopher Bean (cbean@cb-software.net) // // Lexer code heavily borrowed from: // LexAU3.cxx by Jos van der Zande // LexCPP.cxx by Neil Hodgson // LexVB.cxx by Neil Hodgson // // Changes: // 2008-10-25 - Initial release // 2008-10-26 - Changed how is hilighted in 'function ' so that // local isFunction = "" and local functions = "" don't get falsely highlighted // 2008-12-14 - Added bounds checking for szFirstWord and szDo // - Replaced SetOfCharacters with CharacterSet // - Made sure that CharacterSet::Contains is passed only positive values // - Made sure that the return value of Accessor::SafeGetCharAt is positive before // passing to functions that require positive values like isspacechar() // - Removed unused visibleChars processing from ColourisePowerProDoc() // - Fixed bug with folding logic where line continuations didn't end where // they were supposed to // - Moved all helper functions to the top of the file // 2010-06-03 - Added onlySpaces variable to allow the @function and ;comment styles to be indented // - Modified HasFunction function to be a bit more robust // - Renamed HasFunction function to IsFunction // - Cleanup // Copyright 1998-2005 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include "ILexer.h" #include "Scintilla.h" #include "SciLexer.h" #include "WordList.h" #include "LexAccessor.h" #include "Accessor.h" #include "StyleContext.h" #include "CharacterSet.h" #include "LexerModule.h" using namespace Lexilla; static inline bool IsStreamCommentStyle(int style) { return style == SCE_POWERPRO_COMMENTBLOCK; } static inline bool IsLineEndChar(unsigned char ch) { return ch == 0x0a //LF || ch == 0x0c //FF || ch == 0x0d; //CR } static bool IsContinuationLine(Sci_PositionU szLine, Accessor &styler) { Sci_Position startPos = styler.LineStart(szLine); Sci_Position endPos = styler.LineStart(szLine + 1) - 2; while (startPos < endPos) { char stylech = styler.StyleAt(startPos); if (!(stylech == SCE_POWERPRO_COMMENTBLOCK)) { char ch = styler.SafeGetCharAt(endPos); char chPrev = styler.SafeGetCharAt(endPos - 1); char chPrevPrev = styler.SafeGetCharAt(endPos - 2); if (ch > 0 && chPrev > 0 && chPrevPrev > 0 && !isspacechar(ch) && !isspacechar(chPrev) && !isspacechar(chPrevPrev) ) return (chPrevPrev == ';' && chPrev == ';' && ch == '+'); } endPos--; // skip to next char } return false; } // Routine to find first none space on the current line and return its Style // needed for comment lines not starting on pos 1 static int GetStyleFirstWord(Sci_Position szLine, Accessor &styler) { Sci_Position startPos = styler.LineStart(szLine); Sci_Position endPos = styler.LineStart(szLine + 1) - 1; char ch = styler.SafeGetCharAt(startPos); while (ch > 0 && isspacechar(ch) && startPos < endPos) { startPos++; // skip to next char ch = styler.SafeGetCharAt(startPos); } return styler.StyleAt(startPos); } //returns true if there is a function to highlight //used to highlight in 'function ' //note: // sample line (without quotes): "\tfunction asdf() // currentPos will be the position of 'a' static bool IsFunction(Accessor &styler, Sci_PositionU currentPos) { const char function[10] = "function "; //10 includes \0 unsigned int numberOfCharacters = sizeof(function) - 1; Sci_PositionU position = currentPos - numberOfCharacters; //compare each character with the letters in the function array //return false if ALL don't match for (Sci_PositionU i = 0; i < numberOfCharacters; i++) { char c = styler.SafeGetCharAt(position++); if (c != function[i]) return false; } //make sure that there are only spaces (or tabs) between the beginning //of the line and the function declaration position = currentPos - numberOfCharacters - 1; //-1 to move to char before 'function' for (Sci_PositionU j = 0; j < 16; j++) { //check up to 16 preceeding characters char c = styler.SafeGetCharAt(position--, '\0'); //if can't read char, return NUL (past beginning of document) if (c <= 0) //reached beginning of document return true; if (c > 0 && IsLineEndChar(c)) return true; else if (c > 0 && !IsASpaceOrTab(c)) return false; } //fall-through return false; } static void ColourisePowerProDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[], Accessor &styler, bool caseSensitive) { WordList &keywords = *keywordlists[0]; WordList &keywords2 = *keywordlists[1]; WordList &keywords3 = *keywordlists[2]; WordList &keywords4 = *keywordlists[3]; //define the character sets CharacterSet setWordStart(CharacterSet::setAlpha, "_@", 0x80, true); CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true); StyleContext sc(startPos, length, initStyle, styler); char s_save[100]; //for last line highlighting //are there only spaces between the first letter of the line and the beginning of the line bool onlySpaces = true; for (; sc.More(); sc.Forward()) { // save the total current word for eof processing char s[100]; sc.GetCurrentLowered(s, sizeof(s)); if ((sc.ch > 0) && setWord.Contains(sc.ch)) { strcpy(s_save,s); int tp = static_cast(strlen(s_save)); if (tp < 99) { s_save[tp] = static_cast(tolower(sc.ch)); s_save[tp+1] = '\0'; } } if (sc.atLineStart) { if (sc.state == SCE_POWERPRO_DOUBLEQUOTEDSTRING) { // Prevent SCE_POWERPRO_STRINGEOL from leaking back to previous line which // ends with a line continuation by locking in the state upto this position. sc.SetState(SCE_POWERPRO_DOUBLEQUOTEDSTRING); } } // Determine if the current state should terminate. switch (sc.state) { case SCE_POWERPRO_OPERATOR: sc.SetState(SCE_POWERPRO_DEFAULT); break; case SCE_POWERPRO_NUMBER: if (!IsADigit(sc.ch)) sc.SetState(SCE_POWERPRO_DEFAULT); break; case SCE_POWERPRO_IDENTIFIER: //if ((sc.ch > 0) && !setWord.Contains(sc.ch) || (sc.ch == '.')) { // use this line if don't want to match keywords with . in them. ie: win.debug will match both win and debug so win debug will also be colorized if ((sc.ch > 0) && !setWord.Contains(sc.ch)){ // || (sc.ch == '.')) { // use this line if you want to match keywords with a . ie: win.debug will only match win.debug neither win nor debug will be colorized separately char s[1000]; if (caseSensitive) { sc.GetCurrent(s, sizeof(s)); } else { sc.GetCurrentLowered(s, sizeof(s)); } if (keywords.InList(s)) { sc.ChangeState(SCE_POWERPRO_WORD); } else if (keywords2.InList(s)) { sc.ChangeState(SCE_POWERPRO_WORD2); } else if (keywords3.InList(s)) { sc.ChangeState(SCE_POWERPRO_WORD3); } else if (keywords4.InList(s)) { sc.ChangeState(SCE_POWERPRO_WORD4); } sc.SetState(SCE_POWERPRO_DEFAULT); } break; case SCE_POWERPRO_LINECONTINUE: if (sc.atLineStart) { sc.SetState(SCE_POWERPRO_DEFAULT); } else if (sc.Match('/', '*') || sc.Match('/', '/')) { sc.SetState(SCE_POWERPRO_DEFAULT); } break; case SCE_POWERPRO_COMMENTBLOCK: if (sc.Match('*', '/')) { sc.Forward(); sc.ForwardSetState(SCE_POWERPRO_DEFAULT); } break; case SCE_POWERPRO_COMMENTLINE: if (sc.atLineStart) { sc.SetState(SCE_POWERPRO_DEFAULT); } break; case SCE_POWERPRO_DOUBLEQUOTEDSTRING: if (sc.atLineEnd) { sc.ChangeState(SCE_POWERPRO_STRINGEOL); } else if (sc.ch == '\\') { if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') { sc.Forward(); } } else if (sc.ch == '\"') { sc.ForwardSetState(SCE_POWERPRO_DEFAULT); } break; case SCE_POWERPRO_SINGLEQUOTEDSTRING: if (sc.atLineEnd) { sc.ChangeState(SCE_POWERPRO_STRINGEOL); } else if (sc.ch == '\\') { if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') { sc.Forward(); } } else if (sc.ch == '\'') { sc.ForwardSetState(SCE_POWERPRO_DEFAULT); } break; case SCE_POWERPRO_STRINGEOL: if (sc.atLineStart) { sc.SetState(SCE_POWERPRO_DEFAULT); } break; case SCE_POWERPRO_VERBATIM: if (sc.ch == '\"') { if (sc.chNext == '\"') { sc.Forward(); } else { sc.ForwardSetState(SCE_POWERPRO_DEFAULT); } } break; case SCE_POWERPRO_ALTQUOTE: if (sc.ch == '#') { if (sc.chNext == '#') { sc.Forward(); } else { sc.ForwardSetState(SCE_POWERPRO_DEFAULT); } } break; case SCE_POWERPRO_FUNCTION: if (isspacechar(sc.ch) || sc.ch == '(') { sc.SetState(SCE_POWERPRO_DEFAULT); } break; } // Determine if a new state should be entered. if (sc.state == SCE_POWERPRO_DEFAULT) { if (sc.Match('?', '\"')) { sc.SetState(SCE_POWERPRO_VERBATIM); sc.Forward(); } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) { sc.SetState(SCE_POWERPRO_NUMBER); }else if (sc.Match('?','#')) { if (sc.ch == '?' && sc.chNext == '#') { sc.SetState(SCE_POWERPRO_ALTQUOTE); sc.Forward(); } } else if (IsFunction(styler, sc.currentPos)) { //highlight in 'function ' sc.SetState(SCE_POWERPRO_FUNCTION); } else if (onlySpaces && sc.ch == '@') { //alternate function definition [label] sc.SetState(SCE_POWERPRO_FUNCTION); } else if ((sc.ch > 0) && (setWordStart.Contains(sc.ch) || (sc.ch == '?'))) { sc.SetState(SCE_POWERPRO_IDENTIFIER); } else if (sc.Match(";;+")) { sc.SetState(SCE_POWERPRO_LINECONTINUE); } else if (sc.Match('/', '*')) { sc.SetState(SCE_POWERPRO_COMMENTBLOCK); sc.Forward(); // Eat the * so it isn't used for the end of the comment } else if (sc.Match('/', '/')) { sc.SetState(SCE_POWERPRO_COMMENTLINE); } else if (onlySpaces && sc.ch == ';') { //legacy comment that can only have blank space in front of it sc.SetState(SCE_POWERPRO_COMMENTLINE); } else if (sc.Match(";;")) { sc.SetState(SCE_POWERPRO_COMMENTLINE); } else if (sc.ch == '\"') { sc.SetState(SCE_POWERPRO_DOUBLEQUOTEDSTRING); } else if (sc.ch == '\'') { sc.SetState(SCE_POWERPRO_SINGLEQUOTEDSTRING); } else if (isoperator(static_cast(sc.ch))) { sc.SetState(SCE_POWERPRO_OPERATOR); } } //maintain a record of whether or not all the preceding characters on //a line are space characters if (onlySpaces && !IsASpaceOrTab(sc.ch)) onlySpaces = false; //reset when starting a new line if (sc.atLineEnd) onlySpaces = true; } //************************************* // Colourize the last word correctly //************************************* if (sc.state == SCE_POWERPRO_IDENTIFIER) { if (keywords.InList(s_save)) { sc.ChangeState(SCE_POWERPRO_WORD); sc.SetState(SCE_POWERPRO_DEFAULT); } else if (keywords2.InList(s_save)) { sc.ChangeState(SCE_POWERPRO_WORD2); sc.SetState(SCE_POWERPRO_DEFAULT); } else if (keywords3.InList(s_save)) { sc.ChangeState(SCE_POWERPRO_WORD3); sc.SetState(SCE_POWERPRO_DEFAULT); } else if (keywords4.InList(s_save)) { sc.ChangeState(SCE_POWERPRO_WORD4); sc.SetState(SCE_POWERPRO_DEFAULT); } else { sc.SetState(SCE_POWERPRO_DEFAULT); } } sc.Complete(); } static void FoldPowerProDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[], Accessor &styler) { //define the character sets CharacterSet setWordStart(CharacterSet::setAlpha, "_@", 0x80, true); CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true); //used to tell if we're recursively folding the whole document, or just a small piece (ie: if statement or 1 function) bool isFoldingAll = true; Sci_Position endPos = startPos + length; Sci_Position lastLine = styler.GetLine(styler.Length()); //used to help fold the last line correctly // get settings from the config files for folding comments and preprocessor lines bool foldComment = styler.GetPropertyInt("fold.comment") != 0; bool foldInComment = styler.GetPropertyInt("fold.comment") == 2; bool foldCompact = true; // Backtrack to previous line in case need to fix its fold status Sci_Position lineCurrent = styler.GetLine(startPos); if (startPos > 0) { isFoldingAll = false; if (lineCurrent > 0) { lineCurrent--; startPos = styler.LineStart(lineCurrent); } } // vars for style of previous/current/next lines int style = GetStyleFirstWord(lineCurrent,styler); int stylePrev = 0; // find the first previous line without continuation character at the end while ((lineCurrent > 0 && IsContinuationLine(lineCurrent, styler)) || (lineCurrent > 1 && IsContinuationLine(lineCurrent - 1, styler))) { lineCurrent--; startPos = styler.LineStart(lineCurrent); } if (lineCurrent > 0) { stylePrev = GetStyleFirstWord(lineCurrent-1,styler); } // vars for getting first word to check for keywords bool isFirstWordStarted = false; bool isFirstWordEnded = false; const unsigned int FIRST_WORD_MAX_LEN = 10; char szFirstWord[FIRST_WORD_MAX_LEN] = ""; unsigned int firstWordLen = 0; char szDo[3]=""; int szDolen = 0; bool isDoLastWord = false; // var for indentlevel int levelCurrent = SC_FOLDLEVELBASE; if (lineCurrent > 0) levelCurrent = styler.LevelAt(lineCurrent-1) >> 16; int levelNext = levelCurrent; int visibleChars = 0; int functionCount = 0; char chNext = styler.SafeGetCharAt(startPos); char chPrev = '\0'; char chPrevPrev = '\0'; char chPrevPrevPrev = '\0'; for (Sci_Position i = startPos; i < endPos; i++) { char ch = chNext; chNext = styler.SafeGetCharAt(i + 1); if ((ch > 0) && setWord.Contains(ch)) visibleChars++; // get the syle for the current character neede to check in comment int stylech = styler.StyleAt(i); // start the capture of the first word if (!isFirstWordStarted && (ch > 0)) { if (setWord.Contains(ch) || setWordStart.Contains(ch) || ch == ';' || ch == '/') { isFirstWordStarted = true; if (firstWordLen < FIRST_WORD_MAX_LEN - 1) { szFirstWord[firstWordLen++] = static_cast(tolower(ch)); szFirstWord[firstWordLen] = '\0'; } } } // continue capture of the first word on the line else if (isFirstWordStarted && !isFirstWordEnded && (ch > 0)) { if (!setWord.Contains(ch)) { isFirstWordEnded = true; } else if (firstWordLen < (FIRST_WORD_MAX_LEN - 1)) { szFirstWord[firstWordLen++] = static_cast(tolower(ch)); szFirstWord[firstWordLen] = '\0'; } } if (stylech != SCE_POWERPRO_COMMENTLINE) { //reset isDoLastWord if we find a character(ignoring spaces) after 'do' if (isDoLastWord && (ch > 0) && setWord.Contains(ch)) isDoLastWord = false; // --find out if the word "do" is the last on a "if" line-- // collect each letter and put it into a buffer 2 chars long // if we end up with "do" in the buffer when we reach the end of // the line, "do" was the last word on the line if ((ch > 0) && isFirstWordEnded && strcmp(szFirstWord, "if") == 0) { if (szDolen == 2) { szDo[0] = szDo[1]; szDo[1] = static_cast(tolower(ch)); szDo[2] = '\0'; if (strcmp(szDo, "do") == 0) isDoLastWord = true; } else if (szDolen < 2) { szDo[szDolen++] = static_cast(tolower(ch)); szDo[szDolen] = '\0'; } } } // End of Line found so process the information if ((ch == '\r' && chNext != '\n') // \r\n || ch == '\n' // \n || i == endPos) { // end of selection // ************************** // Folding logic for Keywords // ************************** // if a keyword is found on the current line and the line doesn't end with ;;+ (continuation) // and we are not inside a commentblock. if (firstWordLen > 0 && chPrev != '+' && chPrevPrev != ';' && chPrevPrevPrev !=';' && (!IsStreamCommentStyle(style) || foldInComment) ) { // only fold "if" last keyword is "then" (else its a one line if) if (strcmp(szFirstWord, "if") == 0 && isDoLastWord) levelNext++; // create new fold for these words if (strcmp(szFirstWord, "for") == 0) levelNext++; //handle folding for functions/labels //Note: Functions and labels don't have an explicit end like [end function] // 1. functions/labels end at the start of another function // 2. functions/labels end at the end of the file if ((strcmp(szFirstWord, "function") == 0) || (firstWordLen > 0 && szFirstWord[0] == '@')) { if (isFoldingAll) { //if we're folding the whole document (recursivly by lua script) if (functionCount > 0) { levelCurrent--; } else { levelNext++; } functionCount++; } else { //if just folding a small piece (by clicking on the minus sign next to the word) levelCurrent--; } } // end the fold for these words before the current line if (strcmp(szFirstWord, "endif") == 0 || strcmp(szFirstWord, "endfor") == 0) { levelNext--; levelCurrent--; } // end the fold for these words before the current line and Start new fold if (strcmp(szFirstWord, "else") == 0 || strcmp(szFirstWord, "elseif") == 0 ) levelCurrent--; } // Preprocessor and Comment folding int styleNext = GetStyleFirstWord(lineCurrent + 1,styler); // ********************************* // Folding logic for Comment blocks // ********************************* if (foldComment && IsStreamCommentStyle(style)) { // Start of a comment block if (stylePrev != style && IsStreamCommentStyle(styleNext) && styleNext == style) { levelNext++; } // fold till the last line for normal comment lines else if (IsStreamCommentStyle(stylePrev) && styleNext != SCE_POWERPRO_COMMENTLINE && stylePrev == SCE_POWERPRO_COMMENTLINE && style == SCE_POWERPRO_COMMENTLINE) { levelNext--; } // fold till the one but last line for Blockcomment lines else if (IsStreamCommentStyle(stylePrev) && styleNext != SCE_POWERPRO_COMMENTBLOCK && style == SCE_POWERPRO_COMMENTBLOCK) { levelNext--; levelCurrent--; } } int levelUse = levelCurrent; int lev = levelUse | levelNext << 16; if (visibleChars == 0 && foldCompact) lev |= SC_FOLDLEVELWHITEFLAG; if (levelUse < levelNext) lev |= SC_FOLDLEVELHEADERFLAG; if (lev != styler.LevelAt(lineCurrent)) styler.SetLevel(lineCurrent, lev); // reset values for the next line lineCurrent++; stylePrev = style; style = styleNext; levelCurrent = levelNext; visibleChars = 0; // if the last characters are ;;+ then don't reset since the line continues on the next line. if (chPrev != '+' && chPrevPrev != ';' && chPrevPrevPrev != ';') { firstWordLen = 0; szDolen = 0; isFirstWordStarted = false; isFirstWordEnded = false; isDoLastWord = false; //blank out first word for (unsigned int i = 0; i < FIRST_WORD_MAX_LEN; i++) szFirstWord[i] = '\0'; } } // save the last processed characters if ((ch > 0) && !isspacechar(ch)) { chPrevPrevPrev = chPrevPrev; chPrevPrev = chPrev; chPrev = ch; } } //close folds on the last line - without this a 'phantom' //fold can appear when an open fold is on the last line //this can occur because functions and labels don't have an explicit end if (lineCurrent >= lastLine) { int lev = 0; lev |= SC_FOLDLEVELWHITEFLAG; styler.SetLevel(lineCurrent, lev); } } static const char * const powerProWordLists[] = { "Keyword list 1", "Keyword list 2", "Keyword list 3", "Keyword list 4", 0, }; static void ColourisePowerProDocWrapper(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[], Accessor &styler) { ColourisePowerProDoc(startPos, length, initStyle, keywordlists, styler, false); } LexerModule lmPowerPro(SCLEX_POWERPRO, ColourisePowerProDocWrapper, "powerpro", FoldPowerProDoc, powerProWordLists);