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.
15 #include "Scintilla.h"
17 #include "SplitVector.h"
18 #include "Partitioning.h"
19 #include "RunStyles.h"
20 #include "ContractionState.h"
21 #include "CellBuffer.h"
23 #include "Indicator.h"
25 #include "LineMarker.h"
27 #include "ViewStyle.h"
28 #include "CharClassify.h"
29 #include "Decoration.h"
31 #include "PositionCache.h"
34 using namespace Scintilla
;
37 static inline bool IsControlCharacter(int ch
) {
38 // iscntrl returns true for lots of chars > 127 which are displayable
39 return ch
>= 0 && ch
< ' ';
42 LineLayout::LineLayout(int maxLineLength_
) :
63 widthLine(wrapWidthInfinite
),
65 Resize(maxLineLength_
);
68 LineLayout::~LineLayout() {
72 void LineLayout::Resize(int maxLineLength_
) {
73 if (maxLineLength_
> maxLineLength
) {
75 chars
= new char[maxLineLength_
+ 1];
76 styles
= new unsigned char[maxLineLength_
+ 1];
77 indicators
= new char[maxLineLength_
+ 1];
78 // Extra position allocated as sometimes the Windows
79 // GetTextExtentExPoint API writes an extra element.
80 positions
= new int[maxLineLength_
+ 1 + 1];
81 maxLineLength
= maxLineLength_
;
85 void LineLayout::Free() {
98 void LineLayout::Invalidate(validLevel validity_
) {
99 if (validity
> validity_
)
100 validity
= validity_
;
103 int LineLayout::LineStart(int line
) const {
106 } else if ((line
>= lines
) || !lineStarts
) {
107 return numCharsInLine
;
109 return lineStarts
[line
];
113 int LineLayout::LineLastVisible(int line
) const {
116 } else if ((line
>= lines
-1) || !lineStarts
) {
117 int startLine
= LineStart(line
);
118 int endLine
= numCharsInLine
;
119 while ((endLine
> startLine
) && IsEOLChar(chars
[endLine
-1])) {
124 return lineStarts
[line
+1];
128 bool LineLayout::InLine(int offset
, int line
) const {
129 return ((offset
>= LineStart(line
)) && (offset
< LineStart(line
+ 1)) ||
130 ((offset
== numCharsInLine
) && (line
== (lines
-1))));
133 void LineLayout::SetLineStart(int line
, int start
) {
134 if ((line
>= lenLineStarts
) && (line
!= 0)) {
135 int newMaxLines
= line
+ 20;
136 int *newLineStarts
= new int[newMaxLines
];
139 for (int i
= 0; i
< newMaxLines
; i
++) {
140 if (i
< lenLineStarts
)
141 newLineStarts
[i
] = lineStarts
[i
];
143 newLineStarts
[i
] = 0;
146 lineStarts
= newLineStarts
;
147 lenLineStarts
= newMaxLines
;
149 lineStarts
[line
] = start
;
152 void LineLayout::SetBracesHighlight(Range rangeLine
, Position braces
[],
153 char bracesMatchStyle
, int xHighlight
) {
154 if (rangeLine
.ContainsCharacter(braces
[0])) {
155 int braceOffset
= braces
[0] - rangeLine
.start
;
156 if (braceOffset
< numCharsInLine
) {
157 bracePreviousStyles
[0] = styles
[braceOffset
];
158 styles
[braceOffset
] = bracesMatchStyle
;
161 if (rangeLine
.ContainsCharacter(braces
[1])) {
162 int braceOffset
= braces
[1] - rangeLine
.start
;
163 if (braceOffset
< numCharsInLine
) {
164 bracePreviousStyles
[1] = styles
[braceOffset
];
165 styles
[braceOffset
] = bracesMatchStyle
;
168 if ((braces
[0] >= rangeLine
.start
&& braces
[1] <= rangeLine
.end
) ||
169 (braces
[1] >= rangeLine
.start
&& braces
[0] <= rangeLine
.end
)) {
170 xHighlightGuide
= xHighlight
;
174 void LineLayout::RestoreBracesHighlight(Range rangeLine
, Position braces
[]) {
175 if (rangeLine
.ContainsCharacter(braces
[0])) {
176 int braceOffset
= braces
[0] - rangeLine
.start
;
177 if (braceOffset
< numCharsInLine
) {
178 styles
[braceOffset
] = bracePreviousStyles
[0];
181 if (rangeLine
.ContainsCharacter(braces
[1])) {
182 int braceOffset
= braces
[1] - rangeLine
.start
;
183 if (braceOffset
< numCharsInLine
) {
184 styles
[braceOffset
] = bracePreviousStyles
[1];
190 int LineLayout::FindBefore(int x
, int lower
, int upper
) const {
192 int middle
= (upper
+ lower
+ 1) / 2; // Round high
193 int posMiddle
= positions
[middle
];
199 } while (lower
< upper
);
203 LineLayoutCache::LineLayoutCache() :
204 level(0), length(0), size(0), cache(0),
205 allInvalidated(false), styleClock(-1), useCount(0) {
209 LineLayoutCache::~LineLayoutCache() {
213 void LineLayoutCache::Allocate(int length_
) {
214 PLATFORM_ASSERT(cache
== NULL
);
215 allInvalidated
= false;
219 size
= (size
/ 16 + 1) * 16;
222 cache
= new LineLayout
* [size
];
224 for (int i
= 0; i
< size
; i
++)
228 void LineLayoutCache::AllocateForLevel(int linesOnScreen
, int linesInDoc
) {
229 PLATFORM_ASSERT(useCount
== 0);
230 int lengthForLevel
= 0;
231 if (level
== llcCaret
) {
233 } else if (level
== llcPage
) {
234 lengthForLevel
= linesOnScreen
+ 1;
235 } else if (level
== llcDocument
) {
236 lengthForLevel
= linesInDoc
;
238 if (lengthForLevel
> size
) {
240 Allocate(lengthForLevel
);
242 if (lengthForLevel
< length
) {
243 for (int i
= lengthForLevel
; i
< length
; i
++) {
248 length
= lengthForLevel
;
250 PLATFORM_ASSERT(length
== lengthForLevel
);
251 PLATFORM_ASSERT(cache
!= NULL
|| length
== 0);
254 void LineLayoutCache::Deallocate() {
255 PLATFORM_ASSERT(useCount
== 0);
256 for (int i
= 0; i
< length
; i
++)
264 void LineLayoutCache::Invalidate(LineLayout::validLevel validity_
) {
265 if (cache
&& !allInvalidated
) {
266 for (int i
= 0; i
< length
; i
++) {
268 cache
[i
]->Invalidate(validity_
);
271 if (validity_
== LineLayout::llInvalid
) {
272 allInvalidated
= true;
277 void LineLayoutCache::SetLevel(int level_
) {
278 allInvalidated
= false;
279 if ((level_
!= -1) && (level
!= level_
)) {
285 LineLayout
*LineLayoutCache::Retrieve(int lineNumber
, int lineCaret
, int maxChars
, int styleClock_
,
286 int linesOnScreen
, int linesInDoc
) {
287 AllocateForLevel(linesOnScreen
, linesInDoc
);
288 if (styleClock
!= styleClock_
) {
289 Invalidate(LineLayout::llCheckTextAndStyle
);
290 styleClock
= styleClock_
;
292 allInvalidated
= false;
295 if (level
== llcCaret
) {
297 } else if (level
== llcPage
) {
298 if (lineNumber
== lineCaret
) {
300 } else if (length
> 1) {
301 pos
= 1 + (lineNumber
% (length
- 1));
303 } else if (level
== llcDocument
) {
307 PLATFORM_ASSERT(useCount
== 0);
308 if (cache
&& (pos
< length
)) {
310 if ((cache
[pos
]->lineNumber
!= lineNumber
) ||
311 (cache
[pos
]->maxLineLength
< maxChars
)) {
317 cache
[pos
] = new LineLayout(maxChars
);
320 cache
[pos
]->lineNumber
= lineNumber
;
321 cache
[pos
]->inCache
= true;
329 ret
= new LineLayout(maxChars
);
330 ret
->lineNumber
= lineNumber
;
336 void LineLayoutCache::Dispose(LineLayout
*ll
) {
337 allInvalidated
= false;
347 void BreakFinder::Insert(int val
) {
349 if (saeLen
>= saeSize
) {
351 int *selAndEdgeNew
= new int[saeSize
];
352 for (unsigned int j
= 0; j
<saeLen
; j
++) {
353 selAndEdgeNew
[j
] = selAndEdge
[j
];
356 selAndEdge
= selAndEdgeNew
;
359 if (val
>= nextBreak
) {
360 for (unsigned int j
= 0; j
<saeLen
; j
++) {
361 if (val
== selAndEdge
[j
]) {
363 } if (val
< selAndEdge
[j
]) {
364 for (unsigned int k
= saeLen
; k
>j
; k
--) {
365 selAndEdge
[k
] = selAndEdge
[k
-1];
372 // Not less than any so append
373 selAndEdge
[saeLen
++] = val
;
377 extern bool BadUTF(const char *s
, int len
, int &trailBytes
);
379 static int NextBadU(const char *s
, int p
, int len
, int &trailBytes
) {
382 if (BadUTF(s
+ p
, len
- p
, trailBytes
))
388 BreakFinder::BreakFinder(LineLayout
*ll_
, int lineStart_
, int lineEnd_
, int posLineStart_
, bool utf8_
, int xStart
) :
390 lineStart(lineStart_
),
392 posLineStart(posLineStart_
),
394 nextBreak(lineStart_
),
401 selAndEdge
= new int[saeSize
];
402 for (unsigned int j
=0; j
< saeSize
; j
++) {
406 // Search for first visible break
407 // First find the first visible character
408 nextBreak
= ll
->FindBefore(xStart
, lineStart
, lineEnd
);
409 // Now back to a style break
410 while ((nextBreak
> lineStart
) && (ll
->styles
[nextBreak
] == ll
->styles
[nextBreak
- 1])) {
414 if (ll
->selStart
!= ll
->selEnd
) {
415 Insert(ll
->selStart
- posLineStart
- 1);
416 Insert(ll
->selEnd
- posLineStart
- 1);
419 Insert(ll
->edgeColumn
- 1);
424 for (int pos
= -1;;) {
425 pos
= NextBadU(ll
->chars
, pos
, lineEnd
, trailBytes
);
432 saeNext
= (saeLen
> 0) ? selAndEdge
[0] : -1;
435 BreakFinder::~BreakFinder() {
439 int BreakFinder::First() {
443 int BreakFinder::Next() {
444 if (subBreak
== -1) {
445 int prev
= nextBreak
;
446 while (nextBreak
< lineEnd
) {
447 if ((ll
->styles
[nextBreak
] != ll
->styles
[nextBreak
+ 1]) ||
448 (nextBreak
== saeNext
) ||
449 IsControlCharacter(ll
->chars
[nextBreak
]) || IsControlCharacter(ll
->chars
[nextBreak
+ 1])) {
450 if (nextBreak
== saeNext
) {
452 saeNext
= (saeLen
> saeCurrentPos
) ? selAndEdge
[saeCurrentPos
] : -1;
455 if ((nextBreak
- prev
) < lengthStartSubdivision
) {
462 if ((nextBreak
- prev
) < lengthStartSubdivision
) {
467 // Splitting up a long run from prev to nextBreak in lots of approximately lengthEachSubdivision.
468 // For very long runs add extra breaks after spaces or if no spaces before low punctuation.
469 if ((nextBreak
- subBreak
) <= lengthEachSubdivision
) {
473 int lastGoodBreak
= -1;
474 int lastOKBreak
= -1;
476 for (j
= subBreak
+ 1; j
<= nextBreak
; j
++) {
477 if (IsSpaceOrTab(ll
->chars
[j
- 1]) && !IsSpaceOrTab(ll
->chars
[j
])) {
480 if (ll
->chars
[j
] < 'A') {
483 if (((j
- subBreak
) >= lengthEachSubdivision
) && ((lastGoodBreak
>= 0) || (lastOKBreak
>= 0))) {
487 if (lastGoodBreak
>= 0) {
488 subBreak
= lastGoodBreak
;
489 } else if (lastOKBreak
>= 0) {
490 subBreak
= lastOKBreak
;
492 subBreak
= nextBreak
;
494 if (subBreak
>= nextBreak
) {
503 PositionCacheEntry::PositionCacheEntry() :
504 styleNumber(0), len(0), clock(0), positions(0) {
507 void PositionCacheEntry::Set(unsigned int styleNumber_
, const char *s_
,
508 unsigned int len_
, int *positions_
, unsigned int clock_
) {
510 styleNumber
= styleNumber_
;
513 if (s_
&& positions_
) {
514 positions
= new short[len
+ (len
+ 1) / 2];
515 for (unsigned int i
=0;i
<len
;i
++) {
516 positions
[i
] = static_cast<short>(positions_
[i
]);
518 memcpy(reinterpret_cast<char *>(positions
+ len
), s_
, len
);
522 PositionCacheEntry::~PositionCacheEntry() {
526 void PositionCacheEntry::Clear() {
534 bool PositionCacheEntry::Retrieve(unsigned int styleNumber_
, const char *s_
,
535 unsigned int len_
, int *positions_
) const {
536 if ((styleNumber
== styleNumber_
) && (len
== len_
) &&
537 (memcmp(reinterpret_cast<char *>(positions
+ len
), s_
, len
)== 0)) {
538 for (unsigned int i
=0;i
<len
;i
++) {
539 positions_
[i
] = positions
[i
];
547 int PositionCacheEntry::Hash(unsigned int styleNumber
, const char *s
, unsigned int len
) {
548 unsigned int ret
= s
[0] << 7;
549 for (unsigned int i
=0; i
<len
; i
++) {
560 bool PositionCacheEntry::NewerThan(const PositionCacheEntry
&other
) {
561 return clock
> other
.clock
;
564 void PositionCacheEntry::ResetClock() {
570 PositionCache::PositionCache() {
573 pces
= new PositionCacheEntry
[size
];
577 PositionCache::~PositionCache() {
582 void PositionCache::Clear() {
584 for (size_t i
=0;i
<size
;i
++) {
592 void PositionCache::SetSize(size_t size_
) {
596 pces
= new PositionCacheEntry
[size
];
599 void PositionCache::MeasureWidths(Surface
*surface
, ViewStyle
&vstyle
, unsigned int styleNumber
,
600 const char *s
, unsigned int len
, int *positions
) {
603 if ((size
> 0) && (len
< 30)) {
604 // Only store short strings in the cache so it doesn't churn with
605 // long comments with only a single comment.
607 // Two way associative: try two probe positions.
608 int hashValue
= PositionCacheEntry::Hash(styleNumber
, s
, len
);
609 probe
= hashValue
% size
;
610 if (pces
[probe
].Retrieve(styleNumber
, s
, len
, positions
)) {
613 int probe2
= (hashValue
* 37) % size
;
614 if (pces
[probe2
].Retrieve(styleNumber
, s
, len
, positions
)) {
617 // Not found. Choose the oldest of the two slots to replace
618 if (pces
[probe
].NewerThan(pces
[probe2
])) {
622 surface
->MeasureWidths(vstyle
.styles
[styleNumber
].font
, s
, len
, positions
);
626 // Since there are only 16 bits for the clock, wrap it round and
627 // reset all cache entries so none get stuck with a high clock.
628 for (size_t i
=0;i
<size
;i
++) {
629 pces
[i
].ResetClock();
633 pces
[probe
].Set(styleNumber
, s
, len
, positions
, clock
);