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