Update to Scintilla 5.5.2
[TortoiseGit.git] / ext / scintilla / src / EditView.cxx
blob0a7b7d9d633c88df7d18a8c2a27fce58cf00953f
1 // Scintilla source code edit control
2 /** @file EditView.cxx
3 ** Defines the appearance of the main text area of the editor window.
4 **/
5 // Copyright 1998-2014 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 <cassert>
12 #include <cstring>
13 #include <cstdio>
14 #include <cmath>
16 #include <stdexcept>
17 #include <string>
18 #include <string_view>
19 #include <vector>
20 #include <map>
21 #include <set>
22 #include <forward_list>
23 #include <optional>
24 #include <algorithm>
25 #include <iterator>
26 #include <memory>
27 #include <chrono>
28 #include <atomic>
29 #include <thread>
30 #include <future>
32 #include "ScintillaTypes.h"
33 #include "ScintillaMessages.h"
34 #include "ScintillaStructures.h"
35 #include "ILoader.h"
36 #include "ILexer.h"
38 #include "Debugging.h"
39 #include "Geometry.h"
40 #include "Platform.h"
42 #include "CharacterType.h"
43 #include "CharacterCategoryMap.h"
44 #include "Position.h"
45 #include "UniqueString.h"
46 #include "SplitVector.h"
47 #include "Partitioning.h"
48 #include "RunStyles.h"
49 #include "ContractionState.h"
50 #include "CellBuffer.h"
51 #include "PerLine.h"
52 #include "KeyMap.h"
53 #include "Indicator.h"
54 #include "LineMarker.h"
55 #include "Style.h"
56 #include "ViewStyle.h"
57 #include "CharClassify.h"
58 #include "Decoration.h"
59 #include "CaseFolder.h"
60 #include "Document.h"
61 #include "UniConversion.h"
62 #include "Selection.h"
63 #include "PositionCache.h"
64 #include "EditModel.h"
65 #include "MarginView.h"
66 #include "EditView.h"
67 #include "ElapsedPeriod.h"
69 using namespace Scintilla;
70 using namespace Scintilla::Internal;
72 PrintParameters::PrintParameters() noexcept {
73 magnification = 0;
74 colourMode = PrintOption::Normal;
75 wrapState = Wrap::Word;
78 namespace {
80 int WidthStyledText(Surface *surface, const ViewStyle &vs, int styleOffset,
81 const char *text, const unsigned char *styles, size_t len) {
82 int width = 0;
83 size_t start = 0;
84 while (start < len) {
85 const unsigned char style = styles[start];
86 size_t endSegment = start;
87 while ((endSegment + 1 < len) && (styles[endSegment + 1] == style))
88 endSegment++;
89 const Font *fontText = vs.styles[style + styleOffset].font.get();
90 const std::string_view sv(text + start, endSegment - start + 1);
91 width += static_cast<int>(surface->WidthText(fontText, sv));
92 start = endSegment + 1;
94 return width;
99 namespace Scintilla::Internal {
101 bool ValidStyledText(const ViewStyle &vs, size_t styleOffset, const StyledText &st) noexcept {
102 if (st.multipleStyles) {
103 for (size_t iStyle = 0; iStyle<st.length; iStyle++) {
104 if (!vs.ValidStyle(styleOffset + st.styles[iStyle]))
105 return false;
107 } else {
108 if (!vs.ValidStyle(styleOffset + st.style))
109 return false;
111 return true;
114 int WidestLineWidth(Surface *surface, const ViewStyle &vs, int styleOffset, const StyledText &st) {
115 int widthMax = 0;
116 size_t start = 0;
117 while (start < st.length) {
118 const size_t lenLine = st.LineLength(start);
119 int widthSubLine;
120 if (st.multipleStyles) {
121 widthSubLine = WidthStyledText(surface, vs, styleOffset, st.text + start, st.styles + start, lenLine);
122 } else {
123 const Font *fontText = vs.styles[styleOffset + st.style].font.get();
124 const std::string_view text(st.text + start, lenLine);
125 widthSubLine = static_cast<int>(surface->WidthText(fontText, text));
127 if (widthSubLine > widthMax)
128 widthMax = widthSubLine;
129 start += lenLine + 1;
131 return widthMax;
134 void DrawTextNoClipPhase(Surface *surface, PRectangle rc, const Style &style, XYPOSITION ybase,
135 std::string_view text, DrawPhase phase) {
136 const Font *fontText = style.font.get();
137 if (FlagSet(phase, DrawPhase::back)) {
138 if (FlagSet(phase, DrawPhase::text)) {
139 // Drawing both
140 surface->DrawTextNoClip(rc, fontText, ybase, text,
141 style.fore, style.back);
142 } else {
143 surface->FillRectangleAligned(rc, Fill(style.back));
145 } else if (FlagSet(phase, DrawPhase::text)) {
146 surface->DrawTextTransparent(rc, fontText, ybase, text, style.fore);
150 void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRectangle rcText,
151 const StyledText &st, size_t start, size_t length, DrawPhase phase) {
153 if (st.multipleStyles) {
154 int x = static_cast<int>(rcText.left);
155 size_t i = 0;
156 while (i < length) {
157 size_t end = i;
158 size_t style = st.styles[i + start];
159 while (end < length - 1 && st.styles[start + end + 1] == style)
160 end++;
161 style += styleOffset;
162 const Font *fontText = vs.styles[style].font.get();
163 const std::string_view text(st.text + start + i, end - i + 1);
164 const int width = static_cast<int>(surface->WidthText(fontText, text));
165 PRectangle rcSegment = rcText;
166 rcSegment.left = static_cast<XYPOSITION>(x);
167 rcSegment.right = static_cast<XYPOSITION>(x + width + 1);
168 DrawTextNoClipPhase(surface, rcSegment, vs.styles[style],
169 rcText.top + vs.maxAscent, text, phase);
170 x += width;
171 i = end + 1;
173 } else {
174 const size_t style = st.style + styleOffset;
175 DrawTextNoClipPhase(surface, rcText, vs.styles[style],
176 rcText.top + vs.maxAscent,
177 std::string_view(st.text + start, length), phase);
183 EditView::EditView() {
184 tabWidthMinimumPixels = 2; // needed for calculating tab stops for fractional proportional fonts
185 drawOverstrikeCaret = true;
186 bufferedDraw = true;
187 phasesDraw = PhasesDraw::Two;
188 lineWidthMaxSeen = 0;
189 additionalCaretsBlink = true;
190 additionalCaretsVisible = true;
191 imeCaretBlockOverride = false;
192 llc.SetLevel(LineCache::Caret);
193 posCache = CreatePositionCache();
194 posCache->SetSize(0x400);
195 maxLayoutThreads = 1;
196 tabArrowHeight = 4;
197 customDrawTabArrow = nullptr;
198 customDrawWrapMarker = nullptr;
201 EditView::~EditView() = default;
203 bool EditView::SetTwoPhaseDraw(bool twoPhaseDraw) noexcept {
204 const PhasesDraw phasesDrawNew = twoPhaseDraw ? PhasesDraw::Two : PhasesDraw::One;
205 const bool redraw = phasesDraw != phasesDrawNew;
206 phasesDraw = phasesDrawNew;
207 return redraw;
210 bool EditView::SetPhasesDraw(int phases) noexcept {
211 const PhasesDraw phasesDrawNew = static_cast<PhasesDraw>(phases);
212 const bool redraw = phasesDraw != phasesDrawNew;
213 phasesDraw = phasesDrawNew;
214 return redraw;
217 bool EditView::LinesOverlap() const noexcept {
218 return phasesDraw == PhasesDraw::Multiple;
221 void EditView::SetLayoutThreads(unsigned int threads) noexcept {
222 maxLayoutThreads = std::clamp(threads, 1U, std::thread::hardware_concurrency());
225 unsigned int EditView::GetLayoutThreads() const noexcept {
226 return maxLayoutThreads;
229 void EditView::ClearAllTabstops() noexcept {
230 ldTabstops.reset();
233 XYPOSITION EditView::NextTabstopPos(Sci::Line line, XYPOSITION x, XYPOSITION tabWidth) const noexcept {
234 const int next = GetNextTabstop(line, static_cast<int>(x + tabWidthMinimumPixels));
235 if (next > 0)
236 return static_cast<XYPOSITION>(next);
237 return (static_cast<int>((x + tabWidthMinimumPixels) / tabWidth) + 1) * tabWidth;
240 bool EditView::ClearTabstops(Sci::Line line) noexcept {
241 return ldTabstops && ldTabstops->ClearTabstops(line);
244 bool EditView::AddTabstop(Sci::Line line, int x) {
245 if (!ldTabstops) {
246 ldTabstops = std::make_unique<LineTabstops>();
248 return ldTabstops && ldTabstops->AddTabstop(line, x);
251 int EditView::GetNextTabstop(Sci::Line line, int x) const noexcept {
252 if (ldTabstops) {
253 return ldTabstops->GetNextTabstop(line, x);
254 } else {
255 return 0;
259 void EditView::LinesAddedOrRemoved(Sci::Line lineOfPos, Sci::Line linesAdded) {
260 if (ldTabstops) {
261 if (linesAdded > 0) {
262 for (Sci::Line line = lineOfPos; line < lineOfPos + linesAdded; line++) {
263 ldTabstops->InsertLine(line);
265 } else {
266 for (Sci::Line line = (lineOfPos + -linesAdded) - 1; line >= lineOfPos; line--) {
267 ldTabstops->RemoveLine(line);
273 void EditView::DropGraphics() noexcept {
274 pixmapLine.reset();
275 pixmapIndentGuide.reset();
276 pixmapIndentGuideHighlight.reset();
279 void EditView::RefreshPixMaps(Surface *surfaceWindow, const ViewStyle &vsDraw) {
280 if (!pixmapIndentGuide) {
281 // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line
282 pixmapIndentGuide = surfaceWindow->AllocatePixMap(1, vsDraw.lineHeight + 1);
283 pixmapIndentGuideHighlight = surfaceWindow->AllocatePixMap(1, vsDraw.lineHeight + 1);
284 const PRectangle rcIG = PRectangle::FromInts(0, 0, 1, vsDraw.lineHeight);
285 pixmapIndentGuide->FillRectangle(rcIG, vsDraw.styles[StyleIndentGuide].back);
286 pixmapIndentGuideHighlight->FillRectangle(rcIG, vsDraw.styles[StyleBraceLight].back);
287 for (int stripe = 1; stripe < vsDraw.lineHeight + 1; stripe += 2) {
288 const PRectangle rcPixel = PRectangle::FromInts(0, stripe, 1, stripe + 1);
289 pixmapIndentGuide->FillRectangle(rcPixel, vsDraw.styles[StyleIndentGuide].fore);
290 pixmapIndentGuideHighlight->FillRectangle(rcPixel, vsDraw.styles[StyleBraceLight].fore);
292 pixmapIndentGuide->FlushDrawing();
293 pixmapIndentGuideHighlight->FlushDrawing();
297 std::shared_ptr<LineLayout> EditView::RetrieveLineLayout(Sci::Line lineNumber, const EditModel &model) {
298 const Sci::Position posLineStart = model.pdoc->LineStart(lineNumber);
299 const Sci::Position posLineEnd = model.pdoc->LineStart(lineNumber + 1);
300 PLATFORM_ASSERT(posLineEnd >= posLineStart);
301 const Sci::Line lineCaret = model.pdoc->SciLineFromPosition(model.sel.MainCaret());
302 return llc.Retrieve(lineNumber, lineCaret,
303 static_cast<int>(posLineEnd - posLineStart), model.pdoc->GetStyleClock(),
304 model.LinesOnScreen() + 1, model.pdoc->LinesTotal());
307 namespace {
309 constexpr XYPOSITION epsilon = 0.0001f; // A small nudge to avoid floating point precision issues
312 * Return the chDoc argument with case transformed as indicated by the caseForce argument.
313 * chPrevious is needed for camel casing.
314 * This only affects ASCII characters and is provided for languages with case-insensitive
315 * ASCII keywords where the user wishes to view keywords in a preferred case.
317 inline char CaseForce(Style::CaseForce caseForce, char chDoc, char chPrevious) noexcept {
318 switch (caseForce) {
319 case Style::CaseForce::mixed:
320 return chDoc;
321 case Style::CaseForce::lower:
322 return MakeLowerCase(chDoc);
323 case Style::CaseForce::upper:
324 return MakeUpperCase(chDoc);
325 case Style::CaseForce::camel:
326 default: // default should not occur, included to avoid warnings
327 if (IsUpperOrLowerCase(chDoc) && !IsUpperOrLowerCase(chPrevious)) {
328 return MakeUpperCase(chDoc);
329 } else {
330 return MakeLowerCase(chDoc);
335 void LayoutSegments(IPositionCache *pCache,
336 Surface *surface,
337 const ViewStyle &vstyle,
338 LineLayout *ll,
339 const std::vector<TextSegment> &segments,
340 std::atomic<uint32_t> &nextIndex,
341 const bool textUnicode,
342 const bool multiThreaded) {
343 while (true) {
344 const uint32_t i = nextIndex.fetch_add(1, std::memory_order_acq_rel);
345 if (i >= segments.size()) {
346 break;
348 const TextSegment &ts = segments[i];
349 const unsigned int styleSegment = ll->styles[ts.start];
350 XYPOSITION *positions = &ll->positions[ts.start + 1];
351 if (vstyle.styles[styleSegment].visible) {
352 if (ts.representation) {
353 XYPOSITION representationWidth = 0.0;
354 // Tab is a special case of representation, taking a variable amount of space
355 // which will be filled in later.
356 if (ll->chars[ts.start] != '\t') {
357 representationWidth = vstyle.controlCharWidth;
358 if (representationWidth <= 0.0) {
359 assert(ts.representation->stringRep.length() <= Representation::maxLength);
360 XYPOSITION positionsRepr[Representation::maxLength + 1];
361 // ts.representation->stringRep is UTF-8.
362 pCache->MeasureWidths(surface, vstyle, StyleControlChar, true, ts.representation->stringRep,
363 positionsRepr, multiThreaded);
364 representationWidth = positionsRepr[ts.representation->stringRep.length() - 1];
365 if (FlagSet(ts.representation->appearance, RepresentationAppearance::Blob)) {
366 representationWidth += vstyle.ctrlCharPadding;
370 std::fill(positions, positions + ts.length, representationWidth);
371 } else {
372 if ((ts.length == 1) && (' ' == ll->chars[ts.start])) {
373 // Over half the segments are single characters and of these about half are space characters.
374 positions[0] = vstyle.styles[styleSegment].spaceWidth;
375 } else {
376 pCache->MeasureWidths(surface, vstyle, styleSegment, textUnicode,
377 std::string_view(&ll->chars[ts.start], ts.length), positions, multiThreaded);
380 } else if (vstyle.styles[styleSegment].invisibleRepresentation[0]) {
381 const std::string_view text = vstyle.styles[styleSegment].invisibleRepresentation;
382 XYPOSITION positionsRepr[Representation::maxLength + 1];
383 // invisibleRepresentation is UTF-8.
384 pCache->MeasureWidths(surface, vstyle, styleSegment, true, text, positionsRepr, multiThreaded);
385 const XYPOSITION representationWidth = positionsRepr[text.length() - 1];
386 std::fill(positions, positions + ts.length, representationWidth);
394 * Fill in the LineLayout data for the given line.
395 * Copy the given @a line and its styles from the document into local arrays.
396 * Also determine the x position at which each character starts.
398 void EditView::LayoutLine(const EditModel &model, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, int width, bool callerMultiThreaded) {
399 if (!ll)
400 return;
401 const Sci::Line line = ll->LineNumber();
402 PLATFORM_ASSERT(line < model.pdoc->LinesTotal());
403 PLATFORM_ASSERT(ll->chars);
404 const Sci::Position posLineStart = model.pdoc->LineStart(line);
405 Sci::Position posLineEnd = model.pdoc->LineStart(line + 1);
406 // If the line is very long, limit the treatment to a length that should fit in the viewport
407 if (posLineEnd >(posLineStart + ll->maxLineLength)) {
408 posLineEnd = posLineStart + ll->maxLineLength;
410 // Hard to cope when too narrow, so just assume there is space
411 width = std::max(width, 20);
413 if (ll->validity == LineLayout::ValidLevel::checkTextAndStyle) {
414 Sci::Position lineLength = posLineEnd - posLineStart;
415 if (!vstyle.viewEOL) {
416 lineLength = model.pdoc->LineEnd(line) - posLineStart;
418 if (lineLength == ll->numCharsInLine) {
419 // See if chars, styles, indicators, are all the same
420 bool allSame = true;
421 // Check base line layout
422 char chPrevious = 0;
423 for (Sci::Position numCharsInLine = 0; numCharsInLine < lineLength; numCharsInLine++) {
424 const Sci::Position charInDoc = numCharsInLine + posLineStart;
425 const char chDoc = model.pdoc->CharAt(charInDoc);
426 const int styleByte = model.pdoc->StyleIndexAt(charInDoc);
427 allSame = allSame &&
428 (ll->styles[numCharsInLine] == styleByte);
429 allSame = allSame &&
430 (ll->chars[numCharsInLine] == CaseForce(vstyle.styles[styleByte].caseForce, chDoc, chPrevious));
431 chPrevious = chDoc;
433 const int styleByteLast = (posLineEnd > posLineStart) ? model.pdoc->StyleIndexAt(posLineEnd - 1) : 0;
434 allSame = allSame && (ll->styles[lineLength] == styleByteLast); // For eolFilled
435 if (allSame) {
436 ll->validity = (ll->widthLine != width) ? LineLayout::ValidLevel::positions : LineLayout::ValidLevel::lines;
437 } else {
438 ll->validity = LineLayout::ValidLevel::invalid;
440 } else {
441 ll->validity = LineLayout::ValidLevel::invalid;
444 if (ll->validity == LineLayout::ValidLevel::invalid) {
445 ll->widthLine = LineLayout::wrapWidthInfinite;
446 ll->lines = 1;
447 if (vstyle.edgeState == EdgeVisualStyle::Background) {
448 Sci::Position edgePosition = model.pdoc->FindColumn(line, vstyle.theEdge.column);
449 if (edgePosition >= posLineStart) {
450 edgePosition -= posLineStart;
452 ll->edgeColumn = static_cast<int>(edgePosition);
453 } else {
454 ll->edgeColumn = -1;
457 // Fill base line layout
458 const int lineLength = static_cast<int>(posLineEnd - posLineStart);
459 model.pdoc->GetCharRange(ll->chars.get(), posLineStart, lineLength);
460 model.pdoc->GetStyleRange(ll->styles.get(), posLineStart, lineLength);
461 const int numCharsBeforeEOL = static_cast<int>(model.pdoc->LineEnd(line) - posLineStart);
462 const int numCharsInLine = (vstyle.viewEOL) ? lineLength : numCharsBeforeEOL;
463 const unsigned char styleByteLast = (lineLength > 0) ? ll->styles[lineLength - 1] : 0;
464 if (vstyle.someStylesForceCase) {
465 char chPrevious = 0;
466 for (int charInLine = 0; charInLine<lineLength; charInLine++) {
467 const char chDoc = ll->chars[charInLine];
468 ll->chars[charInLine] = CaseForce(vstyle.styles[ll->styles[charInLine]].caseForce, chDoc, chPrevious);
469 chPrevious = chDoc;
472 ll->xHighlightGuide = 0;
473 // Extra element at the end of the line to hold end x position and act as
474 ll->chars[numCharsInLine] = 0; // Also triggers processing in the loops as this is a control character
475 ll->styles[numCharsInLine] = styleByteLast; // For eolFilled
477 // Layout the line, determining the position of each character,
478 // with an extra element at the end for the end of the line.
479 ll->positions[0] = 0;
480 bool lastSegItalics = false;
482 std::vector<TextSegment> segments;
483 BreakFinder bfLayout(ll, nullptr, Range(0, numCharsInLine), posLineStart, 0, BreakFinder::BreakFor::Text, model.pdoc, model.reprs.get(), nullptr);
484 while (bfLayout.More()) {
485 segments.push_back(bfLayout.Next());
488 ll->ClearPositions();
490 if (!segments.empty()) {
492 const size_t threadsForLength = std::max(1, numCharsInLine / bytesPerLayoutThread);
493 size_t threads = std::min<size_t>({ segments.size(), threadsForLength, maxLayoutThreads });
494 if (!surface->SupportsFeature(Supports::ThreadSafeMeasureWidths) || callerMultiThreaded) {
495 threads = 1;
498 std::atomic<uint32_t> nextIndex = 0;
500 const bool textUnicode = CpUtf8 == model.pdoc->dbcsCodePage;
501 const bool multiThreaded = threads > 1;
502 const bool multiThreadedContext = multiThreaded || callerMultiThreaded;
503 IPositionCache *pCache = posCache.get();
505 // If only 1 thread needed then use the main thread, else spin up multiple
506 const std::launch policy = (multiThreaded) ? std::launch::async : std::launch::deferred;
508 std::vector<std::future<void>> futures;
509 for (size_t th = 0; th < threads; th++) {
510 // Find relative positions of everything except for tabs
511 std::future<void> fut = std::async(policy,
512 [pCache, surface, &vstyle, &ll, &segments, &nextIndex, textUnicode, multiThreadedContext]() {
513 LayoutSegments(pCache, surface, vstyle, ll, segments, nextIndex, textUnicode, multiThreadedContext);
515 futures.push_back(std::move(fut));
517 for (const std::future<void> &f : futures) {
518 f.wait();
522 // Accumulate absolute positions from relative positions within segments and expand tabs
523 XYPOSITION xPosition = 0.0;
524 size_t iByte = 0;
525 ll->positions[iByte++] = xPosition;
526 for (const TextSegment &ts : segments) {
527 if (vstyle.styles[ll->styles[ts.start]].visible &&
528 ts.representation &&
529 (ll->chars[ts.start] == '\t')) {
530 // Simple visible tab, go to next tab stop
531 const XYPOSITION startTab = ll->positions[ts.start];
532 const XYPOSITION nextTab = NextTabstopPos(line, startTab, vstyle.tabWidth);
533 xPosition += nextTab - startTab;
535 const XYPOSITION xBeginSegment = xPosition;
536 for (int i = 0; i < ts.length; i++) {
537 xPosition = ll->positions[iByte] + xBeginSegment;
538 ll->positions[iByte++] = xPosition;
542 if (!segments.empty()) {
543 // Not quite the same as before which would effectively ignore trailing invisible segments
544 const TextSegment &ts = segments.back();
545 lastSegItalics = (!ts.representation) && ((ll->chars[ts.end() - 1] != ' ') && vstyle.styles[ll->styles[ts.start]].italic);
548 // Small hack to make lines that end with italics not cut off the edge of the last character
549 if (lastSegItalics) {
550 ll->positions[numCharsInLine] += vstyle.lastSegItalicsOffset;
552 ll->numCharsInLine = numCharsInLine;
553 ll->numCharsBeforeEOL = numCharsBeforeEOL;
554 ll->validity = LineLayout::ValidLevel::positions;
556 if ((ll->validity == LineLayout::ValidLevel::positions) || (ll->widthLine != width)) {
557 ll->widthLine = width;
558 if (width == LineLayout::wrapWidthInfinite) {
559 ll->lines = 1;
560 } else if (width > ll->positions[ll->numCharsInLine]) {
561 // Simple common case where line does not need wrapping.
562 ll->lines = 1;
563 } else {
564 if (FlagSet(vstyle.wrap.visualFlags, WrapVisualFlag::End)) {
565 width -= static_cast<int>(vstyle.aveCharWidth); // take into account the space for end wrap mark
567 XYPOSITION wrapAddIndent = 0; // This will be added to initial indent of line
568 switch (vstyle.wrap.indentMode) {
569 case WrapIndentMode::Fixed:
570 wrapAddIndent = vstyle.wrap.visualStartIndent * vstyle.aveCharWidth;
571 break;
572 case WrapIndentMode::Indent:
573 wrapAddIndent = model.pdoc->IndentSize() * vstyle.spaceWidth;
574 break;
575 case WrapIndentMode::DeepIndent:
576 wrapAddIndent = model.pdoc->IndentSize() * 2 * vstyle.spaceWidth;
577 break;
578 default: // No additional indent for WrapIndentMode::Fixed
579 break;
581 ll->wrapIndent = wrapAddIndent;
582 if (vstyle.wrap.indentMode != WrapIndentMode::Fixed) {
583 for (int i = 0; i < ll->numCharsInLine; i++) {
584 if (!IsSpaceOrTab(ll->chars[i])) {
585 ll->wrapIndent += ll->positions[i]; // Add line indent
586 break;
590 // Check for text width minimum
591 if (ll->wrapIndent > width - static_cast<int>(vstyle.aveCharWidth) * 15)
592 ll->wrapIndent = wrapAddIndent;
593 // Check for wrapIndent minimum
594 if ((FlagSet(vstyle.wrap.visualFlags, WrapVisualFlag::Start)) && (ll->wrapIndent < vstyle.aveCharWidth))
595 ll->wrapIndent = vstyle.aveCharWidth; // Indent to show start visual
596 ll->WrapLine(model.pdoc, posLineStart, vstyle.wrap.state, width);
598 ll->validity = LineLayout::ValidLevel::lines;
602 // Fill the LineLayout bidirectional data fields according to each char style
604 void EditView::UpdateBidiData(const EditModel &model, const ViewStyle &vstyle, LineLayout *ll) {
605 if (model.BidirectionalEnabled()) {
606 ll->EnsureBidiData();
607 for (int stylesInLine = 0; stylesInLine < ll->numCharsInLine; stylesInLine++) {
608 ll->bidiData->stylesFonts[stylesInLine] = vstyle.styles[ll->styles[stylesInLine]].font;
610 ll->bidiData->stylesFonts[ll->numCharsInLine].reset();
612 for (int charsInLine = 0; charsInLine < ll->numCharsInLine; charsInLine++) {
613 const int charWidth = UTF8DrawBytes(&ll->chars[charsInLine], ll->numCharsInLine - charsInLine);
614 const Representation *repr = model.reprs->RepresentationFromCharacter(std::string_view(&ll->chars[charsInLine], charWidth));
616 ll->bidiData->widthReprs[charsInLine] = 0.0f;
617 if (repr && ll->chars[charsInLine] != '\t') {
618 ll->bidiData->widthReprs[charsInLine] = ll->positions[charsInLine + charWidth] - ll->positions[charsInLine];
620 if (charWidth > 1) {
621 for (int c = 1; c < charWidth; c++) {
622 charsInLine++;
623 ll->bidiData->widthReprs[charsInLine] = 0.0f;
627 ll->bidiData->widthReprs[ll->numCharsInLine] = 0.0f;
628 } else {
629 ll->bidiData.reset();
633 Point EditView::LocationFromPosition(Surface *surface, const EditModel &model, SelectionPosition pos, Sci::Line topLine,
634 const ViewStyle &vs, PointEnd pe, const PRectangle rcClient) {
635 Point pt;
636 if (pos.Position() == Sci::invalidPosition)
637 return pt;
638 Sci::Line lineDoc = model.pdoc->SciLineFromPosition(pos.Position());
639 Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
640 if (FlagSet(pe, PointEnd::lineEnd) && (lineDoc > 0) && (pos.Position() == posLineStart)) {
641 // Want point at end of first line
642 lineDoc--;
643 posLineStart = model.pdoc->LineStart(lineDoc);
645 const Sci::Line lineVisible = model.pcs->DisplayFromDoc(lineDoc);
646 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
647 if (surface && ll) {
648 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
649 const int posInLine = static_cast<int>(pos.Position() - posLineStart);
650 pt = ll->PointFromPosition(posInLine, vs.lineHeight, pe);
651 pt.x += vs.textStart - model.xOffset;
653 if (model.BidirectionalEnabled()) {
654 // Fill the line bidi data
655 UpdateBidiData(model, vs, ll.get());
657 // Find subLine
658 const int subLine = ll->SubLineFromPosition(posInLine, pe);
659 const int caretPosition = posInLine - ll->LineStart(subLine);
661 // Get the point from current position
662 const ScreenLine screenLine(ll.get(), subLine, vs, rcClient.right, tabWidthMinimumPixels);
663 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
664 pt.x = slLayout->XFromPosition(caretPosition);
666 pt.x += vs.textStart - model.xOffset;
668 pt.y = 0;
669 if (posInLine >= ll->LineStart(subLine)) {
670 pt.y = static_cast<XYPOSITION>(subLine*vs.lineHeight);
673 pt.y += (lineVisible - topLine) * vs.lineHeight;
674 pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth;
676 return pt;
679 Range EditView::RangeDisplayLine(Surface *surface, const EditModel &model, Sci::Line lineVisible, const ViewStyle &vs) {
680 Range rangeSubLine = Range(0, 0);
681 if (lineVisible < 0) {
682 return rangeSubLine;
684 const Sci::Line lineDoc = model.pcs->DocFromDisplay(lineVisible);
685 const Sci::Position positionLineStart = model.pdoc->LineStart(lineDoc);
686 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
687 if (surface && ll) {
688 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
689 const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc);
690 const int subLine = static_cast<int>(lineVisible - lineStartSet);
691 if (subLine < ll->lines) {
692 rangeSubLine = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
693 if (subLine == ll->lines-1) {
694 rangeSubLine.end = model.pdoc->LineStart(lineDoc + 1) -
695 positionLineStart;
699 rangeSubLine.start += positionLineStart;
700 rangeSubLine.end += positionLineStart;
701 return rangeSubLine;
704 SelectionPosition EditView::SPositionFromLocation(Surface *surface, const EditModel &model, PointDocument pt, bool canReturnInvalid,
705 bool charPosition, bool virtualSpace, const ViewStyle &vs, const PRectangle rcClient) {
706 pt.x = pt.x - vs.textStart;
707 Sci::Line visibleLine = static_cast<int>(std::floor(pt.y / vs.lineHeight));
708 if (!canReturnInvalid && (visibleLine < 0))
709 visibleLine = 0;
710 const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine);
711 if (canReturnInvalid && (lineDoc < 0))
712 return SelectionPosition(Sci::invalidPosition);
713 if (lineDoc >= model.pdoc->LinesTotal())
714 return SelectionPosition(canReturnInvalid ? Sci::invalidPosition :
715 model.pdoc->Length());
716 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
717 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
718 if (surface && ll) {
719 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
720 const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc);
721 const int subLine = static_cast<int>(visibleLine - lineStartSet);
722 if (subLine < ll->lines) {
723 const Range rangeSubLine = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
724 const XYPOSITION subLineStart = ll->positions[rangeSubLine.start];
725 if (subLine > 0) // Wrapped
726 pt.x -= ll->wrapIndent;
727 Sci::Position positionInLine = 0;
728 if (model.BidirectionalEnabled()) {
729 // Fill the line bidi data
730 UpdateBidiData(model, vs, ll.get());
732 const ScreenLine screenLine(ll.get(), subLine, vs, rcClient.right, tabWidthMinimumPixels);
733 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
734 positionInLine = slLayout->PositionFromX(pt.x, charPosition) +
735 rangeSubLine.start;
736 } else {
737 positionInLine = ll->FindPositionFromX(pt.x + subLineStart,
738 rangeSubLine, charPosition);
740 if (positionInLine < rangeSubLine.end) {
741 return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1));
743 if (virtualSpace) {
744 const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth;
745 const int spaceOffset = static_cast<int>(
746 (pt.x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth);
747 return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset);
748 } else if (canReturnInvalid) {
749 if (pt.x < (ll->positions[rangeSubLine.end] - subLineStart)) {
750 return SelectionPosition(model.pdoc->MovePositionOutsideChar(rangeSubLine.end + posLineStart, 1));
752 } else {
753 return SelectionPosition(rangeSubLine.end + posLineStart);
756 if (!canReturnInvalid)
757 return SelectionPosition(ll->numCharsInLine + posLineStart);
759 return SelectionPosition(canReturnInvalid ? Sci::invalidPosition : posLineStart);
763 * Find the document position corresponding to an x coordinate on a particular document line.
764 * Ensure is between whole characters when document is in multi-byte or UTF-8 mode.
765 * This method is used for rectangular selections and does not work on wrapped lines.
767 SelectionPosition EditView::SPositionFromLineX(Surface *surface, const EditModel &model, Sci::Line lineDoc, int x, const ViewStyle &vs) {
768 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
769 if (surface && ll) {
770 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
771 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
772 const Range rangeSubLine = ll->SubLineRange(0, LineLayout::Scope::visibleOnly);
773 const XYPOSITION subLineStart = ll->positions[rangeSubLine.start];
774 const Sci::Position positionInLine = ll->FindPositionFromX(x + subLineStart, rangeSubLine, false);
775 if (positionInLine < rangeSubLine.end) {
776 return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1));
778 const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth;
779 const int spaceOffset = static_cast<int>(
780 (x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth);
781 return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset);
783 return SelectionPosition(0);
786 Sci::Line EditView::DisplayFromPosition(Surface *surface, const EditModel &model, Sci::Position pos, const ViewStyle &vs) {
787 const Sci::Line lineDoc = model.pdoc->SciLineFromPosition(pos);
788 Sci::Line lineDisplay = model.pcs->DisplayFromDoc(lineDoc);
789 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
790 if (surface && ll) {
791 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
792 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
793 const Sci::Position posInLine = pos - posLineStart;
794 lineDisplay--; // To make up for first increment ahead.
795 for (int subLine = 0; subLine < ll->lines; subLine++) {
796 if (posInLine >= ll->LineStart(subLine)) {
797 lineDisplay++;
801 return lineDisplay;
804 Sci::Position EditView::StartEndDisplayLine(Surface *surface, const EditModel &model, Sci::Position pos, bool start, const ViewStyle &vs) {
805 const Sci::Line line = model.pdoc->SciLineFromPosition(pos);
806 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(line, model);
807 Sci::Position posRet = Sci::invalidPosition;
808 if (surface && ll) {
809 const Sci::Position posLineStart = model.pdoc->LineStart(line);
810 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
811 const Sci::Position posInLine = pos - posLineStart;
812 if (posInLine <= ll->maxLineLength) {
813 for (int subLine = 0; subLine < ll->lines; subLine++) {
814 if ((posInLine >= ll->LineStart(subLine)) &&
815 (posInLine <= ll->LineStart(subLine + 1)) &&
816 (posInLine <= ll->numCharsBeforeEOL)) {
817 if (start) {
818 posRet = ll->LineStart(subLine) + posLineStart;
819 } else {
820 if (subLine == ll->lines - 1)
821 posRet = ll->numCharsBeforeEOL + posLineStart;
822 else
823 posRet = model.pdoc->MovePositionOutsideChar(ll->LineStart(subLine + 1) + posLineStart - 1, -1, false);
829 return posRet;
832 namespace {
834 constexpr ColourRGBA colourBug(0xff, 0, 0xfe, 0xf0);
836 // Selection background colours are always defined, the value_or is to show if bug
838 ColourRGBA SelectionBackground(const EditModel &model, const ViewStyle &vsDraw, InSelection inSelection) {
839 if (inSelection == InSelection::inNone)
840 return colourBug; // Not selected is a bug
842 Element element = Element::SelectionBack;
843 if (inSelection == InSelection::inAdditional)
844 element = Element::SelectionAdditionalBack;
845 if (!model.primarySelection)
846 element = Element::SelectionSecondaryBack;
847 if (!model.hasFocus) {
848 if (inSelection == InSelection::inAdditional) {
849 if (ColourOptional colour = vsDraw.ElementColour(Element::SelectionInactiveAdditionalBack)) {
850 return *colour;
853 if (ColourOptional colour = vsDraw.ElementColour(Element::SelectionInactiveBack)) {
854 return *colour;
857 return vsDraw.ElementColour(element).value_or(colourBug);
860 ColourOptional SelectionForeground(const EditModel &model, const ViewStyle &vsDraw, InSelection inSelection) {
861 if (inSelection == InSelection::inNone)
862 return {};
863 Element element = Element::SelectionText;
864 if (inSelection == InSelection::inAdditional)
865 element = Element::SelectionAdditionalText;
866 if (!model.primarySelection) // Secondary selection
867 element = Element::SelectionSecondaryText;
868 if (!model.hasFocus) {
869 if (inSelection == InSelection::inAdditional) {
870 if (ColourOptional colour = vsDraw.ElementColour(Element::SelectionInactiveAdditionalText)) {
871 return colour;
874 element = Element::SelectionInactiveText;
876 return vsDraw.ElementColour(element);
879 ColourRGBA TextBackground(const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
880 ColourOptional background, InSelection inSelection, bool inHotspot, int styleMain, Sci::Position i) {
881 if (inSelection && (vsDraw.selection.layer == Layer::Base)) {
882 return SelectionBackground(model, vsDraw, inSelection).Opaque();
884 if ((vsDraw.edgeState == EdgeVisualStyle::Background) &&
885 (i >= ll->edgeColumn) &&
886 (i < ll->numCharsBeforeEOL))
887 return vsDraw.theEdge.colour;
888 if (inHotspot) {
889 if (const ColourOptional colourHotSpotBack = vsDraw.ElementColour(Element::HotSpotActiveBack)) {
890 return colourHotSpotBack->Opaque();
893 if (background && (styleMain != StyleBraceLight) && (styleMain != StyleBraceBad)) {
894 return *background;
895 } else {
896 return vsDraw.styles[styleMain].back;
900 void DrawTextBlob(Surface *surface, const ViewStyle &vsDraw, PRectangle rcSegment,
901 std::string_view text, ColourRGBA textBack, ColourRGBA textFore, bool fillBackground) {
902 if (rcSegment.Empty())
903 return;
904 if (fillBackground) {
905 surface->FillRectangleAligned(rcSegment, Fill(textBack));
907 const Font *ctrlCharsFont = vsDraw.styles[StyleControlChar].font.get();
908 const int normalCharHeight = static_cast<int>(std::ceil(vsDraw.styles[StyleControlChar].capitalHeight));
909 PRectangle rcCChar = rcSegment;
910 rcCChar.left = rcCChar.left + 1;
911 rcCChar.top = rcSegment.top + vsDraw.maxAscent - normalCharHeight;
912 rcCChar.bottom = rcSegment.top + vsDraw.maxAscent + 1;
913 PRectangle rcCentral = rcCChar;
914 rcCentral.top++;
915 rcCentral.bottom--;
916 surface->FillRectangleAligned(rcCentral, Fill(textFore));
917 PRectangle rcChar = rcCChar;
918 rcChar.left++;
919 rcChar.right--;
920 surface->DrawTextClippedUTF8(rcChar, ctrlCharsFont,
921 rcSegment.top + vsDraw.maxAscent, text,
922 textBack, textFore);
925 void FillLineRemainder(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
926 Sci::Line line, PRectangle rcArea, int subLine) {
927 InSelection eolInSelection = InSelection::inNone;
928 if (vsDraw.selection.visible && (subLine == (ll->lines - 1))) {
929 eolInSelection = model.LineEndInSelection(line);
932 if (eolInSelection && vsDraw.selection.eolFilled && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer == Layer::Base)) {
933 surface->FillRectangleAligned(rcArea, Fill(SelectionBackground(model, vsDraw, eolInSelection).Opaque()));
934 } else {
935 const ColourOptional background = vsDraw.Background(model.GetMark(line), model.caret.active, ll->containsCaret);
936 if (background) {
937 surface->FillRectangleAligned(rcArea, Fill(*background));
938 } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) {
939 surface->FillRectangleAligned(rcArea, Fill(vsDraw.styles[ll->styles[ll->numCharsInLine]].back));
940 } else {
941 surface->FillRectangleAligned(rcArea, Fill(vsDraw.styles[StyleDefault].back));
943 if (eolInSelection && vsDraw.selection.eolFilled && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer != Layer::Base)) {
944 surface->FillRectangleAligned(rcArea, SelectionBackground(model, vsDraw, eolInSelection));
951 void EditView::DrawEOL(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
952 Sci::Line line, int xStart, PRectangle rcLine, int subLine, Sci::Position lineEnd, XYPOSITION subLineStart, ColourOptional background) {
954 const Sci::Position posLineStart = model.pdoc->LineStart(line);
955 PRectangle rcSegment = rcLine;
957 const bool lastSubLine = subLine == (ll->lines - 1);
958 XYPOSITION virtualSpace = 0;
959 if (lastSubLine) {
960 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
961 virtualSpace = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)) * spaceWidth;
963 const XYPOSITION xEol = ll->positions[lineEnd] - subLineStart;
965 // Fill the virtual space and show selections within it
966 if (virtualSpace > 0.0f) {
967 rcSegment.left = xEol + xStart;
968 rcSegment.right = xEol + xStart + virtualSpace;
969 const ColourRGBA backgroundFill = background.value_or(vsDraw.styles[ll->styles[ll->numCharsInLine]].back);
970 surface->FillRectangleAligned(rcSegment, backgroundFill);
971 if (vsDraw.selection.visible && (vsDraw.selection.layer == Layer::Base)) {
972 const SelectionSegment virtualSpaceRange(SelectionPosition(model.pdoc->LineEnd(line)),
973 SelectionPosition(model.pdoc->LineEnd(line),
974 model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line))));
975 for (size_t r = 0; r<model.sel.Count(); r++) {
976 const SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange);
977 if (!portion.Empty()) {
978 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
979 rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] -
980 subLineStart+portion.start.VirtualSpace() * spaceWidth;
981 rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] -
982 subLineStart+portion.end.VirtualSpace() * spaceWidth;
983 rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left;
984 rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right;
985 surface->FillRectangleAligned(rcSegment, Fill(
986 SelectionBackground(model, vsDraw, model.sel.RangeType(r)).Opaque()));
992 InSelection eolInSelection = InSelection::inNone;
993 if (vsDraw.selection.visible && lastSubLine) {
994 eolInSelection = model.LineEndInSelection(line);
997 const ColourRGBA selectionBack = SelectionBackground(model, vsDraw, eolInSelection);
999 // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on
1000 XYPOSITION blobsWidth = 0;
1001 if (lastSubLine) {
1002 for (Sci::Position eolPos = ll->numCharsBeforeEOL; eolPos<ll->numCharsInLine;) {
1003 const int styleMain = ll->styles[eolPos];
1004 const ColourOptional selectionFore = SelectionForeground(model, vsDraw, eolInSelection);
1005 ColourRGBA textFore = selectionFore.value_or(vsDraw.styles[styleMain].fore);
1006 char hexits[4] = "";
1007 std::string_view ctrlChar;
1008 Sci::Position widthBytes = 1;
1009 RepresentationAppearance appearance = RepresentationAppearance::Blob;
1010 const Representation *repr = model.reprs->RepresentationFromCharacter(std::string_view(&ll->chars[eolPos], ll->numCharsInLine - eolPos));
1011 if (repr) {
1012 // Representation of whole text
1013 widthBytes = ll->numCharsInLine - eolPos;
1014 } else {
1015 repr = model.reprs->RepresentationFromCharacter(std::string_view(&ll->chars[eolPos], 1));
1017 if (repr) {
1018 ctrlChar = repr->stringRep;
1019 appearance = repr->appearance;
1020 if (FlagSet(appearance, RepresentationAppearance::Colour)) {
1021 textFore = repr->colour;
1023 } else {
1024 const unsigned char chEOL = ll->chars[eolPos];
1025 if (UTF8IsAscii(chEOL)) {
1026 ctrlChar = ControlCharacterString(chEOL);
1027 } else {
1028 Hexits(hexits, chEOL);
1029 ctrlChar = hexits;
1033 rcSegment.left = xStart + ll->positions[eolPos] - subLineStart + virtualSpace;
1034 rcSegment.right = xStart + ll->positions[eolPos + widthBytes] - subLineStart + virtualSpace;
1035 blobsWidth += rcSegment.Width();
1036 const ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, eolInSelection, false, styleMain, eolPos);
1037 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1)) {
1038 if (vsDraw.selection.layer == Layer::Base) {
1039 surface->FillRectangleAligned(rcSegment, Fill(selectionBack.Opaque()));
1040 } else {
1041 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1043 } else {
1044 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1046 const bool drawEOLSelection = eolInSelection && (line < model.pdoc->LinesTotal() - 1);
1047 ColourRGBA blobText = textBack;
1048 if (drawEOLSelection && (vsDraw.selection.layer == Layer::UnderText)) {
1049 surface->FillRectangleAligned(rcSegment, selectionBack);
1050 blobText = textBack.MixedWith(selectionBack, selectionBack.GetAlphaComponent());
1052 if (FlagSet(appearance, RepresentationAppearance::Blob)) {
1053 DrawTextBlob(surface, vsDraw, rcSegment, ctrlChar, blobText, textFore, phasesDraw == PhasesDraw::One);
1054 } else {
1055 surface->DrawTextTransparentUTF8(rcSegment, vsDraw.styles[StyleControlChar].font.get(),
1056 rcSegment.top + vsDraw.maxAscent, ctrlChar, textFore);
1058 if (drawEOLSelection && (vsDraw.selection.layer == Layer::OverText)) {
1059 surface->FillRectangleAligned(rcSegment, selectionBack);
1061 eolPos += widthBytes;
1065 // Draw the eol-is-selected rectangle
1066 rcSegment.left = xEol + xStart + virtualSpace + blobsWidth;
1067 rcSegment.right = rcSegment.left + vsDraw.aveCharWidth;
1069 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer == Layer::Base)) {
1070 surface->FillRectangleAligned(rcSegment, Fill(selectionBack.Opaque()));
1071 } else {
1072 if (background) {
1073 surface->FillRectangleAligned(rcSegment, Fill(*background));
1074 } else if (line < model.pdoc->LinesTotal() - 1) {
1075 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[ll->styles[ll->numCharsInLine]].back));
1076 } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) {
1077 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[ll->styles[ll->numCharsInLine]].back));
1078 } else {
1079 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[StyleDefault].back));
1081 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer != Layer::Base)) {
1082 surface->FillRectangleAligned(rcSegment, selectionBack);
1086 rcSegment.left = rcSegment.right;
1087 if (rcSegment.left < rcLine.left)
1088 rcSegment.left = rcLine.left;
1089 rcSegment.right = rcLine.right;
1091 const bool drawEOLAnnotationStyledText = (vsDraw.eolAnnotationVisible != EOLAnnotationVisible::Hidden) && model.pdoc->EOLAnnotationStyledText(line).text;
1092 const bool fillRemainder = (!lastSubLine || (!model.GetFoldDisplayText(line) && !drawEOLAnnotationStyledText));
1093 if (fillRemainder) {
1094 // Fill the remainder of the line
1095 FillLineRemainder(surface, model, vsDraw, ll, line, rcSegment, subLine);
1098 bool drawWrapMarkEnd = false;
1100 if (subLine + 1 < ll->lines) {
1101 if (FlagSet(vsDraw.wrap.visualFlags, WrapVisualFlag::End)) {
1102 drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0;
1104 if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret)) {
1105 // Draw right of frame under marker
1106 surface->FillRectangleAligned(Side(rcLine, Edge::right, vsDraw.GetFrameWidth()),
1107 vsDraw.ElementColourForced(Element::CaretLineBack).Opaque());
1111 if (drawWrapMarkEnd) {
1112 PRectangle rcPlace = rcSegment;
1113 const XYPOSITION maxLeft = rcPlace.right - vsDraw.aveCharWidth;
1115 if (FlagSet(vsDraw.wrap.visualFlagsLocation, WrapVisualLocation::EndByText)) {
1116 rcPlace.left = std::min(xEol + xStart + virtualSpace, maxLeft);
1117 rcPlace.right = rcPlace.left + vsDraw.aveCharWidth;
1118 } else {
1119 // rcLine is clipped to text area
1120 rcPlace.right = rcLine.right;
1121 rcPlace.left = maxLeft;
1123 if (!customDrawWrapMarker) {
1124 DrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour());
1125 } else {
1126 customDrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour());
1131 void EditView::DrawFoldDisplayText(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1132 Sci::Line line, int xStart, PRectangle rcLine, int subLine, XYPOSITION subLineStart, DrawPhase phase) {
1133 const bool lastSubLine = subLine == (ll->lines - 1);
1134 if (!lastSubLine)
1135 return;
1137 const char *text = model.GetFoldDisplayText(line);
1138 if (!text)
1139 return;
1141 PRectangle rcSegment = rcLine;
1142 const std::string_view foldDisplayText(text);
1143 const Font *fontText = vsDraw.styles[StyleFoldDisplayText].font.get();
1144 const int widthFoldDisplayText = static_cast<int>(surface->WidthText(fontText, foldDisplayText));
1146 InSelection eolInSelection = InSelection::inNone;
1147 if (vsDraw.selection.visible) {
1148 eolInSelection = model.LineEndInSelection(line);
1151 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1152 const XYPOSITION virtualSpace = model.sel.VirtualSpaceFor(
1153 model.pdoc->LineEnd(line)) * spaceWidth;
1154 rcSegment.left = xStart + ll->positions[ll->numCharsInLine] - subLineStart + virtualSpace + vsDraw.aveCharWidth;
1155 rcSegment.right = rcSegment.left + static_cast<XYPOSITION>(widthFoldDisplayText);
1157 const ColourOptional background = vsDraw.Background(model.GetMark(line), model.caret.active, ll->containsCaret);
1158 const ColourOptional selectionFore = SelectionForeground(model, vsDraw, eolInSelection);
1159 const ColourRGBA textFore = selectionFore.value_or(vsDraw.styles[StyleFoldDisplayText].fore);
1160 const ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, eolInSelection,
1161 false, StyleFoldDisplayText, -1);
1163 if (model.trackLineWidth) {
1164 if (rcSegment.right + 1> lineWidthMaxSeen) {
1165 // Fold display text border drawn on rcSegment.right with width 1 is the last visible object of the line
1166 lineWidthMaxSeen = static_cast<int>(rcSegment.right + 1);
1170 if (FlagSet(phase, DrawPhase::back)) {
1171 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1173 // Fill Remainder of the line
1174 PRectangle rcRemainder = rcSegment;
1175 rcRemainder.left = rcRemainder.right;
1176 if (rcRemainder.left < rcLine.left)
1177 rcRemainder.left = rcLine.left;
1178 rcRemainder.right = rcLine.right;
1179 FillLineRemainder(surface, model, vsDraw, ll, line, rcRemainder, subLine);
1182 if (FlagSet(phase, DrawPhase::text)) {
1183 if (phasesDraw != PhasesDraw::One) {
1184 surface->DrawTextTransparent(rcSegment, fontText,
1185 rcSegment.top + vsDraw.maxAscent, foldDisplayText,
1186 textFore);
1187 } else {
1188 surface->DrawTextNoClip(rcSegment, fontText,
1189 rcSegment.top + vsDraw.maxAscent, foldDisplayText,
1190 textFore, textBack);
1194 if (FlagSet(phase, DrawPhase::indicatorsFore)) {
1195 if (model.foldDisplayTextStyle == FoldDisplayTextStyle::Boxed) {
1196 PRectangle rcBox = rcSegment;
1197 rcBox.left = std::round(rcSegment.left);
1198 rcBox.right = std::round(rcSegment.right);
1199 surface->RectangleFrame(rcBox, Stroke(textFore));
1203 if (FlagSet(phase, DrawPhase::selectionTranslucent)) {
1204 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer != Layer::Base)) {
1205 surface->FillRectangleAligned(rcSegment, SelectionBackground(model, vsDraw, eolInSelection));
1210 void EditView::DrawEOLAnnotationText(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1211 Sci::Line line, int xStart, PRectangle rcLine, int subLine, XYPOSITION subLineStart, DrawPhase phase) {
1213 const bool lastSubLine = subLine == (ll->lines - 1);
1214 if (!lastSubLine)
1215 return;
1217 if (vsDraw.eolAnnotationVisible == EOLAnnotationVisible::Hidden) {
1218 return;
1220 const StyledText stEOLAnnotation = model.pdoc->EOLAnnotationStyledText(line);
1221 if (!stEOLAnnotation.text || !ValidStyledText(vsDraw, vsDraw.eolAnnotationStyleOffset, stEOLAnnotation)) {
1222 return;
1224 const std::string_view eolAnnotationText(stEOLAnnotation.text, stEOLAnnotation.length);
1225 const size_t style = stEOLAnnotation.style + vsDraw.eolAnnotationStyleOffset;
1227 PRectangle rcSegment = rcLine;
1228 const Font *fontText = vsDraw.styles[style].font.get();
1230 const Surface::Ends ends = static_cast<Surface::Ends>(static_cast<int>(vsDraw.eolAnnotationVisible) & 0xff);
1231 const Surface::Ends leftSide = static_cast<Surface::Ends>(static_cast<int>(ends) & 0xf);
1232 const Surface::Ends rightSide = static_cast<Surface::Ends>(static_cast<int>(ends) & 0xf0);
1234 XYPOSITION leftBoxSpace = 0;
1235 XYPOSITION rightBoxSpace = 0;
1236 if (vsDraw.eolAnnotationVisible >= EOLAnnotationVisible::Boxed) {
1237 leftBoxSpace = 1;
1238 rightBoxSpace = 1;
1239 if (vsDraw.eolAnnotationVisible != EOLAnnotationVisible::Boxed) {
1240 switch (leftSide) {
1241 case Surface::Ends::leftFlat:
1242 leftBoxSpace = 1;
1243 break;
1244 case Surface::Ends::leftAngle:
1245 leftBoxSpace = rcLine.Height() / 2.0;
1246 break;
1247 case Surface::Ends::semiCircles:
1248 default:
1249 leftBoxSpace = rcLine.Height() / 3.0;
1250 break;
1252 switch (rightSide) {
1253 case Surface::Ends::rightFlat:
1254 rightBoxSpace = 1;
1255 break;
1256 case Surface::Ends::rightAngle:
1257 rightBoxSpace = rcLine.Height() / 2.0;
1258 break;
1259 case Surface::Ends::semiCircles:
1260 default:
1261 rightBoxSpace = rcLine.Height() / 3.0;
1262 break;
1266 const int widthEOLAnnotationText = static_cast<int>(surface->WidthTextUTF8(fontText, eolAnnotationText) +
1267 leftBoxSpace + rightBoxSpace);
1269 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1270 const XYPOSITION virtualSpace = model.sel.VirtualSpaceFor(
1271 model.pdoc->LineEnd(line)) * spaceWidth;
1272 rcSegment.left = xStart +
1273 ll->positions[ll->numCharsInLine] - subLineStart
1274 + virtualSpace + vsDraw.aveCharWidth;
1276 const char *textFoldDisplay = model.GetFoldDisplayText(line);
1277 if (textFoldDisplay) {
1278 const std::string_view foldDisplayText(textFoldDisplay);
1279 rcSegment.left += static_cast<int>(
1280 surface->WidthText(vsDraw.styles[StyleFoldDisplayText].font.get(), foldDisplayText)) +
1281 vsDraw.aveCharWidth;
1283 rcSegment.right = rcSegment.left + static_cast<XYPOSITION>(widthEOLAnnotationText);
1285 const ColourOptional background = vsDraw.Background(model.GetMark(line), model.caret.active, ll->containsCaret);
1286 const ColourRGBA textFore = vsDraw.styles[style].fore;
1287 const ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, InSelection::inNone,
1288 false, static_cast<int>(style), -1);
1290 if (model.trackLineWidth) {
1291 if (rcSegment.right + 1> lineWidthMaxSeen) {
1292 // EOL Annotation text border drawn on rcSegment.right with width 1 is the last visible object of the line
1293 lineWidthMaxSeen = static_cast<int>(rcSegment.right + 1);
1297 if (FlagSet(phase, DrawPhase::back)) {
1298 // This fills in the whole remainder of the line even though
1299 // it may be double drawing. This is to allow stadiums with
1300 // curved or angled ends to have the area outside in the correct
1301 // background colour.
1302 PRectangle rcRemainder = rcSegment;
1303 rcRemainder.right = rcLine.right;
1304 FillLineRemainder(surface, model, vsDraw, ll, line, rcRemainder, subLine);
1307 PRectangle rcText = rcSegment;
1308 rcText.left += leftBoxSpace;
1309 rcText.right -= rightBoxSpace;
1311 // For single phase drawing, draw the text then any box over it
1312 if (FlagSet(phase, DrawPhase::text)) {
1313 if (phasesDraw == PhasesDraw::One) {
1314 surface->DrawTextNoClipUTF8(rcText, fontText,
1315 rcText.top + vsDraw.maxAscent, eolAnnotationText,
1316 textFore, textBack);
1320 // Draw any box or stadium shape
1321 if (FlagSet(phase, DrawPhase::indicatorsBack)) {
1322 const PRectangle rcBox = PixelAlign(rcSegment, 1);
1324 switch (vsDraw.eolAnnotationVisible) {
1325 case EOLAnnotationVisible::Standard:
1326 if (phasesDraw != PhasesDraw::One) {
1327 surface->FillRectangle(rcBox, textBack);
1329 break;
1331 case EOLAnnotationVisible::Boxed:
1332 if (phasesDraw == PhasesDraw::One) {
1333 // Draw a rectangular outline around the text
1334 surface->RectangleFrame(rcBox, textFore);
1335 } else {
1336 // Draw with a fill to fill the edges of the rectangle.
1337 surface->RectangleDraw(rcBox, FillStroke(textBack, textFore));
1339 break;
1341 default:
1342 if (phasesDraw == PhasesDraw::One) {
1343 // Draw an outline around the text
1344 surface->Stadium(rcBox, FillStroke(ColourRGBA(textBack, 0), textFore), ends);
1345 } else {
1346 // Draw with a fill to fill the edges of the shape.
1347 surface->Stadium(rcBox, FillStroke(textBack, textFore), ends);
1349 break;
1353 // For multi-phase drawing draw the text last as transparent over any box
1354 if (FlagSet(phase, DrawPhase::text)) {
1355 if (phasesDraw != PhasesDraw::One) {
1356 surface->DrawTextTransparentUTF8(rcText, fontText,
1357 rcText.top + vsDraw.maxAscent, eolAnnotationText,
1358 textFore);
1363 namespace {
1365 constexpr bool AnnotationBoxedOrIndented(AnnotationVisible annotationVisible) noexcept {
1366 return annotationVisible == AnnotationVisible::Boxed || annotationVisible == AnnotationVisible::Indented;
1371 void EditView::DrawAnnotation(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1372 Sci::Line line, int xStart, PRectangle rcLine, int subLine, DrawPhase phase) {
1373 const int indent = static_cast<int>(model.pdoc->GetLineIndentation(line) * vsDraw.spaceWidth);
1374 PRectangle rcSegment = rcLine;
1375 const int annotationLine = subLine - ll->lines;
1376 const StyledText stAnnotation = model.pdoc->AnnotationStyledText(line);
1377 if (stAnnotation.text && ValidStyledText(vsDraw, vsDraw.annotationStyleOffset, stAnnotation)) {
1378 if (FlagSet(phase, DrawPhase::back)) {
1379 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[0].back));
1381 rcSegment.left = static_cast<XYPOSITION>(xStart);
1382 if (model.trackLineWidth || AnnotationBoxedOrIndented(vsDraw.annotationVisible)) {
1383 // Only care about calculating width if tracking or need to draw indented box
1384 int widthAnnotation = WidestLineWidth(surface, vsDraw, vsDraw.annotationStyleOffset, stAnnotation);
1385 if (AnnotationBoxedOrIndented(vsDraw.annotationVisible)) {
1386 widthAnnotation += static_cast<int>(vsDraw.spaceWidth * 2); // Margins
1387 rcSegment.left = static_cast<XYPOSITION>(xStart + indent);
1388 rcSegment.right = rcSegment.left + widthAnnotation;
1390 if (widthAnnotation > lineWidthMaxSeen)
1391 lineWidthMaxSeen = widthAnnotation;
1393 const int annotationLines = model.pdoc->AnnotationLines(line);
1394 size_t start = 0;
1395 size_t lengthAnnotation = stAnnotation.LineLength(start);
1396 int lineInAnnotation = 0;
1397 while ((lineInAnnotation < annotationLine) && (start < stAnnotation.length)) {
1398 start += lengthAnnotation + 1;
1399 lengthAnnotation = stAnnotation.LineLength(start);
1400 lineInAnnotation++;
1402 PRectangle rcText = rcSegment;
1403 if ((FlagSet(phase, DrawPhase::back)) && AnnotationBoxedOrIndented(vsDraw.annotationVisible)) {
1404 surface->FillRectangleAligned(rcText,
1405 Fill(vsDraw.styles[stAnnotation.StyleAt(start) + vsDraw.annotationStyleOffset].back));
1406 rcText.left += vsDraw.spaceWidth;
1408 DrawStyledText(surface, vsDraw, vsDraw.annotationStyleOffset, rcText,
1409 stAnnotation, start, lengthAnnotation, phase);
1410 if ((FlagSet(phase, DrawPhase::back)) && (vsDraw.annotationVisible == AnnotationVisible::Boxed)) {
1411 const ColourRGBA colourBorder = vsDraw.styles[vsDraw.annotationStyleOffset].fore;
1412 const PRectangle rcBorder = PixelAlignOutside(rcSegment, surface->PixelDivisions());
1413 surface->FillRectangle(Side(rcBorder, Edge::left, 1), colourBorder);
1414 surface->FillRectangle(Side(rcBorder, Edge::right, 1), colourBorder);
1415 if (subLine == ll->lines) {
1416 surface->FillRectangle(Side(rcBorder, Edge::top, 1), colourBorder);
1418 if (subLine == ll->lines + annotationLines - 1) {
1419 surface->FillRectangle(Side(rcBorder, Edge::bottom, 1), colourBorder);
1422 } else {
1423 // No annotation to draw so show bug with colourBug
1424 if (FlagSet(phase, DrawPhase::back)) {
1425 surface->FillRectangle(rcSegment, colourBug.Opaque());
1430 namespace {
1432 void DrawBlockCaret(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1433 int subLine, int xStart, Sci::Position offset, Sci::Position posCaret, PRectangle rcCaret, ColourRGBA caretColour) {
1435 const Sci::Position lineStart = ll->LineStart(subLine);
1436 Sci::Position posBefore = posCaret;
1437 Sci::Position posAfter = model.pdoc->MovePositionOutsideChar(posCaret + 1, 1);
1438 Sci::Position numCharsToDraw = posAfter - posCaret;
1440 // Work out where the starting and ending offsets are. We need to
1441 // see if the previous character shares horizontal space, such as a
1442 // glyph / combining character. If so we'll need to draw that too.
1443 Sci::Position offsetFirstChar = offset;
1444 Sci::Position offsetLastChar = offset + (posAfter - posCaret);
1445 while ((posBefore > 0) && ((offsetLastChar - numCharsToDraw) >= lineStart)) {
1446 if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - numCharsToDraw]) > 0) {
1447 // The char does not share horizontal space
1448 break;
1450 // Char shares horizontal space, update the numChars to draw
1451 // Update posBefore to point to the prev char
1452 posBefore = model.pdoc->MovePositionOutsideChar(posBefore - 1, -1);
1453 numCharsToDraw = posAfter - posBefore;
1454 offsetFirstChar = offset - (posCaret - posBefore);
1457 // See if the next character shares horizontal space, if so we'll
1458 // need to draw that too.
1459 if (offsetFirstChar < 0)
1460 offsetFirstChar = 0;
1461 numCharsToDraw = offsetLastChar - offsetFirstChar;
1462 while ((offsetLastChar < ll->LineStart(subLine + 1)) && (offsetLastChar <= ll->numCharsInLine)) {
1463 // Update posAfter to point to the 2nd next char, this is where
1464 // the next character ends, and 2nd next begins. We'll need
1465 // to compare these two
1466 posBefore = posAfter;
1467 posAfter = model.pdoc->MovePositionOutsideChar(posAfter + 1, 1);
1468 offsetLastChar = offset + (posAfter - posCaret);
1469 if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - (posAfter - posBefore)]) > 0) {
1470 // The char does not share horizontal space
1471 break;
1473 // Char shares horizontal space, update the numChars to draw
1474 numCharsToDraw = offsetLastChar - offsetFirstChar;
1477 // We now know what to draw, update the caret drawing rectangle
1478 rcCaret.left = ll->positions[offsetFirstChar] - ll->positions[lineStart] + xStart;
1479 rcCaret.right = ll->positions[offsetFirstChar + numCharsToDraw] - ll->positions[lineStart] + xStart;
1481 // Adjust caret position to take into account any word wrapping symbols.
1482 if ((ll->wrapIndent != 0) && (lineStart != 0)) {
1483 const XYPOSITION wordWrapCharWidth = ll->wrapIndent;
1484 rcCaret.left += wordWrapCharWidth;
1485 rcCaret.right += wordWrapCharWidth;
1488 // This character is where the caret block is, we override the colours
1489 // (inversed) for drawing the caret here.
1490 const int styleMain = ll->styles[offsetFirstChar];
1491 const Font *fontText = vsDraw.styles[styleMain].font.get();
1492 const std::string_view text(&ll->chars[offsetFirstChar], numCharsToDraw);
1493 surface->DrawTextClipped(rcCaret, fontText,
1494 rcCaret.top + vsDraw.maxAscent, text, vsDraw.styles[styleMain].back,
1495 caretColour);
1500 void EditView::DrawCarets(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1501 Sci::Line lineDoc, int xStart, PRectangle rcLine, int subLine) const {
1502 // When drag is active it is the only caret drawn
1503 const bool drawDrag = model.posDrag.IsValid();
1504 if (!vsDraw.selection.visible && !drawDrag)
1505 return;
1506 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
1507 // For each selection draw
1508 for (size_t r = 0; (r<model.sel.Count()) || drawDrag; r++) {
1509 const bool mainCaret = r == model.sel.Main();
1510 SelectionPosition posCaret = (drawDrag ? model.posDrag : model.sel.Range(r).caret);
1511 if ((vsDraw.DrawCaretInsideSelection(model.inOverstrike, imeCaretBlockOverride)) &&
1512 !drawDrag &&
1513 posCaret > model.sel.Range(r).anchor) {
1514 if (posCaret.VirtualSpace() > 0)
1515 posCaret.SetVirtualSpace(posCaret.VirtualSpace() - 1);
1516 else
1517 posCaret.SetPosition(model.pdoc->MovePositionOutsideChar(posCaret.Position()-1, -1));
1519 const int offset = static_cast<int>(posCaret.Position() - posLineStart);
1520 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1521 const XYPOSITION virtualOffset = posCaret.VirtualSpace() * spaceWidth;
1522 if (ll->InLine(offset, subLine) && offset <= ll->numCharsBeforeEOL) {
1523 XYPOSITION xposCaret = ll->positions[offset] + virtualOffset - ll->positions[ll->LineStart(subLine)];
1524 if (model.BidirectionalEnabled() && (posCaret.VirtualSpace() == 0)) {
1525 // Get caret point
1526 const ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right, tabWidthMinimumPixels);
1528 const int caretPosition = offset - ll->LineStart(subLine);
1530 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
1531 const XYPOSITION caretLeft = slLayout->XFromPosition(caretPosition);
1533 // In case of start of line, the cursor should be at the right
1534 xposCaret = caretLeft + virtualOffset;
1536 if (ll->wrapIndent != 0) {
1537 const Sci::Position lineStart = ll->LineStart(subLine);
1538 if (lineStart != 0) // Wrapped
1539 xposCaret += ll->wrapIndent;
1541 const bool caretBlinkState = (model.caret.active && model.caret.on) || (!additionalCaretsBlink && !mainCaret);
1542 const bool caretVisibleState = additionalCaretsVisible || mainCaret;
1543 if ((xposCaret >= 0) && vsDraw.IsCaretVisible(mainCaret) &&
1544 (drawDrag || (caretBlinkState && caretVisibleState))) {
1545 bool canDrawBlockCaret = true;
1546 bool drawBlockCaret = false;
1547 XYPOSITION widthOverstrikeCaret;
1548 XYPOSITION caretWidthOffset = 0;
1549 PRectangle rcCaret = rcLine;
1551 if (posCaret.Position() == model.pdoc->Length()) { // At end of document
1552 canDrawBlockCaret = false;
1553 widthOverstrikeCaret = vsDraw.aveCharWidth;
1554 } else if ((posCaret.Position() - posLineStart) >= ll->numCharsInLine) { // At end of line
1555 canDrawBlockCaret = false;
1556 widthOverstrikeCaret = vsDraw.aveCharWidth;
1557 } else {
1558 const int widthChar = model.pdoc->LenChar(posCaret.Position());
1559 widthOverstrikeCaret = ll->positions[offset + widthChar] - ll->positions[offset];
1561 if (widthOverstrikeCaret < 3) // Make sure its visible
1562 widthOverstrikeCaret = 3;
1564 if (xposCaret > 0)
1565 caretWidthOffset = 0.51f; // Move back so overlaps both character cells.
1566 xposCaret += xStart;
1567 const ViewStyle::CaretShape caretShape = drawDrag ? ViewStyle::CaretShape::line :
1568 vsDraw.CaretShapeForMode(model.inOverstrike, mainCaret);
1569 if (drawDrag) {
1570 /* Dragging text, use a line caret */
1571 rcCaret.left = std::round(xposCaret - caretWidthOffset);
1572 rcCaret.right = rcCaret.left + vsDraw.caret.width;
1573 } else if ((caretShape == ViewStyle::CaretShape::bar) && drawOverstrikeCaret) {
1574 /* Over-strike (insert mode), use a modified bar caret */
1575 rcCaret.top = rcCaret.bottom - 2;
1576 rcCaret.left = xposCaret + 1;
1577 rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1;
1578 } else if ((caretShape == ViewStyle::CaretShape::block) || imeCaretBlockOverride) {
1579 /* Block caret */
1580 rcCaret.left = xposCaret;
1581 if (canDrawBlockCaret && !(IsControl(ll->chars[offset]))) {
1582 drawBlockCaret = true;
1583 rcCaret.right = xposCaret + widthOverstrikeCaret;
1584 } else {
1585 rcCaret.right = xposCaret + vsDraw.aveCharWidth;
1587 } else {
1588 /* Line caret */
1589 rcCaret.left = std::round(xposCaret - caretWidthOffset);
1590 rcCaret.right = rcCaret.left + vsDraw.caret.width;
1592 const Element elementCaret = mainCaret ? Element::Caret : Element::CaretAdditional;
1593 const ColourRGBA caretColour = vsDraw.ElementColourForced(elementCaret);
1594 //assert(caretColour.IsOpaque());
1595 if (drawBlockCaret) {
1596 DrawBlockCaret(surface, model, vsDraw, ll, subLine, xStart, offset, posCaret.Position(), rcCaret, caretColour);
1597 } else {
1598 surface->FillRectangleAligned(rcCaret, Fill(caretColour));
1602 if (drawDrag)
1603 break;
1607 namespace {
1609 void DrawWrapIndentAndMarker(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll,
1610 int xStart, PRectangle rcLine, ColourOptional background, DrawWrapMarkerFn customDrawWrapMarker,
1611 bool caretActive) {
1612 // default background here..
1613 surface->FillRectangleAligned(rcLine, Fill(background.value_or(vsDraw.styles[StyleDefault].back)));
1615 if (vsDraw.IsLineFrameOpaque(caretActive, ll->containsCaret)) {
1616 // Draw left of frame under marker
1617 surface->FillRectangleAligned(Side(rcLine, Edge::left, vsDraw.GetFrameWidth()),
1618 vsDraw.ElementColourForced(Element::CaretLineBack).Opaque());
1621 if (FlagSet(vsDraw.wrap.visualFlags, WrapVisualFlag::Start)) {
1623 // draw continuation rect
1624 PRectangle rcPlace = rcLine;
1626 rcPlace.left = static_cast<XYPOSITION>(xStart);
1627 rcPlace.right = rcPlace.left + ll->wrapIndent;
1629 if (FlagSet(vsDraw.wrap.visualFlagsLocation, WrapVisualLocation::StartByText))
1630 rcPlace.left = rcPlace.right - vsDraw.aveCharWidth;
1631 else
1632 rcPlace.right = rcPlace.left + vsDraw.aveCharWidth;
1634 if (!customDrawWrapMarker) {
1635 DrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour());
1636 } else {
1637 customDrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour());
1642 // On the curses platform, the terminal is drawing its own caret, so if the caret is within
1643 // the main selection, do not draw the selection at that position.
1644 // Use iDoc from DrawBackground and DrawForeground here because TextSegment has been adjusted
1645 // such that, if the caret is inside the main selection, the beginning or end of that selection
1646 // is at the end of a text segment.
1647 // This function should only be called if iDoc is within the main selection.
1648 InSelection CharacterInCursesSelection(Sci::Position iDoc, const EditModel &model, const ViewStyle &vsDraw) noexcept {
1649 const SelectionPosition &posCaret = model.sel.RangeMain().caret;
1650 const bool caretAtStart = posCaret < model.sel.RangeMain().anchor && posCaret.Position() == iDoc;
1651 const bool caretAtEnd = posCaret > model.sel.RangeMain().anchor &&
1652 vsDraw.DrawCaretInsideSelection(false, false) &&
1653 model.pdoc->MovePositionOutsideChar(posCaret.Position() - 1, -1) == iDoc;
1654 return (caretAtStart || caretAtEnd) ? InSelection::inNone : InSelection::inMain;
1657 void DrawBackground(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1658 int xStart, PRectangle rcLine, int subLine, Range lineRange, Sci::Position posLineStart,
1659 ColourOptional background) {
1661 const bool selBackDrawn = vsDraw.SelectionBackgroundDrawn();
1662 bool inIndentation = subLine == 0; // Do not handle indentation except on first subline.
1663 const XYPOSITION subLineStart = ll->positions[lineRange.start];
1664 const XYPOSITION horizontalOffset = xStart - subLineStart;
1665 // Does not take margin into account but not significant
1666 const XYPOSITION xStartVisible = subLineStart - xStart;
1668 const BreakFinder::BreakFor breakFor = selBackDrawn ? BreakFinder::BreakFor::Selection : BreakFinder::BreakFor::Text;
1669 BreakFinder bfBack(ll, &model.sel, lineRange, posLineStart, xStartVisible, breakFor, model.pdoc, model.reprs.get(), &vsDraw);
1671 const bool drawWhitespaceBackground = vsDraw.WhitespaceBackgroundDrawn() && !background;
1673 // Background drawing loop
1674 while (bfBack.More()) {
1676 const TextSegment ts = bfBack.Next();
1677 const Sci::Position i = ts.end() - 1;
1678 const Sci::Position iDoc = i + posLineStart;
1680 const Interval horizontal = ll->Span(ts.start, ts.end()).Offset(horizontalOffset);
1681 // Only try to draw if really visible - enhances performance by not calling environment to
1682 // draw strings that are completely past the right side of the window.
1683 if (!horizontal.Empty() && rcLine.Intersects(horizontal)) {
1684 const PRectangle rcSegment = Intersection(rcLine, horizontal);
1686 InSelection inSelection = vsDraw.selection.visible ? model.sel.CharacterInSelection(iDoc) : InSelection::inNone;
1687 if (FlagSet(vsDraw.caret.style, CaretStyle::Curses) && (inSelection == InSelection::inMain))
1688 inSelection = CharacterInCursesSelection(iDoc, model, vsDraw);
1689 const bool inHotspot = model.hotspot.Valid() && model.hotspot.ContainsCharacter(iDoc);
1690 ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, inSelection,
1691 inHotspot, ll->styles[i], i);
1692 if (ts.representation) {
1693 if (ll->chars[i] == '\t') {
1694 // Tab display
1695 if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation)) {
1696 textBack = vsDraw.ElementColourForced(Element::WhiteSpaceBack).Opaque();
1698 } else {
1699 // Blob display
1700 inIndentation = false;
1702 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1703 } else {
1704 // Normal text display
1705 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1706 if (vsDraw.viewWhitespace != WhiteSpace::Invisible) {
1707 for (int cpos = 0; cpos <= i - ts.start; cpos++) {
1708 if (ll->chars[cpos + ts.start] == ' ') {
1709 if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation)) {
1710 const PRectangle rcSpace = Intersection(rcLine,
1711 ll->SpanByte(cpos + ts.start).Offset(horizontalOffset));
1712 surface->FillRectangleAligned(rcSpace,
1713 vsDraw.ElementColourForced(Element::WhiteSpaceBack).Opaque());
1715 } else {
1716 inIndentation = false;
1721 } else if (horizontal.left > rcLine.right) {
1722 break;
1727 void DrawEdgeLine(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll,
1728 int xStart, PRectangle rcLine, Range lineRange) {
1729 if (vsDraw.edgeState == EdgeVisualStyle::Line) {
1730 PRectangle rcSegment = rcLine;
1731 const int edgeX = static_cast<int>(vsDraw.theEdge.column * vsDraw.spaceWidth);
1732 rcSegment.left = static_cast<XYPOSITION>(edgeX + xStart);
1733 if ((ll->wrapIndent != 0) && (lineRange.start != 0))
1734 rcSegment.left -= ll->wrapIndent;
1735 rcSegment.right = rcSegment.left + 1;
1736 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.theEdge.colour));
1737 } else if (vsDraw.edgeState == EdgeVisualStyle::MultiLine) {
1738 for (size_t edge = 0; edge < vsDraw.theMultiEdge.size(); edge++) {
1739 if (vsDraw.theMultiEdge[edge].column >= 0) {
1740 PRectangle rcSegment = rcLine;
1741 const int edgeX = static_cast<int>(vsDraw.theMultiEdge[edge].column * vsDraw.spaceWidth);
1742 rcSegment.left = static_cast<XYPOSITION>(edgeX + xStart);
1743 if ((ll->wrapIndent != 0) && (lineRange.start != 0))
1744 rcSegment.left -= ll->wrapIndent;
1745 rcSegment.right = rcSegment.left + 1;
1746 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.theMultiEdge[edge].colour));
1752 // Draw underline mark as part of background if on base layer
1753 void DrawMarkUnderline(Surface *surface, const EditModel &model, const ViewStyle &vsDraw,
1754 Sci::Line line, PRectangle rcLine) {
1755 int marks = model.GetMark(line);
1756 for (int markBit = 0; (markBit <= MarkerMax) && marks; markBit++) {
1757 if ((marks & 1) && (vsDraw.markers[markBit].markType == MarkerSymbol::Underline) &&
1758 (vsDraw.markers[markBit].layer == Layer::Base)) {
1759 PRectangle rcUnderline = rcLine;
1760 rcUnderline.top = rcUnderline.bottom - 2;
1761 surface->FillRectangleAligned(rcUnderline, Fill(vsDraw.markers[markBit].back));
1763 marks >>= 1;
1767 void DrawTranslucentSelection(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1768 Sci::Line line, int xStart, PRectangle rcLine, int subLine, Range lineRange, int tabWidthMinimumPixels, Layer layer) {
1769 if (vsDraw.selection.layer == layer) {
1770 const Sci::Position posLineStart = model.pdoc->LineStart(line);
1771 const XYPOSITION subLineStart = ll->positions[lineRange.start];
1772 const XYPOSITION horizontalOffset = xStart - subLineStart;
1773 // For each selection draw
1774 Sci::Position virtualSpaces = 0;
1775 if (subLine == (ll->lines - 1)) {
1776 virtualSpaces = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line));
1778 const SelectionPosition posStart(posLineStart + lineRange.start);
1779 const SelectionPosition posEnd(posLineStart + lineRange.end, virtualSpaces);
1780 const SelectionSegment virtualSpaceRange(posStart, posEnd);
1781 for (size_t r = 0; r < model.sel.Count(); r++) {
1782 const SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange);
1783 if (!portion.Empty()) {
1784 const SelectionSegment portionInLine = portion.Subtract(posLineStart);
1785 const ColourRGBA selectionBack = SelectionBackground(
1786 model, vsDraw, model.sel.RangeType(r));
1787 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1788 const Interval intervalVirtual{ portion.start.VirtualSpace() * spaceWidth, portion.end.VirtualSpace() * spaceWidth };
1789 if (model.BidirectionalEnabled()) {
1790 const SelectionSegment portionInSubLine = portionInLine.Subtract(lineRange.start);
1792 const ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right, tabWidthMinimumPixels);
1793 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
1795 if (slLayout) {
1796 const std::vector<Interval> intervals = slLayout->FindRangeIntervals(
1797 portionInSubLine.start.Position(), portionInSubLine.end.Position());
1798 for (const Interval &interval : intervals) {
1799 const PRectangle rcSelection = rcLine.WithHorizontalBounds(interval.Offset(xStart));
1800 surface->FillRectangleAligned(rcSelection, selectionBack);
1804 if (portion.end.VirtualSpace()) {
1805 const XYPOSITION xStartVirtual = ll->positions[lineRange.end] + horizontalOffset;
1806 const PRectangle rcSegment = rcLine.WithHorizontalBounds(intervalVirtual.Offset(xStartVirtual));
1807 surface->FillRectangleAligned(rcSegment, selectionBack);
1809 } else {
1810 Interval intervalSegment = ll->Span(
1811 static_cast<int>(portionInLine.start.Position()),
1812 static_cast<int>(portionInLine.end.Position()))
1813 .Offset(horizontalOffset);
1814 intervalSegment.left += intervalVirtual.left;
1815 intervalSegment.right += intervalVirtual.right;
1816 if ((ll->wrapIndent != 0) && (lineRange.start != 0)) {
1817 if ((portionInLine.start.Position() == lineRange.start) &&
1818 model.sel.Range(r).ContainsCharacter(portion.start.Position() - 1))
1819 intervalSegment.left -= static_cast<int>(ll->wrapIndent); // indentation added to xStart was truncated to int, so we do the same here
1821 const PRectangle rcSegment = Intersection(rcLine, intervalSegment);
1822 if (rcSegment.right > rcLine.left)
1823 surface->FillRectangleAligned(rcSegment, selectionBack);
1830 void DrawCaretLineFramed(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll,
1831 PRectangle rcLine, int subLine) {
1832 const ColourOptional caretlineBack = vsDraw.ElementColour(Element::CaretLineBack);
1833 if (!caretlineBack) {
1834 return;
1837 const ColourRGBA colourFrame = (vsDraw.caretLine.layer == Layer::Base) ?
1838 caretlineBack->Opaque() : *caretlineBack;
1840 const int width = vsDraw.GetFrameWidth();
1842 // Avoid double drawing the corners by removing the left and right sides when drawing top and bottom borders
1843 const PRectangle rcWithoutLeftRight = rcLine.Inset(Point(width, 0.0));
1845 if (subLine == 0 || ll->wrapIndent == 0 || vsDraw.caretLine.layer != Layer::Base || vsDraw.caretLine.subLine) {
1846 // Left
1847 surface->FillRectangleAligned(Side(rcLine, Edge::left, width), colourFrame);
1849 if (subLine == 0 || vsDraw.caretLine.subLine) {
1850 // Top
1851 surface->FillRectangleAligned(Side(rcWithoutLeftRight, Edge::top, width), colourFrame);
1853 if (subLine == ll->lines - 1 || vsDraw.caretLine.layer != Layer::Base || vsDraw.caretLine.subLine) {
1854 // Right
1855 surface->FillRectangleAligned(Side(rcLine, Edge::right, width), colourFrame);
1857 if (subLine == ll->lines - 1 || vsDraw.caretLine.subLine) {
1858 // Bottom
1859 surface->FillRectangleAligned(Side(rcWithoutLeftRight, Edge::bottom, width), colourFrame);
1863 // Draw any translucent whole line states
1864 void DrawTranslucentLineState(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1865 Sci::Line line, PRectangle rcLine, int subLine, Layer layer) {
1866 if ((model.caret.active || vsDraw.caretLine.alwaysShow) && vsDraw.ElementColour(Element::CaretLineBack) && ll->containsCaret &&
1867 vsDraw.caretLine.layer == layer) {
1868 if (vsDraw.caretLine.frame) {
1869 DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine);
1870 } else {
1871 surface->FillRectangleAligned(rcLine, vsDraw.ElementColourForced(Element::CaretLineBack));
1874 const int marksOfLine = model.GetMark(line);
1875 int marksDrawnInText = marksOfLine & vsDraw.maskDrawInText;
1876 for (int markBit = 0; (markBit <= MarkerMax) && marksDrawnInText; markBit++) {
1877 if ((marksDrawnInText & 1) && (vsDraw.markers[markBit].layer == layer)) {
1878 if (vsDraw.markers[markBit].markType == MarkerSymbol::Background) {
1879 surface->FillRectangleAligned(rcLine, vsDraw.markers[markBit].BackWithAlpha());
1880 } else if (vsDraw.markers[markBit].markType == MarkerSymbol::Underline) {
1881 PRectangle rcUnderline = rcLine;
1882 rcUnderline.top = rcUnderline.bottom - 2;
1883 surface->FillRectangleAligned(rcUnderline, vsDraw.markers[markBit].BackWithAlpha());
1886 marksDrawnInText >>= 1;
1888 int marksDrawnInLine = marksOfLine & vsDraw.maskInLine;
1889 for (int markBit = 0; (markBit <= MarkerMax) && marksDrawnInLine; markBit++) {
1890 if ((marksDrawnInLine & 1) && (vsDraw.markers[markBit].layer == layer)) {
1891 surface->FillRectangleAligned(rcLine, vsDraw.markers[markBit].BackWithAlpha());
1893 marksDrawnInLine >>= 1;
1897 void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid,
1898 const ViewStyle &vsDraw, Stroke stroke) {
1900 const XYPOSITION halfWidth = stroke.width / 2.0;
1902 const XYPOSITION leftStroke = std::round(std::min(rcTab.left + 2, rcTab.right - 1)) + halfWidth;
1903 const XYPOSITION rightStroke = std::max(leftStroke, std::round(rcTab.right) - 1.0f - halfWidth);
1904 const XYPOSITION yMidAligned = ymid + halfWidth;
1905 const Point arrowPoint(rightStroke, yMidAligned);
1906 if (rightStroke > leftStroke) {
1907 // When not enough room, don't draw the arrow shaft
1908 surface->LineDraw(Point(leftStroke, yMidAligned), arrowPoint, stroke);
1911 // Draw the arrow head if needed
1912 if (vsDraw.tabDrawMode == TabDrawMode::LongArrow) {
1913 XYPOSITION ydiff = std::floor(rcTab.Height() / 2.0f);
1914 XYPOSITION xhead = rightStroke - ydiff;
1915 if (xhead <= rcTab.left) {
1916 ydiff -= rcTab.left - xhead;
1917 xhead = rcTab.left;
1919 const Point ptsHead[] = {
1920 Point(xhead, yMidAligned - ydiff),
1921 arrowPoint,
1922 Point(xhead, yMidAligned + ydiff)
1924 surface->PolyLine(ptsHead, std::size(ptsHead), stroke);
1928 void DrawIndicator(int indicNum, Sci::Position startPos, Sci::Position endPos, Surface *surface, const ViewStyle &vsDraw,
1929 const LineLayout *ll, int xStart, PRectangle rcLine, Sci::Position secondCharacter, int subLine, Indicator::State state,
1930 int value, bool bidiEnabled, int tabWidthMinimumPixels) {
1932 const XYPOSITION subLineStart = ll->positions[ll->LineStart(subLine)];
1933 const XYPOSITION horizontalOffset = xStart - subLineStart;
1935 std::vector<PRectangle> rectangles;
1937 const XYPOSITION left = ll->XInLine(startPos) + horizontalOffset;
1938 const XYPOSITION right = ll->XInLine(endPos) + horizontalOffset;
1939 const PRectangle rcIndic(left, rcLine.top + vsDraw.maxAscent, right,
1940 std::max(rcLine.top + vsDraw.maxAscent + 3, rcLine.bottom));
1942 if (bidiEnabled) {
1943 ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right - xStart, tabWidthMinimumPixels);
1944 const Range lineRange = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
1946 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
1947 std::vector<Interval> intervals = slLayout->FindRangeIntervals(
1948 startPos - lineRange.start, endPos - lineRange.start);
1949 for (const Interval &interval : intervals) {
1950 PRectangle rcInterval = rcIndic;
1951 rcInterval.left = interval.left + xStart;
1952 rcInterval.right = interval.right + xStart;
1953 rectangles.push_back(rcInterval);
1955 } else {
1956 rectangles.push_back(rcIndic);
1959 for (const PRectangle &rc : rectangles) {
1960 PRectangle rcFirstCharacter = rc;
1961 // Allow full descent space for character indicators
1962 rcFirstCharacter.bottom = rcLine.top + vsDraw.maxAscent + vsDraw.maxDescent;
1963 if (secondCharacter >= 0) {
1964 rcFirstCharacter.right = ll->XInLine(secondCharacter) + horizontalOffset;
1965 } else {
1966 // Indicator continued from earlier line so make an empty box and don't draw
1967 rcFirstCharacter.right = rcFirstCharacter.left;
1969 vsDraw.indicators[indicNum].Draw(surface, rc, rcLine, rcFirstCharacter, state, value);
1973 void DrawIndicators(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1974 Sci::Line line, int xStart, PRectangle rcLine, int subLine, Sci::Position lineEnd, bool under, int tabWidthMinimumPixels) {
1975 // Draw decorators
1976 const Sci::Position posLineStart = model.pdoc->LineStart(line);
1977 const Sci::Position lineStart = ll->LineStart(subLine);
1978 const Sci::Position posLineEnd = posLineStart + lineEnd;
1980 for (const IDecoration *deco : model.pdoc->decorations->View()) {
1981 if (under == vsDraw.indicators[deco->Indicator()].under) {
1982 Sci::Position startPos = posLineStart + lineStart;
1983 while (startPos < posLineEnd) {
1984 const Range rangeRun(deco->StartRun(startPos), deco->EndRun(startPos));
1985 const Sci::Position endPos = std::min(rangeRun.end, posLineEnd);
1986 const int value = deco->ValueAt(startPos);
1987 if (value) {
1988 const bool hover = vsDraw.indicators[deco->Indicator()].IsDynamic() &&
1989 rangeRun.ContainsCharacter(model.hoverIndicatorPos);
1990 const Indicator::State state = hover ? Indicator::State::hover : Indicator::State::normal;
1991 const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(rangeRun.First() + 1, 1);
1992 DrawIndicator(deco->Indicator(), startPos - posLineStart, endPos - posLineStart,
1993 surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, state,
1994 value, model.BidirectionalEnabled(), tabWidthMinimumPixels);
1996 startPos = endPos;
2001 // Use indicators to highlight matching braces
2002 if ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == StyleBraceLight)) ||
2003 (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == StyleBraceBad))) {
2004 const int braceIndicator = (model.bracesMatchStyle == StyleBraceLight) ? vsDraw.braceHighlightIndicator : vsDraw.braceBadLightIndicator;
2005 if (under == vsDraw.indicators[braceIndicator].under) {
2006 const Range rangeLine(posLineStart + lineStart, posLineEnd);
2007 for (size_t brace = 0; brace <= 1; brace++) {
2008 if (rangeLine.ContainsCharacter(model.braces[brace])) {
2009 const Sci::Position braceOffset = model.braces[brace] - posLineStart;
2010 if (braceOffset < ll->numCharsInLine) {
2011 const Sci::Position braceEnd = model.pdoc->MovePositionOutsideChar(model.braces[brace] + 1, 1) - posLineStart;
2012 DrawIndicator(braceIndicator, braceOffset, braceEnd,
2013 surface, vsDraw, ll, xStart, rcLine, braceEnd, subLine, Indicator::State::normal,
2014 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
2021 if (FlagSet(model.changeHistoryOption, ChangeHistoryOption::Indicators)) {
2022 // Draw editions
2023 constexpr int indexHistory = static_cast<int>(IndicatorNumbers::HistoryRevertedToOriginInsertion);
2025 // Draw insertions
2026 Sci::Position startPos = posLineStart + lineStart;
2027 while (startPos < posLineEnd) {
2028 const Range rangeRun(startPos, model.pdoc->EditionEndRun(startPos));
2029 const Sci::Position endPos = std::min(rangeRun.end, posLineEnd);
2030 const int edition = model.pdoc->EditionAt(startPos);
2031 if (edition != 0) {
2032 const int indicator = (edition - 1) * 2 + indexHistory;
2033 const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(rangeRun.First() + 1, 1);
2034 DrawIndicator(indicator, startPos - posLineStart, endPos - posLineStart,
2035 surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, Indicator::State::normal,
2036 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
2038 startPos = endPos;
2042 // Draw deletions
2043 Sci::Position startPos = posLineStart + lineStart;
2044 while (startPos <= posLineEnd) {
2045 const unsigned int editions = model.pdoc->EditionDeletesAt(startPos);
2046 const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(startPos + 1, 1);
2047 for (unsigned int edition = 0; edition < 4; edition++) {
2048 if (editions & (1 << edition)) {
2049 const int indicator = edition * 2 + indexHistory + 1;
2050 DrawIndicator(indicator, startPos - posLineStart, posSecond - posLineStart,
2051 surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, Indicator::State::normal,
2052 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
2055 startPos = model.pdoc->EditionNextDelete(startPos);
2061 void DrawFoldLines(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2062 Sci::Line line, PRectangle rcLine, int subLine) {
2063 const bool lastSubLine = subLine == (ll->lines - 1);
2064 const bool expanded = model.pcs->GetExpanded(line);
2065 const FoldLevel level = model.pdoc->GetFoldLevel(line);
2066 const FoldLevel levelNext = model.pdoc->GetFoldLevel(line + 1);
2067 if (LevelIsHeader(level) &&
2068 (LevelNumber(level) < LevelNumber(levelNext))) {
2069 const ColourRGBA foldLineColour = vsDraw.ElementColour(Element::FoldLine).value_or(
2070 vsDraw.styles[StyleDefault].fore);
2071 // Paint the line above the fold
2072 if ((subLine == 0) && FlagSet(model.foldFlags, (expanded ? FoldFlag::LineBeforeExpanded: FoldFlag::LineBeforeContracted))) {
2073 surface->FillRectangleAligned(Side(rcLine, Edge::top, 1.0), foldLineColour);
2075 // Paint the line below the fold
2076 if (lastSubLine && FlagSet(model.foldFlags, (expanded ? FoldFlag::LineAfterExpanded : FoldFlag::LineAfterContracted))) {
2077 surface->FillRectangleAligned(Side(rcLine, Edge::bottom, 1.0), foldLineColour);
2078 // If contracted fold line drawn then don't overwrite with hidden line
2079 // as fold lines are more specific then hidden lines.
2080 if (!expanded) {
2081 return;
2085 if (lastSubLine && model.pcs->GetVisible(line) && !model.pcs->GetVisible(line + 1)) {
2086 if (const ColourOptional hiddenLineColour = vsDraw.ElementColour(Element::HiddenLine)) {
2087 surface->FillRectangleAligned(Side(rcLine, Edge::bottom, 1.0), *hiddenLineColour);
2092 ColourRGBA InvertedLight(ColourRGBA orig) noexcept {
2093 unsigned int r = orig.GetRed();
2094 unsigned int g = orig.GetGreen();
2095 unsigned int b = orig.GetBlue();
2096 const unsigned int l = (r + g + b) / 3; // There is a better calculation for this that matches human eye
2097 const unsigned int il = 0xff - l;
2098 if (l == 0)
2099 return white;
2100 r = r * il / l;
2101 g = g * il / l;
2102 b = b * il / l;
2103 return ColourRGBA(std::min(r, 0xffu), std::min(g, 0xffu), std::min(b, 0xffu));
2108 void EditView::DrawIndentGuide(Surface *surface, XYPOSITION start, PRectangle rcSegment, bool highlight, bool offset) {
2109 const Point from = Point::FromInts(0, offset ? 1 : 0);
2110 const PRectangle rcCopyArea(start + 1, rcSegment.top,
2111 start + 2, rcSegment.bottom);
2112 surface->Copy(rcCopyArea, from,
2113 highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide);
2116 void EditView::DrawForeground(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2117 int xStart, PRectangle rcLine, int subLine, Sci::Line lineVisible, Range lineRange, Sci::Position posLineStart,
2118 ColourOptional background) {
2120 const bool selBackDrawn = vsDraw.SelectionBackgroundDrawn();
2121 const bool drawWhitespaceBackground = vsDraw.WhitespaceBackgroundDrawn() && !background;
2122 bool inIndentation = subLine == 0; // Do not handle indentation except on first subline.
2124 const XYPOSITION subLineStart = ll->positions[lineRange.start];
2125 const XYPOSITION horizontalOffset = xStart - subLineStart;
2126 const XYPOSITION indentWidth = model.pdoc->IndentSize() * vsDraw.spaceWidth;
2128 // Does not take margin into account but not significant
2129 const XYPOSITION xStartVisible = subLineStart - xStart;
2131 // When lineHeight is odd, dotted indent guides are drawn offset by 1 on odd lines to join together.
2132 const bool offsetGuide = (lineVisible & 1) && (vsDraw.lineHeight & 1);
2134 // Same baseline used for all text
2135 const XYPOSITION ybase = rcLine.top + vsDraw.maxAscent;
2137 // Foreground drawing loop
2138 const BreakFinder::BreakFor breakFor = (((phasesDraw == PhasesDraw::One) && selBackDrawn) || vsDraw.SelectionTextDrawn())
2139 ? BreakFinder::BreakFor::ForegroundAndSelection : BreakFinder::BreakFor::Foreground;
2140 BreakFinder bfFore(ll, &model.sel, lineRange, posLineStart, xStartVisible, breakFor, model.pdoc, model.reprs.get(), &vsDraw);
2142 while (bfFore.More()) {
2144 const TextSegment ts = bfFore.Next();
2145 const Sci::Position i = ts.end() - 1;
2146 const Sci::Position iDoc = i + posLineStart;
2148 const Interval horizontal = ll->Span(ts.start, ts.end()).Offset(horizontalOffset);
2149 // Only try to draw if really visible - enhances performance by not calling environment to
2150 // draw strings that are completely past the right side of the window.
2151 if (rcLine.Intersects(horizontal)) {
2152 const PRectangle rcSegment = rcLine.WithHorizontalBounds(horizontal);
2153 const int styleMain = ll->styles[i];
2154 ColourRGBA textFore = vsDraw.styles[styleMain].fore;
2155 const Font *textFont = vsDraw.styles[styleMain].font.get();
2156 // Hot-spot foreground
2157 const bool inHotspot = model.hotspot.Valid() && model.hotspot.ContainsCharacter(iDoc);
2158 if (inHotspot) {
2159 if (const ColourOptional colourHotSpot = vsDraw.ElementColour(Element::HotSpotActive)) {
2160 textFore = *colourHotSpot;
2163 if (vsDraw.indicatorsSetFore) {
2164 // At least one indicator sets the text colour so see if it applies to this segment
2165 for (const IDecoration *deco : model.pdoc->decorations->View()) {
2166 const int indicatorValue = deco->ValueAt(ts.start + posLineStart);
2167 if (indicatorValue) {
2168 const Indicator &indicator = vsDraw.indicators[deco->Indicator()];
2169 bool hover = false;
2170 if (indicator.IsDynamic()) {
2171 const Sci::Position startPos = ts.start + posLineStart;
2172 const Range rangeRun(deco->StartRun(startPos), deco->EndRun(startPos));
2173 hover = rangeRun.ContainsCharacter(model.hoverIndicatorPos);
2175 if (hover) {
2176 if (indicator.sacHover.style == IndicatorStyle::TextFore) {
2177 textFore = indicator.sacHover.fore;
2179 } else {
2180 if (indicator.sacNormal.style == IndicatorStyle::TextFore) {
2181 if (FlagSet(indicator.Flags(), IndicFlag::ValueFore))
2182 textFore = ColourRGBA::FromRGB(indicatorValue & static_cast<int>(IndicValue::Mask));
2183 else
2184 textFore = indicator.sacNormal.fore;
2190 InSelection inSelection = vsDraw.selection.visible ? model.sel.CharacterInSelection(iDoc) : InSelection::inNone;
2191 if (FlagSet(vsDraw.caret.style, CaretStyle::Curses) && (inSelection == InSelection::inMain))
2192 inSelection = CharacterInCursesSelection(iDoc, model, vsDraw);
2193 if (const ColourOptional selectionFore = SelectionForeground(model, vsDraw, inSelection)) {
2194 textFore = *selectionFore;
2196 ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, inSelection, inHotspot, styleMain, i);
2197 if (ts.representation) {
2198 if (ll->chars[i] == '\t') {
2199 // Tab display
2200 if (phasesDraw == PhasesDraw::One) {
2201 if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation))
2202 textBack = vsDraw.ElementColourForced(Element::WhiteSpaceBack).Opaque();
2203 surface->FillRectangleAligned(rcSegment, Fill(textBack));
2205 if (inIndentation && vsDraw.viewIndentationGuides == IndentView::Real) {
2206 const Interval intervalCharacter = ll->SpanByte(static_cast<int>(i));
2207 for (int indentCount = static_cast<int>((intervalCharacter.left + epsilon) / indentWidth);
2208 indentCount <= (intervalCharacter.right - epsilon) / indentWidth;
2209 indentCount++) {
2210 if (indentCount > 0) {
2211 const XYPOSITION xIndent = std::floor(indentCount * indentWidth);
2212 DrawIndentGuide(surface, xIndent + xStart, rcSegment, ll->xHighlightGuide == xIndent, offsetGuide);
2216 if (vsDraw.viewWhitespace != WhiteSpace::Invisible) {
2217 if (vsDraw.WhiteSpaceVisible(inIndentation)) {
2218 const PRectangle rcTab(rcSegment.left + 1, rcSegment.top + tabArrowHeight,
2219 rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent);
2220 const int segmentTop = static_cast<int>(rcSegment.top) + vsDraw.lineHeight / 2;
2221 const ColourRGBA whiteSpaceFore = vsDraw.ElementColour(Element::WhiteSpace).value_or(textFore);
2222 if (!customDrawTabArrow)
2223 DrawTabArrow(surface, rcTab, segmentTop, vsDraw, Stroke(whiteSpaceFore, 1.0f));
2224 else
2225 customDrawTabArrow(surface, rcTab, segmentTop, vsDraw, Stroke(whiteSpaceFore, 1.0f));
2228 } else {
2229 inIndentation = false;
2230 if (vsDraw.controlCharSymbol >= 32) {
2231 // Using one font for all control characters so it can be controlled independently to ensure
2232 // the box goes around the characters tightly. Seems to be no way to work out what height
2233 // is taken by an individual character - internal leading gives varying results.
2234 const Font *ctrlCharsFont = vsDraw.styles[StyleControlChar].font.get();
2235 const char cc[2] = { static_cast<char>(vsDraw.controlCharSymbol), '\0' };
2236 surface->DrawTextNoClip(rcSegment, ctrlCharsFont,
2237 ybase, cc, textBack, textFore);
2238 } else {
2239 if (FlagSet(ts.representation->appearance, RepresentationAppearance::Colour)) {
2240 textFore = ts.representation->colour;
2242 if (FlagSet(ts.representation->appearance, RepresentationAppearance::Blob)) {
2243 DrawTextBlob(surface, vsDraw, rcSegment, ts.representation->stringRep,
2244 textBack, textFore, phasesDraw == PhasesDraw::One);
2245 } else {
2246 surface->DrawTextTransparentUTF8(rcSegment, vsDraw.styles[StyleControlChar].font.get(),
2247 ybase, ts.representation->stringRep, textFore);
2251 } else {
2252 // Normal text display
2253 if (vsDraw.styles[styleMain].visible) {
2254 const std::string_view text(&ll->chars[ts.start], i - ts.start + 1);
2255 if (phasesDraw != PhasesDraw::One) {
2256 surface->DrawTextTransparent(rcSegment, textFont,
2257 ybase, text, textFore);
2258 } else {
2259 surface->DrawTextNoClip(rcSegment, textFont,
2260 ybase, text, textFore, textBack);
2262 } else if (vsDraw.styles[styleMain].invisibleRepresentation[0]) {
2263 const std::string_view text = vsDraw.styles[styleMain].invisibleRepresentation;
2264 if (phasesDraw != PhasesDraw::One) {
2265 surface->DrawTextTransparentUTF8(rcSegment, textFont,
2266 ybase, text, textFore);
2267 } else {
2268 surface->DrawTextNoClipUTF8(rcSegment, textFont,
2269 ybase, text, textFore, textBack);
2272 if (vsDraw.viewWhitespace != WhiteSpace::Invisible ||
2273 (inIndentation && vsDraw.viewIndentationGuides != IndentView::None)) {
2274 for (int cpos = 0; cpos <= i - ts.start; cpos++) {
2275 if (ll->chars[cpos + ts.start] == ' ') {
2276 if (vsDraw.viewWhitespace != WhiteSpace::Invisible) {
2277 if (vsDraw.WhiteSpaceVisible(inIndentation)) {
2278 const Interval intervalSpace = ll->SpanByte(cpos + ts.start).Offset(horizontalOffset);
2279 const XYPOSITION xmid = (intervalSpace.left + intervalSpace.right) / 2;
2280 if ((phasesDraw == PhasesDraw::One) && drawWhitespaceBackground) {
2281 textBack = vsDraw.ElementColourForced(Element::WhiteSpaceBack).Opaque();
2282 const PRectangle rcSpace = rcLine.WithHorizontalBounds(intervalSpace);
2283 surface->FillRectangleAligned(rcSpace, Fill(textBack));
2285 const int halfDotWidth = vsDraw.whitespaceSize / 2;
2286 PRectangle rcDot(xmid - halfDotWidth,
2287 rcSegment.top + vsDraw.lineHeight / 2, 0.0f, 0.0f);
2288 rcDot.right = rcDot.left + vsDraw.whitespaceSize;
2289 rcDot.bottom = rcDot.top + vsDraw.whitespaceSize;
2290 const ColourRGBA whiteSpaceFore = vsDraw.ElementColour(Element::WhiteSpace).value_or(textFore);
2291 surface->FillRectangleAligned(rcDot, Fill(whiteSpaceFore));
2294 if (inIndentation && vsDraw.viewIndentationGuides == IndentView::Real) {
2295 const Interval intervalCharacter = ll->SpanByte(cpos + ts.start);
2296 for (int indentCount = static_cast<int>((intervalCharacter.left + epsilon) / indentWidth);
2297 indentCount <= (intervalCharacter.right - epsilon) / indentWidth;
2298 indentCount++) {
2299 if (indentCount > 0) {
2300 const XYPOSITION xIndent = std::floor(indentCount * indentWidth);
2301 DrawIndentGuide(surface, xIndent + xStart, rcSegment, ll->xHighlightGuide == xIndent, offsetGuide);
2305 } else {
2306 inIndentation = false;
2311 if ((inHotspot && vsDraw.hotspotUnderline) || vsDraw.styles[styleMain].underline) {
2312 PRectangle rcUL = rcSegment;
2313 rcUL.top = ybase + 1;
2314 rcUL.bottom = ybase + 2;
2315 ColourRGBA colourUnderline = textFore;
2316 if (inHotspot && vsDraw.hotspotUnderline) {
2317 colourUnderline = vsDraw.ElementColour(Element::HotSpotActive).value_or(textFore);
2319 surface->FillRectangleAligned(rcUL, colourUnderline);
2321 } else if (horizontal.left > rcLine.right) {
2322 break;
2327 void EditView::DrawIndentGuidesOverEmpty(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2328 Sci::Line line, int xStart, PRectangle rcLine, int subLine, Sci::Line lineVisible) {
2329 if ((vsDraw.viewIndentationGuides == IndentView::LookForward || vsDraw.viewIndentationGuides == IndentView::LookBoth)
2330 && (subLine == 0)) {
2331 const Sci::Position posLineStart = model.pdoc->LineStart(line);
2332 int indentSpace = model.pdoc->GetLineIndentation(line);
2333 int xStartText = static_cast<int>(ll->positions[model.pdoc->GetLineIndentPosition(line) - posLineStart]);
2335 // Find the most recent line with some text
2337 Sci::Line lineLastWithText = line;
2338 while (lineLastWithText > std::max(line - 20, static_cast<Sci::Line>(0)) && model.pdoc->IsWhiteLine(lineLastWithText)) {
2339 lineLastWithText--;
2341 if (lineLastWithText < line) {
2342 xStartText = 100000; // Don't limit to visible indentation on empty line
2343 // This line is empty, so use indentation of last line with text
2344 int indentLastWithText = model.pdoc->GetLineIndentation(lineLastWithText);
2345 const int isFoldHeader = LevelIsHeader(model.pdoc->GetFoldLevel(lineLastWithText));
2346 if (isFoldHeader) {
2347 // Level is one more level than parent
2348 indentLastWithText += model.pdoc->IndentSize();
2350 if (vsDraw.viewIndentationGuides == IndentView::LookForward) {
2351 // In viLookForward mode, previous line only used if it is a fold header
2352 if (isFoldHeader) {
2353 indentSpace = std::max(indentSpace, indentLastWithText);
2355 } else { // viLookBoth
2356 indentSpace = std::max(indentSpace, indentLastWithText);
2360 Sci::Line lineNextWithText = line;
2361 while (lineNextWithText < std::min(line + 20, model.pdoc->LinesTotal()) && model.pdoc->IsWhiteLine(lineNextWithText)) {
2362 lineNextWithText++;
2364 if (lineNextWithText > line) {
2365 xStartText = 100000; // Don't limit to visible indentation on empty line
2366 // This line is empty, so use indentation of first next line with text
2367 indentSpace = std::max(indentSpace,
2368 model.pdoc->GetLineIndentation(lineNextWithText));
2371 const bool offsetGuide = (lineVisible & 1) && (vsDraw.lineHeight & 1);
2372 for (int indentPos = model.pdoc->IndentSize(); indentPos < indentSpace; indentPos += model.pdoc->IndentSize()) {
2373 const XYPOSITION xIndent = std::floor(indentPos * vsDraw.spaceWidth);
2374 if (xIndent < xStartText) {
2375 DrawIndentGuide(surface, xIndent + xStart, rcLine, ll->xHighlightGuide == xIndent, offsetGuide);
2381 void EditView::DrawLine(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2382 Sci::Line line, Sci::Line lineVisible, int xStart, PRectangle rcLine, int subLine, DrawPhase phase) {
2384 if (subLine >= ll->lines) {
2385 DrawAnnotation(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, phase);
2386 return; // No further drawing
2389 const bool clipLine = !bufferedDraw && !LinesOverlap();
2390 if (clipLine) {
2391 surface->SetClip(rcLine);
2394 // See if something overrides the line background colour.
2395 const ColourOptional background = vsDraw.Background(model.GetMark(line), model.caret.active, ll->containsCaret);
2397 const Sci::Position posLineStart = model.pdoc->LineStart(line);
2399 const Range lineRange = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
2400 const Range lineRangeIncludingEnd = ll->SubLineRange(subLine, LineLayout::Scope::includeEnd);
2401 const XYPOSITION subLineStart = ll->positions[lineRange.start];
2403 if ((ll->wrapIndent != 0) && (subLine > 0)) {
2404 if (FlagSet(phase, DrawPhase::back)) {
2405 DrawWrapIndentAndMarker(surface, vsDraw, ll, xStart, rcLine, background, customDrawWrapMarker, model.caret.active);
2407 xStart += static_cast<int>(ll->wrapIndent);
2410 if (phasesDraw != PhasesDraw::One) {
2411 if (FlagSet(phase, DrawPhase::back)) {
2412 DrawBackground(surface, model, vsDraw, ll,
2413 xStart, rcLine, subLine, lineRange, posLineStart,
2414 background);
2415 DrawFoldDisplayText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, DrawPhase::back);
2416 DrawEOLAnnotationText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, DrawPhase::back);
2417 // Remove drawBack to not draw again in DrawFoldDisplayText
2418 phase = static_cast<DrawPhase>(static_cast<int>(phase) & ~static_cast<int>(DrawPhase::back));
2419 DrawEOL(surface, model, vsDraw, ll,
2420 line, xStart, rcLine, subLine, lineRange.end, subLineStart, background);
2421 if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret))
2422 DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine);
2425 if (FlagSet(phase, DrawPhase::indicatorsBack)) {
2426 DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine,
2427 lineRangeIncludingEnd.end, true, tabWidthMinimumPixels);
2428 DrawEdgeLine(surface, vsDraw, ll, xStart, rcLine, lineRange);
2429 DrawMarkUnderline(surface, model, vsDraw, line, rcLine);
2433 if (FlagSet(phase, DrawPhase::text)) {
2434 if (vsDraw.selection.visible) {
2435 DrawTranslucentSelection(surface, model, vsDraw, ll,
2436 line, xStart, rcLine, subLine, lineRange, tabWidthMinimumPixels, Layer::UnderText);
2438 DrawTranslucentLineState(surface, model, vsDraw, ll, line, rcLine, subLine, Layer::UnderText);
2439 DrawForeground(surface, model, vsDraw, ll,
2440 xStart, rcLine, subLine, lineVisible, lineRange, posLineStart,
2441 background);
2444 if (FlagSet(phase, DrawPhase::indentationGuides)) {
2445 DrawIndentGuidesOverEmpty(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, lineVisible);
2448 if (FlagSet(phase, DrawPhase::indicatorsFore)) {
2449 DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine,
2450 lineRangeIncludingEnd.end, false, tabWidthMinimumPixels);
2453 DrawFoldDisplayText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, phase);
2454 DrawEOLAnnotationText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, phase);
2456 if (phasesDraw == PhasesDraw::One) {
2457 DrawEOL(surface, model, vsDraw, ll,
2458 line, xStart, rcLine, subLine, lineRange.end, subLineStart, background);
2459 if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret))
2460 DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine);
2461 DrawEdgeLine(surface, vsDraw, ll, xStart, rcLine, lineRange);
2462 DrawMarkUnderline(surface, model, vsDraw, line, rcLine);
2465 if (vsDraw.selection.visible && FlagSet(phase, DrawPhase::selectionTranslucent)) {
2466 DrawTranslucentSelection(surface, model, vsDraw, ll,
2467 line, xStart, rcLine, subLine, lineRange, tabWidthMinimumPixels, Layer::OverText);
2470 if (FlagSet(phase, DrawPhase::lineTranslucent)) {
2471 DrawTranslucentLineState(surface, model, vsDraw, ll, line, rcLine, subLine, Layer::OverText);
2474 if (clipLine) {
2475 surface->PopClip();
2479 void EditView::PaintText(Surface *surfaceWindow, const EditModel &model, const ViewStyle &vsDraw,
2480 PRectangle rcArea, PRectangle rcClient) {
2481 // Allow text at start of line to overlap 1 pixel into the margin as this displays
2482 // serifs and italic stems for aliased text.
2483 const int leftTextOverlap = ((model.xOffset == 0) && (vsDraw.leftMarginWidth > 0)) ? 1 : 0;
2485 // Do the painting
2486 if (rcArea.right > vsDraw.textStart - leftTextOverlap) {
2488 Surface *surface = surfaceWindow;
2489 if (bufferedDraw) {
2490 surface = pixmapLine.get();
2491 PLATFORM_ASSERT(pixmapLine->Initialised());
2493 surface->SetMode(model.CurrentSurfaceMode());
2495 const Point ptOrigin = model.GetVisibleOriginInMain();
2497 const int screenLinePaintFirst = static_cast<int>(rcArea.top) / vsDraw.lineHeight;
2498 const int xStart = vsDraw.textStart - model.xOffset + static_cast<int>(ptOrigin.x);
2500 const SelectionPosition posCaret = model.posDrag.IsValid() ? model.posDrag : model.sel.RangeMain().caret;
2501 const Sci::Line lineCaret = model.pdoc->SciLineFromPosition(posCaret.Position());
2502 const int caretOffset = static_cast<int>(posCaret.Position() - model.pdoc->LineStart(lineCaret));
2504 PRectangle rcTextArea = rcClient;
2505 if (vsDraw.marginInside) {
2506 rcTextArea.left += vsDraw.textStart;
2507 rcTextArea.right -= vsDraw.rightMarginWidth;
2508 } else {
2509 rcTextArea = rcArea;
2512 // Remove selection margin from drawing area so text will not be drawn
2513 // on it in unbuffered mode.
2514 const bool clipping = !bufferedDraw && vsDraw.marginInside;
2515 if (clipping) {
2516 PRectangle rcClipText = rcTextArea;
2517 rcClipText.left -= leftTextOverlap;
2518 surfaceWindow->SetClip(rcClipText);
2521 // Loop on visible lines
2522 #if defined(TIME_PAINTING)
2523 double durLayout = 0.0;
2524 double durPaint = 0.0;
2525 double durCopy = 0.0;
2526 ElapsedPeriod epWhole;
2527 #endif
2528 const bool bracesIgnoreStyle = ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == StyleBraceLight)) ||
2529 (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == StyleBraceBad)));
2531 Sci::Line lineDocPrevious = -1; // Used to avoid laying out one document line multiple times
2532 std::shared_ptr<LineLayout> ll;
2533 std::vector<DrawPhase> phases;
2534 if ((phasesDraw == PhasesDraw::Multiple) && !bufferedDraw) {
2535 for (DrawPhase phase = DrawPhase::back; phase <= DrawPhase::carets; phase = static_cast<DrawPhase>(static_cast<int>(phase) * 2)) {
2536 phases.push_back(phase);
2538 } else {
2539 phases.push_back(DrawPhase::all);
2541 for (const DrawPhase &phase : phases) {
2542 int ypos = 0;
2543 if (!bufferedDraw)
2544 ypos += screenLinePaintFirst * vsDraw.lineHeight;
2545 int yposScreen = screenLinePaintFirst * vsDraw.lineHeight;
2546 Sci::Line visibleLine = model.TopLineOfMain() + screenLinePaintFirst;
2547 while (visibleLine < model.pcs->LinesDisplayed() && yposScreen < rcArea.bottom) {
2549 const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine);
2550 // Only visible lines should be handled by the code within the loop
2551 PLATFORM_ASSERT(model.pcs->GetVisible(lineDoc));
2552 const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc);
2553 const int subLine = static_cast<int>(visibleLine - lineStartSet);
2555 // Copy this line and its styles from the document into local arrays
2556 // and determine the x position at which each character starts.
2557 #if defined(TIME_PAINTING)
2558 ElapsedPeriod ep;
2559 #endif
2560 if (lineDoc != lineDocPrevious) {
2561 ll = RetrieveLineLayout(lineDoc, model);
2562 LayoutLine(model, surface, vsDraw, ll.get(), model.wrapWidth);
2563 lineDocPrevious = lineDoc;
2565 #if defined(TIME_PAINTING)
2566 durLayout += ep.Duration(true);
2567 #endif
2568 if (ll) {
2569 ll->containsCaret = vsDraw.selection.visible && (lineDoc == lineCaret)
2570 && (ll->lines == 1 || !vsDraw.caretLine.subLine || ll->InLine(caretOffset, subLine));
2572 PRectangle rcLine = rcTextArea;
2573 rcLine.top = static_cast<XYPOSITION>(ypos);
2574 rcLine.bottom = static_cast<XYPOSITION>(ypos + vsDraw.lineHeight);
2576 const Range rangeLine(model.pdoc->LineStart(lineDoc),
2577 model.pdoc->LineStart(lineDoc + 1));
2579 // Highlight the current braces if any
2580 ll->SetBracesHighlight(rangeLine, model.braces, static_cast<char>(model.bracesMatchStyle),
2581 static_cast<int>(model.highlightGuideColumn * vsDraw.spaceWidth), bracesIgnoreStyle);
2583 if (leftTextOverlap && (bufferedDraw || ((phasesDraw < PhasesDraw::Multiple) && (FlagSet(phase, DrawPhase::back))))) {
2584 // Clear the left margin
2585 PRectangle rcSpacer = rcLine;
2586 rcSpacer.right = rcSpacer.left;
2587 rcSpacer.left -= 1;
2588 surface->FillRectangleAligned(rcSpacer, Fill(vsDraw.styles[StyleDefault].back));
2591 if (model.BidirectionalEnabled()) {
2592 // Fill the line bidi data
2593 UpdateBidiData(model, vsDraw, ll.get());
2596 DrawLine(surface, model, vsDraw, ll.get(), lineDoc, visibleLine, xStart, rcLine, subLine, phase);
2597 #if defined(TIME_PAINTING)
2598 durPaint += ep.Duration(true);
2599 #endif
2600 // Restore the previous styles for the brace highlights in case layout is in cache.
2601 ll->RestoreBracesHighlight(rangeLine, model.braces, bracesIgnoreStyle);
2603 if (FlagSet(phase, DrawPhase::foldLines)) {
2604 DrawFoldLines(surface, model, vsDraw, ll.get(), lineDoc, rcLine, subLine);
2607 if (FlagSet(phase, DrawPhase::carets)) {
2608 DrawCarets(surface, model, vsDraw, ll.get(), lineDoc, xStart, rcLine, subLine);
2611 if (bufferedDraw) {
2612 const Point from = Point::FromInts(vsDraw.textStart - leftTextOverlap, 0);
2613 const PRectangle rcCopyArea = PRectangle::FromInts(vsDraw.textStart - leftTextOverlap, yposScreen,
2614 static_cast<int>(rcClient.right - vsDraw.rightMarginWidth),
2615 yposScreen + vsDraw.lineHeight);
2616 pixmapLine->FlushDrawing();
2617 surfaceWindow->Copy(rcCopyArea, from, *pixmapLine);
2620 lineWidthMaxSeen = std::max(
2621 lineWidthMaxSeen, static_cast<int>(ll->positions[ll->numCharsInLine]));
2622 #if defined(TIME_PAINTING)
2623 durCopy += ep.Duration(true);
2624 #endif
2627 if (!bufferedDraw) {
2628 ypos += vsDraw.lineHeight;
2631 yposScreen += vsDraw.lineHeight;
2632 visibleLine++;
2635 ll.reset();
2636 #if defined(TIME_PAINTING)
2637 if (durPaint < 0.00000001)
2638 durPaint = 0.00000001;
2639 #endif
2640 // Right column limit indicator
2641 PRectangle rcBeyondEOF = (vsDraw.marginInside) ? rcClient : rcArea;
2642 rcBeyondEOF.left = static_cast<XYPOSITION>(vsDraw.textStart);
2643 rcBeyondEOF.right = rcBeyondEOF.right - ((vsDraw.marginInside) ? vsDraw.rightMarginWidth : 0);
2644 rcBeyondEOF.top = static_cast<XYPOSITION>((model.pcs->LinesDisplayed() - model.TopLineOfMain()) * vsDraw.lineHeight);
2645 if (rcBeyondEOF.top < rcBeyondEOF.bottom) {
2646 surfaceWindow->FillRectangleAligned(rcBeyondEOF, Fill(vsDraw.styles[StyleDefault].back));
2647 if (vsDraw.edgeState == EdgeVisualStyle::Line) {
2648 const int edgeX = static_cast<int>(vsDraw.theEdge.column * vsDraw.spaceWidth);
2649 rcBeyondEOF.left = static_cast<XYPOSITION>(edgeX + xStart);
2650 rcBeyondEOF.right = rcBeyondEOF.left + 1;
2651 surfaceWindow->FillRectangleAligned(rcBeyondEOF, Fill(vsDraw.theEdge.colour));
2652 } else if (vsDraw.edgeState == EdgeVisualStyle::MultiLine) {
2653 for (size_t edge = 0; edge < vsDraw.theMultiEdge.size(); edge++) {
2654 if (vsDraw.theMultiEdge[edge].column >= 0) {
2655 const int edgeX = static_cast<int>(vsDraw.theMultiEdge[edge].column * vsDraw.spaceWidth);
2656 rcBeyondEOF.left = static_cast<XYPOSITION>(edgeX + xStart);
2657 rcBeyondEOF.right = rcBeyondEOF.left + 1;
2658 surfaceWindow->FillRectangleAligned(rcBeyondEOF, Fill(vsDraw.theMultiEdge[edge].colour));
2664 if (clipping)
2665 surfaceWindow->PopClip();
2667 //Platform::DebugPrintf("start display %d, offset = %d\n", model.pdoc->Length(), model.xOffset);
2668 #if defined(TIME_PAINTING)
2669 Platform::DebugPrintf(
2670 "Layout:%9.6g Paint:%9.6g Ratio:%9.6g Copy:%9.6g Total:%9.6g\n",
2671 durLayout, durPaint, durLayout / durPaint, durCopy, epWhole.Duration());
2672 #endif
2676 // Space (3 space characters) between line numbers and text when printing.
2677 #define lineNumberPrintSpace " "
2679 Sci::Position EditView::FormatRange(bool draw, CharacterRangeFull chrg, Rectangle rc, Surface *surface, Surface *surfaceMeasure,
2680 const EditModel &model, const ViewStyle &vs) {
2681 // Can't use measurements cached for screen
2682 posCache->Clear();
2684 ViewStyle vsPrint(vs);
2685 vsPrint.technology = Technology::Default;
2687 // Modify the view style for printing as do not normally want any of the transient features to be printed
2688 // Printing supports only the line number margin.
2689 int lineNumberIndex = -1;
2690 for (size_t margin = 0; margin < vs.ms.size(); margin++) {
2691 if ((vsPrint.ms[margin].style == MarginType::Number) && (vsPrint.ms[margin].width > 0)) {
2692 lineNumberIndex = static_cast<int>(margin);
2693 } else {
2694 vsPrint.ms[margin].width = 0;
2697 vsPrint.fixedColumnWidth = 0;
2698 vsPrint.zoomLevel = printParameters.magnification;
2699 // Don't show indentation guides
2700 // If this ever gets changed, cached pixmap would need to be recreated if technology != Technology::Default
2701 vsPrint.viewIndentationGuides = IndentView::None;
2702 // Don't show the selection when printing
2703 vsPrint.selection.visible = false;
2704 vsPrint.elementColours.clear();
2705 vsPrint.elementBaseColours.clear();
2706 vsPrint.caretLine.alwaysShow = false;
2707 // Don't highlight matching braces using indicators
2708 vsPrint.braceHighlightIndicatorSet = false;
2709 vsPrint.braceBadLightIndicatorSet = false;
2711 // Set colours for printing according to users settings
2712 const PrintOption colourMode = printParameters.colourMode;
2713 const std::vector<Style>::iterator endStyles = (colourMode == PrintOption::ColourOnWhiteDefaultBG) ?
2714 vsPrint.styles.begin() + StyleLineNumber : vsPrint.styles.end();
2715 for (std::vector<Style>::iterator it = vsPrint.styles.begin(); it != endStyles; ++it) {
2716 if (colourMode == PrintOption::InvertLight) {
2717 it->fore = InvertedLight(it->fore);
2718 it->back = InvertedLight(it->back);
2719 } else if (colourMode == PrintOption::BlackOnWhite) {
2720 it->fore = black;
2721 it->back = white;
2722 } else if (colourMode == PrintOption::ColourOnWhite || colourMode == PrintOption::ColourOnWhiteDefaultBG) {
2723 it->back = white;
2726 // White background for the line numbers if PrintOption::ScreenColours isn't used
2727 if (colourMode != PrintOption::ScreenColours) {
2728 vsPrint.styles[StyleLineNumber].back = white;
2731 // Printing uses different margins, so reset screen margins
2732 vsPrint.leftMarginWidth = 0;
2733 vsPrint.rightMarginWidth = 0;
2735 vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars);
2736 // Determining width must happen after fonts have been realised in Refresh
2737 int lineNumberWidth = 0;
2738 if (lineNumberIndex >= 0) {
2739 lineNumberWidth = static_cast<int>(surfaceMeasure->WidthText(vsPrint.styles[StyleLineNumber].font.get(),
2740 "99999" lineNumberPrintSpace));
2741 vsPrint.ms[lineNumberIndex].width = lineNumberWidth;
2742 vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars); // Recalculate fixedColumnWidth
2745 // Turn off change history marker backgrounds
2746 constexpr unsigned int changeMarkers =
2747 1u << static_cast<unsigned int>(MarkerOutline::HistoryRevertedToOrigin) |
2748 1u << static_cast<unsigned int>(MarkerOutline::HistorySaved) |
2749 1u << static_cast<unsigned int>(MarkerOutline::HistoryModified) |
2750 1u << static_cast<unsigned int>(MarkerOutline::HistoryRevertedToModified);
2751 vsPrint.maskInLine &= ~changeMarkers;
2753 const Sci::Line linePrintStart = model.pdoc->SciLineFromPosition(chrg.cpMin);
2754 Sci::Line linePrintLast = linePrintStart + (rc.bottom - rc.top) / vsPrint.lineHeight - 1;
2755 if (linePrintLast < linePrintStart)
2756 linePrintLast = linePrintStart;
2757 const Sci::Line linePrintMax = model.pdoc->SciLineFromPosition(chrg.cpMax);
2758 if (linePrintLast > linePrintMax)
2759 linePrintLast = linePrintMax;
2760 //Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n",
2761 // linePrintStart, linePrintLast, linePrintMax, rc.top, rc.bottom, vsPrint.lineHeight,
2762 // surfaceMeasure->Height(vsPrint.styles[StyleLineNumber].font));
2763 Sci::Position endPosPrint = model.pdoc->Length();
2764 if (linePrintLast < model.pdoc->LinesTotal())
2765 endPosPrint = model.pdoc->LineStart(linePrintLast + 1);
2767 // Ensure we are styled to where we are formatting.
2768 model.pdoc->EnsureStyledTo(endPosPrint);
2770 const int xStart = vsPrint.fixedColumnWidth + rc.left;
2771 int ypos = rc.top;
2773 Sci::Line lineDoc = linePrintStart;
2775 Sci::Position nPrintPos = chrg.cpMin;
2776 int visibleLine = 0;
2777 int widthPrint = rc.right - rc.left - vsPrint.fixedColumnWidth;
2778 if (printParameters.wrapState == Wrap::None)
2779 widthPrint = LineLayout::wrapWidthInfinite;
2781 while (lineDoc <= linePrintLast && ypos < rc.bottom) {
2783 // When printing, the hdc and hdcTarget may be the same, so
2784 // changing the state of surfaceMeasure may change the underlying
2785 // state of surface. Therefore, any cached state is discarded before
2786 // using each surface.
2787 surfaceMeasure->FlushCachedState();
2789 // Copy this line and its styles from the document into local arrays
2790 // and determine the x position at which each character starts.
2791 LineLayout ll(lineDoc, static_cast<int>(model.pdoc->LineStart(lineDoc + 1) - model.pdoc->LineStart(lineDoc) + 1));
2792 LayoutLine(model, surfaceMeasure, vsPrint, &ll, widthPrint);
2794 ll.containsCaret = false;
2796 PRectangle rcLine = PRectangle::FromInts(
2797 rc.left,
2798 ypos,
2799 rc.right - 1,
2800 ypos + vsPrint.lineHeight);
2802 // When document line is wrapped over multiple display lines, find where
2803 // to start printing from to ensure a particular position is on the first
2804 // line of the page.
2805 if (visibleLine == 0) {
2806 const Sci::Position startWithinLine = nPrintPos -
2807 model.pdoc->LineStart(lineDoc);
2808 for (int iwl = 0; iwl < ll.lines - 1; iwl++) {
2809 if (ll.LineStart(iwl) <= startWithinLine && ll.LineStart(iwl + 1) >= startWithinLine) {
2810 visibleLine = -iwl;
2814 if (ll.lines > 1 && startWithinLine >= ll.LineStart(ll.lines - 1)) {
2815 visibleLine = -(ll.lines - 1);
2819 if (draw && lineNumberWidth &&
2820 (ypos + vsPrint.lineHeight <= rc.bottom) &&
2821 (visibleLine >= 0)) {
2822 const std::string number = std::to_string(lineDoc + 1) + lineNumberPrintSpace;
2823 PRectangle rcNumber = rcLine;
2824 rcNumber.right = rcNumber.left + lineNumberWidth;
2825 // Right justify
2826 rcNumber.left = rcNumber.right - surfaceMeasure->WidthText(
2827 vsPrint.styles[StyleLineNumber].font.get(), number);
2828 surface->FlushCachedState();
2829 surface->DrawTextNoClip(rcNumber, vsPrint.styles[StyleLineNumber].font.get(),
2830 ypos + vsPrint.maxAscent, number,
2831 vsPrint.styles[StyleLineNumber].fore,
2832 vsPrint.styles[StyleLineNumber].back);
2835 // Draw the line
2836 surface->FlushCachedState();
2838 for (int iwl = 0; iwl < ll.lines; iwl++) {
2839 if (ypos + vsPrint.lineHeight <= rc.bottom) {
2840 if (visibleLine >= 0) {
2841 if (draw) {
2842 rcLine.top = static_cast<XYPOSITION>(ypos);
2843 rcLine.bottom = static_cast<XYPOSITION>(ypos + vsPrint.lineHeight);
2844 DrawLine(surface, model, vsPrint, &ll, lineDoc, visibleLine, xStart, rcLine, iwl, DrawPhase::all);
2846 ypos += vsPrint.lineHeight;
2848 visibleLine++;
2849 if (iwl == ll.lines - 1)
2850 nPrintPos = model.pdoc->LineStart(lineDoc + 1);
2851 else
2852 nPrintPos += ll.LineStart(iwl + 1) - ll.LineStart(iwl);
2856 ++lineDoc;
2859 // Clear cache so measurements are not used for screen
2860 posCache->Clear();
2862 return nPrintPos;