VTE cleanup related to GTK 3.24 requirement
[geany-mirror.git] / scintilla / src / PositionCache.cxx
blob8c0e2046ba4d0574601b6473dd2ab1fc0141e2ed
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 allInvalidated(false), 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 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;
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 cache.clear();
569 void LineLayoutCache::Invalidate(LineLayout::ValidLevel validity_) noexcept {
570 if (!cache.empty() && !allInvalidated) {
571 for (const std::shared_ptr<LineLayout> &ll : cache) {
572 if (ll) {
573 ll->Invalidate(validity_);
576 if (validity_ == LineLayout::ValidLevel::invalid) {
577 allInvalidated = true;
582 void LineLayoutCache::SetLevel(LineCache level_) noexcept {
583 if (level != level_) {
584 level = level_;
585 allInvalidated = false;
586 cache.clear();
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;
598 size_t pos = 0;
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.
605 if (cache[0]) {
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]);
611 } else {
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]);
619 } else {
620 pos = posForLine;
623 } else if (level == LineCache::Document) {
624 pos = lineNumber;
627 if (pos < cache.size()) {
628 if (cache[pos] && !cache[pos]->CanHold(lineNumber, maxChars)) {
629 cache[pos].reset();
631 if (!cache[pos]) {
632 cache[pos] = std::make_shared<LineLayout>(lineNumber, maxChars);
634 #ifdef CHECK_LLC
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) {
638 if (entry) {
639 PLATFORM_ASSERT(!linesInCache[entry->LineNumber()]);
640 linesInCache[entry->LineNumber()] = true;
643 #endif
644 return cache[pos];
647 // Only reach here for level == Cache::none
648 return std::make_shared<LineLayout>(lineNumber, maxChars);
651 namespace {
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);
656 unsigned int k=0;
657 for (const unsigned char uc : charBytes) {
658 k = k * 0x100 + uc;
660 return k;
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)) {
685 return repsC0[ch];
686 } else {
687 return "BAD";
692 void Hexits(char *hexits, int ch) noexcept {
693 hexits[0] = 'x';
694 hexits[1] = "0123456789ABCDEF"[ch / 0x10];
695 hexits[2] = "0123456789ABCDEF"[ch % 0x10];
696 hexits[3] = 0;
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;
705 if (inserted) {
706 // New entry so increment for first byte
707 const unsigned char ucStart = charBytes.empty() ? 0 : charBytes[0];
708 startByteHasReprs[ucStart]++;
709 if (key > maxKey) {
710 maxKey = key;
712 if (key == representationKeyCrLf) {
713 crlf = true;
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
725 return;
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
737 return;
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()) {
749 mapReprs.erase(it);
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) {
756 crlf = false;
762 const Representation *SpecialRepresentations::GetRepresentation(std::string_view charBytes) const {
763 const unsigned int key = KeyFromString(charBytes);
764 if (key > maxKey) {
765 return nullptr;
767 const MapRepresentation::const_iterator it = mapReprs.find(key);
768 if (it != mapReprs.end()) {
769 return &(it->second);
771 return nullptr;
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])
778 return nullptr;
779 return GetRepresentation(charBytes);
781 return nullptr;
784 void SpecialRepresentations::Clear() {
785 mapReprs.clear();
786 constexpr unsigned short none = 0;
787 std::fill(startByteHasReprs, std::end(startByteHasReprs), none);
788 maxKey = 0;
789 crlf = false;
792 void SpecialRepresentations::SetDefaultRepresentations(int dbcsCodePage) {
793 Clear();
795 // C0 control set
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");
802 // C1 control set
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
814 if (dbcsCodePage) {
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 };
818 char hexits[4];
819 Hexits(hexits, k);
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) :
840 ll(ll_),
841 lineRange(lineRange_),
842 nextBreak(static_cast<int>(lineRange_.start)),
843 saeCurrentPos(0),
844 saeNext(0),
845 subBreak(-1),
846 pdoc(pdoc_),
847 encodingFamily(pdoc_->CodePageFamily()),
848 preprs(preprs_) {
850 // Search for first visible break
851 // First find the first visible character
852 if (xStart > 0.0f)
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])) {
856 nextBreak--;
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
874 // the caret's cell.
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() {
907 if (subBreak < 0) {
908 const int prev = nextBreak;
909 const Representation *repr = nullptr;
910 while (nextBreak < lineRange.end) {
911 int charWidth = 1;
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);
918 } else {
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.
930 charWidth = 1;
931 } else {
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.
934 break;
937 repr = nullptr;
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') {
941 charWidth = 2;
943 repr = preprs->GetRepresentation(std::string_view(chars, charWidth));
945 if (((nextBreak > 0) && (ll->styles[nextBreak] != ll->styles[nextBreak - 1])) ||
946 repr ||
947 (nextBreak == saeNext)) {
948 while ((nextBreak >= saeNext) && (saeNext < lineRange.end)) {
949 saeCurrentPos++;
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;
956 } else {
957 repr = nullptr; // Optimize -> should remember repr
959 break;
962 nextBreak += charWidth;
965 const int lengthSegment = nextBreak - prev;
966 if (lengthSegment < lengthStartSubdivision) {
967 return TextSegment(prev, lengthSegment, repr);
969 subBreak = prev;
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;
981 } else {
982 subBreak = -1;
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;
993 uint16_t len;
994 uint16_t clock;
995 bool unicode;
996 std::unique_ptr<XYPOSITION[]> positions;
997 public:
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;
1016 std::mutex mutex;
1017 uint16_t clock;
1018 bool allClear;
1019 public:
1020 PositionCache();
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_) {
1051 Clear();
1052 styleNumber = static_cast<uint16_t>(styleNumber_);
1053 len = static_cast<uint16_t>(sv.length());
1054 clock = clock_;
1055 unicode = unicode_;
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() {
1066 Clear();
1069 void PositionCacheEntry::Clear() noexcept {
1070 positions.reset();
1071 styleNumber = 0;
1072 len = 0;
1073 clock = 0;
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];
1082 return true;
1083 } else {
1084 return false;
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 {
1099 if (clock > 0) {
1100 clock = 1;
1104 PositionCache::PositionCache() {
1105 clock = 1;
1106 pces.resize(0x400);
1107 allClear = true;
1110 void PositionCache::Clear() noexcept {
1111 if (!allClear) {
1112 for (PositionCacheEntry &pce : pces) {
1113 pce.Clear();
1116 clock = 1;
1117 allClear = true;
1120 void PositionCache::SetSize(size_t size_) {
1121 Clear();
1122 pces.resize(size_);
1125 size_t PositionCache::GetSize() const noexcept {
1126 return pces.size();
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);
1138 return;
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);
1151 if (needsLocking) {
1152 guard.lock();
1154 if (pces[probe].Retrieve(styleNumber, unicode, sv, positions)) {
1155 return;
1157 const size_t probe2 = (hashValue * 37) % pces.size();
1158 if (pces[probe2].Retrieve(styleNumber, unicode, sv, positions)) {
1159 return;
1161 // Not found. Choose the oldest of the two slots to replace
1162 if (pces[probe].NewerThan(pces[probe2])) {
1163 probe = probe2;
1167 const Font *fontStyle = style.font.get();
1168 if (unicode) {
1169 surface->MeasureWidthsUTF8(fontStyle, sv, positions);
1170 } else {
1171 surface->MeasureWidths(fontStyle, sv, positions);
1173 if (probe < pces.size()) {
1174 // Store into cache
1175 std::unique_lock<std::mutex> guard(mutex, std::defer_lock);
1176 if (needsLocking) {
1177 guard.lock();
1179 clock++;
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) {
1184 pce.ResetClock();
1186 clock = 2;
1188 allClear = false;
1189 pces[probe].Set(styleNumber, unicode, sv, positions, clock);
1193 std::unique_ptr<IPositionCache> Scintilla::Internal::CreatePositionCache() {
1194 return std::make_unique<PositionCache>();