// Scintilla source code edit control /** @file CellBuffer.cxx ** Manages a buffer of cells. **/ // Copyright 1998-2001 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ScintillaTypes.h" #include "Debugging.h" #include "Position.h" #include "SplitVector.h" #include "Partitioning.h" #include "RunStyles.h" #include "SparseVector.h" #include "ChangeHistory.h" #include "CellBuffer.h" #include "UniConversion.h" namespace Scintilla::Internal { struct CountWidths { // Measures the number of characters in a string divided into those // from the Base Multilingual Plane and those from other planes. Sci::Position countBasePlane; Sci::Position countOtherPlanes; explicit CountWidths(Sci::Position countBasePlane_=0, Sci::Position countOtherPlanes_=0) noexcept : countBasePlane(countBasePlane_), countOtherPlanes(countOtherPlanes_) { } CountWidths operator-() const noexcept { return CountWidths(-countBasePlane , -countOtherPlanes); } Sci::Position WidthUTF32() const noexcept { // All code points take one code unit in UTF-32. return countBasePlane + countOtherPlanes; } Sci::Position WidthUTF16() const noexcept { // UTF-16 takes 2 code units for other planes return countBasePlane + 2 * countOtherPlanes; } void CountChar(int lenChar) noexcept { if (lenChar == 4) { countOtherPlanes++; } else { countBasePlane++; } } }; class ILineVector { public: virtual void Init() = 0; virtual void SetPerLine(PerLine *pl) noexcept = 0; virtual void InsertText(Sci::Line line, Sci::Position delta) noexcept = 0; virtual void InsertLine(Sci::Line line, Sci::Position position, bool lineStart) = 0; virtual void InsertLines(Sci::Line line, const Sci::Position *positions, size_t lines, bool lineStart) = 0; virtual void SetLineStart(Sci::Line line, Sci::Position position) noexcept = 0; virtual void RemoveLine(Sci::Line line) = 0; virtual Sci::Line Lines() const noexcept = 0; virtual void AllocateLines(Sci::Line lines) = 0; virtual Sci::Line LineFromPosition(Sci::Position pos) const noexcept = 0; virtual Sci::Position LineStart(Sci::Line line) const noexcept = 0; virtual void InsertCharacters(Sci::Line line, CountWidths delta) noexcept = 0; virtual void SetLineCharactersWidth(Sci::Line line, CountWidths width) noexcept = 0; virtual Scintilla::LineCharacterIndexType LineCharacterIndex() const noexcept = 0; virtual bool AllocateLineCharacterIndex(Scintilla::LineCharacterIndexType lineCharacterIndex, Sci::Line lines) = 0; virtual bool ReleaseLineCharacterIndex(Scintilla::LineCharacterIndexType lineCharacterIndex) = 0; virtual Sci::Position IndexLineStart(Sci::Line line, Scintilla::LineCharacterIndexType lineCharacterIndex) const noexcept = 0; virtual Sci::Line LineFromPositionIndex(Sci::Position pos, Scintilla::LineCharacterIndexType lineCharacterIndex) const noexcept = 0; virtual ~ILineVector() {} }; } using namespace Scintilla; using namespace Scintilla::Internal; template class LineStartIndex { // line_cast(): cast Sci::Line to either 32-bit or 64-bit value // This avoids warnings from Visual C++ Code Analysis and shortens code static constexpr POS line_cast(Sci::Line pos) noexcept { return static_cast(pos); } public: int refCount; Partitioning starts; LineStartIndex() : refCount(0), starts(4) { // Minimal initial allocation } bool Allocate(Sci::Line lines) { refCount++; Sci::Position length = starts.PositionFromPartition(starts.Partitions()); for (Sci::Line line = starts.Partitions(); line < lines; line++) { // Produce an ascending sequence that will be filled in with correct widths later length++; starts.InsertPartition(line_cast(line), line_cast(length)); } return refCount == 1; } bool Release() { if (refCount == 1) { starts.DeleteAll(); } refCount--; return refCount == 0; } bool Active() const noexcept { return refCount > 0; } Sci::Position LineWidth(Sci::Line line) const noexcept { return starts.PositionFromPartition(line_cast(line) + 1) - starts.PositionFromPartition(line_cast(line)); } void SetLineWidth(Sci::Line line, Sci::Position width) noexcept { const Sci::Position widthCurrent = LineWidth(line); starts.InsertText(line_cast(line), line_cast(width - widthCurrent)); } void AllocateLines(Sci::Line lines) { if (lines > starts.Partitions()) { starts.ReAllocate(lines); } } void InsertLines(Sci::Line line, Sci::Line lines) { // Insert multiple lines with each temporarily 1 character wide. // The line widths will be fixed up by later measuring code. const POS lineAsPos = line_cast(line); const POS lineStart = starts.PositionFromPartition(lineAsPos - 1) + 1; for (POS l = 0; l < line_cast(lines); l++) { starts.InsertPartition(lineAsPos + l, lineStart + l); } } }; template class LineVector : public ILineVector { Partitioning starts; PerLine *perLine; LineStartIndex startsUTF16; LineStartIndex startsUTF32; LineCharacterIndexType activeIndices; void SetActiveIndices() noexcept { activeIndices = (startsUTF32.Active() ? LineCharacterIndexType::Utf32 : LineCharacterIndexType::None) | (startsUTF16.Active() ? LineCharacterIndexType::Utf16 : LineCharacterIndexType::None); } // pos_cast(): cast Sci::Line and Sci::Position to either 32-bit or 64-bit value // This avoids warnings from Visual C++ Code Analysis and shortens code static constexpr POS pos_cast(Sci::Position pos) noexcept { return static_cast(pos); } // line_from_pos_cast(): return 32-bit or 64-bit value as Sci::Line // This avoids warnings from Visual C++ Code Analysis and shortens code static constexpr Sci::Line line_from_pos_cast(POS line) noexcept { return static_cast(line); } public: LineVector() : starts(256), perLine(nullptr), activeIndices(LineCharacterIndexType::None) { } void Init() override { starts.DeleteAll(); if (perLine) { perLine->Init(); } startsUTF32.starts.DeleteAll(); startsUTF16.starts.DeleteAll(); } void SetPerLine(PerLine *pl) noexcept override { perLine = pl; } void InsertText(Sci::Line line, Sci::Position delta) noexcept override { starts.InsertText(pos_cast(line), pos_cast(delta)); } void InsertLine(Sci::Line line, Sci::Position position, bool lineStart) override { const POS lineAsPos = pos_cast(line); starts.InsertPartition(lineAsPos, pos_cast(position)); if (activeIndices != LineCharacterIndexType::None) { if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.InsertLines(line, 1); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.InsertLines(line, 1); } } if (perLine) { if ((line > 0) && lineStart) line--; perLine->InsertLine(line); } } void InsertLines(Sci::Line line, const Sci::Position *positions, size_t lines, bool lineStart) override { const POS lineAsPos = pos_cast(line); if constexpr (sizeof(Sci::Position) == sizeof(POS)) { starts.InsertPartitions(lineAsPos, positions, lines); } else { starts.InsertPartitionsWithCast(lineAsPos, positions, lines); } if (activeIndices != LineCharacterIndexType::None) { if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.InsertLines(line, lines); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.InsertLines(line, lines); } } if (perLine) { if ((line > 0) && lineStart) line--; perLine->InsertLines(line, lines); } } void SetLineStart(Sci::Line line, Sci::Position position) noexcept override { starts.SetPartitionStartPosition(pos_cast(line), pos_cast(position)); } void RemoveLine(Sci::Line line) override { starts.RemovePartition(pos_cast(line)); if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.starts.RemovePartition(pos_cast(line)); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.starts.RemovePartition(pos_cast(line)); } if (perLine) { perLine->RemoveLine(line); } } Sci::Line Lines() const noexcept override { return line_from_pos_cast(starts.Partitions()); } void AllocateLines(Sci::Line lines) override { if (lines > Lines()) { starts.ReAllocate(lines); if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.AllocateLines(lines); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.AllocateLines(lines); } } } Sci::Line LineFromPosition(Sci::Position pos) const noexcept override { return line_from_pos_cast(starts.PartitionFromPosition(pos_cast(pos))); } Sci::Position LineStart(Sci::Line line) const noexcept override { return starts.PositionFromPartition(pos_cast(line)); } void InsertCharacters(Sci::Line line, CountWidths delta) noexcept override { if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.starts.InsertText(pos_cast(line), pos_cast(delta.WidthUTF32())); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.starts.InsertText(pos_cast(line), pos_cast(delta.WidthUTF16())); } } void SetLineCharactersWidth(Sci::Line line, CountWidths width) noexcept override { if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { assert(startsUTF32.starts.Partitions() == starts.Partitions()); startsUTF32.SetLineWidth(line, width.WidthUTF32()); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { assert(startsUTF16.starts.Partitions() == starts.Partitions()); startsUTF16.SetLineWidth(line, width.WidthUTF16()); } } LineCharacterIndexType LineCharacterIndex() const noexcept override { return activeIndices; } bool AllocateLineCharacterIndex(LineCharacterIndexType lineCharacterIndex, Sci::Line lines) override { const LineCharacterIndexType activeIndicesStart = activeIndices; if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf32)) { startsUTF32.Allocate(lines); assert(startsUTF32.starts.Partitions() == starts.Partitions()); } if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf16)) { startsUTF16.Allocate(lines); assert(startsUTF16.starts.Partitions() == starts.Partitions()); } SetActiveIndices(); return activeIndicesStart != activeIndices; } bool ReleaseLineCharacterIndex(LineCharacterIndexType lineCharacterIndex) override { const LineCharacterIndexType activeIndicesStart = activeIndices; if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf32)) { startsUTF32.Release(); } if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf16)) { startsUTF16.Release(); } SetActiveIndices(); return activeIndicesStart != activeIndices; } Sci::Position IndexLineStart(Sci::Line line, LineCharacterIndexType lineCharacterIndex) const noexcept override { if (lineCharacterIndex == LineCharacterIndexType::Utf32) { return startsUTF32.starts.PositionFromPartition(pos_cast(line)); } else { return startsUTF16.starts.PositionFromPartition(pos_cast(line)); } } Sci::Line LineFromPositionIndex(Sci::Position pos, LineCharacterIndexType lineCharacterIndex) const noexcept override { if (lineCharacterIndex == LineCharacterIndexType::Utf32) { return line_from_pos_cast(startsUTF32.starts.PartitionFromPosition(pos_cast(pos))); } else { return line_from_pos_cast(startsUTF16.starts.PartitionFromPosition(pos_cast(pos))); } } }; Action::Action() noexcept { at = ActionType::start; position = 0; lenData = 0; mayCoalesce = false; } void Action::Create(ActionType at_, Sci::Position position_, const char *data_, Sci::Position lenData_, bool mayCoalesce_) { data = nullptr; position = position_; at = at_; if (lenData_) { data = std::make_unique(lenData_); memcpy(&data[0], data_, lenData_); } lenData = lenData_; mayCoalesce = mayCoalesce_; } void Action::Clear() noexcept { data = nullptr; lenData = 0; } // The undo history stores a sequence of user operations that represent the user's view of the // commands executed on the text. // Each user operation contains a sequence of text insertion and text deletion actions. // All the user operations are stored in a list of individual actions with 'start' actions used // as delimiters between user operations. // Initially there is one start action in the history. // As each action is performed, it is recorded in the history. The action may either become // part of the current user operation or may start a new user operation. If it is to be part of the // current operation, then it overwrites the current last action. If it is to be part of a new // operation, it is appended after the current last action. // After writing the new action, a new start action is appended at the end of the history. // The decision of whether to start a new user operation is based upon two factors. If a // compound operation has been explicitly started by calling BeginUndoAction and no matching // EndUndoAction (these calls nest) has been called, then the action is coalesced into the current // operation. If there is no outstanding BeginUndoAction call then a new operation is started // unless it looks as if the new action is caused by the user typing or deleting a stream of text. // Sequences that look like typing or deletion are coalesced into a single user operation. UndoHistory::UndoHistory() { actions.resize(3); maxAction = 0; currentAction = 0; undoSequenceDepth = 0; savePoint = 0; tentativePoint = -1; actions[currentAction].Create(ActionType::start); } void UndoHistory::EnsureUndoRoom() { // Have to test that there is room for 2 more actions in the array // as two actions may be created by the calling function if (static_cast(currentAction) >= (actions.size() - 2)) { // Run out of undo nodes so extend the array actions.resize(actions.size() * 2); } } const char *UndoHistory::AppendAction(ActionType at, Sci::Position position, const char *data, Sci::Position lengthData, bool &startSequence, bool mayCoalesce) { EnsureUndoRoom(); //Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, lengthData, currentAction); //Platform::DebugPrintf("^ %d action %d %d\n", actions[currentAction - 1].at, // actions[currentAction - 1].position, actions[currentAction - 1].lenData); if (currentAction < savePoint) { savePoint = -1; if (!detach) { detach = currentAction; } } else if (detach && (*detach > currentAction)) { detach = currentAction; } const int oldCurrentAction = currentAction; if (currentAction >= 1) { if (0 == undoSequenceDepth) { // Top level actions may not always be coalesced ptrdiff_t targetAct = -1; const Action *actPrevious = &(actions[currentAction + targetAct]); // Container actions may forward the coalesce state of Scintilla Actions. while ((actPrevious->at == ActionType::container) && actPrevious->mayCoalesce) { targetAct--; actPrevious = &(actions[currentAction + targetAct]); } // See if current action can be coalesced into previous action // Will work if both are inserts or deletes and position is same if ((currentAction == savePoint) || (currentAction == tentativePoint)) { currentAction++; } else if (!actions[currentAction].mayCoalesce) { // Not allowed to coalesce if this set currentAction++; } else if (!mayCoalesce || !actPrevious->mayCoalesce) { currentAction++; } else if (at == ActionType::container || actions[currentAction].at == ActionType::container) { ; // A coalescible containerAction } else if ((at != actPrevious->at) && (actPrevious->at != ActionType::start)) { currentAction++; } else if ((at == ActionType::insert) && (position != (actPrevious->position + actPrevious->lenData))) { // Insertions must be immediately after to coalesce currentAction++; } else if (at == ActionType::remove) { if ((lengthData == 1) || (lengthData == 2)) { if ((position + lengthData) == actPrevious->position) { ; // Backspace -> OK } else if (position == actPrevious->position) { ; // Delete -> OK } else { // Removals must be at same position to coalesce currentAction++; } } else { // Removals must be of one character to coalesce currentAction++; } } else { // Action coalesced. } } else { // Actions not at top level are always coalesced unless this is after return to top level if (!actions[currentAction].mayCoalesce) currentAction++; } } else { currentAction++; } startSequence = oldCurrentAction != currentAction; const int actionWithData = currentAction; actions[currentAction].Create(at, position, data, lengthData, mayCoalesce); currentAction++; actions[currentAction].Create(ActionType::start); maxAction = currentAction; return actions[actionWithData].data.get(); } void UndoHistory::BeginUndoAction() { EnsureUndoRoom(); if (undoSequenceDepth == 0) { if (actions[currentAction].at != ActionType::start) { currentAction++; actions[currentAction].Create(ActionType::start); maxAction = currentAction; } actions[currentAction].mayCoalesce = false; } undoSequenceDepth++; } void UndoHistory::EndUndoAction() { PLATFORM_ASSERT(undoSequenceDepth > 0); EnsureUndoRoom(); undoSequenceDepth--; if (0 == undoSequenceDepth) { if (actions[currentAction].at != ActionType::start) { currentAction++; actions[currentAction].Create(ActionType::start); maxAction = currentAction; } actions[currentAction].mayCoalesce = false; } } void UndoHistory::DropUndoSequence() { undoSequenceDepth = 0; } void UndoHistory::DeleteUndoHistory() { for (int i = 1; i < maxAction; i++) actions[i].Clear(); maxAction = 0; currentAction = 0; actions[currentAction].Create(ActionType::start); savePoint = 0; tentativePoint = -1; } void UndoHistory::SetSavePoint() noexcept { savePoint = currentAction; detach.reset(); } bool UndoHistory::IsSavePoint() const noexcept { return savePoint == currentAction; } bool UndoHistory::BeforeSavePoint() const noexcept { return (savePoint < 0) || (savePoint > currentAction); } bool UndoHistory::BeforeReachableSavePoint() const noexcept { return (savePoint >= 0) && !detach && (savePoint > currentAction); } bool UndoHistory::AfterSavePoint() const noexcept { return (savePoint >= 0) && (savePoint <= currentAction); } bool UndoHistory::AfterDetachPoint() const noexcept { return detach && (*detach < currentAction); } void UndoHistory::TentativeStart() { tentativePoint = currentAction; } void UndoHistory::TentativeCommit() { tentativePoint = -1; // Truncate undo history maxAction = currentAction; } bool UndoHistory::TentativeActive() const noexcept { return tentativePoint >= 0; } int UndoHistory::TentativeSteps() noexcept { // Drop any trailing startAction if (actions[currentAction].at == ActionType::start && currentAction > 0) currentAction--; if (tentativePoint >= 0) return currentAction - tentativePoint; else return -1; } bool UndoHistory::CanUndo() const noexcept { return (currentAction > 0) && (maxAction > 0); } int UndoHistory::StartUndo() { // Drop any trailing startAction if (actions[currentAction].at == ActionType::start && currentAction > 0) currentAction--; // Count the steps in this action int act = currentAction; while (actions[act].at != ActionType::start && act > 0) { act--; } return currentAction - act; } const Action &UndoHistory::GetUndoStep() const { return actions[currentAction]; } void UndoHistory::CompletedUndoStep() { currentAction--; } bool UndoHistory::CanRedo() const noexcept { return maxAction > currentAction; } int UndoHistory::StartRedo() { // Drop any leading startAction if (currentAction < maxAction && actions[currentAction].at == ActionType::start) currentAction++; // Count the steps in this action int act = currentAction; while (act < maxAction && actions[act].at != ActionType::start) { act++; } return act - currentAction; } const Action &UndoHistory::GetRedoStep() const { return actions[currentAction]; } void UndoHistory::CompletedRedoStep() { currentAction++; } CellBuffer::CellBuffer(bool hasStyles_, bool largeDocument_) : hasStyles(hasStyles_), largeDocument(largeDocument_) { readOnly = false; utf8Substance = false; utf8LineEnds = LineEndType::Default; collectingUndo = true; if (largeDocument) plv = std::make_unique>(); else plv = std::make_unique>(); } CellBuffer::~CellBuffer() noexcept = default; char CellBuffer::CharAt(Sci::Position position) const noexcept { return substance.ValueAt(position); } unsigned char CellBuffer::UCharAt(Sci::Position position) const noexcept { return substance.ValueAt(position); } void CellBuffer::GetCharRange(char *buffer, Sci::Position position, Sci::Position lengthRetrieve) const { if (lengthRetrieve <= 0) return; if (position < 0) return; if ((position + lengthRetrieve) > substance.Length()) { Platform::DebugPrintf("Bad GetCharRange %.0f for %.0f of %.0f\n", static_cast(position), static_cast(lengthRetrieve), static_cast(substance.Length())); return; } substance.GetRange(buffer, position, lengthRetrieve); } char CellBuffer::StyleAt(Sci::Position position) const noexcept { return hasStyles ? style.ValueAt(position) : '\0'; } void CellBuffer::GetStyleRange(unsigned char *buffer, Sci::Position position, Sci::Position lengthRetrieve) const { if (lengthRetrieve < 0) return; if (position < 0) return; if (!hasStyles) { std::fill(buffer, buffer + lengthRetrieve, static_cast(0)); return; } if ((position + lengthRetrieve) > style.Length()) { Platform::DebugPrintf("Bad GetStyleRange %.0f for %.0f of %.0f\n", static_cast(position), static_cast(lengthRetrieve), static_cast(style.Length())); return; } style.GetRange(reinterpret_cast(buffer), position, lengthRetrieve); } const char *CellBuffer::BufferPointer() { return substance.BufferPointer(); } const char *CellBuffer::RangePointer(Sci::Position position, Sci::Position rangeLength) noexcept { return substance.RangePointer(position, rangeLength); } Sci::Position CellBuffer::GapPosition() const noexcept { return substance.GapPosition(); } SplitView CellBuffer::AllView() const noexcept { const size_t length = substance.Length(); size_t length1 = substance.GapPosition(); if (length1 == 0) { // Assign segment2 to segment1 / length1 to avoid useless test against 0 length1 length1 = length; } return SplitView { substance.ElementPointer(0), length1, substance.ElementPointer(length1) - length1, length }; } // The char* returned is to an allocation owned by the undo history const char *CellBuffer::InsertString(Sci::Position position, const char *s, Sci::Position insertLength, bool &startSequence) { // InsertString and DeleteChars are the bottleneck though which all changes occur const char *data = s; if (!readOnly) { if (collectingUndo) { // Save into the undo/redo stack, but only the characters - not the formatting // This takes up about half load time data = uh.AppendAction(ActionType::insert, position, s, insertLength, startSequence); } BasicInsertString(position, s, insertLength); if (changeHistory) { changeHistory->Insert(position, insertLength, collectingUndo, uh.BeforeReachableSavePoint()); } } return data; } bool CellBuffer::SetStyleAt(Sci::Position position, char styleValue) noexcept { if (!hasStyles) { return false; } const char curVal = style.ValueAt(position); if (curVal != styleValue) { style.SetValueAt(position, styleValue); return true; } else { return false; } } bool CellBuffer::SetStyleFor(Sci::Position position, Sci::Position lengthStyle, char styleValue) noexcept { if (!hasStyles) { return false; } bool changed = false; PLATFORM_ASSERT(lengthStyle == 0 || (lengthStyle > 0 && lengthStyle + position <= style.Length())); while (lengthStyle--) { const char curVal = style.ValueAt(position); if (curVal != styleValue) { style.SetValueAt(position, styleValue); changed = true; } position++; } return changed; } // The char* returned is to an allocation owned by the undo history const char *CellBuffer::DeleteChars(Sci::Position position, Sci::Position deleteLength, bool &startSequence) { // InsertString and DeleteChars are the bottleneck though which all changes occur PLATFORM_ASSERT(deleteLength > 0); const char *data = nullptr; if (!readOnly) { if (collectingUndo) { // Save into the undo/redo stack, but only the characters - not the formatting // The gap would be moved to position anyway for the deletion so this doesn't cost extra data = substance.RangePointer(position, deleteLength); data = uh.AppendAction(ActionType::remove, position, data, deleteLength, startSequence); } if (changeHistory) { changeHistory->DeleteRangeSavingHistory(position, deleteLength, uh.BeforeReachableSavePoint(), uh.AfterDetachPoint()); } BasicDeleteChars(position, deleteLength); } return data; } Sci::Position CellBuffer::Length() const noexcept { return substance.Length(); } void CellBuffer::Allocate(Sci::Position newSize) { if (!largeDocument && (newSize > INT32_MAX)) { throw std::runtime_error("CellBuffer::Allocate: size of standard document limited to 2G."); } substance.ReAllocate(newSize); if (hasStyles) { style.ReAllocate(newSize); } } void CellBuffer::SetUTF8Substance(bool utf8Substance_) noexcept { utf8Substance = utf8Substance_; } void CellBuffer::SetLineEndTypes(LineEndType utf8LineEnds_) { if (utf8LineEnds != utf8LineEnds_) { const LineCharacterIndexType indexes = plv->LineCharacterIndex(); utf8LineEnds = utf8LineEnds_; ResetLineEnds(); AllocateLineCharacterIndex(indexes); } } bool CellBuffer::ContainsLineEnd(const char *s, Sci::Position length) const noexcept { unsigned char chBeforePrev = 0; unsigned char chPrev = 0; for (Sci::Position i = 0; i < length; i++) { const unsigned char ch = s[i]; if ((ch == '\r') || (ch == '\n')) { return true; } else if (utf8LineEnds == LineEndType::Unicode) { if (UTF8IsMultibyteLineEnd(chBeforePrev, chPrev, ch)) { return true; } } chBeforePrev = chPrev; chPrev = ch; } return false; } void CellBuffer::SetPerLine(PerLine *pl) noexcept { plv->SetPerLine(pl); } LineCharacterIndexType CellBuffer::LineCharacterIndex() const noexcept { return plv->LineCharacterIndex(); } void CellBuffer::AllocateLineCharacterIndex(LineCharacterIndexType lineCharacterIndex) { if (utf8Substance) { if (plv->AllocateLineCharacterIndex(lineCharacterIndex, Lines())) { // Changed so recalculate whole file RecalculateIndexLineStarts(0, Lines() - 1); } } } void CellBuffer::ReleaseLineCharacterIndex(LineCharacterIndexType lineCharacterIndex) { plv->ReleaseLineCharacterIndex(lineCharacterIndex); } Sci::Line CellBuffer::Lines() const noexcept { return plv->Lines(); } void CellBuffer::AllocateLines(Sci::Line lines) { plv->AllocateLines(lines); } Sci::Position CellBuffer::LineStart(Sci::Line line) const noexcept { if (line < 0) return 0; else if (line >= Lines()) return Length(); else return plv->LineStart(line); } Sci::Position CellBuffer::LineEnd(Sci::Line line) const noexcept { if (line >= Lines() - 1) { return LineStart(line + 1); } else { Sci::Position position = LineStart(line + 1); if (LineEndType::Unicode == GetLineEndTypes()) { const unsigned char bytes[] = { UCharAt(position - 3), UCharAt(position - 2), UCharAt(position - 1), }; if (UTF8IsSeparator(bytes)) { return position - UTF8SeparatorLength; } if (UTF8IsNEL(bytes + 1)) { return position - UTF8NELLength; } } position--; // Back over CR or LF // When line terminator is CR+LF, may need to go back one more if ((position > LineStart(line)) && (CharAt(position - 1) == '\r')) { position--; } return position; } } Sci::Line CellBuffer::LineFromPosition(Sci::Position pos) const noexcept { return plv->LineFromPosition(pos); } Sci::Position CellBuffer::IndexLineStart(Sci::Line line, LineCharacterIndexType lineCharacterIndex) const noexcept { return plv->IndexLineStart(line, lineCharacterIndex); } Sci::Line CellBuffer::LineFromPositionIndex(Sci::Position pos, LineCharacterIndexType lineCharacterIndex) const noexcept { return plv->LineFromPositionIndex(pos, lineCharacterIndex); } bool CellBuffer::IsReadOnly() const noexcept { return readOnly; } void CellBuffer::SetReadOnly(bool set) noexcept { readOnly = set; } bool CellBuffer::IsLarge() const noexcept { return largeDocument; } bool CellBuffer::HasStyles() const noexcept { return hasStyles; } void CellBuffer::SetSavePoint() { uh.SetSavePoint(); if (changeHistory) { changeHistory->SetSavePoint(); } } bool CellBuffer::IsSavePoint() const noexcept { return uh.IsSavePoint(); } void CellBuffer::TentativeStart() { uh.TentativeStart(); } void CellBuffer::TentativeCommit() { uh.TentativeCommit(); } int CellBuffer::TentativeSteps() noexcept { return uh.TentativeSteps(); } bool CellBuffer::TentativeActive() const noexcept { return uh.TentativeActive(); } // Without undo void CellBuffer::InsertLine(Sci::Line line, Sci::Position position, bool lineStart) { plv->InsertLine(line, position, lineStart); } void CellBuffer::RemoveLine(Sci::Line line) { plv->RemoveLine(line); } bool CellBuffer::UTF8LineEndOverlaps(Sci::Position position) const noexcept { const unsigned char bytes[] = { static_cast(substance.ValueAt(position-2)), static_cast(substance.ValueAt(position-1)), static_cast(substance.ValueAt(position)), static_cast(substance.ValueAt(position+1)), }; return UTF8IsSeparator(bytes) || UTF8IsSeparator(bytes+1) || UTF8IsNEL(bytes+1); } bool CellBuffer::UTF8IsCharacterBoundary(Sci::Position position) const { assert(position >= 0 && position <= Length()); if (position > 0) { std::string back; for (int i = 0; i < UTF8MaxBytes; i++) { const Sci::Position posBack = position - i; if (posBack < 0) { return false; } back.insert(0, 1, substance.ValueAt(posBack)); if (!UTF8IsTrailByte(back.front())) { if (i > 0) { // Have reached a non-trail const int cla = UTF8Classify(back); if ((cla & UTF8MaskInvalid) || (cla != i)) { return false; } } break; } } } if (position < Length()) { const unsigned char fore = substance.ValueAt(position); if (UTF8IsTrailByte(fore)) { return false; } } return true; } void CellBuffer::ResetLineEnds() { // Reinitialize line data -- too much work to preserve const Sci::Line lines = plv->Lines(); plv->Init(); plv->AllocateLines(lines); constexpr Sci::Position position = 0; const Sci::Position length = Length(); plv->InsertText(0, length); Sci::Line lineInsert = 1; constexpr bool atLineStart = true; unsigned char chBeforePrev = 0; unsigned char chPrev = 0; for (Sci::Position i = 0; i < length; i++) { const unsigned char ch = substance.ValueAt(position + i); if (ch == '\r') { InsertLine(lineInsert, (position + i) + 1, atLineStart); lineInsert++; } else if (ch == '\n') { if (chPrev == '\r') { // Patch up what was end of line plv->SetLineStart(lineInsert - 1, (position + i) + 1); } else { InsertLine(lineInsert, (position + i) + 1, atLineStart); lineInsert++; } } else if (utf8LineEnds == LineEndType::Unicode) { if (UTF8IsMultibyteLineEnd(chBeforePrev, chPrev, ch)) { InsertLine(lineInsert, (position + i) + 1, atLineStart); lineInsert++; } } chBeforePrev = chPrev; chPrev = ch; } } namespace { CountWidths CountCharacterWidthsUTF8(std::string_view sv) noexcept { CountWidths cw; size_t remaining = sv.length(); while (remaining > 0) { const int utf8Status = UTF8Classify(sv); const int lenChar = utf8Status & UTF8MaskWidth; cw.CountChar(lenChar); sv.remove_prefix(lenChar); remaining -= lenChar; } return cw; } } bool CellBuffer::MaintainingLineCharacterIndex() const noexcept { return plv->LineCharacterIndex() != LineCharacterIndexType::None; } void CellBuffer::RecalculateIndexLineStarts(Sci::Line lineFirst, Sci::Line lineLast) { std::string text; Sci::Position posLineEnd = LineStart(lineFirst); for HTTP/1.1 200 OK Connection: keep-alive Connection: keep-alive Content-Disposition: inline; filename="CellBuffer.cxx" Content-Disposition: inline; filename="CellBuffer.cxx" Content-Length: 45340 Content-Length: 45340 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 23:29:38 UTC ETag: "32cc9d73048b9f09ae5a7d02486ac6e41fbc1bce" ETag: "32cc9d73048b9f09ae5a7d02486ac6e41fbc1bce" Expires: Tue, 16 Oct 2035 23:29:38 GMT Expires: Tue, 16 Oct 2035 23:29:39 GMT Last-Modified: Sat, 18 Oct 2025 23:29:38 GMT Last-Modified: Sat, 18 Oct 2025 23:29:39 GMT Server: OpenBSD httpd Server: OpenBSD httpd X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff // Scintilla source code edit control /** @file CellBuffer.cxx ** Manages a buffer of cells. **/ // Copyright 1998-2001 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ScintillaTypes.h" #include "Debugging.h" #include "Position.h" #include "SplitVector.h" #include "Partitioning.h" #include "RunStyles.h" #include "SparseVector.h" #include "ChangeHistory.h" #include "CellBuffer.h" #include "UniConversion.h" namespace Scintilla::Internal { struct CountWidths { // Measures the number of characters in a string divided into those // from the Base Multilingual Plane and those from other planes. Sci::Position countBasePlane; Sci::Position countOtherPlanes; explicit CountWidths(Sci::Position countBasePlane_=0, Sci::Position countOtherPlanes_=0) noexcept : countBasePlane(countBasePlane_), countOtherPlanes(countOtherPlanes_) { } CountWidths operator-() const noexcept { return CountWidths(-countBasePlane , -countOtherPlanes); } Sci::Position WidthUTF32() const noexcept { // All code points take one code unit in UTF-32. return countBasePlane + countOtherPlanes; } Sci::Position WidthUTF16() const noexcept { // UTF-16 takes 2 code units for other planes return countBasePlane + 2 * countOtherPlanes; } void CountChar(int lenChar) noexcept { if (lenChar == 4) { countOtherPlanes++; } else { countBasePlane++; } } }; class ILineVector { public: virtual void Init() = 0; virtual void SetPerLine(PerLine *pl) noexcept = 0; virtual void InsertText(Sci::Line line, Sci::Position delta) noexcept = 0; virtual void InsertLine(Sci::Line line, Sci::Position position, bool lineStart) = 0; virtual void InsertLines(Sci::Line line, const Sci::Position *positions, size_t lines, bool lineStart) = 0; virtual void SetLineStart(Sci::Line line, Sci::Position position) noexcept = 0; virtual void RemoveLine(Sci::Line line) = 0; virtual Sci::Line Lines() const noexcept = 0; virtual void AllocateLines(Sci::Line lines) = 0; virtual Sci::Line LineFromPosition(Sci::Position pos) const noexcept = 0; virtual Sci::Position LineStart(Sci::Line line) const noexcept = 0; virtual void InsertCharacters(Sci::Line line, CountWidths delta) noexcept = 0; virtual void SetLineCharactersWidth(Sci::Line line, CountWidths width) noexcept = 0; virtual Scintilla::LineCharacterIndexType LineCharacterIndex() const noexcept = 0; virtual bool AllocateLineCharacterIndex(Scintilla::LineCharacterIndexType lineCharacterIndex, Sci::Line lines) = 0; virtual bool ReleaseLineCharacterIndex(Scintilla::LineCharacterIndexType lineCharacterIndex) = 0; virtual Sci::Position IndexLineStart(Sci::Line line, Scintilla::LineCharacterIndexType lineCharacterIndex) const noexcept = 0; virtual Sci::Line LineFromPositionIndex(Sci::Position pos, Scintilla::LineCharacterIndexType lineCharacterIndex) const noexcept = 0; virtual ~ILineVector() {} }; } using namespace Scintilla; using namespace Scintilla::Internal; template class LineStartIndex { // line_cast(): cast Sci::Line to either 32-bit or 64-bit value // This avoids warnings from Visual C++ Code Analysis and shortens code static constexpr POS line_cast(Sci::Line pos) noexcept { return static_cast(pos); } public: int refCount; Partitioning starts; LineStartIndex() : refCount(0), starts(4) { // Minimal initial allocation } bool Allocate(Sci::Line lines) { refCount++; Sci::Position length = starts.PositionFromPartition(starts.Partitions()); for (Sci::Line line = starts.Partitions(); line < lines; line++) { // Produce an ascending sequence that will be filled in with correct widths later length++; starts.InsertPartition(line_cast(line), line_cast(length)); } return refCount == 1; } bool Release() { if (refCount == 1) { starts.DeleteAll(); } refCount--; return refCount == 0; } bool Active() const noexcept { return refCount > 0; } Sci::Position LineWidth(Sci::Line line) const noexcept { return starts.PositionFromPartition(line_cast(line) + 1) - starts.PositionFromPartition(line_cast(line)); } void SetLineWidth(Sci::Line line, Sci::Position width) noexcept { const Sci::Position widthCurrent = LineWidth(line); starts.InsertText(line_cast(line), line_cast(width - widthCurrent)); } void AllocateLines(Sci::Line lines) { if (lines > starts.Partitions()) { starts.ReAllocate(lines); } } void InsertLines(Sci::Line line, Sci::Line lines) { // Insert multiple lines with each temporarily 1 character wide. // The line widths will be fixed up by later measuring code. const POS lineAsPos = line_cast(line); const POS lineStart = starts.PositionFromPartition(lineAsPos - 1) + 1; for (POS l = 0; l < line_cast(lines); l++) { starts.InsertPartition(lineAsPos + l, lineStart + l); } } }; template class LineVector : public ILineVector { Partitioning starts; PerLine *perLine; LineStartIndex startsUTF16; LineStartIndex startsUTF32; LineCharacterIndexType activeIndices; void SetActiveIndices() noexcept { activeIndices = (startsUTF32.Active() ? LineCharacterIndexType::Utf32 : LineCharacterIndexType::None) | (startsUTF16.Active() ? LineCharacterIndexType::Utf16 : LineCharacterIndexType::None); } // pos_cast(): cast Sci::Line and Sci::Position to either 32-bit or 64-bit value // This avoids warnings from Visual C++ Code Analysis and shortens code static constexpr POS pos_cast(Sci::Position pos) noexcept { return static_cast(pos); } // line_from_pos_cast(): return 32-bit or 64-bit value as Sci::Line // This avoids warnings from Visual C++ Code Analysis and shortens code static constexpr Sci::Line line_from_pos_cast(POS line) noexcept { return static_cast(line); } public: LineVector() : starts(256), perLine(nullptr), activeIndices(LineCharacterIndexType::None) { } void Init() override { starts.DeleteAll(); if (perLine) { perLine->Init(); } startsUTF32.starts.DeleteAll(); startsUTF16.starts.DeleteAll(); } void SetPerLine(PerLine *pl) noexcept override { perLine = pl; } void InsertText(Sci::Line line, Sci::Position delta) noexcept override { starts.InsertText(pos_cast(line), pos_cast(delta)); } void InsertLine(Sci::Line line, Sci::Position position, bool lineStart) override { const POS lineAsPos = pos_cast(line); starts.InsertPartition(lineAsPos, pos_cast(position)); if (activeIndices != LineCharacterIndexType::None) { if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.InsertLines(line, 1); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.InsertLines(line, 1); } } if (perLine) { if ((line > 0) && lineStart) line--; perLine->InsertLine(line); } } void InsertLines(Sci::Line line, const Sci::Position *positions, size_t lines, bool lineStart) override { const POS lineAsPos = pos_cast(line); if constexpr (sizeof(Sci::Position) == sizeof(POS)) { starts.InsertPartitions(lineAsPos, positions, lines); } else { starts.InsertPartitionsWithCast(lineAsPos, positions, lines); } if (activeIndices != LineCharacterIndexType::None) { if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.InsertLines(line, lines); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.InsertLines(line, lines); } } if (perLine) { if ((line > 0) && lineStart) line--; perLine->InsertLines(line, lines); } } void SetLineStart(Sci::Line line, Sci::Position position) noexcept override { starts.SetPartitionStartPosition(pos_cast(line), pos_cast(position)); } void RemoveLine(Sci::Line line) override { starts.RemovePartition(pos_cast(line)); if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.starts.RemovePartition(pos_cast(line)); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.starts.RemovePartition(pos_cast(line)); } if (perLine) { perLine->RemoveLine(line); } } Sci::Line Lines() const noexcept override { return line_from_pos_cast(starts.Partitions()); } void AllocateLines(Sci::Line lines) override { if (lines > Lines()) { starts.ReAllocate(lines); if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.AllocateLines(lines); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.AllocateLines(lines); } } } Sci::Line LineFromPosition(Sci::Position pos) const noexcept override { return line_from_pos_cast(starts.PartitionFromPosition(pos_cast(pos))); } Sci::Position LineStart(Sci::Line line) const noexcept override { return starts.PositionFromPartition(pos_cast(line)); } void InsertCharacters(Sci::Line line, CountWidths delta) noexcept override { if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { startsUTF32.starts.InsertText(pos_cast(line), pos_cast(delta.WidthUTF32())); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { startsUTF16.starts.InsertText(pos_cast(line), pos_cast(delta.WidthUTF16())); } } void SetLineCharactersWidth(Sci::Line line, CountWidths width) noexcept override { if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { assert(startsUTF32.starts.Partitions() == starts.Partitions()); startsUTF32.SetLineWidth(line, width.WidthUTF32()); } if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { assert(startsUTF16.starts.Partitions() == starts.Partitions()); startsUTF16.SetLineWidth(line, width.WidthUTF16()); } } LineCharacterIndexType LineCharacterIndex() const noexcept override { return activeIndices; } bool AllocateLineCharacterIndex(LineCharacterIndexType lineCharacterIndex, Sci::Line lines) override { const LineCharacterIndexType activeIndicesStart = activeIndices; if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf32)) { startsUTF32.Allocate(lines); assert(startsUTF32.starts.Partitions() == starts.Partitions()); } if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf16)) { startsUTF16.Allocate(lines); assert(startsUTF16.starts.Partitions() == starts.Partitions()); } SetActiveIndices(); return activeIndicesStart != activeIndices; } bool ReleaseLineCharacterIndex(LineCharacterIndexType lineCharacterIndex) override { const LineCharacterIndexType activeIndicesStart = activeIndices; if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf32)) { startsUTF32.Release(); } if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf16)) { startsUTF16.Release(); } SetActiveIndices(); return activeIndicesStart != activeIndices; } Sci::Position IndexLineStart(Sci::Line line, LineCharacterIndexType lineCharacterIndex) const noexcept override { if (lineCharacterIndex == LineCharacterIndexType::Utf32) { return startsUTF32.starts.PositionFromPartition(pos_cast(line)); } else { return startsUTF16.starts.PositionFromPartition(pos_cast(line)); } } Sci::Line LineFromPositionIndex(Sci::Position pos, LineCharacterIndexType lineCharacterIndex) const noexcept override { if (lineCharacterIndex == LineCharacterIndexType::Utf32) { return line_from_pos_cast(startsUTF32.starts.PartitionFromPosition(pos_cast(pos))); } else { return line_from_pos_cast(startsUTF16.starts.PartitionFromPosition(pos_cast(pos))); } } }; Action