[BUG_FIXED] (Author: François-R Boyer & Dave Brotherstone) Regex search and replace haning problem fix.

git-svn-id: svn://svn.tuxfamily.org/svnroot/notepadplus/repository/trunk@1009 f5eea248-9336-0410-98b8-ebc06183d4e3
This commit is contained in:
Don Ho 2013-01-25 00:46:29 +00:00
parent 27b2431ad3
commit ffdebe56ed
36 changed files with 4854 additions and 291 deletions

View File

@ -744,6 +744,7 @@ BOOL CALLBACK FindReplaceDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM lP
{
//Single actions
case IDCANCEL:
(*_ppEditView)->execute(SCI_CALLTIPCANCEL);
display(false);
break;
case IDOK : // Find Next : only for FIND_DLG and REPLACE_DLG
@ -1181,7 +1182,7 @@ BOOL CALLBACK FindReplaceDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM lP
// true : the text2find is found
// false : the text2find is not found
bool FindReplaceDlg::processFindNext(const TCHAR *txt2find, const FindOption *options, FindStatus *oFindStatus)
bool FindReplaceDlg::processFindNext(const TCHAR *txt2find, const FindOption *options, FindStatus *oFindStatus, FindNextType findNextType /* = FINDNEXTTYPE_FINDNEXT */)
{
if (oFindStatus)
*oFindStatus = FSFound;
@ -1191,10 +1192,12 @@ bool FindReplaceDlg::processFindNext(const TCHAR *txt2find, const FindOption *op
const FindOption *pOptions = options?options:_env;
(*_ppEditView)->execute(SCI_CALLTIPCANCEL);
int stringSizeFind = lstrlen(txt2find);
TCHAR *pText = new TCHAR[stringSizeFind + 1];
lstrcpy(pText, txt2find);
if (pOptions->_searchType == FindExtended) {
stringSizeFind = Searching::convertExtendedToString(txt2find, pText, stringSizeFind);
}
@ -1202,14 +1205,16 @@ bool FindReplaceDlg::processFindNext(const TCHAR *txt2find, const FindOption *op
int docLength = int((*_ppEditView)->execute(SCI_GETLENGTH));
CharacterRange cr = (*_ppEditView)->getSelection();
//The search "zone" is relative to the selection, so search happens 'outside'
int startPosition = cr.cpMax;
int endPosition = docLength;
if (pOptions->_whichDirection == DIR_UP)
{
//When searching upwards, start is the lower part, end the upper, for backwards search
startPosition = cr.cpMin - 1;
startPosition = cr.cpMax - 1;
endPosition = 0;
}
@ -1222,21 +1227,47 @@ bool FindReplaceDlg::processFindNext(const TCHAR *txt2find, const FindOption *op
else if (NextIncremental==pOptions->_incrementalType)
{
// text to find is not modified, so use current position +1
startPosition = cr.cpMin +1;
startPosition = cr.cpMin;
endPosition = docLength;
if (pOptions->_whichDirection == DIR_UP)
{
//When searching upwards, start is the lower part, end the upper, for backwards search
startPosition = cr.cpMax - 1;
startPosition = cr.cpMax;
endPosition = 0;
}
}
int flags = Searching::buildSearchFlags(pOptions);
switch (findNextType)
{
case FINDNEXTTYPE_FINDNEXT:
flags |= SCFIND_REGEXP_EMPTYMATCH_ALL | SCFIND_REGEXP_SKIPCRLFASONE;
break;
case FINDNEXTTYPE_REPLACENEXT:
flags |= SCFIND_REGEXP_EMPTYMATCH_NOTAFTERMATCH | SCFIND_REGEXP_SKIPCRLFASONE;
break;
case FINDNEXTTYPE_FINDNEXTFORREPLACE:
flags |= SCFIND_REGEXP_EMPTYMATCH_ALL | SCFIND_REGEXP_EMPTYMATCH_ALLOWATSTART | SCFIND_REGEXP_SKIPCRLFASONE;
break;
}
int start, end;
int posFind;
(*_ppEditView)->execute(SCI_SETSEARCHFLAGS, flags);
int posFind = (*_ppEditView)->searchInTarget(pText, stringSizeFind, startPosition, endPosition);
// Don't allow a search to start in the middle of a line end marker
if ((*_ppEditView)->execute(SCI_GETCHARAT, startPosition - 1) == '\r'
&& (*_ppEditView)->execute(SCI_GETCHARAT, startPosition) == '\n')
{
++startPosition;
}
posFind = (*_ppEditView)->searchInTarget(pText, stringSizeFind, startPosition, endPosition);
if (posFind == -1) //no match found in target, check if a new target should be used
{
if (pOptions->_isWrapAround)
@ -1290,17 +1321,30 @@ bool FindReplaceDlg::processFindNext(const TCHAR *txt2find, const FindOption *op
::MessageBox(_hParent, TEXT("Invalid regular expression"), TEXT("Find"), MB_ICONERROR | MB_OK);
return false;
}
int start = posFind;
int end = int((*_ppEditView)->execute(SCI_GETTARGETEND));
start = posFind;
end = int((*_ppEditView)->execute(SCI_GETTARGETEND));
// to make sure the found result is visible:
// prevent recording of absolute positioning commands issued in the process
(*_ppEditView)->execute(SCI_STOPRECORD);
Searching::displaySectionCentered(start, end, *_ppEditView, pOptions->_whichDirection == DIR_DOWN);
// Show a calltip for a zero length match
if (start == end)
{
(*_ppEditView)->execute(SCI_CALLTIPSHOW, start, reinterpret_cast<LPARAM>("^ zero length match"));
}
if (::SendMessage(_hParent, WM_GETCURRENTMACROSTATUS,0,0) == MACRO_RECORDING_IN_PROGRESS)
(*_ppEditView)->execute(SCI_STARTRECORD);
delete [] pText;
return true;
}
@ -1310,60 +1354,62 @@ bool FindReplaceDlg::processFindNext(const TCHAR *txt2find, const FindOption *op
// || the text is replaced, and do NOT find the next occurrence
bool FindReplaceDlg::processReplace(const TCHAR *txt2find, const TCHAR *txt2replace, const FindOption *options)
{
bool moreMatches;
if (!txt2find || !txt2find[0] || !txt2replace)
return false;
const FindOption *pOptions = options?options:_env;
FindOption replaceOptions = options ? *options : *_env;
replaceOptions._incrementalType = NextIncremental;
if ((*_ppEditView)->getCurrentBuffer()->isReadOnly()) return false;
int stringSizeFind = lstrlen(txt2find);
int stringSizeReplace = lstrlen(txt2replace);
TCHAR *pTextFind = new TCHAR[stringSizeFind + 1];
TCHAR *pTextReplace = new TCHAR[stringSizeReplace + 1];
lstrcpy(pTextFind, txt2find);
lstrcpy(pTextReplace, txt2replace);
if (pOptions->_searchType == FindExtended) {
stringSizeFind = Searching::convertExtendedToString(txt2find, pTextFind, stringSizeFind);
stringSizeReplace = Searching::convertExtendedToString(txt2replace, pTextReplace, stringSizeReplace);
}
bool isRegExp = pOptions->_searchType == FindRegex;
int flags = Searching::buildSearchFlags(pOptions);
CharacterRange cr = (*_ppEditView)->getSelection();
(*_ppEditView)->execute(SCI_SETSEARCHFLAGS, flags);
int posFind = (*_ppEditView)->searchInTarget(pTextFind, stringSizeFind, cr.cpMin, cr.cpMax);
if (posFind != -1)
{
if (isRegExp)
{
//For the rare re exp case. ex: replace ^ by AAA
int start = int((*_ppEditView)->execute(SCI_GETTARGETSTART));
int replacedLen = (*_ppEditView)->replaceTargetRegExMode(pTextReplace);
(*_ppEditView)->execute(SCI_SETSEL, start, start + replacedLen);
}
else
Sci_CharacterRange currentSelection = (*_ppEditView)->getSelection();
FindStatus status;
moreMatches = processFindNext(txt2find, &replaceOptions, &status, FINDNEXTTYPE_FINDNEXTFORREPLACE);
if (moreMatches)
{
Sci_CharacterRange nextFind = (*_ppEditView)->getSelection();
// If the next find is the same as the last, then perform the replacement
if (nextFind.cpMin == currentSelection.cpMin && nextFind.cpMax == currentSelection.cpMax)
{
int start = int((*_ppEditView)->execute(SCI_GETTARGETSTART));
int replacedLen = (*_ppEditView)->replaceTarget(pTextReplace);
(*_ppEditView)->execute(SCI_SETSEL, start, start + replacedLen);
int stringSizeFind = lstrlen(txt2find);
int stringSizeReplace = lstrlen(txt2replace);
TCHAR *pTextFind = new TCHAR[stringSizeFind + 1];
TCHAR *pTextReplace = new TCHAR[stringSizeReplace + 1];
lstrcpy(pTextFind, txt2find);
lstrcpy(pTextReplace, txt2replace);
bool isRegExp = replaceOptions._searchType == FindRegex;
int start = currentSelection.cpMin;
int replacedLen = 0;
if (isRegExp)
{
replacedLen = (*_ppEditView)->replaceTargetRegExMode(pTextReplace);
}
else
{
replacedLen = (*_ppEditView)->replaceTarget(pTextReplace);
}
(*_ppEditView)->execute(SCI_SETSEL, start + replacedLen, start + replacedLen);
// Do the next find
moreMatches = processFindNext(txt2find, &replaceOptions, &status, FINDNEXTTYPE_REPLACENEXT);
}
}
else if (posFind == -2) // Invalid Regular expression
{
::MessageBox(_hParent, TEXT("Invalid regular expression"), TEXT("Find"), MB_ICONERROR | MB_OK);
return false;
}
delete [] pTextFind;
delete [] pTextReplace;
return processFindNext(txt2find, pOptions); //after replacing, find the next section for selection
return moreMatches;
}
int FindReplaceDlg::markAll(const TCHAR *txt2find, int styleID)
{
FindOption opt;
@ -1517,9 +1563,14 @@ int FindReplaceDlg::processRange(ProcessOperation op, const TCHAR *txt2find, con
}
bool isRegExp = pOptions->_searchType == FindRegex;
int flags = Searching::buildSearchFlags(pOptions);
int flags = Searching::buildSearchFlags(pOptions) | SCFIND_REGEXP_SKIPCRLFASONE;
// Allow empty matches, but not immediately after previous match for replace all or find all.
// Other search types should ignore empty matches completely.
if (op == ProcessReplaceAll || op == ProcessFindAll)
flags |= SCFIND_REGEXP_EMPTYMATCH_NOTAFTERMATCH;
if (op == ProcessMarkAll && colourStyleID == -1) //if marking, check if purging is needed
{
@ -1535,37 +1586,38 @@ int FindReplaceDlg::processRange(ProcessOperation op, const TCHAR *txt2find, con
//Initial range for searching
(*_ppEditView)->execute(SCI_SETSEARCHFLAGS, flags);
targetStart = (*_ppEditView)->searchInTarget(pTextFind, stringSizeFind, startRange, endRange);
if ((targetStart >= 0) && (op == ProcessFindAll)) //add new filetitle if this file results in hits
{
_pFinder->addFileNameTitle(fileName);
}
bool findAllFileNameAdded = false;
while (targetStart != -1 && targetStart != -2)
{
//int posFindBefore = posFind;
targetStart = int((*_ppEditView)->execute(SCI_GETTARGETSTART));
targetStart = (*_ppEditView)->searchInTarget(pTextFind, stringSizeFind, startRange, endRange);
// If we've not found anything, just break out of the loop
if (targetStart == -1 || targetStart == -2)
break;
targetEnd = int((*_ppEditView)->execute(SCI_GETTARGETEND));
if (targetEnd > endRange) { //we found a result but outside our range, therefore do not process it
break;
}
int foundTextLen = targetEnd - targetStart;
int replaceDelta = 0;
// Search resulted in empty token, possible with RE
/*
if (!foundTextLen) {
delete [] pTextFind;
delete [] pTextReplace;
return -1;
}
*/
switch (op)
{
case ProcessFindAll:
{
if (!findAllFileNameAdded) //add new filetitle in hits if we haven't already
{
_pFinder->addFileNameTitle(fileName);
findAllFileNameAdded = true;
}
int lineNumber = (*_ppEditView)->execute(SCI_LINEFROMPOSITION, targetStart);
int lend = (*_ppEditView)->execute(SCI_GETLINEENDPOSITION, lineNumber);
int lstart = (*_ppEditView)->execute(SCI_POSITIONFROMLINE, lineNumber);
@ -1619,8 +1671,15 @@ int FindReplaceDlg::processRange(ProcessOperation op, const TCHAR *txt2find, con
case ProcessMarkAll:
{
(*_ppEditView)->execute(SCI_SETINDICATORCURRENT, SCE_UNIVERSAL_FOUND_STYLE);
(*_ppEditView)->execute(SCI_INDICATORFILLRANGE, targetStart, foundTextLen);
// In theory, we can't have empty matches for a ProcessMarkAll, but because scintilla
// gets upset if we call INDICATORFILLRANGE with a length of 0, we protect against it here.
// At least in version 2.27, after calling INDICATORFILLRANGE with length 0, further indicators
// on the same line would simply not be shown. This may have been fixed in later version of Scintilla.
if (foundTextLen > 0)
{
(*_ppEditView)->execute(SCI_SETINDICATORCURRENT, SCE_UNIVERSAL_FOUND_STYLE);
(*_ppEditView)->execute(SCI_INDICATORFILLRANGE, targetStart, foundTextLen);
}
if (_env->_doMarkLine)
{
@ -1635,22 +1694,34 @@ int FindReplaceDlg::processRange(ProcessOperation op, const TCHAR *txt2find, con
case ProcessMarkAllExt:
{
(*_ppEditView)->execute(SCI_SETINDICATORCURRENT, colourStyleID);
(*_ppEditView)->execute(SCI_INDICATORFILLRANGE, targetStart, foundTextLen);
// See comment by ProcessMarkAll
if (foundTextLen > 0)
{
(*_ppEditView)->execute(SCI_SETINDICATORCURRENT, colourStyleID);
(*_ppEditView)->execute(SCI_INDICATORFILLRANGE, targetStart, foundTextLen);
}
break;
}
case ProcessMarkAll_2:
{
(*_ppEditView)->execute(SCI_SETINDICATORCURRENT, SCE_UNIVERSAL_FOUND_STYLE_SMART);
(*_ppEditView)->execute(SCI_INDICATORFILLRANGE, targetStart, foundTextLen);
// See comment by ProcessMarkAll
if (foundTextLen > 0)
{
(*_ppEditView)->execute(SCI_SETINDICATORCURRENT, SCE_UNIVERSAL_FOUND_STYLE_SMART);
(*_ppEditView)->execute(SCI_INDICATORFILLRANGE, targetStart, foundTextLen);
}
break;
}
case ProcessMarkAll_IncSearch:
{
(*_ppEditView)->execute(SCI_SETINDICATORCURRENT, SCE_UNIVERSAL_FOUND_STYLE_INC);
(*_ppEditView)->execute(SCI_INDICATORFILLRANGE, targetStart, foundTextLen);
// See comment by ProcessMarkAll
if (foundTextLen > 0)
{
(*_ppEditView)->execute(SCI_SETINDICATORCURRENT, SCE_UNIVERSAL_FOUND_STYLE_INC);
(*_ppEditView)->execute(SCI_INDICATORFILLRANGE, targetStart, foundTextLen);
}
break;
}
@ -1675,14 +1746,14 @@ int FindReplaceDlg::processRange(ProcessOperation op, const TCHAR *txt2find, con
if( targetStart + foundTextLen == endRange )
break;
if (!foundTextLen && !replaceDelta)
startRange = targetStart + foundTextLen + 1; // find a empty string so just step forword
else
startRange = targetStart + foundTextLen + replaceDelta; //search from result onwards
startRange = targetStart + foundTextLen + replaceDelta; //search from result onwards
endRange += replaceDelta; //adjust end of range in case of replace
targetStart = (*_ppEditView)->searchInTarget(pTextFind, stringSizeFind, startRange, endRange);
}
}
delete [] pTextFind;
delete [] pTextReplace;

View File

@ -180,8 +180,17 @@ private:
static SearchResultMarking EmptySearchResultMarking;
};
enum FindStatus { FSFound, FSNotFound, FSTopReached, FSEndReached};
enum FindNextType {
FINDNEXTTYPE_FINDNEXT,
FINDNEXTTYPE_REPLACENEXT,
FINDNEXTTYPE_FINDNEXTFORREPLACE
};
class FindReplaceDlg : public StaticDialog
{
friend class FindIncrementDlg;
@ -208,7 +217,7 @@ public :
void initOptionsFromDlg();
void doDialog(DIALOG_TYPE whichType, bool isRTL = false, bool toShow = true);
bool processFindNext(const TCHAR *txt2find, const FindOption *options = NULL, FindStatus *oFindStatus = NULL);
bool processFindNext(const TCHAR *txt2find, const FindOption *options = NULL, FindStatus *oFindStatus = NULL, FindNextType findNextType = FINDNEXTTYPE_FINDNEXT);
bool processReplace(const TCHAR *txt2find, const TCHAR *txt2replace, const FindOption *options = NULL);
int markAll(const TCHAR *txt2find, int styleID);
@ -317,6 +326,8 @@ private :
TabBar _tab;
winVer _winVer;
void enableReplaceFunc(bool isEnable);
void enableFindInFilesControls(bool isEnable = true);
void enableFindInFilesFunc();
@ -347,6 +358,8 @@ private :
static const int FR_OP_FIF = 4;
static const int FR_OP_GLOBAL = 8;
void saveInMacro(int cmd, int cmdType);
};
//FindIncrementDlg: incremental search dialog, docked in rebar
@ -402,4 +415,7 @@ private :
void markSelectedTextInc(bool enable, FindOption *opt = NULL);
};
#endif //FIND_REPLACE_DLG_H

View File

@ -4,7 +4,7 @@
* Converted from boost::xpressive to boost::regex and performance improvements
* (principally caching the compiled regex), and support for UTF8 encoded text
* (c) 2012 Dave Brotherstone - Changes for boost::regex
*
* (c) 2013 Francois-R.Boyer@PolyMtl.ca - Empty match modes and best match backward search.
*
*/
#include <stdlib.h>
@ -35,49 +35,196 @@ using namespace Scintilla;
using namespace boost;
typedef basic_regex<char> charregex_t;
typedef boost::wregex wcharregex_t;
// , std::vector<boost::sub_match<DocumentIterator> >::allocator_type
typedef match_results<UTF8DocumentIterator> utf8match_t;
typedef match_results<AnsiDocumentIterator> ansimatch_t;
class BoostRegexSearch : public RegexSearchBase
{
public:
BoostRegexSearch() : substituted(NULL), lastCompileFlags(-1) {}
BoostRegexSearch() : _substituted(NULL) {}
virtual ~BoostRegexSearch()
{
if (substituted)
{
delete [] substituted;
substituted = NULL;
}
delete[] _substituted;
_substituted = NULL;
}
virtual long FindText(Document* doc, int minPos, int maxPos, const char *s,
bool caseSensitive, bool word, bool wordStart, int flags, int *length);
virtual long FindText(Document* doc, int startPosition, int endPosition, const char *regex,
bool caseSensitive, bool word, bool wordStart, int sciSearchFlags, int *lengthRet);
virtual const char *SubstituteByPosition(Document* doc, const char *text, int *length);
private:
wchar_t *utf8ToWchar(const char *utf8);
char *wcharToUtf8(const wchar_t *w);
class SearchParameters;
charregex_t m_charre;
wcharregex_t m_wcharre;
class Match : private DocWatcher {
public:
Match() : _document(NULL), _documentModified(false), _position(-1), _endPosition(-1), _endPositionForContinuationCheck(-1) {}
~Match() { setDocument(NULL); }
Match(Document* document, int position = -1, int endPosition = -1) : _document(NULL) { set(document, position, endPosition); }
Match& operator=(Match& m) {
set(m._document, m.position(), m.endPosition());
return *this;
}
Match& operator=(int /*nullptr*/) {
_position = -1;
return *this;
}
void set(Document* document = NULL, int position = -1, int endPosition = -1) {
setDocument(document);
_position = position;
_endPositionForContinuationCheck = _endPosition = endPosition;
_documentModified = false;
}
bool isContinuationSearch(Document* document, int startPosition, int direction) {
if (hasDocumentChanged(document))
return false;
if (direction > 0)
return startPosition == _endPositionForContinuationCheck;
else
return startPosition == _position;
}
bool isEmpty() {
return _position == _endPosition;
}
int position() {
return _position;
}
int endPosition() {
return _endPosition;
}
int length() {
return _endPosition - _position;
}
int found() {
return _position >= 0;
}
private:
bool hasDocumentChanged(Document* currentDocument) {
return currentDocument != _document || _documentModified;
}
void setDocument(Document* newDocument) {
if (newDocument != _document)
{
if (_document != NULL)
_document->RemoveWatcher(this, NULL);
_document = newDocument;
if (_document != NULL)
_document->AddWatcher(this, NULL);
}
}
// DocWatcher, so we can track modifications to know if we should consider a search to be a continuation of last search:
virtual void NotifyModified(Document* modifiedDocument, DocModification mh, void* /*userData*/)
{
if (modifiedDocument == _document)
{
if (mh.modificationType & (SC_PERFORMED_UNDO | SC_PERFORMED_REDO))
_documentModified = true;
// Replacing last found text should not make isContinuationSearch return false.
else if (mh.modificationType & SC_MOD_DELETETEXT)
{
if (mh.position == position() && mh.length == length()) // Deleting what we last found.
_endPositionForContinuationCheck = _position;
else _documentModified = true;
}
else if (mh.modificationType & SC_MOD_INSERTTEXT)
{
if (mh.position == position() && position() == _endPositionForContinuationCheck) // Replace at last found position.
_endPositionForContinuationCheck += mh.length;
else _documentModified = true;
}
}
}
virtual void NotifyDeleted(Document* deletedDocument, void* /*userData*/)
{
if (deletedDocument == _document)
set(NULL);
}
virtual void NotifyModifyAttempt(Document* /*document*/, void* /*userData*/) {}
virtual void NotifySavePoint(Document* /*document*/, void* /*userData*/, bool /*atSavePoint*/) {}
virtual void NotifyStyleNeeded(Document* /*document*/, void* /*userData*/, int /*endPos*/) {}
virtual void NotifyLexerChanged(Document* /*document*/, void* /*userData*/) {}
virtual void NotifyErrorOccurred(Document* /*document*/, void* /*userData*/, int /*status*/) {}
Document* _document;
bool _documentModified;
int _position, _endPosition;
int _endPositionForContinuationCheck;
};
utf8match_t m_utf8match;
ansimatch_t m_ansimatch;
class CharTPtr { // Automatically translatable from utf8 to wchar_t*, if required, with allocation and deallocation on destruction; char* is not deallocated.
public:
CharTPtr(const char* ptr) : _charPtr(ptr), _wcharPtr(NULL) {}
~CharTPtr() {
delete[] _wcharPtr;
}
operator const char*() {
return _charPtr;
}
operator const wchar_t*() {
if (_wcharPtr == NULL)
_wcharPtr = utf8ToWchar(_charPtr);
return _wcharPtr;
}
private:
const char* _charPtr;
wchar_t* _wcharPtr;
};
template <class CharT, class CharacterIterator>
class EncodingDependent {
public:
EncodingDependent() : _lastCompileFlags(-1) {}
void compileRegex(const char *regex, const int compileFlags);
Match FindText(SearchParameters& search);
char *SubstituteByPosition(const char *text, int *length);
private:
Match FindTextForward(SearchParameters& search);
Match FindTextBackward(SearchParameters& search);
public:
typedef CharT Char;
typedef basic_regex<CharT> Regex;
typedef match_results<CharacterIterator> MatchResults;
MatchResults _match;
private:
Regex _regex;
std::string _lastRegexString;
int _lastCompileFlags;
};
char *substituted;
std::string m_lastRegexString;
std::string m_lastRegexUtf8string;
int lastCompileFlags;
class SearchParameters {
public:
int nextCharacter(int position);
bool isLineStart(int position);
bool isLineEnd(int position);
Document* _document;
const char *_regexString;
int _compileFlags;
int _startPosition;
int _endPosition;
regex_constants::match_flag_type _boostRegexFlags;
int _direction;
bool _is_allowed_empty;
bool _is_allowed_empty_at_start_position;
bool _skip_windows_line_end_as_one_character;
};
static wchar_t *utf8ToWchar(const char *utf8);
static char *wcharToUtf8(const wchar_t *w);
static char *stringToCharPtr(const std::string& str);
static char *stringToCharPtr(const std::wstring& str);
EncodingDependent<char, AnsiDocumentIterator> _ansi;
EncodingDependent<wchar_t, UTF8DocumentIterator> _utf8;
char *_substituted;
Match _lastMatch;
int _lastDirection;
};
#ifdef SCI_NAMESPACE
@ -96,200 +243,199 @@ RegexSearchBase *CreateRegexSearch(CharClassify* /* charClassTable */)
/**
* Find text in document, supporting both forward and backward
* searches (just pass minPos > maxPos to do a backward search)
* searches (just pass startPosition > endPosition to do a backward search).
*/
long BoostRegexSearch::FindText(Document* doc, int minPos, int maxPos, const char *s,
bool caseSensitive, bool /*word*/, bool /*wordStart*/, int searchFlags, int *length)
long BoostRegexSearch::FindText(Document* doc, int startPosition, int endPosition, const char *regexString,
bool caseSensitive, bool /*word*/, bool /*wordStart*/, int sciSearchFlags, int *lengthRet)
{
int startPos, endPos, increment;
if (minPos > maxPos)
{
startPos = maxPos;
endPos = minPos;
increment = -1;
}
else
{
startPos = minPos;
endPos = maxPos;
increment = 1;
}
// Range endpoints should not be inside DBCS characters, but just in case, move them.
startPos = doc->MovePositionOutsideChar(startPos, 1, false);
endPos = doc->MovePositionOutsideChar(endPos, 1, false);
int compileFlags(regex_constants::ECMAScript);
if (!caseSensitive)
{
compileFlags |= regex_constants::icase;
}
bool isUtf8 = (doc->CodePage() == SC_CP_UTF8);
try
{
try {
SearchParameters search;
if (compileFlags != lastCompileFlags
|| (isUtf8 && m_lastRegexUtf8string != s)
|| (!isUtf8 && m_lastRegexString != s)) // Test to see if we're called with the same
// regex as last time, if we are, then we don't need to recompile it
search._document = doc;
if (startPosition > endPosition
|| startPosition == endPosition && _lastDirection < 0) // If we search in an empty region, suppose the direction is the same as last search (this is only important to verify if there can be an empty match in that empty region).
{
if (isUtf8)
{
const wchar_t* wchars = utf8ToWchar(s);
m_wcharre = wcharregex_t(wchars, static_cast<regex_constants::syntax_option_type>(compileFlags));
delete [] wchars;
m_lastRegexUtf8string = s;
}
else
{ // Ansi
m_charre = charregex_t(s, static_cast<regex_constants::syntax_option_type>(compileFlags));
m_lastRegexString = s;
}
lastCompileFlags = compileFlags;
search._startPosition = endPosition;
search._endPosition = startPosition;
search._direction = -1;
}
else
{
search._startPosition = startPosition;
search._endPosition = endPosition;
search._direction = 1;
}
_lastDirection = search._direction;
// Range endpoints should not be inside DBCS characters, but just in case, move them.
search._startPosition = doc->MovePositionOutsideChar(search._startPosition, 1, false);
search._endPosition = doc->MovePositionOutsideChar(search._endPosition, 1, false);
const bool isUtf8 = (doc->CodePage() == SC_CP_UTF8);
search._compileFlags =
regex_constants::ECMAScript
| (caseSensitive ? 0 : regex_constants::icase);
search._regexString = regexString;
const bool starts_at_line_start = search.isLineStart(search._startPosition);
const bool ends_at_line_end = search.isLineEnd(search._endPosition);
search._boostRegexFlags =
(starts_at_line_start ? regex_constants::match_default : regex_constants::match_not_bol)
| (ends_at_line_end ? regex_constants::match_default : regex_constants::match_not_eol)
| ((sciSearchFlags & SCFIND_REGEXP_DOTMATCHESNL) ? regex_constants::match_default : regex_constants::match_not_dot_newline);
const int empty_match_style = sciSearchFlags & SCFIND_REGEXP_EMPTYMATCH_MASK;
const int allow_empty_at_start = sciSearchFlags & SCFIND_REGEXP_EMPTYMATCH_ALLOWATSTART;
search._is_allowed_empty = empty_match_style != SCFIND_REGEXP_EMPTYMATCH_NONE;
search._is_allowed_empty_at_start_position = search._is_allowed_empty &&
(allow_empty_at_start
|| !_lastMatch.isContinuationSearch(doc, startPosition, search._direction)
|| empty_match_style == SCFIND_REGEXP_EMPTYMATCH_ALL && !_lastMatch.isEmpty() // If last match is empty and this is a continuation, then we would have same empty match at start position, if it was allowed.
);
search._skip_windows_line_end_as_one_character = (sciSearchFlags & SCFIND_REGEXP_SKIPCRLFASONE) != 0;
Match match =
isUtf8 ? _utf8.FindText(search)
: _ansi.FindText(search);
if (match.found())
{
*lengthRet = match.length();
_lastMatch = match;
return match.position();
}
else
{
_lastMatch = NULL;
return -1;
}
}
catch(regex_error& /*ex*/)
{
// -1 is normally used for not found, -2 is used here for invalid regex
return -2;
}
// Work out the range of lines we're searching across, moving beyond an empty end-of-line
int lineRangeStart = doc->LineFromPosition(startPos);
int lineRangeEnd = doc->LineFromPosition(endPos);
regex_constants::match_flag_type flags(regex_constants::match_default);
// Work out the flags:
if (startPos != doc->LineStart(lineRangeStart))
{
flags |= regex_constants::match_not_bol;
}
if (endPos != doc->LineEnd(lineRangeEnd))
{
flags |= regex_constants::match_not_eol;
}
if (0 == (searchFlags & SCFIND_REGEXP_DOTMATCHESNL))
{
flags |= regex_constants::match_not_dot_newline;
}
int pos(-1);
int lenRet(0);
if (doc->CodePage() == SC_CP_UTF8)
{
UTF8DocumentIterator end(doc, endPos, endPos);
bool success = boost::regex_search(UTF8DocumentIterator(doc, startPos, endPos), end, m_utf8match, m_wcharre, flags);
if (success)
{
pos = m_utf8match[0].first.pos();
lenRet = m_utf8match[0].second.pos() - pos;
if (increment == -1)
{
// Check for the last match on this line.
int repetitions = 100; // Break out of infinite loop
int previousPos = pos;
while (success && ((pos + lenRet) <= endPos))
{
if (previousPos >= pos && 0 >= (--repetitions))
break;
previousPos = pos;
success = regex_search(UTF8DocumentIterator(doc, pos + 1, endPos), end, m_utf8match, m_wcharre, flags);
// success = regex_search(DocumentIterator(doc, pos + 1, endPos), end, match, re, static_cast<regex_constants::match_flag_type>(flags));
if (success)
{
if ((pos + lenRet) <= minPos)
{
pos = m_utf8match[0].first.pos();
lenRet = m_utf8match[0].second.pos() - pos;
}
else
{
success = 0;
}
}
}
}
*length = lenRet;
}
}
else
{
AnsiDocumentIterator end(doc, endPos, endPos);
bool success = boost::regex_search(AnsiDocumentIterator(doc, startPos, endPos), end, m_ansimatch, m_charre, flags);
if (success)
{
pos = m_ansimatch[0].first.pos();
lenRet = m_ansimatch.length();
if (increment == -1)
{
// Check for the last match on this line.
int repetitions = 100; // Break out of infinite loop
int previousPos = pos;
while (success && ((pos + lenRet) <= endPos))
{
if (previousPos >= pos && 0 >= (--repetitions))
break;
previousPos = pos;
success = regex_search(AnsiDocumentIterator(doc, pos + 1, endPos), end, m_ansimatch, m_charre, flags);
// success = regex_search(DocumentIterator(doc, pos + 1, endPos), end, match, re, static_cast<regex_constants::match_flag_type>(flags));
if (success)
{
if ((pos + lenRet) <= minPos)
{
pos = m_ansimatch[0].first.pos();
lenRet = m_ansimatch[0].length();
}
else
{
success = 0;
}
}
}
}
*length = lenRet;
}
}
return pos;
}
template <class CharT, class CharacterIterator>
BoostRegexSearch::Match BoostRegexSearch::EncodingDependent<CharT, CharacterIterator>::FindText(SearchParameters& search)
{
compileRegex(search._regexString, search._compileFlags);
return (search._direction > 0)
? FindTextForward(search)
: FindTextBackward(search);
}
template <class CharT, class CharacterIterator>
BoostRegexSearch::Match BoostRegexSearch::EncodingDependent<CharT, CharacterIterator>::FindTextForward(SearchParameters& search)
{
CharacterIterator endIterator(search._document, search._endPosition, search._endPosition);
int next_search_from_position = search._startPosition;
bool found = false;
bool match_is_valid = false;
do {
search._boostRegexFlags = search.isLineStart(next_search_from_position)
? search._boostRegexFlags & ~regex_constants::match_not_bol
: search._boostRegexFlags | regex_constants::match_not_bol;
const bool end_reached = next_search_from_position > search._endPosition;
found = !end_reached && boost::regex_search(CharacterIterator(search._document, next_search_from_position, search._endPosition), endIterator, _match, _regex, search._boostRegexFlags);
if (found) {
const int position = _match[0].first.pos();
const int length = _match[0].second.pos() - position;
const bool match_is_non_empty = length != 0;
const bool is_allowed_empty_here = search._is_allowed_empty && (search._is_allowed_empty_at_start_position || position > search._startPosition);
match_is_valid = match_is_non_empty || is_allowed_empty_here;
if (!match_is_valid)
next_search_from_position = search.nextCharacter(position);
}
} while (found && !match_is_valid);
if (found)
return Match(search._document, _match[0].first.pos(), _match[0].second.pos());
else
return Match();
}
template <class CharT, class CharacterIterator>
BoostRegexSearch::Match BoostRegexSearch::EncodingDependent<CharT, CharacterIterator>::FindTextBackward(SearchParameters& search)
{
// Change backward search into series of forward search. It is slow: search all backward becomes O(n^2) instead of O(n) (if search forward is O(n)).
//NOTE: Maybe we should cache results. Maybe we could reverse regex to do a real backward search, for simple regex.
search._direction = 1;
const bool is_allowed_empty_at_end_position = search._is_allowed_empty_at_start_position;
search._is_allowed_empty_at_start_position = search._is_allowed_empty;
MatchResults bestMatch;
int bestPosition = -1;
int bestEnd = -1;
for (;;) {
Match matchRange = FindText(search);
if (!matchRange.found())
break;
int position = matchRange.position();
int endPosition = matchRange.endPosition();
if (endPosition > bestEnd && (endPosition < search._endPosition || position != endPosition || is_allowed_empty_at_end_position)) // We are searching for the longest match which has the fathest end (but may not accept empty match at end position).
{
bestMatch = _match;
bestPosition = position;
bestEnd = endPosition;
}
search._startPosition = search.nextCharacter(position);
}
if (bestPosition >= 0)
return Match(search._document, bestPosition, bestEnd);
else
return Match();
}
template <class CharT, class CharacterIterator>
void BoostRegexSearch::EncodingDependent<CharT, CharacterIterator>::compileRegex(const char *regex, const int compileFlags)
{
if (_lastCompileFlags != compileFlags || _lastRegexString != regex)
{
_regex = Regex(CharTPtr(regex), static_cast<regex_constants::syntax_option_type>(compileFlags));
_lastRegexString = regex;
_lastCompileFlags = compileFlags;
}
}
int BoostRegexSearch::SearchParameters::nextCharacter(int position)
{
if (_skip_windows_line_end_as_one_character && _document->CharAt(position) == '\r' && _document->CharAt(position+1) == '\n')
return position + 2;
else
return position + 1;
}
bool BoostRegexSearch::SearchParameters::isLineStart(int position)
{
return (position == 0)
|| _document->CharAt(position-1) == '\n'
|| _document->CharAt(position-1) == '\r' && _document->CharAt(position) != '\n';
}
bool BoostRegexSearch::SearchParameters::isLineEnd(int position)
{
return (position == _document->Length())
|| _document->CharAt(position) == '\r'
|| _document->CharAt(position) == '\n' && (position == 0 || _document->CharAt(position-1) != '\n');
}
const char *BoostRegexSearch::SubstituteByPosition(Document* doc, const char *text, int *length) {
delete []substituted;
substituted = NULL;
if (doc->CodePage() == SC_CP_UTF8)
{
const wchar_t* wtext = utf8ToWchar(text);
std::wstring replaced = m_utf8match.format(wtext, boost::format_all);
delete[] wtext;
substituted = wcharToUtf8(replaced.c_str());
*length = strlen(substituted);
}
else
{
std::string replaced = m_ansimatch.format(text, boost::format_all);
*length = replaced.size();
substituted = new char[*length + 1];
strcpy(substituted, replaced.c_str());
}
delete[] _substituted;
_substituted = (doc->CodePage() == SC_CP_UTF8)
? _utf8.SubstituteByPosition(text, length)
: _ansi.SubstituteByPosition(text, length);
return _substituted;
}
template <class CharT, class CharacterIterator>
char *BoostRegexSearch::EncodingDependent<CharT, CharacterIterator>::SubstituteByPosition(const char *text, int *length) {
char *substituted = stringToCharPtr(_match.format((const CharT*)CharTPtr(text), boost::format_all));
*length = strlen(substituted);
return substituted;
}
@ -300,11 +446,10 @@ wchar_t *BoostRegexSearch::utf8ToWchar(const char *utf8)
wchar_t *w = new wchar_t[wcharSize + 1];
UTF16FromUTF8(utf8, utf8Size, w, wcharSize + 1);
w[wcharSize] = 0;
return w;
}
char* BoostRegexSearch::wcharToUtf8(const wchar_t *w)
char *BoostRegexSearch::wcharToUtf8(const wchar_t *w)
{
int wcharSize = wcslen(w);
int charSize = UTF8Length(w, wcharSize);
@ -312,4 +457,15 @@ char* BoostRegexSearch::wcharToUtf8(const wchar_t *w)
UTF8FromUTF16(w, wcharSize, c, charSize);
c[charSize] = 0;
return c;
}
char *BoostRegexSearch::stringToCharPtr(const std::string& str)
{
char *charPtr = new char[str.length() + 1];
strcpy(charPtr, str.c_str());
return charPtr;
}
char *BoostRegexSearch::stringToCharPtr(const std::wstring& str)
{
return wcharToUtf8(str.c_str());
}

View File

@ -1,6 +1,12 @@
#ifndef BOOSTREGEXSEARCH_H
#define BOOSTREGEXSEARCH_H
#define SCFIND_REGEXP_DOTMATCHESNL 0x10000000
#define SCFIND_REGEXP_DOTMATCHESNL 0x10000000
#define SCFIND_REGEXP_EMPTYMATCH_MASK 0xE0000000
#define SCFIND_REGEXP_EMPTYMATCH_NONE 0x00000000
#define SCFIND_REGEXP_EMPTYMATCH_NOTAFTERMATCH 0x20000000
#define SCFIND_REGEXP_EMPTYMATCH_ALL 0x40000000
#define SCFIND_REGEXP_EMPTYMATCH_ALLOWATSTART 0x80000000
#define SCFIND_REGEXP_SKIPCRLFASONE 0x08000000
#endif

View File

@ -834,6 +834,13 @@ val SCFIND_MATCHCASE=4
val SCFIND_WORDSTART=0x00100000
val SCFIND_REGEXP=0x00200000
val SCFIND_POSIX=0x00400000
val SCFIND_REGEXP_DOTMATCHESNL=0x10000000
val SCFIND_REGEXP_EMPTYMATCH_MASK=0xE0000000
val SCFIND_REGEXP_EMPTYMATCH_NONE=0x00000000
val SCFIND_REGEXP_EMPTYMATCH_NOTAFTERMATCH=0x20000000
val SCFIND_REGEXP_EMPTYMATCH_ALL=0x40000000
val SCFIND_REGEXP_EMPTYMATCH_ALLOWATSTART=0x80000000
val SCFIND_REGEXP_SKIPCRLFASONE=0x08000000
# Find some text in the document.
fun position FindText=2150(int flags, findtext ft)

View File

@ -0,0 +1,63 @@
# List many windows message numbers
msgs = {
"WM_ACTIVATE":6,
"WM_ACTIVATEAPP":28,
"WM_CAPTURECHANGED":533,
"WM_CHAR":258,
"WM_CLOSE":16,
"WM_CREATE":1,
"WM_COMMAND":273,
"WM_DESTROY":2,
"WM_ENTERSIZEMOVE":561,
"WM_ERASEBKGND":20,
"WM_EXITSIZEMOVE":562,
"WM_GETMINMAXINFO":36,
"WM_GETTEXT":13,
"WM_IME_SETCONTEXT":0x0281,
"WM_IME_NOTIFY":0x0282,
"WM_KEYDOWN":256,
"WM_KEYUP":257,
"WM_KILLFOCUS":8,
"WM_LBUTTONDOWN":513,
"WM_LBUTTONUP":514,
"WM_MBUTTONDOWN":519,
"WM_MBUTTONUP":520,
"WM_MBUTTONDBLCLK":521,
"WM_MOUSEACTIVATE":33,
"WM_MOUSEMOVE":512,
"WM_MOVE":3,
"WM_MOVING":534,
"WM_NCACTIVATE":134,
"WM_NCCALCSIZE":131,
"WM_NCCREATE":129,
"WM_NCDESTROY":130,
"WM_NCHITTEST":132,
"WM_NCLBUTTONDBLCLK":163,
"WM_NCLBUTTONDOWN":161,
"WM_NCLBUTTONUP":162,
"WM_NCMOUSEMOVE":160,
"WM_NCPAINT":133,
"WM_PAINT":15,
"WM_PARENTNOTIFY":528,
"WM_SETCURSOR":32,
"WM_SETFOCUS":7,
"WM_SETFONT":48,
"WM_SETTEXT":12,
"WM_SHOWWINDOW":24,
"WM_SIZE":5,
"WM_SIZING":532,
"WM_SYNCPAINT":136,
"WM_SYSCOMMAND":274,
"WM_SYSKEYDOWN":260,
"WM_TIMER":275,
"WM_USER":1024,
"WM_USER+1":1025,
"WM_WINDOWPOSCHANGED":71,
"WM_WINDOWPOSCHANGING":70,
}
sgsm={}
for k,v in msgs.items():
sgsm[v] = k

31
scintilla/test/README Normal file
View File

@ -0,0 +1,31 @@
The test directory contains some unit and performance tests for Scintilla.
The tests can only be run on Windows using Python 3.x. Running on another platform
would require writing a file similar to XiteWin.py for that platform. Python 3.x is required
because its default string type is Unicode and earlier Python versions use byte strings
and the interface to the platform assumes a particular string type.
A test application is in xite.py and this can be run to experiment:
pythonw xite.py
To run the basic tests:
pythonw simpleTests.py
There are some lexing tests with simple input files in several languages in the examples
subdirectory and their expected lexed states in *.styled where the start of each style
is marked with {styleNumber}, for example:
{15}<%@{16}language=javas{15}%>{0}
To run the lexing tests:
pythonw lexTests.py
To check for performance regressions:
pythonw performanceTests.py
While each test run will be different and the timer has only limited granularity, some results
from a 2 GHz Athlon with a DEBUG build are:
0.187 testAddLine
. 0.203 testAddLineMiddle
. 0.171 testHuge
. 0.203 testHugeInserts
. 0.312 testHugeReplace
.

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
""" Define the menu structure used by the Pentacle applications """
MenuStructure = [
["&File", [
["&New", "<control>N"],
["&Open...", "<control>O"],
["&Save", "<control>S"],
["Save &As...", "<control><shift>S"],
["Test", ""],
["Exercised", ""],
["Uncalled", ""],
["-", ""],
["&Exit", ""]]],
[ "&Edit", [
["&Undo", "<control>Z"],
["&Redo", "<control>Y"],
["-", ""],
["Cu&t", "<control>X"],
["&Copy", "<control>C"],
["&Paste", "<control>V"],
["&Delete", "Del"],
["Select &All", "<control>A"],
]],
]

666
scintilla/test/XiteWin.py Normal file
View File

@ -0,0 +1,666 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import unicode_literals
import os, sys, unittest
import ctypes
from ctypes import wintypes
from ctypes import c_int, c_ulong, c_char_p, c_wchar_p, c_ushort
user32=ctypes.windll.user32
gdi32=ctypes.windll.gdi32
kernel32=ctypes.windll.kernel32
from MessageNumbers import msgs, sgsm
import XiteMenu
scintillaDirectory = ".."
scintillaIncludeDirectory = os.path.join(scintillaDirectory, "include")
sys.path.append(scintillaIncludeDirectory)
import Face
scintillaBinDirectory = os.path.join(scintillaDirectory, "bin")
os.environ['PATH'] = os.environ['PATH'] + ";" + scintillaBinDirectory
#print(os.environ['PATH'])
WFUNC = ctypes.WINFUNCTYPE(c_int, c_int, c_int, c_int, c_int)
WS_CHILD = 0x40000000
WS_CLIPCHILDREN = 0x2000000
WS_OVERLAPPEDWINDOW = 0xcf0000
WS_VISIBLE = 0x10000000
WS_HSCROLL = 0x100000
WS_VSCROLL = 0x200000
WA_INACTIVE = 0
MF_POPUP = 16
MF_SEPARATOR = 0x800
IDYES = 6
OFN_HIDEREADONLY = 4
MB_OK = 0
MB_YESNOCANCEL = 3
MF_CHECKED = 8
MF_UNCHECKED = 0
SW_SHOW = 5
PM_REMOVE = 1
VK_SHIFT = 16
VK_CONTROL = 17
VK_MENU = 18
class OPENFILENAME(ctypes.Structure):
_fields_ = (("lStructSize", c_int),
("hwndOwner", c_int),
("hInstance", c_int),
("lpstrFilter", c_wchar_p),
("lpstrCustomFilter", c_char_p),
("nMaxCustFilter", c_int),
("nFilterIndex", c_int),
("lpstrFile", c_wchar_p),
("nMaxFile", c_int),
("lpstrFileTitle", c_wchar_p),
("nMaxFileTitle", c_int),
("lpstrInitialDir", c_wchar_p),
("lpstrTitle", c_wchar_p),
("flags", c_int),
("nFileOffset", c_ushort),
("nFileExtension", c_ushort),
("lpstrDefExt", c_char_p),
("lCustData", c_int),
("lpfnHook", c_char_p),
("lpTemplateName", c_char_p),
("pvReserved", c_char_p),
("dwReserved", c_int),
("flagsEx", c_int))
def __init__(self, win, title):
ctypes.Structure.__init__(self)
self.lStructSize = ctypes.sizeof(OPENFILENAME)
self.nMaxFile = 1024
self.hwndOwner = win
self.lpstrTitle = title
self.Flags = OFN_HIDEREADONLY
trace = False
#~ trace = True
def WindowSize(w):
rc = ctypes.wintypes.RECT()
user32.GetClientRect(w, ctypes.byref(rc))
return rc.right - rc.left, rc.bottom - rc.top
def IsKeyDown(key):
return (user32.GetKeyState(key) & 0x8000) != 0
def KeyTranslate(w):
tr = { 9: "Tab", 0xD:"Enter", 0x1B: "Esc" }
if w in tr:
return tr[w]
elif ord("A") <= w <= ord("Z"):
return chr(w)
elif 0x70 <= w <= 0x7b:
return "F" + str(w-0x70+1)
else:
return "Unknown_" + hex(w)
class WNDCLASS(ctypes.Structure):
_fields_= (\
('style', c_int),
('lpfnWndProc', WFUNC),
('cls_extra', c_int),
('wnd_extra', c_int),
('hInst', c_int),
('hIcon', c_int),
('hCursor', c_int),
('hbrBackground', c_int),
('menu_name', c_wchar_p),
('lpzClassName', c_wchar_p),
)
class XTEXTRANGE(ctypes.Structure):
_fields_= (\
('cpMin', c_int),
('cpMax', c_int),
('lpstrText', c_char_p),
)
class TEXTRANGE(ctypes.Structure):
_fields_= (\
('cpMin', c_int),
('cpMax', c_int),
('lpstrText', ctypes.POINTER(ctypes.c_char)),
)
class FINDTEXT(ctypes.Structure):
_fields_= (\
('cpMin', c_int),
('cpMax', c_int),
('lpstrText', c_char_p),
('cpMinText', c_int),
('cpMaxText', c_int),
)
hinst = ctypes.windll.kernel32.GetModuleHandleW(0)
def RegisterClass(name, func, background = 0):
# register a window class for toplevel windows.
wc = WNDCLASS()
wc.style = 0
wc.lpfnWndProc = func
wc.cls_extra = 0
wc.wnd_extra = 0
wc.hInst = hinst
wc.hIcon = 0
wc.hCursor = 0
wc.hbrBackground = background
wc.menu_name = 0
wc.lpzClassName = name
user32.RegisterClassW(ctypes.byref(wc))
class SciCall:
def __init__(self, fn, ptr, msg):
self._fn = fn
self._ptr = ptr
self._msg = msg
def __call__(self, w=0, l=0):
return self._fn(self._ptr, self._msg, w, l)
class Scintilla:
def __init__(self, face, hwndParent, hinstance):
self.__dict__["face"] = face
self.__dict__["used"] = set()
self.__dict__["all"] = set()
# The k member is for accessing constants as a dictionary
self.__dict__["k"] = {}
for f in face.features:
self.all.add(f)
if face.features[f]["FeatureType"] == "val":
self.k[f] = int(self.face.features[f]["Value"], 0)
elif face.features[f]["FeatureType"] == "evt":
self.k["SCN_"+f] = int(self.face.features[f]["Value"], 0)
# Get the function first as that also loads the DLL
import ctypes.util
print >> sys.stderr, "SciLexer path: ",ctypes.util.find_library("scilexer")
self.__dict__["_scifn"] = ctypes.windll.SciLexer.Scintilla_DirectFunction
self.__dict__["_hwnd"] = user32.CreateWindowExW(0,
"Scintilla", "Source",
WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_CLIPCHILDREN,
0, 0, 100, 100, hwndParent, 0, hinstance, 0)
self.__dict__["_sciptr"] = user32.SendMessageW(self._hwnd,
int(self.face.features["GetDirectPointer"]["Value"], 0), 0,0)
user32.ShowWindow(self._hwnd, SW_SHOW)
def __getattr__(self, name):
if name in self.face.features:
self.used.add(name)
feature = self.face.features[name]
value = int(feature["Value"], 0)
#~ print("Feature", name, feature)
if feature["FeatureType"] == "val":
self.__dict__[name] = value
return value
else:
return SciCall(self._scifn, self._sciptr, value)
elif ("Get" + name) in self.face.features:
self.used.add("Get" + name)
feature = self.face.features["Get" + name]
value = int(feature["Value"], 0)
if feature["FeatureType"] == "get" and \
not name.startswith("Get") and \
not feature["Param1Type"] and \
not feature["Param2Type"] and \
feature["ReturnType"] in ["bool", "int", "position"]:
#~ print("property", feature)
return self._scifn(self._sciptr, value, 0, 0)
elif name.startswith("SCN_") and name in self.k:
self.used.add(name)
feature = self.face.features[name[4:]]
value = int(feature["Value"], 0)
#~ print("Feature", name, feature)
if feature["FeatureType"] == "val":
return value
raise AttributeError(name)
def __setattr__(self, name, val):
if ("Set" + name) in self.face.features:
self.used.add("Set" + name)
feature = self.face.features["Set" + name]
value = int(feature["Value"], 0)
#~ print("setproperty", feature)
if feature["FeatureType"] == "set" and not name.startswith("Set"):
if feature["Param1Type"] in ["bool", "int", "position"]:
return self._scifn(self._sciptr, value, val, 0)
elif feature["Param2Type"] in ["string"]:
return self._scifn(self._sciptr, value, 0, val)
raise AttributeError(name)
raise AttributeError(name)
def getvalue(self, name):
if name in self.face.features:
feature = self.face.features[name]
if feature["FeatureType"] != "evt":
try:
return int(feature["Value"], 0)
except ValueError:
return -1
return -1
def ByteRange(self, start, end):
tr = TEXTRANGE()
tr.cpMin = start
tr.cpMax = end
length = end - start
tr.lpstrText = ctypes.create_string_buffer(length + 1)
self.GetTextRange(0, ctypes.byref(tr))
text = tr.lpstrText[:length]
text += b"\0" * (length - len(text))
return text
def StyledTextRange(self, start, end):
tr = TEXTRANGE()
tr.cpMin = start
tr.cpMax = end
length = 2 * (end - start)
tr.lpstrText = ctypes.create_string_buffer(length + 2)
self.GetStyledText(0, ctypes.byref(tr))
styledText = tr.lpstrText[:length]
styledText += b"\0" * (length - len(styledText))
return styledText
def FindBytes(self, start, end, s, flags):
ft = FINDTEXT()
ft.cpMin = start
ft.cpMax = end
ft.lpstrText = s
ft.cpMinText = 0
ft.cpMaxText = 0
pos = self.FindText(flags, ctypes.byref(ft))
#~ print(start, end, ft.cpMinText, ft.cpMaxText)
return pos
def FindBytes2(self, start, end, s, flags):
ft = FINDTEXT()
ft.cpMin = start
ft.cpMax = end
ft.lpstrText = s
ft.cpMinText = 0
ft.cpMaxText = 0
pos = self.FindText(flags, ctypes.byref(ft))
#~ print(start, end, ft.cpMinText, ft.cpMaxText)
return (pos, ft.cpMinText, ft.cpMaxText)
def Contents(self):
return self.ByteRange(0, self.Length)
def SizeTo(self, width, height):
user32.SetWindowPos(self._hwnd, 0, 0, 0, width, height, 0)
def FocusOn(self):
user32.SetFocus(self._hwnd)
class XiteWin():
def __init__(self, test=""):
self.face = Face.Face()
self.face.ReadFromFile(os.path.join(scintillaIncludeDirectory, "Scintilla.iface"))
self.titleDirty = True
self.fullPath = ""
self.test = test
self.appName = "xite"
self.cmds = {}
self.windowName = "XiteWindow"
self.wfunc = WFUNC(self.WndProc)
RegisterClass(self.windowName, self.wfunc)
user32.CreateWindowExW(0, self.windowName, self.appName, \
WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, \
0, 0, 500, 700, 0, 0, hinst, 0)
args = sys.argv[1:]
self.SetMenus()
if args:
self.GrabFile(args[0])
self.ed.FocusOn()
self.ed.GotoPos(self.ed.Length)
if self.test:
print(self.test)
for k in self.cmds:
if self.cmds[k] == "Test":
user32.PostMessageW(self.win, msgs["WM_COMMAND"], k, 0)
def OnSize(self):
width, height = WindowSize(self.win)
self.ed.SizeTo(width, height)
user32.InvalidateRect(self.win, 0, 0)
def OnCreate(self, hwnd):
self.win = hwnd
self.ed = Scintilla(self.face, hwnd, hinst)
self.ed.FocusOn()
def Invalidate(self):
user32.InvalidateRect(self.win, 0, 0)
def WndProc(self, h, m, w, l):
ms = sgsm.get(m, "XXX")
if trace:
print("%s %s %s %s" % (hex(h)[2:],ms,w,l))
if ms == "WM_CLOSE":
user32.PostQuitMessage(0)
elif ms == "WM_CREATE":
self.OnCreate(h)
return 0
elif ms == "WM_SIZE":
# Work out size
if w != 1:
self.OnSize()
return 0
elif ms == "WM_COMMAND":
cmdCode = w & 0xffff
if cmdCode in self.cmds:
self.Command(self.cmds[cmdCode])
return 0
elif ms == "WM_ACTIVATE":
if w != WA_INACTIVE:
self.ed.FocusOn()
return 0
else:
return user32.DefWindowProcW(h, m, w, l)
return 0
def Command(self, name):
name = name.replace(" ", "")
method = "Cmd" + name
cmd = None
try:
cmd = getattr(self, method)
except AttributeError:
return
if cmd:
cmd()
def KeyDown(self, w, prefix = ""):
keyName = prefix
if IsKeyDown(VK_CONTROL):
keyName += "<control>"
if IsKeyDown(VK_SHIFT):
keyName += "<shift>"
keyName += KeyTranslate(w)
if trace:
print("Key:", keyName)
if keyName in self.keys:
method = "Cmd" + self.keys[keyName]
getattr(self, method)()
return True
#~ print("UKey:", keyName)
return False
def Accelerator(self, msg):
ms = sgsm.get(msg.message, "XXX")
if ms == "WM_KEYDOWN":
return self.KeyDown(msg.wParam)
elif ms == "WM_SYSKEYDOWN":
return self.KeyDown(msg.wParam, "<alt>")
return False
def AppLoop(self):
msg = ctypes.wintypes.MSG()
lpmsg = ctypes.byref(msg)
while user32.GetMessageW(lpmsg, 0, 0, 0):
if trace and msg.message != msgs["WM_TIMER"]:
print('mm', hex(msg.hWnd)[2:],sgsm.get(msg.message, "XXX"))
if not self.Accelerator(msg):
user32.TranslateMessage(lpmsg)
user32.DispatchMessageW(lpmsg)
def DoEvents(self):
msg = ctypes.wintypes.MSG()
lpmsg = ctypes.byref(msg)
cont = True
while cont:
cont = user32.PeekMessageW(lpmsg, 0, 0, 0, PM_REMOVE)
if cont:
if not self.Accelerator(msg):
user32.TranslateMessage(lpmsg)
user32.DispatchMessageW(lpmsg)
def SetTitle(self, changePath):
if changePath or self.titleDirty != self.ed.Modify:
self.titleDirty = self.ed.Modify
self.title = self.fullPath
if self.titleDirty:
self.title += " * "
else:
self.title += " - "
self.title += self.appName
if self.win:
user32.SetWindowTextW(self.win, self.title)
def Open(self):
ofx = OPENFILENAME(self.win, "Open File")
opath = "\0" * 1024
ofx.lpstrFile = opath
filters = ["Python (.py;.pyw)|*.py;*.pyw|All|*.*"]
filterText = "\0".join([f.replace("|", "\0") for f in filters])+"\0\0"
ofx.lpstrFilter = filterText
if ctypes.windll.comdlg32.GetOpenFileNameW(ctypes.byref(ofx)):
absPath = opath.replace("\0", "")
self.GrabFile(absPath)
self.ed.FocusOn()
self.ed.LexerLanguage = "python"
self.ed.Lexer = self.ed.SCLEX_PYTHON
self.ed.SetKeyWords(0, b"class def else for from if import print return while")
for style in [k for k in self.ed.k if k.startswith("SCE_P_")]:
self.ed.StyleSetFont(self.ed.k[style], b"Verdana")
if "COMMENT" in style:
self.ed.StyleSetFore(self.ed.k[style], 127 * 256)
self.ed.StyleSetFont(self.ed.k[style], b"Comic Sans MS")
elif "OPERATOR" in style:
self.ed.StyleSetBold(self.ed.k[style], 1)
self.ed.StyleSetFore(self.ed.k[style], 127 * 256 * 256)
elif "WORD" in style:
self.ed.StyleSetItalic(self.ed.k[style], 255)
self.ed.StyleSetFore(self.ed.k[style], 255 * 256 * 256)
elif "TRIPLE" in style:
self.ed.StyleSetFore(self.ed.k[style], 0xA0A0)
elif "STRING" in style or "CHARACTER" in style:
self.ed.StyleSetFore(self.ed.k[style], 0xA000A0)
else:
self.ed.StyleSetFore(self.ed.k[style], 0)
def SaveAs(self):
ofx = OPENFILENAME(self.win, "Save File")
opath = "\0" * 1024
ofx.lpstrFile = opath
if ctypes.windll.comdlg32.GetSaveFileNameW(ctypes.byref(ofx)):
self.fullPath = opath.replace("\0", "")
self.Save()
self.SetTitle(1)
self.ed.FocusOn()
def SetMenus(self):
ui = XiteMenu.MenuStructure
self.cmds = {}
self.keys = {}
cmdId = 0
self.menuBar = user32.CreateMenu()
for name, contents in ui:
cmdId += 1
menu = user32.CreateMenu()
for item in contents:
text, key = item
cmdText = text.replace("&", "")
cmdText = cmdText.replace("...", "")
cmdText = cmdText.replace(" ", "")
cmdId += 1
if key:
keyText = key.replace("<control>", "Ctrl+")
keyText = keyText.replace("<shift>", "Shift+")
text += "\t" + keyText
if text == "-":
user32.AppendMenuW(menu, MF_SEPARATOR, cmdId, text)
else:
user32.AppendMenuW(menu, 0, cmdId, text)
self.cmds[cmdId] = cmdText
self.keys[key] = cmdText
#~ print(cmdId, item)
user32.AppendMenuW(self.menuBar, MF_POPUP, menu, name)
user32.SetMenu(self.win, self.menuBar)
self.CheckMenuItem("Wrap", True)
user32.ShowWindow(self.win, SW_SHOW)
def CheckMenuItem(self, name, val):
#~ print(name, val)
if self.cmds:
for k,v in self.cmds.items():
if v == name:
#~ print(name, k)
user32.CheckMenuItem(user32.GetMenu(self.win), \
k, [MF_UNCHECKED, MF_CHECKED][val])
def Exit(self):
sys.exit(0)
def DisplayMessage(self, msg, ask):
return IDYES == user32.MessageBoxW(self.win, \
msg, self.appName, [MB_OK, MB_YESNOCANCEL][ask])
def NewDocument(self):
self.ed.ClearAll()
self.ed.EmptyUndoBuffer()
self.ed.SetSavePoint()
def SaveIfUnsure(self):
if self.ed.Modify:
msg = "Save changes to \"" + self.fullPath + "\"?"
print(msg)
decision = self.DisplayMessage(msg, True)
if decision:
self.CmdSave()
return decision
return True
def New(self):
if self.SaveIfUnsure():
self.fullPath = ""
self.overrideMode = None
self.NewDocument()
self.SetTitle(1)
self.Invalidate()
def CheckMenus(self):
pass
def MoveSelection(self, caret, anchor=-1):
if anchor == -1:
anchor = caret
self.ed.SetSelectionStart(caret)
self.ed.SetSelectionEnd(anchor)
self.ed.ScrollCaret()
self.Invalidate()
def GrabFile(self, name):
self.fullPath = name
self.overrideMode = None
self.NewDocument()
fsr = open(name, "rb")
data = fsr.read()
fsr.close()
self.ed.AddText(len(data), data)
self.ed.EmptyUndoBuffer()
self.MoveSelection(0)
self.SetTitle(1)
def Save(self):
fos = open(self.fullPath, "wb")
blockSize = 1024
length = self.ed.Length
i = 0
while i < length:
grabSize = length - i
if grabSize > blockSize:
grabSize = blockSize
#~ print(i, grabSize, length)
data = self.ed.ByteRange(i, i + grabSize)
fos.write(data)
i += grabSize
fos.close()
self.ed.SetSavePoint()
self.SetTitle(0)
# Command handlers are called by menu actions
def CmdNew(self):
self.New()
def CmdOpen(self):
self.Open()
def CmdSave(self):
if (self.fullPath == None) or (len(self.fullPath) == 0):
self.SaveAs()
else:
self.Save()
def CmdSaveAs(self):
self.SaveAs()
def CmdTest(self):
runner = unittest.TextTestRunner()
if self.test:
tests = unittest.defaultTestLoader.loadTestsFromName(self.test)
else:
tests = unittest.defaultTestLoader.loadTestsFromName("simpleTests")
results = runner.run(tests)
#~ print(results)
if self.test:
user32.PostQuitMessage(0)
def CmdExercised(self):
print()
unused = sorted(self.ed.all.difference(self.ed.used))
print("Unused", len(unused))
print()
print("\n".join(unused))
print()
print("Used", len(self.ed.used))
print()
print("\n".join(sorted(self.ed.used)))
def Uncalled(self):
print("")
unused = sorted(self.ed.all.difference(self.ed.used))
uu = {}
for u in unused:
v = self.ed.getvalue(u)
if v > 2000:
uu[v] = u
#~ for x in sorted(uu.keys())[150:]:
return uu
def CmdExit(self):
self.Exit()
def CmdUndo(self):
self.ed.Undo()
def CmdRedo(self):
self.ed.Redo()
def CmdCut(self):
self.ed.Cut()
def CmdCopy(self):
self.ed.Copy()
def CmdPaste(self):
self.ed.Paste()
def CmdDelete(self):
self.ed.Clear()
xiteFrame = None
def main(test):
global xiteFrame
xiteFrame = XiteWin(test)
xiteFrame.AppLoop()
#~ xiteFrame.CmdExercised()
return xiteFrame.Uncalled()

View File

@ -0,0 +1,12 @@
<%@language=javas%>
<%
#include
function x() {
}
%>
<%@language=vbscript%>
<%
sub x 'comment
%>
<head>
<body></body>

View File

@ -0,0 +1,12 @@
{15}<%@{16}language=javas{15}%>{0}
{15}<%{56}
#{61}include{56}
{62}function{56} {61}x{65}(){56} {65}{{56}
{65}}{56}
{15}%>{0}
{15}<%@{16}language=vbscript{15}%>{0}
{15}<%{81}
{84}sub{81} {86}x{81} {82}'comment {81}
{15}%>{0}
{1}<head>{0}
{1}<body></body>{0}

View File

@ -0,0 +1,7 @@
// A demonstration program
#include <stdio.h>
int main() {
double x[] = {3.14159,6.02e23,1.6e-19,1.0+1};
int y[] = {75,0113,0x4b};
printf("hello world %d\n", 9);
}

View File

@ -0,0 +1,7 @@
{2}// A demonstration program
{9}#include <stdio.h>
{5}int{0} {11}main{10}(){0} {10}{{0}
{11}double{0} {11}x{10}[]{0} {10}={0} {10}{{4}3.14159{10},{4}6.02e23{10},{4}1.6e-19{10},{4}1.0{10}+{4}1{10}};{0}
{5}int{0} {11}y{10}[]{0} {10}={0} {10}{{4}75{10},{4}0113{10},{4}0x4b{10}};{0}
{11}printf{10}({6}"hello world %d\n"{10},{0} {4}9{10});{0}
{10}}{0}

View File

@ -0,0 +1,47 @@
$
// /++ +/ doccomments are not yet supported
/* */
/** */
/// drdr
/+ /+ +/ +/
//keyword test
keyword1
keyword2
keyword4
keyword5
keyword6
keyword7
//unicode identifier test
вапёasdÓΘΣαԷԸՑהכ拉麺とひシマイ단결을
//strings test
's
'
w's'w
"multiline
string"w
e"zz"e
r"asd\"e
r"multiline
string"c
r`asd\`e
`multiline
string`d
x"023 abc"e
x"023
abc"w
//numbers test
a[3..4]=3
2.stringof
2.0.stringof
2.
2.2e+2
2.2e-2
.2e+2
.2
2e+2
0x2e+2
0x2ep+10
,.2.stringof,
end

View File

@ -0,0 +1,47 @@
{14}${0}
{2}// /++ +/ doccomments are not yet supported
{1}/* */{0}
{3}/** */{0}
{15}/// drdr
{4}/+ /+ +/ +/{0}
{2}//keyword test
{6}keyword1{0}
{7}keyword2{0}
{9}keyword4{0}
{20}keyword5{0}
{21}keyword6{0}
{22}keyword7{0}
{2}//unicode identifier test
{14}вапёasdÓΘΣαԷԸՑהכ拉麺とひシマイ단결을{0}
{2}//strings test
{11}'s
'
{14}w{12}'s'{14}w{0}
{10}"multiline
string"w{0}
{14}e{10}"zz"{14}e{0}
{19}r"asd\"{14}e{0}
{19}r"multiline
string"c{0}
{14}r{18}`asd\`{14}e{0}
{18}`multiline
string`d{0}
{19}x"023 abc"{14}e{0}
{19}x"023
abc"w{0}
{2}//numbers test
{14}a{13}[{5}3{13}..{5}4{13}]={5}3{0}
{5}2.stringof{0}
{5}2.0{13}.{14}stringof{0}
{5}2.{0}
{5}2.2e+2{0}
{5}2.2e-2{0}
{5}.2e+2{0}
{5}.2{0}
{5}2e+2{0}
{5}0x2e{13}+{5}2{0}
{5}0x2ep+10{0}
{13},{5}.2{13}.{14}stringof{13},{0}
{14}end{0}

View File

@ -0,0 +1,12 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<script type="text/javascript">
var b = /abc/i.test('abc');
'x\
</t>'
</script>
<head>
<meta name="Date.Modified" content="20010515" />
<title>SinkWorld - Portability</title>
<unknown>SinkWorld - Portability</unknown>
<link rel="stylesheet" type="text/css" href="SW.css">
</head>

View File

@ -0,0 +1,12 @@
{1}<html{8} {3}xmlns{8}={6}"http://www.w3.org/1999/xhtml"{1}>{0}
{1}<script{8} {3}type{8}={6}"text/javascript"{1}>{40}
{46}var{41} {46}b{41} {50}={41} {52}/abc/i{46}.test{50}({49}'abc'{50});{41}
{49}'x\
</t>'{41}
{1}</script>{0}
{1}<head>{0}
{1}<meta{8} {3}name{8}={6}"Date.Modified"{8} {3}content{8}={6}"20010515"{8} {11}/>{0}
{1}<title>{0}SinkWorld - Portability{1}</title>{0}
{2}<unknown>{0}SinkWorld - Portability{2}</unknown>{0}
{1}<link{8} {3}rel{8}={6}"stylesheet"{8} {3}type{8}={6}"text/css"{8} {3}href{8}={6}"SW.css"{1}>{0}
{1}</head>{0}

View File

@ -0,0 +1,6 @@
<head> <!-- About to script -->
<?php
echo "<!-- -->\n";
/* ?> */
?>
<strong>for</strong><b>if</b>

View File

@ -0,0 +1,6 @@
{1}<head>{0} {9}<!-- About to script -->{0}
{18}<?php{118}
echo {119}"<!-- -->\n"{127};{118}
{124}/* ?> */{118}
{18}?>{0}
{1}<strong>{0}for{1}</strong><b>{0}if{1}</b>{0}

View File

@ -0,0 +1,11 @@
# Convert all punctuation characters except '_', '*', and '.' into spaces.
def depunctuate(s):
'''A docstring'''
"""Docstring 2"""
d = ""
for ch in s:
if ch in 'abcde':
d = d + ch
else:
d = d + " "
return d

View File

@ -0,0 +1,11 @@
{1}# Convert all punctuation characters except '_', '*', and '.' into spaces.{0}
{5}def{0} {9}depunctuate{10}({11}s{10}):{0}
{6}'''A docstring'''{0}
{7}"""Docstring 2"""{0}
{11}d{0} {10}={0} {3}""{0}
{5}for{0} {11}ch{0} {5}in{0} {11}s{10}:{0}
{5}if{0} {11}ch{0} {5}in{0} {4}'abcde'{10}:{0}
{11}d{0} {10}={0} {11}d{0} {10}+{0} {11}ch{0}
{5}else{10}:{0}
{11}d{0} {10}={0} {11}d{0} {10}+{0} {3}" "{0}
{5}return{0} {11}d{0}

View File

@ -0,0 +1,9 @@
' String"
Dim a As String = "hello, world"
Dim b As String = "hello world"
Dim c As String = "Joe said ""Hello"" to me"
Dim d As String = "\\\\server\\share\\file.txt"
' Character
""C "c"C "cc"C
' Date
d = #5/31/1993# or # 01/01/0001 12:00:00AM #

View File

@ -0,0 +1,9 @@
{1}' String"
{3}Dim{0} {7}a{0} {3}As{0} {3}String{0} {6}={0} {4}"hello, world"{0}
{3}Dim{0} {7}b{0} {3}As{0} {3}String{0} {6}={0} {4}"hello world"{0}
{3}Dim{0} {7}c{0} {3}As{0} {3}String{0} {6}={0} {4}"Joe said ""Hello"" to me"{0}
{3}Dim{0} {7}d{0} {3}As{0} {3}String{0} {6}={0} {4}"\\\\server\\share\\file.txt"{0}
{1}' Character
{4}""C{0} {4}"c"C{0} {4}"cc"C{0}
{1}' Date
{7}d{0} {6}={0} {8}#5/31/1993#{0} {3}or{0} {8}# 01/01/0001 12:00:00AM #{0}

113
scintilla/test/lexTests.py Normal file
View File

@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
import io
import os
import unittest
import XiteWin
keywordsHTML = [
b"b body content head href html link meta "
b"name rel script strong title type xmlns",
b"function",
b"sub"
]
class TestLexers(unittest.TestCase):
def setUp(self):
self.xite = XiteWin.xiteFrame
self.ed = self.xite.ed
self.ed.ClearAll()
self.ed.EmptyUndoBuffer()
def AsStyled(self):
text = self.ed.Contents()
data = io.BytesIO()
prevStyle = -1
for o in range(self.ed.Length):
styleNow = self.ed.GetStyleAt(o)
if styleNow != prevStyle:
styleBuf = "{%0d}" % styleNow
data.write(styleBuf.encode('utf-8'))
prevStyle = styleNow
data.write(text[o:o+1])
return data.getvalue()
def LexExample(self, name, lexerName, keywords=None):
if keywords is None:
keywords = []
self.ed.LexerLanguage = lexerName
bits = self.ed.StyleBitsNeeded
mask = 2 << bits - 1
self.ed.StyleBits = bits
for i in range(len(keywords)):
self.ed.SetKeyWords(i, keywords[i])
nameExample = os.path.join("examples", name)
namePrevious = nameExample +".styled"
nameNew = nameExample +".new"
with open(nameExample, "rb") as f:
prog = f.read()
BOM = b"\xEF\xBB\xBF"
if prog.startswith(BOM):
prog = prog[len(BOM):]
lenDocument = len(prog)
self.ed.AddText(lenDocument, prog)
self.ed.Colourise(0, lenDocument)
self.assertEquals(self.ed.EndStyled, lenDocument)
with open(namePrevious, "rb") as f:
prevStyled = f.read()
progStyled = self.AsStyled()
if progStyled != prevStyled:
with open(nameNew, "wb") as f:
f.write(progStyled)
print(progStyled)
print(prevStyled)
self.assertEquals(progStyled, prevStyled)
# The whole file doesn't parse like it did before so don't try line by line
# as that is likely to fail many times.
return
# Try partial lexes from the start of every line which should all be identical.
for line in range(self.ed.LineCount):
lineStart = self.ed.PositionFromLine(line)
self.ed.StartStyling(lineStart, mask)
self.assertEquals(self.ed.EndStyled, lineStart)
self.ed.Colourise(0, lenDocument)
progStyled = self.AsStyled()
if progStyled != prevStyled:
with open(nameNew, "wb") as f:
f.write(progStyled)
assertEquals(progStyled, prevStyled)
# Give up after one failure
return
def testCXX(self):
self.LexExample("x.cxx", b"cpp", [b"int"])
def testPython(self):
self.LexExample("x.py", b"python",
[b"class def else for if import in print return while"])
def testHTML(self):
self.LexExample("x.html", b"hypertext", keywordsHTML)
def testASP(self):
self.LexExample("x.asp", b"hypertext", keywordsHTML)
def testPHP(self):
self.LexExample("x.php", b"hypertext", keywordsHTML)
def testVB(self):
self.LexExample("x.vb", b"vb", [b"as dim or string"])
def testD(self):
self.LexExample("x.d", b"d",
[b"keyword1", b"keyword2", b"", b"keyword4", b"keyword5",
b"keyword6", b"keyword7"])
if __name__ == '__main__':
XiteWin.main("lexTests")

View File

@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
import os, string, time, unittest
import XiteWin
class TestPerformance(unittest.TestCase):
def setUp(self):
self.xite = XiteWin.xiteFrame
self.ed = self.xite.ed
self.ed.ClearAll()
self.ed.EmptyUndoBuffer()
def testAddLine(self):
data = (string.ascii_letters + string.digits + "\n").encode('utf-8')
start = time.time()
for i in range(1000):
self.ed.AddText(len(data), data)
self.assertEquals(self.ed.LineCount, i + 2)
end = time.time()
duration = end - start
print("%6.3f testAddLine" % duration)
self.xite.DoEvents()
self.assert_(self.ed.Length > 0)
def testAddLineMiddle(self):
data = (string.ascii_letters + string.digits + "\n").encode('utf-8')
start = time.time()
for i in range(1000):
self.ed.AddText(len(data), data)
self.assertEquals(self.ed.LineCount, i + 2)
end = time.time()
duration = end - start
print("%6.3f testAddLineMiddle" % duration)
self.xite.DoEvents()
self.assert_(self.ed.Length > 0)
def testHuge(self):
data = (string.ascii_letters + string.digits + "\n").encode('utf-8')
data = data * 100000
start = time.time()
self.ed.AddText(len(data), data)
end = time.time()
duration = end - start
print("%6.3f testHuge" % duration)
self.xite.DoEvents()
self.assert_(self.ed.Length > 0)
def testHugeInserts(self):
data = (string.ascii_letters + string.digits + "\n").encode('utf-8')
data = data * 100000
insert = (string.digits + "\n").encode('utf-8')
self.ed.AddText(len(data), data)
start = time.time()
for i in range(1000):
self.ed.InsertText(0, insert)
end = time.time()
duration = end - start
print("%6.3f testHugeInserts" % duration)
self.xite.DoEvents()
self.assert_(self.ed.Length > 0)
def testHugeReplace(self):
oneLine = (string.ascii_letters + string.digits + "\n").encode('utf-8')
data = oneLine * 100000
insert = (string.digits + "\n").encode('utf-8')
self.ed.AddText(len(data), data)
start = time.time()
for i in range(1000):
self.ed.TargetStart = i * len(insert)
self.ed.TargetEnd = self.ed.TargetStart + len(oneLine)
self.ed.ReplaceTarget(len(insert), insert)
end = time.time()
duration = end - start
print("%6.3f testHugeReplace" % duration)
self.xite.DoEvents()
self.assert_(self.ed.Length > 0)
if __name__ == '__main__':
XiteWin.main("performanceTests")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
The test/unit directory contains unit tests for Scintilla data structures.
The tests can be run on Windows or Linux using g++ and GNU make.
The Google Test framework is used.
http://code.google.com/p/googletest/
Google test must be installed first.
On Linux, install the google test packages libgtest-dev and libgtest0.
On Windows download Google test and install it as a peer to the directory
containing scintilla. The makefile assumes it is in ../../../../gtest-1.5.0.
To run the tests:
make
./unitTest

View File

@ -0,0 +1,4 @@
if PLAT_WIN
make.command=mingw32-make
command.go.*.cxx=./unitTest
command.go.needs.$(file.patterns.cplusplus)=$(make.command)

View File

@ -0,0 +1,68 @@
# Build all the unit tests
# Should be run using mingw32-make on Windows
.SUFFIXES: .cxx
GTEST_DIR = ../../../../gtest-1.5.0
ifdef windir
DEL = del /q
# Find Google Test headers.
CPPFLAGS += -I$(GTEST_DIR)/include
GTEST_ALL = gtest-all.o
LINKFLAGS = $(CPPFLAGS) $(CXXFLAGS)
EXE = unitTest.exe
else
DEL = rm -f
CPPFLAGS = $(shell gtest-config --cppflags)
CXXFLAGS = $(shell gtest-config --cxxflags)
LINKFLAGS = $(shell gtest-config --ldflags --libs)
EXE = unitTest
# For coverage testing with gcov
#CPPFLAGS += -fprofile-arcs -ftest-coverage
#LINKFLAGS += -fprofile-arcs -ftest-coverage
endif
#vpath %.cxx ../src ../lexlib ../lexers
vpath %.cxx ../../src
INCLUDEDIRS = -I ../../include -I ../../src -I../../lexlib
# Find headers of test code.
CPPFLAGS += $(INCLUDEDIRS)
CXXFLAGS += -g -Wall -Wextra -Wno-unused-function
#~ CXXFLAGS += -g -Wall
CASES:=$(addsuffix .o,$(basename $(notdir $(wildcard test*.cxx))))
TESTEDOBJS=ContractionState.o RunStyles.o
TESTS=$(EXE)
GTEST_HEADERS=$(GTEST_DIR)/include/gtest/*.h $(GTEST_DIR)/include/gtest/internal/*.h
all: $(TESTS)
clean:
$(DEL) $(TESTS) *.a *.o *.exe *.gcov *.gcda *.gcno
# Usually you shouldn't tweak such internal variables, indicated by a
# trailing _.
GTEST_SRCS_ = $(GTEST_DIR)/src/*.cc $(GTEST_DIR)/src/*.h $(GTEST_HEADERS)
gtest-all.o: $(GTEST_SRCS_)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -I$(GTEST_DIR) -c \
$(GTEST_DIR)/src/gtest-all.cc
.cxx.o:
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $<
$(EXE): $(CASES) $(TESTEDOBJS) unitTest.o $(GTEST_ALL)
$(CXX) $(LINKFLAGS) $^ -o $@

View File

@ -0,0 +1,146 @@
// Unit Tests for Scintilla internal data structures
#include <string.h>
#include "Platform.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include "ContractionState.h"
#include <gtest/gtest.h>
// Test ContractionState.
class ContractionStateTest : public ::testing::Test {
protected:
virtual void SetUp() {
pcs = new ContractionState();
}
virtual void TearDown() {
delete pcs;
pcs = 0;
}
ContractionState *pcs;
};
TEST_F(ContractionStateTest, IsEmptyInitially) {
EXPECT_EQ(1, pcs->LinesInDoc());
EXPECT_EQ(1, pcs->LinesDisplayed());
EXPECT_EQ(0, pcs->DisplayFromDoc(0));
EXPECT_EQ(0, pcs->DocFromDisplay(0));
}
TEST_F(ContractionStateTest, OneLine) {
pcs->InsertLine(0);
EXPECT_EQ(2, pcs->LinesInDoc());
EXPECT_EQ(2, pcs->LinesDisplayed());
EXPECT_EQ(0, pcs->DisplayFromDoc(0));
EXPECT_EQ(0, pcs->DocFromDisplay(0));
EXPECT_EQ(1, pcs->DisplayFromDoc(1));
EXPECT_EQ(1, pcs->DocFromDisplay(1));
}
TEST_F(ContractionStateTest, InsertionThenDeletions) {
pcs->InsertLines(0,4);
pcs->DeleteLine(1);
EXPECT_EQ(4, pcs->LinesInDoc());
EXPECT_EQ(4, pcs->LinesDisplayed());
for (int l=0;l<4;l++) {
EXPECT_EQ(l, pcs->DisplayFromDoc(l));
EXPECT_EQ(l, pcs->DocFromDisplay(l));
}
pcs->DeleteLines(0,2);
EXPECT_EQ(2, pcs->LinesInDoc());
EXPECT_EQ(2, pcs->LinesDisplayed());
for (int l=0;l<2;l++) {
EXPECT_EQ(l, pcs->DisplayFromDoc(l));
EXPECT_EQ(l, pcs->DocFromDisplay(l));
}
}
TEST_F(ContractionStateTest, ShowHide) {
pcs->InsertLines(0,4);
EXPECT_EQ(true, pcs->GetVisible(0));
EXPECT_EQ(true, pcs->GetVisible(1));
EXPECT_EQ(true, pcs->GetVisible(2));
EXPECT_EQ(5, pcs->LinesDisplayed());
pcs->SetVisible(1, 1, false);
EXPECT_EQ(true, pcs->GetVisible(0));
EXPECT_EQ(0, pcs->GetVisible(1));
EXPECT_EQ(true, pcs->GetVisible(2));
EXPECT_EQ(4, pcs->LinesDisplayed());
EXPECT_EQ(1, pcs->HiddenLines());
pcs->SetVisible(1, 2, true);
for (int l=0;l<4;l++) {
EXPECT_EQ(true, pcs->GetVisible(0));
}
pcs->SetVisible(1, 1, false);
EXPECT_EQ(0, pcs->GetVisible(1));
pcs->ShowAll();
for (int l=0;l<4;l++) {
EXPECT_EQ(true, pcs->GetVisible(0));
}
EXPECT_EQ(0, pcs->HiddenLines());
}
TEST_F(ContractionStateTest, Hidden) {
pcs->InsertLines(0,1);
for (int l=0;l<2;l++) {
EXPECT_EQ(true, pcs->GetVisible(0));
}
EXPECT_EQ(0, pcs->HiddenLines());
pcs->SetVisible(1, 1, false);
EXPECT_EQ(true, pcs->GetVisible(0));
EXPECT_EQ(0, pcs->GetVisible(1));
EXPECT_EQ(1, pcs->HiddenLines());
pcs->SetVisible(1, 1, true);
for (int l=0;l<2;l++) {
EXPECT_EQ(true, pcs->GetVisible(0));
}
EXPECT_EQ(0, pcs->HiddenLines());
}
TEST_F(ContractionStateTest, Contracting) {
pcs->InsertLines(0,4);
for (int l=0;l<4;l++) {
EXPECT_EQ(true, pcs->GetExpanded(l));
}
pcs->SetExpanded(2, false);
EXPECT_EQ(true, pcs->GetExpanded(1));
EXPECT_EQ(0, pcs->GetExpanded(2));
EXPECT_EQ(true, pcs->GetExpanded(3));
EXPECT_EQ(2, pcs->ContractedNext(0));
EXPECT_EQ(2, pcs->ContractedNext(1));
EXPECT_EQ(2, pcs->ContractedNext(2));
EXPECT_EQ(-1, pcs->ContractedNext(3));
pcs->SetExpanded(2, true);
EXPECT_EQ(true, pcs->GetExpanded(1));
EXPECT_EQ(true, pcs->GetExpanded(2));
EXPECT_EQ(true, pcs->GetExpanded(3));
}
TEST_F(ContractionStateTest, ChangeHeight) {
pcs->InsertLines(0,4);
for (int l=0;l<4;l++) {
EXPECT_EQ(1, pcs->GetHeight(l));
}
pcs->SetHeight(1, 2);
EXPECT_EQ(1, pcs->GetHeight(0));
EXPECT_EQ(2, pcs->GetHeight(1));
EXPECT_EQ(1, pcs->GetHeight(2));
}

View File

@ -0,0 +1,208 @@
// Unit Tests for Scintilla internal data structures
#include <string.h>
#include "Platform.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include <gtest/gtest.h>
const int growSize = 4;
const int lengthTestArray = 8;
static const int testArray[lengthTestArray] = {3, 4, 5, 6, 7, 8, 9, 10};
// Test SplitVectorWithRangeAdd.
class SplitVectorWithRangeAddTest : public ::testing::Test {
protected:
virtual void SetUp() {
psvwra = new SplitVectorWithRangeAdd(growSize);
}
virtual void TearDown() {
delete psvwra;
psvwra = 0;
}
SplitVectorWithRangeAdd *psvwra;
};
TEST_F(SplitVectorWithRangeAddTest, IsEmptyInitially) {
EXPECT_EQ(0, psvwra->Length());
}
TEST_F(SplitVectorWithRangeAddTest, IncrementExceptEnds) {
psvwra->InsertFromArray(0, testArray, 0, lengthTestArray);
psvwra->RangeAddDelta(1, lengthTestArray-1, 1);
for (int i=0; i<psvwra->Length(); i++) {
if ((i == 0) || (i == lengthTestArray-1))
EXPECT_EQ(i+3, psvwra->ValueAt(i));
else
EXPECT_EQ(i+4, psvwra->ValueAt(i));
}
}
// Test Partitioning.
class PartitioningTest : public ::testing::Test {
protected:
virtual void SetUp() {
pp = new Partitioning(growSize);
}
virtual void TearDown() {
delete pp;
pp = 0;
}
Partitioning *pp;
};
TEST_F(PartitioningTest, IsEmptyInitially) {
EXPECT_EQ(1, pp->Partitions());
EXPECT_EQ(0, pp->PositionFromPartition(pp->Partitions()));
EXPECT_EQ(0, pp->PartitionFromPosition(0));
}
TEST_F(PartitioningTest, SimpleInsert) {
pp->InsertText(0, 1);
EXPECT_EQ(1, pp->Partitions());
EXPECT_EQ(1, pp->PositionFromPartition(pp->Partitions()));
}
TEST_F(PartitioningTest, TwoPartitions) {
pp->InsertText(0, 2);
pp->InsertPartition(1, 1);
EXPECT_EQ(2, pp->Partitions());
EXPECT_EQ(0, pp->PositionFromPartition(0));
EXPECT_EQ(1, pp->PositionFromPartition(1));
EXPECT_EQ(2, pp->PositionFromPartition(2));
}
TEST_F(PartitioningTest, MoveStart) {
pp->InsertText(0, 3);
pp->InsertPartition(1, 2);
pp->SetPartitionStartPosition(1,1);
EXPECT_EQ(2, pp->Partitions());
EXPECT_EQ(0, pp->PositionFromPartition(0));
EXPECT_EQ(1, pp->PositionFromPartition(1));
EXPECT_EQ(3, pp->PositionFromPartition(2));
}
TEST_F(PartitioningTest, InsertAgain) {
pp->InsertText(0, 3);
pp->InsertPartition(1, 2);
pp->InsertText(0,3);
pp->InsertText(1,2);
EXPECT_EQ(2, pp->Partitions());
EXPECT_EQ(0, pp->PositionFromPartition(0));
EXPECT_EQ(5, pp->PositionFromPartition(1));
EXPECT_EQ(8, pp->PositionFromPartition(2));
}
TEST_F(PartitioningTest, InsertReversed) {
pp->InsertText(0, 3);
pp->InsertPartition(1, 2);
pp->InsertText(1,2);
pp->InsertText(0,3);
EXPECT_EQ(2, pp->Partitions());
EXPECT_EQ(0, pp->PositionFromPartition(0));
EXPECT_EQ(5, pp->PositionFromPartition(1));
EXPECT_EQ(8, pp->PositionFromPartition(2));
}
TEST_F(PartitioningTest, InverseSearch) {
pp->InsertText(0, 3);
pp->InsertPartition(1, 2);
pp->SetPartitionStartPosition(1,1);
EXPECT_EQ(2, pp->Partitions());
EXPECT_EQ(0, pp->PositionFromPartition(0));
EXPECT_EQ(1, pp->PositionFromPartition(1));
EXPECT_EQ(3, pp->PositionFromPartition(2));
EXPECT_EQ(0, pp->PartitionFromPosition(0));
EXPECT_EQ(1, pp->PartitionFromPosition(1));
EXPECT_EQ(1, pp->PartitionFromPosition(2));
EXPECT_EQ(1, pp->PartitionFromPosition(3));
}
TEST_F(PartitioningTest, DeletePartition) {
pp->InsertText(0, 2);
pp->InsertPartition(1, 1);
pp->RemovePartition(1);
EXPECT_EQ(1, pp->Partitions());
EXPECT_EQ(0, pp->PositionFromPartition(0));
EXPECT_EQ(2, pp->PositionFromPartition(1));
}
TEST_F(PartitioningTest, DeleteAll) {
pp->InsertText(0, 3);
pp->InsertPartition(1, 2);
pp->SetPartitionStartPosition(1,1);
pp->DeleteAll();
// Back to initial state
EXPECT_EQ(1, pp->Partitions());
EXPECT_EQ(0, pp->PositionFromPartition(pp->Partitions()));
}
TEST_F(PartitioningTest, TestBackwards) {
pp->InsertText(0, 10);
pp->InsertPartition(1, 3);
pp->InsertPartition(2, 6);
pp->InsertPartition(3, 9);
pp->InsertText(2,4);
pp->InsertText(1,2);
pp->InsertText(0,3);
EXPECT_EQ(4, pp->Partitions());
EXPECT_EQ(0, pp->PositionFromPartition(0));
EXPECT_EQ(6, pp->PositionFromPartition(1));
EXPECT_EQ(11, pp->PositionFromPartition(2));
EXPECT_EQ(18, pp->PositionFromPartition(3));
EXPECT_EQ(19, pp->PositionFromPartition(4));
}
TEST_F(PartitioningTest, TestMany) {
// Provoke backstep call
pp->InsertText(0, 42);
for (int i=0; i<20; i++) {
pp->InsertPartition(i+1, (i+1) * 2);
}
for (int i=20; i>0; i--) {
pp->InsertText(i,2);
}
EXPECT_EQ(21, pp->Partitions());
for (int i=1; i<20; i++) {
EXPECT_EQ(i*4 - 2, pp->PositionFromPartition(i));
EXPECT_EQ(i, pp->PartitionFromPosition(i*4 - 2));
}
pp->InsertText(19,2);
EXPECT_EQ(3, pp->PartitionFromPosition(10));
pp->InsertText(0,2);
pp->InsertText(0,-2);
pp->RemovePartition(1);
EXPECT_EQ(0, pp->PositionFromPartition(0));
EXPECT_EQ(6, pp->PositionFromPartition(1));
EXPECT_EQ(10, pp->PositionFromPartition(2));
pp->RemovePartition(10);
EXPECT_EQ(46, pp->PositionFromPartition(10));
EXPECT_EQ(10, pp->PartitionFromPosition(46));
EXPECT_EQ(50, pp->PositionFromPartition(11));
EXPECT_EQ(11, pp->PartitionFromPosition(50));
}
#if !PLAT_WIN
// Omit death tests on Windows where they trigger a system "unitTest.exe has stopped working" popup.
TEST_F(PartitioningTest, OutOfRangeDeathTest) {
::testing::FLAGS_gtest_death_test_style = "threadsafe";
pp->InsertText(0, 2);
pp->InsertPartition(1, 1);
EXPECT_EQ(2, pp->Partitions());
ASSERT_DEATH(pp->PositionFromPartition(-1), "Assertion");
ASSERT_DEATH(pp->PositionFromPartition(3), "Assertion");
}
#endif

View File

@ -0,0 +1,283 @@
// Unit Tests for Scintilla internal data structures
#include <string.h>
#include "Platform.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include <gtest/gtest.h>
// Test RunStyles.
class RunStylesTest : public ::testing::Test {
protected:
virtual void SetUp() {
prs = new RunStyles();
}
virtual void TearDown() {
delete prs;
prs = 0;
}
RunStyles *prs;
};
TEST_F(RunStylesTest, IsEmptyInitially) {
EXPECT_EQ(0, prs->Length());
EXPECT_EQ(1, prs->Runs());
}
TEST_F(RunStylesTest, SimpleInsert) {
prs->InsertSpace(0, 1);
EXPECT_EQ(1, prs->Length());
EXPECT_EQ(1, prs->Runs());
EXPECT_EQ(0, prs->ValueAt(0));
EXPECT_EQ(1, prs->FindNextChange(0, prs->Length()));
EXPECT_EQ(2, prs->FindNextChange(1, prs->Length()));
}
TEST_F(RunStylesTest, TwoRuns) {
prs->InsertSpace(0, 2);
EXPECT_EQ(2, prs->Length());
EXPECT_EQ(1, prs->Runs());
prs->SetValueAt(0, 2);
EXPECT_EQ(2, prs->Runs());
EXPECT_EQ(2, prs->ValueAt(0));
EXPECT_EQ(0, prs->ValueAt(1));
EXPECT_EQ(1, prs->FindNextChange(0, prs->Length()));
EXPECT_EQ(2, prs->FindNextChange(1, prs->Length()));
EXPECT_EQ(3, prs->FindNextChange(2, prs->Length()));
}
TEST_F(RunStylesTest, LongerRuns) {
prs->InsertSpace(0, 5);
prs->SetValueAt(0, 3);
prs->SetValueAt(1, 3);
EXPECT_EQ(3, prs->ValueAt(0));
EXPECT_EQ(3, prs->ValueAt(1));
EXPECT_EQ(0, prs->ValueAt(2));
EXPECT_EQ(2, prs->Runs());
EXPECT_EQ(0, prs->StartRun(0));
EXPECT_EQ(2, prs->EndRun(0));
EXPECT_EQ(0, prs->StartRun(1));
EXPECT_EQ(2, prs->EndRun(1));
EXPECT_EQ(2, prs->StartRun(2));
EXPECT_EQ(5, prs->EndRun(2));
EXPECT_EQ(2, prs->StartRun(3));
EXPECT_EQ(5, prs->EndRun(3));
EXPECT_EQ(2, prs->StartRun(4));
EXPECT_EQ(5, prs->EndRun(4));
// At end
EXPECT_EQ(2, prs->StartRun(5));
EXPECT_EQ(5, prs->EndRun(5));
// After end is same as end
EXPECT_EQ(2, prs->StartRun(6));
EXPECT_EQ(5, prs->EndRun(6));
EXPECT_EQ(2, prs->FindNextChange(0, prs->Length()));
EXPECT_EQ(5, prs->FindNextChange(2, prs->Length()));
EXPECT_EQ(6, prs->FindNextChange(5, prs->Length()));
}
TEST_F(RunStylesTest, FillRange) {
prs->InsertSpace(0, 5);
int startFill = 1;
int lengthFill = 3;
EXPECT_EQ(true, prs->FillRange(startFill, 99, lengthFill));
EXPECT_EQ(1, startFill);
EXPECT_EQ(3, lengthFill);
EXPECT_EQ(0, prs->ValueAt(0));
EXPECT_EQ(99, prs->ValueAt(1));
EXPECT_EQ(99, prs->ValueAt(2));
EXPECT_EQ(99, prs->ValueAt(3));
EXPECT_EQ(0, prs->ValueAt(4));
EXPECT_EQ(0, prs->StartRun(0));
EXPECT_EQ(1, prs->EndRun(0));
EXPECT_EQ(1, prs->StartRun(1));
EXPECT_EQ(4, prs->EndRun(1));
}
TEST_F(RunStylesTest, FillRangeAlreadyFilled) {
prs->InsertSpace(0, 5);
int startFill = 1;
int lengthFill = 3;
EXPECT_EQ(true, prs->FillRange(startFill, 99, lengthFill));
EXPECT_EQ(1, startFill);
EXPECT_EQ(3, lengthFill);
int startFill2 = 2;
int lengthFill2 = 1;
// Compiler warnings if 'false' used instead of '0' as expected value:
EXPECT_EQ(0, prs->FillRange(startFill2, 99, lengthFill2));
EXPECT_EQ(2, startFill2);
EXPECT_EQ(1, lengthFill2);
EXPECT_EQ(0, prs->ValueAt(0));
EXPECT_EQ(99, prs->ValueAt(1));
EXPECT_EQ(99, prs->ValueAt(2));
EXPECT_EQ(99, prs->ValueAt(3));
EXPECT_EQ(0, prs->ValueAt(4));
EXPECT_EQ(3, prs->Runs());
}
TEST_F(RunStylesTest, FillRangeAlreadyPartFilled) {
prs->InsertSpace(0, 5);
int startFill = 1;
int lengthFill = 2;
EXPECT_EQ(true, prs->FillRange(startFill, 99, lengthFill));
EXPECT_EQ(1, startFill);
EXPECT_EQ(2, lengthFill);
int startFill2 = 2;
int lengthFill2 = 2;
EXPECT_EQ(true, prs->FillRange(startFill2, 99, lengthFill2));
EXPECT_EQ(3, startFill2);
EXPECT_EQ(1, lengthFill2);
EXPECT_EQ(3, prs->Runs());
}
TEST_F(RunStylesTest, DeleteRange) {
prs->InsertSpace(0, 5);
prs->SetValueAt(0, 3);
EXPECT_EQ(2, prs->Runs());
prs->SetValueAt(1, 3);
EXPECT_EQ(2, prs->Runs());
prs->DeleteRange(1, 1);
EXPECT_EQ(4, prs->Length());
EXPECT_EQ(2, prs->Runs());
EXPECT_EQ(3, prs->ValueAt(0));
EXPECT_EQ(0, prs->ValueAt(1));
EXPECT_EQ(0, prs->StartRun(0));
EXPECT_EQ(1, prs->EndRun(0));
EXPECT_EQ(1, prs->StartRun(1));
EXPECT_EQ(4, prs->EndRun(1));
EXPECT_EQ(1, prs->StartRun(2));
EXPECT_EQ(4, prs->EndRun(2));
}
TEST_F(RunStylesTest, Find) {
prs->InsertSpace(0, 5);
int startFill = 1;
int lengthFill = 3;
EXPECT_EQ(true, prs->FillRange(startFill, 99, lengthFill));
EXPECT_EQ(1, startFill);
EXPECT_EQ(3, lengthFill);
EXPECT_EQ(0, prs->Find(0,0));
EXPECT_EQ(1, prs->Find(99,0));
EXPECT_EQ(-1, prs->Find(3,0));
EXPECT_EQ(4, prs->Find(0,1));
EXPECT_EQ(1, prs->Find(99,1));
EXPECT_EQ(-1, prs->Find(3,1));
EXPECT_EQ(4, prs->Find(0,2));
EXPECT_EQ(2, prs->Find(99,2));
EXPECT_EQ(-1, prs->Find(3,2));
EXPECT_EQ(4, prs->Find(0,4));
EXPECT_EQ(-1, prs->Find(99,4));
EXPECT_EQ(-1, prs->Find(3,4));
EXPECT_EQ(-1, prs->Find(0,5));
EXPECT_EQ(-1, prs->Find(99,5));
EXPECT_EQ(-1, prs->Find(3,5));
EXPECT_EQ(-1, prs->Find(0,6));
EXPECT_EQ(-1, prs->Find(99,6));
EXPECT_EQ(-1, prs->Find(3,6));
}
TEST_F(RunStylesTest, AllSame) {
EXPECT_EQ(true, prs->AllSame());
prs->InsertSpace(0, 5);
EXPECT_EQ(true, prs->AllSame());
EXPECT_EQ(0, prs->AllSameAs(88));
EXPECT_EQ(true, prs->AllSameAs(0));
int startFill = 1;
int lengthFill = 3;
EXPECT_EQ(true, prs->FillRange(startFill, 99, lengthFill));
EXPECT_EQ(0, prs->AllSame());
EXPECT_EQ(0, prs->AllSameAs(88));
EXPECT_EQ(0, prs->AllSameAs(0));
EXPECT_EQ(true, prs->FillRange(startFill, 0, lengthFill));
EXPECT_EQ(true, prs->AllSame());
EXPECT_EQ(0, prs->AllSameAs(88));
EXPECT_EQ(true, prs->AllSameAs(0));
}
TEST_F(RunStylesTest, FindWithReversion) {
prs->InsertSpace(0, 5);
EXPECT_EQ(1, prs->Runs());
int startFill = 1;
int lengthFill = 1;
EXPECT_EQ(true, prs->FillRange(startFill, 99, lengthFill));
EXPECT_EQ(1, startFill);
EXPECT_EQ(1, lengthFill);
EXPECT_EQ(3, prs->Runs());
startFill = 2;
lengthFill = 1;
EXPECT_EQ(true, prs->FillRange(startFill, 99, lengthFill));
EXPECT_EQ(2, startFill);
EXPECT_EQ(1, lengthFill);
EXPECT_EQ(3, prs->Runs());
startFill = 1;
lengthFill = 1;
EXPECT_EQ(true, prs->FillRange(startFill, 0, lengthFill));
EXPECT_EQ(3, prs->Runs());
EXPECT_EQ(1, lengthFill);
startFill = 2;
lengthFill = 1;
EXPECT_EQ(true, prs->FillRange(startFill, 0, lengthFill));
EXPECT_EQ(1, prs->Runs());
EXPECT_EQ(1, lengthFill);
EXPECT_EQ(-1, prs->Find(0,6));
}
TEST_F(RunStylesTest, FinalRunInversion) {
EXPECT_EQ(1, prs->Runs());
prs->InsertSpace(0, 1);
EXPECT_EQ(1, prs->Runs());
prs->SetValueAt(0, 1);
EXPECT_EQ(1, prs->Runs());
prs->InsertSpace(1, 1);
EXPECT_EQ(1, prs->Runs());
prs->SetValueAt(1, 1);
EXPECT_EQ(1, prs->Runs());
prs->SetValueAt(1, 0);
EXPECT_EQ(2, prs->Runs());
prs->SetValueAt(1, 1);
EXPECT_EQ(1, prs->Runs());
}
TEST_F(RunStylesTest, DeleteAll) {
prs->InsertSpace(0, 5);
prs->SetValueAt(0, 3);
prs->SetValueAt(1, 3);
prs->DeleteAll();
EXPECT_EQ(0, prs->Length());
EXPECT_EQ(0, prs->ValueAt(0));
EXPECT_EQ(1, prs->Runs());
}

View File

@ -0,0 +1,244 @@
// Unit Tests for Scintilla internal data structures
#include <string>
#include <vector>
#include <algorithm>
#include "Platform.h"
#include "SparseState.h"
#include <gtest/gtest.h>
// Test SparseState.
class SparseStateTest : public ::testing::Test {
protected:
virtual void SetUp() {
pss = new SparseState<int>();
}
virtual void TearDown() {
delete pss;
pss = 0;
}
SparseState<int> *pss;
};
TEST_F(SparseStateTest, IsEmptyInitially) {
EXPECT_EQ(0u, pss->size());
int val = pss->ValueAt(0);
EXPECT_EQ(0, val);
}
TEST_F(SparseStateTest, SimpleSetAndGet) {
pss->Set(0, 22);
pss->Set(1, 23);
EXPECT_EQ(2u, pss->size());
EXPECT_EQ(0, pss->ValueAt(-1));
EXPECT_EQ(22, pss->ValueAt(0));
EXPECT_EQ(23, pss->ValueAt(1));
EXPECT_EQ(23, pss->ValueAt(2));
}
TEST_F(SparseStateTest, RetrieveBetween) {
pss->Set(0, 10);
pss->Set(2, 12);
EXPECT_EQ(2u, pss->size());
EXPECT_EQ(0, pss->ValueAt(-1));
EXPECT_EQ(10, pss->ValueAt(0));
EXPECT_EQ(10, pss->ValueAt(1));
EXPECT_EQ(12, pss->ValueAt(2));
}
TEST_F(SparseStateTest, RetrieveBefore) {
pss->Set(2, 12);
EXPECT_EQ(1u, pss->size());
EXPECT_EQ(0, pss->ValueAt(-1));
EXPECT_EQ(0, pss->ValueAt(0));
EXPECT_EQ(0, pss->ValueAt(1));
EXPECT_EQ(12, pss->ValueAt(2));
}
TEST_F(SparseStateTest, Delete) {
pss->Set(0, 30);
pss->Set(2, 32);
pss->Delete(2);
EXPECT_EQ(1u, pss->size());
EXPECT_EQ(0, pss->ValueAt(-1));
EXPECT_EQ(30, pss->ValueAt(0));
EXPECT_EQ(30, pss->ValueAt(1));
EXPECT_EQ(30, pss->ValueAt(2));
}
TEST_F(SparseStateTest, DeleteBetweeen) {
pss->Set(0, 30);
pss->Set(2, 32);
pss->Delete(1);
EXPECT_EQ(1u, pss->size());
EXPECT_EQ(0, pss->ValueAt(-1));
EXPECT_EQ(30, pss->ValueAt(0));
EXPECT_EQ(30, pss->ValueAt(1));
EXPECT_EQ(30, pss->ValueAt(2));
}
TEST_F(SparseStateTest, ReplaceLast) {
pss->Set(0, 30);
pss->Set(2, 31);
pss->Set(2, 32);
EXPECT_EQ(2u, pss->size());
EXPECT_EQ(0, pss->ValueAt(-1));
EXPECT_EQ(30, pss->ValueAt(0));
EXPECT_EQ(30, pss->ValueAt(1));
EXPECT_EQ(32, pss->ValueAt(2));
EXPECT_EQ(32, pss->ValueAt(3));
}
TEST_F(SparseStateTest, OnlyChangeAppended) {
pss->Set(0, 30);
pss->Set(2, 31);
pss->Set(3, 31);
EXPECT_EQ(2u, pss->size());
}
TEST_F(SparseStateTest, MergeBetween) {
pss->Set(0, 30);
pss->Set(2, 32);
pss->Set(4, 34);
EXPECT_EQ(3u, pss->size());
SparseState<int> ssAdditions(3);
ssAdditions.Set(4, 34);
EXPECT_EQ(1u, ssAdditions.size());
bool mergeChanged = pss->Merge(ssAdditions,5);
EXPECT_EQ(0, mergeChanged);
ssAdditions.Set(4, 44);
EXPECT_EQ(1u, ssAdditions.size());
mergeChanged = pss->Merge(ssAdditions,5);
EXPECT_EQ(true, mergeChanged);
EXPECT_EQ(3u, pss->size());
EXPECT_EQ(44, pss->ValueAt(4));
}
TEST_F(SparseStateTest, MergeAtExisting) {
pss->Set(0, 30);
pss->Set(2, 32);
pss->Set(4, 34);
EXPECT_EQ(3u, pss->size());
SparseState<int> ssAdditions(4);
ssAdditions.Set(4, 34);
EXPECT_EQ(1u, ssAdditions.size());
bool mergeChanged = pss->Merge(ssAdditions,5);
EXPECT_EQ(0, mergeChanged);
ssAdditions.Set(4, 44);
EXPECT_EQ(1u, ssAdditions.size());
mergeChanged = pss->Merge(ssAdditions,5);
EXPECT_EQ(true, mergeChanged);
EXPECT_EQ(3u, pss->size());
EXPECT_EQ(44, pss->ValueAt(4));
}
TEST_F(SparseStateTest, MergeWhichRemoves) {
pss->Set(0, 30);
pss->Set(2, 32);
pss->Set(4, 34);
EXPECT_EQ(3u, pss->size());
SparseState<int> ssAdditions(2);
ssAdditions.Set(2, 22);
EXPECT_EQ(1u, ssAdditions.size());
EXPECT_EQ(22, ssAdditions.ValueAt(2));
bool mergeChanged = pss->Merge(ssAdditions,5);
EXPECT_EQ(true, mergeChanged);
EXPECT_EQ(2u, pss->size());
EXPECT_EQ(22, pss->ValueAt(2));
}
TEST_F(SparseStateTest, MergeIgnoreSome) {
pss->Set(0, 30);
pss->Set(2, 32);
pss->Set(4, 34);
SparseState<int> ssAdditions(2);
ssAdditions.Set(2, 32);
bool mergeChanged = pss->Merge(ssAdditions,3);
EXPECT_EQ(0, mergeChanged);
EXPECT_EQ(2u, pss->size());
EXPECT_EQ(32, pss->ValueAt(2));
}
TEST_F(SparseStateTest, MergeIgnoreSomeStart) {
pss->Set(0, 30);
pss->Set(2, 32);
pss->Set(4, 34);
SparseState<int> ssAdditions(2);
ssAdditions.Set(2, 32);
bool mergeChanged = pss->Merge(ssAdditions,2);
EXPECT_EQ(0, mergeChanged);
EXPECT_EQ(2u, pss->size());
EXPECT_EQ(32, pss->ValueAt(2));
}
TEST_F(SparseStateTest, MergeIgnoreRepeat) {
pss->Set(0, 30);
pss->Set(2, 32);
pss->Set(4, 34);
SparseState<int> ssAdditions(5);
// Appending same value as at end of pss.
ssAdditions.Set(5, 34);
bool mergeChanged = pss->Merge(ssAdditions,6);
EXPECT_EQ(0, mergeChanged);
EXPECT_EQ(3u, pss->size());
EXPECT_EQ(34, pss->ValueAt(4));
}
class SparseStateStringTest : public ::testing::Test {
protected:
virtual void SetUp() {
pss = new SparseState<std::string>();
}
virtual void TearDown() {
delete pss;
pss = 0;
}
SparseState<std::string> *pss;
};
TEST_F(SparseStateStringTest, IsEmptyInitially) {
EXPECT_EQ(0u, pss->size());
std::string val = pss->ValueAt(0);
EXPECT_EQ("", val);
}
TEST_F(SparseStateStringTest, SimpleSetAndGet) {
EXPECT_EQ(0u, pss->size());
pss->Set(0, "22");
pss->Set(1, "23");
EXPECT_EQ(2u, pss->size());
EXPECT_EQ("", pss->ValueAt(-1));
EXPECT_EQ("22", pss->ValueAt(0));
EXPECT_EQ("23", pss->ValueAt(1));
EXPECT_EQ("23", pss->ValueAt(2));
}
TEST_F(SparseStateStringTest, DeleteBetweeen) {
pss->Set(0, "30");
pss->Set(2, "32");
pss->Delete(1);
EXPECT_EQ(1u, pss->size());
EXPECT_EQ("", pss->ValueAt(-1));
EXPECT_EQ("30", pss->ValueAt(0));
EXPECT_EQ("30", pss->ValueAt(1));
EXPECT_EQ("30", pss->ValueAt(2));
}

View File

@ -0,0 +1,220 @@
// Unit Tests for Scintilla internal data structures
#include <string.h>
#include "Platform.h"
#include "SplitVector.h"
#include <gtest/gtest.h>
// Test SplitVector.
class SplitVectorTest : public ::testing::Test {
protected:
virtual void SetUp() {
psv = new SplitVector<int>;
}
virtual void TearDown() {
delete psv;
psv = 0;
}
SplitVector<int> *psv;
};
const int lengthTestArray = 4;
static const int testArray[4] = {3, 4, 5, 6};
TEST_F(SplitVectorTest, IsEmptyInitially) {
EXPECT_EQ(0, psv->Length());
}
TEST_F(SplitVectorTest, InsertOne) {
psv->InsertValue(0, 10, 0);
psv->Insert(5, 3);
EXPECT_EQ(11, psv->Length());
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ((i == 5) ? 3 : 0, psv->ValueAt(i));
}
}
TEST_F(SplitVectorTest, Insertion) {
psv->InsertValue(0, 10, 0);
EXPECT_EQ(10, psv->Length());
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ(0, psv->ValueAt(i));
}
}
TEST_F(SplitVectorTest, EnsureLength) {
psv->EnsureLength(4);
EXPECT_EQ(4, psv->Length());
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ(0, psv->ValueAt(i));
}
}
TEST_F(SplitVectorTest, InsertFromArray) {
psv->InsertFromArray(0, testArray, 0, lengthTestArray);
EXPECT_EQ(lengthTestArray, psv->Length());
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ(i+3, psv->ValueAt(i));
}
}
TEST_F(SplitVectorTest, SetValue) {
psv->InsertValue(0, 10, 0);
psv->SetValueAt(5, 3);
EXPECT_EQ(10, psv->Length());
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ((i == 5) ? 3 : 0, psv->ValueAt(i));
}
// Move the gap
psv->InsertValue(7, 1, 17);
EXPECT_EQ(17, psv->ValueAt(7));
EXPECT_EQ(0, psv->ValueAt(8));
// Set after the gap
psv->SetValueAt(8, 19);
EXPECT_EQ(19, psv->ValueAt(8));
}
TEST_F(SplitVectorTest, Indexing) {
psv->InsertValue(0, 10, 0);
psv->SetValueAt(5, 3);
EXPECT_EQ(10, psv->Length());
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ((i == 5) ? 3 : 0, (*psv)[i]);
}
}
TEST_F(SplitVectorTest, Fill) {
psv->InsertValue(0, 10, 0);
EXPECT_EQ(10, psv->Length());
psv->InsertValue(7, 1, 1);
EXPECT_EQ(11, psv->Length());
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ((i == 7) ? 1 : 0, psv->ValueAt(i));
}
}
TEST_F(SplitVectorTest, DeleteOne) {
psv->InsertFromArray(0, testArray, 0, lengthTestArray);
psv->Delete(2);
EXPECT_EQ(lengthTestArray-1, psv->Length());
EXPECT_EQ(3, (*psv)[0]);
EXPECT_EQ(4, (*psv)[1]);
EXPECT_EQ(6, (*psv)[2]);
}
TEST_F(SplitVectorTest, DeleteRange) {
psv->InsertValue(0, 10, 0);
EXPECT_EQ(10, psv->Length());
psv->InsertValue(7, 1, 1);
EXPECT_EQ(11, psv->Length());
psv->DeleteRange(2, 3);
EXPECT_EQ(8, psv->Length());
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ((i == 4) ? 1 : 0, psv->ValueAt(i));
}
}
TEST_F(SplitVectorTest, DeleteAll) {
psv->InsertValue(0, 10, 0);
psv->InsertValue(7, 1, 1);
psv->DeleteRange(2, 3);
psv->DeleteAll();
EXPECT_EQ(0, psv->Length());
}
TEST_F(SplitVectorTest, GetRange) {
psv->InsertValue(0, 10, 0);
psv->InsertValue(7, 1, 1);
int retrieveArray[11] = {0};
psv->GetRange(retrieveArray, 0, 11);
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ((i==7) ? 1 : 0, retrieveArray[i]);
}
}
TEST_F(SplitVectorTest, GetRangeOverGap) {
psv->InsertFromArray(0, testArray, 0, lengthTestArray);
EXPECT_EQ(lengthTestArray, psv->Length());
int retrieveArray[lengthTestArray] = {0};
psv->GetRange(retrieveArray, 0, lengthTestArray);
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ(i+3, retrieveArray[i]);
}
}
TEST_F(SplitVectorTest, ReplaceUp) {
// Replace each element by inserting and then deleting the displaced element
// This should perform many moves
const int testLength=105;
psv->EnsureLength(testLength);
for (int i=0; i<testLength; i++)
psv->SetValueAt(i, i+2);
EXPECT_EQ(testLength, psv->Length());
for (int i=0; i<psv->Length(); i++) {
psv->InsertValue(i, 1, i+9);
psv->Delete(i+1);
}
for (int i=0; i<psv->Length(); i++)
EXPECT_EQ(i+9, psv->ValueAt(i));
}
TEST_F(SplitVectorTest, ReplaceDown) {
// From the end, replace each element by inserting and then deleting the displaced element
// This should perform many moves
const int testLength=24;
psv->EnsureLength(testLength);
for (int i=0; i<testLength; i++)
psv->SetValueAt(i, i+12);
EXPECT_EQ(testLength, psv->Length());
for (int i=psv->Length()-1; i>=0; i--) {
psv->InsertValue(i, 1, i+5);
psv->Delete(i+1);
}
for (int i=0; i<psv->Length(); i++)
EXPECT_EQ(i+5, psv->ValueAt(i));
}
TEST_F(SplitVectorTest, BufferPointer) {
psv->InsertFromArray(0, testArray, 0, lengthTestArray);
int *retrievePointer = psv->BufferPointer();
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ(i+3, retrievePointer[i]);
}
}
TEST_F(SplitVectorTest, DeleteBackAndForth) {
psv->InsertValue(0, 10, 87);
for (int i=0; i<10; i+=2) {
int len = 10 - i;
EXPECT_EQ(len, psv->Length());
for (int i=0; i<psv->Length(); i++) {
EXPECT_EQ(87, psv->ValueAt(i));
}
psv->Delete(len-1);
psv->Delete(0);
}
}
TEST_F(SplitVectorTest, GrowSize) {
psv->SetGrowSize(5);
EXPECT_EQ(5, psv->GetGrowSize());
}
TEST_F(SplitVectorTest, OutsideBounds) {
psv->InsertValue(0, 10, 87);
EXPECT_EQ(0, psv->ValueAt(-1));
EXPECT_EQ(0, psv->ValueAt(10));
/* Could be a death test as this asserts:
psv->SetValueAt(-1,98);
psv->SetValueAt(10,99);
EXPECT_EQ(0, psv->ValueAt(-1));
EXPECT_EQ(0, psv->ValueAt(10));
*/
}

View File

@ -0,0 +1,50 @@
// Unit Tests for Scintilla internal data structures
/*
Currently tested:
SplitVector
Partitioning
RunStyles
ContractionState
To do:
Decoration
DecorationList
PerLine *
CellBuffer *
Range
StyledText
CaseFolder ...
Document
RESearch
Selection
UniConversion
Style
lexlib:
Accessor
LexAccessor
CharacterSet
OptionSet
PropSetSimple
StyleContext
WordList
*/
#include <stdio.h>
#include "Platform.h"
#include <gtest/gtest.h>
// Needed for PLATFORM_ASSERT in code being tested
void Platform::Assert(const char *c, const char *file, int line) {
fprintf(stderr, "Assertion [%s] failed at %s %d\n", c, file, line);
abort();
}
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

6
scintilla/test/xite.py Normal file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
import XiteWin
if __name__ == "__main__":
XiteWin.main("")