1 // Scintilla source code edit control
2 /** @file PositionCache.cxx
3 ** Classes for caching layout information.
5 // Copyright 1998-2007 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
16 #include <string_view>
25 #include "ScintillaTypes.h"
26 #include "ScintillaMessages.h"
30 #include "Debugging.h"
34 #include "CharacterType.h"
35 #include "CharacterCategoryMap.h"
37 #include "UniqueString.h"
38 #include "SplitVector.h"
39 #include "Partitioning.h"
40 #include "RunStyles.h"
41 #include "ContractionState.h"
42 #include "CellBuffer.h"
44 #include "Indicator.h"
45 #include "LineMarker.h"
47 #include "ViewStyle.h"
48 #include "CharClassify.h"
49 #include "Decoration.h"
50 #include "CaseFolder.h"
52 #include "UniConversion.h"
53 #include "Selection.h"
54 #include "PositionCache.h"
56 using namespace Scintilla
;
57 using namespace Scintilla::Internal
;
59 void BidiData::Resize(size_t maxLineLength_
) {
60 stylesFonts
.resize(maxLineLength_
+ 1);
61 widthReprs
.resize(maxLineLength_
+ 1);
64 LineLayout::LineLayout(Sci::Line lineNumber_
, int maxLineLength_
) :
66 lineNumber(lineNumber_
),
70 validity(ValidLevel::invalid
),
72 highlightColumn(false),
75 bracePreviousStyles
{},
76 widthLine(wrapWidthInfinite
),
79 Resize(maxLineLength_
);
82 LineLayout::~LineLayout() {
86 void LineLayout::Resize(int maxLineLength_
) {
87 if (maxLineLength_
> maxLineLength
) {
89 chars
= std::make_unique
<char[]>(maxLineLength_
+ 1);
90 styles
= std::make_unique
<unsigned char []>(maxLineLength_
+ 1);
91 // Extra position allocated as sometimes the Windows
92 // GetTextExtentExPoint API writes an extra element.
93 positions
= std::make_unique
<XYPOSITION
[]>(maxLineLength_
+ 1 + 1);
95 bidiData
->Resize(maxLineLength_
);
98 maxLineLength
= maxLineLength_
;
102 void LineLayout::EnsureBidiData() {
104 bidiData
= std::make_unique
<BidiData
>();
105 bidiData
->Resize(maxLineLength
);
109 void LineLayout::Free() noexcept
{
117 void LineLayout::Invalidate(ValidLevel validity_
) noexcept
{
118 if (validity
> validity_
)
119 validity
= validity_
;
122 Sci::Line
LineLayout::LineNumber() const noexcept
{
126 bool LineLayout::CanHold(Sci::Line lineDoc
, int lineLength_
) const noexcept
{
127 return (lineNumber
== lineDoc
) && (lineLength_
<= maxLineLength
);
130 int LineLayout::LineStart(int line
) const noexcept
{
133 } else if ((line
>= lines
) || !lineStarts
) {
134 return numCharsInLine
;
136 return lineStarts
[line
];
140 int LineLayout::LineLength(int line
) const noexcept
{
142 return numCharsInLine
;
143 } if (line
>= lines
- 1) {
144 return numCharsInLine
- lineStarts
[line
];
146 return lineStarts
[line
+ 1] - lineStarts
[line
];
150 int LineLayout::LineLastVisible(int line
, Scope scope
) const noexcept
{
153 } else if ((line
>= lines
-1) || !lineStarts
) {
154 return scope
== Scope::visibleOnly
? numCharsBeforeEOL
: numCharsInLine
;
156 return lineStarts
[line
+1];
160 Range
LineLayout::SubLineRange(int subLine
, Scope scope
) const noexcept
{
161 return Range(LineStart(subLine
), LineLastVisible(subLine
, scope
));
164 bool LineLayout::InLine(int offset
, int line
) const noexcept
{
165 return ((offset
>= LineStart(line
)) && (offset
< LineStart(line
+ 1))) ||
166 ((offset
== numCharsInLine
) && (line
== (lines
-1)));
169 int LineLayout::SubLineFromPosition(int posInLine
, PointEnd pe
) const noexcept
{
170 if (!lineStarts
|| (posInLine
> maxLineLength
)) {
174 for (int line
= 0; line
< lines
; line
++) {
175 if (FlagSet(pe
, PointEnd::subLineEnd
)) {
176 // Return subline not start of next
177 if (lineStarts
[line
+ 1] <= posInLine
+ 1)
180 if (lineStarts
[line
+ 1] <= posInLine
)
188 void LineLayout::SetLineStart(int line
, int start
) {
189 if ((line
>= lenLineStarts
) && (line
!= 0)) {
190 const int newMaxLines
= line
+ 20;
191 std::unique_ptr
<int[]> newLineStarts
= std::make_unique
<int[]>(newMaxLines
);
193 std::copy(lineStarts
.get(), lineStarts
.get() + lenLineStarts
, newLineStarts
.get());
195 lineStarts
= std::move(newLineStarts
);
196 lenLineStarts
= newMaxLines
;
198 lineStarts
[line
] = start
;
201 void LineLayout::SetBracesHighlight(Range rangeLine
, const Sci::Position braces
[],
202 char bracesMatchStyle
, int xHighlight
, bool ignoreStyle
) {
203 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[0])) {
204 const Sci::Position braceOffset
= braces
[0] - rangeLine
.start
;
205 if (braceOffset
< numCharsInLine
) {
206 bracePreviousStyles
[0] = styles
[braceOffset
];
207 styles
[braceOffset
] = bracesMatchStyle
;
210 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[1])) {
211 const Sci::Position braceOffset
= braces
[1] - rangeLine
.start
;
212 if (braceOffset
< numCharsInLine
) {
213 bracePreviousStyles
[1] = styles
[braceOffset
];
214 styles
[braceOffset
] = bracesMatchStyle
;
217 if ((braces
[0] >= rangeLine
.start
&& braces
[1] <= rangeLine
.end
) ||
218 (braces
[1] >= rangeLine
.start
&& braces
[0] <= rangeLine
.end
)) {
219 xHighlightGuide
= xHighlight
;
223 void LineLayout::RestoreBracesHighlight(Range rangeLine
, const Sci::Position braces
[], bool ignoreStyle
) {
224 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[0])) {
225 const Sci::Position braceOffset
= braces
[0] - rangeLine
.start
;
226 if (braceOffset
< numCharsInLine
) {
227 styles
[braceOffset
] = bracePreviousStyles
[0];
230 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[1])) {
231 const Sci::Position braceOffset
= braces
[1] - rangeLine
.start
;
232 if (braceOffset
< numCharsInLine
) {
233 styles
[braceOffset
] = bracePreviousStyles
[1];
239 int LineLayout::FindBefore(XYPOSITION x
, Range range
) const noexcept
{
240 Sci::Position lower
= range
.start
;
241 Sci::Position upper
= range
.end
;
243 const Sci::Position middle
= (upper
+ lower
+ 1) / 2; // Round high
244 const XYPOSITION posMiddle
= positions
[middle
];
250 } while (lower
< upper
);
251 return static_cast<int>(lower
);
255 int LineLayout::FindPositionFromX(XYPOSITION x
, Range range
, bool charPosition
) const noexcept
{
256 int pos
= FindBefore(x
, range
);
257 while (pos
< range
.end
) {
259 if (x
< (positions
[pos
+ 1])) {
263 if (x
< ((positions
[pos
] + positions
[pos
+ 1]) / 2)) {
269 return static_cast<int>(range
.end
);
272 Point
LineLayout::PointFromPosition(int posInLine
, int lineHeight
, PointEnd pe
) const noexcept
{
274 // In case of very long line put x at arbitrary large position
275 if (posInLine
> maxLineLength
) {
276 pt
.x
= positions
[maxLineLength
] - positions
[LineStart(lines
)];
279 for (int subLine
= 0; subLine
< lines
; subLine
++) {
280 const Range rangeSubLine
= SubLineRange(subLine
, Scope::visibleOnly
);
281 if (posInLine
>= rangeSubLine
.start
) {
282 pt
.y
= static_cast<XYPOSITION
>(subLine
*lineHeight
);
283 if (posInLine
<= rangeSubLine
.end
) {
284 pt
.x
= positions
[posInLine
] - positions
[rangeSubLine
.start
];
285 if (rangeSubLine
.start
!= 0) // Wrapped lines may be indented
287 if (FlagSet(pe
, PointEnd::subLineEnd
)) // Return end of first subline not start of next
289 } else if (FlagSet(pe
, PointEnd::lineEnd
) && (subLine
== (lines
-1))) {
290 pt
.x
= positions
[numCharsInLine
] - positions
[rangeSubLine
.start
];
291 if (rangeSubLine
.start
!= 0) // Wrapped lines may be indented
301 int LineLayout::EndLineStyle() const noexcept
{
302 return styles
[numCharsBeforeEOL
> 0 ? numCharsBeforeEOL
-1 : 0];
305 ScreenLine::ScreenLine(
306 const LineLayout
*ll_
,
310 int tabWidthMinimumPixels_
) :
312 start(ll
->LineStart(subLine
)),
313 len(ll
->LineLength(subLine
)),
315 height(static_cast<float>(vs
.lineHeight
)),
316 ctrlCharPadding(vs
.ctrlCharPadding
),
317 tabWidth(vs
.tabWidth
),
318 tabWidthMinimumPixels(tabWidthMinimumPixels_
) {
321 ScreenLine::~ScreenLine() {
324 std::string_view
ScreenLine::Text() const {
325 return std::string_view(&ll
->chars
[start
], len
);
328 size_t ScreenLine::Length() const {
332 size_t ScreenLine::RepresentationCount() const {
333 return std::count_if(&ll
->bidiData
->widthReprs
[start
],
334 &ll
->bidiData
->widthReprs
[start
+ len
],
335 [](XYPOSITION w
) noexcept
{ return w
> 0.0f
; });
338 XYPOSITION
ScreenLine::Width() const {
342 XYPOSITION
ScreenLine::Height() const {
346 XYPOSITION
ScreenLine::TabWidth() const {
350 XYPOSITION
ScreenLine::TabWidthMinimumPixels() const {
351 return static_cast<XYPOSITION
>(tabWidthMinimumPixels
);
354 const Font
*ScreenLine::FontOfPosition(size_t position
) const {
355 return ll
->bidiData
->stylesFonts
[start
+ position
].get();
358 XYPOSITION
ScreenLine::RepresentationWidth(size_t position
) const {
359 return ll
->bidiData
->widthReprs
[start
+ position
];
362 XYPOSITION
ScreenLine::TabPositionAfter(XYPOSITION xPosition
) const {
363 return (std::floor((xPosition
+ TabWidthMinimumPixels()) / TabWidth()) + 1) * TabWidth();
366 LineLayoutCache::LineLayoutCache() :
367 level(LineCache::None
),
368 allInvalidated(false), styleClock(-1) {
371 LineLayoutCache::~LineLayoutCache() = default;
375 constexpr size_t AlignUp(size_t value
, size_t alignment
) noexcept
{
376 return ((value
- 1) / alignment
+ 1) * alignment
;
379 constexpr size_t alignmentLLC
= 20;
381 constexpr bool GraphicASCII(char ch
) noexcept
{
382 return ch
>= ' ' && ch
<= '~';
385 bool AllGraphicASCII(std::string_view text
) noexcept
{
386 return std::all_of(text
.cbegin(), text
.cend(), GraphicASCII
);
392 size_t LineLayoutCache::EntryForLine(Sci::Line line
) const noexcept
{
394 case LineCache::None
:
396 case LineCache::Caret
:
398 case LineCache::Page
:
399 return 1 + (line
% (cache
.size() - 1));
400 case LineCache::Document
:
406 void LineLayoutCache::AllocateForLevel(Sci::Line linesOnScreen
, Sci::Line linesInDoc
) {
407 size_t lengthForLevel
= 0;
408 if (level
== LineCache::Caret
) {
410 } else if (level
== LineCache::Page
) {
411 lengthForLevel
= AlignUp(linesOnScreen
+ 1, alignmentLLC
);
412 } else if (level
== LineCache::Document
) {
413 lengthForLevel
= AlignUp(linesInDoc
, alignmentLLC
);
416 if (lengthForLevel
!= cache
.size()) {
417 allInvalidated
= false;
418 cache
.resize(lengthForLevel
);
419 // Cache::none -> no entries
420 // Cache::caret -> 1 entry can take any line
421 // Cache::document -> entry per line so each line in correct entry after resize
422 if (level
== LineCache::Page
) {
423 // Cache::page -> locates lines in particular entries which may be incorrect after
424 // a resize so move them to correct entries.
425 for (size_t i
= 1; i
< cache
.size();) {
426 size_t increment
= 1;
428 const size_t posForLine
= EntryForLine(cache
[i
]->LineNumber());
429 if (posForLine
!= i
) {
430 if (cache
[posForLine
]) {
431 if (EntryForLine(cache
[posForLine
]->LineNumber()) == posForLine
) {
432 // [posForLine] already holds line that is in correct place
433 cache
[i
].reset(); // This line has nowhere to go so reset it.
435 std::swap(cache
[i
], cache
[posForLine
]);
437 // Don't increment as newly swapped in value may have to move
440 cache
[posForLine
] = std::move(cache
[i
]);
448 for (size_t i
= 1; i
< cache
.size(); i
++) {
450 PLATFORM_ASSERT(EntryForLine(cache
[i
]->LineNumber()) == i
);
456 PLATFORM_ASSERT(cache
.size() == lengthForLevel
);
459 void LineLayoutCache::Deallocate() noexcept
{
463 void LineLayoutCache::Invalidate(LineLayout::ValidLevel validity_
) noexcept
{
464 if (!cache
.empty() && !allInvalidated
) {
465 for (const std::shared_ptr
<LineLayout
> &ll
: cache
) {
467 ll
->Invalidate(validity_
);
470 if (validity_
== LineLayout::ValidLevel::invalid
) {
471 allInvalidated
= true;
476 void LineLayoutCache::SetLevel(LineCache level_
) noexcept
{
477 if (level
!= level_
) {
479 allInvalidated
= false;
484 std::shared_ptr
<LineLayout
> LineLayoutCache::Retrieve(Sci::Line lineNumber
, Sci::Line lineCaret
, int maxChars
, int styleClock_
,
485 Sci::Line linesOnScreen
, Sci::Line linesInDoc
) {
486 AllocateForLevel(linesOnScreen
, linesInDoc
);
487 if (styleClock
!= styleClock_
) {
488 Invalidate(LineLayout::ValidLevel::checkTextAndStyle
);
489 styleClock
= styleClock_
;
491 allInvalidated
= false;
493 if (level
== LineCache::Page
) {
494 // If first entry is this line then just reuse it.
495 if (!(cache
[0] && (cache
[0]->lineNumber
== lineNumber
))) {
496 const size_t posForLine
= EntryForLine(lineNumber
);
497 if (lineNumber
== lineCaret
) {
498 // Use position 0 for caret line.
500 // Another line is currently in [0] so move it out to its normal position.
501 // Since it was recently the caret line its likely to be needed soon.
502 const size_t posNewForEntry0
= EntryForLine(cache
[0]->lineNumber
);
503 if (posForLine
== posNewForEntry0
) {
504 std::swap(cache
[0], cache
[posNewForEntry0
]);
506 cache
[posNewForEntry0
] = std::move(cache
[0]);
509 if (cache
[posForLine
] && (cache
[posForLine
]->lineNumber
== lineNumber
)) {
510 // Caret line is currently somewhere else so move it to [0].
511 cache
[0] = std::move(cache
[posForLine
]);
517 } else if (level
== LineCache::Document
) {
521 if (pos
< cache
.size()) {
522 if (cache
[pos
] && !cache
[pos
]->CanHold(lineNumber
, maxChars
)) {
526 cache
[pos
] = std::make_shared
<LineLayout
>(lineNumber
, maxChars
);
529 // Expensive check that there is only one entry for any line number
530 std::vector
<bool> linesInCache(linesInDoc
);
531 for (const auto &entry
: cache
) {
533 PLATFORM_ASSERT(!linesInCache
[entry
->LineNumber()]);
534 linesInCache
[entry
->LineNumber()] = true;
541 // Only reach here for level == Cache::none
542 return std::make_shared
<LineLayout
>(lineNumber
, maxChars
);
547 // Simply pack the (maximum 4) character bytes into an int
548 constexpr unsigned int KeyFromString(std::string_view charBytes
) noexcept
{
549 PLATFORM_ASSERT(charBytes
.length() <= 4);
551 for (const unsigned char uc
: charBytes
) {
557 constexpr unsigned int representationKeyCrLf
= KeyFromString("\r\n");
561 void SpecialRepresentations::SetRepresentation(std::string_view charBytes
, std::string_view value
) {
562 if ((charBytes
.length() <= 4) && (value
.length() <= Representation::maxLength
)) {
563 const unsigned int key
= KeyFromString(charBytes
);
564 const bool inserted
= mapReprs
.insert_or_assign(key
, Representation(value
)).second
;
566 // New entry so increment for first byte
567 const unsigned char ucStart
= charBytes
.empty() ? 0 : charBytes
[0];
568 startByteHasReprs
[ucStart
]++;
572 if (key
== representationKeyCrLf
) {
579 void SpecialRepresentations::SetRepresentationAppearance(std::string_view charBytes
, RepresentationAppearance appearance
) {
580 if (charBytes
.length() <= 4) {
581 const unsigned int key
= KeyFromString(charBytes
);
582 const MapRepresentation::iterator it
= mapReprs
.find(key
);
583 if (it
== mapReprs
.end()) {
584 // Not present so fail
587 it
->second
.appearance
= appearance
;
591 void SpecialRepresentations::SetRepresentationColour(std::string_view charBytes
, ColourRGBA colour
) {
592 if (charBytes
.length() <= 4) {
593 const unsigned int key
= KeyFromString(charBytes
);
594 const MapRepresentation::iterator it
= mapReprs
.find(key
);
595 if (it
== mapReprs
.end()) {
596 // Not present so fail
599 it
->second
.appearance
= it
->second
.appearance
| RepresentationAppearance::Colour
;
600 it
->second
.colour
= colour
;
604 void SpecialRepresentations::ClearRepresentation(std::string_view charBytes
) {
605 if (charBytes
.length() <= 4) {
606 const unsigned int key
= KeyFromString(charBytes
);
607 const MapRepresentation::iterator it
= mapReprs
.find(key
);
608 if (it
!= mapReprs
.end()) {
610 const unsigned char ucStart
= charBytes
.empty() ? 0 : charBytes
[0];
611 startByteHasReprs
[ucStart
]--;
612 if (key
== maxKey
&& startByteHasReprs
[ucStart
] == 0) {
613 maxKey
= mapReprs
.empty() ? 0 : mapReprs
.crbegin()->first
;
615 if (key
== representationKeyCrLf
) {
622 const Representation
*SpecialRepresentations::GetRepresentation(std::string_view charBytes
) const {
623 const unsigned int key
= KeyFromString(charBytes
);
627 const MapRepresentation::const_iterator it
= mapReprs
.find(key
);
628 if (it
!= mapReprs
.end()) {
629 return &(it
->second
);
634 const Representation
*SpecialRepresentations::RepresentationFromCharacter(std::string_view charBytes
) const {
635 if (charBytes
.length() <= 4) {
636 const unsigned char ucStart
= charBytes
.empty() ? 0 : charBytes
[0];
637 if (!startByteHasReprs
[ucStart
])
639 return GetRepresentation(charBytes
);
644 void SpecialRepresentations::Clear() {
646 constexpr unsigned short none
= 0;
647 std::fill(startByteHasReprs
, std::end(startByteHasReprs
), none
);
652 void BreakFinder::Insert(Sci::Position val
) {
653 const int posInLine
= static_cast<int>(val
);
654 if (posInLine
> nextBreak
) {
655 const std::vector
<int>::iterator it
= std::lower_bound(selAndEdge
.begin(), selAndEdge
.end(), posInLine
);
656 if (it
== selAndEdge
.end()) {
657 selAndEdge
.push_back(posInLine
);
658 } else if (*it
!= posInLine
) {
659 selAndEdge
.insert(it
, 1, posInLine
);
664 BreakFinder::BreakFinder(const LineLayout
*ll_
, const Selection
*psel
, Range lineRange_
, Sci::Position posLineStart_
,
665 XYPOSITION xStart
, BreakFor breakFor
, const Document
*pdoc_
, const SpecialRepresentations
*preprs_
, const ViewStyle
*pvsDraw
) :
667 lineRange(lineRange_
),
668 posLineStart(posLineStart_
),
669 nextBreak(static_cast<int>(lineRange_
.start
)),
674 encodingFamily(pdoc_
->CodePageFamily()),
677 // Search for first visible break
678 // First find the first visible character
680 nextBreak
= ll
->FindBefore(xStart
, lineRange
);
681 // Now back to a style break
682 while ((nextBreak
> lineRange
.start
) && (ll
->styles
[nextBreak
] == ll
->styles
[nextBreak
- 1])) {
686 if (FlagSet(breakFor
, BreakFor::Selection
)) {
687 const SelectionPosition
posStart(posLineStart
);
688 const SelectionPosition
posEnd(posLineStart
+ lineRange
.end
);
689 const SelectionSegment
segmentLine(posStart
, posEnd
);
690 for (size_t r
=0; r
<psel
->Count(); r
++) {
691 const SelectionSegment portion
= psel
->Range(r
).Intersect(segmentLine
);
692 if (!(portion
.start
== portion
.end
)) {
693 if (portion
.start
.IsValid())
694 Insert(portion
.start
.Position() - posLineStart
);
695 if (portion
.end
.IsValid())
696 Insert(portion
.end
.Position() - posLineStart
);
699 // On the curses platform, the terminal is drawing its own caret, so add breaks around the
700 // caret in the main selection in order to help prevent the selection from being drawn in
702 if (FlagSet(pvsDraw
->caret
.style
, CaretStyle::Curses
) && !psel
->RangeMain().Empty()) {
703 const Sci::Position caretPos
= psel
->RangeMain().caret
.Position();
704 const Sci::Position anchorPos
= psel
->RangeMain().anchor
.Position();
705 if (caretPos
< anchorPos
) {
706 const Sci::Position nextPos
= pdoc
->MovePositionOutsideChar(caretPos
+ 1, 1);
707 Insert(nextPos
- posLineStart
);
708 } else if (caretPos
> anchorPos
&& pvsDraw
->DrawCaretInsideSelection(false, false)) {
709 const Sci::Position prevPos
= pdoc
->MovePositionOutsideChar(caretPos
- 1, -1);
710 if (prevPos
> anchorPos
)
711 Insert(prevPos
- posLineStart
);
715 if (FlagSet(breakFor
, BreakFor::Foreground
) && pvsDraw
->indicatorsSetFore
) {
716 for (const IDecoration
*deco
: pdoc
->decorations
->View()) {
717 if (pvsDraw
->indicators
[deco
->Indicator()].OverridesTextFore()) {
718 Sci::Position startPos
= deco
->EndRun(posLineStart
);
719 while (startPos
< (posLineStart
+ lineRange
.end
)) {
720 Insert(startPos
- posLineStart
);
721 startPos
= deco
->EndRun(startPos
);
726 Insert(ll
->edgeColumn
);
727 Insert(lineRange
.end
);
728 saeNext
= (!selAndEdge
.empty()) ? selAndEdge
[0] : -1;
731 BreakFinder::~BreakFinder() noexcept
= default;
733 TextSegment
BreakFinder::Next() {
735 const int prev
= nextBreak
;
736 const Representation
*repr
= nullptr;
737 while (nextBreak
< lineRange
.end
) {
739 const char * const chars
= &ll
->chars
[nextBreak
];
740 const unsigned char ch
= chars
[0];
741 if (!UTF8IsAscii(ch
) && encodingFamily
!= EncodingFamily::eightBit
) {
742 if (encodingFamily
== EncodingFamily::unicode
) {
743 charWidth
= UTF8DrawBytes(reinterpret_cast<const unsigned char *>(chars
), static_cast<int>(lineRange
.end
- nextBreak
));
745 charWidth
= pdoc
->DBCSDrawBytes(std::string_view(chars
, lineRange
.end
- nextBreak
));
749 if (preprs
->MayContain(ch
)) {
750 // Special case \r\n line ends if there is a representation
751 if (ch
== '\r' && preprs
->ContainsCrLf() && chars
[1] == '\n') {
754 repr
= preprs
->GetRepresentation(std::string_view(chars
, charWidth
));
756 if (((nextBreak
> 0) && (ll
->styles
[nextBreak
] != ll
->styles
[nextBreak
- 1])) ||
758 (nextBreak
== saeNext
)) {
759 while ((nextBreak
>= saeNext
) && (saeNext
< lineRange
.end
)) {
761 saeNext
= static_cast<int>((saeCurrentPos
< selAndEdge
.size()) ? selAndEdge
[saeCurrentPos
] : lineRange
.end
);
763 if ((nextBreak
> prev
) || repr
) {
764 // Have a segment to report
765 if (nextBreak
== prev
) {
766 nextBreak
+= charWidth
;
768 repr
= nullptr; // Optimize -> should remember repr
773 nextBreak
+= charWidth
;
776 const int lengthSegment
= nextBreak
- prev
;
777 if (lengthSegment
< lengthStartSubdivision
) {
778 return TextSegment(prev
, lengthSegment
, repr
);
783 // Splitting up a long run from prev to nextBreak in lots of approximately lengthEachSubdivision.
784 const int startSegment
= subBreak
;
785 const int remaining
= nextBreak
- startSegment
;
786 int lengthSegment
= remaining
;
787 if (lengthSegment
> lengthEachSubdivision
) {
788 lengthSegment
= static_cast<int>(pdoc
->SafeSegment(std::string_view(&ll
->chars
[startSegment
], lengthEachSubdivision
)));
790 if (lengthSegment
< remaining
) {
791 subBreak
+= lengthSegment
;
795 return TextSegment(startSegment
, lengthSegment
);
798 bool BreakFinder::More() const noexcept
{
799 return (subBreak
>= 0) || (nextBreak
< lineRange
.end
);
802 PositionCacheEntry::PositionCacheEntry() noexcept
:
803 styleNumber(0), len(0), clock(0) {
806 // Copy constructor not currently used, but needed for being element in std::vector.
807 PositionCacheEntry::PositionCacheEntry(const PositionCacheEntry
&other
) :
808 styleNumber(other
.styleNumber
), len(other
.len
), clock(other
.clock
) {
809 if (other
.positions
) {
810 const size_t lenData
= len
+ (len
/ sizeof(XYPOSITION
)) + 1;
811 positions
= std::make_unique
<XYPOSITION
[]>(lenData
);
812 memcpy(positions
.get(), other
.positions
.get(), lenData
* sizeof(XYPOSITION
));
816 void PositionCacheEntry::Set(unsigned int styleNumber_
, std::string_view sv
,
817 const XYPOSITION
*positions_
, uint16_t clock_
) {
819 styleNumber
= static_cast<uint16_t>(styleNumber_
);
820 len
= static_cast<uint16_t>(sv
.length());
822 if (sv
.data() && positions_
) {
823 positions
= std::make_unique
<XYPOSITION
[]>(len
+ (len
/ sizeof(XYPOSITION
)) + 1);
824 for (unsigned int i
=0; i
<len
; i
++) {
825 positions
[i
] = positions_
[i
];
827 memcpy(&positions
[len
], sv
.data(), sv
.length());
831 PositionCacheEntry::~PositionCacheEntry() {
835 void PositionCacheEntry::Clear() noexcept
{
842 bool PositionCacheEntry::Retrieve(unsigned int styleNumber_
, std::string_view sv
, XYPOSITION
*positions_
) const noexcept
{
843 if ((styleNumber
== styleNumber_
) && (len
== sv
.length()) &&
844 (memcmp(&positions
[len
], sv
.data(), sv
.length())== 0)) {
845 for (unsigned int i
=0; i
<len
; i
++) {
846 positions_
[i
] = positions
[i
];
854 size_t PositionCacheEntry::Hash(unsigned int styleNumber_
, std::string_view sv
) noexcept
{
855 const size_t h1
= std::hash
<std::string_view
>{}(sv
);
856 const size_t h2
= std::hash
<unsigned int>{}(styleNumber_
);
857 return h1
^ (h2
<< 1);
860 bool PositionCacheEntry::NewerThan(const PositionCacheEntry
&other
) const noexcept
{
861 return clock
> other
.clock
;
864 void PositionCacheEntry::ResetClock() noexcept
{
870 PositionCache::PositionCache() {
876 void PositionCache::Clear() noexcept
{
878 for (PositionCacheEntry
&pce
: pces
) {
886 void PositionCache::SetSize(size_t size_
) {
891 size_t PositionCache::GetSize() const noexcept
{
895 void PositionCache::MeasureWidths(Surface
*surface
, const ViewStyle
&vstyle
, unsigned int styleNumber
,
896 std::string_view sv
, XYPOSITION
*positions
) {
897 const Style
&style
= vstyle
.styles
[styleNumber
];
898 if (style
.monospaceASCII
) {
899 if (AllGraphicASCII(sv
)) {
900 const XYPOSITION monospaceCharacterWidth
= style
.monospaceCharacterWidth
;
901 for (size_t i
= 0; i
< sv
.length(); i
++) {
902 positions
[i
] = monospaceCharacterWidth
* (i
+1);
908 size_t probe
= pces
.size(); // Out of bounds
909 if ((!pces
.empty()) && (sv
.length() < 30)) {
910 // Only store short strings in the cache so it doesn't churn with
911 // long comments with only a single comment.
913 // Two way associative: try two probe positions.
914 const size_t hashValue
= PositionCacheEntry::Hash(styleNumber
, sv
);
915 probe
= hashValue
% pces
.size();
916 if (pces
[probe
].Retrieve(styleNumber
, sv
, positions
)) {
919 const size_t probe2
= (hashValue
* 37) % pces
.size();
920 if (pces
[probe2
].Retrieve(styleNumber
, sv
, positions
)) {
923 // Not found. Choose the oldest of the two slots to replace
924 if (pces
[probe
].NewerThan(pces
[probe2
])) {
929 const Font
*fontStyle
= style
.font
.get();
930 surface
->MeasureWidths(fontStyle
, sv
, positions
);
931 if (probe
< pces
.size()) {
935 // Since there are only 16 bits for the clock, wrap it round and
936 // reset all cache entries so none get stuck with a high clock.
937 for (PositionCacheEntry
&pce
: pces
) {
943 pces
[probe
].Set(styleNumber
, sv
, positions
, clock
);