// 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 "Platform.h" #include "ILoader.h" #include "ILexer.h" #include "Scintilla.h" #include "CharacterCategory.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; void BidiData::Resize(size_t maxLineLength_) { stylesFonts.resize(maxLineLength_ + 1); widthReprs.resize(maxLineLength_ + 1); } LineLayout::LineLayout(int maxLineLength_) : lenLineStarts(0), lineNumber(-1), inCache(false), maxLineLength(-1), numCharsInLine(0), numCharsBeforeEOL(0), validity(llInvalid), 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_) { if (validity > validity_) validity = validity_; } int LineLayout::LineStart(int line) const { if (line <= 0) { return 0; } else if ((line >= lines) || !lineStarts) { return numCharsInLine; } else { return lineStarts[line]; } } int Scintilla::LineLayout::LineLength(int line) const { 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 { 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 { return Range(LineStart(subLine), LineLastVisible(subLine, scope)); } bool LineLayout::InLine(int offset, int line) const { return ((offset >= LineStart(line)) && (offset < LineStart(line + 1))) || ((offset == numCharsInLine) && (line == (lines-1))); } int LineLayout::SubLineFromPosition(int posInLine, PointEnd pe) const { if (!lineStarts || (posInLine > maxLineLength)) { return lines - 1; } for (int line = 0; line < lines; line++) { if (pe & peSubLineEnd) { // 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); for (int i = 0; i < newMaxLines; i++) { if (i < lenLineStarts) newLineStarts[i] = lineStarts[i]; else newLineStarts[i] = 0; } 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 { 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 { 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 { 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 (pe & peSubLineEnd) // Return end of first subline not start of next break; } else if ((pe & peLineEnd) && (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 { 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(tabWidthMiniHTTP/1.1 200 OK Connection: keep-alive Connection: keep-alive Content-Disposition: inline; filename="PositionCache.cxx" Content-Disposition: inline; filename="PositionCache.cxx" Content-Length: 23210 Content-Length: 23210 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: Wed, 24 Dec 2025 19:00:17 UTC ETag: "8cdbb4f3d44934f38730d09fb4a1674927c714ba" ETag: "8cdbb4f3d44934f38730d09fb4a1674927c714ba" Expires: Sat, 22 Dec 2035 19:00:17 GMT Expires: Sat, 22 Dec 2035 19:00:17 GMT Last-Modified: Wed, 24 Dec 2025 19:00:17 GMT Last-Modified: Wed, 24 Dec 2025 19:00:17 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 "Platform.h" #include "ILoader.h" #include "ILexer.h" #include "Scintilla.h" #include "CharacterCategory.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; void BidiData::Resize(size_t maxLineLength_) { stylesFonts.resize(maxLineLength_ + 1); widthReprs.resize(maxLineLength_ + 1); } LineLayout::LineLayout(int maxLineLength_) : lenLineStarts(0), lineNumber(-1), inCache(false), maxLineLength(-1), numCharsInLine(0), numCharsBeforeEOL(0), validity(llInvalid), 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_) { if (validity > validity_) validity = validity_; } int LineLayout::LineStart(int line) const { if (line <= 0) { return 0; } else if ((line >= lines) || !lineStarts) { return numCharsInLine; } else { return lineStarts[line]; } } int Scintilla::LineLayout::LineLength(int line) const { 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 { 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 { return Range(LineStart(subLine), LineLastVisible(subLine, scope)); } bool LineLayout::InLine(int offset, int line) const { return ((offset >= LineStart(line)) && (offset < LineStart(line + 1))) || ((offset == numCharsInLine) && (line == (lines-1))); } int LineLayout::SubLineFromPosition(int posInLine, PointEnd pe) const { if (!lineStarts || (posInLine > maxLineLength)) { return lines - 1; } for (int line = 0; line < lines; line++) { if (pe & peSubLineEnd) { // 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); for (int i = 0; i < newMaxLines; i++) { if (i < lenLineStarts) newLineStarts[i] = lineStarts[i]; else newLineStarts[i] = 0; } 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 { 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 { 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 { 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 (pe & peSubLineEnd) // Return end of first subline not start of next break; } else if ((pe & peLineEnd) && (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 { 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) {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]; } 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(0), allInvalidated(false), styleClock(-1), useCount(0) { Allocate(0); } LineLayoutCache::~LineLayoutCache() { Deallocate(); } void LineLayoutCache::Allocate(size_t length_) { PLATFORM_ASSERT(cache.empty()); allInvalidated = false; cache.resize(length_); } void LineLayoutCache::AllocateForLevel(Sci::Line linesOnScreen, Sci::Line linesInDoc) { PLATFORM_ASSERT(useCount == 0); size_t lengthForLevel = 0; if (level == llcCaret) { lengthForLevel = 1; } else if (level == llcPage) { lengthForLevel = linesOnScreen + 1; } else if (level == llcDocument) { lengthForLevel = linesInDoc; } if (lengthForLevel > cache.size()) { Deallocate(); Allocate(lengthForLevel); } else { if (lengthForLevel < cache.size()) { for (size_t i = lengthForLevel; i < cache.size(); i++) { cache[i].reset(); } } cache.resize(lengthForLevel); } PLATFORM_ASSERT(cache.size() == lengthForLevel); } void LineLayoutCache::Deallocate() noexcept { PLATFORM_ASSERT(useCount == 0); cache.clear(); } void LineLayoutCache::Invalidate(LineLayout::validLevel validity_) { if (!cache.empty() && !allInvalidated) { for (const std::unique_ptr &ll : cache) { if (ll) { ll->Invalidate(validity_); } } if (validity_ == LineLayout::llInvalid) { allInvalidated = true; } } } void LineLayoutCache::SetLevel(int level_) noexcept { allInvalidated = false; if ((level_ != -1) && (level != level_)) { level = level_; Deallocate(); } } LineLayout *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::llCheckTextAndStyle); styleClock = styleClock_; } allInvalidated = false; Sci::Position pos = -1; LineLayout *ret = nullptr; if (level == llcCaret) { pos = 0; } else if (level == llcPage) { if (lineNumber == lineCaret) { pos = 0; } else if (cache.size() > 1) { pos = 1 + (lineNumber % (cache.size() - 1)); } } else if (level == llcDocument) { pos = lineNumber; } if (pos >= 0) { PLATFORM_ASSERT(useCount == 0); if (!cache.empty() && (pos < static_cast(cache.size()))) { if (cache[pos]) { if ((cache[pos]->lineNumber != lineNumber) || (cache[pos]->maxLineLength < maxChars)) { cache[pos].reset(); } } if (!cache[pos]) { cache[pos] = std::make_unique(maxChars); } cache[pos]->lineNumber = lineNumber; cache[pos]->inCache = true; ret = cache[pos].get(); useCount++; } } if (!ret) { ret = new LineLayout(maxChars); ret->lineNumber = lineNumber; } return ret; } void LineLayoutCache::Dispose(LineLayout *ll) noexcept { allInvalidated = false; if (ll) { if (!ll->inCache) { delete ll; } else { useCount--; } } } // Simply pack the (maximum 4) character bytes into an int static unsigned int KeyFromString(const char *charBytes, size_t len) { PLATFORM_ASSERT(len <= 4); unsigned int k=0; for (size_t i=0; isecond); } return nullptr; } bool SpecialRepresentations::Contains(const char *charBytes, size_t len) const { PLATFORM_ASSERT(len <= 4); const unsigned char ucStart = charBytes[0]; if (!startByteHasReprs[ucStart]) return false; MapRepresentation::const_iterator it = mapReprs.find(KeyFromString(charBytes, len)); return it != mapReprs.end(); } void SpecialRepresentations::Clear() { mapReprs.clear(); const short