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 maxValidity(LineLayout::ValidLevel::invalid
), 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 maxValidity
= LineLayout::ValidLevel::lines
;
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
{
566 maxValidity
= LineLayout::ValidLevel::invalid
;
570 void LineLayoutCache::Invalidate(LineLayout::ValidLevel validity_
) noexcept
{
571 if (maxValidity
> validity_
) {
572 maxValidity
= validity_
;
573 for (const std::shared_ptr
<LineLayout
> &ll
: cache
) {
575 ll
->Invalidate(validity_
);
581 void LineLayoutCache::SetLevel(LineCache level_
) noexcept
{
582 if (level
!= level_
) {
584 maxValidity
= LineLayout::ValidLevel::invalid
;
589 std::shared_ptr
<LineLayout
> LineLayoutCache::Retrieve(Sci::Line lineNumber
, Sci::Line lineCaret
, int maxChars
, int styleClock_
,
590 Sci::Line linesOnScreen
, Sci::Line linesInDoc
) {
591 AllocateForLevel(linesOnScreen
, linesInDoc
);
592 if (styleClock
!= styleClock_
) {
593 Invalidate(LineLayout::ValidLevel::checkTextAndStyle
);
594 styleClock
= styleClock_
;
596 maxValidity
= LineLayout::ValidLevel::lines
;
598 if (level
== LineCache::Page
) {
599 // If first entry is this line then just reuse it.
600 if (!(cache
[0] && (cache
[0]->LineNumber() == lineNumber
))) {
601 const size_t posForLine
= EntryForLine(lineNumber
);
602 if (lineNumber
== lineCaret
) {
603 // Use position 0 for caret line.
605 // Another line is currently in [0] so move it out to its normal position.
606 // Since it was recently the caret line its likely to be needed soon.
607 const size_t posNewForEntry0
= EntryForLine(cache
[0]->LineNumber());
608 if (posForLine
== posNewForEntry0
) {
609 std::swap(cache
[0], cache
[posNewForEntry0
]);
611 cache
[posNewForEntry0
] = std::move(cache
[0]);
614 if (cache
[posForLine
] && (cache
[posForLine
]->LineNumber() == lineNumber
)) {
615 // Caret line is currently somewhere else so move it to [0].
616 cache
[0] = std::move(cache
[posForLine
]);
622 } else if (level
== LineCache::Document
) {
626 if (pos
< cache
.size()) {
627 if (cache
[pos
] && !cache
[pos
]->CanHold(lineNumber
, maxChars
)) {
631 cache
[pos
] = std::make_shared
<LineLayout
>(lineNumber
, maxChars
);
634 // Expensive check that there is only one entry for any line number
635 std::vector
<bool> linesInCache(linesInDoc
);
636 for (const auto &entry
: cache
) {
638 PLATFORM_ASSERT(!linesInCache
[entry
->LineNumber()]);
639 linesInCache
[entry
->LineNumber()] = true;
646 // Only reach here for level == Cache::none
647 return std::make_shared
<LineLayout
>(lineNumber
, maxChars
);
652 // Simply pack the (maximum 4) character bytes into an int
653 constexpr unsigned int KeyFromString(std::string_view charBytes
) noexcept
{
654 PLATFORM_ASSERT(charBytes
.length() <= 4);
656 for (const unsigned char uc
: charBytes
) {
662 constexpr unsigned int representationKeyCrLf
= KeyFromString("\r\n");
664 const char *const repsC0
[] = {
665 "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
666 "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
667 "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
668 "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
671 const char *const repsC1
[] = {
672 "PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA",
673 "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3",
674 "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA",
675 "SOS", "SGCI", "SCI", "CSI", "ST", "OSC", "PM", "APC"
680 namespace Scintilla::Internal
{
682 const char *ControlCharacterString(unsigned char ch
) noexcept
{
683 if (ch
< std::size(repsC0
)) {
691 void Hexits(char *hexits
, int ch
) noexcept
{
693 hexits
[1] = "0123456789ABCDEF"[ch
/ 0x10];
694 hexits
[2] = "0123456789ABCDEF"[ch
% 0x10];
700 void SpecialRepresentations::SetRepresentation(std::string_view charBytes
, std::string_view value
) {
701 if ((charBytes
.length() <= 4) && (value
.length() <= Representation::maxLength
)) {
702 const unsigned int key
= KeyFromString(charBytes
);
703 const bool inserted
= mapReprs
.insert_or_assign(key
, Representation(value
)).second
;
705 // New entry so increment for first byte
706 const unsigned char ucStart
= charBytes
.empty() ? 0 : charBytes
[0];
707 startByteHasReprs
[ucStart
]++;
711 if (key
== representationKeyCrLf
) {
718 void SpecialRepresentations::SetRepresentationAppearance(std::string_view charBytes
, RepresentationAppearance appearance
) {
719 if (charBytes
.length() <= 4) {
720 const unsigned int key
= KeyFromString(charBytes
);
721 const MapRepresentation::iterator it
= mapReprs
.find(key
);
722 if (it
== mapReprs
.end()) {
723 // Not present so fail
726 it
->second
.appearance
= appearance
;
730 void SpecialRepresentations::SetRepresentationColour(std::string_view charBytes
, ColourRGBA colour
) {
731 if (charBytes
.length() <= 4) {
732 const unsigned int key
= KeyFromString(charBytes
);
733 const MapRepresentation::iterator it
= mapReprs
.find(key
);
734 if (it
== mapReprs
.end()) {
735 // Not present so fail
738 it
->second
.appearance
= it
->second
.appearance
| RepresentationAppearance::Colour
;
739 it
->second
.colour
= colour
;
743 void SpecialRepresentations::ClearRepresentation(std::string_view charBytes
) {
744 if (charBytes
.length() <= 4) {
745 const unsigned int key
= KeyFromString(charBytes
);
746 const MapRepresentation::iterator it
= mapReprs
.find(key
);
747 if (it
!= mapReprs
.end()) {
749 const unsigned char ucStart
= charBytes
.empty() ? 0 : charBytes
[0];
750 startByteHasReprs
[ucStart
]--;
751 if (key
== maxKey
&& startByteHasReprs
[ucStart
] == 0) {
752 maxKey
= mapReprs
.empty() ? 0 : mapReprs
.crbegin()->first
;
754 if (key
== representationKeyCrLf
) {
761 const Representation
*SpecialRepresentations::GetRepresentation(std::string_view charBytes
) const {
762 const unsigned int key
= KeyFromString(charBytes
);
766 const MapRepresentation::const_iterator it
= mapReprs
.find(key
);
767 if (it
!= mapReprs
.end()) {
768 return &(it
->second
);
773 const Representation
*SpecialRepresentations::RepresentationFromCharacter(std::string_view charBytes
) const {
774 if (charBytes
.length() <= 4) {
775 const unsigned char ucStart
= charBytes
.empty() ? 0 : charBytes
[0];
776 if (!startByteHasReprs
[ucStart
])
778 return GetRepresentation(charBytes
);
783 void SpecialRepresentations::Clear() {
785 constexpr unsigned short none
= 0;
786 std::fill(startByteHasReprs
, std::end(startByteHasReprs
), none
);
791 void SpecialRepresentations::SetDefaultRepresentations(int dbcsCodePage
) {
795 for (size_t j
= 0; j
< std::size(repsC0
); j
++) {
796 const char c
[2] = { static_cast<char>(j
), 0 };
797 SetRepresentation(std::string_view(c
, 1), repsC0
[j
]);
799 SetRepresentation("\x7f", "DEL");
802 // As well as Unicode mode, ISO-8859-1 should use these
803 if (CpUtf8
== dbcsCodePage
) {
804 for (size_t j
= 0; j
< std::size(repsC1
); j
++) {
805 const char c1
[3] = { '\xc2', static_cast<char>(0x80 + j
), 0 };
806 SetRepresentation(c1
, repsC1
[j
]);
808 SetRepresentation("\xe2\x80\xa8", "LS");
809 SetRepresentation("\xe2\x80\xa9", "PS");
812 // Invalid as single bytes in multi-byte encodings
814 for (int k
= 0x80; k
< 0x100; k
++) {
815 if ((CpUtf8
== dbcsCodePage
) || !IsDBCSValidSingleByte(dbcsCodePage
, k
)) {
816 const char hiByte
[2] = { static_cast<char>(k
), 0 };
819 SetRepresentation(hiByte
, hexits
);
825 void BreakFinder::Insert(Sci::Position val
) {
826 const int posInLine
= static_cast<int>(val
);
827 if (posInLine
> nextBreak
) {
828 const std::vector
<int>::iterator it
= std::lower_bound(selAndEdge
.begin(), selAndEdge
.end(), posInLine
);
829 if (it
== selAndEdge
.end()) {
830 selAndEdge
.push_back(posInLine
);
831 } else if (*it
!= posInLine
) {
832 selAndEdge
.insert(it
, 1, posInLine
);
837 BreakFinder::BreakFinder(const LineLayout
*ll_
, const Selection
*psel
, Range lineRange_
, Sci::Position posLineStart
,
838 XYPOSITION xStart
, BreakFor breakFor
, const Document
*pdoc_
, const SpecialRepresentations
*preprs_
, const ViewStyle
*pvsDraw
) :
840 lineRange(lineRange_
),
841 nextBreak(static_cast<int>(lineRange_
.start
)),
846 encodingFamily(pdoc_
->CodePageFamily()),
849 // Search for first visible break
850 // First find the first visible character
852 nextBreak
= ll
->FindBefore(xStart
, lineRange
);
853 // Now back to a style break
854 while ((nextBreak
> lineRange
.start
) && (ll
->styles
[nextBreak
] == ll
->styles
[nextBreak
- 1])) {
858 if (FlagSet(breakFor
, BreakFor::Selection
)) {
859 const SelectionPosition
posStart(posLineStart
);
860 const SelectionPosition
posEnd(posLineStart
+ lineRange
.end
);
861 const SelectionSegment
segmentLine(posStart
, posEnd
);
862 for (size_t r
=0; r
<psel
->Count(); r
++) {
863 const SelectionSegment portion
= psel
->Range(r
).Intersect(segmentLine
);
864 if (!(portion
.start
== portion
.end
)) {
865 if (portion
.start
.IsValid())
866 Insert(portion
.start
.Position() - posLineStart
);
867 if (portion
.end
.IsValid())
868 Insert(portion
.end
.Position() - posLineStart
);
871 // On the curses platform, the terminal is drawing its own caret, so add breaks around the
872 // caret in the main selection in order to help prevent the selection from being drawn in
874 if (FlagSet(pvsDraw
->caret
.style
, CaretStyle::Curses
) && !psel
->RangeMain().Empty()) {
875 const Sci::Position caretPos
= psel
->RangeMain().caret
.Position();
876 const Sci::Position anchorPos
= psel
->RangeMain().anchor
.Position();
877 if (caretPos
< anchorPos
) {
878 const Sci::Position nextPos
= pdoc
->MovePositionOutsideChar(caretPos
+ 1, 1);
879 Insert(nextPos
- posLineStart
);
880 } else if (caretPos
> anchorPos
&& pvsDraw
->DrawCaretInsideSelection(false, false)) {
881 const Sci::Position prevPos
= pdoc
->MovePositionOutsideChar(caretPos
- 1, -1);
882 if (prevPos
> anchorPos
)
883 Insert(prevPos
- posLineStart
);
887 if (FlagSet(breakFor
, BreakFor::Foreground
) && pvsDraw
->indicatorsSetFore
) {
888 for (const IDecoration
*deco
: pdoc
->decorations
->View()) {
889 if (pvsDraw
->indicators
[deco
->Indicator()].OverridesTextFore()) {
890 Sci::Position startPos
= deco
->EndRun(posLineStart
);
891 while (startPos
< (posLineStart
+ lineRange
.end
)) {
892 Insert(startPos
- posLineStart
);
893 startPos
= deco
->EndRun(startPos
);
898 Insert(ll
->edgeColumn
);
899 Insert(lineRange
.end
);
900 saeNext
= (!selAndEdge
.empty()) ? selAndEdge
[0] : -1;
903 BreakFinder::~BreakFinder() noexcept
= default;
905 TextSegment
BreakFinder::Next() {
907 const int prev
= nextBreak
;
908 const Representation
*repr
= nullptr;
909 while (nextBreak
< lineRange
.end
) {
911 const char * const chars
= &ll
->chars
[nextBreak
];
912 const unsigned char ch
= chars
[0];
913 bool characterStyleConsistent
= true; // All bytes of character in same style?
914 if (!UTF8IsAscii(ch
) && encodingFamily
!= EncodingFamily::eightBit
) {
915 if (encodingFamily
== EncodingFamily::unicode
) {
916 charWidth
= UTF8DrawBytes(chars
, lineRange
.end
- nextBreak
);
918 charWidth
= pdoc
->DBCSDrawBytes(std::string_view(chars
, lineRange
.end
- nextBreak
));
920 for (int trail
= 1; trail
< charWidth
; trail
++) {
921 if (ll
->styles
[nextBreak
] != ll
->styles
[nextBreak
+ trail
]) {
922 characterStyleConsistent
= false;
926 if (!characterStyleConsistent
) {
927 if (nextBreak
== prev
) {
928 // Show first character representation bytes since it has inconsistent styles.
931 // Return segment before nextBreak but allow to be split up if too long
932 // If not split up, next call will hit the above 'charWidth = 1;' and display bytes.
937 if (preprs
->MayContain(ch
)) {
938 // Special case \r\n line ends if there is a representation
939 if (ch
== '\r' && preprs
->ContainsCrLf() && chars
[1] == '\n') {
942 repr
= preprs
->GetRepresentation(std::string_view(chars
, charWidth
));
944 if (((nextBreak
> 0) && (ll
->styles
[nextBreak
] != ll
->styles
[nextBreak
- 1])) ||
946 (nextBreak
== saeNext
)) {
947 while ((nextBreak
>= saeNext
) && (saeNext
< lineRange
.end
)) {
949 saeNext
= static_cast<int>((saeCurrentPos
< selAndEdge
.size()) ? selAndEdge
[saeCurrentPos
] : lineRange
.end
);
951 if ((nextBreak
> prev
) || repr
) {
952 // Have a segment to report
953 if (nextBreak
== prev
) {
954 nextBreak
+= charWidth
;
956 repr
= nullptr; // Optimize -> should remember repr
961 nextBreak
+= charWidth
;
964 const int lengthSegment
= nextBreak
- prev
;
965 if (lengthSegment
< lengthStartSubdivision
) {
966 return TextSegment(prev
, lengthSegment
, repr
);
971 // Splitting up a long run from prev to nextBreak in lots of approximately lengthEachSubdivision.
972 const int startSegment
= subBreak
;
973 const int remaining
= nextBreak
- startSegment
;
974 int lengthSegment
= remaining
;
975 if (lengthSegment
> lengthEachSubdivision
) {
976 lengthSegment
= static_cast<int>(pdoc
->SafeSegment(std::string_view(&ll
->chars
[startSegment
], lengthEachSubdivision
)));
978 if (lengthSegment
< remaining
) {
979 subBreak
+= lengthSegment
;
983 return TextSegment(startSegment
, lengthSegment
);
986 bool BreakFinder::More() const noexcept
{
987 return (subBreak
>= 0) || (nextBreak
< lineRange
.end
);
990 class PositionCacheEntry
{
991 uint16_t styleNumber
;
995 std::unique_ptr
<XYPOSITION
[]> positions
;
997 PositionCacheEntry() noexcept
;
998 // Copy constructor not currently used, but needed for being element in std::vector.
999 PositionCacheEntry(const PositionCacheEntry
&);
1000 PositionCacheEntry(PositionCacheEntry
&&) noexcept
= default;
1001 // Deleted so PositionCacheEntry objects can not be assigned.
1002 void operator=(const PositionCacheEntry
&) = delete;
1003 void operator=(PositionCacheEntry
&&) = delete;
1004 ~PositionCacheEntry();
1005 void Set(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
, const XYPOSITION
*positions_
, uint16_t clock_
);
1006 void Clear() noexcept
;
1007 bool Retrieve(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
, XYPOSITION
*positions_
) const noexcept
;
1008 static size_t Hash(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
) noexcept
;
1009 bool NewerThan(const PositionCacheEntry
&other
) const noexcept
;
1010 void ResetClock() noexcept
;
1013 class PositionCache
: public IPositionCache
{
1014 std::vector
<PositionCacheEntry
> pces
;
1020 // Deleted so LineAnnotation objects can not be copied.
1021 PositionCache(const PositionCache
&) = delete;
1022 PositionCache(PositionCache
&&) = delete;
1023 void operator=(const PositionCache
&) = delete;
1024 void operator=(PositionCache
&&) = delete;
1025 ~PositionCache() override
= default;
1027 void Clear() noexcept override
;
1028 void SetSize(size_t size_
) override
;
1029 size_t GetSize() const noexcept override
;
1030 void MeasureWidths(Surface
*surface
, const ViewStyle
&vstyle
, unsigned int styleNumber
,
1031 bool unicode
, std::string_view sv
, XYPOSITION
*positions
, bool needsLocking
) override
;
1034 PositionCacheEntry::PositionCacheEntry() noexcept
:
1035 styleNumber(0), len(0), clock(0), unicode(false) {
1038 // Copy constructor not currently used, but needed for being element in std::vector.
1039 PositionCacheEntry::PositionCacheEntry(const PositionCacheEntry
&other
) :
1040 styleNumber(other
.styleNumber
), len(other
.len
), clock(other
.clock
), unicode(other
.unicode
) {
1041 if (other
.positions
) {
1042 const size_t lenData
= len
+ (len
/ sizeof(XYPOSITION
)) + 1;
1043 positions
= std::make_unique
<XYPOSITION
[]>(lenData
);
1044 memcpy(positions
.get(), other
.positions
.get(), lenData
* sizeof(XYPOSITION
));
1048 void PositionCacheEntry::Set(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
,
1049 const XYPOSITION
*positions_
, uint16_t clock_
) {
1051 styleNumber
= static_cast<uint16_t>(styleNumber_
);
1052 len
= static_cast<uint16_t>(sv
.length());
1055 if (sv
.data() && positions_
) {
1056 positions
= std::make_unique
<XYPOSITION
[]>(len
+ (len
/ sizeof(XYPOSITION
)) + 1);
1057 for (unsigned int i
=0; i
<len
; i
++) {
1058 positions
[i
] = positions_
[i
];
1060 memcpy(&positions
[len
], sv
.data(), sv
.length());
1064 PositionCacheEntry::~PositionCacheEntry() {
1068 void PositionCacheEntry::Clear() noexcept
{
1075 bool PositionCacheEntry::Retrieve(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
, XYPOSITION
*positions_
) const noexcept
{
1076 if ((styleNumber
== styleNumber_
) && (unicode
== unicode_
) && (len
== sv
.length()) &&
1077 (memcmp(&positions
[len
], sv
.data(), sv
.length())== 0)) {
1078 for (unsigned int i
=0; i
<len
; i
++) {
1079 positions_
[i
] = positions
[i
];
1087 size_t PositionCacheEntry::Hash(unsigned int styleNumber_
, bool unicode_
, std::string_view sv
) noexcept
{
1088 const size_t h1
= std::hash
<std::string_view
>{}(sv
);
1089 const size_t h2
= std::hash
<unsigned int>{}(styleNumber_
);
1090 return h1
^ (h2
<< 1) ^ static_cast<size_t>(unicode_
);
1093 bool PositionCacheEntry::NewerThan(const PositionCacheEntry
&other
) const noexcept
{
1094 return clock
> other
.clock
;
1097 void PositionCacheEntry::ResetClock() noexcept
{
1103 PositionCache::PositionCache() {
1109 void PositionCache::Clear() noexcept
{
1111 for (PositionCacheEntry
&pce
: pces
) {
1119 void PositionCache::SetSize(size_t size_
) {
1124 size_t PositionCache::GetSize() const noexcept
{
1128 void PositionCache::MeasureWidths(Surface
*surface
, const ViewStyle
&vstyle
, unsigned int styleNumber
,
1129 bool unicode
, std::string_view sv
, XYPOSITION
*positions
, bool needsLocking
) {
1130 const Style
&style
= vstyle
.styles
[styleNumber
];
1131 if (style
.monospaceASCII
) {
1132 if (AllGraphicASCII(sv
)) {
1133 const XYPOSITION monospaceCharacterWidth
= style
.monospaceCharacterWidth
;
1134 for (size_t i
= 0; i
< sv
.length(); i
++) {
1135 positions
[i
] = monospaceCharacterWidth
* (i
+1);
1141 size_t probe
= pces
.size(); // Out of bounds
1142 if ((!pces
.empty()) && (sv
.length() < 30)) {
1143 // Only store short strings in the cache so it doesn't churn with
1144 // long comments with only a single comment.
1146 // Two way associative: try two probe positions.
1147 const size_t hashValue
= PositionCacheEntry::Hash(styleNumber
, unicode
, sv
);
1148 probe
= hashValue
% pces
.size();
1149 std::unique_lock
<std::mutex
> guard(mutex
, std::defer_lock
);
1153 if (pces
[probe
].Retrieve(styleNumber
, unicode
, sv
, positions
)) {
1156 const size_t probe2
= (hashValue
* 37) % pces
.size();
1157 if (pces
[probe2
].Retrieve(styleNumber
, unicode
, sv
, positions
)) {
1160 // Not found. Choose the oldest of the two slots to replace
1161 if (pces
[probe
].NewerThan(pces
[probe2
])) {
1166 const Font
*fontStyle
= style
.font
.get();
1168 surface
->MeasureWidthsUTF8(fontStyle
, sv
, positions
);
1170 surface
->MeasureWidths(fontStyle
, sv
, positions
);
1172 if (probe
< pces
.size()) {
1174 std::unique_lock
<std::mutex
> guard(mutex
, std::defer_lock
);
1179 if (clock
> 60000) {
1180 // Since there are only 16 bits for the clock, wrap it round and
1181 // reset all cache entries so none get stuck with a high clock.
1182 for (PositionCacheEntry
&pce
: pces
) {
1188 pces
[probe
].Set(styleNumber
, unicode
, sv
, positions
, clock
);
1192 std::unique_ptr
<IPositionCache
> Scintilla::Internal::CreatePositionCache() {
1193 return std::make_unique
<PositionCache
>();