Use tm_parser_scope_separator() instead of symbols_get_context_separator()
[geany-mirror.git] / scintilla / src / MarginView.cxx
blobcc243f41fd35f31162ee2e67fa9b95b0ba45a536
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.
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 <optional>
23 #include <algorithm>
24 #include <memory>
26 #include "ScintillaTypes.h"
27 #include "ScintillaMessages.h"
28 #include "ScintillaStructures.h"
29 #include "ILoader.h"
30 #include "ILexer.h"
32 #include "Debugging.h"
33 #include "Geometry.h"
34 #include "Platform.h"
36 #include "CharacterCategoryMap.h"
37 #include "Position.h"
38 #include "UniqueString.h"
39 #include "SplitVector.h"
40 #include "Partitioning.h"
41 #include "RunStyles.h"
42 #include "ContractionState.h"
43 #include "CellBuffer.h"
44 #include "KeyMap.h"
45 #include "Indicator.h"
46 #include "LineMarker.h"
47 #include "Style.h"
48 #include "ViewStyle.h"
49 #include "CharClassify.h"
50 #include "Decoration.h"
51 #include "CaseFolder.h"
52 #include "Document.h"
53 #include "UniConversion.h"
54 #include "Selection.h"
55 #include "PositionCache.h"
56 #include "EditModel.h"
57 #include "MarginView.h"
58 #include "EditView.h"
60 using namespace Scintilla;
62 namespace Scintilla::Internal {
64 void DrawWrapMarker(Surface *surface, PRectangle rcPlace,
65 bool isEndMarker, ColourRGBA wrapColour) {
67 const XYPOSITION extraFinalPixel = surface->SupportsFeature(Supports::LineDrawsFinal) ? 0.0f : 1.0f;
69 const PRectangle rcAligned = PixelAlignOutside(rcPlace, surface->PixelDivisions());
71 const XYPOSITION widthStroke = std::floor(rcAligned.Width() / 6);
73 constexpr XYPOSITION xa = 1; // gap before start
74 const XYPOSITION w = rcAligned.Width() - xa - widthStroke;
76 // isEndMarker -> x-mirrored symbol for start marker
78 const XYPOSITION x0 = isEndMarker ? rcAligned.left : rcAligned.right - widthStroke;
79 const XYPOSITION y0 = rcAligned.top;
81 const XYPOSITION dy = std::floor(rcAligned.Height() / 5);
82 const XYPOSITION y = std::floor(rcAligned.Height() / 2) + dy;
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);
95 Relative rel = { x0, isEndMarker ? 1 : -1, y0, 1, widthStroke / 2.0f };
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)
103 surface->PolyLine(head, std::size(head), Stroke(wrapColour, widthStroke));
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),
112 surface->PolyLine(body, std::size(body), Stroke(wrapColour, widthStroke));
115 MarginView::MarginView() noexcept {
116 wrapMarkerPaddingRight = 3;
117 customDrawWrapMarker = nullptr;
120 void MarginView::DropGraphics() noexcept {
121 pixmapSelMargin.reset();
122 pixmapSelPattern.reset();
123 pixmapSelPatternOffset1.reset();
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);
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;
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;
147 if (vsDraw.foldmarginColour) {
148 // override default fold margin colour
149 colourFMFill = *vsDraw.foldmarginColour;
151 if (vsDraw.foldmarginHighlightColour) {
152 // override default fold margin highlight colour
153 colourFMStripes = *vsDraw.foldmarginHighlightColour;
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);
165 pixmapSelPattern->FlushDrawing();
166 pixmapSelPatternOffset1->FlushDrawing();
170 namespace {
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;
178 constexpr MarkerOutline TailFromNextLevel(FoldLevel levelNextNum) noexcept {
179 return (levelNextNum > FoldLevel::Base) ? MarkerOutline::FolderMidTail : MarkerOutline::FolderTail;
182 int FoldingMark(FoldLevel level, FoldLevel levelNext, bool firstSubLine, bool lastSubLine,
183 bool isExpanded, bool needWhiteClosure, MarkerOutline folderOpenMid, MarkerOutline folderEnd) noexcept {
185 const FoldLevel levelNum = LevelNumberPart(level);
186 const FoldLevel levelNextNum = LevelNumberPart(levelNext);
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);
196 } else if (levelNum > FoldLevel::Base) {
197 return 1 << MarkerOutline::FolderSub;
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;
206 } else if (levelNum > FoldLevel::Base) {
207 return 1 << MarkerOutline::FolderSub;
210 } else if (LevelIsWhitespace(level)) {
211 if (needWhiteClosure) {
212 if (LevelIsWhitespace(levelNext)) {
213 return 1 << MarkerOutline::FolderSub;
214 } else {
215 return 1 << TailFromNextLevel(levelNextNum);
217 } else if (levelNum > FoldLevel::Base) {
218 if (levelNextNum < levelNum) {
219 return 1 << TailFromNextLevel(levelNextNum);
220 } else {
221 return 1 << MarkerOutline::FolderSub;
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;
233 } else {
234 return 1 << MarkerOutline::FolderSub;
238 // No folding mark on this line
239 return 0;
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);
263 if (!LevelIsHeader(levelPrev)) {
264 if (LevelNumber(level) < LevelNumber(levelPrev))
265 needWhiteClosure = true;
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);
276 while ((visibleLine < model.pcs->LinesDisplayed()) && yposScreen < rc.bottom) {
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;
286 int marks = firstSubLine ? model.pdoc->GetMark(lineDoc) : 0;
288 bool headWithTail = false;
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);
298 marks |= FoldingMark(level, levelNext, firstSubLine, lastSubLine,
299 isExpanded, needWhiteClosure, folderOpenMid, folderEnd);
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;
312 if (highlightDelimiter.IsFoldBlockHighlighted(firstFollowupLine))
313 headWithTail = true;
315 } else if (LevelIsWhitespace(level)) {
316 if (needWhiteClosure) {
317 needWhiteClosure = LevelIsWhitespace(levelNext);
319 } else if (levelNum > FoldLevel::Base) {
320 if (levelNextNum < levelNum) {
321 needWhiteClosure = LevelIsWhitespace(levelNext);
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);
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
347 } else {
348 const int state = model.pdoc->GetLineState(lineDoc);
349 sprintf(number, "%0X", state);
351 sNumber = number;
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);
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;
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);
393 marks &= marginStyle.mask;
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;
412 } else if (highlightDelimiter.IsTailOfFoldBlock(lineDoc)) {
413 part = LineMarker::FoldPart::tail;
416 vs.markers[markBit].Draw(surface, rcMarker, vs.styles[StyleLineNumber].font.get(), part, marginStyle.style);
418 marks >>= 1;
422 visibleLine++;
423 yposScreen += vs.lineHeight;
427 void MarginView::PaintMargin(Surface *surface, Sci::Line topLine, PRectangle rc, PRectangle rcMargin,
428 const EditModel &model, const ViewStyle &vs) {
430 PRectangle rcOneMargin = rcMargin;
431 rcOneMargin.right = rcMargin.left;
432 if (rcOneMargin.bottom < rc.bottom)
433 rcOneMargin.bottom = rc.bottom;
435 const Point ptOrigin = model.GetVisibleOriginInMain();
436 for (const MarginStyle &marginStyle : vs.ms) {
437 if (marginStyle.width > 0) {
439 rcOneMargin.left = rcOneMargin.right;
440 rcOneMargin.right = rcOneMargin.left + marginStyle.width;
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;
466 surface->FillRectangle(rcOneMargin, colour);
468 } else {
469 surface->FillRectangle(rcOneMargin, vs.styles[StyleLineNumber].back);
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);
478 PaintOneMargin(surface, rc, rcOneMargin, marginStyle, model, vs);
482 PRectangle rcBlankMargin = rcMargin;
483 rcBlankMargin.left = rcOneMargin.right;
484 surface->FillRectangle(rcBlankMargin, vs.styles[StyleDefault].back);