1 | // Scintilla source code edit control |
2 | /** @file MarginView.cxx |
3 | ** Defines the appearance of the editor margin. |
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. |
7 | |
8 | #include <cstddef> |
9 | #include <cstdlib> |
10 | #include <cstdint> |
11 | #include <cassert> |
12 | #include <cstring> |
13 | #include <cstdio> |
14 | #include <cmath> |
15 | |
16 | #include <stdexcept> |
17 | #include <string> |
18 | #include <string_view> |
19 | #include <vector> |
20 | #include <map> |
21 | #include <set> |
22 | #include <optional> |
23 | #include <algorithm> |
24 | #include <memory> |
25 | |
26 | #include "ScintillaTypes.h" |
27 | #include "ScintillaMessages.h" |
28 | #include "ScintillaStructures.h" |
29 | #include "ILoader.h" |
30 | #include "ILexer.h" |
31 | |
32 | #include "Debugging.h" |
33 | #include "Geometry.h" |
34 | #include "Platform.h" |
35 | |
36 | #include "CharacterCategoryMap.h" |
37 | #include "Position.h" |
38 | #include "UniqueString.h" |
39 | #include "SplitVector.h" |
40 | #include "Partitioning.h" |
41 | #include "RunStyles.h" |
42 | #include "ContractionState.h" |
43 | #include "CellBuffer.h" |
44 | #include "KeyMap.h" |
45 | #include "Indicator.h" |
46 | #include "LineMarker.h" |
47 | #include "Style.h" |
48 | #include "ViewStyle.h" |
49 | #include "CharClassify.h" |
50 | #include "Decoration.h" |
51 | #include "CaseFolder.h" |
52 | #include "Document.h" |
53 | #include "UniConversion.h" |
54 | #include "Selection.h" |
55 | #include "PositionCache.h" |
56 | #include "EditModel.h" |
57 | #include "MarginView.h" |
58 | #include "EditView.h" |
59 | |
60 | using namespace Scintilla; |
61 | |
62 | namespace Scintilla::Internal { |
63 | |
64 | void DrawWrapMarker(Surface *surface, PRectangle rcPlace, |
65 | bool isEndMarker, ColourRGBA wrapColour) { |
66 | |
67 | const XYPOSITION = surface->SupportsFeature(Supports::LineDrawsFinal) ? 0.0f : 1.0f; |
68 | |
69 | const PRectangle rcAligned = PixelAlignOutside(rcPlace, surface->PixelDivisions()); |
70 | |
71 | const XYPOSITION widthStroke = std::floor(rcAligned.Width() / 6); |
72 | |
73 | constexpr XYPOSITION xa = 1; // gap before start |
74 | const XYPOSITION w = rcAligned.Width() - xa - widthStroke; |
75 | |
76 | // isEndMarker -> x-mirrored symbol for start marker |
77 | |
78 | const XYPOSITION x0 = isEndMarker ? rcAligned.left : rcAligned.right - widthStroke; |
79 | const XYPOSITION y0 = rcAligned.top; |
80 | |
81 | const XYPOSITION dy = std::floor(rcAligned.Height() / 5); |
82 | const XYPOSITION y = std::floor(rcAligned.Height() / 2) + dy; |
83 | |
84 | struct Relative { |
85 | XYPOSITION xBase; |
86 | int xDir; |
87 | XYPOSITION yBase; |
88 | int yDir; |
89 | XYPOSITION halfWidth; |
90 | Point At(XYPOSITION xRelative, XYPOSITION yRelative) const noexcept { |
91 | return Point(xBase + xDir * xRelative + halfWidth, yBase + yDir * yRelative + halfWidth); |
92 | } |
93 | }; |
94 | |
95 | Relative rel = { x0, isEndMarker ? 1 : -1, y0, 1, widthStroke / 2.0f }; |
96 | |
97 | // arrow head |
98 | const Point head[] = { |
99 | rel.At(xa + dy, y - dy), |
100 | rel.At(xa, y), |
101 | rel.At(xa + dy + extraFinalPixel, y + dy + extraFinalPixel) |
102 | }; |
103 | surface->PolyLine(head, std::size(head), Stroke(wrapColour, widthStroke)); |
104 | |
105 | // arrow body |
106 | const Point body[] = { |
107 | rel.At(xa, y), |
108 | rel.At(xa + w, y), |
109 | rel.At(xa + w, y - 2 * dy), |
110 | rel.At(xa, y - 2 * dy), |
111 | }; |
112 | surface->PolyLine(body, std::size(body), Stroke(wrapColour, widthStroke)); |
113 | } |
114 | |
115 | MarginView::MarginView() noexcept { |
116 | wrapMarkerPaddingRight = 3; |
117 | customDrawWrapMarker = nullptr; |
118 | } |
119 | |
120 | void MarginView::DropGraphics() noexcept { |
121 | pixmapSelMargin.reset(); |
122 | pixmapSelPattern.reset(); |
123 | pixmapSelPatternOffset1.reset(); |
124 | } |
125 | |
126 | void MarginView::RefreshPixMaps(Surface *surfaceWindow, const ViewStyle &vsDraw) { |
127 | if (!pixmapSelPattern) { |
128 | constexpr int patternSize = 8; |
129 | pixmapSelPattern = surfaceWindow->AllocatePixMap(patternSize, patternSize); |
130 | pixmapSelPatternOffset1 = surfaceWindow->AllocatePixMap(patternSize, patternSize); |
131 | // This complex procedure is to reproduce the checkerboard dithered pattern used by windows |
132 | // for scroll bars and Visual Studio for its selection margin. The colour of this pattern is half |
133 | // way between the chrome colour and the chrome highlight colour making a nice transition |
134 | // between the window chrome and the content area. And it works in low colour depths. |
135 | const PRectangle rcPattern = PRectangle::FromInts(0, 0, patternSize, patternSize); |
136 | |
137 | // Initialize default colours based on the chrome colour scheme. Typically the highlight is white. |
138 | ColourRGBA colourFMFill = vsDraw.selbar; |
139 | ColourRGBA colourFMStripes = vsDraw.selbarlight; |
140 | |
141 | if (!(vsDraw.selbarlight == ColourRGBA(0xff, 0xff, 0xff))) { |
142 | // User has chosen an unusual chrome colour scheme so just use the highlight edge colour. |
143 | // (Typically, the highlight colour is white.) |
144 | colourFMFill = vsDraw.selbarlight; |
145 | } |
146 | |
147 | if (vsDraw.foldmarginColour) { |
148 | // override default fold margin colour |
149 | colourFMFill = *vsDraw.foldmarginColour; |
150 | } |
151 | if (vsDraw.foldmarginHighlightColour) { |
152 | // override default fold margin highlight colour |
153 | colourFMStripes = *vsDraw.foldmarginHighlightColour; |
154 | } |
155 | |
156 | pixmapSelPattern->FillRectangle(rcPattern, colourFMFill); |
157 | pixmapSelPatternOffset1->FillRectangle(rcPattern, colourFMStripes); |
158 | for (int y = 0; y < patternSize; y++) { |
159 | for (int x = y % 2; x < patternSize; x += 2) { |
160 | const PRectangle rcPixel = PRectangle::FromInts(x, y, x + 1, y + 1); |
161 | pixmapSelPattern->FillRectangle(rcPixel, colourFMStripes); |
162 | pixmapSelPatternOffset1->FillRectangle(rcPixel, colourFMFill); |
163 | } |
164 | } |
165 | pixmapSelPattern->FlushDrawing(); |
166 | pixmapSelPatternOffset1->FlushDrawing(); |
167 | } |
168 | } |
169 | |
170 | namespace { |
171 | |
172 | MarkerOutline SubstituteMarkerIfEmpty(MarkerOutline markerCheck, MarkerOutline markerDefault, const ViewStyle &vs) noexcept { |
173 | if (vs.markers[static_cast<size_t>(markerCheck)].markType == MarkerSymbol::Empty) |
174 | return markerDefault; |
175 | return markerCheck; |
176 | } |
177 | |
178 | constexpr MarkerOutline TailFromNextLevel(FoldLevel levelNextNum) noexcept { |
179 | return (levelNextNum > FoldLevel::Base) ? MarkerOutline::FolderMidTail : MarkerOutline::FolderTail; |
180 | } |
181 | |
182 | int FoldingMark(FoldLevel level, FoldLevel levelNext, bool firstSubLine, bool lastSubLine, |
183 | bool isExpanded, bool needWhiteClosure, MarkerOutline folderOpenMid, MarkerOutline folderEnd) noexcept { |
184 | |
185 | const FoldLevel levelNum = LevelNumberPart(level); |
186 | const FoldLevel levelNextNum = LevelNumberPart(levelNext); |
187 | |
188 | if (LevelIsHeader(level)) { |
189 | if (firstSubLine) { |
190 | if (levelNum < levelNextNum) { |
191 | if (levelNum == FoldLevel::Base) { |
192 | return 1 << (isExpanded ? MarkerOutline::FolderOpen : MarkerOutline::Folder); |
193 | } else { |
194 | return 1 << (isExpanded ? folderOpenMid : folderEnd); |
195 | } |
196 | } else if (levelNum > FoldLevel::Base) { |
197 | return 1 << MarkerOutline::FolderSub; |
198 | } |
199 | } else { |
200 | if (levelNum < levelNextNum) { |
201 | if (isExpanded) { |
202 | return 1 << MarkerOutline::FolderSub; |
203 | } else if (levelNum > FoldLevel::Base) { |
204 | return 1 << MarkerOutline::FolderSub; |
205 | } |
206 | } else if (levelNum > FoldLevel::Base) { |
207 | return 1 << MarkerOutline::FolderSub; |
208 | } |
209 | } |
210 | } else if (LevelIsWhitespace(level)) { |
211 | if (needWhiteClosure) { |
212 | if (LevelIsWhitespace(levelNext)) { |
213 | return 1 << MarkerOutline::FolderSub; |
214 | } else { |
215 | return 1 << TailFromNextLevel(levelNextNum); |
216 | } |
217 | } else if (levelNum > FoldLevel::Base) { |
218 | if (levelNextNum < levelNum) { |
219 | return 1 << TailFromNextLevel(levelNextNum); |
220 | } else { |
221 | return 1 << MarkerOutline::FolderSub; |
222 | } |
223 | } |
224 | } else if (levelNum > FoldLevel::Base) { |
225 | if (levelNextNum < levelNum) { |
226 | if (LevelIsWhitespace(levelNext)) { |
227 | return 1 << MarkerOutline::FolderSub; |
228 | } else if (lastSubLine) { |
229 | return 1 << TailFromNextLevel(levelNextNum); |
230 | } else { |
231 | return 1 << MarkerOutline::FolderSub; |
232 | } |
233 | } else { |
234 | return 1 << MarkerOutline::FolderSub; |
235 | } |
236 | } |
237 | |
238 | // No folding mark on this line |
239 | return 0; |
240 | } |
241 | |
242 | } |
243 | |
244 | void MarginView::PaintOneMargin(Surface *surface, PRectangle rc, PRectangle rcOneMargin, const MarginStyle &marginStyle, |
245 | const EditModel &model, const ViewStyle &vs) { |
246 | const Point ptOrigin = model.GetVisibleOriginInMain(); |
247 | const Sci::Line lineStartPaint = static_cast<Sci::Line>(rcOneMargin.top + ptOrigin.y) / vs.lineHeight; |
248 | Sci::Line visibleLine = model.TopLineOfMain() + lineStartPaint; |
249 | XYPOSITION yposScreen = lineStartPaint * vs.lineHeight - ptOrigin.y; |
250 | // Work out whether the top line is whitespace located after a |
251 | // lessening of fold level which implies a 'fold tail' but which should not |
252 | // be displayed until the last of a sequence of whitespace. |
253 | bool needWhiteClosure = false; |
254 | if (marginStyle.ShowsFolding()) { |
255 | const FoldLevel level = model.pdoc->GetFoldLevel(model.pcs->DocFromDisplay(visibleLine)); |
256 | if (LevelIsWhitespace(level)) { |
257 | Sci::Line lineBack = model.pcs->DocFromDisplay(visibleLine); |
258 | FoldLevel levelPrev = level; |
259 | while ((lineBack > 0) && LevelIsWhitespace(levelPrev)) { |
260 | lineBack--; |
261 | levelPrev = model.pdoc->GetFoldLevel(lineBack); |
262 | } |
263 | if (!LevelIsHeader(levelPrev)) { |
264 | if (LevelNumber(level) < LevelNumber(levelPrev)) |
265 | needWhiteClosure = true; |
266 | } |
267 | } |
268 | } |
269 | |
270 | // Old code does not know about new markers needed to distinguish all cases |
271 | const MarkerOutline folderOpenMid = SubstituteMarkerIfEmpty(MarkerOutline::FolderOpenMid, |
272 | MarkerOutline::FolderOpen, vs); |
273 | const MarkerOutline folderEnd = SubstituteMarkerIfEmpty(MarkerOutline::FolderEnd, |
274 | MarkerOutline::Folder, vs); |
275 | |
276 | while ((visibleLine < model.pcs->LinesDisplayed()) && yposScreen < rc.bottom) { |
277 | |
278 | PLATFORM_ASSERT(visibleLine < model.pcs->LinesDisplayed()); |
279 | const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine); |
280 | PLATFORM_ASSERT((lineDoc == 0) || model.pcs->GetVisible(lineDoc)); |
281 | const Sci::Line firstVisibleLine = model.pcs->DisplayFromDoc(lineDoc); |
282 | const Sci::Line lastVisibleLine = model.pcs->DisplayLastFromDoc(lineDoc); |
283 | const bool firstSubLine = visibleLine == firstVisibleLine; |
284 | const bool lastSubLine = visibleLine == lastVisibleLine; |
285 | |
286 | int marks = firstSubLine ? model.pdoc->GetMark(lineDoc) : 0; |
287 | |
288 | bool headWithTail = false; |
289 | |
290 | if (marginStyle.ShowsFolding()) { |
291 | // Decide which fold indicator should be displayed |
292 | const FoldLevel level = model.pdoc->GetFoldLevel(lineDoc); |
293 | const FoldLevel levelNext = model.pdoc->GetFoldLevel(lineDoc + 1); |
294 | const FoldLevel levelNum = LevelNumberPart(level); |
295 | const FoldLevel levelNextNum = LevelNumberPart(levelNext); |
296 | const bool isExpanded = model.pcs->GetExpanded(lineDoc); |
297 | |
298 | marks |= FoldingMark(level, levelNext, firstSubLine, lastSubLine, |
299 | isExpanded, needWhiteClosure, folderOpenMid, folderEnd); |
300 | |
301 | // Change needWhiteClosure and headWithTail if needed |
302 | if (LevelIsHeader(level)) { |
303 | needWhiteClosure = false; |
304 | const Sci::Line firstFollowupLine = model.pcs->DocFromDisplay(model.pcs->DisplayFromDoc(lineDoc + 1)); |
305 | const FoldLevel firstFollowupLineLevel = model.pdoc->GetFoldLevel(firstFollowupLine); |
306 | const FoldLevel secondFollowupLineLevelNum = LevelNumberPart(model.pdoc->GetFoldLevel(firstFollowupLine + 1)); |
307 | if (!isExpanded) { |
308 | if (LevelIsWhitespace(firstFollowupLineLevel) && |
309 | (levelNum > secondFollowupLineLevelNum)) |
310 | needWhiteClosure = true; |
311 | |
312 | if (highlightDelimiter.IsFoldBlockHighlighted(firstFollowupLine)) |
313 | headWithTail = true; |
314 | } |
315 | } else if (LevelIsWhitespace(level)) { |
316 | if (needWhiteClosure) { |
317 | needWhiteClosure = LevelIsWhitespace(levelNext); |
318 | } |
319 | } else if (levelNum > FoldLevel::Base) { |
320 | if (levelNextNum < levelNum) { |
321 | needWhiteClosure = LevelIsWhitespace(levelNext); |
322 | } |
323 | } |
324 | } |
325 | |
326 | const PRectangle rcMarker( |
327 | rcOneMargin.left, |
328 | yposScreen, |
329 | rcOneMargin.right, |
330 | yposScreen + vs.lineHeight); |
331 | if (marginStyle.style == MarginType::Number) { |
332 | if (firstSubLine) { |
333 | std::string sNumber; |
334 | if (lineDoc >= 0) { |
335 | sNumber = std::to_string(lineDoc + 1); |
336 | } |
337 | if (FlagSet(model.foldFlags, (FoldFlag::LevelNumbers | FoldFlag::LineState))) { |
338 | char number[100] = "" ; |
339 | if (FlagSet(model.foldFlags, FoldFlag::LevelNumbers)) { |
340 | const FoldLevel lev = model.pdoc->GetFoldLevel(lineDoc); |
341 | sprintf(number, "%c%c %03X %03X" , |
342 | LevelIsHeader(lev) ? 'H' : '_', |
343 | LevelIsWhitespace(lev) ? 'W' : '_', |
344 | LevelNumber(lev), |
345 | static_cast<int>(lev) >> 16 |
346 | ); |
347 | } else { |
348 | const int state = model.pdoc->GetLineState(lineDoc); |
349 | sprintf(number, "%0X" , state); |
350 | } |
351 | sNumber = number; |
352 | } |
353 | PRectangle rcNumber = rcMarker; |
354 | // Right justify |
355 | const XYPOSITION width = surface->WidthText(vs.styles[StyleLineNumber].font.get(), sNumber); |
356 | const XYPOSITION xpos = rcNumber.right - width - vs.marginNumberPadding; |
357 | rcNumber.left = xpos; |
358 | DrawTextNoClipPhase(surface, rcNumber, vs.styles[StyleLineNumber], |
359 | rcNumber.top + vs.maxAscent, sNumber, DrawPhase::all); |
360 | } else if (FlagSet(vs.wrap.visualFlags, WrapVisualFlag::Margin)) { |
361 | PRectangle rcWrapMarker = rcMarker; |
362 | rcWrapMarker.right -= wrapMarkerPaddingRight; |
363 | rcWrapMarker.left = rcWrapMarker.right - vs.styles[StyleLineNumber].aveCharWidth; |
364 | if (!customDrawWrapMarker) { |
365 | DrawWrapMarker(surface, rcWrapMarker, false, vs.styles[StyleLineNumber].fore); |
366 | } else { |
367 | customDrawWrapMarker(surface, rcWrapMarker, false, vs.styles[StyleLineNumber].fore); |
368 | } |
369 | } |
370 | } else if (marginStyle.style == MarginType::Text || marginStyle.style == MarginType::RText) { |
371 | const StyledText stMargin = model.pdoc->MarginStyledText(lineDoc); |
372 | if (stMargin.text && ValidStyledText(vs, vs.marginStyleOffset, stMargin)) { |
373 | if (firstSubLine) { |
374 | surface->FillRectangle(rcMarker, |
375 | vs.styles[stMargin.StyleAt(0) + vs.marginStyleOffset].back); |
376 | PRectangle rcText = rcMarker; |
377 | if (marginStyle.style == MarginType::RText) { |
378 | const int width = WidestLineWidth(surface, vs, vs.marginStyleOffset, stMargin); |
379 | rcText.left = rcText.right - width - 3; |
380 | } |
381 | DrawStyledText(surface, vs, vs.marginStyleOffset, rcText, |
382 | stMargin, 0, stMargin.length, DrawPhase::all); |
383 | } else { |
384 | // if we're displaying annotation lines, colour the margin to match the associated document line |
385 | const int annotationLines = model.pdoc->AnnotationLines(lineDoc); |
386 | if (annotationLines && (visibleLine > lastVisibleLine - annotationLines)) { |
387 | surface->FillRectangle(rcMarker, vs.styles[stMargin.StyleAt(0) + vs.marginStyleOffset].back); |
388 | } |
389 | } |
390 | } |
391 | } |
392 | |
393 | marks &= marginStyle.mask; |
394 | |
395 | if (marks) { |
396 | for (int markBit = 0; (markBit < 32) && marks; markBit++) { |
397 | if (marks & 1) { |
398 | LineMarker::FoldPart part = LineMarker::FoldPart::undefined; |
399 | if (marginStyle.ShowsFolding() && highlightDelimiter.IsFoldBlockHighlighted(lineDoc)) { |
400 | if (highlightDelimiter.IsBodyOfFoldBlock(lineDoc)) { |
401 | part = LineMarker::FoldPart::body; |
402 | } else if (highlightDelimiter.IsHeadOfFoldBlock(lineDoc)) { |
403 | if (firstSubLine) { |
404 | part = headWithTail ? LineMarker::FoldPart::headWithTail : LineMarker::FoldPart::head; |
405 | } else { |
406 | if (model.pcs->GetExpanded(lineDoc) || headWithTail) { |
407 | part = LineMarker::FoldPart::body; |
408 | } else { |
409 | part = LineMarker::FoldPart::undefined; |
410 | } |
411 | } |
412 | } else if (highlightDelimiter.IsTailOfFoldBlock(lineDoc)) { |
413 | part = LineMarker::FoldPart::tail; |
414 | } |
415 | } |
416 | vs.markers[markBit].Draw(surface, rcMarker, vs.styles[StyleLineNumber].font.get(), part, marginStyle.style); |
417 | } |
418 | marks >>= 1; |
419 | } |
420 | } |
421 | |
422 | visibleLine++; |
423 | yposScreen += vs.lineHeight; |
424 | } |
425 | } |
426 | |
427 | void MarginView::PaintMargin(Surface *surface, Sci::Line topLine, PRectangle rc, PRectangle rcMargin, |
428 | const EditModel &model, const ViewStyle &vs) { |
429 | |
430 | PRectangle rcOneMargin = rcMargin; |
431 | rcOneMargin.right = rcMargin.left; |
432 | if (rcOneMargin.bottom < rc.bottom) |
433 | rcOneMargin.bottom = rc.bottom; |
434 | |
435 | const Point ptOrigin = model.GetVisibleOriginInMain(); |
436 | for (const MarginStyle &marginStyle : vs.ms) { |
437 | if (marginStyle.width > 0) { |
438 | |
439 | rcOneMargin.left = rcOneMargin.right; |
440 | rcOneMargin.right = rcOneMargin.left + marginStyle.width; |
441 | |
442 | if (marginStyle.style != MarginType::Number) { |
443 | if (marginStyle.ShowsFolding()) { |
444 | // Required because of special way brush is created for selection margin |
445 | // Ensure patterns line up when scrolling with separate margin view |
446 | // by choosing correctly aligned variant. |
447 | const bool invertPhase = static_cast<int>(ptOrigin.y) & 1; |
448 | surface->FillRectangle(rcOneMargin, |
449 | invertPhase ? *pixmapSelPattern : *pixmapSelPatternOffset1); |
450 | } else { |
451 | ColourRGBA colour; |
452 | switch (marginStyle.style) { |
453 | case MarginType::Back: |
454 | colour = vs.styles[StyleDefault].back; |
455 | break; |
456 | case MarginType::Fore: |
457 | colour = vs.styles[StyleDefault].fore; |
458 | break; |
459 | case MarginType::Colour: |
460 | colour = marginStyle.back; |
461 | break; |
462 | default: |
463 | colour = vs.styles[StyleLineNumber].back; |
464 | break; |
465 | } |
466 | surface->FillRectangle(rcOneMargin, colour); |
467 | } |
468 | } else { |
469 | surface->FillRectangle(rcOneMargin, vs.styles[StyleLineNumber].back); |
470 | } |
471 | |
472 | if (marginStyle.ShowsFolding() && highlightDelimiter.isEnabled) { |
473 | const Sci::Line lastLine = model.pcs->DocFromDisplay(topLine + model.LinesOnScreen()) + 1; |
474 | model.pdoc->GetHighlightDelimiters(highlightDelimiter, |
475 | model.pdoc->SciLineFromPosition(model.sel.MainCaret()), lastLine); |
476 | } |
477 | |
478 | PaintOneMargin(surface, rc, rcOneMargin, marginStyle, model, vs); |
479 | } |
480 | } |
481 | |
482 | PRectangle rcBlankMargin = rcMargin; |
483 | rcBlankMargin.left = rcOneMargin.right; |
484 | surface->FillRectangle(rcBlankMargin, vs.styles[StyleDefault].back); |
485 | } |
486 | |
487 | } |
488 | |
489 | |