// Scintilla source code edit control /** @file PositionCache.cxx ** Classes for caching layout information. **/ // Copyright 1998-2007 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 "ScintillaMessages.h" #include "ILoader.h" #include "ILexer.h" #include "Debugging.h" #include "Geometry.h" #include "Platform.h" #include "CharacterCategoryMap.h" #include "Position.h" #include "UniqueString.h" #include "SplitVector.h" #include "Partitioning.h" #include "RunStyles.h" #include "ContractionState.h" #include "CellBuffer.h" #include "KeyMap.h" #include "Indicator.h" #include "LineMarker.h" #include "Style.h" #include "ViewStyle.h" #include "CharClassify.h" #include "Decoration.h" #include "CaseFolder.h" #include "Document.h" #include "UniConversion.h" #include "Selection.h" #include "PositionCache.h" using namespace Scintilla; using namespace Scintilla::Internal; void BidiData::Resize(size_t maxLineLength_) { stylesFonts.resize(maxLineLength_ + 1); widthReprs.resize(maxLineLength_ + 1); } LineLayout::LineLayout(Sci::Line lineNumber_, int maxLineLength_) : lenLineStarts(0), lineNumber(lineNumber_), maxLineLength(-1), numCharsInLine(0), numCharsBeforeEOL(0), validity(ValidLevel::invalid), xHighlightGuide(0), highlightColumn(false), containsCaret(false), edgeColumn(0), bracePreviousStyles{}, hotspot(0,0), widthLine(wrapWidthInfinite), lines(1), wrapIndent(0) { Resize(maxLineLength_); } LineLayout::~LineLayout() { Free(); } void LineLayout::Resize(int maxLineLength_) { if (maxLineLength_ > maxLineLength) { Free(); chars = std::make_unique(maxLineLength_ + 1); styles = std::make_unique(maxLineLength_ + 1); // Extra position allocated as sometimes the Windows // GetTextExtentExPoint API writes an extra element. positions = std::make_unique(maxLineLength_ + 1 + 1); if (bidiData) { bidiData->Resize(maxLineLength_); } maxLineLength = maxLineLength_; } } void LineLayout::EnsureBidiData() { if (!bidiData) { bidiData = std::make_unique(); bidiData->Resize(maxLineLength); } } void LineLayout::Free() noexcept { chars.reset(); styles.reset(); positions.reset(); lineStarts.reset(); bidiData.reset(); } void LineLayout::Invalidate(ValidLevel validity_) noexcept { if (validity > validity_) validity = validity_; } Sci::Line LineLayout::LineNumber() const noexcept { return lineNumber; } bool LineLayout::CanHold(Sci::Line lineDoc, int lineLength_) const noexcept { return (lineNumber == lineDoc) && (lineLength_ <= maxLineLength); } int LineLayout::LineStart(int line) const noexcept { if (line <= 0) { return 0; } else if ((line >= lines) || !lineStarts) { return numCharsInLine; } else { return lineStarts[line]; } } int LineLayout::LineLength(int line) const noexcept { if (!lineStarts) { return numCharsInLine; } if (line >= lines - 1) { return numCharsInLine - lineStarts[line]; } else { return lineStarts[line + 1] - lineStarts[line]; } } int LineLayout::LineLastVisible(int line, Scope scope) const noexcept { if (line < 0) { return 0; } else if ((line >= lines-1) || !lineStarts) { return scope == Scope::visibleOnly ? numCharsBeforeEOL : numCharsInLine; } else { return lineStarts[line+1]; } } Range LineLayout::SubLineRange(int subLine, Scope scope) const noexcept { return Range(LineStart(subLine), LineLastVisible(subLine, scope)); } bool LineLayout::InLine(int offset, int line) const noexcept { return ((offset >= LineStart(line)) && (offset < LineStart(line + 1))) || ((offset == numCharsInLine) && (line == (lines-1))); } int LineLayout::SubLineFromPosition(int posInLine, PointEnd pe) const noexcept { if (!lineStarts || (posInLine > maxLineLength)) { return lines - 1; } for (int line = 0; line < lines; line++) { if (FlagSet(pe, PointEnd::subLineEnd)) { // Return subline not start of next if (lineStarts[line + 1] <= posInLine + 1) return line; } else { if (lineStarts[line + 1] <= posInLine) return line; } } return lines - 1; } void LineLayout::SetLineStart(int line, int start) { if ((line >= lenLineStarts) && (line != 0)) { const int newMaxLines = line + 20; std::unique_ptr newLineStarts = std::make_unique(newMaxLines); if (lenLineStarts) { std::copy(lineStarts.get(), lineStarts.get() + lenLineStarts, newLineStarts.get()); } lineStarts = std::move(newLineStarts); lenLineStarts = newMaxLines; } lineStarts[line] = start; } void LineLayout::SetBracesHighlight(Range rangeLine, const Sci::Position braces[], char bracesMatchStyle, int xHighlight, bool ignoreStyle) { if (!ignoreStyle && rangeLine.ContainsCharacter(braces[0])) { const Sci::Position braceOffset = braces[0] - rangeLine.start; if (braceOffset < numCharsInLine) { bracePreviousStyles[0] = styles[braceOffset]; styles[braceOffset] = bracesMatchStyle; } } if (!ignoreStyle && rangeLine.ContainsCharacter(braces[1])) { const Sci::Position braceOffset = braces[1] - rangeLine.start; if (braceOffset < numCharsInLine) { bracePreviousStyles[1] = styles[braceOffset]; styles[braceOffset] = bracesMatchStyle; } } if ((braces[0] >= rangeLine.start && braces[1] <= rangeLine.end) || (braces[1] >= rangeLine.start && braces[0] <= rangeLine.end)) { xHighlightGuide = xHighlight; } } void LineLayout::RestoreBracesHighlight(Range rangeLine, const Sci::Position braces[], bool ignoreStyle) { if (!ignoreStyle && rangeLine.ContainsCharacter(braces[0])) { const Sci::Position braceOffset = braces[0] - rangeLine.start; if (braceOffset < numCharsInLine) { styles[braceOffset] = bracePreviousStyles[0]; } } if (!ignoreStyle && rangeLine.ContainsCharacter(braces[1])) { const Sci::Position braceOffset = braces[1] - rangeLine.start; if (braceOffset < numCharsInLine) { styles[braceOffset] = bracePreviousStyles[1]; } } xHighlightGuide = 0; } int LineLayout::FindBefore(XYPOSITION x, Range range) const noexcept { Sci::Position lower = range.start; Sci::Position upper = range.end; do { const Sci::Position middle = (upper + lower + 1) / 2; // Round high const XYPOSITION posMiddle = positions[middle]; if (x < posMiddle) { upper = middle - 1; } else { lower = middle; } } while (lower < upper); return static_cast(lower); } int LineLayout::FindPositionFromX(XYPOSITION x, Range range, bool charPosition) const noexcept { int pos = FindBefore(x, range); while (pos < range.end) { if (charPosition) { if (x < (positions[pos + 1])) { return pos; } } else { if (x < ((positions[pos] + positions[pos + 1]) / 2)) { return pos; } } pos++; } return static_cast(range.end); } Point LineLayout::PointFromPosition(int posInLine, int lineHeight, PointEnd pe) const noexcept { Point pt; // In case of very long line put x at arbitrary large position if (posInLine > maxLineLength) { pt.x = positions[maxLineLength] - positions[LineStart(lines)]; } for (int subLine = 0; subLine < lines; subLine++) { const Range rangeSubLine = SubLineRange(subLine, Scope::visibleOnly); if (posInLine >= rangeSubLine.start) { pt.y = static_cast(subLine*lineHeight); if (posInLine <= rangeSubLine.end) { pt.x = positions[posInLine] - positions[rangeSubLine.start]; if (rangeSubLine.start != 0) // Wrapped lines may be indented pt.x += wrapIndent; if (FlagSet(pe, PointEnd::subLineEnd)) // Return end of first subline not start of next break; } else if (FlagSet(pe, PointEnd::lineEnd) && (subLine == (lines-1))) { pt.x = positions[numCharsInLine] - positions[rangeSubLine.start]; if (rangeSubLine.start != 0) // Wrapped lines may be indented pt.x += wrapIndent; } } else { break; } } return pt; } int LineLayout::EndLineStyle() const noexcept { return styles[numCharsBeforeEOL > 0 ? numCharsBeforeEOL-1 : 0]; } ScreenLine::ScreenLine( const LineLayout *ll_, int subLine, const ViewStyle &vs, XYPOSITION width_, int tabWidthMinimumPixels_) : ll(ll_), start(ll->LineStart(subLine)), len(ll->LineLength(subLine)), width(width_), height(static_cast(vs.lineHeight)), ctrlCharPadding(vs.ctrlCharPadding), tabWidth(vs.tabWidth), tabWidthMinimumPixels(tabWidthMinimumPixels_) { } ScreenLine::~ScreenLine() { } std::string_view ScreenLine::Text() const { return std::string_view(&ll->chars[start], len); } size_t ScreenLine::Length() const { return len; } size_t ScreenLine::RepresentationCount() const { return std::count_if(&ll->bidiData->widthReprs[start], &ll->bidiData->widthReprs[start + len], [](XYPOSITION w) noexcept { return w > 0.0f; }); } XYPOSITION ScreenLine::Width() const { return width; } XYPOSITION ScreenLine::Height() const { return height; } XYPOSITION ScreenLine::TabWidth() const { return tabWidth; } XYPOSITION ScreenLine::TabWidthMinimumPixels() const { return static_cast(tabWidthMinimumPixels); } const Font *ScreenLine::FontOfPosition(size_t position) const { return ll->bidiData->stylesFonts[start + position].get(); } XYPOSITION ScreenLine::RepresentationWidth(size_t position) const { return ll->bidiData->widthReprs[start + position]; } XYPOSITION ScreenLine::TabPositionAfter(XYPOSITION xPosition) const { return (std::floor((xPosition + TabWidthMinimumPixels()) / TabWidth()) + 1) * TabWidth(); } LineLayoutCache::LineLayoutCache() : level(LineCache::None), allInvalidated(false), styleClock(-1) { } LineLayoutCache::~LineLayoutCache() = default; namespace { constexpr size_t AlignUp(size_t value, size_t alignment) noexcept { return ((value - 1) / alignment + 1) * alignment; } constexpr size_t alignmentLLC = 20; } size_t LineLayoutCache::EntryForLine(Sci::Line line) const noexcept { switch (level) { case LineCache::None: return 0; case LineCache::Caret: return 0; case LineCache::Page: return 1 + (line % (cache.size() - 1)); case LineCache::Document: return line; } return 0; } void LineLayoutCache::AllocateForLevel(Sci::Line linesOnScreen, Sci::Line linesInDoc) { size_t lengthForLevel = 0; if (level == LineCache::Caret) { lengthForLevel = 1; } else if (level == LineCache::Page) { lengthForLevel = AlignUp(linesOnScreen + 1, alignmentLLC); } else if (level == LineCache::Document) { lengthForLevel = AlignUp(linesInDoc, alignmentLLC); } if (lengthForLevel != cache.size()) { allInvalidated = false; cache.resize(lengthForLevel); // Cache::none -> no entries // Cache::caret -> 1 entry can take any line // Cache::document -> entry per line so each line in correct entry after resize if (level == LineCache::Page) { // Cache::page -> locates lines in particular entries which may be incorrect after // a resize so move them to correct entries. for (size_t i = 1; i < cache.size();) { size_t increment = 1; if (cache[i]) { const size_t posForLine = EntryForLine(cache[i]->LineNumber()); if (posForLine != i) { if (cache[posForLine]) { if (EntryForLine(cache[posForLine]->LineNumber()) == posForLine) { // [posForLine] already holds line that is in correct place cache[i].reset(); // This line has nowhere to go so reset it. } else { std::swap(cache[i], cache[posForLine]); increment = 0; // Don't increment as newly swapped in value may have to move } } else { cache[posForLine] = std::move(cache[i]); } } } i += increment; } #ifdef CHECK_LLC for (size_t i = 1; i < cache.size(); i++) { if (cache[i]) { PLATFORM_ASSERT(EntryForLine(cache[i]->LineNumber()) == i); } } #endif } } PLATFORM_ASSERT(cache.size() == lengthForLevel); } void LineLayoutCache::Deallocate() noexcept { cache.clear(); } void LineLayoutCache::Invalidate(LineLayout::ValidLevel validity_) noexcept { if (!cache.empty() && !allInvalidated) { for (const std::shared_ptr &ll : cache) { if (ll) { ll->Invalidate(validity_); } } if (validity_ == LineLayout::ValidLevel::invalid) { allInvalidated = true; } } } void LineLayoutCache::SetLevel(LineCache level_) noexcept { if (level != level_) { level = level_; allInvalidated = false; cache.clear(); } } std::shared_ptr LineLayoutCache::Retrieve(Sci::Line lineNumber, Sci::Line lineCaret, int maxChars, int styleClock_, Sci::Line linesOnScreen, Sci::Line linesInDoc) { AllocateForLevel(linesOnScreen, linesInDoc); if (styleClock != styleClock_) { Invalidate(LineLayout::ValidLevel::checkTextAndStyle); styleClock = styleClock_; } allInvalidated = false; size_t pos = 0; if (level == LineCache::Page) { // If first entry is this line then just reuse it. if (!(cache[0] && (cache[0]->lineNumber == lineNumber))) { const size_t posForLine = EntryForLine(lineNumber); if (lineNumber == lineCaret) { // Use position 0 for caret line. if (cache[0]) { // Another line is currently in [0] so move it out to its normal position. // Since it was recently the caret line its likely to be needed soon. const size_t posNewForEntry0 = EntryForLine(cache[0]->lineNumber); if (posForLine == posNewForEntry0) { std::swap(cache[0], cache[posNewForEntry0]); } else { cache[posNewForEntry0] = std::move(cache[0]); } } if (cache[posForLine] && (cache[posForLine]->lineNumber == lineNumber)) { // Caret line is currently somewhere else so move it to [0]. cache[0] = std::move(cache[posForLine]); } } else { pos = posForLine; } } } else if (level == LineCache::Document) { pos = lineNumber; } if (pos < cache.size()) { if (cache[pos] && !cache[pos]->CanHold(lineNumber, maxChars)) { cache[pos].reset(); } if (!cache[pos]) { cache[pos] = std::make_shared(lineNumber, maxChars); } #ifdef CHECK_LLC // Expensive check that there is only one entry for any line number std::vector linesInCache(linesInDoc); for (const auto &entry : cache) { if (entry) { PLATFORM_ASSERT(!linesInCache[entry->LineNumber()]); linesInCache[entry->LineNumber()] = true; } } #endif return cache[pos]; } // Only reach here for level == Cache::none return std::make_shared(lineNumber, maxChars); } // Simply pack the (maximum 4) character bytes into an int static unsigned int KeyFromString(const char *charBytes, size_t len) noexcept { PLATFORM_ASSERT(len <= 4); unsigned int k=0; for (size_t i=0; isecond); } return nullptr; } bool SpecialRepresentations::Contains(const char HTTP/1.1 200 OK Connection: keep-alive Connection: keep-alive Content-Disposition: inline; filename="PositionCache.cxx" Content-Disposition: inline; filename="PositionCache.cxx" Content-Length: 25775 Content-Length: 25775 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: Fri, 17 Oct 2025 23:30:23 UTC ETag: "fb3d8fa012facefbf1e7758ba0489899484a66a2" ETag: "fb3d8fa012facefbf1e7758ba0489899484a66a2" Expires: Mon, 15 Oct 2035 23:30:23 GMT Expires: Mon, 15 Oct 2035 23:30:23 GMT Last-Modified: Fri, 17 Oct 2025 23:30:23 GMT Last-Modified: Fri, 17 Oct 2025 23:30:23 GMT Server: OpenBSD httpd Server: OpenBSD httpd X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff // Scintilla source code edit control /** @file PositionCache.cxx ** Classes for caching layout information. **/ // Copyright 1998-2007 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 "ScintillaMessages.h" #include "ILoader.h" #include "ILexer.h" #include "Debugging.h" #include "Geometry.h" #include "Platform.h" #include "CharacterCategoryMap.h" #include "Position.h" #include "UniqueString.h" #include "SplitVector.h" #include "Partitioning.h" #include "RunStyles.h" #include "ContractionState.h" #include "CellBuffer.h" #include "KeyMap.h" #include "Indicator.h" #include "LineMarker.h" #include "Style.h" #include "ViewStyle.h" #include "CharClassify.h" #include "Decoration.h" #include "CaseFolder.h" #include "Document.h" #include "UniConversion.h" #include "Selection.h" #include "PositionCache.h" using namespace Scintilla; using namespace Scintilla::Internal; void BidiData::Resize(size_t maxLineLength_) { stylesFonts.resize(maxLineLength_ + 1); widthReprs.resize(maxLineLength_ + 1); } LineLayout::LineLayout(Sci::Line lineNumber_, int maxLineLength_) : lenLineStarts(0), lineNumber(lineNumber_), maxLineLength(-1), numCharsInLine(0), numCharsBeforeEOL(0), validity(ValidLevel::invalid), xHighlightGuide(0), highlightColumn(false), containsCaret(false), edgeColumn(0), bracePreviousStyles{}, hotspot(0,0), widthLine(wrapWidthInfinite), lines(1), wrapIndent(0) { Resize(maxLineLength_); } LineLayout::~LineLayout() { Free(); } void LineLayout::Resize(int maxLineLength_) { if (maxLineLength_ > maxLineLength) { Free(); chars = std::make_unique(maxLineLength_ + 1); styles = std::make_unique(maxLineLength_ + 1); // Extra position allocated as sometimes the Windows // GetTextExtentExPoint API writes an extra element. positions = std::make_unique(maxLineLength_ + 1 + 1); if (bidiData) { bidiData->Resize(maxLineLength_); } maxLineLength = maxLineLength_; } } void LineLayout::EnsureBidiData() { if (!bidiData) { bidiData = std::make_unique(); bidiData->Resize(maxLineLength); } } void LineLayout::Free() noexcept { chars.reset(); styles.reset(); positions.reset(); lineStarts.reset(); bidiData.reset(); } void LineLayout::Invalidate(ValidLevel validity_) noexcept { if (validity > validity_) validity = validity_; } Sci::Line LineLayout::LineNumber() const noexcept { return lineNumber; } bool LineLayout::CanHold(Sci::Line lineDoc, int lineLength_) const noexcept { return (lineNumber == lineDoc) && (lineLength_ <= maxLineLength); } int LineLayout::LineStart(int line) const noexcept { if (line <= 0) { return 0; } else if ((line >= lines) || !lineStarts) { return numCharsInLine; } else { return lineStarts[line]; } } int LineLayout::LineLength(int line) const noexcept { if (!lineStarts) { return numCharsInLine; } if (line >= lines - 1) { return numCharsInLine - lineStarts[line]; } else { return lineStarts[line + 1] - lineStarts[line]; } } int LineLayout::LineLastVisible(int line, Scope scope) const noexcept { if (line < 0) { return 0; } else if ((line >= lines-1) || !lineStarts) { return scope == Scope::visibleOnly ? numCharsBeforeEOL : numCharsInLine; } else { return lineStarts[line+1]; } } Range LineLayout::SubLineRange(int subLine, Scope scope) const noexcept { return Range(LineStart(subLine), LineLastVisible(subLine, scope)); } bool LineLayout::InLine(int offset, int line) const noexcept { return ((offset >= LineStart(line)) && (offset < LineStart(line + 1))) || ((offset == numCharsInLine) && (line == (lines-1))); } int LineLayout::SubLineFromPosition(int posInLine, PointEnd pe) const noexcept { if (!lineStarts || (posInLine > maxLineLength)) { return lines - 1; } for (int line = 0; line < lines; line++) { if (FlagSet(pe, PointEnd::subLineEnd)) { // Return subline not start of next if (lineStarts[line + 1] <= posInLine + 1) return line; } else { if (lineStarts[line + 1] <= posInLine) return line; } } return lines - 1; } void LineLayout::SetLineStart(int line, int start) { if ((line >= lenLineStarts) && (line != 0)) { const int newMaxLines = line + 20; std::unique_ptr newLineStarts = std::make_unique(newMaxLines); if (lenLineStarts) { std::copy(lineStarts.get(), lineStarts.get() + lenLineStarts, newLineStarts.get()); } lineStarts = std::move(newLineStarts); lenLineStarts = newMaxLines; } lineStarts[line] = start; } void LineLayout::SetBracesHighlight(Range rangeLine, const Sci::Position braces[], char bracesMatchStyle, int xHighlight, bool ignoreStyle) { if (!ignoreStyle && rangeLine.ContainsCharacter(braces[0])) { const Sci::Position braceOffset = braces[0] - rangeLine.start; if (braceOffset < numCharsInLine) { bracePreviousStyles[0] = styles[braceOffset]; styles[braceOffset] = bracesMatchStyle; } } if (!ignoreStyle && rangeLine.ContainsCharacter(braces[1])) { const Sci::Position braceOffset = braces[1] - rangeLine.start; if (braceOffset < numCharsInLine) { bracePreviousStyles[1] = styles[braceOffset]; styles[braceOffset] = bracesMatchStyle; } } if ((braces[0] >= rangeLine.start && braces[1] <= rangeLine.end) || (braces[1] >= rangeLine.start && braces[0] <= rangeLine.end)) { xHighlightGuide = xHighlight; } } void LineLayout::RestoreBracesHighlight(Range rangeLine, const Sci::Position braces[], bool ignoreStyle) { if (!ignoreStyle && rangeLine.ContainsCharacter(braces[0])) { const Sci::Position braceOffset = braces[0] - rangeLine.start; if (braceOffset < numCharsInLine) { styles[braceOffset] = bracePreviousStyles[0]; } } if (!ignoreStyle && rangeLine.ContainsCharacter(braces[1])) { const Sci::Position braceOffset = braces[1] - rangeLine.start; if (braceOffset < numCharsInLine) { styles[braceOffset] = bracePreviousStyles[1]; } } xHighlightGuide = 0; } int LineLayout::FindBefore(XYPOSITION x, Range range) const noexcept { Sci::Position lower = range.start; Sci::Position upper = range.end; do { const Sci::Position middle = (upper + lower + 1) / 2; // Round high const XYPOSITION posMiddle = positions[middle]; if (x < posMiddle) { upper = middle - 1; } else { lower = middle; } } while (lower < upper); return static_cast(lower); } int LineLayout::FindPositionFromX(XYPOSITION x, Range range, bool charPosition) const noexcept { int pos = FindBefore(x, range); while (pos < range.end) { if (charPosition) { if (x < (positions[pos + 1])) { return pos; } } else { if (x < ((positions[pos] + positions[pos + 1]) / 2)) { return pos; } } pos++; } return static_cast(range.end); } Point LineLayout::PointFromPosition(int posInLine, int lineHeight, PointEnd pe) const noexcept { Point pt; // In case of very long line put x at arbitrary large position if (posInLine > maxLineLength) { pt.x = positions[maxLineLength] - positions[LineStart(lines)]; } for (int subLine = 0; subLine < lines; subLine++) { const Range rangeSubLine = SubLineRange(subLine, Scope::visibleOnly); if (posInLine >= rangeSubLine.start) { pt.y = static_cast(subLine*lineHeight); if (posInLine <= rangeSubLine.end) { pt.x = positions[posInLine] - positions[rangeSubLine.start]; if (rangeSubLine.start != 0) // Wrapped lines may be indented pt.x += wrapIndent; if (FlagSet(pe, PointEnd::subLineEnd)) // Return end of first subline not start of next break; } else if (FlagSet(pe, PointEnd::lineEnd) && (subLine == (lines-1))) { pt.x = positions[numCharsInLine] - positions[rangeSubLine.start]; if (rangeSubLine.start != 0) // Wrapped lines may be indented pt.x += wrapIndent; } } else { break; } } return pt; } int LineLayout::EndLineStyle() const noexcept { return styles[numCharsBeforeEOL > 0 ? numCharsBeforeEOL-1 : 0]; } ScreenLine::ScreenLine( const LineLayout *ll_, int subLine, const ViewStyle &vs, XYPOSITION width_, int tabWidthMinimumPixels_) : ll(ll_), start(ll->LineStart(subLine)), len(ll->LineLength(subLine)), width(width_), height(