Fixed issue #4141: Cannot remove remote: "usage: git remote remove <name>"
[TortoiseGit.git] / ext / scintilla / src / EditView.cxx
blob1e86aa50d24f66576f274457dad4929febb1f455
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"
68 #include "Editor.h"
70 using namespace Scintilla;
71 using namespace Scintilla::Internal;
73 PrintParameters::PrintParameters() noexcept {
74 magnification = 0;
75 colourMode = PrintOption::Normal;
76 wrapState = Wrap::Word;
79 namespace {
81 int WidthStyledText(Surface *surface, const ViewStyle &vs, int styleOffset,
82 const char *text, const unsigned char *styles, size_t len) {
83 int width = 0;
84 size_t start = 0;
85 while (start < len) {
86 const unsigned char style = styles[start];
87 size_t endSegment = start;
88 while ((endSegment + 1 < len) && (styles[endSegment + 1] == style))
89 endSegment++;
90 const Font *fontText = vs.styles[style + styleOffset].font.get();
91 const std::string_view sv(text + start, endSegment - start + 1);
92 width += static_cast<int>(surface->WidthText(fontText, sv));
93 start = endSegment + 1;
95 return width;
100 namespace Scintilla::Internal {
102 bool ValidStyledText(const ViewStyle &vs, size_t styleOffset, const StyledText &st) noexcept {
103 if (st.multipleStyles) {
104 for (size_t iStyle = 0; iStyle<st.length; iStyle++) {
105 if (!vs.ValidStyle(styleOffset + st.styles[iStyle]))
106 return false;
108 } else {
109 if (!vs.ValidStyle(styleOffset + st.style))
110 return false;
112 return true;
115 int WidestLineWidth(Surface *surface, const ViewStyle &vs, int styleOffset, const StyledText &st) {
116 int widthMax = 0;
117 size_t start = 0;
118 while (start < st.length) {
119 const size_t lenLine = st.LineLength(start);
120 int widthSubLine;
121 if (st.multipleStyles) {
122 widthSubLine = WidthStyledText(surface, vs, styleOffset, st.text + start, st.styles + start, lenLine);
123 } else {
124 const Font *fontText = vs.styles[styleOffset + st.style].font.get();
125 const std::string_view text(st.text + start, lenLine);
126 widthSubLine = static_cast<int>(surface->WidthText(fontText, text));
128 if (widthSubLine > widthMax)
129 widthMax = widthSubLine;
130 start += lenLine + 1;
132 return widthMax;
135 void DrawTextNoClipPhase(Surface *surface, PRectangle rc, const Style &style, XYPOSITION ybase,
136 std::string_view text, DrawPhase phase) {
137 const Font *fontText = style.font.get();
138 if (FlagSet(phase, DrawPhase::back)) {
139 if (FlagSet(phase, DrawPhase::text)) {
140 // Drawing both
141 surface->DrawTextNoClip(rc, fontText, ybase, text,
142 style.fore, style.back);
143 } else {
144 surface->FillRectangleAligned(rc, Fill(style.back));
146 } else if (FlagSet(phase, DrawPhase::text)) {
147 surface->DrawTextTransparent(rc, fontText, ybase, text, style.fore);
151 void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRectangle rcText,
152 const StyledText &st, size_t start, size_t length, DrawPhase phase) {
154 if (st.multipleStyles) {
155 int x = static_cast<int>(rcText.left);
156 size_t i = 0;
157 while (i < length) {
158 size_t end = i;
159 size_t style = st.styles[i + start];
160 while (end < length - 1 && st.styles[start + end + 1] == style)
161 end++;
162 style += styleOffset;
163 const Font *fontText = vs.styles[style].font.get();
164 const std::string_view text(st.text + start + i, end - i + 1);
165 const int width = static_cast<int>(surface->WidthText(fontText, text));
166 PRectangle rcSegment = rcText;
167 rcSegment.left = static_cast<XYPOSITION>(x);
168 rcSegment.right = static_cast<XYPOSITION>(x + width + 1);
169 DrawTextNoClipPhase(surface, rcSegment, vs.styles[style],
170 rcText.top + vs.maxAscent, text, phase);
171 x += width;
172 i = end + 1;
174 } else {
175 const size_t style = st.style + styleOffset;
176 DrawTextNoClipPhase(surface, rcText, vs.styles[style],
177 rcText.top + vs.maxAscent,
178 std::string_view(st.text + start, length), phase);
184 EditView::EditView() {
185 tabWidthMinimumPixels = 2; // needed for calculating tab stops for fractional proportional fonts
186 drawOverstrikeCaret = true;
187 bufferedDraw = true;
188 phasesDraw = PhasesDraw::Two;
189 lineWidthMaxSeen = 0;
190 additionalCaretsBlink = true;
191 additionalCaretsVisible = true;
192 imeCaretBlockOverride = false;
193 llc.SetLevel(LineCache::Caret);
194 posCache = CreatePositionCache();
195 posCache->SetSize(0x400);
196 maxLayoutThreads = 1;
197 tabArrowHeight = 4;
198 customDrawTabArrow = nullptr;
199 customDrawWrapMarker = nullptr;
200 editor = nullptr;
203 EditView::~EditView() = default;
205 bool EditView::SetTwoPhaseDraw(bool twoPhaseDraw) noexcept {
206 const PhasesDraw phasesDrawNew = twoPhaseDraw ? PhasesDraw::Two : PhasesDraw::One;
207 const bool redraw = phasesDraw != phasesDrawNew;
208 phasesDraw = phasesDrawNew;
209 return redraw;
212 bool EditView::SetPhasesDraw(int phases) noexcept {
213 const PhasesDraw phasesDrawNew = static_cast<PhasesDraw>(phases);
214 const bool redraw = phasesDraw != phasesDrawNew;
215 phasesDraw = phasesDrawNew;
216 return redraw;
219 bool EditView::LinesOverlap() const noexcept {
220 return phasesDraw == PhasesDraw::Multiple;
223 void EditView::SetLayoutThreads(unsigned int threads) noexcept {
224 maxLayoutThreads = std::clamp(threads, 1U, std::thread::hardware_concurrency());
227 unsigned int EditView::GetLayoutThreads() const noexcept {
228 return maxLayoutThreads;
231 void EditView::ClearAllTabstops() noexcept {
232 ldTabstops.reset();
235 XYPOSITION EditView::NextTabstopPos(Sci::Line line, XYPOSITION x, XYPOSITION tabWidth) const noexcept {
236 const int next = GetNextTabstop(line, static_cast<int>(x + tabWidthMinimumPixels));
237 if (next > 0)
238 return static_cast<XYPOSITION>(next);
239 return (static_cast<int>((x + tabWidthMinimumPixels) / tabWidth) + 1) * tabWidth;
242 bool EditView::ClearTabstops(Sci::Line line) noexcept {
243 return ldTabstops && ldTabstops->ClearTabstops(line);
246 bool EditView::AddTabstop(Sci::Line line, int x) {
247 if (!ldTabstops) {
248 ldTabstops = std::make_unique<LineTabstops>();
250 return ldTabstops && ldTabstops->AddTabstop(line, x);
253 int EditView::GetNextTabstop(Sci::Line line, int x) const noexcept {
254 if (ldTabstops) {
255 return ldTabstops->GetNextTabstop(line, x);
256 } else {
257 return 0;
261 void EditView::LinesAddedOrRemoved(Sci::Line lineOfPos, Sci::Line linesAdded) {
262 if (ldTabstops) {
263 if (linesAdded > 0) {
264 for (Sci::Line line = lineOfPos; line < lineOfPos + linesAdded; line++) {
265 ldTabstops->InsertLine(line);
267 } else {
268 for (Sci::Line line = (lineOfPos + -linesAdded) - 1; line >= lineOfPos; line--) {
269 ldTabstops->RemoveLine(line);
275 void EditView::DropGraphics() noexcept {
276 pixmapLine.reset();
277 pixmapIndentGuide.reset();
278 pixmapIndentGuideHighlight.reset();
281 void EditView::RefreshPixMaps(Surface *surfaceWindow, const ViewStyle &vsDraw) {
282 if (!pixmapIndentGuide) {
283 // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line
284 pixmapIndentGuide = surfaceWindow->AllocatePixMap(1, vsDraw.lineHeight + 1);
285 pixmapIndentGuideHighlight = surfaceWindow->AllocatePixMap(1, vsDraw.lineHeight + 1);
286 const PRectangle rcIG = PRectangle::FromInts(0, 0, 1, vsDraw.lineHeight);
287 pixmapIndentGuide->FillRectangle(rcIG, vsDraw.styles[StyleIndentGuide].back);
288 pixmapIndentGuideHighlight->FillRectangle(rcIG, vsDraw.styles[StyleBraceLight].back);
289 for (int stripe = 1; stripe < vsDraw.lineHeight + 1; stripe += 2) {
290 const PRectangle rcPixel = PRectangle::FromInts(0, stripe, 1, stripe + 1);
291 pixmapIndentGuide->FillRectangle(rcPixel, vsDraw.styles[StyleIndentGuide].fore);
292 pixmapIndentGuideHighlight->FillRectangle(rcPixel, vsDraw.styles[StyleBraceLight].fore);
294 pixmapIndentGuide->FlushDrawing();
295 pixmapIndentGuideHighlight->FlushDrawing();
299 std::shared_ptr<LineLayout> EditView::RetrieveLineLayout(Sci::Line lineNumber, const EditModel &model) {
300 const Sci::Position posLineStart = model.pdoc->LineStart(lineNumber);
301 const Sci::Position posLineEnd = model.pdoc->LineStart(lineNumber + 1);
302 PLATFORM_ASSERT(posLineEnd >= posLineStart);
303 const Sci::Line lineCaret = model.pdoc->SciLineFromPosition(model.sel.MainCaret());
304 return llc.Retrieve(lineNumber, lineCaret,
305 static_cast<int>(posLineEnd - posLineStart), model.pdoc->GetStyleClock(),
306 model.LinesOnScreen() + 1, model.pdoc->LinesTotal());
309 namespace {
311 constexpr XYPOSITION epsilon = 0.0001f; // A small nudge to avoid floating point precision issues
314 * Return the chDoc argument with case transformed as indicated by the caseForce argument.
315 * chPrevious is needed for camel casing.
316 * This only affects ASCII characters and is provided for languages with case-insensitive
317 * ASCII keywords where the user wishes to view keywords in a preferred case.
319 inline char CaseForce(Style::CaseForce caseForce, char chDoc, char chPrevious) noexcept {
320 switch (caseForce) {
321 case Style::CaseForce::mixed:
322 return chDoc;
323 case Style::CaseForce::lower:
324 return MakeLowerCase(chDoc);
325 case Style::CaseForce::upper:
326 return MakeUpperCase(chDoc);
327 case Style::CaseForce::camel:
328 default: // default should not occur, included to avoid warnings
329 if (IsUpperOrLowerCase(chDoc) && !IsUpperOrLowerCase(chPrevious)) {
330 return MakeUpperCase(chDoc);
331 } else {
332 return MakeLowerCase(chDoc);
337 void LayoutSegments(IPositionCache *pCache,
338 Surface *surface,
339 const ViewStyle &vstyle,
340 LineLayout *ll,
341 const std::vector<TextSegment> &segments,
342 std::atomic<uint32_t> &nextIndex,
343 const bool textUnicode,
344 const bool multiThreaded) {
345 while (true) {
346 const uint32_t i = nextIndex.fetch_add(1, std::memory_order_acq_rel);
347 if (i >= segments.size()) {
348 break;
350 const TextSegment &ts = segments[i];
351 const unsigned int styleSegment = ll->styles[ts.start];
352 XYPOSITION *positions = &ll->positions[ts.start + 1];
353 if (vstyle.styles[styleSegment].visible) {
354 if (ts.representation) {
355 XYPOSITION representationWidth = 0.0;
356 // Tab is a special case of representation, taking a variable amount of space
357 // which will be filled in later.
358 if (ll->chars[ts.start] != '\t') {
359 representationWidth = vstyle.controlCharWidth;
360 if (representationWidth <= 0.0) {
361 assert(ts.representation->stringRep.length() <= Representation::maxLength);
362 XYPOSITION positionsRepr[Representation::maxLength + 1];
363 // ts.representation->stringRep is UTF-8.
364 pCache->MeasureWidths(surface, vstyle, StyleControlChar, true, ts.representation->stringRep,
365 positionsRepr, multiThreaded);
366 representationWidth = positionsRepr[ts.representation->stringRep.length() - 1];
367 if (FlagSet(ts.representation->appearance, RepresentationAppearance::Blob)) {
368 representationWidth += vstyle.ctrlCharPadding;
372 std::fill(positions, positions + ts.length, representationWidth);
373 } else {
374 if ((ts.length == 1) && (' ' == ll->chars[ts.start])) {
375 // Over half the segments are single characters and of these about half are space characters.
376 positions[0] = vstyle.styles[styleSegment].spaceWidth;
377 } else {
378 pCache->MeasureWidths(surface, vstyle, styleSegment, textUnicode,
379 std::string_view(&ll->chars[ts.start], ts.length), positions, multiThreaded);
382 } else if (vstyle.styles[styleSegment].invisibleRepresentation[0]) {
383 const std::string_view text = vstyle.styles[styleSegment].invisibleRepresentation;
384 XYPOSITION positionsRepr[Representation::maxLength + 1];
385 // invisibleRepresentation is UTF-8.
386 pCache->MeasureWidths(surface, vstyle, styleSegment, true, text, positionsRepr, multiThreaded);
387 const XYPOSITION representationWidth = positionsRepr[text.length() - 1];
388 std::fill(positions, positions + ts.length, representationWidth);
396 * Fill in the LineLayout data for the given line.
397 * Copy the given @a line and its styles from the document into local arrays.
398 * Also determine the x position at which each character starts.
400 void EditView::LayoutLine(const EditModel &model, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, int width, bool callerMultiThreaded) {
401 if (!ll)
402 return;
403 const Sci::Line line = ll->LineNumber();
404 PLATFORM_ASSERT(line < model.pdoc->LinesTotal());
405 PLATFORM_ASSERT(ll->chars);
406 const Sci::Position posLineStart = model.pdoc->LineStart(line);
407 Sci::Position posLineEnd = model.pdoc->LineStart(line + 1);
408 // If the line is very long, limit the treatment to a length that should fit in the viewport
409 if (posLineEnd >(posLineStart + ll->maxLineLength)) {
410 posLineEnd = posLineStart + ll->maxLineLength;
412 // Hard to cope when too narrow, so just assume there is space
413 width = std::max(width, 20);
415 if (ll->validity == LineLayout::ValidLevel::checkTextAndStyle) {
416 Sci::Position lineLength = posLineEnd - posLineStart;
417 if (!vstyle.viewEOL) {
418 lineLength = model.pdoc->LineEnd(line) - posLineStart;
420 if (lineLength == ll->numCharsInLine) {
421 // See if chars, styles, indicators, are all the same
422 bool allSame = true;
423 // Check base line layout
424 char chPrevious = 0;
425 for (Sci::Position numCharsInLine = 0; numCharsInLine < lineLength; numCharsInLine++) {
426 const Sci::Position charInDoc = numCharsInLine + posLineStart;
427 const char chDoc = model.pdoc->CharAt(charInDoc);
428 const int styleByte = model.pdoc->StyleIndexAt(charInDoc);
429 allSame = allSame &&
430 (ll->styles[numCharsInLine] == styleByte);
431 allSame = allSame &&
432 (ll->chars[numCharsInLine] == CaseForce(vstyle.styles[styleByte].caseForce, chDoc, chPrevious));
433 chPrevious = chDoc;
435 const int styleByteLast = (posLineEnd > posLineStart) ? model.pdoc->StyleIndexAt(posLineEnd - 1) : 0;
436 allSame = allSame && (ll->styles[lineLength] == styleByteLast); // For eolFilled
437 if (allSame) {
438 ll->validity = (ll->widthLine != width) ? LineLayout::ValidLevel::positions : LineLayout::ValidLevel::lines;
439 } else {
440 ll->validity = LineLayout::ValidLevel::invalid;
442 } else {
443 ll->validity = LineLayout::ValidLevel::invalid;
446 if (ll->validity == LineLayout::ValidLevel::invalid) {
447 ll->widthLine = LineLayout::wrapWidthInfinite;
448 ll->lines = 1;
449 if (vstyle.edgeState == EdgeVisualStyle::Background) {
450 Sci::Position edgePosition = model.pdoc->FindColumn(line, vstyle.theEdge.column);
451 if (edgePosition >= posLineStart) {
452 edgePosition -= posLineStart;
454 ll->edgeColumn = static_cast<int>(edgePosition);
455 } else {
456 ll->edgeColumn = -1;
459 // Fill base line layout
460 const int lineLength = static_cast<int>(posLineEnd - posLineStart);
461 model.pdoc->GetCharRange(ll->chars.get(), posLineStart, lineLength);
462 model.pdoc->GetStyleRange(ll->styles.get(), posLineStart, lineLength);
463 const int numCharsBeforeEOL = static_cast<int>(model.pdoc->LineEnd(line) - posLineStart);
464 const int numCharsInLine = (vstyle.viewEOL) ? lineLength : numCharsBeforeEOL;
465 const unsigned char styleByteLast = (lineLength > 0) ? ll->styles[lineLength - 1] : 0;
466 if (vstyle.someStylesForceCase) {
467 char chPrevious = 0;
468 for (int charInLine = 0; charInLine<lineLength; charInLine++) {
469 const char chDoc = ll->chars[charInLine];
470 ll->chars[charInLine] = CaseForce(vstyle.styles[ll->styles[charInLine]].caseForce, chDoc, chPrevious);
471 chPrevious = chDoc;
474 ll->xHighlightGuide = 0;
475 // Extra element at the end of the line to hold end x position and act as
476 ll->chars[numCharsInLine] = 0; // Also triggers processing in the loops as this is a control character
477 ll->styles[numCharsInLine] = styleByteLast; // For eolFilled
479 // Layout the line, determining the position of each character,
480 // with an extra element at the end for the end of the line.
481 ll->positions[0] = 0;
482 bool lastSegItalics = false;
484 std::vector<TextSegment> segments;
485 BreakFinder bfLayout(ll, nullptr, Range(0, numCharsInLine), posLineStart, 0, BreakFinder::BreakFor::Text, model.pdoc, &model.reprs, nullptr);
486 while (bfLayout.More()) {
487 segments.push_back(bfLayout.Next());
490 ll->ClearPositions();
492 if (!segments.empty()) {
494 const size_t threadsForLength = std::max(1, numCharsInLine / bytesPerLayoutThread);
495 size_t threads = std::min<size_t>({ segments.size(), threadsForLength, maxLayoutThreads });
496 if (!surface->SupportsFeature(Supports::ThreadSafeMeasureWidths) || callerMultiThreaded) {
497 threads = 1;
500 std::atomic<uint32_t> nextIndex = 0;
502 const bool textUnicode = CpUtf8 == model.pdoc->dbcsCodePage;
503 const bool multiThreaded = threads > 1;
504 const bool multiThreadedContext = multiThreaded || callerMultiThreaded;
505 IPositionCache *pCache = posCache.get();
507 // If only 1 thread needed then use the main thread, else spin up multiple
508 const std::launch policy = (multiThreaded) ? std::launch::async : std::launch::deferred;
510 std::vector<std::future<void>> futures;
511 for (size_t th = 0; th < threads; th++) {
512 // Find relative positions of everything except for tabs
513 std::future<void> fut = std::async(policy,
514 [pCache, surface, &vstyle, &ll, &segments, &nextIndex, textUnicode, multiThreadedContext]() {
515 LayoutSegments(pCache, surface, vstyle, ll, segments, nextIndex, textUnicode, multiThreadedContext);
517 futures.push_back(std::move(fut));
519 for (const std::future<void> &f : futures) {
520 f.wait();
524 // Accumulate absolute positions from relative positions within segments and expand tabs
525 XYPOSITION xPosition = 0.0;
526 size_t iByte = 0;
527 ll->positions[iByte++] = xPosition;
528 for (const TextSegment &ts : segments) {
529 if (vstyle.styles[ll->styles[ts.start]].visible &&
530 ts.representation &&
531 (ll->chars[ts.start] == '\t')) {
532 // Simple visible tab, go to next tab stop
533 const XYPOSITION startTab = ll->positions[ts.start];
534 const XYPOSITION nextTab = NextTabstopPos(line, startTab, vstyle.tabWidth);
535 xPosition += nextTab - startTab;
537 const XYPOSITION xBeginSegment = xPosition;
538 for (int i = 0; i < ts.length; i++) {
539 xPosition = ll->positions[iByte] + xBeginSegment;
540 ll->positions[iByte++] = xPosition;
544 if (!segments.empty()) {
545 // Not quite the same as before which would effectively ignore trailing invisible segments
546 const TextSegment &ts = segments.back();
547 lastSegItalics = (!ts.representation) && ((ll->chars[ts.end() - 1] != ' ') && vstyle.styles[ll->styles[ts.start]].italic);
550 // Small hack to make lines that end with italics not cut off the edge of the last character
551 if (lastSegItalics) {
552 ll->positions[numCharsInLine] += vstyle.lastSegItalicsOffset;
554 ll->numCharsInLine = numCharsInLine;
555 ll->numCharsBeforeEOL = numCharsBeforeEOL;
556 ll->validity = LineLayout::ValidLevel::positions;
558 if ((ll->validity == LineLayout::ValidLevel::positions) || (ll->widthLine != width)) {
559 ll->widthLine = width;
560 if (width == LineLayout::wrapWidthInfinite) {
561 ll->lines = 1;
562 } else if (width > ll->positions[ll->numCharsInLine]) {
563 // Simple common case where line does not need wrapping.
564 ll->lines = 1;
565 } else {
566 if (FlagSet(vstyle.wrap.visualFlags, WrapVisualFlag::End)) {
567 width -= static_cast<int>(vstyle.aveCharWidth); // take into account the space for end wrap mark
569 XYPOSITION wrapAddIndent = 0; // This will be added to initial indent of line
570 switch (vstyle.wrap.indentMode) {
571 case WrapIndentMode::Fixed:
572 wrapAddIndent = vstyle.wrap.visualStartIndent * vstyle.aveCharWidth;
573 break;
574 case WrapIndentMode::Indent:
575 wrapAddIndent = model.pdoc->IndentSize() * vstyle.spaceWidth;
576 break;
577 case WrapIndentMode::DeepIndent:
578 wrapAddIndent = model.pdoc->IndentSize() * 2 * vstyle.spaceWidth;
579 break;
580 default: // No additional indent for WrapIndentMode::Fixed
581 break;
583 ll->wrapIndent = wrapAddIndent;
584 if (vstyle.wrap.indentMode != WrapIndentMode::Fixed) {
585 for (int i = 0; i < ll->numCharsInLine; i++) {
586 if (!IsSpaceOrTab(ll->chars[i])) {
587 ll->wrapIndent += ll->positions[i]; // Add line indent
588 break;
592 // Check for text width minimum
593 if (ll->wrapIndent > width - static_cast<int>(vstyle.aveCharWidth) * 15)
594 ll->wrapIndent = wrapAddIndent;
595 // Check for wrapIndent minimum
596 if ((FlagSet(vstyle.wrap.visualFlags, WrapVisualFlag::Start)) && (ll->wrapIndent < vstyle.aveCharWidth))
597 ll->wrapIndent = vstyle.aveCharWidth; // Indent to show start visual
598 ll->WrapLine(model.pdoc, posLineStart, vstyle.wrap.state, width);
600 ll->validity = LineLayout::ValidLevel::lines;
604 // Fill the LineLayout bidirectional data fields according to each char style
606 void EditView::UpdateBidiData(const EditModel &model, const ViewStyle &vstyle, LineLayout *ll) {
607 if (model.BidirectionalEnabled()) {
608 ll->EnsureBidiData();
609 for (int stylesInLine = 0; stylesInLine < ll->numCharsInLine; stylesInLine++) {
610 ll->bidiData->stylesFonts[stylesInLine] = vstyle.styles[ll->styles[stylesInLine]].font;
612 ll->bidiData->stylesFonts[ll->numCharsInLine].reset();
614 for (int charsInLine = 0; charsInLine < ll->numCharsInLine; charsInLine++) {
615 const int charWidth = UTF8DrawBytes(&ll->chars[charsInLine], ll->numCharsInLine - charsInLine);
616 const Representation *repr = model.reprs.RepresentationFromCharacter(std::string_view(&ll->chars[charsInLine], charWidth));
618 ll->bidiData->widthReprs[charsInLine] = 0.0f;
619 if (repr && ll->chars[charsInLine] != '\t') {
620 ll->bidiData->widthReprs[charsInLine] = ll->positions[charsInLine + charWidth] - ll->positions[charsInLine];
622 if (charWidth > 1) {
623 for (int c = 1; c < charWidth; c++) {
624 charsInLine++;
625 ll->bidiData->widthReprs[charsInLine] = 0.0f;
629 ll->bidiData->widthReprs[ll->numCharsInLine] = 0.0f;
630 } else {
631 ll->bidiData.reset();
635 Point EditView::LocationFromPosition(Surface *surface, const EditModel &model, SelectionPosition pos, Sci::Line topLine,
636 const ViewStyle &vs, PointEnd pe, const PRectangle rcClient) {
637 Point pt;
638 if (pos.Position() == Sci::invalidPosition)
639 return pt;
640 Sci::Line lineDoc = model.pdoc->SciLineFromPosition(pos.Position());
641 Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
642 if (FlagSet(pe, PointEnd::lineEnd) && (lineDoc > 0) && (pos.Position() == posLineStart)) {
643 // Want point at end of first line
644 lineDoc--;
645 posLineStart = model.pdoc->LineStart(lineDoc);
647 const Sci::Line lineVisible = model.pcs->DisplayFromDoc(lineDoc);
648 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
649 if (surface && ll) {
650 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
651 const int posInLine = static_cast<int>(pos.Position() - posLineStart);
652 pt = ll->PointFromPosition(posInLine, vs.lineHeight, pe);
653 pt.x += vs.textStart - model.xOffset;
655 if (model.BidirectionalEnabled()) {
656 // Fill the line bidi data
657 UpdateBidiData(model, vs, ll.get());
659 // Find subLine
660 const int subLine = ll->SubLineFromPosition(posInLine, pe);
661 const int caretPosition = posInLine - ll->LineStart(subLine);
663 // Get the point from current position
664 const ScreenLine screenLine(ll.get(), subLine, vs, rcClient.right, tabWidthMinimumPixels);
665 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
666 pt.x = slLayout->XFromPosition(caretPosition);
668 pt.x += vs.textStart - model.xOffset;
670 pt.y = 0;
671 if (posInLine >= ll->LineStart(subLine)) {
672 pt.y = static_cast<XYPOSITION>(subLine*vs.lineHeight);
675 pt.y += (lineVisible - topLine) * vs.lineHeight;
676 pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth;
678 return pt;
681 Range EditView::RangeDisplayLine(Surface *surface, const EditModel &model, Sci::Line lineVisible, const ViewStyle &vs) {
682 Range rangeSubLine = Range(0, 0);
683 if (lineVisible < 0) {
684 return rangeSubLine;
686 const Sci::Line lineDoc = model.pcs->DocFromDisplay(lineVisible);
687 const Sci::Position positionLineStart = model.pdoc->LineStart(lineDoc);
688 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
689 if (surface && ll) {
690 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
691 const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc);
692 const int subLine = static_cast<int>(lineVisible - lineStartSet);
693 if (subLine < ll->lines) {
694 rangeSubLine = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
695 if (subLine == ll->lines-1) {
696 rangeSubLine.end = model.pdoc->LineStart(lineDoc + 1) -
697 positionLineStart;
701 rangeSubLine.start += positionLineStart;
702 rangeSubLine.end += positionLineStart;
703 return rangeSubLine;
706 SelectionPosition EditView::SPositionFromLocation(Surface *surface, const EditModel &model, PointDocument pt, bool canReturnInvalid,
707 bool charPosition, bool virtualSpace, const ViewStyle &vs, const PRectangle rcClient) {
708 pt.x = pt.x - vs.textStart;
709 Sci::Line visibleLine = static_cast<int>(std::floor(pt.y / vs.lineHeight));
710 if (!canReturnInvalid && (visibleLine < 0))
711 visibleLine = 0;
712 const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine);
713 if (canReturnInvalid && (lineDoc < 0))
714 return SelectionPosition(Sci::invalidPosition);
715 if (lineDoc >= model.pdoc->LinesTotal())
716 return SelectionPosition(canReturnInvalid ? Sci::invalidPosition :
717 model.pdoc->Length());
718 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
719 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
720 if (surface && ll) {
721 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
722 const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc);
723 const int subLine = static_cast<int>(visibleLine - lineStartSet);
724 if (subLine < ll->lines) {
725 const Range rangeSubLine = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
726 const XYPOSITION subLineStart = ll->positions[rangeSubLine.start];
727 if (subLine > 0) // Wrapped
728 pt.x -= ll->wrapIndent;
729 Sci::Position positionInLine = 0;
730 if (model.BidirectionalEnabled()) {
731 // Fill the line bidi data
732 UpdateBidiData(model, vs, ll.get());
734 const ScreenLine screenLine(ll.get(), subLine, vs, rcClient.right, tabWidthMinimumPixels);
735 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
736 positionInLine = slLayout->PositionFromX(pt.x, charPosition) +
737 rangeSubLine.start;
738 } else {
739 positionInLine = ll->FindPositionFromX(pt.x + subLineStart,
740 rangeSubLine, charPosition);
742 if (positionInLine < rangeSubLine.end) {
743 return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1));
745 if (virtualSpace) {
746 const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth;
747 const int spaceOffset = static_cast<int>(
748 (pt.x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth);
749 return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset);
750 } else if (canReturnInvalid) {
751 if (pt.x < (ll->positions[rangeSubLine.end] - subLineStart)) {
752 return SelectionPosition(model.pdoc->MovePositionOutsideChar(rangeSubLine.end + posLineStart, 1));
754 } else {
755 return SelectionPosition(rangeSubLine.end + posLineStart);
758 if (!canReturnInvalid)
759 return SelectionPosition(ll->numCharsInLine + posLineStart);
761 return SelectionPosition(canReturnInvalid ? Sci::invalidPosition : posLineStart);
765 * Find the document position corresponding to an x coordinate on a particular document line.
766 * Ensure is between whole characters when document is in multi-byte or UTF-8 mode.
767 * This method is used for rectangular selections and does not work on wrapped lines.
769 SelectionPosition EditView::SPositionFromLineX(Surface *surface, const EditModel &model, Sci::Line lineDoc, int x, const ViewStyle &vs) {
770 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
771 if (surface && ll) {
772 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
773 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
774 const Range rangeSubLine = ll->SubLineRange(0, LineLayout::Scope::visibleOnly);
775 const XYPOSITION subLineStart = ll->positions[rangeSubLine.start];
776 const Sci::Position positionInLine = ll->FindPositionFromX(x + subLineStart, rangeSubLine, false);
777 if (positionInLine < rangeSubLine.end) {
778 return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1));
780 const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth;
781 const int spaceOffset = static_cast<int>(
782 (x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth);
783 return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset);
785 return SelectionPosition(0);
788 Sci::Line EditView::DisplayFromPosition(Surface *surface, const EditModel &model, Sci::Position pos, const ViewStyle &vs) {
789 const Sci::Line lineDoc = model.pdoc->SciLineFromPosition(pos);
790 Sci::Line lineDisplay = model.pcs->DisplayFromDoc(lineDoc);
791 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
792 if (surface && ll) {
793 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
794 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
795 const Sci::Position posInLine = pos - posLineStart;
796 lineDisplay--; // To make up for first increment ahead.
797 for (int subLine = 0; subLine < ll->lines; subLine++) {
798 if (posInLine >= ll->LineStart(subLine)) {
799 lineDisplay++;
803 return lineDisplay;
806 Sci::Position EditView::StartEndDisplayLine(Surface *surface, const EditModel &model, Sci::Position pos, bool start, const ViewStyle &vs) {
807 const Sci::Line line = model.pdoc->SciLineFromPosition(pos);
808 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(line, model);
809 Sci::Position posRet = Sci::invalidPosition;
810 if (surface && ll) {
811 const Sci::Position posLineStart = model.pdoc->LineStart(line);
812 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
813 const Sci::Position posInLine = pos - posLineStart;
814 if (posInLine <= ll->maxLineLength) {
815 for (int subLine = 0; subLine < ll->lines; subLine++) {
816 if ((posInLine >= ll->LineStart(subLine)) &&
817 (posInLine <= ll->LineStart(subLine + 1)) &&
818 (posInLine <= ll->numCharsBeforeEOL)) {
819 if (start) {
820 posRet = ll->LineStart(subLine) + posLineStart;
821 } else {
822 if (subLine == ll->lines - 1)
823 posRet = ll->numCharsBeforeEOL + posLineStart;
824 else
825 posRet = model.pdoc->MovePositionOutsideChar(ll->LineStart(subLine + 1) + posLineStart - 1, -1, false);
831 return posRet;
834 namespace {
836 constexpr ColourRGBA colourBug(0xff, 0, 0xfe, 0xf0);
838 // Selection background colours are always defined, the value_or is to show if bug
840 ColourRGBA SelectionBackground(const EditModel &model, const ViewStyle &vsDraw, InSelection inSelection) {
841 if (inSelection == InSelection::inNone)
842 return colourBug; // Not selected is a bug
844 Element element = Element::SelectionBack;
845 if (inSelection == InSelection::inAdditional)
846 element = Element::SelectionAdditionalBack;
847 if (!model.primarySelection)
848 element = Element::SelectionSecondaryBack;
849 if (!model.hasFocus) {
850 if (inSelection == InSelection::inAdditional) {
851 if (ColourOptional colour = vsDraw.ElementColour(Element::SelectionInactiveAdditionalBack)) {
852 return *colour;
855 if (ColourOptional colour = vsDraw.ElementColour(Element::SelectionInactiveBack)) {
856 return *colour;
859 return vsDraw.ElementColour(element).value_or(colourBug);
862 ColourOptional SelectionForeground(const EditModel &model, const ViewStyle &vsDraw, InSelection inSelection) {
863 if (inSelection == InSelection::inNone)
864 return {};
865 Element element = Element::SelectionText;
866 if (inSelection == InSelection::inAdditional)
867 element = Element::SelectionAdditionalText;
868 if (!model.primarySelection) // Secondary selection
869 element = Element::SelectionSecondaryText;
870 if (!model.hasFocus) {
871 if (inSelection == InSelection::inAdditional) {
872 if (ColourOptional colour = vsDraw.ElementColour(Element::SelectionInactiveAdditionalText)) {
873 return colour;
876 element = Element::SelectionInactiveText;
878 return vsDraw.ElementColour(element);
881 ColourRGBA TextBackground(const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
882 ColourOptional background, InSelection inSelection, bool inHotspot, int styleMain, Sci::Position i) {
883 if (inSelection && (vsDraw.selection.layer == Layer::Base)) {
884 return SelectionBackground(model, vsDraw, inSelection).Opaque();
886 if ((vsDraw.edgeState == EdgeVisualStyle::Background) &&
887 (i >= ll->edgeColumn) &&
888 (i < ll->numCharsBeforeEOL))
889 return vsDraw.theEdge.colour;
890 if (inHotspot) {
891 if (const ColourOptional colourHotSpotBack = vsDraw.ElementColour(Element::HotSpotActiveBack)) {
892 return colourHotSpotBack->Opaque();
895 if (background && (styleMain != StyleBraceLight) && (styleMain != StyleBraceBad)) {
896 return *background;
897 } else {
898 return vsDraw.styles[styleMain].back;
902 void DrawTextBlob(Surface *surface, const ViewStyle &vsDraw, PRectangle rcSegment,
903 std::string_view text, ColourRGBA textBack, ColourRGBA textFore, bool fillBackground) {
904 if (rcSegment.Empty())
905 return;
906 if (fillBackground) {
907 surface->FillRectangleAligned(rcSegment, Fill(textBack));
909 const Font *ctrlCharsFont = vsDraw.styles[StyleControlChar].font.get();
910 const int normalCharHeight = static_cast<int>(std::ceil(vsDraw.styles[StyleControlChar].capitalHeight));
911 PRectangle rcCChar = rcSegment;
912 rcCChar.left = rcCChar.left + 1;
913 rcCChar.top = rcSegment.top + vsDraw.maxAscent - normalCharHeight;
914 rcCChar.bottom = rcSegment.top + vsDraw.maxAscent + 1;
915 PRectangle rcCentral = rcCChar;
916 rcCentral.top++;
917 rcCentral.bottom--;
918 surface->FillRectangleAligned(rcCentral, Fill(textFore));
919 PRectangle rcChar = rcCChar;
920 rcChar.left++;
921 rcChar.right--;
922 surface->DrawTextClippedUTF8(rcChar, ctrlCharsFont,
923 rcSegment.top + vsDraw.maxAscent, text,
924 textBack, textFore);
927 void FillLineRemainder(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
928 Sci::Line line, PRectangle rcArea, int subLine) {
929 InSelection eolInSelection = InSelection::inNone;
930 if (vsDraw.selection.visible && (subLine == (ll->lines - 1))) {
931 eolInSelection = model.LineEndInSelection(line);
934 if (eolInSelection && vsDraw.selection.eolFilled && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer == Layer::Base)) {
935 surface->FillRectangleAligned(rcArea, Fill(SelectionBackground(model, vsDraw, eolInSelection).Opaque()));
936 } else {
937 const ColourOptional background = vsDraw.Background(model.GetMark(line), model.caret.active, ll->containsCaret);
938 if (background) {
939 surface->FillRectangleAligned(rcArea, Fill(*background));
940 } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) {
941 surface->FillRectangleAligned(rcArea, Fill(vsDraw.styles[ll->styles[ll->numCharsInLine]].back));
942 } else {
943 surface->FillRectangleAligned(rcArea, Fill(vsDraw.styles[StyleDefault].back));
945 if (eolInSelection && vsDraw.selection.eolFilled && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer != Layer::Base)) {
946 surface->FillRectangleAligned(rcArea, SelectionBackground(model, vsDraw, eolInSelection));
953 void EditView::DrawEOL(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
954 Sci::Line line, int xStart, PRectangle rcLine, int subLine, Sci::Position lineEnd, XYPOSITION subLineStart, ColourOptional background) {
956 const Sci::Position posLineStart = model.pdoc->LineStart(line);
957 PRectangle rcSegment = rcLine;
959 const bool lastSubLine = subLine == (ll->lines - 1);
960 XYPOSITION virtualSpace = 0;
961 if (lastSubLine) {
962 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
963 virtualSpace = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)) * spaceWidth;
965 const XYPOSITION xEol = ll->positions[lineEnd] - subLineStart;
967 // Fill the virtual space and show selections within it
968 if (virtualSpace > 0.0f) {
969 rcSegment.left = xEol + xStart;
970 rcSegment.right = xEol + xStart + virtualSpace;
971 const ColourRGBA backgroundFill = background.value_or(vsDraw.styles[ll->styles[ll->numCharsInLine]].back);
972 surface->FillRectangleAligned(rcSegment, backgroundFill);
973 if (vsDraw.selection.visible && (vsDraw.selection.layer == Layer::Base)) {
974 const SelectionSegment virtualSpaceRange(SelectionPosition(model.pdoc->LineEnd(line)),
975 SelectionPosition(model.pdoc->LineEnd(line),
976 model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line))));
977 for (size_t r = 0; r<model.sel.Count(); r++) {
978 const SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange);
979 if (!portion.Empty()) {
980 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
981 rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] -
982 subLineStart+portion.start.VirtualSpace() * spaceWidth;
983 rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] -
984 subLineStart+portion.end.VirtualSpace() * spaceWidth;
985 rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left;
986 rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right;
987 surface->FillRectangleAligned(rcSegment, Fill(
988 SelectionBackground(model, vsDraw, model.sel.RangeType(r)).Opaque()));
994 InSelection eolInSelection = InSelection::inNone;
995 if (vsDraw.selection.visible && lastSubLine) {
996 eolInSelection = model.LineEndInSelection(line);
999 const ColourRGBA selectionBack = SelectionBackground(model, vsDraw, eolInSelection);
1001 // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on
1002 XYPOSITION blobsWidth = 0;
1003 if (lastSubLine) {
1004 for (Sci::Position eolPos = ll->numCharsBeforeEOL; eolPos<ll->numCharsInLine;) {
1005 const int styleMain = ll->styles[eolPos];
1006 const ColourOptional selectionFore = SelectionForeground(model, vsDraw, eolInSelection);
1007 ColourRGBA textFore = selectionFore.value_or(vsDraw.styles[styleMain].fore);
1008 char hexits[4] = "";
1009 std::string_view ctrlChar;
1010 Sci::Position widthBytes = 1;
1011 RepresentationAppearance appearance = RepresentationAppearance::Blob;
1012 const Representation *repr = model.reprs.RepresentationFromCharacter(std::string_view(&ll->chars[eolPos], ll->numCharsInLine - eolPos));
1013 if (repr) {
1014 // Representation of whole text
1015 widthBytes = ll->numCharsInLine - eolPos;
1016 } else {
1017 repr = model.reprs.RepresentationFromCharacter(std::string_view(&ll->chars[eolPos], 1));
1019 if (repr) {
1020 ctrlChar = repr->stringRep;
1021 appearance = repr->appearance;
1022 if (FlagSet(appearance, RepresentationAppearance::Colour)) {
1023 textFore = repr->colour;
1025 } else {
1026 const unsigned char chEOL = ll->chars[eolPos];
1027 if (UTF8IsAscii(chEOL)) {
1028 ctrlChar = ControlCharacterString(chEOL);
1029 } else {
1030 Hexits(hexits, chEOL);
1031 ctrlChar = hexits;
1035 rcSegment.left = xStart + ll->positions[eolPos] - subLineStart + virtualSpace;
1036 rcSegment.right = xStart + ll->positions[eolPos + widthBytes] - subLineStart + virtualSpace;
1037 blobsWidth += rcSegment.Width();
1038 const ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, eolInSelection, false, styleMain, eolPos);
1039 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1)) {
1040 if (vsDraw.selection.layer == Layer::Base) {
1041 surface->FillRectangleAligned(rcSegment, Fill(selectionBack.Opaque()));
1042 } else {
1043 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1045 } else {
1046 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1048 const bool drawEOLSelection = eolInSelection && (line < model.pdoc->LinesTotal() - 1);
1049 ColourRGBA blobText = textBack;
1050 if (drawEOLSelection && (vsDraw.selection.layer == Layer::UnderText)) {
1051 surface->FillRectangleAligned(rcSegment, selectionBack);
1052 blobText = textBack.MixedWith(selectionBack, selectionBack.GetAlphaComponent());
1054 if (FlagSet(appearance, RepresentationAppearance::Blob)) {
1055 DrawTextBlob(surface, vsDraw, rcSegment, ctrlChar, blobText, textFore, phasesDraw == PhasesDraw::One);
1056 } else {
1057 surface->DrawTextTransparentUTF8(rcSegment, vsDraw.styles[StyleControlChar].font.get(),
1058 rcSegment.top + vsDraw.maxAscent, ctrlChar, textFore);
1060 if (drawEOLSelection && (vsDraw.selection.layer == Layer::OverText)) {
1061 surface->FillRectangleAligned(rcSegment, selectionBack);
1063 eolPos += widthBytes;
1067 // Draw the eol-is-selected rectangle
1068 rcSegment.left = xEol + xStart + virtualSpace + blobsWidth;
1069 rcSegment.right = rcSegment.left + vsDraw.aveCharWidth;
1071 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer == Layer::Base)) {
1072 surface->FillRectangleAligned(rcSegment, Fill(selectionBack.Opaque()));
1073 } else {
1074 if (background) {
1075 surface->FillRectangleAligned(rcSegment, Fill(*background));
1076 } else if (line < model.pdoc->LinesTotal() - 1) {
1077 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[ll->styles[ll->numCharsInLine]].back));
1078 } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) {
1079 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[ll->styles[ll->numCharsInLine]].back));
1080 } else {
1081 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[StyleDefault].back));
1083 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer != Layer::Base)) {
1084 surface->FillRectangleAligned(rcSegment, selectionBack);
1088 rcSegment.left = rcSegment.right;
1089 if (rcSegment.left < rcLine.left)
1090 rcSegment.left = rcLine.left;
1091 rcSegment.right = rcLine.right;
1093 const bool drawEOLAnnotationStyledText = (vsDraw.eolAnnotationVisible != EOLAnnotationVisible::Hidden) && model.pdoc->EOLAnnotationStyledText(line).text;
1094 const bool fillRemainder = (!lastSubLine || (!model.GetFoldDisplayText(line) && !drawEOLAnnotationStyledText));
1095 if (fillRemainder) {
1096 // Fill the remainder of the line
1097 FillLineRemainder(surface, model, vsDraw, ll, line, rcSegment, subLine);
1100 bool drawWrapMarkEnd = false;
1102 if (subLine + 1 < ll->lines) {
1103 if (FlagSet(vsDraw.wrap.visualFlags, WrapVisualFlag::End)) {
1104 drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0;
1106 if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret)) {
1107 // Draw right of frame under marker
1108 surface->FillRectangleAligned(Side(rcLine, Edge::right, vsDraw.GetFrameWidth()),
1109 vsDraw.ElementColourForced(Element::CaretLineBack).Opaque());
1113 if (drawWrapMarkEnd) {
1114 PRectangle rcPlace = rcSegment;
1115 const XYPOSITION maxLeft = rcPlace.right - vsDraw.aveCharWidth;
1117 if (FlagSet(vsDraw.wrap.visualFlagsLocation, WrapVisualLocation::EndByText)) {
1118 rcPlace.left = std::min(xEol + xStart + virtualSpace, maxLeft);
1119 rcPlace.right = rcPlace.left + vsDraw.aveCharWidth;
1120 } else {
1121 // rcLine is clipped to text area
1122 rcPlace.right = rcLine.right;
1123 rcPlace.left = maxLeft;
1125 if (!customDrawWrapMarker) {
1126 DrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour());
1127 } else {
1128 customDrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour());
1133 void EditView::DrawFoldDisplayText(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1134 Sci::Line line, int xStart, PRectangle rcLine, int subLine, XYPOSITION subLineStart, DrawPhase phase) {
1135 const bool lastSubLine = subLine == (ll->lines - 1);
1136 if (!lastSubLine)
1137 return;
1139 const char *text = model.GetFoldDisplayText(line);
1140 if (!text)
1141 return;
1143 PRectangle rcSegment = rcLine;
1144 const std::string_view foldDisplayText(text);
1145 const Font *fontText = vsDraw.styles[StyleFoldDisplayText].font.get();
1146 const int widthFoldDisplayText = static_cast<int>(surface->WidthText(fontText, foldDisplayText));
1148 InSelection eolInSelection = InSelection::inNone;
1149 if (vsDraw.selection.visible) {
1150 eolInSelection = model.LineEndInSelection(line);
1153 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1154 const XYPOSITION virtualSpace = model.sel.VirtualSpaceFor(
1155 model.pdoc->LineEnd(line)) * spaceWidth;
1156 rcSegment.left = xStart + ll->positions[ll->numCharsInLine] - subLineStart + virtualSpace + vsDraw.aveCharWidth;
1157 rcSegment.right = rcSegment.left + static_cast<XYPOSITION>(widthFoldDisplayText);
1159 const ColourOptional background = vsDraw.Background(model.GetMark(line), model.caret.active, ll->containsCaret);
1160 const ColourOptional selectionFore = SelectionForeground(model, vsDraw, eolInSelection);
1161 const ColourRGBA textFore = selectionFore.value_or(vsDraw.styles[StyleFoldDisplayText].fore);
1162 const ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, eolInSelection,
1163 false, StyleFoldDisplayText, -1);
1165 if (model.trackLineWidth) {
1166 if (rcSegment.right + 1> lineWidthMaxSeen) {
1167 // Fold display text border drawn on rcSegment.right with width 1 is the last visible object of the line
1168 lineWidthMaxSeen = static_cast<int>(rcSegment.right + 1);
1172 if (FlagSet(phase, DrawPhase::back)) {
1173 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1175 // Fill Remainder of the line
1176 PRectangle rcRemainder = rcSegment;
1177 rcRemainder.left = rcRemainder.right;
1178 if (rcRemainder.left < rcLine.left)
1179 rcRemainder.left = rcLine.left;
1180 rcRemainder.right = rcLine.right;
1181 FillLineRemainder(surface, model, vsDraw, ll, line, rcRemainder, subLine);
1184 if (FlagSet(phase, DrawPhase::text)) {
1185 if (phasesDraw != PhasesDraw::One) {
1186 surface->DrawTextTransparent(rcSegment, fontText,
1187 rcSegment.top + vsDraw.maxAscent, foldDisplayText,
1188 textFore);
1189 } else {
1190 surface->DrawTextNoClip(rcSegment, fontText,
1191 rcSegment.top + vsDraw.maxAscent, foldDisplayText,
1192 textFore, textBack);
1196 if (FlagSet(phase, DrawPhase::indicatorsFore)) {
1197 if (model.foldDisplayTextStyle == FoldDisplayTextStyle::Boxed) {
1198 PRectangle rcBox = rcSegment;
1199 rcBox.left = std::round(rcSegment.left);
1200 rcBox.right = std::round(rcSegment.right);
1201 surface->RectangleFrame(rcBox, Stroke(textFore));
1205 if (FlagSet(phase, DrawPhase::selectionTranslucent)) {
1206 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer != Layer::Base)) {
1207 surface->FillRectangleAligned(rcSegment, SelectionBackground(model, vsDraw, eolInSelection));
1212 void EditView::DrawEOLAnnotationText(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1213 Sci::Line line, int xStart, PRectangle rcLine, int subLine, XYPOSITION subLineStart, DrawPhase phase) {
1215 const bool lastSubLine = subLine == (ll->lines - 1);
1216 if (!lastSubLine)
1217 return;
1219 if (vsDraw.eolAnnotationVisible == EOLAnnotationVisible::Hidden) {
1220 return;
1222 const StyledText stEOLAnnotation = model.pdoc->EOLAnnotationStyledText(line);
1223 if (!stEOLAnnotation.text || !ValidStyledText(vsDraw, vsDraw.eolAnnotationStyleOffset, stEOLAnnotation)) {
1224 return;
1226 const std::string_view eolAnnotationText(stEOLAnnotation.text, stEOLAnnotation.length);
1227 const size_t style = stEOLAnnotation.style + vsDraw.eolAnnotationStyleOffset;
1229 PRectangle rcSegment = rcLine;
1230 const Font *fontText = vsDraw.styles[style].font.get();
1232 const Surface::Ends ends = static_cast<Surface::Ends>(static_cast<int>(vsDraw.eolAnnotationVisible) & 0xff);
1233 const Surface::Ends leftSide = static_cast<Surface::Ends>(static_cast<int>(ends) & 0xf);
1234 const Surface::Ends rightSide = static_cast<Surface::Ends>(static_cast<int>(ends) & 0xf0);
1236 XYPOSITION leftBoxSpace = 0;
1237 XYPOSITION rightBoxSpace = 0;
1238 if (vsDraw.eolAnnotationVisible >= EOLAnnotationVisible::Boxed) {
1239 leftBoxSpace = 1;
1240 rightBoxSpace = 1;
1241 if (vsDraw.eolAnnotationVisible != EOLAnnotationVisible::Boxed) {
1242 switch (leftSide) {
1243 case Surface::Ends::leftFlat:
1244 leftBoxSpace = 1;
1245 break;
1246 case Surface::Ends::leftAngle:
1247 leftBoxSpace = rcLine.Height() / 2.0;
1248 break;
1249 case Surface::Ends::semiCircles:
1250 default:
1251 leftBoxSpace = rcLine.Height() / 3.0;
1252 break;
1254 switch (rightSide) {
1255 case Surface::Ends::rightFlat:
1256 rightBoxSpace = 1;
1257 break;
1258 case Surface::Ends::rightAngle:
1259 rightBoxSpace = rcLine.Height() / 2.0;
1260 break;
1261 case Surface::Ends::semiCircles:
1262 default:
1263 rightBoxSpace = rcLine.Height() / 3.0;
1264 break;
1268 const int widthEOLAnnotationText = static_cast<int>(surface->WidthTextUTF8(fontText, eolAnnotationText) +
1269 leftBoxSpace + rightBoxSpace);
1271 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1272 const XYPOSITION virtualSpace = model.sel.VirtualSpaceFor(
1273 model.pdoc->LineEnd(line)) * spaceWidth;
1274 rcSegment.left = xStart +
1275 ll->positions[ll->numCharsInLine] - subLineStart
1276 + virtualSpace + vsDraw.aveCharWidth;
1278 const char *textFoldDisplay = model.GetFoldDisplayText(line);
1279 if (textFoldDisplay) {
1280 const std::string_view foldDisplayText(textFoldDisplay);
1281 rcSegment.left += static_cast<int>(
1282 surface->WidthText(vsDraw.styles[StyleFoldDisplayText].font.get(), foldDisplayText)) +
1283 vsDraw.aveCharWidth;
1285 rcSegment.right = rcSegment.left + static_cast<XYPOSITION>(widthEOLAnnotationText);
1287 const ColourOptional background = vsDraw.Background(model.GetMark(line), model.caret.active, ll->containsCaret);
1288 const ColourRGBA textFore = vsDraw.styles[style].fore;
1289 const ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, InSelection::inNone,
1290 false, static_cast<int>(style), -1);
1292 if (model.trackLineWidth) {
1293 if (rcSegment.right + 1> lineWidthMaxSeen) {
1294 // EOL Annotation text border drawn on rcSegment.right with width 1 is the last visible object of the line
1295 lineWidthMaxSeen = static_cast<int>(rcSegment.right + 1);
1299 if (FlagSet(phase, DrawPhase::back)) {
1300 // This fills in the whole remainder of the line even though
1301 // it may be double drawing. This is to allow stadiums with
1302 // curved or angled ends to have the area outside in the correct
1303 // background colour.
1304 PRectangle rcRemainder = rcSegment;
1305 rcRemainder.right = rcLine.right;
1306 FillLineRemainder(surface, model, vsDraw, ll, line, rcRemainder, subLine);
1309 PRectangle rcText = rcSegment;
1310 rcText.left += leftBoxSpace;
1311 rcText.right -= rightBoxSpace;
1313 // For single phase drawing, draw the text then any box over it
1314 if (FlagSet(phase, DrawPhase::text)) {
1315 if (phasesDraw == PhasesDraw::One) {
1316 surface->DrawTextNoClipUTF8(rcText, fontText,
1317 rcText.top + vsDraw.maxAscent, eolAnnotationText,
1318 textFore, textBack);
1322 // Draw any box or stadium shape
1323 if (FlagSet(phase, DrawPhase::indicatorsBack)) {
1324 const PRectangle rcBox = PixelAlign(rcSegment, 1);
1326 switch (vsDraw.eolAnnotationVisible) {
1327 case EOLAnnotationVisible::Standard:
1328 if (phasesDraw != PhasesDraw::One) {
1329 surface->FillRectangle(rcBox, textBack);
1331 break;
1333 case EOLAnnotationVisible::Boxed:
1334 if (phasesDraw == PhasesDraw::One) {
1335 // Draw a rectangular outline around the text
1336 surface->RectangleFrame(rcBox, textFore);
1337 } else {
1338 // Draw with a fill to fill the edges of the rectangle.
1339 surface->RectangleDraw(rcBox, FillStroke(textBack, textFore));
1341 break;
1343 default:
1344 if (phasesDraw == PhasesDraw::One) {
1345 // Draw an outline around the text
1346 surface->Stadium(rcBox, FillStroke(ColourRGBA(textBack, 0), textFore), ends);
1347 } else {
1348 // Draw with a fill to fill the edges of the shape.
1349 surface->Stadium(rcBox, FillStroke(textBack, textFore), ends);
1351 break;
1355 // For multi-phase drawing draw the text last as transparent over any box
1356 if (FlagSet(phase, DrawPhase::text)) {
1357 if (phasesDraw != PhasesDraw::One) {
1358 surface->DrawTextTransparentUTF8(rcText, fontText,
1359 rcText.top + vsDraw.maxAscent, eolAnnotationText,
1360 textFore);
1365 namespace {
1367 constexpr bool AnnotationBoxedOrIndented(AnnotationVisible annotationVisible) noexcept {
1368 return annotationVisible == AnnotationVisible::Boxed || annotationVisible == AnnotationVisible::Indented;
1373 void EditView::DrawAnnotation(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1374 Sci::Line line, int xStart, PRectangle rcLine, int subLine, DrawPhase phase) {
1375 const int indent = static_cast<int>(model.pdoc->GetLineIndentation(line) * vsDraw.spaceWidth);
1376 PRectangle rcSegment = rcLine;
1377 const int annotationLine = subLine - ll->lines;
1378 const StyledText stAnnotation = model.pdoc->AnnotationStyledText(line);
1379 if (stAnnotation.text && ValidStyledText(vsDraw, vsDraw.annotationStyleOffset, stAnnotation)) {
1380 if (FlagSet(phase, DrawPhase::back)) {
1381 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[0].back));
1383 rcSegment.left = static_cast<XYPOSITION>(xStart);
1384 if (model.trackLineWidth || AnnotationBoxedOrIndented(vsDraw.annotationVisible)) {
1385 // Only care about calculating width if tracking or need to draw indented box
1386 int widthAnnotation = WidestLineWidth(surface, vsDraw, vsDraw.annotationStyleOffset, stAnnotation);
1387 if (AnnotationBoxedOrIndented(vsDraw.annotationVisible)) {
1388 widthAnnotation += static_cast<int>(vsDraw.spaceWidth * 2); // Margins
1389 rcSegment.left = static_cast<XYPOSITION>(xStart + indent);
1390 rcSegment.right = rcSegment.left + widthAnnotation;
1392 if (widthAnnotation > lineWidthMaxSeen)
1393 lineWidthMaxSeen = widthAnnotation;
1395 const int annotationLines = model.pdoc->AnnotationLines(line);
1396 size_t start = 0;
1397 size_t lengthAnnotation = stAnnotation.LineLength(start);
1398 int lineInAnnotation = 0;
1399 while ((lineInAnnotation < annotationLine) && (start < stAnnotation.length)) {
1400 start += lengthAnnotation + 1;
1401 lengthAnnotation = stAnnotation.LineLength(start);
1402 lineInAnnotation++;
1404 PRectangle rcText = rcSegment;
1405 if ((FlagSet(phase, DrawPhase::back)) && AnnotationBoxedOrIndented(vsDraw.annotationVisible)) {
1406 surface->FillRectangleAligned(rcText,
1407 Fill(vsDraw.styles[stAnnotation.StyleAt(start) + vsDraw.annotationStyleOffset].back));
1408 rcText.left += vsDraw.spaceWidth;
1410 DrawStyledText(surface, vsDraw, vsDraw.annotationStyleOffset, rcText,
1411 stAnnotation, start, lengthAnnotation, phase);
1412 if ((FlagSet(phase, DrawPhase::back)) && (vsDraw.annotationVisible == AnnotationVisible::Boxed)) {
1413 const ColourRGBA colourBorder = vsDraw.styles[vsDraw.annotationStyleOffset].fore;
1414 const PRectangle rcBorder = PixelAlignOutside(rcSegment, surface->PixelDivisions());
1415 surface->FillRectangle(Side(rcBorder, Edge::left, 1), colourBorder);
1416 surface->FillRectangle(Side(rcBorder, Edge::right, 1), colourBorder);
1417 if (subLine == ll->lines) {
1418 surface->FillRectangle(Side(rcBorder, Edge::top, 1), colourBorder);
1420 if (subLine == ll->lines + annotationLines - 1) {
1421 surface->FillRectangle(Side(rcBorder, Edge::bottom, 1), colourBorder);
1424 } else {
1425 // No annotation to draw so show bug with colourBug
1426 if (FlagSet(phase, DrawPhase::back)) {
1427 surface->FillRectangle(rcSegment, colourBug.Opaque());
1432 namespace {
1434 void DrawBlockCaret(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1435 int subLine, int xStart, Sci::Position offset, Sci::Position posCaret, PRectangle rcCaret, ColourRGBA caretColour) {
1437 const Sci::Position lineStart = ll->LineStart(subLine);
1438 Sci::Position posBefore = posCaret;
1439 Sci::Position posAfter = model.pdoc->MovePositionOutsideChar(posCaret + 1, 1);
1440 Sci::Position numCharsToDraw = posAfter - posCaret;
1442 // Work out where the starting and ending offsets are. We need to
1443 // see if the previous character shares horizontal space, such as a
1444 // glyph / combining character. If so we'll need to draw that too.
1445 Sci::Position offsetFirstChar = offset;
1446 Sci::Position offsetLastChar = offset + (posAfter - posCaret);
1447 while ((posBefore > 0) && ((offsetLastChar - numCharsToDraw) >= lineStart)) {
1448 if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - numCharsToDraw]) > 0) {
1449 // The char does not share horizontal space
1450 break;
1452 // Char shares horizontal space, update the numChars to draw
1453 // Update posBefore to point to the prev char
1454 posBefore = model.pdoc->MovePositionOutsideChar(posBefore - 1, -1);
1455 numCharsToDraw = posAfter - posBefore;
1456 offsetFirstChar = offset - (posCaret - posBefore);
1459 // See if the next character shares horizontal space, if so we'll
1460 // need to draw that too.
1461 if (offsetFirstChar < 0)
1462 offsetFirstChar = 0;
1463 numCharsToDraw = offsetLastChar - offsetFirstChar;
1464 while ((offsetLastChar < ll->LineStart(subLine + 1)) && (offsetLastChar <= ll->numCharsInLine)) {
1465 // Update posAfter to point to the 2nd next char, this is where
1466 // the next character ends, and 2nd next begins. We'll need
1467 // to compare these two
1468 posBefore = posAfter;
1469 posAfter = model.pdoc->MovePositionOutsideChar(posAfter + 1, 1);
1470 offsetLastChar = offset + (posAfter - posCaret);
1471 if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - (posAfter - posBefore)]) > 0) {
1472 // The char does not share horizontal space
1473 break;
1475 // Char shares horizontal space, update the numChars to draw
1476 numCharsToDraw = offsetLastChar - offsetFirstChar;
1479 // We now know what to draw, update the caret drawing rectangle
1480 rcCaret.left = ll->positions[offsetFirstChar] - ll->positions[lineStart] + xStart;
1481 rcCaret.right = ll->positions[offsetFirstChar + numCharsToDraw] - ll->positions[lineStart] + xStart;
1483 // Adjust caret position to take into account any word wrapping symbols.
1484 if ((ll->wrapIndent != 0) && (lineStart != 0)) {
1485 const XYPOSITION wordWrapCharWidth = ll->wrapIndent;
1486 rcCaret.left += wordWrapCharWidth;
1487 rcCaret.right += wordWrapCharWidth;
1490 // This character is where the caret block is, we override the colours
1491 // (inversed) for drawing the caret here.
1492 const int styleMain = ll->styles[offsetFirstChar];
1493 const Font *fontText = vsDraw.styles[styleMain].font.get();
1494 const std::string_view text(&ll->chars[offsetFirstChar], numCharsToDraw);
1495 surface->DrawTextClipped(rcCaret, fontText,
1496 rcCaret.top + vsDraw.maxAscent, text, vsDraw.styles[styleMain].back,
1497 caretColour);
1502 void EditView::DrawCarets(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1503 Sci::Line lineDoc, int xStart, PRectangle rcLine, int subLine) const {
1504 // When drag is active it is the only caret drawn
1505 const bool drawDrag = model.posDrag.IsValid();
1506 if (!vsDraw.selection.visible && !drawDrag)
1507 return;
1508 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
1509 // For each selection draw
1510 for (size_t r = 0; (r<model.sel.Count()) || drawDrag; r++) {
1511 const bool mainCaret = r == model.sel.Main();
1512 SelectionPosition posCaret = (drawDrag ? model.posDrag : model.sel.Range(r).caret);
1513 if ((vsDraw.DrawCaretInsideSelection(model.inOverstrike, imeCaretBlockOverride)) &&
1514 !drawDrag &&
1515 posCaret > model.sel.Range(r).anchor) {
1516 if (posCaret.VirtualSpace() > 0)
1517 posCaret.SetVirtualSpace(posCaret.VirtualSpace() - 1);
1518 else
1519 posCaret.SetPosition(model.pdoc->MovePositionOutsideChar(posCaret.Position()-1, -1));
1521 const int offset = static_cast<int>(posCaret.Position() - posLineStart);
1522 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1523 const XYPOSITION virtualOffset = posCaret.VirtualSpace() * spaceWidth;
1524 if (ll->InLine(offset, subLine) && offset <= ll->numCharsBeforeEOL) {
1525 XYPOSITION xposCaret = ll->positions[offset] + virtualOffset - ll->positions[ll->LineStart(subLine)];
1526 if (model.BidirectionalEnabled() && (posCaret.VirtualSpace() == 0)) {
1527 // Get caret point
1528 const ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right, tabWidthMinimumPixels);
1530 const int caretPosition = offset - ll->LineStart(subLine);
1532 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
1533 const XYPOSITION caretLeft = slLayout->XFromPosition(caretPosition);
1535 // In case of start of line, the cursor should be at the right
1536 xposCaret = caretLeft + virtualOffset;
1538 if (ll->wrapIndent != 0) {
1539 const Sci::Position lineStart = ll->LineStart(subLine);
1540 if (lineStart != 0) // Wrapped
1541 xposCaret += ll->wrapIndent;
1543 const bool caretBlinkState = (model.caret.active && model.caret.on) || (!additionalCaretsBlink && !mainCaret);
1544 const bool caretVisibleState = additionalCaretsVisible || mainCaret;
1545 if ((xposCaret >= 0) && vsDraw.IsCaretVisible(mainCaret) &&
1546 (drawDrag || (caretBlinkState && caretVisibleState))) {
1547 bool canDrawBlockCaret = true;
1548 bool drawBlockCaret = false;
1549 XYPOSITION widthOverstrikeCaret;
1550 XYPOSITION caretWidthOffset = 0;
1551 PRectangle rcCaret = rcLine;
1553 if (posCaret.Position() == model.pdoc->Length()) { // At end of document
1554 canDrawBlockCaret = false;
1555 widthOverstrikeCaret = vsDraw.aveCharWidth;
1556 } else if ((posCaret.Position() - posLineStart) >= ll->numCharsInLine) { // At end of line
1557 canDrawBlockCaret = false;
1558 widthOverstrikeCaret = vsDraw.aveCharWidth;
1559 } else {
1560 const int widthChar = model.pdoc->LenChar(posCaret.Position());
1561 widthOverstrikeCaret = ll->positions[offset + widthChar] - ll->positions[offset];
1563 if (widthOverstrikeCaret < 3) // Make sure its visible
1564 widthOverstrikeCaret = 3;
1566 if (xposCaret > 0)
1567 caretWidthOffset = 0.51f; // Move back so overlaps both character cells.
1568 xposCaret += xStart;
1569 const ViewStyle::CaretShape caretShape = drawDrag ? ViewStyle::CaretShape::line :
1570 vsDraw.CaretShapeForMode(model.inOverstrike, mainCaret);
1571 if (drawDrag) {
1572 /* Dragging text, use a line caret */
1573 rcCaret.left = std::round(xposCaret - caretWidthOffset);
1574 rcCaret.right = rcCaret.left + vsDraw.caret.width;
1575 } else if ((caretShape == ViewStyle::CaretShape::bar) && drawOverstrikeCaret) {
1576 /* Over-strike (insert mode), use a modified bar caret */
1577 rcCaret.top = rcCaret.bottom - 2;
1578 rcCaret.left = xposCaret + 1;
1579 rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1;
1580 } else if ((caretShape == ViewStyle::CaretShape::block) || imeCaretBlockOverride) {
1581 /* Block caret */
1582 rcCaret.left = xposCaret;
1583 if (canDrawBlockCaret && !(IsControl(ll->chars[offset]))) {
1584 drawBlockCaret = true;
1585 rcCaret.right = xposCaret + widthOverstrikeCaret;
1586 } else {
1587 rcCaret.right = xposCaret + vsDraw.aveCharWidth;
1589 } else {
1590 /* Line caret */
1591 rcCaret.left = std::round(xposCaret - caretWidthOffset);
1592 rcCaret.right = rcCaret.left + vsDraw.caret.width;
1594 const Element elementCaret = mainCaret ? Element::Caret : Element::CaretAdditional;
1595 const ColourRGBA caretColour = vsDraw.ElementColourForced(elementCaret);
1596 //assert(caretColour.IsOpaque());
1597 if (drawBlockCaret) {
1598 DrawBlockCaret(surface, model, vsDraw, ll, subLine, xStart, offset, posCaret.Position(), rcCaret, caretColour);
1599 } else {
1600 surface->FillRectangleAligned(rcCaret, Fill(caretColour));
1604 if (drawDrag)
1605 break;
1609 namespace {
1611 void DrawWrapIndentAndMarker(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll,
1612 int xStart, PRectangle rcLine, ColourOptional background, DrawWrapMarkerFn customDrawWrapMarker,
1613 bool caretActive) {
1614 // default background here..
1615 surface->FillRectangleAligned(rcLine, Fill(background.value_or(vsDraw.styles[StyleDefault].back)));
1617 if (vsDraw.IsLineFrameOpaque(caretActive, ll->containsCaret)) {
1618 // Draw left of frame under marker
1619 surface->FillRectangleAligned(Side(rcLine, Edge::left, vsDraw.GetFrameWidth()),
1620 vsDraw.ElementColourForced(Element::CaretLineBack).Opaque());
1623 if (FlagSet(vsDraw.wrap.visualFlags, WrapVisualFlag::Start)) {
1625 // draw continuation rect
1626 PRectangle rcPlace = rcLine;
1628 rcPlace.left = static_cast<XYPOSITION>(xStart);
1629 rcPlace.right = rcPlace.left + ll->wrapIndent;
1631 if (FlagSet(vsDraw.wrap.visualFlagsLocation, WrapVisualLocation::StartByText))
1632 rcPlace.left = rcPlace.right - vsDraw.aveCharWidth;
1633 else
1634 rcPlace.right = rcPlace.left + vsDraw.aveCharWidth;
1636 if (!customDrawWrapMarker) {
1637 DrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour());
1638 } else {
1639 customDrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour());
1644 // On the curses platform, the terminal is drawing its own caret, so if the caret is within
1645 // the main selection, do not draw the selection at that position.
1646 // Use iDoc from DrawBackground and DrawForeground here because TextSegment has been adjusted
1647 // such that, if the caret is inside the main selection, the beginning or end of that selection
1648 // is at the end of a text segment.
1649 // This function should only be called if iDoc is within the main selection.
1650 InSelection CharacterInCursesSelection(Sci::Position iDoc, const EditModel &model, const ViewStyle &vsDraw) noexcept {
1651 const SelectionPosition &posCaret = model.sel.RangeMain().caret;
1652 const bool caretAtStart = posCaret < model.sel.RangeMain().anchor && posCaret.Position() == iDoc;
1653 const bool caretAtEnd = posCaret > model.sel.RangeMain().anchor &&
1654 vsDraw.DrawCaretInsideSelection(false, false) &&
1655 model.pdoc->MovePositionOutsideChar(posCaret.Position() - 1, -1) == iDoc;
1656 return (caretAtStart || caretAtEnd) ? InSelection::inNone : InSelection::inMain;
1659 void DrawBackground(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1660 int xStart, PRectangle rcLine, int subLine, Range lineRange, Sci::Position posLineStart,
1661 ColourOptional background) {
1663 const bool selBackDrawn = vsDraw.SelectionBackgroundDrawn();
1664 bool inIndentation = subLine == 0; // Do not handle indentation except on first subline.
1665 const XYPOSITION subLineStart = ll->positions[lineRange.start];
1666 const XYPOSITION horizontalOffset = xStart - subLineStart;
1667 // Does not take margin into account but not significant
1668 const XYPOSITION xStartVisible = subLineStart - xStart;
1670 const BreakFinder::BreakFor breakFor = selBackDrawn ? BreakFinder::BreakFor::Selection : BreakFinder::BreakFor::Text;
1671 BreakFinder bfBack(ll, &model.sel, lineRange, posLineStart, xStartVisible, breakFor, model.pdoc, &model.reprs, &vsDraw);
1673 const bool drawWhitespaceBackground = vsDraw.WhitespaceBackgroundDrawn() && !background;
1675 // Background drawing loop
1676 while (bfBack.More()) {
1678 const TextSegment ts = bfBack.Next();
1679 const Sci::Position i = ts.end() - 1;
1680 const Sci::Position iDoc = i + posLineStart;
1682 const Interval horizontal = ll->Span(ts.start, ts.end()).Offset(horizontalOffset);
1683 // Only try to draw if really visible - enhances performance by not calling environment to
1684 // draw strings that are completely past the right side of the window.
1685 if (!horizontal.Empty() && rcLine.Intersects(horizontal)) {
1686 const PRectangle rcSegment = Intersection(rcLine, horizontal);
1688 InSelection inSelection = vsDraw.selection.visible ? model.sel.CharacterInSelection(iDoc) : InSelection::inNone;
1689 if (FlagSet(vsDraw.caret.style, CaretStyle::Curses) && (inSelection == InSelection::inMain))
1690 inSelection = CharacterInCursesSelection(iDoc, model, vsDraw);
1691 const bool inHotspot = model.hotspot.Valid() && model.hotspot.ContainsCharacter(iDoc);
1692 ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, inSelection,
1693 inHotspot, ll->styles[i], i);
1694 if (ts.representation) {
1695 if (ll->chars[i] == '\t') {
1696 // Tab display
1697 if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation)) {
1698 textBack = vsDraw.ElementColourForced(Element::WhiteSpaceBack).Opaque();
1700 } else {
1701 // Blob display
1702 inIndentation = false;
1704 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1705 } else {
1706 // Normal text display
1707 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1708 if (vsDraw.viewWhitespace != WhiteSpace::Invisible) {
1709 for (int cpos = 0; cpos <= i - ts.start; cpos++) {
1710 if (ll->chars[cpos + ts.start] == ' ') {
1711 if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation)) {
1712 const PRectangle rcSpace = Intersection(rcLine,
1713 ll->SpanByte(cpos + ts.start).Offset(horizontalOffset));
1714 surface->FillRectangleAligned(rcSpace,
1715 vsDraw.ElementColourForced(Element::WhiteSpaceBack).Opaque());
1717 } else {
1718 inIndentation = false;
1723 } else if (horizontal.left > rcLine.right) {
1724 break;
1729 void DrawEdgeLine(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll,
1730 int xStart, PRectangle rcLine, Range lineRange) {
1731 if (vsDraw.edgeState == EdgeVisualStyle::Line) {
1732 PRectangle rcSegment = rcLine;
1733 const int edgeX = static_cast<int>(vsDraw.theEdge.column * vsDraw.spaceWidth);
1734 rcSegment.left = static_cast<XYPOSITION>(edgeX + xStart);
1735 if ((ll->wrapIndent != 0) && (lineRange.start != 0))
1736 rcSegment.left -= ll->wrapIndent;
1737 rcSegment.right = rcSegment.left + 1;
1738 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.theEdge.colour));
1739 } else if (vsDraw.edgeState == EdgeVisualStyle::MultiLine) {
1740 for (size_t edge = 0; edge < vsDraw.theMultiEdge.size(); edge++) {
1741 if (vsDraw.theMultiEdge[edge].column >= 0) {
1742 PRectangle rcSegment = rcLine;
1743 const int edgeX = static_cast<int>(vsDraw.theMultiEdge[edge].column * vsDraw.spaceWidth);
1744 rcSegment.left = static_cast<XYPOSITION>(edgeX + xStart);
1745 if ((ll->wrapIndent != 0) && (lineRange.start != 0))
1746 rcSegment.left -= ll->wrapIndent;
1747 rcSegment.right = rcSegment.left + 1;
1748 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.theMultiEdge[edge].colour));
1754 // Draw underline mark as part of background if on base layer
1755 void DrawMarkUnderline(Surface *surface, const EditModel &model, const ViewStyle &vsDraw,
1756 Sci::Line line, PRectangle rcLine) {
1757 int marks = model.GetMark(line);
1758 for (int markBit = 0; (markBit <= MarkerMax) && marks; markBit++) {
1759 if ((marks & 1) && (vsDraw.markers[markBit].markType == MarkerSymbol::Underline) &&
1760 (vsDraw.markers[markBit].layer == Layer::Base)) {
1761 PRectangle rcUnderline = rcLine;
1762 rcUnderline.top = rcUnderline.bottom - 2;
1763 surface->FillRectangleAligned(rcUnderline, Fill(vsDraw.markers[markBit].back));
1765 marks >>= 1;
1769 void DrawTranslucentSelection(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1770 Sci::Line line, int xStart, PRectangle rcLine, int subLine, Range lineRange, int tabWidthMinimumPixels, Layer layer) {
1771 if (vsDraw.selection.layer == layer) {
1772 const Sci::Position posLineStart = model.pdoc->LineStart(line);
1773 const XYPOSITION subLineStart = ll->positions[lineRange.start];
1774 const XYPOSITION horizontalOffset = xStart - subLineStart;
1775 // For each selection draw
1776 Sci::Position virtualSpaces = 0;
1777 if (subLine == (ll->lines - 1)) {
1778 virtualSpaces = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line));
1780 const SelectionPosition posStart(posLineStart + lineRange.start);
1781 const SelectionPosition posEnd(posLineStart + lineRange.end, virtualSpaces);
1782 const SelectionSegment virtualSpaceRange(posStart, posEnd);
1783 for (size_t r = 0; r < model.sel.Count(); r++) {
1784 const SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange);
1785 if (!portion.Empty()) {
1786 const SelectionSegment portionInLine = portion.Subtract(posLineStart);
1787 const ColourRGBA selectionBack = SelectionBackground(
1788 model, vsDraw, model.sel.RangeType(r));
1789 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1790 const Interval intervalVirtual{ portion.start.VirtualSpace() * spaceWidth, portion.end.VirtualSpace() * spaceWidth };
1791 if (model.BidirectionalEnabled()) {
1792 const SelectionSegment portionInSubLine = portionInLine.Subtract(lineRange.start);
1794 const ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right, tabWidthMinimumPixels);
1795 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
1797 if (slLayout) {
1798 const std::vector<Interval> intervals = slLayout->FindRangeIntervals(
1799 portionInSubLine.start.Position(), portionInSubLine.end.Position());
1800 for (const Interval &interval : intervals) {
1801 const PRectangle rcSelection = rcLine.WithHorizontalBounds(interval.Offset(xStart));
1802 surface->FillRectangleAligned(rcSelection, selectionBack);
1806 if (portion.end.VirtualSpace()) {
1807 const XYPOSITION xStartVirtual = ll->positions[lineRange.end] + horizontalOffset;
1808 const PRectangle rcSegment = rcLine.WithHorizontalBounds(intervalVirtual.Offset(xStartVirtual));
1809 surface->FillRectangleAligned(rcSegment, selectionBack);
1811 } else {
1812 Interval intervalSegment = ll->Span(
1813 static_cast<int>(portionInLine.start.Position()),
1814 static_cast<int>(portionInLine.end.Position()))
1815 .Offset(horizontalOffset);
1816 intervalSegment.left += intervalVirtual.left;
1817 intervalSegment.right += intervalVirtual.right;
1818 if ((ll->wrapIndent != 0) && (lineRange.start != 0)) {
1819 if ((portionInLine.start.Position() == lineRange.start) &&
1820 model.sel.Range(r).ContainsCharacter(portion.start.Position() - 1))
1821 intervalSegment.left -= static_cast<int>(ll->wrapIndent); // indentation added to xStart was truncated to int, so we do the same here
1823 const PRectangle rcSegment = Intersection(rcLine, intervalSegment);
1824 if (rcSegment.right > rcLine.left)
1825 surface->FillRectangleAligned(rcSegment, selectionBack);
1832 void DrawCaretLineFramed(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll,
1833 PRectangle rcLine, int subLine) {
1834 const ColourOptional caretlineBack = vsDraw.ElementColour(Element::CaretLineBack);
1835 if (!caretlineBack) {
1836 return;
1839 const ColourRGBA colourFrame = (vsDraw.caretLine.layer == Layer::Base) ?
1840 caretlineBack->Opaque() : *caretlineBack;
1842 const int width = vsDraw.GetFrameWidth();
1844 // Avoid double drawing the corners by removing the left and right sides when drawing top and bottom borders
1845 const PRectangle rcWithoutLeftRight = rcLine.Inset(Point(width, 0.0));
1847 if (subLine == 0 || ll->wrapIndent == 0 || vsDraw.caretLine.layer != Layer::Base || vsDraw.caretLine.subLine) {
1848 // Left
1849 surface->FillRectangleAligned(Side(rcLine, Edge::left, width), colourFrame);
1851 if (subLine == 0 || vsDraw.caretLine.subLine) {
1852 // Top
1853 surface->FillRectangleAligned(Side(rcWithoutLeftRight, Edge::top, width), colourFrame);
1855 if (subLine == ll->lines - 1 || vsDraw.caretLine.layer != Layer::Base || vsDraw.caretLine.subLine) {
1856 // Right
1857 surface->FillRectangleAligned(Side(rcLine, Edge::right, width), colourFrame);
1859 if (subLine == ll->lines - 1 || vsDraw.caretLine.subLine) {
1860 // Bottom
1861 surface->FillRectangleAligned(Side(rcWithoutLeftRight, Edge::bottom, width), colourFrame);
1865 // Draw any translucent whole line states
1866 void DrawTranslucentLineState(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1867 Sci::Line line, PRectangle rcLine, int subLine, Layer layer) {
1868 if ((model.caret.active || vsDraw.caretLine.alwaysShow) && vsDraw.ElementColour(Element::CaretLineBack) && ll->containsCaret &&
1869 vsDraw.caretLine.layer == layer) {
1870 if (vsDraw.caretLine.frame) {
1871 DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine);
1872 } else {
1873 surface->FillRectangleAligned(rcLine, vsDraw.ElementColourForced(Element::CaretLineBack));
1876 const int marksOfLine = model.GetMark(line);
1877 int marksDrawnInText = marksOfLine & vsDraw.maskDrawInText;
1878 for (int markBit = 0; (markBit <= MarkerMax) && marksDrawnInText; markBit++) {
1879 if ((marksDrawnInText & 1) && (vsDraw.markers[markBit].layer == layer)) {
1880 if (vsDraw.markers[markBit].markType == MarkerSymbol::Background) {
1881 surface->FillRectangleAligned(rcLine, vsDraw.markers[markBit].BackWithAlpha());
1882 } else if (vsDraw.markers[markBit].markType == MarkerSymbol::Underline) {
1883 PRectangle rcUnderline = rcLine;
1884 rcUnderline.top = rcUnderline.bottom - 2;
1885 surface->FillRectangleAligned(rcUnderline, vsDraw.markers[markBit].BackWithAlpha());
1888 marksDrawnInText >>= 1;
1890 int marksDrawnInLine = marksOfLine & vsDraw.maskInLine;
1891 for (int markBit = 0; (markBit <= MarkerMax) && marksDrawnInLine; markBit++) {
1892 if ((marksDrawnInLine & 1) && (vsDraw.markers[markBit].layer == layer)) {
1893 surface->FillRectangleAligned(rcLine, vsDraw.markers[markBit].BackWithAlpha());
1895 marksDrawnInLine >>= 1;
1899 void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid,
1900 const ViewStyle &vsDraw, Stroke stroke) {
1902 const XYPOSITION halfWidth = stroke.width / 2.0;
1904 const XYPOSITION leftStroke = std::round(std::min(rcTab.left + 2, rcTab.right - 1)) + halfWidth;
1905 const XYPOSITION rightStroke = std::max(leftStroke, std::round(rcTab.right) - 1.0f - halfWidth);
1906 const XYPOSITION yMidAligned = ymid + halfWidth;
1907 const Point arrowPoint(rightStroke, yMidAligned);
1908 if (rightStroke > leftStroke) {
1909 // When not enough room, don't draw the arrow shaft
1910 surface->LineDraw(Point(leftStroke, yMidAligned), arrowPoint, stroke);
1913 // Draw the arrow head if needed
1914 if (vsDraw.tabDrawMode == TabDrawMode::LongArrow) {
1915 XYPOSITION ydiff = std::floor(rcTab.Height() / 2.0f);
1916 XYPOSITION xhead = rightStroke - ydiff;
1917 if (xhead <= rcTab.left) {
1918 ydiff -= rcTab.left - xhead;
1919 xhead = rcTab.left;
1921 const Point ptsHead[] = {
1922 Point(xhead, yMidAligned - ydiff),
1923 arrowPoint,
1924 Point(xhead, yMidAligned + ydiff)
1926 surface->PolyLine(ptsHead, std::size(ptsHead), stroke);
1930 void DrawIndicator(int indicNum, Sci::Position startPos, Sci::Position endPos, Surface *surface, const ViewStyle &vsDraw,
1931 const LineLayout *ll, int xStart, PRectangle rcLine, Sci::Position secondCharacter, int subLine, Indicator::State state,
1932 int value, bool bidiEnabled, int tabWidthMinimumPixels) {
1934 const XYPOSITION subLineStart = ll->positions[ll->LineStart(subLine)];
1935 const XYPOSITION horizontalOffset = xStart - subLineStart;
1937 std::vector<PRectangle> rectangles;
1939 const XYPOSITION left = ll->XInLine(startPos) + horizontalOffset;
1940 const XYPOSITION right = ll->XInLine(endPos) + horizontalOffset;
1941 const PRectangle rcIndic(left, rcLine.top + vsDraw.maxAscent, right,
1942 std::max(rcLine.top + vsDraw.maxAscent + 3, rcLine.bottom));
1944 if (bidiEnabled) {
1945 ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right - xStart, tabWidthMinimumPixels);
1946 const Range lineRange = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
1948 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
1949 std::vector<Interval> intervals = slLayout->FindRangeIntervals(
1950 startPos - lineRange.start, endPos - lineRange.start);
1951 for (const Interval &interval : intervals) {
1952 PRectangle rcInterval = rcIndic;
1953 rcInterval.left = interval.left + xStart;
1954 rcInterval.right = interval.right + xStart;
1955 rectangles.push_back(rcInterval);
1957 } else {
1958 rectangles.push_back(rcIndic);
1961 for (const PRectangle &rc : rectangles) {
1962 PRectangle rcFirstCharacter = rc;
1963 // Allow full descent space for character indicators
1964 rcFirstCharacter.bottom = rcLine.top + vsDraw.maxAscent + vsDraw.maxDescent;
1965 if (secondCharacter >= 0) {
1966 rcFirstCharacter.right = ll->XInLine(secondCharacter) + horizontalOffset;
1967 } else {
1968 // Indicator continued from earlier line so make an empty box and don't draw
1969 rcFirstCharacter.right = rcFirstCharacter.left;
1971 vsDraw.indicators[indicNum].Draw(surface, rc, rcLine, rcFirstCharacter, state, value);
1975 void DrawIndicators(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1976 Sci::Line line, int xStart, PRectangle rcLine, int subLine, Sci::Position lineEnd, bool under, int tabWidthMinimumPixels) {
1977 // Draw decorators
1978 const Sci::Position posLineStart = model.pdoc->LineStart(line);
1979 const Sci::Position lineStart = ll->LineStart(subLine);
1980 const Sci::Position posLineEnd = posLineStart + lineEnd;
1982 for (const IDecoration *deco : model.pdoc->decorations->View()) {
1983 if (under == vsDraw.indicators[deco->Indicator()].under) {
1984 Sci::Position startPos = posLineStart + lineStart;
1985 while (startPos < posLineEnd) {
1986 const Range rangeRun(deco->StartRun(startPos), deco->EndRun(startPos));
1987 const Sci::Position endPos = std::min(rangeRun.end, posLineEnd);
1988 const int value = deco->ValueAt(startPos);
1989 if (value) {
1990 const bool hover = vsDraw.indicators[deco->Indicator()].IsDynamic() &&
1991 rangeRun.ContainsCharacter(model.hoverIndicatorPos);
1992 const Indicator::State state = hover ? Indicator::State::hover : Indicator::State::normal;
1993 const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(rangeRun.First() + 1, 1);
1994 DrawIndicator(deco->Indicator(), startPos - posLineStart, endPos - posLineStart,
1995 surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, state,
1996 value, model.BidirectionalEnabled(), tabWidthMinimumPixels);
1998 startPos = endPos;
2003 // Use indicators to highlight matching braces
2004 if ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == StyleBraceLight)) ||
2005 (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == StyleBraceBad))) {
2006 const int braceIndicator = (model.bracesMatchStyle == StyleBraceLight) ? vsDraw.braceHighlightIndicator : vsDraw.braceBadLightIndicator;
2007 if (under == vsDraw.indicators[braceIndicator].under) {
2008 const Range rangeLine(posLineStart + lineStart, posLineEnd);
2009 for (size_t brace = 0; brace <= 1; brace++) {
2010 if (rangeLine.ContainsCharacter(model.braces[brace])) {
2011 const Sci::Position braceOffset = model.braces[brace] - posLineStart;
2012 if (braceOffset < ll->numCharsInLine) {
2013 const Sci::Position braceEnd = model.pdoc->MovePositionOutsideChar(model.braces[brace] + 1, 1) - posLineStart;
2014 DrawIndicator(braceIndicator, braceOffset, braceEnd,
2015 surface, vsDraw, ll, xStart, rcLine, braceEnd, subLine, Indicator::State::normal,
2016 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
2023 if (FlagSet(model.changeHistoryOption, ChangeHistoryOption::Indicators)) {
2024 // Draw editions
2025 constexpr int indexHistory = static_cast<int>(IndicatorNumbers::HistoryRevertedToOriginInsertion);
2027 // Draw insertions
2028 Sci::Position startPos = posLineStart + lineStart;
2029 while (startPos < posLineEnd) {
2030 const Range rangeRun(startPos, model.pdoc->EditionEndRun(startPos));
2031 const Sci::Position endPos = std::min(rangeRun.end, posLineEnd);
2032 const int edition = model.pdoc->EditionAt(startPos);
2033 if (edition != 0) {
2034 const int indicator = (edition - 1) * 2 + indexHistory;
2035 const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(rangeRun.First() + 1, 1);
2036 DrawIndicator(indicator, startPos - posLineStart, endPos - posLineStart,
2037 surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, Indicator::State::normal,
2038 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
2040 startPos = endPos;
2044 // Draw deletions
2045 Sci::Position startPos = posLineStart + lineStart;
2046 while (startPos <= posLineEnd) {
2047 const unsigned int editions = model.pdoc->EditionDeletesAt(startPos);
2048 const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(startPos + 1, 1);
2049 for (unsigned int edition = 0; edition < 4; edition++) {
2050 if (editions & (1 << edition)) {
2051 const int indicator = edition * 2 + indexHistory + 1;
2052 DrawIndicator(indicator, startPos - posLineStart, posSecond - posLineStart,
2053 surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, Indicator::State::normal,
2054 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
2057 startPos = model.pdoc->EditionNextDelete(startPos);
2063 void DrawFoldLines(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2064 Sci::Line line, PRectangle rcLine, int subLine) {
2065 const bool lastSubLine = subLine == (ll->lines - 1);
2066 const bool expanded = model.pcs->GetExpanded(line);
2067 const FoldLevel level = model.pdoc->GetFoldLevel(line);
2068 const FoldLevel levelNext = model.pdoc->GetFoldLevel(line + 1);
2069 if (LevelIsHeader(level) &&
2070 (LevelNumber(level) < LevelNumber(levelNext))) {
2071 const ColourRGBA foldLineColour = vsDraw.ElementColour(Element::FoldLine).value_or(
2072 vsDraw.styles[StyleDefault].fore);
2073 // Paint the line above the fold
2074 if ((subLine == 0) && FlagSet(model.foldFlags, (expanded ? FoldFlag::LineBeforeExpanded: FoldFlag::LineBeforeContracted))) {
2075 surface->FillRectangleAligned(Side(rcLine, Edge::top, 1.0), foldLineColour);
2077 // Paint the line below the fold
2078 if (lastSubLine && FlagSet(model.foldFlags, (expanded ? FoldFlag::LineAfterExpanded : FoldFlag::LineAfterContracted))) {
2079 surface->FillRectangleAligned(Side(rcLine, Edge::bottom, 1.0), foldLineColour);
2080 // If contracted fold line drawn then don't overwrite with hidden line
2081 // as fold lines are more specific then hidden lines.
2082 if (!expanded) {
2083 return;
2087 if (lastSubLine && model.pcs->GetVisible(line) && !model.pcs->GetVisible(line + 1)) {
2088 if (const ColourOptional hiddenLineColour = vsDraw.ElementColour(Element::HiddenLine)) {
2089 surface->FillRectangleAligned(Side(rcLine, Edge::bottom, 1.0), *hiddenLineColour);
2094 ColourRGBA InvertedLight(ColourRGBA orig) noexcept {
2095 unsigned int r = orig.GetRed();
2096 unsigned int g = orig.GetGreen();
2097 unsigned int b = orig.GetBlue();
2098 const unsigned int l = (r + g + b) / 3; // There is a better calculation for this that matches human eye
2099 const unsigned int il = 0xff - l;
2100 if (l == 0)
2101 return white;
2102 r = r * il / l;
2103 g = g * il / l;
2104 b = b * il / l;
2105 return ColourRGBA(std::min(r, 0xffu), std::min(g, 0xffu), std::min(b, 0xffu));
2110 void EditView::DrawIndentGuide(Surface *surface, XYPOSITION start, PRectangle rcSegment, bool highlight, bool offset) {
2111 const Point from = Point::FromInts(0, offset ? 1 : 0);
2112 const PRectangle rcCopyArea(start + 1, rcSegment.top,
2113 start + 2, rcSegment.bottom);
2114 surface->Copy(rcCopyArea, from,
2115 highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide);
2118 void EditView::DrawForeground(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2119 int xStart, PRectangle rcLine, int subLine, Sci::Line lineVisible, Range lineRange, Sci::Position posLineStart,
2120 ColourOptional background) {
2122 const bool selBackDrawn = vsDraw.SelectionBackgroundDrawn();
2123 const bool drawWhitespaceBackground = vsDraw.WhitespaceBackgroundDrawn() && !background;
2124 bool inIndentation = subLine == 0; // Do not handle indentation except on first subline.
2126 const XYPOSITION subLineStart = ll->positions[lineRange.start];
2127 const XYPOSITION horizontalOffset = xStart - subLineStart;
2128 const XYPOSITION indentWidth = model.pdoc->IndentSize() * vsDraw.spaceWidth;
2130 // Does not take margin into account but not significant
2131 const XYPOSITION xStartVisible = subLineStart - xStart;
2133 // When lineHeight is odd, dotted indent guides are drawn offset by 1 on odd lines to join together.
2134 const bool offsetGuide = (lineVisible & 1) && (vsDraw.lineHeight & 1);
2136 // Same baseline used for all text
2137 const XYPOSITION ybase = rcLine.top + vsDraw.maxAscent;
2139 // Foreground drawing loop
2140 const BreakFinder::BreakFor breakFor = (((phasesDraw == PhasesDraw::One) && selBackDrawn) || vsDraw.SelectionTextDrawn())
2141 ? BreakFinder::BreakFor::ForegroundAndSelection : BreakFinder::BreakFor::Foreground;
2142 BreakFinder bfFore(ll, &model.sel, lineRange, posLineStart, xStartVisible, breakFor, model.pdoc, &model.reprs, &vsDraw);
2144 while (bfFore.More()) {
2146 const TextSegment ts = bfFore.Next();
2147 const Sci::Position i = ts.end() - 1;
2148 const Sci::Position iDoc = i + posLineStart;
2150 const Interval horizontal = ll->Span(ts.start, ts.end()).Offset(horizontalOffset);
2151 // Only try to draw if really visible - enhances performance by not calling environment to
2152 // draw strings that are completely past the right side of the window.
2153 if (rcLine.Intersects(horizontal)) {
2154 const PRectangle rcSegment = rcLine.WithHorizontalBounds(horizontal);
2155 const int styleMain = ll->styles[i];
2156 ColourRGBA textFore = vsDraw.styles[styleMain].fore;
2157 const Font *textFont = vsDraw.styles[styleMain].font.get();
2158 // Hot-spot foreground
2159 const bool inHotspot = model.hotspot.Valid() && model.hotspot.ContainsCharacter(iDoc);
2160 if (inHotspot) {
2161 if (const ColourOptional colourHotSpot = vsDraw.ElementColour(Element::HotSpotActive)) {
2162 textFore = *colourHotSpot;
2165 if (vsDraw.indicatorsSetFore) {
2166 // At least one indicator sets the text colour so see if it applies to this segment
2167 for (const IDecoration *deco : model.pdoc->decorations->View()) {
2168 const int indicatorValue = deco->ValueAt(ts.start + posLineStart);
2169 if (indicatorValue) {
2170 const Indicator &indicator = vsDraw.indicators[deco->Indicator()];
2171 bool hover = false;
2172 if (indicator.IsDynamic()) {
2173 const Sci::Position startPos = ts.start + posLineStart;
2174 const Range rangeRun(deco->StartRun(startPos), deco->EndRun(startPos));
2175 hover = rangeRun.ContainsCharacter(model.hoverIndicatorPos);
2177 if (hover) {
2178 if (indicator.sacHover.style == IndicatorStyle::TextFore) {
2179 textFore = indicator.sacHover.fore;
2181 } else {
2182 if (indicator.sacNormal.style == IndicatorStyle::TextFore) {
2183 if (FlagSet(indicator.Flags(), IndicFlag::ValueFore))
2184 textFore = ColourRGBA::FromRGB(indicatorValue & static_cast<int>(IndicValue::Mask));
2185 else
2186 textFore = indicator.sacNormal.fore;
2192 InSelection inSelection = vsDraw.selection.visible ? model.sel.CharacterInSelection(iDoc) : InSelection::inNone;
2193 if (FlagSet(vsDraw.caret.style, CaretStyle::Curses) && (inSelection == InSelection::inMain))
2194 inSelection = CharacterInCursesSelection(iDoc, model, vsDraw);
2195 if (const ColourOptional selectionFore = SelectionForeground(model, vsDraw, inSelection)) {
2196 textFore = *selectionFore;
2198 ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, inSelection, inHotspot, styleMain, i);
2199 if (ts.representation) {
2200 if (ll->chars[i] == '\t') {
2201 // Tab display
2202 if (phasesDraw == PhasesDraw::One) {
2203 if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation))
2204 textBack = vsDraw.ElementColourForced(Element::WhiteSpaceBack).Opaque();
2205 surface->FillRectangleAligned(rcSegment, Fill(textBack));
2207 if (inIndentation && vsDraw.viewIndentationGuides == IndentView::Real) {
2208 const Interval intervalCharacter = ll->SpanByte(static_cast<int>(i));
2209 for (int indentCount = static_cast<int>((intervalCharacter.left + epsilon) / indentWidth);
2210 indentCount <= (intervalCharacter.right - epsilon) / indentWidth;
2211 indentCount++) {
2212 if (indentCount > 0) {
2213 const XYPOSITION xIndent = std::floor(indentCount * indentWidth);
2214 DrawIndentGuide(surface, xIndent + xStart, rcSegment, ll->xHighlightGuide == xIndent, offsetGuide);
2218 if (vsDraw.viewWhitespace != WhiteSpace::Invisible) {
2219 if (vsDraw.WhiteSpaceVisible(inIndentation)) {
2220 const PRectangle rcTab(rcSegment.left + 1, rcSegment.top + tabArrowHeight,
2221 rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent);
2222 const int segmentTop = static_cast<int>(rcSegment.top) + vsDraw.lineHeight / 2;
2223 const ColourRGBA whiteSpaceFore = vsDraw.ElementColour(Element::WhiteSpace).value_or(textFore);
2224 if (!customDrawTabArrow)
2225 DrawTabArrow(surface, rcTab, segmentTop, vsDraw, Stroke(whiteSpaceFore, 1.0f));
2226 else
2227 customDrawTabArrow(surface, rcTab, segmentTop, vsDraw, Stroke(whiteSpaceFore, 1.0f));
2230 } else {
2231 inIndentation = false;
2232 if (vsDraw.controlCharSymbol >= 32) {
2233 // Using one font for all control characters so it can be controlled independently to ensure
2234 // the box goes around the characters tightly. Seems to be no way to work out what height
2235 // is taken by an individual character - internal leading gives varying results.
2236 const Font *ctrlCharsFont = vsDraw.styles[StyleControlChar].font.get();
2237 const char cc[2] = { static_cast<char>(vsDraw.controlCharSymbol), '\0' };
2238 surface->DrawTextNoClip(rcSegment, ctrlCharsFont,
2239 ybase, cc, textBack, textFore);
2240 } else {
2241 if (FlagSet(ts.representation->appearance, RepresentationAppearance::Colour)) {
2242 textFore = ts.representation->colour;
2244 if (FlagSet(ts.representation->appearance, RepresentationAppearance::Blob)) {
2245 DrawTextBlob(surface, vsDraw, rcSegment, ts.representation->stringRep,
2246 textBack, textFore, phasesDraw == PhasesDraw::One);
2247 } else {
2248 surface->DrawTextTransparentUTF8(rcSegment, vsDraw.styles[StyleControlChar].font.get(),
2249 ybase, ts.representation->stringRep, textFore);
2253 } else {
2254 // Normal text display
2255 if (vsDraw.styles[styleMain].visible) {
2256 const std::string_view text(&ll->chars[ts.start], i - ts.start + 1);
2257 if (phasesDraw != PhasesDraw::One) {
2258 surface->DrawTextTransparent(rcSegment, textFont,
2259 ybase, text, textFore);
2260 } else {
2261 surface->DrawTextNoClip(rcSegment, textFont,
2262 ybase, text, textFore, textBack);
2264 } else if (vsDraw.styles[styleMain].invisibleRepresentation[0]) {
2265 const std::string_view text = vsDraw.styles[styleMain].invisibleRepresentation;
2266 if (phasesDraw != PhasesDraw::One) {
2267 surface->DrawTextTransparentUTF8(rcSegment, textFont,
2268 ybase, text, textFore);
2269 } else {
2270 surface->DrawTextNoClipUTF8(rcSegment, textFont,
2271 ybase, text, textFore, textBack);
2274 if (vsDraw.viewWhitespace != WhiteSpace::Invisible ||
2275 (inIndentation && vsDraw.viewIndentationGuides != IndentView::None)) {
2276 for (int cpos = 0; cpos <= i - ts.start; cpos++) {
2277 if (ll->chars[cpos + ts.start] == ' ') {
2278 if (vsDraw.viewWhitespace != WhiteSpace::Invisible) {
2279 if (vsDraw.WhiteSpaceVisible(inIndentation)) {
2280 const Interval intervalSpace = ll->SpanByte(cpos + ts.start).Offset(horizontalOffset);
2281 const XYPOSITION xmid = (intervalSpace.left + intervalSpace.right) / 2;
2282 if ((phasesDraw == PhasesDraw::One) && drawWhitespaceBackground) {
2283 textBack = vsDraw.ElementColourForced(Element::WhiteSpaceBack).Opaque();
2284 const PRectangle rcSpace = rcLine.WithHorizontalBounds(intervalSpace);
2285 surface->FillRectangleAligned(rcSpace, Fill(textBack));
2287 const int halfDotWidth = vsDraw.whitespaceSize / 2;
2288 PRectangle rcDot(xmid - halfDotWidth,
2289 rcSegment.top + vsDraw.lineHeight / 2, 0.0f, 0.0f);
2290 rcDot.right = rcDot.left + vsDraw.whitespaceSize;
2291 rcDot.bottom = rcDot.top + vsDraw.whitespaceSize;
2292 const ColourRGBA whiteSpaceFore = vsDraw.ElementColour(Element::WhiteSpace).value_or(textFore);
2293 surface->FillRectangleAligned(rcDot, Fill(whiteSpaceFore));
2296 if (inIndentation && vsDraw.viewIndentationGuides == IndentView::Real) {
2297 const Interval intervalCharacter = ll->SpanByte(cpos + ts.start);
2298 for (int indentCount = static_cast<int>((intervalCharacter.left + epsilon) / indentWidth);
2299 indentCount <= (intervalCharacter.right - epsilon) / indentWidth;
2300 indentCount++) {
2301 if (indentCount > 0) {
2302 const XYPOSITION xIndent = std::floor(indentCount * indentWidth);
2303 DrawIndentGuide(surface, xIndent + xStart, rcSegment, ll->xHighlightGuide == xIndent, offsetGuide);
2307 } else {
2308 inIndentation = false;
2313 if ((inHotspot && vsDraw.hotspotUnderline) || vsDraw.styles[styleMain].underline) {
2314 PRectangle rcUL = rcSegment;
2315 rcUL.top = ybase + 1;
2316 rcUL.bottom = ybase + 2;
2317 ColourRGBA colourUnderline = textFore;
2318 if (inHotspot && vsDraw.hotspotUnderline) {
2319 colourUnderline = vsDraw.ElementColour(Element::HotSpotActive).value_or(textFore);
2321 surface->FillRectangleAligned(rcUL, colourUnderline);
2323 } else if (horizontal.left > rcLine.right) {
2324 break;
2329 void EditView::DrawIndentGuidesOverEmpty(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2330 Sci::Line line, int xStart, PRectangle rcLine, int subLine, Sci::Line lineVisible) {
2331 if ((vsDraw.viewIndentationGuides == IndentView::LookForward || vsDraw.viewIndentationGuides == IndentView::LookBoth)
2332 && (subLine == 0)) {
2333 const Sci::Position posLineStart = model.pdoc->LineStart(line);
2334 int indentSpace = model.pdoc->GetLineIndentation(line);
2335 int xStartText = static_cast<int>(ll->positions[model.pdoc->GetLineIndentPosition(line) - posLineStart]);
2337 // Find the most recent line with some text
2339 Sci::Line lineLastWithText = line;
2340 while (lineLastWithText > std::max(line - 20, static_cast<Sci::Line>(0)) && model.pdoc->IsWhiteLine(lineLastWithText)) {
2341 lineLastWithText--;
2343 if (lineLastWithText < line) {
2344 xStartText = 100000; // Don't limit to visible indentation on empty line
2345 // This line is empty, so use indentation of last line with text
2346 int indentLastWithText = model.pdoc->GetLineIndentation(lineLastWithText);
2347 const int isFoldHeader = LevelIsHeader(model.pdoc->GetFoldLevel(lineLastWithText));
2348 if (isFoldHeader) {
2349 // Level is one more level than parent
2350 indentLastWithText += model.pdoc->IndentSize();
2352 if (vsDraw.viewIndentationGuides == IndentView::LookForward) {
2353 // In viLookForward mode, previous line only used if it is a fold header
2354 if (isFoldHeader) {
2355 indentSpace = std::max(indentSpace, indentLastWithText);
2357 } else { // viLookBoth
2358 indentSpace = std::max(indentSpace, indentLastWithText);
2362 Sci::Line lineNextWithText = line;
2363 while (lineNextWithText < std::min(line + 20, model.pdoc->LinesTotal()) && model.pdoc->IsWhiteLine(lineNextWithText)) {
2364 lineNextWithText++;
2366 if (lineNextWithText > line) {
2367 xStartText = 100000; // Don't limit to visible indentation on empty line
2368 // This line is empty, so use indentation of first next line with text
2369 indentSpace = std::max(indentSpace,
2370 model.pdoc->GetLineIndentation(lineNextWithText));
2373 const bool offsetGuide = (lineVisible & 1) && (vsDraw.lineHeight & 1);
2374 for (int indentPos = model.pdoc->IndentSize(); indentPos < indentSpace; indentPos += model.pdoc->IndentSize()) {
2375 const XYPOSITION xIndent = std::floor(indentPos * vsDraw.spaceWidth);
2376 if (xIndent < xStartText) {
2377 DrawIndentGuide(surface, xIndent + xStart, rcLine, ll->xHighlightGuide == xIndent, offsetGuide);
2383 void EditView::DrawLine(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2384 Sci::Line line, Sci::Line lineVisible, int xStart, PRectangle rcLine, int subLine, DrawPhase phase) {
2386 if (subLine >= ll->lines) {
2387 DrawAnnotation(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, phase);
2388 return; // No further drawing
2391 const bool clipLine = !bufferedDraw && !LinesOverlap();
2392 if (clipLine) {
2393 surface->SetClip(rcLine);
2396 // See if something overrides the line background colour.
2397 ColourOptional background = vsDraw.Background(model.GetMark(line), model.caret.active, ll->containsCaret);
2398 SCNotification scn = { 0 };
2399 scn.nmhdr.code = SCN_GETBKCOLOR;
2400 scn.line = line;
2401 scn.lParam = -1;
2402 if (editor)
2403 ((Editor*)editor)->NotifyParent(&scn);
2404 if (scn.lParam != -1)
2405 background = ColourRGBA::FromRGB(static_cast<int>(scn.lParam));
2407 const Sci::Position posLineStart = model.pdoc->LineStart(line);
2409 const Range lineRange = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
2410 const Range lineRangeIncludingEnd = ll->SubLineRange(subLine, LineLayout::Scope::includeEnd);
2411 const XYPOSITION subLineStart = ll->positions[lineRange.start];
2413 if ((ll->wrapIndent != 0) && (subLine > 0)) {
2414 if (FlagSet(phase, DrawPhase::back)) {
2415 DrawWrapIndentAndMarker(surface, vsDraw, ll, xStart, rcLine, background, customDrawWrapMarker, model.caret.active);
2417 xStart += static_cast<int>(ll->wrapIndent);
2420 if (phasesDraw != PhasesDraw::One) {
2421 if (FlagSet(phase, DrawPhase::back)) {
2422 DrawBackground(surface, model, vsDraw, ll,
2423 xStart, rcLine, subLine, lineRange, posLineStart,
2424 background);
2425 DrawFoldDisplayText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, DrawPhase::back);
2426 DrawEOLAnnotationText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, DrawPhase::back);
2427 // Remove drawBack to not draw again in DrawFoldDisplayText
2428 phase = static_cast<DrawPhase>(static_cast<int>(phase) & ~static_cast<int>(DrawPhase::back));
2429 DrawEOL(surface, model, vsDraw, ll,
2430 line, xStart, rcLine, subLine, lineRange.end, subLineStart, background);
2431 if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret))
2432 DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine);
2435 if (FlagSet(phase, DrawPhase::indicatorsBack)) {
2436 DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine,
2437 lineRangeIncludingEnd.end, true, tabWidthMinimumPixels);
2438 DrawEdgeLine(surface, vsDraw, ll, xStart, rcLine, lineRange);
2439 DrawMarkUnderline(surface, model, vsDraw, line, rcLine);
2443 if (FlagSet(phase, DrawPhase::text)) {
2444 if (vsDraw.selection.visible) {
2445 DrawTranslucentSelection(surface, model, vsDraw, ll,
2446 line, xStart, rcLine, subLine, lineRange, tabWidthMinimumPixels, Layer::UnderText);
2448 DrawTranslucentLineState(surface, model, vsDraw, ll, line, rcLine, subLine, Layer::UnderText);
2449 DrawForeground(surface, model, vsDraw, ll,
2450 xStart, rcLine, subLine, lineVisible, lineRange, posLineStart,
2451 background);
2454 if (FlagSet(phase, DrawPhase::indentationGuides)) {
2455 DrawIndentGuidesOverEmpty(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, lineVisible);
2458 if (FlagSet(phase, DrawPhase::indicatorsFore)) {
2459 DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine,
2460 lineRangeIncludingEnd.end, false, tabWidthMinimumPixels);
2463 DrawFoldDisplayText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, phase);
2464 DrawEOLAnnotationText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, phase);
2466 if (phasesDraw == PhasesDraw::One) {
2467 DrawEOL(surface, model, vsDraw, ll,
2468 line, xStart, rcLine, subLine, lineRange.end, subLineStart, background);
2469 if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret))
2470 DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine);
2471 DrawEdgeLine(surface, vsDraw, ll, xStart, rcLine, lineRange);
2472 DrawMarkUnderline(surface, model, vsDraw, line, rcLine);
2475 if (vsDraw.selection.visible && FlagSet(phase, DrawPhase::selectionTranslucent)) {
2476 DrawTranslucentSelection(surface, model, vsDraw, ll,
2477 line, xStart, rcLine, subLine, lineRange, tabWidthMinimumPixels, Layer::OverText);
2480 if (FlagSet(phase, DrawPhase::lineTranslucent)) {
2481 DrawTranslucentLineState(surface, model, vsDraw, ll, line, rcLine, subLine, Layer::OverText);
2484 if (clipLine) {
2485 surface->PopClip();
2489 void EditView::PaintText(Surface *surfaceWindow, const EditModel &model, const ViewStyle &vsDraw,
2490 PRectangle rcArea, PRectangle rcClient) {
2491 // Allow text at start of line to overlap 1 pixel into the margin as this displays
2492 // serifs and italic stems for aliased text.
2493 const int leftTextOverlap = ((model.xOffset == 0) && (vsDraw.leftMarginWidth > 0)) ? 1 : 0;
2495 // Do the painting
2496 if (rcArea.right > vsDraw.textStart - leftTextOverlap) {
2498 Surface *surface = surfaceWindow;
2499 if (bufferedDraw) {
2500 surface = pixmapLine.get();
2501 PLATFORM_ASSERT(pixmapLine->Initialised());
2503 surface->SetMode(model.CurrentSurfaceMode());
2505 const Point ptOrigin = model.GetVisibleOriginInMain();
2507 const int screenLinePaintFirst = static_cast<int>(rcArea.top) / vsDraw.lineHeight;
2508 const int xStart = vsDraw.textStart - model.xOffset + static_cast<int>(ptOrigin.x);
2510 const SelectionPosition posCaret = model.posDrag.IsValid() ? model.posDrag : model.sel.RangeMain().caret;
2511 const Sci::Line lineCaret = model.pdoc->SciLineFromPosition(posCaret.Position());
2512 const int caretOffset = static_cast<int>(posCaret.Position() - model.pdoc->LineStart(lineCaret));
2514 PRectangle rcTextArea = rcClient;
2515 if (vsDraw.marginInside) {
2516 rcTextArea.left += vsDraw.textStart;
2517 rcTextArea.right -= vsDraw.rightMarginWidth;
2518 } else {
2519 rcTextArea = rcArea;
2522 // Remove selection margin from drawing area so text will not be drawn
2523 // on it in unbuffered mode.
2524 const bool clipping = !bufferedDraw && vsDraw.marginInside;
2525 if (clipping) {
2526 PRectangle rcClipText = rcTextArea;
2527 rcClipText.left -= leftTextOverlap;
2528 surfaceWindow->SetClip(rcClipText);
2531 // Loop on visible lines
2532 #if defined(TIME_PAINTING)
2533 double durLayout = 0.0;
2534 double durPaint = 0.0;
2535 double durCopy = 0.0;
2536 ElapsedPeriod epWhole;
2537 #endif
2538 const bool bracesIgnoreStyle = ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == StyleBraceLight)) ||
2539 (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == StyleBraceBad)));
2541 Sci::Line lineDocPrevious = -1; // Used to avoid laying out one document line multiple times
2542 std::shared_ptr<LineLayout> ll;
2543 std::vector<DrawPhase> phases;
2544 if ((phasesDraw == PhasesDraw::Multiple) && !bufferedDraw) {
2545 for (DrawPhase phase = DrawPhase::back; phase <= DrawPhase::carets; phase = static_cast<DrawPhase>(static_cast<int>(phase) * 2)) {
2546 phases.push_back(phase);
2548 } else {
2549 phases.push_back(DrawPhase::all);
2551 for (const DrawPhase &phase : phases) {
2552 int ypos = 0;
2553 if (!bufferedDraw)
2554 ypos += screenLinePaintFirst * vsDraw.lineHeight;
2555 int yposScreen = screenLinePaintFirst * vsDraw.lineHeight;
2556 Sci::Line visibleLine = model.TopLineOfMain() + screenLinePaintFirst;
2557 while (visibleLine < model.pcs->LinesDisplayed() && yposScreen < rcArea.bottom) {
2559 const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine);
2560 // Only visible lines should be handled by the code within the loop
2561 PLATFORM_ASSERT(model.pcs->GetVisible(lineDoc));
2562 const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc);
2563 const int subLine = static_cast<int>(visibleLine - lineStartSet);
2565 // Copy this line and its styles from the document into local arrays
2566 // and determine the x position at which each character starts.
2567 #if defined(TIME_PAINTING)
2568 ElapsedPeriod ep;
2569 #endif
2570 if (lineDoc != lineDocPrevious) {
2571 ll = RetrieveLineLayout(lineDoc, model);
2572 LayoutLine(model, surface, vsDraw, ll.get(), model.wrapWidth);
2573 lineDocPrevious = lineDoc;
2575 #if defined(TIME_PAINTING)
2576 durLayout += ep.Duration(true);
2577 #endif
2578 if (ll) {
2579 ll->containsCaret = vsDraw.selection.visible && (lineDoc == lineCaret)
2580 && (ll->lines == 1 || !vsDraw.caretLine.subLine || ll->InLine(caretOffset, subLine));
2582 PRectangle rcLine = rcTextArea;
2583 rcLine.top = static_cast<XYPOSITION>(ypos);
2584 rcLine.bottom = static_cast<XYPOSITION>(ypos + vsDraw.lineHeight);
2586 const Range rangeLine(model.pdoc->LineStart(lineDoc),
2587 model.pdoc->LineStart(lineDoc + 1));
2589 // Highlight the current braces if any
2590 ll->SetBracesHighlight(rangeLine, model.braces, static_cast<char>(model.bracesMatchStyle),
2591 static_cast<int>(model.highlightGuideColumn * vsDraw.spaceWidth), bracesIgnoreStyle);
2593 if (leftTextOverlap && (bufferedDraw || ((phasesDraw < PhasesDraw::Multiple) && (FlagSet(phase, DrawPhase::back))))) {
2594 // Clear the left margin
2595 PRectangle rcSpacer = rcLine;
2596 rcSpacer.right = rcSpacer.left;
2597 rcSpacer.left -= 1;
2598 surface->FillRectangleAligned(rcSpacer, Fill(vsDraw.styles[StyleDefault].back));
2601 if (model.BidirectionalEnabled()) {
2602 // Fill the line bidi data
2603 UpdateBidiData(model, vsDraw, ll.get());
2606 DrawLine(surface, model, vsDraw, ll.get(), lineDoc, visibleLine, xStart, rcLine, subLine, phase);
2607 #if defined(TIME_PAINTING)
2608 durPaint += ep.Duration(true);
2609 #endif
2610 // Restore the previous styles for the brace highlights in case layout is in cache.
2611 ll->RestoreBracesHighlight(rangeLine, model.braces, bracesIgnoreStyle);
2613 if (FlagSet(phase, DrawPhase::foldLines)) {
2614 DrawFoldLines(surface, model, vsDraw, ll.get(), lineDoc, rcLine, subLine);
2617 if (FlagSet(phase, DrawPhase::carets)) {
2618 DrawCarets(surface, model, vsDraw, ll.get(), lineDoc, xStart, rcLine, subLine);
2621 if (bufferedDraw) {
2622 const Point from = Point::FromInts(vsDraw.textStart - leftTextOverlap, 0);
2623 const PRectangle rcCopyArea = PRectangle::FromInts(vsDraw.textStart - leftTextOverlap, yposScreen,
2624 static_cast<int>(rcClient.right - vsDraw.rightMarginWidth),
2625 yposScreen + vsDraw.lineHeight);
2626 pixmapLine->FlushDrawing();
2627 surfaceWindow->Copy(rcCopyArea, from, *pixmapLine);
2630 lineWidthMaxSeen = std::max(
2631 lineWidthMaxSeen, static_cast<int>(ll->positions[ll->numCharsInLine]));
2632 #if defined(TIME_PAINTING)
2633 durCopy += ep.Duration(true);
2634 #endif
2637 if (!bufferedDraw) {
2638 ypos += vsDraw.lineHeight;
2641 yposScreen += vsDraw.lineHeight;
2642 visibleLine++;
2645 ll.reset();
2646 #if defined(TIME_PAINTING)
2647 if (durPaint < 0.00000001)
2648 durPaint = 0.00000001;
2649 #endif
2650 // Right column limit indicator
2651 PRectangle rcBeyondEOF = (vsDraw.marginInside) ? rcClient : rcArea;
2652 rcBeyondEOF.left = static_cast<XYPOSITION>(vsDraw.textStart);
2653 rcBeyondEOF.right = rcBeyondEOF.right - ((vsDraw.marginInside) ? vsDraw.rightMarginWidth : 0);
2654 rcBeyondEOF.top = static_cast<XYPOSITION>((model.pcs->LinesDisplayed() - model.TopLineOfMain()) * vsDraw.lineHeight);
2655 if (rcBeyondEOF.top < rcBeyondEOF.bottom) {
2656 surfaceWindow->FillRectangleAligned(rcBeyondEOF, Fill(vsDraw.styles[StyleDefault].back));
2657 if (vsDraw.edgeState == EdgeVisualStyle::Line) {
2658 const int edgeX = static_cast<int>(vsDraw.theEdge.column * vsDraw.spaceWidth);
2659 rcBeyondEOF.left = static_cast<XYPOSITION>(edgeX + xStart);
2660 rcBeyondEOF.right = rcBeyondEOF.left + 1;
2661 surfaceWindow->FillRectangleAligned(rcBeyondEOF, Fill(vsDraw.theEdge.colour));
2662 } else if (vsDraw.edgeState == EdgeVisualStyle::MultiLine) {
2663 for (size_t edge = 0; edge < vsDraw.theMultiEdge.size(); edge++) {
2664 if (vsDraw.theMultiEdge[edge].column >= 0) {
2665 const int edgeX = static_cast<int>(vsDraw.theMultiEdge[edge].column * vsDraw.spaceWidth);
2666 rcBeyondEOF.left = static_cast<XYPOSITION>(edgeX + xStart);
2667 rcBeyondEOF.right = rcBeyondEOF.left + 1;
2668 surfaceWindow->FillRectangleAligned(rcBeyondEOF, Fill(vsDraw.theMultiEdge[edge].colour));
2674 if (clipping)
2675 surfaceWindow->PopClip();
2677 //Platform::DebugPrintf("start display %d, offset = %d\n", model.pdoc->Length(), model.xOffset);
2678 #if defined(TIME_PAINTING)
2679 Platform::DebugPrintf(
2680 "Layout:%9.6g Paint:%9.6g Ratio:%9.6g Copy:%9.6g Total:%9.6g\n",
2681 durLayout, durPaint, durLayout / durPaint, durCopy, epWhole.Duration());
2682 #endif
2686 // Space (3 space characters) between line numbers and text when printing.
2687 #define lineNumberPrintSpace " "
2689 Sci::Position EditView::FormatRange(bool draw, CharacterRangeFull chrg, Rectangle rc, Surface *surface, Surface *surfaceMeasure,
2690 const EditModel &model, const ViewStyle &vs) {
2691 // Can't use measurements cached for screen
2692 posCache->Clear();
2694 ViewStyle vsPrint(vs);
2695 vsPrint.technology = Technology::Default;
2697 // Modify the view style for printing as do not normally want any of the transient features to be printed
2698 // Printing supports only the line number margin.
2699 int lineNumberIndex = -1;
2700 for (size_t margin = 0; margin < vs.ms.size(); margin++) {
2701 if ((vsPrint.ms[margin].style == MarginType::Number) && (vsPrint.ms[margin].width > 0)) {
2702 lineNumberIndex = static_cast<int>(margin);
2703 } else {
2704 vsPrint.ms[margin].width = 0;
2707 vsPrint.fixedColumnWidth = 0;
2708 vsPrint.zoomLevel = printParameters.magnification;
2709 // Don't show indentation guides
2710 // If this ever gets changed, cached pixmap would need to be recreated if technology != Technology::Default
2711 vsPrint.viewIndentationGuides = IndentView::None;
2712 // Don't show the selection when printing
2713 vsPrint.selection.visible = false;
2714 vsPrint.elementColours.clear();
2715 vsPrint.elementBaseColours.clear();
2716 vsPrint.caretLine.alwaysShow = false;
2717 // Don't highlight matching braces using indicators
2718 vsPrint.braceHighlightIndicatorSet = false;
2719 vsPrint.braceBadLightIndicatorSet = false;
2721 // Set colours for printing according to users settings
2722 const PrintOption colourMode = printParameters.colourMode;
2723 const std::vector<Style>::iterator endStyles = (colourMode == PrintOption::ColourOnWhiteDefaultBG) ?
2724 vsPrint.styles.begin() + StyleLineNumber : vsPrint.styles.end();
2725 for (std::vector<Style>::iterator it = vsPrint.styles.begin(); it != endStyles; ++it) {
2726 if (colourMode == PrintOption::InvertLight) {
2727 it->fore = InvertedLight(it->fore);
2728 it->back = InvertedLight(it->back);
2729 } else if (colourMode == PrintOption::BlackOnWhite) {
2730 it->fore = black;
2731 it->back = white;
2732 } else if (colourMode == PrintOption::ColourOnWhite || colourMode == PrintOption::ColourOnWhiteDefaultBG) {
2733 it->back = white;
2736 // White background for the line numbers if PrintOption::ScreenColours isn't used
2737 if (colourMode != PrintOption::ScreenColours) {
2738 vsPrint.styles[StyleLineNumber].back = white;
2741 // Printing uses different margins, so reset screen margins
2742 vsPrint.leftMarginWidth = 0;
2743 vsPrint.rightMarginWidth = 0;
2745 vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars);
2746 // Determining width must happen after fonts have been realised in Refresh
2747 int lineNumberWidth = 0;
2748 if (lineNumberIndex >= 0) {
2749 lineNumberWidth = static_cast<int>(surfaceMeasure->WidthText(vsPrint.styles[StyleLineNumber].font.get(),
2750 "99999" lineNumberPrintSpace));
2751 vsPrint.ms[lineNumberIndex].width = lineNumberWidth;
2752 vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars); // Recalculate fixedColumnWidth
2755 // Turn off change history marker backgrounds
2756 constexpr unsigned int changeMarkers =
2757 1u << static_cast<unsigned int>(MarkerOutline::HistoryRevertedToOrigin) |
2758 1u << static_cast<unsigned int>(MarkerOutline::HistorySaved) |
2759 1u << static_cast<unsigned int>(MarkerOutline::HistoryModified) |
2760 1u << static_cast<unsigned int>(MarkerOutline::HistoryRevertedToModified);
2761 vsPrint.maskInLine &= ~changeMarkers;
2763 const Sci::Line linePrintStart = model.pdoc->SciLineFromPosition(chrg.cpMin);
2764 Sci::Line linePrintLast = linePrintStart + (rc.bottom - rc.top) / vsPrint.lineHeight - 1;
2765 if (linePrintLast < linePrintStart)
2766 linePrintLast = linePrintStart;
2767 const Sci::Line linePrintMax = model.pdoc->SciLineFromPosition(chrg.cpMax);
2768 if (linePrintLast > linePrintMax)
2769 linePrintLast = linePrintMax;
2770 //Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n",
2771 // linePrintStart, linePrintLast, linePrintMax, rc.top, rc.bottom, vsPrint.lineHeight,
2772 // surfaceMeasure->Height(vsPrint.styles[StyleLineNumber].font));
2773 Sci::Position endPosPrint = model.pdoc->Length();
2774 if (linePrintLast < model.pdoc->LinesTotal())
2775 endPosPrint = model.pdoc->LineStart(linePrintLast + 1);
2777 // Ensure we are styled to where we are formatting.
2778 model.pdoc->EnsureStyledTo(endPosPrint);
2780 const int xStart = vsPrint.fixedColumnWidth + rc.left;
2781 int ypos = rc.top;
2783 Sci::Line lineDoc = linePrintStart;
2785 Sci::Position nPrintPos = chrg.cpMin;
2786 int visibleLine = 0;
2787 int widthPrint = rc.right - rc.left - vsPrint.fixedColumnWidth;
2788 if (printParameters.wrapState == Wrap::None)
2789 widthPrint = LineLayout::wrapWidthInfinite;
2791 while (lineDoc <= linePrintLast && ypos < rc.bottom) {
2793 // When printing, the hdc and hdcTarget may be the same, so
2794 // changing the state of surfaceMeasure may change the underlying
2795 // state of surface. Therefore, any cached state is discarded before
2796 // using each surface.
2797 surfaceMeasure->FlushCachedState();
2799 // Copy this line and its styles from the document into local arrays
2800 // and determine the x position at which each character starts.
2801 LineLayout ll(lineDoc, static_cast<int>(model.pdoc->LineStart(lineDoc + 1) - model.pdoc->LineStart(lineDoc) + 1));
2802 LayoutLine(model, surfaceMeasure, vsPrint, &ll, widthPrint);
2804 ll.containsCaret = false;
2806 PRectangle rcLine = PRectangle::FromInts(
2807 rc.left,
2808 ypos,
2809 rc.right - 1,
2810 ypos + vsPrint.lineHeight);
2812 // When document line is wrapped over multiple display lines, find where
2813 // to start printing from to ensure a particular position is on the first
2814 // line of the page.
2815 if (visibleLine == 0) {
2816 const Sci::Position startWithinLine = nPrintPos -
2817 model.pdoc->LineStart(lineDoc);
2818 for (int iwl = 0; iwl < ll.lines - 1; iwl++) {
2819 if (ll.LineStart(iwl) <= startWithinLine && ll.LineStart(iwl + 1) >= startWithinLine) {
2820 visibleLine = -iwl;
2824 if (ll.lines > 1 && startWithinLine >= ll.LineStart(ll.lines - 1)) {
2825 visibleLine = -(ll.lines - 1);
2829 if (draw && lineNumberWidth &&
2830 (ypos + vsPrint.lineHeight <= rc.bottom) &&
2831 (visibleLine >= 0)) {
2832 const std::string number = std::to_string(lineDoc + 1) + lineNumberPrintSpace;
2833 PRectangle rcNumber = rcLine;
2834 rcNumber.right = rcNumber.left + lineNumberWidth;
2835 // Right justify
2836 rcNumber.left = rcNumber.right - surfaceMeasure->WidthText(
2837 vsPrint.styles[StyleLineNumber].font.get(), number);
2838 surface->FlushCachedState();
2839 surface->DrawTextNoClip(rcNumber, vsPrint.styles[StyleLineNumber].font.get(),
2840 ypos + vsPrint.maxAscent, number,
2841 vsPrint.styles[StyleLineNumber].fore,
2842 vsPrint.styles[StyleLineNumber].back);
2845 // Draw the line
2846 surface->FlushCachedState();
2848 for (int iwl = 0; iwl < ll.lines; iwl++) {
2849 if (ypos + vsPrint.lineHeight <= rc.bottom) {
2850 if (visibleLine >= 0) {
2851 if (draw) {
2852 rcLine.top = static_cast<XYPOSITION>(ypos);
2853 rcLine.bottom = static_cast<XYPOSITION>(ypos + vsPrint.lineHeight);
2854 DrawLine(surface, model, vsPrint, &ll, lineDoc, visibleLine, xStart, rcLine, iwl, DrawPhase::all);
2856 ypos += vsPrint.lineHeight;
2858 visibleLine++;
2859 if (iwl == ll.lines - 1)
2860 nPrintPos = model.pdoc->LineStart(lineDoc + 1);
2861 else
2862 nPrintPos += ll.LineStart(iwl + 1) - ll.LineStart(iwl);
2866 ++lineDoc;
2869 // Clear cache so measurements are not used for screen
2870 posCache->Clear();
2872 return nPrintPos;