mirror of
				https://github.com/notepad-plus-plus/notepad-plus-plus.git
				synced 2025-10-31 19:44:06 +01:00 
			
		
		
		
	git-svn-id: svn://svn.tuxfamily.org/svnroot/notepadplus/repository/trunk@654 f5eea248-9336-0410-98b8-ebc06183d4e3
		
			
				
	
	
		
			551 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			551 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*------------------------------------------------------------------------------------
 | |
| 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 <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <ctype.h>
 | |
| #include <stdio.h>
 | |
| #include <stdarg.h>
 | |
| #include <assert.h>
 | |
| #include <windows.h>
 | |
| 
 | |
| #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"
 | |
| 
 | |
| #define KEYWORD_BOXHEADER 1
 | |
| #define KEYWORD_FOLDCONTRACTED 2
 | |
| 
 | |
| 
 | |
| static inline bool IsADigit(char ch) {
 | |
| 	return isascii(ch) && isdigit(ch);
 | |
| }
 | |
| 
 | |
| static inline bool isspacechar(unsigned char ch) {
 | |
| 	return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
 | |
| }
 | |
| 
 | |
| static bool isInOpList(WordList & opList, char op)
 | |
| {
 | |
| 	for (int i = 0 ; i < opList.len ; i++)
 | |
| 		if (op == *(opList.words[i]))
 | |
| 			return true;
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| static int cmpString(const void *a1, const void *a2) {
 | |
| 	// Can't work out the correct incantation to use modern casts here
 | |
| 	return strcmp(*(char**)(a1), *(char**)(a2));
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool isInList(WordList & list, const char *s, bool specialMode, bool ignoreCase) 
 | |
| {
 | |
| 	if (!list.words)
 | |
| 		return false;
 | |
| 
 | |
| 	unsigned char firstChar = s[0];
 | |
| 	int j = list.starts[firstChar];
 | |
| 
 | |
| 	if (j >= 0)
 | |
| 	{
 | |
| 		while ((ignoreCase?toupper(list.words[j][0]):list.words[j][0]) == (ignoreCase?toupper(s[0]):s[0]))
 | |
| 		{
 | |
| 			if (!list.words[j][1])
 | |
| 			{
 | |
| 				if (specialMode)
 | |
| 				{
 | |
| 					return true;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					if (!s[1])
 | |
| 						return true;
 | |
| 				}
 | |
| 			}
 | |
| 			int a1 = ignoreCase?toupper(list.words[j][1]):list.words[j][1];
 | |
| 			int b1 = ignoreCase?toupper(s[1]):s[1];
 | |
| 			if (a1 == b1) 
 | |
| 			{
 | |
| 				
 | |
| 				const char *a = list.words[j] + 1;
 | |
| 				int An = ignoreCase?toupper((int)*a):(int)*a;
 | |
| 				
 | |
| 				const char *b = s + 1;
 | |
| 				int Bn = ignoreCase?toupper((int)*b):(int)*b;
 | |
| 
 | |
| 				
 | |
| 				while (An && (An == Bn))
 | |
| 				{
 | |
| 					a++;
 | |
| 					An = ignoreCase?toupper((int)*a):(int)*a;
 | |
| 					b++;
 | |
| 					Bn = ignoreCase?toupper((int)*b):(int)*b;
 | |
| 				}
 | |
| 				if (specialMode)
 | |
| 				{
 | |
| 					if (!An)
 | |
| 						return true;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					if (!An && !Bn)
 | |
| 						return true;
 | |
| 				}
 | |
| 			}
 | |
| 			j++;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (ignoreCase)
 | |
| 	{
 | |
| 		// if the 1st char is not a letter, no need to test one more time
 | |
| 		if (!isalpha(s[0]))
 | |
| 			return false;
 | |
| 
 | |
| 		firstChar = isupper(s[0])?tolower(s[0]):toupper(s[0]);
 | |
| 		j = list.starts[firstChar];
 | |
| 		if (j >= 0) 
 | |
| 		{
 | |
| 			while (toupper(list.words[j][0]) == toupper(s[0])) 
 | |
| 			{
 | |
| 				if (!list.words[j][1])
 | |
| 				{
 | |
| 					if (specialMode)
 | |
| 					{
 | |
| 						return true;
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						if (!s[1])
 | |
| 							return true;
 | |
| 					}
 | |
| 				}
 | |
| 				int a1 = toupper(list.words[j][1]);
 | |
| 				int b1 = toupper(s[1]);
 | |
| 				if (a1 == b1) 
 | |
| 				{
 | |
| 					const char *a = list.words[j] + 1;
 | |
| 					int An = toupper((int)*a);
 | |
| 
 | |
| 					const char *b = s + 1;
 | |
| 					int Bn = toupper((int)*b);
 | |
| 
 | |
| 					while (An && (An == Bn))
 | |
| 					{
 | |
| 						a++;
 | |
| 						An = toupper((int)*a);
 | |
| 						b++;
 | |
| 						Bn = toupper((int)*b);
 | |
| 					}
 | |
| 					if (specialMode)
 | |
| 					{
 | |
| 						if (!*a)
 | |
| 							return true;
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						if (!*a && !*b)
 | |
| 							return true;
 | |
| 					}
 | |
| 				}
 | |
| 				j++;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false;
 | |
| }
 | |
| /*
 | |
| static void getRange(unsigned int start, unsigned int end, Accessor &styler, char *s, unsigned int len) 
 | |
| {
 | |
| 	unsigned int i = 0;
 | |
| 	while ((i < end - start + 1) && (i < len-1)) 
 | |
| 	{
 | |
| 		s[i] = static_cast<char>(styler[start + i]);
 | |
| 		i++;
 | |
| 	}
 | |
| 	s[i] = '\0';
 | |
| }
 | |
| */
 | |
| static inline bool isAWordChar(const int ch) {
 | |
| 	//return (ch < 0x80) && (isalnum(ch) || ch == '.' || ch == '_');
 | |
| 	return ((ch > 0x20) && (ch <= 0xFF) && (ch != ' ') && (ch != '\n'));
 | |
| }
 | |
| 
 | |
| static inline bool isAWordStart(const int ch) {
 | |
| 	return isAWordChar(ch);
 | |
| }
 | |
| 
 | |
| static void ColouriseUserDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
 | |
|                             Accessor &styler) {
 | |
| 
 | |
| 	// It seems that there're only 9 keywordlists
 | |
| 	WordList &keywords = *keywordlists[0];
 | |
| 	WordList &blockOpenWords = *keywordlists[1];
 | |
| 	WordList &blockCloseWords = *keywordlists[2];
 | |
| 	WordList &symbols = *keywordlists[3];
 | |
| 	WordList &comments = *keywordlists[4];
 | |
| 	WordList &keywords5 = *keywordlists[5];
 | |
| 	WordList &keywords6 = *keywordlists[6];
 | |
|     WordList &keywords7 = *keywordlists[7];
 | |
| 	WordList &keywords8 = *keywordlists[8];
 | |
| 	//WordList &keywords9 = *keywordlists[9];
 | |
| 	//WordList &keywords10 = *keywordlists[10];
 | |
| 
 | |
| 	int chPrevNonWhite = ' ';
 | |
| 	int visibleChars = 0;
 | |
| 
 | |
| 	bool isCaseIgnored = styler.GetPropertyInt("userDefine.ignoreCase", 0) != 0;
 | |
| 	bool isCommentLineSymbol = styler.GetPropertyInt("userDefine.commentLineSymbol", 0) != 0;
 | |
| 	bool isCommentSymbol = styler.GetPropertyInt("userDefine.commentSymbol", 0) != 0;
 | |
| 
 | |
| 	bool doPrefix4G1 = styler.GetPropertyInt("userDefine.g1Prefix", 0) != 0;
 | |
| 	bool doPrefix4G2 = styler.GetPropertyInt("userDefine.g2Prefix", 0) != 0;
 | |
| 	bool doPrefix4G3 = styler.GetPropertyInt("userDefine.g3Prefix", 0) != 0;
 | |
| 	bool doPrefix4G4 = styler.GetPropertyInt("userDefine.g4Prefix", 0) != 0;
 | |
| 	
 | |
| 	char delimOpen[3];
 | |
| 	char delimClose[3];
 | |
|     bool escaped = false;
 | |
| 	char escapeChar = static_cast<char>(styler.GetPropertyInt("userDefine.escapeChar", 0));
 | |
| 
 | |
| 	bool hasDot = false;
 | |
| 	bool hasExp = false;
 | |
| 
 | |
| 	for (int i = 0 ; i < 3 ; i++)
 | |
| 	{
 | |
| 		delimOpen[i] = keywords.words[0][i] == '0'?'\0':keywords.words[0][i];
 | |
| 		delimClose[i] = keywords.words[0][i+3] == '0'?'\0':keywords.words[0][i+3];
 | |
| 	}
 | |
| 
 | |
| 	StyleContext sc(startPos, length, initStyle, styler);
 | |
| 
 | |
| 	for (; sc.More(); sc.Forward()) 
 | |
| 	{
 | |
| 		// Determine if the current state should terminate.
 | |
| 		switch (sc.state)
 | |
| 		{			
 | |
| 			case SCE_USER_NUMBER :
 | |
| 			{
 | |
| 				if (sc.ch == '.')
 | |
| 				{
 | |
| 					if (!hasDot)
 | |
| 					{
 | |
| 						hasDot = true;
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 				//if (!isAWordChar(sc.ch))
 | |
| 				if (sc.ch == 'e' || sc.ch == 'E')
 | |
| 				{
 | |
| 					if (!hasExp && sc.More())
 | |
| 					{
 | |
| 						hasExp = true;
 | |
| 						sc.Forward();
 | |
| 						if (IsADigit(sc.ch)) break;
 | |
| 						if (sc.More())
 | |
| 							if (sc.ch == '+' || sc.ch == '-')
 | |
| 							{
 | |
| 								sc.Forward();
 | |
| 								if (IsADigit(sc.ch)) break;
 | |
| 							}
 | |
| 					}
 | |
| 				}
 | |
| 				if (!IsADigit(sc.ch))
 | |
|     			{
 | |
| 					sc.SetState(SCE_USER_DEFAULT);
 | |
| 					hasDot = false;
 | |
| 					hasExp = false;
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			case SCE_USER_DELIMITER1 :
 | |
| 			{
 | |
| 				if (sc.ch == escapeChar)
 | |
| 					escaped = !escaped;
 | |
| 				else
 | |
| 				{
 | |
| 					if (delimClose[0] && (sc.ch == delimClose[0]) && !escaped)
 | |
|                         sc.ForwardSetState(SCE_USER_DEFAULT);
 | |
|                     escaped = false;
 | |
|                 }
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			case SCE_USER_DELIMITER2 :
 | |
| 			{
 | |
| 				if (sc.ch == escapeChar)
 | |
| 					escaped = !escaped;
 | |
| 				else
 | |
| 				{
 | |
| 					if (delimClose[0] && (sc.ch == delimClose[1]) && !escaped)
 | |
|                         sc.ForwardSetState(SCE_USER_DEFAULT);
 | |
|                     escaped = false;
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			case SCE_USER_DELIMITER3 :
 | |
| 			{
 | |
| 				if (sc.ch == escapeChar)
 | |
| 					escaped = !escaped;
 | |
| 				else
 | |
| 				{
 | |
| 					if (delimClose[0] && (sc.ch == delimClose[2]) && !escaped)
 | |
|                         sc.ForwardSetState(SCE_USER_DEFAULT);
 | |
|                     escaped = false;
 | |
|                 }
 | |
| 				break;
 | |
| 			}
 | |
| 			
 | |
| 			case SCE_USER_IDENTIFIER : 
 | |
| 			{
 | |
| 				bool isSymbol = isInOpList(symbols, sc.ch);
 | |
| 
 | |
| 				if (!isAWordChar(sc.ch)  || isSymbol)
 | |
| 				{
 | |
| 					bool doDefault = true;
 | |
| 					const int tokenLen = 100;
 | |
| 					char s[tokenLen];
 | |
| 					sc.GetCurrent(s, sizeof(s));
 | |
| 					char commentLineStr[tokenLen+10] = "0";
 | |
| 					char *p = commentLineStr+1;
 | |
| 					strcpy(p, s);
 | |
| 					char commentOpen[tokenLen+10] = "1";
 | |
| 					p = commentOpen+1;
 | |
| 					strcpy(p, s);
 | |
| 					
 | |
| 					if (isInList(keywords5, s, doPrefix4G1, isCaseIgnored))
 | |
| 						sc.ChangeState(SCE_USER_WORD1);
 | |
| 					else if (isInList(keywords6, s, doPrefix4G2, isCaseIgnored))
 | |
| 						sc.ChangeState(SCE_USER_WORD2);
 | |
| 					else if (isInList(keywords7, s, doPrefix4G3, isCaseIgnored))
 | |
| 						sc.ChangeState(SCE_USER_WORD3);
 | |
| 					else if (isInList(keywords8, s, doPrefix4G4, isCaseIgnored)) 
 | |
| 						sc.ChangeState(SCE_USER_WORD4);
 | |
| 
 | |
| 					//else if (blockOpenWords.InList(s)) 
 | |
| 					else if (isInList(blockOpenWords, s, false, isCaseIgnored)) 
 | |
| 						sc.ChangeState(SCE_USER_BLOCK_OPERATOR_OPEN);
 | |
| 					//else if (blockCloseWords.InList(s)) 
 | |
| 					else if (isInList(blockCloseWords, s, false, isCaseIgnored))
 | |
| 						sc.ChangeState(SCE_USER_BLOCK_OPERATOR_CLOSE);
 | |
| 					else if (isInList(comments,commentLineStr, isCommentLineSymbol, isCaseIgnored))
 | |
| 					{
 | |
| 						sc.ChangeState(SCE_USER_COMMENTLINE);
 | |
| 						doDefault = false;
 | |
| 					}
 | |
| 					else if (isInList(comments, commentOpen, isCommentSymbol, isCaseIgnored)) 
 | |
| 					{
 | |
| 					      sc.ChangeState(SCE_USER_COMMENT);
 | |
| 					      doDefault = false;
 | |
| 					}
 | |
| 					if (doDefault)
 | |
| 						sc.SetState(SCE_USER_DEFAULT);
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 			
 | |
| 			case SCE_USER_COMMENT :
 | |
| 			{
 | |
| 				char *pCommentClose = NULL;
 | |
| 				for (int i = 0 ; i < comments.len ; i++)
 | |
| 				{
 | |
| 					if (comments.words[i][0] == '2')
 | |
| 					{
 | |
| 						pCommentClose = comments.words[i] + 1;
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 				if (pCommentClose)
 | |
| 				{
 | |
| 					int len = strlen(pCommentClose);
 | |
| 					if (len == 1)
 | |
| 					{
 | |
| 						if (sc.Match(pCommentClose[0])) 
 | |
| 						{
 | |
| 							sc.Forward();
 | |
| 							sc.SetState(SCE_USER_DEFAULT);
 | |
| 						}
 | |
| 					}
 | |
| 					else 
 | |
| 					{
 | |
| 						if (sc.Match(pCommentClose)) 
 | |
| 						{
 | |
| 							for (int i = 0 ; i < len ; i++)
 | |
| 								sc.Forward();
 | |
| 							sc.SetState(SCE_USER_DEFAULT);
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				break;
 | |
| 			} 
 | |
| 			
 | |
| 			case SCE_USER_COMMENTLINE :
 | |
| 			{
 | |
| 				if (sc.atLineEnd) 
 | |
| 				{
 | |
| 					sc.SetState(SCE_USER_DEFAULT);
 | |
| 					visibleChars = 0;
 | |
| 				}
 | |
| 				break;
 | |
| 			} 
 | |
| 			
 | |
| 			case SCE_USER_OPERATOR :
 | |
| 			{
 | |
| 				sc.SetState(SCE_USER_DEFAULT);
 | |
| 				break;
 | |
| 			} 
 | |
| 			
 | |
| 			default :
 | |
| 				break;
 | |
| 		}
 | |
| 
 | |
| 		// Determine if a new state should be entered.
 | |
| 		if (sc.state == SCE_USER_DEFAULT) 
 | |
| 		{
 | |
| 			//char aSymbol[2] = {sc.ch, '\0'};
 | |
| 
 | |
| 			if (IsADigit(sc.ch))
 | |
| 				sc.SetState(SCE_USER_NUMBER);
 | |
| 			//else if (symbols.InList(aSymbol))
 | |
| 			else if (delimOpen[0] && (sc.ch == delimOpen[0]))
 | |
|             {
 | |
|                 escaped = false;
 | |
| 				sc.SetState(SCE_USER_DELIMITER1);
 | |
|             }
 | |
| 			else if (delimOpen[0] && (sc.ch == delimOpen[1]))
 | |
|             {
 | |
|                 escaped = false;
 | |
| 				sc.SetState(SCE_USER_DELIMITER2);
 | |
|             }
 | |
| 			else if (delimOpen[0] && (sc.ch == delimOpen[2]))
 | |
|             {
 | |
|                 escaped = false;
 | |
| 				sc.SetState(SCE_USER_DELIMITER3);
 | |
|             }
 | |
| 			else if (isInOpList(symbols, sc.ch))
 | |
| 				sc.SetState(SCE_USER_OPERATOR);
 | |
| 			else if (sc.ch == '-' && IsADigit(sc.chNext))
 | |
| 				sc.SetState(SCE_USER_NUMBER);
 | |
| 			else if (sc.ch == '.' && IsADigit(sc.chNext))
 | |
| 			{
 | |
| 				hasDot = true;
 | |
|                 sc.SetState(SCE_USER_NUMBER);
 | |
| 			}
 | |
| 			else if (isAWordStart(sc.ch)) 
 | |
| 				sc.SetState(SCE_USER_IDENTIFIER);
 | |
| 		}
 | |
| 
 | |
| 		if (sc.atLineEnd) 
 | |
| 		{
 | |
| 			// Reset states to begining of colourise so no surprises
 | |
| 			// if different sets of lines lexed.
 | |
| 		   if (sc.state == SCE_USER_COMMENTLINE)
 | |
| 				sc.SetState(SCE_USER_DEFAULT);
 | |
| 			
 | |
| 			chPrevNonWhite = ' ';
 | |
| 			visibleChars = 0;
 | |
| 		}
 | |
| 		if (!isspacechar(sc.ch)) {
 | |
| 			chPrevNonWhite = sc.ch;
 | |
| 			visibleChars++;
 | |
| 		}
 | |
| 		
 | |
| 	}
 | |
| 	sc.Complete();
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| static void FoldUserDoc(unsigned int startPos, int length, int initStyle, WordList *[],  Accessor &styler) 
 | |
| {
 | |
| 	unsigned int endPos = startPos + length;
 | |
| 	int visibleChars = 0;
 | |
| 	int lineCurrent = styler.GetLine(startPos);
 | |
| 	int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
 | |
| 	int levelCurrent = levelPrev;
 | |
| 	char chNext = styler[startPos];
 | |
| 	int styleNext = styler.StyleAt(startPos);
 | |
| 	int style = initStyle;
 | |
| 	
 | |
| 	for (unsigned int i = startPos; i < endPos; i++) 
 | |
| 	{
 | |
| 		char ch = chNext;
 | |
| 		chNext = styler.SafeGetCharAt(i + 1);
 | |
| 		int stylePrev = style;
 | |
| 		style = styleNext;
 | |
| 		styleNext = styler.StyleAt(i + 1);
 | |
| 		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
 | |
| 
 | |
| 		if (stylePrev != SCE_USER_BLOCK_OPERATOR_OPEN && style == SCE_USER_BLOCK_OPERATOR_OPEN)
 | |
| 		{
 | |
| 			levelCurrent++;
 | |
| 		} 
 | |
| 
 | |
| 		if (stylePrev != SCE_USER_BLOCK_OPERATOR_CLOSE && style == SCE_USER_BLOCK_OPERATOR_CLOSE)
 | |
| 		{
 | |
| 			levelCurrent--;
 | |
| 		}
 | |
| 
 | |
| 		if (atEOL) 
 | |
| 		{
 | |
| 			int lev = levelPrev;
 | |
| 
 | |
| 			if ((levelCurrent > levelPrev) && (visibleChars > 0))
 | |
| 				lev |= SC_FOLDLEVELHEADERFLAG;
 | |
| 			if (lev != styler.LevelAt(lineCurrent))
 | |
| 			{
 | |
| 				styler.SetLevel(lineCurrent, lev);
 | |
| 			}
 | |
| 			lineCurrent++;
 | |
| 			levelPrev = levelCurrent;
 | |
| 			visibleChars = 0;
 | |
| 		}
 | |
| 		if (!isspacechar(ch))
 | |
| 			visibleChars++;
 | |
| 	}
 | |
| 	// Fill in the real level of the next line, keeping the current flags as they will be filled in later
 | |
| 	int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
 | |
| 	styler.SetLevel(lineCurrent, levelPrev | flagsNext);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 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);
 |