// Scintilla source code edit control /** @file Document.cxx ** Text document that handles notifications, DBCS, styling, words and end of line. **/ // Copyright 1998-2003 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include "Platform.h" #include "Scintilla.h" #include "SVector.h" #include "CellBuffer.h" #include "Document.h" #include "RESearch.h" // This is ASCII specific but is safe with chars >= 0x80 static inline bool isspacechar(unsigned char ch) { return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d)); } static inline bool IsPunctuation(char ch) { return isascii(ch) && ispunct(ch); } static inline bool IsADigit(char ch) { return isascii(ch) && isdigit(ch); } static inline bool IsLowerCase(char ch) { return isascii(ch) && islower(ch); } static inline bool IsUpperCase(char ch) { return isascii(ch) && isupper(ch); } Document::Document() { refCount = 0; #ifdef unix eolMode = SC_EOL_LF; #else eolMode = SC_EOL_CRLF; #endif dbcsCodePage = 0; stylingBits = 5; stylingBitsMask = 0x1F; stylingMask = 0; SetDefaultCharClasses(true); endStyled = 0; styleClock = 0; enteredCount = 0; enteredReadOnlyCount = 0; tabInChars = 8; indentInChars = 0; actualIndentInChars = 8; useTabs = true; tabIndents = true; backspaceUnindents = false; watchers = 0; lenWatchers = 0; matchesValid = false; pre = 0; substituted = 0; } Document::~Document() { for (int i = 0; i < lenWatchers; i++) { watchers[i].watcher->NotifyDeleted(this, watchers[i].userData); } delete []watchers; watchers = 0; lenWatchers = 0; delete pre; pre = 0; delete []substituted; substituted = 0; } // Increase reference count and return its previous value. int Document::AddRef() { return refCount++; } // Decrease reference count and return its previous value. // Delete the document if reference count reaches zero. int Document::Release() { int curRefCount = --refCount; if (curRefCount == 0) delete this; return curRefCount; } void Document::SetSavePoint() { cb.SetSavePoint(); NotifySavePoint(true); } int Document::AddMark(int line, int markerNum) { int prev = cb.AddMark(line, markerNum); DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line); NotifyModified(mh); return prev; } void Document::AddMarkSet(int line, int valueSet) { unsigned int m = valueSet; for (int i = 0; m; i++, m >>= 1) if (m & 1) cb.AddMark(line, i); DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line); NotifyModified(mh); } void Document::DeleteMark(int line, int markerNum) { cb.DeleteMark(line, markerNum); DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line); NotifyModified(mh); } void Document::DeleteMarkFromHandle(int markerHandle) { cb.DeleteMarkFromHandle(markerHandle); DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0); NotifyModified(mh); } void Document::DeleteAllMarks(int markerNum) { cb.DeleteAllMarks(markerNum); DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0); NotifyModified(mh); } int Document::LineStart(int line) { return cb.LineStart(line); } int Document::LineEnd(int line) { if (line == LinesTotal() - 1) { return LineStart(line + 1); } else { int position = LineStart(line + 1) - 1; // When line terminator is CR+LF, may need to go back one more if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) { position--; } return position; } } int Document::LineFromPosition(int pos) { return cb.LineFromPosition(pos); } int Document::LineEndPosition(int position) { return LineEnd(LineFromPosition(position)); } int Document::VCHomePosition(int position) { int line = LineFromPosition(position); int startPosition = LineStart(line); int endLine = LineStart(line + 1) - 1; int startText = startPosition; while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) ) startText++; if (position == startText) return startPosition; else return startText; } int Document::SetLevel(int line, int level) { int prev = cb.SetLevel(line, level); if (prev != level) { DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0); mh.line = line; mh.foldLevelNow = level; mh.foldLevelPrev = prev; NotifyModified(mh); } return prev; } static bool IsSubordinate(int levelStart, int levelTry) { if (levelTry & SC_FOLDLEVELWHITEFLAG) return true; else return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK); } int Document::GetLastChild(int lineParent, int level) { if (level == -1) level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK; int maxLine = LinesTotal(); int lineMaxSubord = lineParent; while (lineMaxSubord < maxLine - 1) { EnsureStyledTo(LineStart(lineMaxSubord + 2)); if (!IsSubordinate(level, GetLevel(lineMaxSubord + 1))) break; lineMaxSubord++; } if (lineMaxSubord > lineParent) { if (level > (GetLevel(lineMaxSubord + 1) & SC_FOLDLEVELNUMBERMASK)) { // Have chewed up some whitespace that belongs to a parent so seek back if (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG) { lineMaxSubord--; } } } return lineMaxSubord; } int Document::GetFoldParent(int line) { int level = GetLevel(line) & SC_FOLDLEVELNUMBERMASK; int lineLook = line - 1; while ((lineLook > 0) && ( (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) || ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level)) ) { lineLook--; } if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) && ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) { return lineLook; } else { return -1; } } int Document::ClampPositionIntoDocument(int pos) { return Platform::Clamp(pos, 0, Length()); } bool Document::IsCrLf(int pos) { if (pos < 0) return false; if (pos >= (Length() - 1)) return false; return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n'); } static const int maxBytesInDBCSCharacter=5; int Document::LenChar(int pos) { if (pos < 0) { return 1; } else if (IsCrLf(pos)) { return 2; } else if (SC_CP_UTF8 == dbcsCodePage) { unsigned char ch = static_cast(cb.CharAt(pos)); if (ch < 0x80) return 1; int len = 2; if (ch >= (0x80 + 0x40 + 0x20)) len = 3; int lengthDoc = Length(); if ((pos + len) > lengthDoc) return lengthDoc -pos; else return len; } else if (dbcsCodePage) { char mbstr[maxBytesInDBCSCharacter+1]; int i; for (i=0; i= Length()) return Length(); // PLATFORM_ASSERT(pos > 0 && pos < Length()); if (checkLineEnd && IsCrLf(pos - 1)) { if (moveDir > 0) return pos + 1; else return pos - 1; } // Not between CR and LF if (dbcsCodePage) { if (SC_CP_UTF8 == dbcsCodePage) { unsigned char ch = static_cast(cb.CharAt(pos)); while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) { // ch is a trail byte if (moveDir > 0) pos++; else pos--; ch = static_cast(cb.CharAt(pos)); } } else { // Anchor DBCS calculations at start of line because start of line can // not be a DBCS trail byte. int posCheck = LineStart(LineFromPosition(pos)); while (posCheck < pos) { char mbstr[maxBytesInDBCSCharacter+1]; int i; for(i=0;i pos) { if (moveDir > 0) { return posCheck + mbsize; } else { return posCheck; } } posCheck += mbsize; } } } return pos; } void Document::ModifiedAt(int pos) { if (endStyled > pos) endStyled = pos; } void Document::CheckReadOnly() { if (cb.IsReadOnly() && enteredReadOnlyCount == 0) { enteredReadOnlyCount++; NotifyModifyAttempt(); enteredReadOnlyCount--; } } // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt. // SetStyleAt does not change the persistent state of a document // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number bool Document::DeleteChars(int pos, int len) { if (len == 0) return false; if ((pos + len) > Length()) return false; CheckReadOnly(); if (enteredCount != 0) { return false; } else { enteredCount++; if (!cb.IsReadOnly()) { NotifyModified( DocModification( SC_MOD_BEFOREDELETE | SC_PERFORMED_USER, pos, len, 0, 0)); int prevLinesTotal = LinesTotal(); bool startSavePoint = cb.IsSavePoint(); const char *text = cb.DeleteChars(pos * 2, len * 2); if (startSavePoint && cb.IsCollectingUndo()) NotifySavePoint(!startSavePoint); if ((pos < Length()) || (pos == 0)) ModifiedAt(pos); else ModifiedAt(pos-1); NotifyModified( DocModification( SC_MOD_DELETETEXT | SC_PERFORMED_USER, pos, len, LinesTotal() - prevLinesTotal, text)); } enteredCount--; } return !cb.IsReadOnly(); } /** * Insert a styled string (char/style pairs) with a length. */ bool Document::InsertStyledString(int position, char *s, int insertLength) { CheckReadOnly(); if (enteredCount != 0) { return false; } else { enteredCount++; if (!cb.IsReadOnly()) { NotifyModified( DocModification( SC_MOD_BEFOREINSERT | SC_PERFORMED_USER, position / 2, insertLength / 2, 0, s)); int prevLinesTotal = LinesTotal(); bool startSavePoint = cb.IsSavePoint(); const char *text = cb.InsertString(position, s, insertLength); if (startSavePoint && cb.IsCollectingUndo()) NotifySavePoint(!startSavePoint); ModifiedAt(position / 2); NotifyModified( DocModification( SC_MOD_INSERTTEXT | SC_PERFORMED_USER, position / 2, insertLength / 2, LinesTotal() - prevLinesTotal, text)); } enteredCount--; } return !cb.IsReadOnly(); } int Document::Undo() { int newPos = -1; CheckReadOnly(); if (enteredCount == 0) { enteredCount++; if (!cb.IsReadOnly()) { bool startSavePoint = cb.IsSavePoint(); bool multiLine = false; int steps = cb.StartUndo(); //Platform::DebugPrintf("Steps=%d\n", steps); for (int step = 0; step < steps; step++) { const int prevLinesTotal = LinesTotal(); const Action &action = cb.GetUndoStep(); if (action.at == removeAction) { NotifyModified(DocModification( SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action)); } else { NotifyModified(DocModification( SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action)); } cb.PerformUndoStep(); int cellPosition = action.position / 2; ModifiedAt(cellPosition); newPos = cellPosition; int modFlags = SC_PERFORMED_UNDO; // With undo, an insertion action becomes a deletion notification if (action.at == removeAction) { newPos += action.lenData; modFlags |= SC_MOD_INSERTTEXT; } else { modFlags |= SC_MOD_DELETETEXT; } if (steps > 1) modFlags |= SC_MULTISTEPUNDOREDO; const int linesAdded = LinesTotal() - prevLinesTotal; if (linesAdded != 0) multiLine = true; if (step == steps - 1) { modFlags |= SC_LASTSTEPINUNDOREDO; if (multiLine) modFlags |= SC_MULTILINEUNDOREDO; } NotifyModified(DocModification(modFlags, cellPosition, action.lenData, linesAdded, action.data)); } bool endSavePoint = cb.IsSavePoint(); if (startSavePoint != endSavePoint) NotifySavePoint(endSavePoint); } enteredCount--; } return newPos; } int Document::Redo() { int newPos = -1; CheckReadOnly(); if (enteredCount == 0) { enteredCount++; if (!cb.IsReadOnly()) { bool startSavePoint = cb.IsSavePoint(); bool multiLine = false; int steps = cb.StartRedo(); for (int step = 0; step < steps; step++) { const int prevLinesTotal = LinesTotal(); const Action &action = cb.GetRedoStep(); if (action.at == insertAction) { NotifyModified(DocModification( SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action)); } else { NotifyModified(DocModification( SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action)); } cb.PerformRedoStep(); ModifiedAt(action.position / 2); newPos = action.position / 2; int modFlags = SC_PERFORMED_REDO; if (action.at == insertAction) { newPos += action.lenData; modFlags |= SC_MOD_INSERTTEXT; } else { modFlags |= SC_MOD_DELETETEXT; } if (steps > 1) modFlags |= SC_MULTISTEPUNDOREDO; const int linesAdded = LinesTotal() - prevLinesTotal; if (linesAdded != 0) multiLine = true; if (step == steps - 1) { modFlags |= SC_LASTSTEPINUNDOREDO; if (multiLine) modFlags |= SC_MULTILINEUNDOREDO; } NotifyModified( DocModification(modFlags, action.position / 2, action.lenData, linesAdded, action.data)); } bool endSavePoint = cb.IsSavePoint(); if (startSavePoint != endSavePoint) NotifySavePoint(endSavePoint); } enteredCount--; } return newPos; } /** * Insert a single character. */ bool Document::InsertChar(int pos, char ch) { char chs[2]; chs[0] = ch; chs[1] = 0; return InsertStyledString(pos*2, chs, 2); } /** * Insert a null terminated string. */ bool Document::InsertString(int position, const char *s) { return InsertString(position, s, strlen(s)); } /** * Insert a string with a length. */ bool Document::InsertString(int position, const char *s, size_t insertLength) { bool changed = false; if (insertLength > 0) { char *sWithStyle = new char[insertLength * 2]; if (sWithStyle) { for (size_t i = 0; i < insertLength; i++) { sWithStyle[i*2] = s[i]; sWithStyle[i*2 + 1] = 0; } changed = InsertStyledString(position*2, sWithStyle, static_cast(insertLength*2)); delete []sWithStyle; } } return changed; } void Document::ChangeChar(int pos, char ch) { DeleteChars(pos, 1); InsertChar(pos, ch); } void Document::DelChar(int pos) { DeleteChars(pos, LenChar(pos)); } void Document::DelCharBack(int pos) { if (pos <= 0) { return; } else if (IsCrLf(pos - 2)) { DeleteChars(pos - 2, 2); } else if (dbcsCodePage) { int startChar = MovePositionOutsideChar(pos - 1, -1, false); DeleteChars(startChar, pos - startChar); } else { DeleteChars(pos - 1, 1); } } static bool isindentchar(char ch) { return (ch == ' ') || (ch == '\t'); } static int NextTab(int pos, int tabSize) { return ((pos / tabSize) + 1) * tabSize; } static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) { length--; // ensure space for \0 if (!insertSpaces) { while ((indent >= tabSize) && (length > 0)) { *linebuf++ = '\t'; indent -= tabSize; length--; } } while ((indent > 0) && (length > 0)) { *linebuf++ = ' '; indent--; length--; } *linebuf = '\0'; } int Document::GetLineIndentation(int line) { int indent = 0; if ((line >= 0) && (line < LinesTotal())) { int lineStart = LineStart(line); int length = Length(); for (int i = lineStart;i < length;i++) { char ch = cb.CharAt(i); if (ch == ' ') indent++; else if (ch == '\t') indent = NextTab(indent, tabInChars); else return indent; } } return indent; } void Document::SetLineIndentation(int line, int indent) { int indentOfLine = GetLineIndentation(line); HTTP/1.1 200 OK Connection: close Connection: keep-alive Content-Disposition: inline; filename="Document.cxx" Content-Disposition: inline; filename="Document.cxx" Content-Length: 40256 Content-Length: 40256 Content-Security-Policy: default-src 'none' Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Type: text/plain; charset=UTF-8 Date: Sat, 18 Oct 2025 05:18:22 UTC ETag: "e25638933a32b12021d297dd55e98b4cd97710c3" ETag: "e25638933a32b12021d297dd55e98b4cd97710c3" Expires: Tue, 16 Oct 2035 05:18:22 GMT Expires: Tue, 16 Oct 2035 05:18:22 GMT Last-Modified: Sat, 18 Oct 2025 05:18:22 GMT Last-Modified: Sat, 18 Oct 2025 05:18:22 GMT Server: OpenBSD httpd Server: OpenBSD httpd X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff // Scintilla source code edit control /** @file Document.cxx ** Text document that handles notifications, DBCS, styling, words and end of line. **/ // Copyright 1998-2003 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include "Platform.h" #include "Scintilla.h" #include "SVector.h" #include "CellBuffer.h" #include "Document.h" #include "RESearch.h" // This is ASCII specific but is safe with chars >= 0x80 static inline bool isspacechar(unsigned char ch) { return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d)); } static inline bool IsPunctuation(char ch) { return isascii(ch) && ispunct(ch); } static inline bool IsADigit(char ch) { return isascii(ch) && isdigit(ch); } static inline bool IsLowerCase(char ch) { return isascii(ch) && islower(ch); } static inline bool IsUpperCase(char ch) { return isascii(ch) && isupper(ch); } Document::Document() { refCount = 0; #ifdef unix eolMode = SC_EOL_LF; #else eolMode = SC_EOL_CRLF; #endif dbcsCodePage = 0; stylingBits = 5; stylingBitsMask = 0x1F; stylingMask = 0; SetDefaultCharClasses(true); endStyled = 0; styleClock = 0; enteredCount = 0; enteredReadOnlyCount = 0; tabInChars = 8; indentInChars = 0; actualIndentInChars = 8; useTabs = true; tabIndents = true; backspaceUnindents = false; watchers = 0; lenWatchers = 0; matchesValid = false; pre = 0; substituted = 0; } Document::~Document() { for (int i = 0; i < lenWatchers; i++) { watchers[i].watcher->NotifyDeleted(this, watchers[i].userData); } delete []watchers; watchers = 0; lenWatchers = 0; delete pre; pre = 0; delete []substituted; substituted = 0; } // Increase reference count and return its previous value. int Document::AddRef() { return refCount++; } // Decrease reference count and return its previous value. // Delete the document if reference count reaches zero. int Document::Release() { int curRefCount = --refCount; if (curRefCount == 0) delete this; return curRefCount; } void Document::SetSavePoint() { cb.SetSavePoint(); NotifySavePoint(true); } int Document::AddMark(int line, int markerNum) { int prev = cb.AddMark(line, markerNum); DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line); NotifyModified(mh); return prev; } void Document::AddMarkSet(int line, int valueSet) { unsigned int m = valueSet; for (int i = 0; m; i++, m >>= 1) if (m & 1) cb.AddMark(line, i); DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line); NotifyModified(mh); } void Document::DeleteMark(int line, int markerNum) { cb.DeleteMark(line, markerNum); DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line); NotifyModified(mh); } void Document::DeleteMarkFromHandle(int markerHandle) { cb.DeleteMarkFromHandle(markerHandle); DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0); NotifyModified(mh); } void Document::DeleteAllMarks(int markerNum) { cb.DeleteAllMarks(markerNum); DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0); NotifyModified(mh); } int Document::LineStart(int line) { return cb.LineStart(line); } int Document::LineEnd(int line) { if (line == LinesTotal() - 1) { return LineStart(line + 1); } else { int position = LineStart(line + 1) - 1; // When line terminator is CR+LF, may need to go back one more if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) { position--; } return position; } } int Document::LineFromPosition(int pos) { return cb.LineFromPosition(pos); } int Document::LineEndPosition(int position) { return LineEnd(LineFromPosition(position)); } int Document::VCHomePosition(int position) { int line = LineFromPosition(position); int startPosition = LineStart(line); int endLine = LineStart(line + 1) - 1; int startText = startPosition; while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) ) startText++; if (position == startText) return startPosition; else return startText; } int Document::SetLevel(int line, int level) { int prev = cb.SetLevel(line, level); if (prev != level) { DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0); mh.line = line; mh.foldLevelNow = level; mh.foldLevelPrev = prev; NotifyModified(mh); } return prev; } static bool IsSubordinate(int levelStart, int levelTry) { if (levelTry & SC_FOLDLEVELWHITEFLAG) return true; else return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK); } int Document::GetLastChild(int lineParent, int level) { if (level == -1) level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK; int maxLine = LinesTotal(); int lineMaxSubord = lineParent; while (lineMaxSubord < maxLine - 1) { EnsureStyledTo(LineStart(lineMaxSubord + 2)); if (!IsSubordinate(level, GetLevel(lineMaxSubord + 1))) break; lineMaxSubord++; } if (lineMaxSubord > lineParent) { if (level > (GetLevel(lineMaxSubord + 1) & SC_FOLDLEVELNUMBERMASK)) { // Have chewed up some whitespace that belongs to a parent so seek back if (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG) { lineMaxSubord--; } } } return lineMaxSubord; } int Document::GetFoldParent(int line) { int level = GetLevel(line) & SC_FOLDLEVELNUMBERMASK; int lineLook = line - 1; while ((lineLook > 0) && ( (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) || ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level)) ) { lineLook--; } if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) && ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) { return lineLook; } else { return -1; } } int Document::ClampPositionIntoDocument(int pos) { return Platform::Clamp(pos, 0, Length()); } bool Document::IsCrLf(int pos) { if (pos < 0) return false; if (pos >= (Length() - 1)) return false; return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n'); } static const int maxBytesInDBCSCharacter=5; int Document::LenChar(int pos) { if (pos < 0) { return 1; } else if (IsCrLf(pos)) { return 2; } else if (SC_CP_UTF8 == dbcsCodePage) { unsigned char ch = static_cast(cb.CharAt(pos)); if (ch < 0x80) return 1; int len = 2; if (ch >= (0x80 + 0x40 + 0x20)) len = 3; int lengthDoc = Length(); if ((pos + len) > lengthDoc) return lengthDoc -pos; else return len; } else if (dbcsCodePage) { char mbstr[maxBytesInDBCSCharacter+1]; int i; for (i=0; i= Length()) return Length(); // PLATFORM_ASSERT(pos > 0 && pos < Length()); if (checkLineEnd && IsCrLf(pos - 1)) { if (moveDir > 0) return pos + 1; else return pos - 1; } // Not between CR and LF if (dbcsCodePage) { if (SC_CP_UTF8 == dbcsCodePage) { unsigned char ch = static_cast(cb.CharAt(pos)); while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) { // ch is a trail byte if (moveDir > 0) pos++; else pos--; ch = static_cast(cb.CharAt(pos)); } } else { // Anchor DBCS calculations at start of line because start of line can // not be a DBCS trail byte. int posCheck = LineStart(LineFromPosition(pos)); while (posCheck < pos) { char mbstr[maxBytesInDBCSCharacter+1]; int i; for(i=0;i pos) { if (moveDir > 0) { return posCheck + mbsize; } else { return posCheck; } } posCheck += mbsize; } } } return pos; } void Document::ModifiedAt(int pos) { if (endStyled > pos) endStyled = pos; } void Document::CheckReadOnly() { if (cb.IsReadOnly() && enteredReadOnlyCount == 0) { enteredReadOnlyCount++; NotifyModifyAttempt(); enteredReadOnlyCount--; } } // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt. // SetStyleAt does not change the persistent state of a document // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number bool Document::DeleteChars(int pos, int len) { if (len == 0) return false; if ((pos + len) > Length()) return false; CheckReadOnly(); if (enteredCount != 0) { return false; } else { enteredCount++; if (!cb.IsReadOnly()) { NotifyModified( DocModification( SC_MOD_BEFOREDELETE | SC_PERFORMED_USER, pos, len, 0, 0)); int prevLinesTotal = LinesTotal(); bool startSavePoint = cb.IsSavePoint(); const char *text = cb.DeleteChars(pos * 2, len * 2); if (startSavePoint && cb.IsCollectingUndo()) NotifySavePoint(!startSavePoint); if ((pos < Length()) || (pos == 0)) ModifiedAt(pos); else ModifiedAt(pos-1); NotifyModified( DocModification( SC_MOD_DELETETEXT | SC_PERFORMED_USER, pos, len, LinesTotal() - prevLinesTotal, text)); } enteredCount--; } return !cb.IsReadOnly(); } /** * Insert a styled string (char/style pairs) with a length. */ bool Document::InsertStyledString(int position, char *s, int insertLength) { CheckReadOnly(); if (enteredCount != 0) { return false; } else { enteredCount++; if (!cb.IsReadOnly()) { NotifyModified( DocModification( SC_MOD_BEFOREINSERT | SC_PERFORMED_USER, position / 2, insertLength / 2, 0, s)); int prevLinesTotal = LinesTotal(); bool startSavePoint = cb.IsSavePoint(); const char *text = cb.InsertString(position, s, insertLength); if (startSavePoint && cb.IsCollectingUndo()) NotifySavePoint(!startSavePoint); ModifiedAt(position / 2); NotifyModified( DocModification( SC_MOD_INSERTTEXT | SC_PERFORMED_USER, position / 2, insertLength / 2, LinesTotal() - prevLinesTotal, text)); } enteredCount--; } return !cb.IsReadOnly(); } int Document::Undo() { int newPos = -1; CheckReadOnly(); if (enteredCount == 0) { enteredCount++; if (!cb.IsReadOnly()) { bool startSavePoint = cb.IsSavePoint(); bool multiLine = false; int steps = cb.StartUndo(); //Platform::DebugPrintf("Steps=%d\n", steps); for (int step = 0; step < steps; step++) { const int prevLinesTotal = LinesTotal(); const Action &action = cb.GetUndoStep(); if (action.at == removeAction) { NotifyModified(DocModification( SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action)); } else { NotifyModified(DocModification( SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action)); } cb.PerformUndoStep(); int cellPosition = action.position / 2; ModifiedAt(cellPosition); newPos = cellPosition; int modFlags = SC_PERFORMED_UNDO; // With undo, an insertion action becomes a deletion notification if (action.at == removeAction) { newPos += action.lenData; modFlags |= SC_MOD_INSERTTEXT; } else { modFlags |= SC_MOD_DELETETEXT; } if (steps > 1) modFlags |= SC_MULTISTEPUNDOREDO; const int linesAdded = LinesTotal() - prevLinesTotal; if (linesAdded != 0) multiLine = true; if (step == steps - 1) { modFlags |= SC_LASTSTEPINUNDOREDO; if (multiLine) modFlags |= SC_MULTILINEUNDOREDO; } NotifyModified(DocModification(modFlags, cellPosition, action.lenData, linesAdded, action.data)); } bool endSavePoint = cb.IsSavePoint(); if (startSavePoint != endSavePoint) NotifySavePoint(endSavePoint); } enteredCount--; } return newPos; } int Document::Redo() { int newPos = -1; CheckReadOnly(); if (enteredCount == 0) { enteredCount++; if (!cb.IsReadOnly()) { bool startSavePoint = cb.IsSavePoint(); bool multiLine = false; int steps = cb.StartRedo(); for (int step = 0; step < steps; step++) { const int prevLinesTotal = LinesTotal(); const Action &action = cb.GetRedoStep(); if (action.at == insertAction) { NotifyModified(DocModification( SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action)); } else { NotifyModified(DocModification( SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action)); } cb.PerformRedoStep(); ModifiedAt(action.position / 2); newPos = action.position / 2; int modFlags = SC_PERFORMED_REDO; if (action.at == insertAction) { newPos += action.lenData; modFlags |= SC_MOD_INSERTTEXT; } else { modFlags |= SC_MOD_DELETETEXT; } if (steps > 1) modFlags |= SC_MULTISTEPUNDOREDO; const int linesAdded = LinesTotal() - prevLinesTotal; if (linesAdded != 0) multiLine = true; if (step == steps - 1) { modFlags |= SC_LASTSTEPINUNDOREDO; if (multiLine) modFlags |= SC_MULTILINEUNDOREDO; } NotifyModified( DocModification(modFlags, action.position / 2, action.lenData, linesAdded, action.data)); } bool endSavePoint = cb.IsSavePoint(); if (startSavePoint != endSavePoint) NotifySavePoint(endSavePoint); } enteredCount--; } return newPos; } /** * Insert a single character. */ bool Document::InsertChar(int pos, char ch) { char chs[2]; chs[0] = ch; chs[1] = 0; return InsertStyledString(pos*2, chs, 2); } /** * Insert a null terminated string. */ bool Document::InsertString(int position, const char *s) { return InsertString(position, s, strlen(s)); } /** * Insert a string with a length. */ bool Document::InsertString(int position, const char *s, size_t insertLength) { bool changed = false; if (insertLength > 0) { char *sWithStyle = new char[insertLength * 2]; if (sWithStyle) { for (size_t i = 0; i < insertLength; i++) { sWithStyle[i*2] = s[i]; sWithStyle[i*2 + 1] = 0; } changed = InsertStyledString(position*2, sWithStyle, static_cast(insertLength*2)); delete []sWithStyle; } } return changed; } void Document::ChangeChar(int pos, char ch) { DeleteChars(pos, 1); InsertChar(pos, ch); } void Document::DelChar(int pos) { DeleteChars(pos, LenChar(pos)); } void Document::DelCharBack(int pos) { if (pos <= 0) { return; } else if (IsCrLf(pos - 2)) { DeleteChars(pos - 2, 2); } else if (dbcsCodePage) { int startChar = MovePositionOutsideChar(pos - 1, -1, false); DeleteChars(startChar, pos - startChar); } else { DeleteChars(pos - 1, 1); } } static bool isindentchar(char ch) { return (ch == ' ') || (ch == '\t'); } static int NextTab(int pos, int tabSize) { return ((pos / tabSize) + 1) * tabSize; } static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) { length--; // ensure space for \0 if (!insertSpaces) { while ((indent >= tabSize) && (length > 0)) { *linebuf++ = '\t'; indent -= tabSize; length--; } } while ((indent > 0) && (length > 0)) { *linebuf++ = ' '; indent--; length--; } *linebuf = '\0'; } int Document::GetLineIndentation(int line) { int indent = 0; if ((line >= 0) && (line < LinesTotal())) { int lineStart = LineStart(line); int length = Length(); for (int i = lineStart;i < length;i++) { char ch = cb.CharAt(i); if (ch == ' ') indent++; else if (ch == '\t') indent = NextTab(indent, tabInChars); else return indent; } } return indent; } void Document::SetLineIndentation(int line, int indent) { int indentOfLine = GetLineIndentation(line); if (indent < 0) indent = 0; if (indent != indentOfLine) { char linebuf[1000]; CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs); int thisLineStart = LineStart(line); int indentPos = GetLineIndentPosition(line); BeginUndoAction(); DeleteChars(thisLineStart, indentPos - thisLineStart); InsertString(thisLineStart, linebuf); EndUndoAction(); } } int Document::GetLineIndentPosition(int line) { if (line < 0) return 0; int pos = LineStart(line); int length = Length(); while ((pos < length) && isindentchar(cb.CharAt(pos))) { pos++; } return pos; } int Document::GetColumn(int pos) { int column = 0; int line = LineFromPosition(pos); if ((line >= 0) && (line < LinesTotal())) { for (int i = LineStart(line);i < pos;) { char ch = cb.CharAt(i); if (ch == '\t') { column = NextTab(column, tabInChars); i++; } else if (ch == '\r') { return column; } else if (ch == '\n') { return column; } else { column++; i = MovePositionOutsideChar(i + 1, 1); } } } return column; } int Document::FindColumn(int line, int column) { int position = LineStart(line); int columnCurrent = 0; if ((line >= 0) && (line < LinesTotal())) { while ((columnCurrent < column) && (position < Length())) { char ch = cb.CharAt(position); if (ch == '\t') { columnCurrent = NextTab(columnCurrent, tabInChars); position++; } else if (ch == '\r') { return position; } else if (ch == '\n') { return position; } else { columnCurrent++; position = MovePositionOutsideChar(position + 1, 1); } } } return position; } void Document::Indent(bool forwards, int lineBottom, int lineTop) { // Dedent - suck white space off the front of the line to dedent by equivalent of a tab for (int line = lineBottom; line >= lineTop; line--) { int indentOfLine = GetLineIndentation(line); if (forwards) { if (LineStart(line) < LineEnd(line)) { SetLineIndentation(line, indentOfLine + IndentSize()); } } else { SetLineIndentation(line, indentOfLine - IndentSize()); } } } // Convert line endings for a piece of text to a particular mode. // Stop at len or when a NUL is found. // Caller must delete the returned pointer. char *Document::TransformLineEnds(int *pLenOut, const char *s, size_t len, int eolMode) { char *dest = new char[2 * len + 1]; const char *sptr = s; char *dptr = dest; for (size_t i = 0; (i < len) && (*sptr != '\0'); i++) { if (*sptr == '\n' || *sptr == '\r') { if (eolMode == SC_EOL_CR) { *dptr++ = '\r'; } else if (eolMode == SC_EOL_LF) { *dptr++ = '\n'; } else { // eolMode == SC_EOL_CRLF *dptr++ = '\r'; *dptr++ = '\n'; } if ((*sptr == '\r') && (i+1 < len) && (*(sptr+1) == '\n')) { i++; sptr++; } sptr++; } else { *dptr++ = *sptr++; } } *dptr++ = '\0'; *pLenOut = (dptr - dest) - 1; return dest; } void Document::ConvertLineEnds(int eolModeSet) { BeginUndoAction(); for (int pos = 0; pos < Length(); pos++) { if (cb.CharAt(pos) == '\r') { if (cb.CharAt(pos + 1) == '\n') { // CRLF if (eolModeSet == SC_EOL_CR) { DeleteChars(pos + 1, 1); // Delete the LF } else if (eolModeSet == SC_EOL_LF) { DeleteChars(pos, 1); // Delete the CR } else { pos++; } } else { // CR if (eolModeSet == SC_EOL_CRLF) { InsertString(pos + 1, "\n", 1); // Insert LF pos++; } else if (eolModeSet == SC_EOL_LF) { InsertString(pos, "\n", 1); // Insert LF DeleteChars(pos + 1, 1); // Delete CR } } } else if (cb.CharAt(pos) == '\n') { // LF if (eolModeSet == SC_EOL_CRLF) { InsertString(pos, "\r", 1); // Insert CR pos++; } else if (eolModeSet == SC_EOL_CR) { InsertString(pos, "\r", 1); // Insert CR DeleteChars(pos + 1, 1); // Delete LF } } } EndUndoAction(); } bool Document::IsWhiteLine(int line) { int currentChar = LineStart(line); int endLine = LineEnd(line); while (currentChar < endLine) { if (cb.CharAt(currentChar) != ' ' && cb.CharAt(currentChar) != '\t') { return false; } ++currentChar; } return true; } int Document::ParaUp(int pos) { int line = LineFromPosition(pos); line--; while (line >= 0 && IsWhiteLine(line)) { // skip empty lines line--; } while (line >= 0 && !IsWhiteLine(line)) { // skip non-empty lines line--; } line++; return LineStart(line); } int Document::ParaDown(int pos) { int line = LineFromPosition(pos); while (line < LinesTotal() && !IsWhiteLine(line)) { // skip non-empty lines line++; } while (line < LinesTotal() && IsWhiteLine(line)) { // skip empty lines line++; } if (line < LinesTotal()) return LineStart(line); else // end of a document return LineEnd(line-1); } Document::charClassification Document::WordCharClass(unsigned char ch) { if ((SC_CP_UTF8 == dbcsCodePage) && (ch >= 0x80)) return ccWord; return charClass[ch]; } /** * Used by commmands that want to select whole words. * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0. */ int Document::ExtendWordSelect(int pos, int delta, bool onlyWordCharacters) { charClassification ccStart = ccWord; if (delta < 0) { if (!onlyWordCharacters) ccStart = WordCharClass(cb.CharAt(pos-1)); while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart)) pos--; } else { if (!onlyWordCharacters) ccStart = WordCharClass(cb.CharAt(pos)); while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart)) pos++; } return MovePositionOutsideChar(pos, delta); } /** * Find the start of the next word in either a forward (delta >= 0) or backwards direction * (delta < 0). * This is looking for a transition between character classes although there is also some * additional movement to transit white space. * Used by cursor movement by word commands. */ int Document::NextWordStart(int pos, int delta) { if (delta < 0) { while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccSpace)) pos--; if (pos > 0) { charClassification ccStart = WordCharClass(cb.CharAt(pos-1)); while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart)) { pos--; } } } else { charClassification ccStart = WordCharClass(cb.CharAt(pos)); while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart)) pos++; while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccSpace)) pos++; } return pos; } /** * Find the end of the next word in either a forward (delta >= 0) or backwards direction * (delta < 0). * This is looking for a transition between character classes although there is also some * additional movement to transit