Fix typos
[TortoiseGit.git] / ext / scintilla / src / PositionCache.cxx
blobb1ada52afbba681a8b0878b6717408ceb13204ed
1 // Scintilla source code edit control
2 /** @file PositionCache.cxx
3 ** Classes for caching layout information.
4 **/
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.
8 #include <cstddef>
9 #include <cstdlib>
10 #include <cstdint>
11 #include <cstring>
12 #include <cmath>
14 #include <stdexcept>
15 #include <string>
16 #include <string_view>
17 #include <vector>
18 #include <map>
19 #include <set>
20 #include <optional>
21 #include <algorithm>
22 #include <iterator>
23 #include <memory>
24 #include <mutex>
26 #include "ScintillaTypes.h"
27 #include "ScintillaMessages.h"
28 #include "ILoader.h"
29 #include "ILexer.h"
31 #include "Debugging.h"
32 #include "Geometry.h"
33 #include "Platform.h"
35 #include "CharacterType.h"
36 #include "CharacterCategoryMap.h"
37 #include "Position.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"
44 #include "KeyMap.h"
45 #include "Indicator.h"
46 #include "LineMarker.h"
47 #include "Style.h"
48 #include "ViewStyle.h"
49 #include "CharClassify.h"
50 #include "Decoration.h"
51 #include "CaseFolder.h"
52 #include "Document.h"
53 #include "UniConversion.h"
54 #include "DBCS.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_) :
67 lenLineStarts(0),
68 lineNumber(lineNumber_),
69 maxLineLength(-1),
70 numCharsInLine(0),
71 numCharsBeforeEOL(0),
72 validity(ValidLevel::invalid),
73 xHighlightGuide(0),
74 highlightColumn(false),
75 containsCaret(false),
76 edgeColumn(0),
77 bracePreviousStyles{},
78 widthLine(wrapWidthInfinite),
79 lines(1),
80 wrapIndent(0) {
81 Resize(maxLineLength_);
84 LineLayout::~LineLayout() {
85 Free();
88 void LineLayout::Resize(int maxLineLength_) {
89 if (maxLineLength_ > maxLineLength) {
90 Free();
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);
97 if (bidiData) {
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_));
108 lines = 0;
109 Invalidate(ValidLevel::invalid);
112 void LineLayout::EnsureBidiData() {
113 if (!bidiData) {
114 bidiData = std::make_unique<BidiData>();
115 bidiData->Resize(maxLineLength);
119 void LineLayout::Free() noexcept {
120 chars.reset();
121 styles.reset();
122 positions.reset();
123 lineStarts.reset();
124 lenLineStarts = 0;
125 bidiData.reset();
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 {
138 return lineNumber;
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 {
146 if (line <= 0) {
147 return 0;
148 } else if ((line >= lines) || !lineStarts) {
149 return numCharsInLine;
150 } else {
151 return lineStarts[line];
155 int LineLayout::LineLength(int line) const noexcept {
156 if (!lineStarts) {
157 return numCharsInLine;
158 } if (line >= lines - 1) {
159 return numCharsInLine - lineStarts[line];
160 } else {
161 return lineStarts[line + 1] - lineStarts[line];
165 int LineLayout::LineLastVisible(int line, Scope scope) const noexcept {
166 if (line < 0) {
167 return 0;
168 } else if ((line >= lines-1) || !lineStarts) {
169 return scope == Scope::visibleOnly ? numCharsBeforeEOL : numCharsInLine;
170 } else {
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)) {
186 return lines - 1;
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)
193 return line;
194 } else {
195 if (lineStarts[line + 1] <= posInLine)
196 return line;
200 return lines - 1;
203 void LineLayout::AddLineStart(Sci::Position start) {
204 lines++;
205 if (lines >= lenLineStarts) {
206 const int newMaxLines = lines + 20;
207 std::unique_ptr<int[]> newLineStarts = std::make_unique<int[]>(newMaxLines);
208 if (lenLineStarts) {
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];
252 xHighlightGuide = 0;
255 int LineLayout::FindBefore(XYPOSITION x, Range range) const noexcept {
256 Sci::Position lower = range.start;
257 Sci::Position upper = range.end;
258 do {
259 const Sci::Position middle = (upper + lower + 1) / 2; // Round high
260 const XYPOSITION posMiddle = positions[middle];
261 if (x < posMiddle) {
262 upper = middle - 1;
263 } else {
264 lower = 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) {
274 if (charPosition) {
275 if (x < (positions[pos + 1])) {
276 return pos;
278 } else {
279 if (x < ((positions[pos] + positions[pos + 1]) / 2)) {
280 return pos;
283 pos++;
285 return static_cast<int>(range.end);
288 Point LineLayout::PointFromPosition(int posInLine, int lineHeight, PointEnd pe) const noexcept {
289 Point pt;
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
302 pt.x += wrapIndent;
303 if (FlagSet(pe, PointEnd::subLineEnd)) // Return end of first subline not start of next
304 break;
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
308 pt.x += wrapIndent;
310 } else {
311 break;
314 return pt;
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;
344 lines = 0;
345 // Calculate line start positions based upon width.
346 Sci::Position lastLineStart = 0;
347 XYPOSITION startOffset = wrapWidth;
348 Sci::Position p = 0;
349 while (p < numCharsInLine) {
350 while (p < numCharsInLine && positions[p + 1] < startOffset) {
351 p++;
353 if (p < numCharsInLine) {
354 // backtrack to find lastGoodBreak
355 Sci::Position lastGoodBreak = p;
356 if (p > 0) {
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])) {
364 break;
366 if (IsBreakSpace(chars[pos - 1]) && !IsBreakSpace(chars[pos])) {
367 break;
369 pos = CharacterBoundary(pos - 1, -1);
371 if (pos > lastLineStart) {
372 lastGoodBreak = pos;
375 if (lastGoodBreak == lastLineStart) {
376 // Try moving to start of last character
377 if (p > 0) {
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;
393 lines++;
396 ScreenLine::ScreenLine(
397 const LineLayout *ll_,
398 int subLine,
399 const ViewStyle &vs,
400 XYPOSITION width_,
401 int tabWidthMinimumPixels_) :
402 ll(ll_),
403 start(ll->LineStart(subLine)),
404 len(ll->LineLength(subLine)),
405 width(width_),
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 {
420 return len;
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 {
430 return width;
433 XYPOSITION ScreenLine::Height() const {
434 return height;
437 XYPOSITION ScreenLine::TabWidth() const {
438 return tabWidth;
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 {
458 switch (level) {
459 case LineCache::None:
460 return false;
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:
467 default:
468 return true;
472 LineLayoutCache::LineLayoutCache() :
473 level(LineCache::None),
474 maxValidity(LineLayout::ValidLevel::invalid), styleClock(-1) {
477 LineLayoutCache::~LineLayoutCache() = default;
479 namespace {
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 {
499 switch (level) {
500 case LineCache::None:
501 return 0;
502 case LineCache::Caret:
503 return 0;
504 case LineCache::Page:
505 return 1 + (line % (cache.size() - 1));
506 case LineCache::Document:
507 return line;
509 return 0;
512 void LineLayoutCache::AllocateForLevel(Sci::Line linesOnScreen, Sci::Line linesInDoc) {
513 size_t lengthForLevel = 0;
514 if (level == LineCache::Caret) {
515 lengthForLevel = 1;
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;
533 if (cache[i]) {
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.
540 } else {
541 std::swap(cache[i], cache[posForLine]);
542 increment = 0;
543 // Don't increment as newly swapped in value may have to move
545 } else {
546 cache[posForLine] = std::move(cache[i]);
550 i += increment;
553 #ifdef CHECK_LLC
554 for (size_t i = 1; i < cache.size(); i++) {
555 if (cache[i]) {
556 PLATFORM_ASSERT(EntryForLine(cache[i]->LineNumber()) == i);
559 #endif
562 PLATFORM_ASSERT(cache.size() == lengthForLevel);
565 void LineLayoutCache::Deallocate() noexcept {
566 maxValidity = LineLayout::ValidLevel::invalid;
567 cache.clear();
570 void LineLayoutCache::Invalidate(LineLayout::ValidLevel validity_) noexcept {
571 if (maxValidity > validity_) {
572 maxValidity = validity_;
573 for (const std::shared_ptr<LineLayout> &ll : cache) {
574 if (ll) {
575 ll->Invalidate(validity_);
581 void LineLayoutCache::SetLevel(LineCache level_) noexcept {
582 if (level != level_) {
583 level = level_;
584 maxValidity = LineLayout::ValidLevel::invalid;
585 cache.clear();
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;
597 size_t pos = 0;
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.
604 if (cache[0]) {
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]);
610 } else {
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]);
618 } else {
619 pos = posForLine;
622 } else if (level == LineCache::Document) {
623 pos = lineNumber;
626 if (pos < cache.size()) {
627 if (cache[pos] && !cache[pos]->CanHold(lineNumber, maxChars)) {
628 cache[pos].reset();
630 if (!cache[pos]) {
631 cache[pos] = std::make_shared<LineLayout>(lineNumber, maxChars);
633 #ifdef CHECK_LLC
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) {
637 if (entry) {
638 PLATFORM_ASSERT(!linesInCache[entry->LineNumber()]);
639 linesInCache[entry->LineNumber()] = true;
642 #endif
643 return cache[pos];
646 // Only reach here for level == Cache::none
647 return std::make_shared<LineLayout>(lineNumber, maxChars);
650 namespace {
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);
655 unsigned int k=0;
656 for (const unsigned char uc : charBytes) {
657 k = k * 0x100 + uc;
659 return k;
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)) {
684 return repsC0[ch];
685 } else {
686 return "BAD";
691 void Hexits(char *hexits, int ch) noexcept {
692 hexits[0] = 'x';
693 hexits[1] = "0123456789ABCDEF"[ch / 0x10];
694 hexits[2] = "0123456789ABCDEF"[ch % 0x10];
695 hexits[3] = 0;
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;
704 if (inserted) {
705 // New entry so increment for first byte
706 const unsigned char ucStart = charBytes.empty() ? 0 : charBytes[0];
707 startByteHasReprs[ucStart]++;
708 if (key > maxKey) {
709 maxKey = key;
711 if (key == representationKeyCrLf) {
712 crlf = true;
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
724 return;
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
736 return;
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()) {
748 mapReprs.erase(it);
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) {
755 crlf = false;
761 const Representation *SpecialRepresentations::GetRepresentation(std::string_view charBytes) const {
762 const unsigned int key = KeyFromString(charBytes);
763 if (key > maxKey) {
764 return nullptr;
766 const MapRepresentation::const_iterator it = mapReprs.find(key);
767 if (it != mapReprs.end()) {
768 return &(it->second);
770 return nullptr;
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])
777 return nullptr;
778 return GetRepresentation(charBytes);
780 return nullptr;
783 void SpecialRepresentations::Clear() {
784 mapReprs.clear();
785 constexpr unsigned short none = 0;
786 std::fill(startByteHasReprs, std::end(startByteHasReprs), none);
787 maxKey = 0;
788 crlf = false;
791 void SpecialRepresentations::SetDefaultRepresentations(int dbcsCodePage) {
792 Clear();
794 // C0 control set
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");
801 // C1 control set
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
813 if (dbcsCodePage) {
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 };
817 char hexits[4];
818 Hexits(hexits, k);
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) :
839 ll(ll_),
840 lineRange(lineRange_),
841 nextBreak(static_cast<int>(lineRange_.start)),
842 saeCurrentPos(0),
843 saeNext(0),
844 subBreak(-1),
845 pdoc(pdoc_),
846 encodingFamily(pdoc_->CodePageFamily()),
847 preprs(preprs_) {
849 // Search for first visible break
850 // First find the first visible character
851 if (xStart > 0.0f)
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])) {
855 nextBreak--;
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
873 // the caret's cell.
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() {
906 if (subBreak < 0) {
907 const int prev = nextBreak;
908 const Representation *repr = nullptr;
909 while (nextBreak < lineRange.end) {
910 int charWidth = 1;
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);
917 } else {
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.
929 charWidth = 1;
930 } else {
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.
933 break;
936 repr = nullptr;
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') {
940 charWidth = 2;
942 repr = preprs->GetRepresentation(std::string_view(chars, charWidth));
944 if (((nextBreak > 0) && (ll->styles[nextBreak] != ll->styles[nextBreak - 1])) ||
945 repr ||
946 (nextBreak == saeNext)) {
947 while ((nextBreak >= saeNext) && (saeNext < lineRange.end)) {
948 saeCurrentPos++;
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;
955 } else {
956 repr = nullptr; // Optimize -> should remember repr
958 break;
961 nextBreak += charWidth;
964 const int lengthSegment = nextBreak - prev;
965 if (lengthSegment < lengthStartSubdivision) {
966 return TextSegment(prev, lengthSegment, repr);
968 subBreak = prev;
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;
980 } else {
981 subBreak = -1;
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;
992 uint16_t len;
993 uint16_t clock;
994 bool unicode;
995 std::unique_ptr<XYPOSITION[]> positions;
996 public:
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;
1015 std::mutex mutex;
1016 uint16_t clock;
1017 bool allClear;
1018 public:
1019 PositionCache();
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_) {
1050 Clear();
1051 styleNumber = static_cast<uint16_t>(styleNumber_);
1052 len = static_cast<uint16_t>(sv.length());
1053 clock = clock_;
1054 unicode = unicode_;
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() {
1065 Clear();
1068 void PositionCacheEntry::Clear() noexcept {
1069 positions.reset();
1070 styleNumber = 0;
1071 len = 0;
1072 clock = 0;
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];
1081 return true;
1082 } else {
1083 return false;
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 {
1098 if (clock > 0) {
1099 clock = 1;
1103 PositionCache::PositionCache() {
1104 clock = 1;
1105 pces.resize(0x400);
1106 allClear = true;
1109 void PositionCache::Clear() noexcept {
1110 if (!allClear) {
1111 for (PositionCacheEntry &pce : pces) {
1112 pce.Clear();
1115 clock = 1;
1116 allClear = true;
1119 void PositionCache::SetSize(size_t size_) {
1120 Clear();
1121 pces.resize(size_);
1124 size_t PositionCache::GetSize() const noexcept {
1125 return pces.size();
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);
1137 return;
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);
1150 if (needsLocking) {
1151 guard.lock();
1153 if (pces[probe].Retrieve(styleNumber, unicode, sv, positions)) {
1154 return;
1156 const size_t probe2 = (hashValue * 37) % pces.size();
1157 if (pces[probe2].Retrieve(styleNumber, unicode, sv, positions)) {
1158 return;
1160 // Not found. Choose the oldest of the two slots to replace
1161 if (pces[probe].NewerThan(pces[probe2])) {
1162 probe = probe2;
1166 const Font *fontStyle = style.font.get();
1167 if (unicode) {
1168 surface->MeasureWidthsUTF8(fontStyle, sv, positions);
1169 } else {
1170 surface->MeasureWidths(fontStyle, sv, positions);
1172 if (probe < pces.size()) {
1173 // Store into cache
1174 std::unique_lock<std::mutex> guard(mutex, std::defer_lock);
1175 if (needsLocking) {
1176 guard.lock();
1178 clock++;
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) {
1183 pce.ResetClock();
1185 clock = 2;
1187 allClear = false;
1188 pces[probe].Set(styleNumber, unicode, sv, positions, clock);
1192 std::unique_ptr<IPositionCache> Scintilla::Internal::CreatePositionCache() {
1193 return std::make_unique<PositionCache>();