--- /dev/null
+// Scintilla source code edit control\r
+/** @file Document.cxx\r
+ ** Text document that handles notifications, DBCS, styling, words and end of line.\r
+ **/\r
+// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>\r
+// The License.txt file describes the conditions under which this software may be distributed.\r
+\r
+#include <stdlib.h>\r
+#include <string.h>\r
+#include <stdio.h>\r
+#include <ctype.h>\r
+\r
+#include "Platform.h"\r
+\r
+#include "Scintilla.h"\r
+#include "SplitVector.h"\r
+#include "Partitioning.h"\r
+#include "RunStyles.h"\r
+#include "CellBuffer.h"\r
+#include "CharClassify.h"\r
+#include "Decoration.h"\r
+#include "Document.h"\r
+#include "RESearch.h"\r
+\r
+#ifdef SCI_NAMESPACE\r
+using namespace Scintilla;\r
+#endif\r
+\r
+// This is ASCII specific but is safe with chars >= 0x80\r
+static inline bool isspacechar(unsigned char ch) {\r
+ return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));\r
+}\r
+\r
+static inline bool IsPunctuation(char ch) {\r
+ return isascii(ch) && ispunct(ch);\r
+}\r
+\r
+static inline bool IsADigit(char ch) {\r
+ return isascii(ch) && isdigit(ch);\r
+}\r
+\r
+static inline bool IsLowerCase(char ch) {\r
+ return isascii(ch) && islower(ch);\r
+}\r
+\r
+static inline bool IsUpperCase(char ch) {\r
+ return isascii(ch) && isupper(ch);\r
+}\r
+\r
+Document::Document() {\r
+ refCount = 0;\r
+#ifdef unix\r
+ eolMode = SC_EOL_LF;\r
+#else\r
+ eolMode = SC_EOL_CRLF;\r
+#endif\r
+ dbcsCodePage = 0;\r
+ stylingBits = 5;\r
+ stylingBitsMask = 0x1F;\r
+ stylingMask = 0;\r
+ endStyled = 0;\r
+ styleClock = 0;\r
+ enteredModification = 0;\r
+ enteredStyling = 0;\r
+ enteredReadOnlyCount = 0;\r
+ tabInChars = 8;\r
+ indentInChars = 0;\r
+ actualIndentInChars = 8;\r
+ useTabs = true;\r
+ tabIndents = true;\r
+ backspaceUnindents = false;\r
+ watchers = 0;\r
+ lenWatchers = 0;\r
+\r
+ matchesValid = false;\r
+ regex = 0;\r
+}\r
+\r
+Document::~Document() {\r
+ for (int i = 0; i < lenWatchers; i++) {\r
+ watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);\r
+ }\r
+ delete []watchers;\r
+ watchers = 0;\r
+ lenWatchers = 0;\r
+ delete regex;\r
+ regex = 0;\r
+}\r
+\r
+// Increase reference count and return its previous value.\r
+int Document::AddRef() {\r
+ return refCount++;\r
+}\r
+\r
+// Decrease reference count and return its previous value.\r
+// Delete the document if reference count reaches zero.\r
+int Document::Release() {\r
+ int curRefCount = --refCount;\r
+ if (curRefCount == 0)\r
+ delete this;\r
+ return curRefCount;\r
+}\r
+\r
+void Document::SetSavePoint() {\r
+ cb.SetSavePoint();\r
+ NotifySavePoint(true);\r
+}\r
+\r
+int Document::AddMark(int line, int markerNum) {\r
+ int prev = cb.AddMark(line, markerNum);\r
+ DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);\r
+ NotifyModified(mh);\r
+ return prev;\r
+}\r
+\r
+void Document::AddMarkSet(int line, int valueSet) {\r
+ unsigned int m = valueSet;\r
+ for (int i = 0; m; i++, m >>= 1)\r
+ if (m & 1)\r
+ cb.AddMark(line, i);\r
+ DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);\r
+ NotifyModified(mh);\r
+}\r
+\r
+void Document::DeleteMark(int line, int markerNum) {\r
+ cb.DeleteMark(line, markerNum);\r
+ DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);\r
+ NotifyModified(mh);\r
+}\r
+\r
+void Document::DeleteMarkFromHandle(int markerHandle) {\r
+ cb.DeleteMarkFromHandle(markerHandle);\r
+ DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);\r
+ mh.line = -1;\r
+ NotifyModified(mh);\r
+}\r
+\r
+void Document::DeleteAllMarks(int markerNum) {\r
+ cb.DeleteAllMarks(markerNum);\r
+ DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);\r
+ mh.line = -1;\r
+ NotifyModified(mh);\r
+}\r
+\r
+int Document::LineStart(int line) const {\r
+ return cb.LineStart(line);\r
+}\r
+\r
+int Document::LineEnd(int line) const {\r
+ if (line == LinesTotal() - 1) {\r
+ return LineStart(line + 1);\r
+ } else {\r
+ int position = LineStart(line + 1) - 1;\r
+ // When line terminator is CR+LF, may need to go back one more\r
+ if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) {\r
+ position--;\r
+ }\r
+ return position;\r
+ }\r
+}\r
+\r
+int Document::LineFromPosition(int pos) {\r
+ return cb.LineFromPosition(pos);\r
+}\r
+\r
+int Document::LineEndPosition(int position) {\r
+ return LineEnd(LineFromPosition(position));\r
+}\r
+\r
+int Document::VCHomePosition(int position) {\r
+ int line = LineFromPosition(position);\r
+ int startPosition = LineStart(line);\r
+ int endLine = LineEnd(line);\r
+ int startText = startPosition;\r
+ while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) )\r
+ startText++;\r
+ if (position == startText)\r
+ return startPosition;\r
+ else\r
+ return startText;\r
+}\r
+\r
+int Document::SetLevel(int line, int level) {\r
+ int prev = cb.SetLevel(line, level);\r
+ if (prev != level) {\r
+ DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER,\r
+ LineStart(line), 0, 0, 0, line);\r
+ mh.foldLevelNow = level;\r
+ mh.foldLevelPrev = prev;\r
+ NotifyModified(mh);\r
+ }\r
+ return prev;\r
+}\r
+\r
+static bool IsSubordinate(int levelStart, int levelTry) {\r
+ if (levelTry & SC_FOLDLEVELWHITEFLAG)\r
+ return true;\r
+ else\r
+ return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);\r
+}\r
+\r
+int Document::GetLastChild(int lineParent, int level) {\r
+ if (level == -1)\r
+ level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK;\r
+ int maxLine = LinesTotal();\r
+ int lineMaxSubord = lineParent;\r
+ while (lineMaxSubord < maxLine - 1) {\r
+ EnsureStyledTo(LineStart(lineMaxSubord + 2));\r
+ if (!IsSubordinate(level, GetLevel(lineMaxSubord + 1)))\r
+ break;\r
+ lineMaxSubord++;\r
+ }\r
+ if (lineMaxSubord > lineParent) {\r
+ if (level > (GetLevel(lineMaxSubord + 1) & SC_FOLDLEVELNUMBERMASK)) {\r
+ // Have chewed up some whitespace that belongs to a parent so seek back\r
+ if (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG) {\r
+ lineMaxSubord--;\r
+ }\r
+ }\r
+ }\r
+ return lineMaxSubord;\r
+}\r
+\r
+int Document::GetFoldParent(int line) {\r
+ int level = GetLevel(line) & SC_FOLDLEVELNUMBERMASK;\r
+ int lineLook = line - 1;\r
+ while ((lineLook > 0) && (\r
+ (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||\r
+ ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))\r
+ ) {\r
+ lineLook--;\r
+ }\r
+ if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&\r
+ ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {\r
+ return lineLook;\r
+ } else {\r
+ return -1;\r
+ }\r
+}\r
+\r
+int Document::ClampPositionIntoDocument(int pos) {\r
+ return Platform::Clamp(pos, 0, Length());\r
+}\r
+\r
+bool Document::IsCrLf(int pos) {\r
+ if (pos < 0)\r
+ return false;\r
+ if (pos >= (Length() - 1))\r
+ return false;\r
+ return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');\r
+}\r
+\r
+static const int maxBytesInDBCSCharacter=5;\r
+\r
+int Document::LenChar(int pos) {\r
+ if (pos < 0) {\r
+ return 1;\r
+ } else if (IsCrLf(pos)) {\r
+ return 2;\r
+ } else if (SC_CP_UTF8 == dbcsCodePage) {\r
+ unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));\r
+ if (ch < 0x80)\r
+ return 1;\r
+ int len = 2;\r
+ if (ch >= (0x80 + 0x40 + 0x20 + 0x10))\r
+ len = 4;\r
+ else if (ch >= (0x80 + 0x40 + 0x20))\r
+ len = 3;\r
+ int lengthDoc = Length();\r
+ if ((pos + len) > lengthDoc)\r
+ return lengthDoc -pos;\r
+ else\r
+ return len;\r
+ } else if (dbcsCodePage) {\r
+ char mbstr[maxBytesInDBCSCharacter+1];\r
+ int i;\r
+ for (i=0; i<Platform::DBCSCharMaxLength(); i++) {\r
+ mbstr[i] = cb.CharAt(pos+i);\r
+ }\r
+ mbstr[i] = '\0';\r
+ return Platform::DBCSCharLength(dbcsCodePage, mbstr);\r
+ } else {\r
+ return 1;\r
+ }\r
+}\r
+\r
+static bool IsTrailByte(int ch) {\r
+ return (ch >= 0x80) && (ch < (0x80 + 0x40));\r
+}\r
+\r
+static int BytesFromLead(int leadByte) {\r
+ if (leadByte > 0xF4) {\r
+ // Characters longer than 4 bytes not possible in current UTF-8\r
+ return 0;\r
+ } else if (leadByte >= 0xF0) {\r
+ return 4;\r
+ } else if (leadByte >= 0xE0) {\r
+ return 3;\r
+ } else if (leadByte >= 0xC2) {\r
+ return 2;\r
+ }\r
+ return 0;\r
+}\r
+\r
+bool Document::InGoodUTF8(int pos, int &start, int &end) {\r
+ int lead = pos;\r
+ while ((lead>0) && (pos-lead < 4) && IsTrailByte(static_cast<unsigned char>(cb.CharAt(lead-1))))\r
+ lead--;\r
+ start = 0;\r
+ if (lead > 0) {\r
+ start = lead-1;\r
+ }\r
+ int leadByte = static_cast<unsigned char>(cb.CharAt(start));\r
+ int bytes = BytesFromLead(leadByte);\r
+ if (bytes == 0) {\r
+ return false;\r
+ } else {\r
+ int trailBytes = bytes - 1;\r
+ int len = pos - lead + 1;\r
+ if (len > trailBytes)\r
+ // pos too far from lead\r
+ return false;\r
+ // Check that there are enough trails for this lead\r
+ int trail = pos + 1;\r
+ while ((trail-lead<trailBytes) && (trail < Length())) {\r
+ if (!IsTrailByte(static_cast<unsigned char>(cb.CharAt(trail)))) {\r
+ return false;\r
+ }\r
+ trail++;\r
+ }\r
+ end = start + bytes;\r
+ return true;\r
+ }\r
+}\r
+\r
+// Normalise a position so that it is not halfway through a two byte character.\r
+// This can occur in two situations -\r
+// When lines are terminated with \r\n pairs which should be treated as one character.\r
+// When displaying DBCS text such as Japanese.\r
+// If moving, move the position in the indicated direction.\r
+int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {\r
+ //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);\r
+ // If out of range, just return minimum/maximum value.\r
+ if (pos <= 0)\r
+ return 0;\r
+ if (pos >= Length())\r
+ return Length();\r
+\r
+ // PLATFORM_ASSERT(pos > 0 && pos < Length());\r
+ if (checkLineEnd && IsCrLf(pos - 1)) {\r
+ if (moveDir > 0)\r
+ return pos + 1;\r
+ else\r
+ return pos - 1;\r
+ }\r
+\r
+ // Not between CR and LF\r
+\r
+ if (dbcsCodePage) {\r
+ if (SC_CP_UTF8 == dbcsCodePage) {\r
+ unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));\r
+ int startUTF = pos;\r
+ int endUTF = pos;\r
+ if (IsTrailByte(ch) && InGoodUTF8(pos, startUTF, endUTF)) {\r
+ // ch is a trail byte within a UTF-8 character\r
+ if (moveDir > 0)\r
+ pos = endUTF;\r
+ else\r
+ pos = startUTF;\r
+ }\r
+ } else {\r
+ // Anchor DBCS calculations at start of line because start of line can\r
+ // not be a DBCS trail byte.\r
+ int posCheck = LineStart(LineFromPosition(pos));\r
+ while (posCheck < pos) {\r
+ char mbstr[maxBytesInDBCSCharacter+1];\r
+ int i;\r
+ for(i=0;i<Platform::DBCSCharMaxLength();i++) {\r
+ mbstr[i] = cb.CharAt(posCheck+i);\r
+ }\r
+ mbstr[i] = '\0';\r
+\r
+ int mbsize = Platform::DBCSCharLength(dbcsCodePage, mbstr);\r
+ if (posCheck + mbsize == pos) {\r
+ return pos;\r
+ } else if (posCheck + mbsize > pos) {\r
+ if (moveDir > 0) {\r
+ return posCheck + mbsize;\r
+ } else {\r
+ return posCheck;\r
+ }\r
+ }\r
+ posCheck += mbsize;\r
+ }\r
+ }\r
+ }\r
+\r
+ return pos;\r
+}\r
+\r
+void Document::ModifiedAt(int pos) {\r
+ if (endStyled > pos)\r
+ endStyled = pos;\r
+}\r
+\r
+void Document::CheckReadOnly() {\r
+ if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {\r
+ enteredReadOnlyCount++;\r
+ NotifyModifyAttempt();\r
+ enteredReadOnlyCount--;\r
+ }\r
+}\r
+\r
+// Document only modified by gateways DeleteChars, InsertString, Undo, Redo, and SetStyleAt.\r
+// SetStyleAt does not change the persistent state of a document\r
+\r
+bool Document::DeleteChars(int pos, int len) {\r
+ if (len == 0)\r
+ return false;\r
+ if ((pos + len) > Length())\r
+ return false;\r
+ CheckReadOnly();\r
+ if (enteredModification != 0) {\r
+ return false;\r
+ } else {\r
+ enteredModification++;\r
+ if (!cb.IsReadOnly()) {\r
+ NotifyModified(\r
+ DocModification(\r
+ SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,\r
+ pos, len,\r
+ 0, 0));\r
+ int prevLinesTotal = LinesTotal();\r
+ bool startSavePoint = cb.IsSavePoint();\r
+ bool startSequence = false;\r
+ const char *text = cb.DeleteChars(pos, len, startSequence);\r
+ if (startSavePoint && cb.IsCollectingUndo())\r
+ NotifySavePoint(!startSavePoint);\r
+ if ((pos < Length()) || (pos == 0))\r
+ ModifiedAt(pos);\r
+ else\r
+ ModifiedAt(pos-1);\r
+ NotifyModified(\r
+ DocModification(\r
+ SC_MOD_DELETETEXT | SC_PERFORMED_USER | (startSequence?SC_STARTACTION:0),\r
+ pos, len,\r
+ LinesTotal() - prevLinesTotal, text));\r
+ }\r
+ enteredModification--;\r
+ }\r
+ return !cb.IsReadOnly();\r
+}\r
+\r
+/**\r
+ * Insert a string with a length.\r
+ */\r
+bool Document::InsertString(int position, const char *s, int insertLength) {\r
+ if (insertLength <= 0) {\r
+ return false;\r
+ }\r
+ CheckReadOnly();\r
+ if (enteredModification != 0) {\r
+ return false;\r
+ } else {\r
+ enteredModification++;\r
+ if (!cb.IsReadOnly()) {\r
+ NotifyModified(\r
+ DocModification(\r
+ SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,\r
+ position, insertLength,\r
+ 0, s));\r
+ int prevLinesTotal = LinesTotal();\r
+ bool startSavePoint = cb.IsSavePoint();\r
+ bool startSequence = false;\r
+ const char *text = cb.InsertString(position, s, insertLength, startSequence);\r
+ if (startSavePoint && cb.IsCollectingUndo())\r
+ NotifySavePoint(!startSavePoint);\r
+ ModifiedAt(position);\r
+ NotifyModified(\r
+ DocModification(\r
+ SC_MOD_INSERTTEXT | SC_PERFORMED_USER | (startSequence?SC_STARTACTION:0),\r
+ position, insertLength,\r
+ LinesTotal() - prevLinesTotal, text));\r
+ }\r
+ enteredModification--;\r
+ }\r
+ return !cb.IsReadOnly();\r
+}\r
+\r
+int Document::Undo() {\r
+ int newPos = -1;\r
+ CheckReadOnly();\r
+ if (enteredModification == 0) {\r
+ enteredModification++;\r
+ if (!cb.IsReadOnly()) {\r
+ bool startSavePoint = cb.IsSavePoint();\r
+ bool multiLine = false;\r
+ int steps = cb.StartUndo();\r
+ //Platform::DebugPrintf("Steps=%d\n", steps);\r
+ for (int step = 0; step < steps; step++) {\r
+ const int prevLinesTotal = LinesTotal();\r
+ const Action &action = cb.GetUndoStep();\r
+ if (action.at == removeAction) {\r
+ NotifyModified(DocModification(\r
+ SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));\r
+ } else {\r
+ NotifyModified(DocModification(\r
+ SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));\r
+ }\r
+ cb.PerformUndoStep();\r
+ int cellPosition = action.position;\r
+ ModifiedAt(cellPosition);\r
+ newPos = cellPosition;\r
+\r
+ int modFlags = SC_PERFORMED_UNDO;\r
+ // With undo, an insertion action becomes a deletion notification\r
+ if (action.at == removeAction) {\r
+ newPos += action.lenData;\r
+ modFlags |= SC_MOD_INSERTTEXT;\r
+ } else {\r
+ modFlags |= SC_MOD_DELETETEXT;\r
+ }\r
+ if (steps > 1)\r
+ modFlags |= SC_MULTISTEPUNDOREDO;\r
+ const int linesAdded = LinesTotal() - prevLinesTotal;\r
+ if (linesAdded != 0)\r
+ multiLine = true;\r
+ if (step == steps - 1) {\r
+ modFlags |= SC_LASTSTEPINUNDOREDO;\r
+ if (multiLine)\r
+ modFlags |= SC_MULTILINEUNDOREDO;\r
+ }\r
+ NotifyModified(DocModification(modFlags, cellPosition, action.lenData,\r
+ linesAdded, action.data));\r
+ }\r
+\r
+ bool endSavePoint = cb.IsSavePoint();\r
+ if (startSavePoint != endSavePoint)\r
+ NotifySavePoint(endSavePoint);\r
+ }\r
+ enteredModification--;\r
+ }\r
+ return newPos;\r
+}\r
+\r
+int Document::Redo() {\r
+ int newPos = -1;\r
+ CheckReadOnly();\r
+ if (enteredModification == 0) {\r
+ enteredModification++;\r
+ if (!cb.IsReadOnly()) {\r
+ bool startSavePoint = cb.IsSavePoint();\r
+ bool multiLine = false;\r
+ int steps = cb.StartRedo();\r
+ for (int step = 0; step < steps; step++) {\r
+ const int prevLinesTotal = LinesTotal();\r
+ const Action &action = cb.GetRedoStep();\r
+ if (action.at == insertAction) {\r
+ NotifyModified(DocModification(\r
+ SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));\r
+ } else {\r
+ NotifyModified(DocModification(\r
+ SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));\r
+ }\r
+ cb.PerformRedoStep();\r
+ ModifiedAt(action.position);\r
+ newPos = action.position;\r
+\r
+ int modFlags = SC_PERFORMED_REDO;\r
+ if (action.at == insertAction) {\r
+ newPos += action.lenData;\r
+ modFlags |= SC_MOD_INSERTTEXT;\r
+ } else {\r
+ modFlags |= SC_MOD_DELETETEXT;\r
+ }\r
+ if (steps > 1)\r
+ modFlags |= SC_MULTISTEPUNDOREDO;\r
+ const int linesAdded = LinesTotal() - prevLinesTotal;\r
+ if (linesAdded != 0)\r
+ multiLine = true;\r
+ if (step == steps - 1) {\r
+ modFlags |= SC_LASTSTEPINUNDOREDO;\r
+ if (multiLine)\r
+ modFlags |= SC_MULTILINEUNDOREDO;\r
+ }\r
+ NotifyModified(\r
+ DocModification(modFlags, action.position, action.lenData,\r
+ linesAdded, action.data));\r
+ }\r
+\r
+ bool endSavePoint = cb.IsSavePoint();\r
+ if (startSavePoint != endSavePoint)\r
+ NotifySavePoint(endSavePoint);\r
+ }\r
+ enteredModification--;\r
+ }\r
+ return newPos;\r
+}\r
+\r
+/**\r
+ * Insert a single character.\r
+ */\r
+bool Document::InsertChar(int pos, char ch) {\r
+ char chs[1];\r
+ chs[0] = ch;\r
+ return InsertString(pos, chs, 1);\r
+}\r
+\r
+/**\r
+ * Insert a null terminated string.\r
+ */\r
+bool Document::InsertCString(int position, const char *s) {\r
+ return InsertString(position, s, strlen(s));\r
+}\r
+\r
+void Document::ChangeChar(int pos, char ch) {\r
+ DeleteChars(pos, 1);\r
+ InsertChar(pos, ch);\r
+}\r
+\r
+void Document::DelChar(int pos) {\r
+ DeleteChars(pos, LenChar(pos));\r
+}\r
+\r
+void Document::DelCharBack(int pos) {\r
+ if (pos <= 0) {\r
+ return;\r
+ } else if (IsCrLf(pos - 2)) {\r
+ DeleteChars(pos - 2, 2);\r
+ } else if (dbcsCodePage) {\r
+ int startChar = MovePositionOutsideChar(pos - 1, -1, false);\r
+ DeleteChars(startChar, pos - startChar);\r
+ } else {\r
+ DeleteChars(pos - 1, 1);\r
+ }\r
+}\r
+\r
+static bool isindentchar(char ch) {\r
+ return (ch == ' ') || (ch == '\t');\r
+}\r
+\r
+static int NextTab(int pos, int tabSize) {\r
+ return ((pos / tabSize) + 1) * tabSize;\r
+}\r
+\r
+static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) {\r
+ length--; // ensure space for \0\r
+ if (!insertSpaces) {\r
+ while ((indent >= tabSize) && (length > 0)) {\r
+ *linebuf++ = '\t';\r
+ indent -= tabSize;\r
+ length--;\r
+ }\r
+ }\r
+ while ((indent > 0) && (length > 0)) {\r
+ *linebuf++ = ' ';\r
+ indent--;\r
+ length--;\r
+ }\r
+ *linebuf = '\0';\r
+}\r
+\r
+int Document::GetLineIndentation(int line) {\r
+ int indent = 0;\r
+ if ((line >= 0) && (line < LinesTotal())) {\r
+ int lineStart = LineStart(line);\r
+ int length = Length();\r
+ for (int i = lineStart;i < length;i++) {\r
+ char ch = cb.CharAt(i);\r
+ if (ch == ' ')\r
+ indent++;\r
+ else if (ch == '\t')\r
+ indent = NextTab(indent, tabInChars);\r
+ else\r
+ return indent;\r
+ }\r
+ }\r
+ return indent;\r
+}\r
+\r
+void Document::SetLineIndentation(int line, int indent) {\r
+ int indentOfLine = GetLineIndentation(line);\r
+ if (indent < 0)\r
+ indent = 0;\r
+ if (indent != indentOfLine) {\r
+ char linebuf[1000];\r
+ CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs);\r
+ int thisLineStart = LineStart(line);\r
+ int indentPos = GetLineIndentPosition(line);\r
+ BeginUndoAction();\r
+ DeleteChars(thisLineStart, indentPos - thisLineStart);\r
+ InsertCString(thisLineStart, linebuf);\r
+ EndUndoAction();\r
+ }\r
+}\r
+\r
+int Document::GetLineIndentPosition(int line) const {\r
+ if (line < 0)\r
+ return 0;\r
+ int pos = LineStart(line);\r
+ int length = Length();\r
+ while ((pos < length) && isindentchar(cb.CharAt(pos))) {\r
+ pos++;\r
+ }\r
+ return pos;\r
+}\r
+\r
+int Document::GetColumn(int pos) {\r
+ int column = 0;\r
+ int line = LineFromPosition(pos);\r
+ if ((line >= 0) && (line < LinesTotal())) {\r
+ for (int i = LineStart(line);i < pos;) {\r
+ char ch = cb.CharAt(i);\r
+ if (ch == '\t') {\r
+ column = NextTab(column, tabInChars);\r
+ i++;\r
+ } else if (ch == '\r') {\r
+ return column;\r
+ } else if (ch == '\n') {\r
+ return column;\r
+ } else if (i >= Length()) {\r
+ return column;\r
+ } else {\r
+ column++;\r
+ i = MovePositionOutsideChar(i + 1, 1, false);\r
+ }\r
+ }\r
+ }\r
+ return column;\r
+}\r
+\r
+int Document::FindColumn(int line, int column) {\r
+ int position = LineStart(line);\r
+ int columnCurrent = 0;\r
+ if ((line >= 0) && (line < LinesTotal())) {\r
+ while ((columnCurrent < column) && (position < Length())) {\r
+ char ch = cb.CharAt(position);\r
+ if (ch == '\t') {\r
+ columnCurrent = NextTab(columnCurrent, tabInChars);\r
+ position++;\r
+ } else if (ch == '\r') {\r
+ return position;\r
+ } else if (ch == '\n') {\r
+ return position;\r
+ } else {\r
+ columnCurrent++;\r
+ position = MovePositionOutsideChar(position + 1, 1, false);\r
+ }\r
+ }\r
+ }\r
+ return position;\r
+}\r
+\r
+void Document::Indent(bool forwards, int lineBottom, int lineTop) {\r
+ // Dedent - suck white space off the front of the line to dedent by equivalent of a tab\r
+ for (int line = lineBottom; line >= lineTop; line--) {\r
+ int indentOfLine = GetLineIndentation(line);\r
+ if (forwards) {\r
+ if (LineStart(line) < LineEnd(line)) {\r
+ SetLineIndentation(line, indentOfLine + IndentSize());\r
+ }\r
+ } else {\r
+ SetLineIndentation(line, indentOfLine - IndentSize());\r
+ }\r
+ }\r
+}\r
+\r
+// Convert line endings for a piece of text to a particular mode.\r
+// Stop at len or when a NUL is found.\r
+// Caller must delete the returned pointer.\r
+char *Document::TransformLineEnds(int *pLenOut, const char *s, size_t len, int eolMode) {\r
+ char *dest = new char[2 * len + 1];\r
+ const char *sptr = s;\r
+ char *dptr = dest;\r
+ for (size_t i = 0; (i < len) && (*sptr != '\0'); i++) {\r
+ if (*sptr == '\n' || *sptr == '\r') {\r
+ if (eolMode == SC_EOL_CR) {\r
+ *dptr++ = '\r';\r
+ } else if (eolMode == SC_EOL_LF) {\r
+ *dptr++ = '\n';\r
+ } else { // eolMode == SC_EOL_CRLF\r
+ *dptr++ = '\r';\r
+ *dptr++ = '\n';\r
+ }\r
+ if ((*sptr == '\r') && (i+1 < len) && (*(sptr+1) == '\n')) {\r
+ i++;\r
+ sptr++;\r
+ }\r
+ sptr++;\r
+ } else {\r
+ *dptr++ = *sptr++;\r
+ }\r
+ }\r
+ *dptr++ = '\0';\r
+ *pLenOut = (dptr - dest) - 1;\r
+ return dest;\r
+}\r
+\r
+void Document::ConvertLineEnds(int eolModeSet) {\r
+ BeginUndoAction();\r
+\r
+ for (int pos = 0; pos < Length(); pos++) {\r
+ if (cb.CharAt(pos) == '\r') {\r
+ if (cb.CharAt(pos + 1) == '\n') {\r
+ // CRLF\r
+ if (eolModeSet == SC_EOL_CR) {\r
+ DeleteChars(pos + 1, 1); // Delete the LF\r
+ } else if (eolModeSet == SC_EOL_LF) {\r
+ DeleteChars(pos, 1); // Delete the CR\r
+ } else {\r
+ pos++;\r
+ }\r
+ } else {\r
+ // CR\r
+ if (eolModeSet == SC_EOL_CRLF) {\r
+ InsertString(pos + 1, "\n", 1); // Insert LF\r
+ pos++;\r
+ } else if (eolModeSet == SC_EOL_LF) {\r
+ InsertString(pos, "\n", 1); // Insert LF\r
+ DeleteChars(pos + 1, 1); // Delete CR\r
+ }\r
+ }\r
+ } else if (cb.CharAt(pos) == '\n') {\r
+ // LF\r
+ if (eolModeSet == SC_EOL_CRLF) {\r
+ InsertString(pos, "\r", 1); // Insert CR\r
+ pos++;\r
+ } else if (eolModeSet == SC_EOL_CR) {\r
+ InsertString(pos, "\r", 1); // Insert CR\r
+ DeleteChars(pos + 1, 1); // Delete LF\r
+ }\r
+ }\r
+ }\r
+\r
+ EndUndoAction();\r
+}\r
+\r
+bool Document::IsWhiteLine(int line) const {\r
+ int currentChar = LineStart(line);\r
+ int endLine = LineEnd(line);\r
+ while (currentChar < endLine) {\r
+ if (cb.CharAt(currentChar) != ' ' && cb.CharAt(currentChar) != '\t') {\r
+ return false;\r
+ }\r
+ ++currentChar;\r
+ }\r
+ return true;\r
+}\r
+\r
+int Document::ParaUp(int pos) {\r
+ int line = LineFromPosition(pos);\r
+ line--;\r
+ while (line >= 0 && IsWhiteLine(line)) { // skip empty lines\r
+ line--;\r
+ }\r
+ while (line >= 0 && !IsWhiteLine(line)) { // skip non-empty lines\r
+ line--;\r
+ }\r
+ line++;\r
+ return LineStart(line);\r
+}\r
+\r
+int Document::ParaDown(int pos) {\r
+ int line = LineFromPosition(pos);\r
+ while (line < LinesTotal() && !IsWhiteLine(line)) { // skip non-empty lines\r
+ line++;\r
+ }\r
+ while (line < LinesTotal() && IsWhiteLine(line)) { // skip empty lines\r
+ line++;\r
+ }\r
+ if (line < LinesTotal())\r
+ return LineStart(line);\r
+ else // end of a document\r
+ return LineEnd(line-1);\r
+}\r
+\r
+CharClassify::cc Document::WordCharClass(unsigned char ch) {\r
+ if ((SC_CP_UTF8 == dbcsCodePage) && (ch >= 0x80))\r
+ return CharClassify::ccWord;\r
+ return charClass.GetClass(ch);\r
+}\r
+\r
+/**\r
+ * Used by commmands that want to select whole words.\r
+ * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0.\r
+ */\r
+int Document::ExtendWordSelect(int pos, int delta, bool onlyWordCharacters) {\r
+ CharClassify::cc ccStart = CharClassify::ccWord;\r
+ if (delta < 0) {\r
+ if (!onlyWordCharacters)\r
+ ccStart = WordCharClass(cb.CharAt(pos-1));\r
+ while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart))\r
+ pos--;\r
+ } else {\r
+ if (!onlyWordCharacters && pos < Length())\r
+ ccStart = WordCharClass(cb.CharAt(pos));\r
+ while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))\r
+ pos++;\r
+ }\r
+ return MovePositionOutsideChar(pos, delta);\r
+}\r
+\r
+/**\r
+ * Find the start of the next word in either a forward (delta >= 0) or backwards direction\r
+ * (delta < 0).\r
+ * This is looking for a transition between character classes although there is also some\r
+ * additional movement to transit white space.\r
+ * Used by cursor movement by word commands.\r
+ */\r
+int Document::NextWordStart(int pos, int delta) {\r
+ if (delta < 0) {\r
+ while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == CharClassify::ccSpace))\r
+ pos--;\r
+ if (pos > 0) {\r
+ CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos-1));\r
+ while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart)) {\r
+ pos--;\r
+ }\r
+ }\r
+ } else {\r
+ CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos));\r
+ while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))\r
+ pos++;\r
+ while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == CharClassify::ccSpace))\r
+ pos++;\r
+ }\r
+ return pos;\r
+}\r
+\r
+/**\r
+ * Find the end of the next word in either a forward (delta >= 0) or backwards direction\r
+ * (delta < 0).\r
+ * This is looking for a transition between character classes although there is also some\r
+ * additional movement to transit white space.\r
+ * Used by cursor movement by word commands.\r
+ */\r
+int Document::NextWordEnd(int pos, int delta) {\r
+ if (delta < 0) {\r
+ if (pos > 0) {\r
+ CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos-1));\r
+ if (ccStart != CharClassify::ccSpace) {\r
+ while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccStart) {\r
+ pos--;\r
+ }\r
+ }\r
+ while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == CharClassify::ccSpace) {\r
+ pos--;\r
+ }\r
+ }\r
+ } else {\r
+ while (pos < Length() && WordCharClass(cb.CharAt(pos)) == CharClassify::ccSpace) {\r
+ pos++;\r
+ }\r
+ if (pos < Length()) {\r
+ CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos));\r
+ while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccStart) {\r
+ pos++;\r
+ }\r
+ }\r
+ }\r
+ return pos;\r
+}\r
+\r
+/**\r
+ * Check that the character at the given position is a word or punctuation character and that\r
+ * the previous character is of a different character class.\r
+ */\r
+bool Document::IsWordStartAt(int pos) {\r
+ if (pos > 0) {\r
+ CharClassify::cc ccPos = WordCharClass(CharAt(pos));\r
+ return (ccPos == CharClassify::ccWord || ccPos == CharClassify::ccPunctuation) &&\r
+ (ccPos != WordCharClass(CharAt(pos - 1)));\r
+ }\r
+ return true;\r
+}\r
+\r
+/**\r
+ * Check that the character at the given position is a word or punctuation character and that\r
+ * the next character is of a different character class.\r
+ */\r
+bool Document::IsWordEndAt(int pos) {\r
+ if (pos < Length()) {\r
+ CharClassify::cc ccPrev = WordCharClass(CharAt(pos-1));\r
+ return (ccPrev == CharClassify::ccWord || ccPrev == CharClassify::ccPunctuation) &&\r
+ (ccPrev != WordCharClass(CharAt(pos)));\r
+ }\r
+ return true;\r
+}\r
+\r
+/**\r
+ * Check that the given range is has transitions between character classes at both\r
+ * ends and where the characters on the inside are word or punctuation characters.\r
+ */\r
+bool Document::IsWordAt(int start, int end) {\r
+ return IsWordStartAt(start) && IsWordEndAt(end);\r
+}\r
+\r
+// The comparison and case changing functions here assume ASCII\r
+// or extended ASCII such as the normal Windows code page.\r
+\r
+static inline char MakeUpperCase(char ch) {\r
+ if (ch < 'a' || ch > 'z')\r
+ return ch;\r
+ else\r
+ return static_cast<char>(ch - 'a' + 'A');\r
+}\r
+\r
+static inline char MakeLowerCase(char ch) {\r
+ if (ch < 'A' || ch > 'Z')\r
+ return ch;\r
+ else\r
+ return static_cast<char>(ch - 'A' + 'a');\r
+}\r
+\r
+/**\r
+ * Find text in document, supporting both forward and backward\r
+ * searches (just pass minPos > maxPos to do a backward search)\r
+ * Has not been tested with backwards DBCS searches yet.\r
+ */\r
+long Document::FindText(int minPos, int maxPos, const char *s,\r
+ bool caseSensitive, bool word, bool wordStart, bool regExp, int flags,\r
+ int *length) {\r
+ if (regExp) {\r
+ if (!regex)\r
+ regex = CreateRegexSearch(&charClass);\r
+ return regex->FindText(this, minPos, maxPos, s, caseSensitive, word, wordStart, flags, length);\r
+ } else {\r
+\r
+ bool forward = minPos <= maxPos;\r
+ int increment = forward ? 1 : -1;\r
+\r
+ // Range endpoints should not be inside DBCS characters, but just in case, move them.\r
+ int startPos = MovePositionOutsideChar(minPos, increment, false);\r
+ int endPos = MovePositionOutsideChar(maxPos, increment, false);\r
+\r
+ // Compute actual search ranges needed\r
+ int lengthFind = *length;\r
+ if (lengthFind == -1)\r
+ lengthFind = static_cast<int>(strlen(s));\r
+ int endSearch = endPos;\r
+ if (startPos <= endPos) {\r
+ endSearch = endPos - lengthFind + 1;\r
+ }\r
+ //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);\r
+ char firstChar = s[0];\r
+ if (!caseSensitive)\r
+ firstChar = static_cast<char>(MakeUpperCase(firstChar));\r
+ int pos = forward ? startPos : (startPos - 1);\r
+ while (forward ? (pos < endSearch) : (pos >= endSearch)) {\r
+ char ch = CharAt(pos);\r
+ if (caseSensitive) {\r
+ if (ch == firstChar) {\r
+ bool found = true;\r
+ if (pos + lengthFind > Platform::Maximum(startPos, endPos)) found = false;\r
+ for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {\r
+ ch = CharAt(pos + posMatch);\r
+ if (ch != s[posMatch])\r
+ found = false;\r
+ }\r
+ if (found) {\r
+ if ((!word && !wordStart) ||\r
+ word && IsWordAt(pos, pos + lengthFind) ||\r
+ wordStart && IsWordStartAt(pos))\r
+ return pos;\r
+ }\r
+ }\r
+ } else {\r
+ if (MakeUpperCase(ch) == firstChar) {\r
+ bool found = true;\r
+ if (pos + lengthFind > Platform::Maximum(startPos, endPos)) found = false;\r
+ for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {\r
+ ch = CharAt(pos + posMatch);\r
+ if (MakeUpperCase(ch) != MakeUpperCase(s[posMatch]))\r
+ found = false;\r
+ }\r
+ if (found) {\r
+ if ((!word && !wordStart) ||\r
+ word && IsWordAt(pos, pos + lengthFind) ||\r
+ wordStart && IsWordStartAt(pos))\r
+ return pos;\r
+ }\r
+ }\r
+ }\r
+ pos += increment;\r
+ if (dbcsCodePage && (pos >= 0)) {\r
+ // Ensure trying to match from start of character\r
+ pos = MovePositionOutsideChar(pos, increment, false);\r
+ }\r
+ }\r
+ }\r
+ //Platform::DebugPrintf("Not found\n");\r
+ return -1;\r
+}\r
+\r
+const char *Document::SubstituteByPosition(const char *text, int *length) {\r
+ return regex->SubstituteByPosition(this, text, length);\r
+}\r
+\r
+int Document::LinesTotal() const {\r
+ return cb.Lines();\r
+}\r
+\r
+void Document::ChangeCase(Range r, bool makeUpperCase) {\r
+ for (int pos = r.start; pos < r.end;) {\r
+ int len = LenChar(pos);\r
+ if (len == 1) {\r
+ char ch = CharAt(pos);\r
+ if (makeUpperCase) {\r
+ if (IsLowerCase(ch)) {\r
+ ChangeChar(pos, static_cast<char>(MakeUpperCase(ch)));\r
+ }\r
+ } else {\r
+ if (IsUpperCase(ch)) {\r
+ ChangeChar(pos, static_cast<char>(MakeLowerCase(ch)));\r
+ }\r
+ }\r
+ }\r
+ pos += len;\r
+ }\r
+}\r
+\r
+void Document::SetDefaultCharClasses(bool includeWordClass) {\r
+ charClass.SetDefaultCharClasses(includeWordClass);\r
+}\r
+\r
+void Document::SetCharClasses(const unsigned char *chars, CharClassify::cc newCharClass) {\r
+ charClass.SetCharClasses(chars, newCharClass);\r
+}\r
+\r
+void Document::SetStylingBits(int bits) {\r
+ stylingBits = bits;\r
+ stylingBitsMask = (1 << stylingBits) - 1;\r
+}\r
+\r
+void Document::StartStyling(int position, char mask) {\r
+ stylingMask = mask;\r
+ endStyled = position;\r
+}\r
+\r
+bool Document::SetStyleFor(int length, char style) {\r
+ if (enteredStyling != 0) {\r
+ return false;\r
+ } else {\r
+ enteredStyling++;\r
+ style &= stylingMask;\r
+ int prevEndStyled = endStyled;\r
+ if (cb.SetStyleFor(endStyled, length, style, stylingMask)) {\r
+ DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,\r
+ prevEndStyled, length);\r
+ NotifyModified(mh);\r
+ }\r
+ endStyled += length;\r
+ enteredStyling--;\r
+ return true;\r
+ }\r
+}\r
+\r
+bool Document::SetStyles(int length, char *styles) {\r
+ if (enteredStyling != 0) {\r
+ return false;\r
+ } else {\r
+ enteredStyling++;\r
+ bool didChange = false;\r
+ int startMod = 0;\r
+ int endMod = 0;\r
+ for (int iPos = 0; iPos < length; iPos++, endStyled++) {\r
+ PLATFORM_ASSERT(endStyled < Length());\r
+ if (cb.SetStyleAt(endStyled, styles[iPos], stylingMask)) {\r
+ if (!didChange) {\r
+ startMod = endStyled;\r
+ }\r
+ didChange = true;\r
+ endMod = endStyled;\r
+ }\r
+ }\r
+ if (didChange) {\r
+ DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,\r
+ startMod, endMod - startMod + 1);\r
+ NotifyModified(mh);\r
+ }\r
+ enteredStyling--;\r
+ return true;\r
+ }\r
+}\r
+\r
+void Document::EnsureStyledTo(int pos) {\r
+ if ((enteredStyling == 0) && (pos > GetEndStyled())) {\r
+ IncrementStyleClock();\r
+ // Ask the watchers to style, and stop as soon as one responds.\r
+ for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++) {\r
+ watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos);\r
+ }\r
+ }\r
+}\r
+\r
+int Document::SetLineState(int line, int state) {\r
+ int statePrevious = cb.SetLineState(line, state);\r
+ if (state != statePrevious) {\r
+ DocModification mh(SC_MOD_CHANGELINESTATE, 0, 0, 0, 0, line);\r
+ NotifyModified(mh);\r
+ }\r
+ return statePrevious;\r
+}\r
+\r
+void Document::IncrementStyleClock() {\r
+ styleClock = (styleClock + 1) % 0x100000;\r
+}\r
+\r
+void Document::DecorationFillRange(int position, int value, int fillLength) {\r
+ if (decorations.FillRange(position, value, fillLength)) {\r
+ DocModification mh(SC_MOD_CHANGEINDICATOR | SC_PERFORMED_USER,\r
+ position, fillLength);\r
+ NotifyModified(mh);\r
+ }\r
+}\r
+\r
+bool Document::AddWatcher(DocWatcher *watcher, void *userData) {\r
+ for (int i = 0; i < lenWatchers; i++) {\r
+ if ((watchers[i].watcher == watcher) &&\r
+ (watchers[i].userData == userData))\r
+ return false;\r
+ }\r
+ WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];\r
+ if (!pwNew)\r
+ return false;\r
+ for (int j = 0; j < lenWatchers; j++)\r
+ pwNew[j] = watchers[j];\r
+ pwNew[lenWatchers].watcher = watcher;\r
+ pwNew[lenWatchers].userData = userData;\r
+ delete []watchers;\r
+ watchers = pwNew;\r
+ lenWatchers++;\r
+ return true;\r
+}\r
+\r
+bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {\r
+ for (int i = 0; i < lenWatchers; i++) {\r
+ if ((watchers[i].watcher == watcher) &&\r
+ (watchers[i].userData == userData)) {\r
+ if (lenWatchers == 1) {\r
+ delete []watchers;\r
+ watchers = 0;\r
+ lenWatchers = 0;\r
+ } else {\r
+ WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];\r
+ if (!pwNew)\r
+ return false;\r
+ for (int j = 0; j < lenWatchers - 1; j++) {\r
+ pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];\r
+ }\r
+ delete []watchers;\r
+ watchers = pwNew;\r
+ lenWatchers--;\r
+ }\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+}\r
+\r
+void Document::NotifyModifyAttempt() {\r
+ for (int i = 0; i < lenWatchers; i++) {\r
+ watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);\r
+ }\r
+}\r
+\r
+void Document::NotifySavePoint(bool atSavePoint) {\r
+ for (int i = 0; i < lenWatchers; i++) {\r
+ watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);\r
+ }\r
+}\r
+\r
+void Document::NotifyModified(DocModification mh) {\r
+ if (mh.modificationType & SC_MOD_INSERTTEXT) {\r
+ decorations.InsertSpace(mh.position, mh.length);\r
+ } else if (mh.modificationType & SC_MOD_DELETETEXT) {\r
+ decorations.DeleteRange(mh.position, mh.length);\r
+ }\r
+ for (int i = 0; i < lenWatchers; i++) {\r
+ watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);\r
+ }\r
+}\r
+\r
+bool Document::IsWordPartSeparator(char ch) {\r
+ return (WordCharClass(ch) == CharClassify::ccWord) && IsPunctuation(ch);\r
+}\r
+\r
+int Document::WordPartLeft(int pos) {\r
+ if (pos > 0) {\r
+ --pos;\r
+ char startChar = cb.CharAt(pos);\r
+ if (IsWordPartSeparator(startChar)) {\r
+ while (pos > 0 && IsWordPartSeparator(cb.CharAt(pos))) {\r
+ --pos;\r
+ }\r
+ }\r
+ if (pos > 0) {\r
+ startChar = cb.CharAt(pos);\r
+ --pos;\r
+ if (IsLowerCase(startChar)) {\r
+ while (pos > 0 && IsLowerCase(cb.CharAt(pos)))\r
+ --pos;\r
+ if (!IsUpperCase(cb.CharAt(pos)) && !IsLowerCase(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else if (IsUpperCase(startChar)) {\r
+ while (pos > 0 && IsUpperCase(cb.CharAt(pos)))\r
+ --pos;\r
+ if (!IsUpperCase(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else if (IsADigit(startChar)) {\r
+ while (pos > 0 && IsADigit(cb.CharAt(pos)))\r
+ --pos;\r
+ if (!IsADigit(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else if (IsPunctuation(startChar)) {\r
+ while (pos > 0 && IsPunctuation(cb.CharAt(pos)))\r
+ --pos;\r
+ if (!IsPunctuation(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else if (isspacechar(startChar)) {\r
+ while (pos > 0 && isspacechar(cb.CharAt(pos)))\r
+ --pos;\r
+ if (!isspacechar(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else if (!isascii(startChar)) {\r
+ while (pos > 0 && !isascii(cb.CharAt(pos)))\r
+ --pos;\r
+ if (isascii(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else {\r
+ ++pos;\r
+ }\r
+ }\r
+ }\r
+ return pos;\r
+}\r
+\r
+int Document::WordPartRight(int pos) {\r
+ char startChar = cb.CharAt(pos);\r
+ int length = Length();\r
+ if (IsWordPartSeparator(startChar)) {\r
+ while (pos < length && IsWordPartSeparator(cb.CharAt(pos)))\r
+ ++pos;\r
+ startChar = cb.CharAt(pos);\r
+ }\r
+ if (!isascii(startChar)) {\r
+ while (pos < length && !isascii(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else if (IsLowerCase(startChar)) {\r
+ while (pos < length && IsLowerCase(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else if (IsUpperCase(startChar)) {\r
+ if (IsLowerCase(cb.CharAt(pos + 1))) {\r
+ ++pos;\r
+ while (pos < length && IsLowerCase(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else {\r
+ while (pos < length && IsUpperCase(cb.CharAt(pos)))\r
+ ++pos;\r
+ }\r
+ if (IsLowerCase(cb.CharAt(pos)) && IsUpperCase(cb.CharAt(pos - 1)))\r
+ --pos;\r
+ } else if (IsADigit(startChar)) {\r
+ while (pos < length && IsADigit(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else if (IsPunctuation(startChar)) {\r
+ while (pos < length && IsPunctuation(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else if (isspacechar(startChar)) {\r
+ while (pos < length && isspacechar(cb.CharAt(pos)))\r
+ ++pos;\r
+ } else {\r
+ ++pos;\r
+ }\r
+ return pos;\r
+}\r
+\r
+bool IsLineEndChar(char c) {\r
+ return (c == '\n' || c == '\r');\r
+}\r
+\r
+int Document::ExtendStyleRange(int pos, int delta, bool singleLine) {\r
+ int sStart = cb.StyleAt(pos);\r
+ if (delta < 0) {\r
+ while (pos > 0 && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) )\r
+ pos--;\r
+ pos++;\r
+ } else {\r
+ while (pos < (Length()) && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) )\r
+ pos++;\r
+ }\r
+ return pos;\r
+}\r
+\r
+static char BraceOpposite(char ch) {\r
+ switch (ch) {\r
+ case '(':\r
+ return ')';\r
+ case ')':\r
+ return '(';\r
+ case '[':\r
+ return ']';\r
+ case ']':\r
+ return '[';\r
+ case '{':\r
+ return '}';\r
+ case '}':\r
+ return '{';\r
+ case '<':\r
+ return '>';\r
+ case '>':\r
+ return '<';\r
+ default:\r
+ return '\0';\r
+ }\r
+}\r
+\r
+// TODO: should be able to extend styled region to find matching brace\r
+int Document::BraceMatch(int position, int /*maxReStyle*/) {\r
+ char chBrace = CharAt(position);\r
+ char chSeek = BraceOpposite(chBrace);\r
+ if (chSeek == '\0')\r
+ return - 1;\r
+ char styBrace = static_cast<char>(StyleAt(position) & stylingBitsMask);\r
+ int direction = -1;\r
+ if (chBrace == '(' || chBrace == '[' || chBrace == '{' || chBrace == '<')\r
+ direction = 1;\r
+ int depth = 1;\r
+ position = position + direction;\r
+ while ((position >= 0) && (position < Length())) {\r
+ position = MovePositionOutsideChar(position, direction);\r
+ char chAtPos = CharAt(position);\r
+ char styAtPos = static_cast<char>(StyleAt(position) & stylingBitsMask);\r
+ if ((position > GetEndStyled()) || (styAtPos == styBrace)) {\r
+ if (chAtPos == chBrace)\r
+ depth++;\r
+ if (chAtPos == chSeek)\r
+ depth--;\r
+ if (depth == 0)\r
+ return position;\r
+ }\r
+ position = position + direction;\r
+ }\r
+ return - 1;\r
+}\r
+\r
+/**\r
+ * Implementation of RegexSearchBase for the default built-in regular expression engine\r
+ */\r
+class BuiltinRegex : public RegexSearchBase {\r
+public:\r
+ BuiltinRegex(CharClassify *charClassTable) : search(charClassTable), substituted(NULL) {}\r
+\r
+ virtual ~BuiltinRegex() {\r
+ delete substituted;\r
+ }\r
+\r
+ virtual long FindText(Document *doc, int minPos, int maxPos, const char *s,\r
+ bool caseSensitive, bool word, bool wordStart, int flags,\r
+ int *length);\r
+\r
+ virtual const char *SubstituteByPosition(Document* doc, const char *text, int *length);\r
+\r
+private:\r
+ RESearch search;\r
+ char *substituted;\r
+};\r
+\r
+// Define a way for the Regular Expression code to access the document\r
+class DocumentIndexer : public CharacterIndexer {\r
+ Document *pdoc;\r
+ int end;\r
+public:\r
+ DocumentIndexer(Document *pdoc_, int end_) :\r
+ pdoc(pdoc_), end(end_) {\r
+ }\r
+\r
+ virtual ~DocumentIndexer() {\r
+ }\r
+\r
+ virtual char CharAt(int index) {\r
+ if (index < 0 || index >= end)\r
+ return 0;\r
+ else\r
+ return pdoc->CharAt(index);\r
+ }\r
+};\r
+\r
+long BuiltinRegex::FindText(Document *doc, int minPos, int maxPos, const char *s,\r
+ bool caseSensitive, bool, bool, int flags,\r
+ int *length) {\r
+ bool posix = (flags & SCFIND_POSIX) != 0;\r
+ int increment = (minPos <= maxPos) ? 1 : -1;\r
+\r
+ int startPos = minPos;\r
+ int endPos = maxPos;\r
+\r
+ // Range endpoints should not be inside DBCS characters, but just in case, move them.\r
+ startPos = doc->MovePositionOutsideChar(startPos, 1, false);\r
+ endPos = doc->MovePositionOutsideChar(endPos, 1, false);\r
+\r
+ const char *errmsg = search.Compile(s, *length, caseSensitive, posix);\r
+ if (errmsg) {\r
+ return -1;\r
+ }\r
+ // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))\r
+ // Replace first '.' with '-' in each property file variable reference:\r
+ // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))\r
+ // Replace: $(\1-\2)\r
+ int lineRangeStart = doc->LineFromPosition(startPos);\r
+ int lineRangeEnd = doc->LineFromPosition(endPos);\r
+ if ((increment == 1) &&\r
+ (startPos >= doc->LineEnd(lineRangeStart)) &&\r
+ (lineRangeStart < lineRangeEnd)) {\r
+ // the start position is at end of line or between line end characters.\r
+ lineRangeStart++;\r
+ startPos = doc->LineStart(lineRangeStart);\r
+ }\r
+ int pos = -1;\r
+ int lenRet = 0;\r
+ char searchEnd = s[*length - 1];\r
+ int lineRangeBreak = lineRangeEnd + increment;\r
+ for (int line = lineRangeStart; line != lineRangeBreak; line += increment) {\r
+ int startOfLine = doc->LineStart(line);\r
+ int endOfLine = doc->LineEnd(line);\r
+ if (increment == 1) {\r
+ if (line == lineRangeStart) {\r
+ if ((startPos != startOfLine) && (s[0] == '^'))\r
+ continue; // Can't match start of line if start position after start of line\r
+ startOfLine = startPos;\r
+ }\r
+ if (line == lineRangeEnd) {\r
+ if ((endPos != endOfLine) && (searchEnd == '$'))\r
+ continue; // Can't match end of line if end position before end of line\r
+ endOfLine = endPos;\r
+ }\r
+ } else {\r
+ if (line == lineRangeEnd) {\r
+ if ((endPos != startOfLine) && (s[0] == '^'))\r
+ continue; // Can't match start of line if end position after start of line\r
+ startOfLine = endPos;\r
+ }\r
+ if (line == lineRangeStart) {\r
+ if ((startPos != endOfLine) && (searchEnd == '$'))\r
+ continue; // Can't match end of line if start position before end of line\r
+ endOfLine = startPos;\r
+ }\r
+ }\r
+\r
+ DocumentIndexer di(doc, endOfLine);\r
+ int success = search.Execute(di, startOfLine, endOfLine);\r
+ if (success) {\r
+ pos = search.bopat[0];\r
+ lenRet = search.eopat[0] - search.bopat[0];\r
+ if (increment == -1) {\r
+ // Check for the last match on this line.\r
+ int repetitions = 1000; // Break out of infinite loop\r
+ while (success && (search.eopat[0] <= endOfLine) && (repetitions--)) {\r
+ success = search.Execute(di, pos+1, endOfLine);\r
+ if (success) {\r
+ if (search.eopat[0] <= minPos) {\r
+ pos = search.bopat[0];\r
+ lenRet = search.eopat[0] - search.bopat[0];\r
+ } else {\r
+ success = 0;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ *length = lenRet;\r
+ return pos;\r
+}\r
+\r
+const char *BuiltinRegex::SubstituteByPosition(Document* doc, const char *text, int *length) {\r
+ delete []substituted;\r
+ substituted = 0;\r
+ DocumentIndexer di(doc, doc->Length());\r
+ if (!search.GrabMatches(di))\r
+ return 0;\r
+ unsigned int lenResult = 0;\r
+ for (int i = 0; i < *length; i++) {\r
+ if (text[i] == '\\') {\r
+ if (text[i + 1] >= '1' && text[i + 1] <= '9') {\r
+ unsigned int patNum = text[i + 1] - '0';\r
+ lenResult += search.eopat[patNum] - search.bopat[patNum];\r
+ i++;\r
+ } else {\r
+ switch (text[i + 1]) {\r
+ case 'a':\r
+ case 'b':\r
+ case 'f':\r
+ case 'n':\r
+ case 'r':\r
+ case 't':\r
+ case 'v':\r
+ i++;\r
+ }\r
+ lenResult++;\r
+ }\r
+ } else {\r
+ lenResult++;\r
+ }\r
+ }\r
+ substituted = new char[lenResult + 1];\r
+ if (!substituted)\r
+ return 0;\r
+ char *o = substituted;\r
+ for (int j = 0; j < *length; j++) {\r
+ if (text[j] == '\\') {\r
+ if (text[j + 1] >= '1' && text[j + 1] <= '9') {\r
+ unsigned int patNum = text[j + 1] - '0';\r
+ unsigned int len = search.eopat[patNum] - search.bopat[patNum];\r
+ if (search.pat[patNum]) // Will be null if try for a match that did not occur\r
+ memcpy(o, search.pat[patNum], len);\r
+ o += len;\r
+ j++;\r
+ } else {\r
+ j++;\r
+ switch (text[j]) {\r
+ case 'a':\r
+ *o++ = '\a';\r
+ break;\r
+ case 'b':\r
+ *o++ = '\b';\r
+ break;\r
+ case 'f':\r
+ *o++ = '\f';\r
+ break;\r
+ case 'n':\r
+ *o++ = '\n';\r
+ break;\r
+ case 'r':\r
+ *o++ = '\r';\r
+ break;\r
+ case 't':\r
+ *o++ = '\t';\r
+ break;\r
+ case 'v':\r
+ *o++ = '\v';\r
+ break;\r
+ default:\r
+ *o++ = '\\';\r
+ j--;\r
+ }\r
+ }\r
+ } else {\r
+ *o++ = text[j];\r
+ }\r
+ }\r
+ *o = '\0';\r
+ *length = lenResult;\r
+ return substituted;\r
+}\r
+\r
+#ifndef SCI_OWNREGEX\r
+\r
+RegexSearchBase *CreateRegexSearch(CharClassify *charClassTable) {\r
+ return new BuiltinRegex(charClassTable);\r
+}\r
+\r
+#endif\r