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.
25 #include "Scintilla.h"
27 #include "CharacterCategory.h"
29 #include "UniqueString.h"
30 #include "SplitVector.h"
31 #include "Partitioning.h"
32 #include "RunStyles.h"
33 #include "ContractionState.h"
34 #include "CellBuffer.h"
36 #include "Indicator.h"
37 #include "LineMarker.h"
39 #include "ViewStyle.h"
40 #include "CharClassify.h"
41 #include "Decoration.h"
42 #include "CaseFolder.h"
44 #include "UniConversion.h"
45 #include "Selection.h"
46 #include "PositionCache.h"
48 using namespace Scintilla
;
50 LineLayout::LineLayout(int maxLineLength_
) :
59 highlightColumn(false),
62 bracePreviousStyles
{},
64 widthLine(wrapWidthInfinite
),
67 Resize(maxLineLength_
);
70 LineLayout::~LineLayout() {
74 void LineLayout::Resize(int maxLineLength_
) {
75 if (maxLineLength_
> maxLineLength
) {
77 chars
.reset(new char[maxLineLength_
+ 1]);
78 styles
.reset(new unsigned char[maxLineLength_
+ 1]);
79 // Extra position allocated as sometimes the Windows
80 // GetTextExtentExPoint API writes an extra element.
81 positions
.reset(new XYPOSITION
[maxLineLength_
+ 1 + 1]);
82 maxLineLength
= maxLineLength_
;
86 void LineLayout::Free() noexcept
{
93 void LineLayout::Invalidate(validLevel validity_
) {
94 if (validity
> validity_
)
98 int LineLayout::LineStart(int line
) const {
101 } else if ((line
>= lines
) || !lineStarts
) {
102 return numCharsInLine
;
104 return lineStarts
[line
];
108 int LineLayout::LineLastVisible(int line
, Scope scope
) const {
111 } else if ((line
>= lines
-1) || !lineStarts
) {
112 return scope
== Scope::visibleOnly
? numCharsBeforeEOL
: numCharsInLine
;
114 return lineStarts
[line
+1];
118 Range
LineLayout::SubLineRange(int subLine
, Scope scope
) const {
119 return Range(LineStart(subLine
), LineLastVisible(subLine
, scope
));
122 bool LineLayout::InLine(int offset
, int line
) const {
123 return ((offset
>= LineStart(line
)) && (offset
< LineStart(line
+ 1))) ||
124 ((offset
== numCharsInLine
) && (line
== (lines
-1)));
127 void LineLayout::SetLineStart(int line
, int start
) {
128 if ((line
>= lenLineStarts
) && (line
!= 0)) {
129 int newMaxLines
= line
+ 20;
130 int *newLineStarts
= new int[newMaxLines
];
131 for (int i
= 0; i
< newMaxLines
; i
++) {
132 if (i
< lenLineStarts
)
133 newLineStarts
[i
] = lineStarts
[i
];
135 newLineStarts
[i
] = 0;
137 lineStarts
.reset(newLineStarts
);
138 lenLineStarts
= newMaxLines
;
140 lineStarts
[line
] = start
;
143 void LineLayout::SetBracesHighlight(Range rangeLine
, const Sci::Position braces
[],
144 char bracesMatchStyle
, int xHighlight
, bool ignoreStyle
) {
145 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[0])) {
146 const Sci::Position braceOffset
= braces
[0] - rangeLine
.start
;
147 if (braceOffset
< numCharsInLine
) {
148 bracePreviousStyles
[0] = styles
[braceOffset
];
149 styles
[braceOffset
] = bracesMatchStyle
;
152 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[1])) {
153 const Sci::Position braceOffset
= braces
[1] - rangeLine
.start
;
154 if (braceOffset
< numCharsInLine
) {
155 bracePreviousStyles
[1] = styles
[braceOffset
];
156 styles
[braceOffset
] = bracesMatchStyle
;
159 if ((braces
[0] >= rangeLine
.start
&& braces
[1] <= rangeLine
.end
) ||
160 (braces
[1] >= rangeLine
.start
&& braces
[0] <= rangeLine
.end
)) {
161 xHighlightGuide
= xHighlight
;
165 void LineLayout::RestoreBracesHighlight(Range rangeLine
, const Sci::Position braces
[], bool ignoreStyle
) {
166 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[0])) {
167 const Sci::Position braceOffset
= braces
[0] - rangeLine
.start
;
168 if (braceOffset
< numCharsInLine
) {
169 styles
[braceOffset
] = bracePreviousStyles
[0];
172 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[1])) {
173 const Sci::Position braceOffset
= braces
[1] - rangeLine
.start
;
174 if (braceOffset
< numCharsInLine
) {
175 styles
[braceOffset
] = bracePreviousStyles
[1];
181 int LineLayout::FindBefore(XYPOSITION x
, Range range
) const {
182 Sci::Position lower
= range
.start
;
183 Sci::Position upper
= range
.end
;
185 const Sci::Position middle
= (upper
+ lower
+ 1) / 2; // Round high
186 const XYPOSITION posMiddle
= positions
[middle
];
192 } while (lower
< upper
);
193 return static_cast<int>(lower
);
197 int LineLayout::FindPositionFromX(XYPOSITION x
, Range range
, bool charPosition
) const {
198 int pos
= FindBefore(x
, range
);
199 while (pos
< range
.end
) {
201 if (x
< (positions
[pos
+ 1])) {
205 if (x
< ((positions
[pos
] + positions
[pos
+ 1]) / 2)) {
211 return static_cast<int>(range
.end
);
214 Point
LineLayout::PointFromPosition(int posInLine
, int lineHeight
, PointEnd pe
) const {
216 // In case of very long line put x at arbitrary large position
217 if (posInLine
> maxLineLength
) {
218 pt
.x
= positions
[maxLineLength
] - positions
[LineStart(lines
)];
221 for (int subLine
= 0; subLine
< lines
; subLine
++) {
222 const Range rangeSubLine
= SubLineRange(subLine
, Scope::visibleOnly
);
223 if (posInLine
>= rangeSubLine
.start
) {
224 pt
.y
= static_cast<XYPOSITION
>(subLine
*lineHeight
);
225 if (posInLine
<= rangeSubLine
.end
) {
226 pt
.x
= positions
[posInLine
] - positions
[rangeSubLine
.start
];
227 if (rangeSubLine
.start
!= 0) // Wrapped lines may be indented
229 if (pe
& peSubLineEnd
) // Return end of first subline not start of next
231 } else if ((pe
& peLineEnd
) && (subLine
== (lines
-1))) {
232 pt
.x
= positions
[numCharsInLine
] - positions
[rangeSubLine
.start
];
233 if (rangeSubLine
.start
!= 0) // Wrapped lines may be indented
243 int LineLayout::EndLineStyle() const {
244 return styles
[numCharsBeforeEOL
> 0 ? numCharsBeforeEOL
-1 : 0];
247 LineLayoutCache::LineLayoutCache() :
249 allInvalidated(false), styleClock(-1), useCount(0) {
253 LineLayoutCache::~LineLayoutCache() {
257 void LineLayoutCache::Allocate(size_t length_
) {
258 PLATFORM_ASSERT(cache
.empty());
259 allInvalidated
= false;
260 cache
.resize(length_
);
263 void LineLayoutCache::AllocateForLevel(Sci::Line linesOnScreen
, Sci::Line linesInDoc
) {
264 PLATFORM_ASSERT(useCount
== 0);
265 size_t lengthForLevel
= 0;
266 if (level
== llcCaret
) {
268 } else if (level
== llcPage
) {
269 lengthForLevel
= linesOnScreen
+ 1;
270 } else if (level
== llcDocument
) {
271 lengthForLevel
= linesInDoc
;
273 if (lengthForLevel
> cache
.size()) {
275 Allocate(lengthForLevel
);
277 if (lengthForLevel
< cache
.size()) {
278 for (size_t i
= lengthForLevel
; i
< cache
.size(); i
++) {
282 cache
.resize(lengthForLevel
);
284 PLATFORM_ASSERT(cache
.size() == lengthForLevel
);
287 void LineLayoutCache::Deallocate() noexcept
{
288 PLATFORM_ASSERT(useCount
== 0);
292 void LineLayoutCache::Invalidate(LineLayout::validLevel validity_
) {
293 if (!cache
.empty() && !allInvalidated
) {
294 for (const std::unique_ptr
<LineLayout
> &ll
: cache
) {
296 ll
->Invalidate(validity_
);
299 if (validity_
== LineLayout::llInvalid
) {
300 allInvalidated
= true;
305 void LineLayoutCache::SetLevel(int level_
) noexcept
{
306 allInvalidated
= false;
307 if ((level_
!= -1) && (level
!= level_
)) {
313 LineLayout
*LineLayoutCache::Retrieve(Sci::Line lineNumber
, Sci::Line lineCaret
, int maxChars
, int styleClock_
,
314 Sci::Line linesOnScreen
, Sci::Line linesInDoc
) {
315 AllocateForLevel(linesOnScreen
, linesInDoc
);
316 if (styleClock
!= styleClock_
) {
317 Invalidate(LineLayout::llCheckTextAndStyle
);
318 styleClock
= styleClock_
;
320 allInvalidated
= false;
321 Sci::Position pos
= -1;
322 LineLayout
*ret
= nullptr;
323 if (level
== llcCaret
) {
325 } else if (level
== llcPage
) {
326 if (lineNumber
== lineCaret
) {
328 } else if (cache
.size() > 1) {
329 pos
= 1 + (lineNumber
% (cache
.size() - 1));
331 } else if (level
== llcDocument
) {
335 PLATFORM_ASSERT(useCount
== 0);
336 if (!cache
.empty() && (pos
< static_cast<int>(cache
.size()))) {
338 if ((cache
[pos
]->lineNumber
!= lineNumber
) ||
339 (cache
[pos
]->maxLineLength
< maxChars
)) {
344 cache
[pos
].reset(new LineLayout(maxChars
));
346 cache
[pos
]->lineNumber
= lineNumber
;
347 cache
[pos
]->inCache
= true;
348 ret
= cache
[pos
].get();
354 ret
= new LineLayout(maxChars
);
355 ret
->lineNumber
= lineNumber
;
361 void LineLayoutCache::Dispose(LineLayout
*ll
) noexcept
{
362 allInvalidated
= false;
372 // Simply pack the (maximum 4) character bytes into an int
373 static unsigned int KeyFromString(const char *charBytes
, size_t len
) {
374 PLATFORM_ASSERT(len
<= 4);
376 for (size_t i
=0; i
<len
&& charBytes
[i
]; i
++) {
378 const unsigned char uc
= charBytes
[i
];
384 SpecialRepresentations::SpecialRepresentations() noexcept
{
385 const short none
= 0;
386 std::fill(startByteHasReprs
, std::end(startByteHasReprs
), none
);
389 void SpecialRepresentations::SetRepresentation(const char *charBytes
, const char *value
) {
390 const unsigned int key
= KeyFromString(charBytes
, UTF8MaxBytes
);
391 MapRepresentation::iterator it
= mapReprs
.find(key
);
392 if (it
== mapReprs
.end()) {
393 // New entry so increment for first byte
394 const unsigned char ucStart
= charBytes
[0];
395 startByteHasReprs
[ucStart
]++;
397 mapReprs
[key
] = Representation(value
);
400 void SpecialRepresentations::ClearRepresentation(const char *charBytes
) {
401 MapRepresentation::iterator it
= mapReprs
.find(KeyFromString(charBytes
, UTF8MaxBytes
));
402 if (it
!= mapReprs
.end()) {
404 const unsigned char ucStart
= charBytes
[0];
405 startByteHasReprs
[ucStart
]--;
409 const Representation
*SpecialRepresentations::RepresentationFromCharacter(const char *charBytes
, size_t len
) const {
410 PLATFORM_ASSERT(len
<= 4);
411 const unsigned char ucStart
= charBytes
[0];
412 if (!startByteHasReprs
[ucStart
])
414 MapRepresentation::const_iterator it
= mapReprs
.find(KeyFromString(charBytes
, len
));
415 if (it
!= mapReprs
.end()) {
416 return &(it
->second
);
421 bool SpecialRepresentations::Contains(const char *charBytes
, size_t len
) const {
422 PLATFORM_ASSERT(len
<= 4);
423 const unsigned char ucStart
= charBytes
[0];
424 if (!startByteHasReprs
[ucStart
])
426 MapRepresentation::const_iterator it
= mapReprs
.find(KeyFromString(charBytes
, len
));
427 return it
!= mapReprs
.end();
430 void SpecialRepresentations::Clear() {
432 const short none
= 0;
433 std::fill(startByteHasReprs
, std::end(startByteHasReprs
), none
);
436 void BreakFinder::Insert(Sci::Position val
) {
437 const int posInLine
= static_cast<int>(val
);
438 if (posInLine
> nextBreak
) {
439 const std::vector
<int>::iterator it
= std::lower_bound(selAndEdge
.begin(), selAndEdge
.end(), posInLine
);
440 if (it
== selAndEdge
.end()) {
441 selAndEdge
.push_back(posInLine
);
442 } else if (*it
!= posInLine
) {
443 selAndEdge
.insert(it
, 1, posInLine
);
448 BreakFinder::BreakFinder(const LineLayout
*ll_
, const Selection
*psel
, Range lineRange_
, Sci::Position posLineStart_
,
449 int xStart
, bool breakForSelection
, const Document
*pdoc_
, const SpecialRepresentations
*preprs_
, const ViewStyle
*pvsDraw
) :
451 lineRange(lineRange_
),
452 posLineStart(posLineStart_
),
453 nextBreak(static_cast<int>(lineRange_
.start
)),
458 encodingFamily(pdoc_
->CodePageFamily()),
461 // Search for first visible break
462 // First find the first visible character
464 nextBreak
= ll
->FindBefore(static_cast<XYPOSITION
>(xStart
), lineRange
);
465 // Now back to a style break
466 while ((nextBreak
> lineRange
.start
) && (ll
->styles
[nextBreak
] == ll
->styles
[nextBreak
- 1])) {
470 if (breakForSelection
) {
471 const SelectionPosition
posStart(posLineStart
);
472 const SelectionPosition
posEnd(posLineStart
+ lineRange
.end
);
473 const SelectionSegment
segmentLine(posStart
, posEnd
);
474 for (size_t r
=0; r
<psel
->Count(); r
++) {
475 const SelectionSegment portion
= psel
->Range(r
).Intersect(segmentLine
);
476 if (!(portion
.start
== portion
.end
)) {
477 if (portion
.start
.IsValid())
478 Insert(portion
.start
.Position() - posLineStart
);
479 if (portion
.end
.IsValid())
480 Insert(portion
.end
.Position() - posLineStart
);
484 if (pvsDraw
&& pvsDraw
->indicatorsSetFore
) {
485 for (const IDecoration
*deco
: pdoc
->decorations
->View()) {
486 if (pvsDraw
->indicators
[deco
->Indicator()].OverridesTextFore()) {
487 Sci::Position startPos
= deco
->EndRun(posLineStart
);
488 while (startPos
< (posLineStart
+ lineRange
.end
)) {
489 Insert(startPos
- posLineStart
);
490 startPos
= deco
->EndRun(startPos
);
495 Insert(ll
->edgeColumn
);
496 Insert(lineRange
.end
);
497 saeNext
= (!selAndEdge
.empty()) ? selAndEdge
[0] : -1;
500 BreakFinder::~BreakFinder() {
503 TextSegment
BreakFinder::Next() {
504 if (subBreak
== -1) {
505 const int prev
= nextBreak
;
506 while (nextBreak
< lineRange
.end
) {
508 if (encodingFamily
== efUnicode
)
509 charWidth
= UTF8DrawBytes(reinterpret_cast<unsigned char *>(&ll
->chars
[nextBreak
]),
510 static_cast<int>(lineRange
.end
- nextBreak
));
511 else if (encodingFamily
== efDBCS
)
512 charWidth
= pdoc
->DBCSDrawBytes(
513 &ll
->chars
[nextBreak
], static_cast<int>(lineRange
.end
- nextBreak
));
514 const Representation
*repr
= preprs
->RepresentationFromCharacter(&ll
->chars
[nextBreak
], charWidth
);
515 if (((nextBreak
> 0) && (ll
->styles
[nextBreak
] != ll
->styles
[nextBreak
- 1])) ||
517 (nextBreak
== saeNext
)) {
518 while ((nextBreak
>= saeNext
) && (saeNext
< lineRange
.end
)) {
520 saeNext
= static_cast<int>((saeCurrentPos
< selAndEdge
.size()) ? selAndEdge
[saeCurrentPos
] : lineRange
.end
);
522 if ((nextBreak
> prev
) || repr
) {
523 // Have a segment to report
524 if (nextBreak
== prev
) {
525 nextBreak
+= charWidth
;
527 repr
= nullptr; // Optimize -> should remember repr
529 if ((nextBreak
- prev
) < lengthStartSubdivision
) {
530 return TextSegment(prev
, nextBreak
- prev
, repr
);
536 nextBreak
+= charWidth
;
538 if ((nextBreak
- prev
) < lengthStartSubdivision
) {
539 return TextSegment(prev
, nextBreak
- prev
);
543 // Splitting up a long run from prev to nextBreak in lots of approximately lengthEachSubdivision.
544 // For very long runs add extra breaks after spaces or if no spaces before low punctuation.
545 const int startSegment
= subBreak
;
546 if ((nextBreak
- subBreak
) <= lengthEachSubdivision
) {
548 return TextSegment(startSegment
, nextBreak
- startSegment
);
550 subBreak
+= pdoc
->SafeSegment(&ll
->chars
[subBreak
], nextBreak
-subBreak
, lengthEachSubdivision
);
551 if (subBreak
>= nextBreak
) {
553 return TextSegment(startSegment
, nextBreak
- startSegment
);
555 return TextSegment(startSegment
, subBreak
- startSegment
);
560 bool BreakFinder::More() const noexcept
{
561 return (subBreak
>= 0) || (nextBreak
< lineRange
.end
);
564 PositionCacheEntry::PositionCacheEntry() noexcept
:
565 styleNumber(0), len(0), clock(0), positions(nullptr) {
568 // Copy constructor not currently used, but needed for being element in std::vector.
569 PositionCacheEntry::PositionCacheEntry(const PositionCacheEntry
&other
) :
570 styleNumber(other
.styleNumber
), len(other
.styleNumber
), clock(other
.styleNumber
), positions(nullptr) {
571 if (other
.positions
) {
572 const size_t lenData
= len
+ (len
/ sizeof(XYPOSITION
)) + 1;
573 positions
.reset(new XYPOSITION
[lenData
]);
574 memcpy(positions
.get(), other
.positions
.get(), lenData
* sizeof(XYPOSITION
));
578 void PositionCacheEntry::Set(unsigned int styleNumber_
, const char *s_
,
579 unsigned int len_
, const XYPOSITION
*positions_
, unsigned int clock_
) {
581 styleNumber
= styleNumber_
;
584 if (s_
&& positions_
) {
585 positions
.reset(new XYPOSITION
[len
+ (len
/ sizeof(XYPOSITION
)) + 1]);
586 for (unsigned int i
=0; i
<len
; i
++) {
587 positions
[i
] = positions_
[i
];
589 memcpy(&positions
[len
], s_
, len
);
593 PositionCacheEntry::~PositionCacheEntry() {
597 void PositionCacheEntry::Clear() noexcept
{
604 bool PositionCacheEntry::Retrieve(unsigned int styleNumber_
, const char *s_
,
605 unsigned int len_
, XYPOSITION
*positions_
) const {
606 if ((styleNumber
== styleNumber_
) && (len
== len_
) &&
607 (memcmp(&positions
[len
], s_
, len
)== 0)) {
608 for (unsigned int i
=0; i
<len
; i
++) {
609 positions_
[i
] = positions
[i
];
617 unsigned int PositionCacheEntry::Hash(unsigned int styleNumber_
, const char *s
, unsigned int len_
) noexcept
{
618 unsigned int ret
= s
[0] << 7;
619 for (unsigned int i
=0; i
<len_
; i
++) {
630 bool PositionCacheEntry::NewerThan(const PositionCacheEntry
&other
) const noexcept
{
631 return clock
> other
.clock
;
634 void PositionCacheEntry::ResetClock() noexcept
{
640 PositionCache::PositionCache() {
646 PositionCache::~PositionCache() {
650 void PositionCache::Clear() noexcept
{
652 for (PositionCacheEntry
&pce
: pces
) {
660 void PositionCache::SetSize(size_t size_
) {
665 void PositionCache::MeasureWidths(Surface
*surface
, const ViewStyle
&vstyle
, unsigned int styleNumber
,
666 const char *s
, unsigned int len
, XYPOSITION
*positions
, const Document
*pdoc
) {
669 size_t probe
= pces
.size(); // Out of bounds
670 if ((!pces
.empty()) && (len
< 30)) {
671 // Only store short strings in the cache so it doesn't churn with
672 // long comments with only a single comment.
674 // Two way associative: try two probe positions.
675 const unsigned int hashValue
= PositionCacheEntry::Hash(styleNumber
, s
, len
);
676 probe
= hashValue
% pces
.size();
677 if (pces
[probe
].Retrieve(styleNumber
, s
, len
, positions
)) {
680 const unsigned int probe2
= (hashValue
* 37) % pces
.size();
681 if (pces
[probe2
].Retrieve(styleNumber
, s
, len
, positions
)) {
684 // Not found. Choose the oldest of the two slots to replace
685 if (pces
[probe
].NewerThan(pces
[probe2
])) {
689 if (len
> BreakFinder::lengthStartSubdivision
) {
690 // Break up into segments
691 unsigned int startSegment
= 0;
692 XYPOSITION xStartSegment
= 0;
693 while (startSegment
< len
) {
694 const unsigned int lenSegment
= pdoc
->SafeSegment(s
+ startSegment
, len
- startSegment
, BreakFinder::lengthEachSubdivision
);
695 FontAlias fontStyle
= vstyle
.styles
[styleNumber
].font
;
696 surface
->MeasureWidths(fontStyle
, s
+ startSegment
, lenSegment
, positions
+ startSegment
);
697 for (unsigned int inSeg
= 0; inSeg
< lenSegment
; inSeg
++) {
698 positions
[startSegment
+ inSeg
] += xStartSegment
;
700 xStartSegment
= positions
[startSegment
+ lenSegment
- 1];
701 startSegment
+= lenSegment
;
704 FontAlias fontStyle
= vstyle
.styles
[styleNumber
].font
;
705 surface
->MeasureWidths(fontStyle
, s
, len
, positions
);
707 if (probe
< pces
.size()) {
711 // Since there are only 16 bits for the clock, wrap it round and
712 // reset all cache entries so none get stuck with a high clock.
713 for (PositionCacheEntry
&pce
: pces
) {
718 pces
[probe
].Set(styleNumber
, s
, len
, positions
, clock
);