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>
26 #include "ScintillaTypes.h"
27 #include "ScintillaMessages.h"
31 #include "Debugging.h"
35 #include "CharacterType.h"
36 #include "CharacterCategoryMap.h"
38 #include "UniqueString.h"
39 #include "SplitVector.h"
40 #include "Partitioning.h"
41 #include "RunStyles.h"
42 #include "ContractionState.h"
43 #include "CellBuffer.h"
45 #include "Indicator.h"
46 #include "LineMarker.h"
48 #include "ViewStyle.h"
49 #include "CharClassify.h"
50 #include "Decoration.h"
51 #include "CaseFolder.h"
53 #include "UniConversion.h"
55 #include "Selection.h"
56 #include "PositionCache.h"
58 using namespace Scintilla
;
59 using namespace Scintilla::Internal
;
61 void BidiData::Resize(size_t maxLineLength_
) {
62 stylesFonts
.resize(maxLineLength_
+ 1);
63 widthReprs
.resize(maxLineLength_
+ 1);
66 LineLayout::LineLayout(Sci::Line lineNumber_
, int maxLineLength_
) :
68 lineNumber(lineNumber_
),
72 validity(ValidLevel::invalid
),
74 highlightColumn(false),
77 bracePreviousStyles
{},
78 widthLine(wrapWidthInfinite
),
81 Resize(maxLineLength_
);
84 LineLayout::~LineLayout() {
88 void LineLayout::Resize(int maxLineLength_
) {
89 if (maxLineLength_
> maxLineLength
) {
91 const size_t lineAllocation
= maxLineLength_
+ 1;
92 chars
= std::make_unique
<char[]>(lineAllocation
);
93 styles
= std::make_unique
<unsigned char []>(lineAllocation
);
94 // Extra position allocated as sometimes the Windows
95 // GetTextExtentExPoint API writes an extra element.
96 positions
= std::make_unique
<XYPOSITION
[]>(lineAllocation
+ 1);
98 bidiData
->Resize(maxLineLength_
);
101 maxLineLength
= maxLineLength_
;
105 void LineLayout::ReSet(Sci::Line lineNumber_
, Sci::Position maxLineLength_
) {
106 lineNumber
= lineNumber_
;
107 Resize(static_cast<int>(maxLineLength_
));
109 Invalidate(ValidLevel::invalid
);
112 void LineLayout::EnsureBidiData() {
114 bidiData
= std::make_unique
<BidiData
>();
115 bidiData
->Resize(maxLineLength
);
119 void LineLayout::Free() noexcept
{
128 void LineLayout::ClearPositions() {
129 std::fill(&positions
[0], &positions
[maxLineLength
+ 2], 0.0f
);
132 void LineLayout::Invalidate(ValidLevel validity_
) noexcept
{
133 if (validity
> validity_
)
134 validity
= validity_
;
137 Sci::Line
LineLayout::LineNumber() const noexcept
{
141 bool LineLayout::CanHold(Sci::Line lineDoc
, int lineLength_
) const noexcept
{
142 return (lineNumber
== lineDoc
) && (lineLength_
<= maxLineLength
);
145 int LineLayout::LineStart(int line
) const noexcept
{
148 } else if ((line
>= lines
) || !lineStarts
) {
149 return numCharsInLine
;
151 return lineStarts
[line
];
155 int LineLayout::LineLength(int line
) const noexcept
{
157 return numCharsInLine
;
158 } if (line
>= lines
- 1) {
159 return numCharsInLine
- lineStarts
[line
];
161 return lineStarts
[line
+ 1] - lineStarts
[line
];
165 int LineLayout::LineLastVisible(int line
, Scope scope
) const noexcept
{
168 } else if ((line
>= lines
-1) || !lineStarts
) {
169 return scope
== Scope::visibleOnly
? numCharsBeforeEOL
: numCharsInLine
;
171 return lineStarts
[line
+1];
175 Range
LineLayout::SubLineRange(int subLine
, Scope scope
) const noexcept
{
176 return Range(LineStart(subLine
), LineLastVisible(subLine
, scope
));
179 bool LineLayout::InLine(int offset
, int line
) const noexcept
{
180 return ((offset
>= LineStart(line
)) && (offset
< LineStart(line
+ 1))) ||
181 ((offset
== numCharsInLine
) && (line
== (lines
-1)));
184 int LineLayout::SubLineFromPosition(int posInLine
, PointEnd pe
) const noexcept
{
185 if (!lineStarts
|| (posInLine
> maxLineLength
)) {
189 for (int line
= 0; line
< lines
; line
++) {
190 if (FlagSet(pe
, PointEnd::subLineEnd
)) {
191 // Return subline not start of next
192 if (lineStarts
[line
+ 1] <= posInLine
+ 1)
195 if (lineStarts
[line
+ 1] <= posInLine
)
203 void LineLayout::AddLineStart(Sci::Position start
) {
205 if (lines
>= lenLineStarts
) {
206 const int newMaxLines
= lines
+ 20;
207 std::unique_ptr
<int[]> newLineStarts
= std::make_unique
<int[]>(newMaxLines
);
209 std::copy(lineStarts
.get(), lineStarts
.get() + lenLineStarts
, newLineStarts
.get());
211 lineStarts
= std::move(newLineStarts
);
212 lenLineStarts
= newMaxLines
;
214 lineStarts
[lines
] = static_cast<int>(start
);
217 void LineLayout::SetBracesHighlight(Range rangeLine
, const Sci::Position braces
[],
218 char bracesMatchStyle
, int xHighlight
, bool ignoreStyle
) {
219 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[0])) {
220 const Sci::Position braceOffset
= braces
[0] - rangeLine
.start
;
221 if (braceOffset
< numCharsInLine
) {
222 bracePreviousStyles
[0] = styles
[braceOffset
];
223 styles
[braceOffset
] = bracesMatchStyle
;
226 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[1])) {
227 const Sci::Position braceOffset
= braces
[1] - rangeLine
.start
;
228 if (braceOffset
< numCharsInLine
) {
229 bracePreviousStyles
[1] = styles
[braceOffset
];
230 styles
[braceOffset
] = bracesMatchStyle
;
233 if ((braces
[0] >= rangeLine
.start
&& braces
[1] <= rangeLine
.end
) ||
234 (braces
[1] >= rangeLine
.start
&& braces
[0] <= rangeLine
.end
)) {
235 xHighlightGuide
= xHighlight
;
239 void LineLayout::RestoreBracesHighlight(Range rangeLine
, const Sci::Position braces
[], bool ignoreStyle
) {
240 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[0])) {
241 const Sci::Position braceOffset
= braces
[0] - rangeLine
.start
;
242 if (braceOffset
< numCharsInLine
) {
243 styles
[braceOffset
] = bracePreviousStyles
[0];
246 if (!ignoreStyle
&& rangeLine
.ContainsCharacter(braces
[1])) {
247 const Sci::Position braceOffset
= braces
[1] - rangeLine
.start
;
248 if (braceOffset
< numCharsInLine
) {
249 styles
[braceOffset
] = bracePreviousStyles
[1];
255 int LineLayout::FindBefore(XYPOSITION x
, Range range
) const noexcept
{
256 Sci::Position lower
= range
.start
;
257 Sci::Position upper
= range
.end
;
259 const Sci::Position middle
= (upper
+ lower
+ 1) / 2; // Round high
260 const XYPOSITION posMiddle
= positions
[middle
];
266 } while (lower
< upper
);
267 return static_cast<int>(lower
);
271 int LineLayout::FindPositionFromX(XYPOSITION x
, Range range
, bool charPosition
) const noexcept
{
272 int pos
= FindBefore(x
, range
);
273 while (pos
< range
.end
) {
275 if (x
< (positions
[pos
+ 1])) {
279 if (x
< ((positions
[pos
] + positions
[pos
+ 1]) / 2)) {
285 return static_cast<int>(range
.end
);
288 Point
LineLayout::PointFromPosition(int posInLine
, int lineHeight
, PointEnd pe
) const noexcept
{
290 // In case of very long line put x at arbitrary large position
291 if (posInLine
> maxLineLength
) {
292 pt
.x
= positions
[maxLineLength
] - positions
[LineStart(lines
)];
295 for (int subLine
= 0; subLine
< lines
; subLine
++) {
296 const Range rangeSubLine
= SubLineRange(subLine
, Scope::visibleOnly
);
297 if (posInLine
>= rangeSubLine
.start
) {
298 pt
.y
= static_cast<XYPOSITION
>(subLine
*lineHeight
);
299 if (posInLine
<= rangeSubLine
.end
) {
300 pt
.x
= positions
[posInLine
] - positions
[rangeSubLine
.start
];
301 if (rangeSubLine
.start
!= 0) // Wrapped lines may be indented
303 if (FlagSet(pe
, PointEnd::subLineEnd
)) // Return end of first subline not start of next
305 } else if (FlagSet(pe
, PointEnd::lineEnd
) && (subLine
== (lines
-1))) {
306 pt
.x
= positions
[numCharsInLine
] - positions
[rangeSubLine
.start
];
307 if (rangeSubLine
.start
!= 0) // Wrapped lines may be indented
317 XYPOSITION
LineLayout::XInLine(Sci::Position index
) const noexcept
{
318 // For positions inside line return value from positions
319 // For positions after line return last position + 1.0
320 if (index
<= numCharsInLine
) {
321 return positions
[index
];
323 return positions
[numCharsInLine
] + 1.0;
326 Interval
LineLayout::Span(int start
, int end
) const noexcept
{
327 return { positions
[start
], positions
[end
] };
330 Interval
LineLayout::SpanByte(int index
) const noexcept
{
331 return Span(index
, index
+1);
334 int LineLayout::EndLineStyle() const noexcept
{
335 return styles
[numCharsBeforeEOL
> 0 ? numCharsBeforeEOL
-1 : 0];
338 void LineLayout::WrapLine(const Document
*pdoc
, Sci::Position posLineStart
, Wrap wrapState
, XYPOSITION wrapWidth
) {
339 // Document wants document positions but simpler to work in line positions
340 // so take care of adding and subtracting line start in a lambda.
341 auto CharacterBoundary
= [=](Sci::Position i
, Sci::Position moveDir
) noexcept
-> Sci::Position
{
342 return pdoc
->MovePositionOutsideChar(i
+ posLineStart
, moveDir
) - posLineStart
;
345 // Calculate line start positions based upon width.
346 Sci::Position lastLineStart
= 0;
347 XYPOSITION startOffset
= wrapWidth
;
349 while (p
< numCharsInLine
) {
350 while (p
< numCharsInLine
&& positions
[p
+ 1] < startOffset
) {
353 if (p
< numCharsInLine
) {
354 // backtrack to find lastGoodBreak
355 Sci::Position lastGoodBreak
= p
;
357 lastGoodBreak
= CharacterBoundary(p
, -1);
359 if (wrapState
!= Wrap::Char
) {
360 Sci::Position pos
= lastGoodBreak
;
361 while (pos
> lastLineStart
) {
362 // style boundary and space
363 if (wrapState
!= Wrap::WhiteSpace
&& (styles
[pos
- 1] != styles
[pos
])) {
366 if (IsBreakSpace(chars
[pos
- 1]) && !IsBreakSpace(chars
[pos
])) {
369 pos
= CharacterBoundary(pos
- 1, -1);
371 if (pos
> lastLineStart
) {
375 if (lastGoodBreak
== lastLineStart
) {
376 // Try moving to start of last character
378 lastGoodBreak
= CharacterBoundary(p
, -1);
380 if (lastGoodBreak
== lastLineStart
) {
381 // Ensure at least one character on line.
382 lastGoodBreak
= CharacterBoundary(lastGoodBreak
+ 1, 1);
385 lastLineStart
= lastGoodBreak
;
386 AddLineStart(lastLineStart
);
387 startOffset
= positions
[lastLineStart
];
388 // take into account the space for start wrap mark and indent
389 startOffset
+= wrapWidth
- wrapIndent
;
390 p
= lastLineStart
+ 1;
396 ScreenLine::ScreenLine(
397 const LineLayout
*ll_
,
401 int tabWidthMinimumPixels_
) :
403 start(ll
->LineStart(subLine
)),
404 len(ll
->LineLength(subLine
)),
406 height(static_cast<float>(vs
.lineHeight
)),
407 ctrlCharPadding(vs
.ctrlCharPadding
),
408 tabWidth(vs
.tabWidth
),
409 tabWidthMinimumPixels(tabWidthMinimumPixels_
) {
412 ScreenLine::~ScreenLine() {
415 std::string_view
ScreenLine::Text() const {
416 return std::string_view(&ll
->chars
[start
], len
);
419 size_t ScreenLine::Length() const {
423 size_t ScreenLine::RepresentationCount() const {
424 return std::count_if(&ll
->bidiData
->widthReprs
[start
],
425 &ll
->bidiData
->widthReprs
[start
+ len
],
426 [](XYPOSITION w
) noexcept
{ return w
> 0.0f
; });
429 XYPOSITION
ScreenLine::Width() const {
433 XYPOSITION
ScreenLine::Height() const {
437 XYPOSITION
ScreenLine::TabWidth() const {
441 XYPOSITION
ScreenLine::TabWidthMinimumPixels() const {
442 return static_cast<XYPOSITION
>(tabWidthMinimumPixels
);
445 const Font
*ScreenLine::FontOfPosition(size_t position
) const {
446 return ll
->bidiData
->stylesFonts
[start
+ position
].get();
449 XYPOSITION
ScreenLine::RepresentationWidth(size_t position
) const {
450 return ll
->bidiData
->widthReprs
[start
+ position
];
453 XYPOSITION
ScreenLine::TabPositionAfter(XYPOSITION xPosition
) const {
454 return (std::floor((xPosition
+ TabWidthMinimumPixels()) / TabWidth()) + 1) * TabWidth();
457 bool SignificantLines::LineMayCache(Sci::Line line
) const noexcept
{
459 case LineCache::None
:
461 case LineCache::Caret
:
462 return line
== lineCaret
;
463 case LineCache::Page
:
464 return (std::abs(line
- lineCaret
) < linesOnScreen
) ||
465 ((line
>= lineTop
) && (line
<= (lineTop
+ linesOnScreen
)));
466 case LineCache::Document
:
472 LineLayoutCache::LineLayoutCache() :
473 level(LineCache::None
),
474 allInvalidated(false), styleClock(-1) {
477 LineLayoutCache::~LineLayoutCache() = default;
481 constexpr size_t AlignUp(size_t value
, size_t alignment
) noexcept
{
482 return ((value
- 1) / alignment
+ 1) * alignment
;
485 constexpr size_t alignmentLLC
= 20;
487 constexpr bool GraphicASCII(char ch
) noexcept
{
488 return ch
>= ' ' && ch
<= '~';
491 bool AllGraphicASCII(std::string_view text
) {
492 return std::all_of(text
.cbegin(), text
.cend(), GraphicASCII
);
498 size_t LineLayoutCache::EntryForLine(Sci::Line line
) const noexcept
{
500 case LineCache::None
:
502 case LineCache::Caret
:
504 case LineCache::Page
:
505 return 1 + (line
% (cache
.size() - 1));
506 case LineCache::Document
:
512 void LineLayoutCache::AllocateForLevel(Sci::Line linesOnScreen
, Sci::Line linesInDoc
) {
513 size_t lengthForLevel
= 0;
514 if (level
== LineCache::Caret
) {
516 } else if (level
== LineCache::Page
) {
517 lengthForLevel
= AlignUp(linesOnScreen
+ 1, alignmentLLC
);
518 } else if (level
== LineCache::Document
) {
519 lengthForLevel
= AlignUp(linesInDoc
, alignmentLLC
);
522 if (lengthForLevel
!= cache
.size()) {
523 allInvalidated
= false;
524 cache
.resize(lengthForLevel
);
525 // Cache::none -> no entries
526 // Cache::caret -> 1 entry can take any line
527 // Cache::document -> entry per line so each line in correct entry after resize
528 if (level
== LineCache::Page
) {
529 // Cache::page -> locates lines in particular entries which may be incorrect after
530 // a resize so move them to correct entries.
531 for (size_t i
= 1; i
< cache
.size();) {
532 size_t increment
= 1;
534 const size_t posForLine
= EntryForLine(cache
[i
]->LineNumber());
535 if (posForLine
!= i
) {
536 if (cache
[posForLine
]) {
537 if (EntryForLine(cache
[posForLine
]->LineNumber()) == posForLine
) {
538 // [posForLine] already holds line that is in correct place
539 cache
[i
].reset(); // This line has nowhere to go so reset it.
541 std::swap(cache
[i
], cache
[posForLine
]);
543 // Don't increment as newly swapped in value may have to move
546 cache
[posForLine
] = std::move(cache
[i
]);
554 for (size_t i
= 1; i
< cache
.size(); i
++) {
556 PLATFORM_ASSERT(EntryForLine(cache
[i
]->LineNumber()) == i
);
562 PLATFORM_ASSERT(cache
.size() == lengthForLevel
);
565 void LineLayoutCache::Deallocate() noexcept
{
569 void LineLayoutCache::Invalidate(LineLayout::ValidLevel validity_
) noexcept
{
570 if (!cache
.empty() && !allInvalidated
) {
571 for (const std::shared_ptr
<LineLayout
> &ll
: cache
) {
573 ll
->Invalidate(validity_
);
576 if (validity_
== LineLayout::ValidLevel::invalid
) {
577 allInvalidated
= true;
582 void LineLayoutCache::SetLevel(LineCache level_
) noexcept
{
583 if (level
!= level_
) {
585 allInvalidated
= false;
590 std::shared_ptr
<LineLayout
> LineLayoutCache::Retrieve(Sci::Line lineNumber
, Sci::Line lineCaret
, int maxChars
, int styleClock_
,
591 Sci::Line linesOnScreen
, Sci::Line linesInDoc
) {
592 AllocateForLevel(linesOnScreen
, linesInDoc
);
593 if (styleClock
!= styleClock_
) {
594 Invalidate(LineLayout::ValidLevel::checkTextAndStyle
);
595 styleClock
= styleClock_
;
597 allInvalidated
= false;
599 if (level
== LineCache::Page
) {
600 // If first entry is this line then just reuse it.
601 if (!(cache
[0] && (cache
[0]->LineNumber() == lineNumber
))) {
602 const size_t posForLine
= EntryForLine(lineNumber
);
603 if (lineNumber
== lineCaret
) {
604 // Use position 0 for caret line.
606 // Another line is currently in [0] so move it out to its normal position.
607 // Since it was recently the caret line its likely to be needed soon.
608 const size_t posNewForEntry0
= EntryForLine(cache
[0]->LineNumber());
609 if (posForLine
== posNewForEntry0
) {
610 std::swap(cache
[0], cache
[posNewForEntry0
]);
612 cache
[posNewForEntry0
] = std::move(cache
[0]);
615 if (cache
[posForLine
] && (cache
[posForLine
]->LineNumber() == lineNumber
)) {
616 // Caret line is currently somewhere else so move it to [0].
617 cache
[0] = std::move(cache
[posForLine
]);
623 } else if (level
== LineCache::Document
) {
627 if (pos
< cache
.size()) {
628 if (cache
[pos
] && !cache
[pos
]->CanHold(lineNumber
, maxChars
)) {
632 cache
[pos
] = std::make_shared
<LineLayout
>(lineNumber
, maxChars
);
635 // Expensive check that there is only one entry for any line number
636 std::vector
<bool> linesInCache(linesInDoc
);
637 for (const auto &entry
: cache
) {
639 PLATFORM_ASSERT(!linesInCache
[entry
->LineNumber()]);
640 linesInCache
[entry
->LineNumber()] = true;
647 // Only reach here for level == Cache::none
648 return std::make_shared
<LineLayout
>(lineNumber
, maxChars
);
653 // Simply pack the (maximum 4) character bytes into an int
654 constexpr unsigned int KeyFromString(std::string_view charBytes
) noexcept
{
655 PLATFORM_ASSERT(charBytes
.length() <= 4);
657 for (const unsigned char uc
: charBytes
) {
663 constexpr unsigned int representationKeyCrLf
= KeyFromString("\r\n");
665 const char *const repsC0
[] = {
666 "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
667 "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
668 "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
669 "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
672 const char *const repsC1
[] = {
673 "PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA",
674 "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3",
675 "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA",
676 "SOS", "SGCI", "SCI", "CSI", "ST", "OSC", "PM", "APC"
681 namespace Scintilla::Internal
{
683 const char *ControlCharacterString(unsigned char ch
) noexcept
{
684 if (ch
< std::size(repsC0
)) {
692 void Hexits(char *hexits
, int ch
) noexcept
{
694 hexits
[1] = "0123456789ABCDEF"[ch
/ 0x10];
695 hexits
[2] = "0123456789ABCDEF"[ch
% 0x10];
701 void SpecialRepresentations::SetRepresentation(std::string_view charBytes
, std::string_view value
) {
702 if ((charBytes
.length() <= 4) && (value
.length() <= Representation::maxLength
)) {
703 const unsigned int key
= KeyFromString(charBytes
);
704 const bool inserted
= mapReprs
.insert_or_assign(key
, Representation(value
)).second
;
706 // New entry so increment for first byte
707 const unsigned char ucStart
= charBytes
.empty() ? 0 : charBytes
[0];
708 startByteHasReprs
[ucStart
]++;
712 if (key
== representationKeyCrLf
) {
719 void SpecialRepresentations::SetRepresentationAppearance(std::string_view charBytes
, RepresentationAppearance appearance
) {
720 if (charBytes
.length() <= 4) {
721 const unsigned int key
= KeyFromString(charBytes
);
722 const MapRepresentation::iterator it
= mapReprs
.find(key
);
723 if (it
== mapReprs
.end()) {
724 // Not present so fail
727 it
->second
.appearance
= appearance
;
731 void SpecialRepresentations::SetRepresentationColour(std::string_view charBytes
, ColourRGBA colour
) {
732 if (charBytes
.length() <= 4) {
733 const unsigned int key
= KeyFromString(charBytes
);
734 const MapRepresentation::iterator it
= mapReprs
.find(key
);
735 if (it
== mapReprs
.end()) {
736 // Not present so fail
739 it
->second
.appearance
= it
->second
.appearance
| RepresentationAppearance::Colour
;
740 it
->second
.colour
= colour
;
744 void SpecialRepresentations::ClearRepresentation(std::string_view charBytes
) {
745 if (charBytes
.length() <= 4) {
746 const unsigned int key
= KeyFromString(charBytes
);
747 const MapRepresentation::iterator it
= mapReprs
.find(key
);
748 if (it
!= mapReprs
.end()) {
750 const unsigned char ucStart
= charBytes
.empty() ? 0 : charBytes
[0];
751 startByteHasReprs
[ucStart
]--;
752 if (key
== maxKey
&& startByteHasReprs
[ucStart
] == 0) {
753 maxKey
= mapReprs
.empty() ? 0 : mapReprs
.crbegin()->first
;
755 if (key
== representationKeyCrLf
) {
762 const Representation
*SpecialRepresentations::GetRepresentation(std::string_view charBytes
) const {
763 const unsigned int key
= KeyFromString(charBytes
);
767 const MapRepresentation::const_iterator it
= mapReprs
.find(key
);
768 if (it
!= mapReprs
.end()) {
769 return &(it
->second
);
774 const Representation
*SpecialRepresentations::RepresentationFromCharacter(std::string_view charBytes
) const {
775 if (charBytes
.length() <= 4) {
776 const unsigned char ucStart
= charBytes
.empty() ? 0 : charBytes
[0];
777 if (!startByteHasReprs
[ucStart
])
779 return GetRepresentation(charBytes
);
784 void SpecialRepresentations::Clear() {
786 constexpr unsigned short none
= 0;
787 std::fill(startByteHasReprs
, std::end(startByteHasReprs
), none
);
792 void SpecialRepresentations::SetDefaultRepresentations(int dbcsCodePage
) {
796 for (size_t j
= 0; j
< std::size(repsC0
); j
++) {
797 const char c
[2] = { static_cast<char>(j
), 0 };
798 SetRepresentation(std::string_view(c
, 1), repsC0
[j
]);
800 SetRepresentation("\x7f", "DEL");
803 // As well as Unicode mode, ISO-8859-1 should use these
804 if (CpUtf8
== dbcsCodePage
) {
805 for (size_t j
= 0; j
< std::size(repsC1
); j
++) {
806 const char c1
[3] = { '\xc2', static_cast<char>(0x80 + j
), 0 };
807 SetRepresentation(c1
, repsC1
[j
]);
809 SetRepresentation("\xe2\x80\xa8", "LS");
810 SetRepresentation("\xe2\x80\xa9", "PS");
813 // Invalid as single bytes in multi-byte encodings
815 for (int k
= 0x80; k
< 0x100; k
++) {
816 if ((CpUtf8
== dbcsCodePage
) || !IsDBCSValidSingleByte(dbcsCodePage
, k
)) {
817 const char hiByte
[2] = { static_cast<char>(k
), 0 };
820 SetRepresentation(hiByte
, hexits
);
826 void BreakFinder::Insert(Sci::Position val
) {
827 const int posInLine
= static_cast<int>(val
);
828 if (posInLine
> nextBreak
) {
829 const std::vector
<int>::iterator it
= std::lower_bound(selAndEdge
.begin(), selAndEdge
.end(), posInLine
);
830 if (it
== selAndEdge
.end()) {
831 selAndEdge
.push_back(posInLine
);
832 } else if (*it
!= posInLine
) {
833 selAndEdge
.insert(it
, 1, posInLine
);
838 BreakFinder::BreakFinder(const LineLayout
*ll_
, const Selection
*psel
, Range lineRange_
, Sci::Position posLineStart
,
839 XYPOSITION xStart
, BreakFor breakFor
, const Document
*pdoc_
, const SpecialRepresentations
*preprs_
, const ViewStyle
*pvsDraw
) :
841 lineRange(lineRange_
),
842 nextBreak(static_cast<int>(lineRange_
.start
)),
847 encodingFamily(pdoc_
->CodePageFamily()),
850 // Search for first visible break
851 // First find the first visible character
853 nextBreak
= ll
->FindBefore(xStart
, lineRange
);
854 // Now back to a style break
855 while ((nextBreak
> lineRange
.start
) && (ll
->styles
[nextBreak
] == ll
->styles
[nextBreak
- 1])) {
859 if (FlagSet(breakFor
, BreakFor::Selection
)) {
860 const SelectionPosition
posStart(posLineStart
);
861 const SelectionPosition
posEnd(posLineStart
+ lineRange
.end
);
862 const SelectionSegment
segmentLine(posStart
, posEnd
);
863 for (size_t r
=0; r
<psel
->Count(); r
++) {
864 const SelectionSegment portion
= psel
->Range(r
).Intersect(segmentLine
);
865 if (!(portion
.start
== portion
.end
)) {
866 if (portion
.start
.IsValid())
867 Insert(portion
.start
.Position() - posLineStart
);
868 if (portion
.end
.IsValid())
869 Insert(portion
.end
.Position() - posLineStart
);
872 // On the curses platform, the terminal is drawing its own caret, so add breaks around the
873 // caret in the main selection in order to help prevent the selection from being drawn in
875 if (FlagSet(pvsDraw
->caret
.style
, CaretStyle::Curses
) && !psel
->RangeMain().Empty()) {
876 const Sci::Position caretPos
= psel
->RangeMain().caret
.Position();
877 const Sci::Position anchorPos
= psel
->RangeMain().anchor
.Position();
878 if (caretPos
< anchorPos
) {
879 const Sci::Position nextPos
= pdoc
->MovePositionOutsideChar(caretPos
+ 1, 1);
880 Insert(nextPos
- posLineStart
);
881 } else if (caretPos
> anchorPos
&& pvsDraw
->DrawCaretInsideSelection(false, false)) {
882 const Sci::Position prevPos
= pdoc
->MovePositionOutsideChar(caretPos
- 1, -1);
883 if (prevPos
> anchorPos
)
884 Insert(prevPos
- posLineStart
);
888 if (FlagSet(breakFor
, BreakFor::Foreground
) && pvsDraw
->indicatorsSetFore
) {
889 for (const IDecoration
*deco
: pdoc
->decorations
->View()) {
890 if (pvsDraw
->indicators
[deco
->Indicator()].OverridesTextFore()) {
891 Sci::Position startPos
= deco
->EndRun(posLineStart
);
892 while (startPos
< (posLineStart
+ lineRange
.end
)) {
893 Insert(startPos
- posLineStart
);
894 startPos
= deco
->EndRun(startPos
);
899 Insert(ll
->edgeColumn
);
900 Insert(lineRange
.end
);
901 saeNext
= (!selAndEdge
.empty()) ? selAndEdge
[0] : -1;
904 BreakFinder::~BreakFinder() noexcept
= default;
906 TextSegment
BreakFinder::Next() {
908 const int prev
= nextBreak
;
909 const Representation
*repr
= nullptr;
910 while (nextBreak
< lineRange
.end
) {
912 const char * const chars
= &ll
->chars
[nextBreak
];
913 const unsigned char ch
= chars
[0];
914 bool characterStyleConsistent
= true; // All bytes of character in same style?
915 if (!UTF8IsAscii(ch
) && encodingFamily
!= EncodingFamily::eightBit
) {
916 if (encodingFamily
== EncodingFamily::unicode
) {
917 charWidth
= UTF8DrawBytes(chars
, lineRange
.end
- nextBreak
);
919 charWidth
= pdoc
->DBCSDrawBytes(std::string_view(chars
, lineRange
.end
- nextBreak
));
921 for (int trail
= 1; trail
< charWidth
; trail
++) {
922 if (ll
->styles
[nextBreak
] != ll
->styles
[nextBreak
+ trail
]) {
923 characterStyleConsistent
= false;
927 if (!characterStyleConsistent
) {
928 if (nextBreak
== prev
) {
929 // Show first character representation bytes since it has inconsistent styles.
932 // Return segment before nextBreak but allow to be split up if too long
933 // If not split up, next call will hit the above 'charWidth = 1;' and display bytes.
938 if (preprs
->MayContain(ch
)) {
939 // Special case \r\n line ends if there is a representation
940 if (ch
== '\r' && preprs
->ContainsCrLf() && chars
[1] == '\n') {
943 repr
= preprs
->GetRepresentation(std::string_view(chars
, charWidth
));
945 if (((nextBreak
> 0) && (ll
->styles
[nextBreak
] != ll
->styles
[nextBreak
- 1])) ||
947 (nextBreak
== saeNext
)) {
948 while ((nextBreak
>= saeNext
) && (saeNext
< lineRange
.end
)) {
950 saeNext
= static_cast<int>((saeCurrentPos
< selAndEdge
.size()) ? selAndEdge
[saeCurrentPos
] : lineRange
.end
);
952 if ((nextBreak
> prev
) || repr
) {
953 // Have a segment to report
954 if (nextBreak
== prev
) {
955 nextBreak
+= charWidth
;
957 repr
= nullptr; // Optimize -> should remember repr
962 nextBreak
+= charWidth
;
965 const int lengthSegment
= nextBreak
- prev
;
966 if (lengthSegment
< lengthStartSubdivision
) {
967 return TextSegment(prev
, lengthSegment
, repr
);
972 // Splitting up a long run from prev to nextBreak in lots of approximately lengthEachSubdivision.
973 const int startSegment
= subBreak
;
974 const int remaining
= nextBreak
- startSegment
;
975 int lengthSegment
= remaining
;
976 if (lengthSegment
> lengthEachSubdivision
) {
977 lengthSegment
= static_cast<int>(pdoc
->SafeSegment(std::string_view(&ll
->chars
[startSegment
], lengthEachSubdivision
)));
979 if (lengthSegment
< remaining
) {
980 subBreak
+= lengthSegment
;
984 return TextSegment(startSegment
, lengthSegment
);
987 bool BreakFinder::More() const noexcept
{
988 return (subBreak
>= 0) || (nextBreak
< lineRange
.end
);
991 class PositionCacheEntry
{
992 uint16_t styleNumber
;
996 std::unique_ptr
<XYPOSITION
[]> positions
;
998 PositionCacheEntry() noexcept
;
999 // Copy constructor not currently used, but needed for being element in std::vector.
1000 PositionCacheEntry(const PositionCacheEntry
&);
1001 PositionCacheEntry(PositionCacheEntry
&&) noexcept
= default;
1002 // Deleted so PositionCacheEntry objects can not be assigned.
1003 void operator=(const PositionCacheEntry
&) = delete;
1004 void operator=(PositionCacheEntry
&&) = delete;
1005 ~PositionCacheEntry();
1006 void Set(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
, const XYPOSITION
*positions_
, uint16_t clock_
);
1007 void Clear() noexcept
;
1008 bool Retrieve(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
, XYPOSITION
*positions_
) const noexcept
;
1009 static size_t Hash(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
) noexcept
;
1010 bool NewerThan(const PositionCacheEntry
&other
) const noexcept
;
1011 void ResetClock() noexcept
;
1014 class PositionCache
: public IPositionCache
{
1015 std::vector
<PositionCacheEntry
> pces
;
1021 // Deleted so LineAnnotation objects can not be copied.
1022 PositionCache(const PositionCache
&) = delete;
1023 PositionCache(PositionCache
&&) = delete;
1024 void operator=(const PositionCache
&) = delete;
1025 void operator=(PositionCache
&&) = delete;
1026 ~PositionCache() override
= default;
1028 void Clear() noexcept override
;
1029 void SetSize(size_t size_
) override
;
1030 size_t GetSize() const noexcept override
;
1031 void MeasureWidths(Surface
*surface
, const ViewStyle
&vstyle
, unsigned int styleNumber
,
1032 bool unicode
, std::string_view sv
, XYPOSITION
*positions
, bool needsLocking
) override
;
1035 PositionCacheEntry::PositionCacheEntry() noexcept
:
1036 styleNumber(0), len(0), clock(0), unicode(false) {
1039 // Copy constructor not currently used, but needed for being element in std::vector.
1040 PositionCacheEntry::PositionCacheEntry(const PositionCacheEntry
&other
) :
1041 styleNumber(other
.styleNumber
), len(other
.len
), clock(other
.clock
), unicode(other
.unicode
) {
1042 if (other
.positions
) {
1043 const size_t lenData
= len
+ (len
/ sizeof(XYPOSITION
)) + 1;
1044 positions
= std::make_unique
<XYPOSITION
[]>(lenData
);
1045 memcpy(positions
.get(), other
.positions
.get(), lenData
* sizeof(XYPOSITION
));
1049 void PositionCacheEntry::Set(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
,
1050 const XYPOSITION
*positions_
, uint16_t clock_
) {
1052 styleNumber
= static_cast<uint16_t>(styleNumber_
);
1053 len
= static_cast<uint16_t>(sv
.length());
1056 if (sv
.data() && positions_
) {
1057 positions
= std::make_unique
<XYPOSITION
[]>(len
+ (len
/ sizeof(XYPOSITION
)) + 1);
1058 for (unsigned int i
=0; i
<len
; i
++) {
1059 positions
[i
] = positions_
[i
];
1061 memcpy(&positions
[len
], sv
.data(), sv
.length());
1065 PositionCacheEntry::~PositionCacheEntry() {
1069 void PositionCacheEntry::Clear() noexcept
{
1076 bool PositionCacheEntry::Retrieve(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
, XYPOSITION
*positions_
) const noexcept
{
1077 if ((styleNumber
== styleNumber_
) && (unicode
== unicode_
) && (len
== sv
.length()) &&
1078 (memcmp(&positions
[len
], sv
.data(), sv
.length())== 0)) {
1079 for (unsigned int i
=0; i
<len
; i
++) {
1080 positions_
[i
] = positions
[i
];
1088 size_t PositionCacheEntry::Hash(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
) noexcept
{
1089 const size_t h1
= std::hash
<std::string_view
>{}(sv
);
1090 const size_t h2
= std::hash
<unsigned int>{}(styleNumber_
);
1091 return h1
^ (h2
<< 1) ^ static_cast<size_t>(unicode_
);
1094 bool PositionCacheEntry::NewerThan(const PositionCacheEntry
&other
) const noexcept
{
1095 return clock
> other
.clock
;
1098 void PositionCacheEntry::ResetClock() noexcept
{
1104 PositionCache::PositionCache() {
1110 void PositionCache::Clear() noexcept
{
1112 for (PositionCacheEntry
&pce
: pces
) {
1120 void PositionCache::SetSize(size_t size_
) {
1125 size_t PositionCache::GetSize() const noexcept
{
1129 void PositionCache::MeasureWidths(Surface
*surface
, const ViewStyle
&vstyle
, unsigned int styleNumber
,
1130 bool unicode
, std::string_view sv
, XYPOSITION
*positions
, bool needsLocking
) {
1131 const Style
&style
= vstyle
.styles
[styleNumber
];
1132 if (style
.monospaceASCII
) {
1133 if (AllGraphicASCII(sv
)) {
1134 const XYPOSITION monospaceCharacterWidth
= style
.monospaceCharacterWidth
;
1135 for (size_t i
= 0; i
< sv
.length(); i
++) {
1136 positions
[i
] = monospaceCharacterWidth
* (i
+1);
1142 size_t probe
= pces
.size(); // Out of bounds
1143 if ((!pces
.empty()) && (sv
.length() < 30)) {
1144 // Only store short strings in the cache so it doesn't churn with
1145 // long comments with only a single comment.
1147 // Two way associative: try two probe positions.
1148 const size_t hashValue
= PositionCacheEntry::Hash(styleNumber
, unicode
, sv
);
1149 probe
= hashValue
% pces
.size();
1150 std::unique_lock
<std::mutex
> guard(mutex
, std::defer_lock
);
1154 if (pces
[probe
].Retrieve(styleNumber
, unicode
, sv
, positions
)) {
1157 const size_t probe2
= (hashValue
* 37) % pces
.size();
1158 if (pces
[probe2
].Retrieve(styleNumber
, unicode
, sv
, positions
)) {
1161 // Not found. Choose the oldest of the two slots to replace
1162 if (pces
[probe
].NewerThan(pces
[probe2
])) {
1167 const Font
*fontStyle
= style
.font
.get();
1169 surface
->MeasureWidthsUTF8(fontStyle
, sv
, positions
);
1171 surface
->MeasureWidths(fontStyle
, sv
, positions
);
1173 if (probe
< pces
.size()) {
1175 std::unique_lock
<std::mutex
> guard(mutex
, std::defer_lock
);
1180 if (clock
> 60000) {
1181 // Since there are only 16 bits for the clock, wrap it round and
1182 // reset all cache entries so none get stuck with a high clock.
1183 for (PositionCacheEntry
&pce
: pces
) {
1189 pces
[probe
].Set(styleNumber
, unicode
, sv
, positions
, clock
);
1193 std::unique_ptr
<IPositionCache
> Scintilla::Internal::CreatePositionCache() {
1194 return std::make_unique
<PositionCache
>();