// Scintilla source code edit control /** @file Editor.cxx ** Main code for the edit control. **/ // Copyright 1998-2011 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include #include #include #include #include #include #include "Platform.h" #include "ILexer.h" #include "Scintilla.h" #include "SplitVector.h" #include "Partitioning.h" #include "RunStyles.h" #include "ContractionState.h" #include "CellBuffer.h" #include "KeyMap.h" #include "Indicator.h" #include "XPM.h" #include "LineMarker.h" #include "Style.h" #include "ViewStyle.h" #include "CharClassify.h" #include "Decoration.h" #include "CaseFolder.h" #include "Document.h" #include "UniConversion.h" #include "Selection.h" #include "PositionCache.h" #include "Editor.h" #ifdef SCI_NAMESPACE using namespace Scintilla; #endif /* return whether this modification represents an operation that may reasonably be deferred (not done now OR [possibly] at all) */ static bool CanDeferToLastStep(const DocModification &mh) { if (mh.modificationType & (SC_MOD_BEFOREINSERT | SC_MOD_BEFOREDELETE)) return true; // CAN skip if (!(mh.modificationType & (SC_PERFORMED_UNDO | SC_PERFORMED_REDO))) return false; // MUST do if (mh.modificationType & SC_MULTISTEPUNDOREDO) return true; // CAN skip return false; // PRESUMABLY must do } static bool CanEliminate(const DocModification &mh) { return (mh.modificationType & (SC_MOD_BEFOREINSERT | SC_MOD_BEFOREDELETE)) != 0; } /* return whether this modification represents the FINAL step in a [possibly lengthy] multi-step Undo/Redo sequence */ static bool IsLastStep(const DocModification &mh) { return (mh.modificationType & (SC_PERFORMED_UNDO | SC_PERFORMED_REDO)) != 0 && (mh.modificationType & SC_MULTISTEPUNDOREDO) != 0 && (mh.modificationType & SC_LASTSTEPINUNDOREDO) != 0 && (mh.modificationType & SC_MULTILINEUNDOREDO) != 0; } Caret::Caret() : active(false), on(false), period(500) {} Timer::Timer() : ticking(false), ticksToWait(0), tickerID(0) {} Idler::Idler() : state(false), idlerID(0) {} static inline bool IsControlCharacter(int ch) { // iscntrl returns true for lots of chars > 127 which are displayable return ch >= 0 && ch < ' '; } static inline bool IsAllSpacesOrTabs(char *s, unsigned int len) { for (unsigned int i = 0; i < len; i++) { // This is safe because IsSpaceOrTab() will return false for null terminators if (!IsSpaceOrTab(s[i])) return false; } return true; } PrintParameters::PrintParameters() { magnification = 0; colourMode = SC_PRINT_NORMAL; wrapState = eWrapWord; } Editor::Editor() { ctrlID = 0; stylesValid = false; technology = SC_TECHNOLOGY_DEFAULT; scaleRGBAImage = 100; cursorMode = SC_CURSORNORMAL; hasFocus = false; hideSelection = false; inOverstrike = false; drawOverstrikeCaret = true; errorStatus = 0; mouseDownCaptures = true; bufferedDraw = true; twoPhaseDraw = true; lastClickTime = 0; dwellDelay = SC_TIME_FOREVER; ticksToDwell = SC_TIME_FOREVER; dwelling = false; ptMouseLast.x = 0; ptMouseLast.y = 0; inDragDrop = ddNone; dropWentOutside = false; posDrag = SelectionPosition(invalidPosition); posDrop = SelectionPosition(invalidPosition); hotSpotClickPos = INVALID_POSITION; selectionType = selChar; lastXChosen = 0; lineAnchorPos = 0; originalAnchorPos = 0; wordSelectAnchorStartPos = 0; wordSelectAnchorEndPos = 0; wordSelectInitialCaretPos = -1; primarySelection = true; caretXPolicy = CARET_SLOP | CARET_EVEN; caretXSlop = 50; caretYPolicy = CARET_EVEN; caretYSlop = 0; visiblePolicy = 0; visibleSlop = 0; searchAnchor = 0; xOffset = 0; xCaretMargin = 50; horizontalScrollBarVisible = true; scrollWidth = 2000; trackLineWidth = false; lineWidthMaxSeen = 0; verticalScrollBarVisible = true; endAtLastLine = true; caretSticky = SC_CARETSTICKY_OFF; marginOptions = SC_MARGINOPTION_NONE; mouseSelectionRectangularSwitch = false; multipleSelection = false; additionalSelectionTyping = false; multiPasteMode = SC_MULTIPASTE_ONCE; additionalCaretsBlink = true; additionalCaretsVisible = true; virtualSpaceOptions = SCVS_NONE; pixmapLine = 0; pixmapSelMargin = 0; pixmapSelPattern = 0; pixmapSelPatternOffset1 = 0; pixmapIndentGuide = 0; pixmapIndentGuideHighlight = 0; targetStart = 0; targetEnd = 0; searchFlags = 0; topLine = 0; posTopLine = 0; lengthForEncode = -1; needUpdateUI = 0; ContainerNeedsUpdate(SC_UPDATE_CONTENT); braces[0] = invalidPosition; braces[1] = invalidPosition; bracesMatchStyle = STYLE_BRACEBAD; highlightGuideColumn = 0; paintState = notPainting; paintAbandonedByStyling = false; paintingAllText = false; willRedrawAll = false; modEventMask = SC_MODEVENTMASKALL; pdoc = new Document(); pdoc->AddRef(); pdoc->AddWatcher(this, 0); recordingMacro = false; foldFlags = 0; foldAutomatic = 0; wrapWidth = LineLayout::wrapWidthInfinite; convertPastes = true; hsStart = -1; hsEnd = -1; llc.SetLevel(LineLayoutCache::llcCaret); posCache.SetSize(0x400); SetRepresentations(); } Editor::~Editor() { pdoc->RemoveWatcher(this, 0); pdoc->Release(); pdoc = 0; DropGraphics(true); } void Editor::Finalise() { SetIdle(false); CancelModes(); } void Editor::SetRepresentations() { reprs.Clear(); // C0 control set const char *reps[] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; for (size_t j=0; j < (sizeof(reps) / sizeof(reps[0])); j++) { char c[2] = { static_cast(j), 0 }; reprs.SetRepresentation(c, reps[j]); } // C1 control set // As well as Unicode mode, ISO-8859-1 should use these if (IsUnicodeMode()) { const char *repsC1[] = { "PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA", "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3", "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA", "SOS", "SGCI", "SCI", "CSI", "ST", "OSC", "PM", "APC" }; for (size_t j=0; j < (sizeof(repsC1) / sizeof(repsC1[0])); j++) { char c1[3] = { '\xc2', static_cast(0x80+j), 0 }; reprs.SetRepresentation(c1, repsC1[j]); } } // UTF-8 invalid bytes if (IsUnicodeMode()) { for (int k=0x80; k < 0x100; k++) { char hiByte[2] = { static_cast(k), 0 }; char hexits[4]; sprintf(hexits, "x%2X", k); reprs.SetRepresentation(hiByte, hexits); } } } void Editor::DropGraphics(bool freeObjects) { if (freeObjects) { delete pixmapLine; pixmapLine = 0; delete pixmapSelMargin; pixmapSelMargin = 0; delete pixmapSelPattern; pixmapSelPattern = 0; delete pixmapSelPatternOffset1; pixmapSelPatternOffset1 = 0; delete pixmapIndentGuide; pixmapIndentGuide = 0; delete pixmapIndentGuideHighlight; pixmapIndentGuideHighlight = 0; } else { if (pixmapLine) pixmapLine->Release(); if (pixmapSelMargin) pixmapSelMargin->Release(); if (pixmapSelPattern) pixmapSelPattern->Release(); if (pixmapSelPatternOffset1) pixmapSelPatternOffset1->Release(); if (pixmapIndentGuide) pixmapIndentGuide->Release(); if (pixmapIndentGuideHighlight) pixmapIndentGuideHighlight->Release(); } } void Editor::AllocateGraphics() { if (!pixmapLine) pixmapLine = Surface::Allocate(technology); if (!pixmapSelMargin) pixmapSelMargin = Surface::Allocate(technology); if (!pixmapSelPattern) pixmapSelPattern = Surface::Allocate(technology); if (!pixmapSelPatternOffset1) pixmapSelPatternOffset1 = Surface::Allocate(technology); if (!pixmapIndentGuide) pixmapIndentGuide = Surface::Allocate(technology); if (!pixmapIndentGuideHighlight) pixmapIndentGuideHighlight = Surface::Allocate(technology); } void Editor::InvalidateStyleData() { stylesValid = false; vs.technology = technology; DropGraphics(false); AllocateGraphics(); llc.Invalidate(LineLayout::llInvalid); posCache.Clear(); } void Editor::InvalidateStyleRedraw() { NeedWrapping(); InvalidateStyleData(); Redraw(); } void Editor::RefreshStyleData() { if (!stylesValid) { stylesValid = true; AutoSurface surface(this); if (surface) { vs.Refresh(*surface, pdoc->tabInChars); } SetScrollBars(); SetRectangularRange(); } } Point Editor::GetVisibleOriginInMain() { return Point(0,0); } Point Editor::DocumentPointFromView(Point ptView) { Point ptDocument = ptView; if (wMargin.GetID()) { Point ptOrigin = GetVisibleOriginInMain(); ptDocument.x += ptOrigin.x; ptDocument.y += ptOrigin.y; } else { ptDocument.x += xOffset; ptDocument.y += topLine * vs.lineHeight; } return ptDocument; } int Editor::TopLineOfMain() const { if (wMargin.GetID()) return 0; else return topLine; } PRectangle Editor::GetClientRectangle() { return wMain.GetClientPosition(); } PRectangle Editor::GetTextRectangle() { PRectangle rc = GetClientRectangle(); rc.left += vs.textStart; rc.right -= vs.rightMarginWidth; return rc; } int Editor::LinesOnScreen() { PRectangle rcClient = GetClientRectangle(); int htClient = rcClient.bottom - rcClient.top; //Platform::DebugPrintf("lines on screen = %d\n", htClient / lineHeight + 1); return htClient / vs.lineHeight; } int Editor::LinesToScroll() { int retVal = LinesOnScreen() - 1; if (retVal < 1) return 1; else return retVal; } int Editor::MaxScrollPos() { //Platform::DebugPrintf("Lines %d screen = %d maxScroll = %d\n", //LinesTotal(), LinesOnScreen(), LinesTotal() - LinesOnScreen() + 1); int retVal = cs.LinesDisplayed(); if (endAtLastLine) { retVal -= LinesOnScreen(); } else { retVal--; } if (retVal < 0) { return 0; } else { return retVal; } } const char *ControlCharacterString(unsigned char ch) { const char *reps[] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; if (ch < (sizeof(reps) / sizeof(reps[0]))) { return reps[ch]; } else { return "BAD"; } } /** * Convenience class to ensure LineLayout objects are always disposed. */ class AutoLineLayout { LineLayoutCache &llc; LineLayout *ll; AutoLineLayout &operator=(const AutoLineLayout &); public: AutoLineLayout(LineLayoutCache &llc_, LineLayout *ll_) : llc(llc_), ll(ll_) {} ~AutoLineLayout() { llc.Dispose(ll); ll = 0; } LineLayout *operator->() const { return ll; } operator LineLayout *() const { return ll; } void Set(LineLayout *ll_) { llc.Dispose(ll); ll = ll_; } }; SelectionPosition Editor::ClampPositionIntoDocument(SelectionPosition sp) const { if (sp.Position() < 0) { return SelectionPosition(0); } else if (sp.Position() > pdoc->Length()) { return SelectionPosition(pdoc->Length()); } else { // If not at end of line then set offset to 0 if (!pdoc->IsLineEndPosition(sp.Position())) sp.SetVirtualSpace(0); return sp; } } Point Editor::LocationFromPosition(SelectionPosition pos) { Point pt; RefreshStyleData(); if (pos.Position() == INVALID_POSITION) return pt; int line = pdoc->LineFromPosition(pos.Position()); int lineVisible = cs.DisplayFromDoc(line); //Platform::DebugPrintf("line=%d\n", line); AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(line)); if (surface && ll) { // -1 because of adding in for visible lines in following loop. pt.y = (lineVisible - topLine - 1) * vs.lineHeight; pt.x = 0; unsigned int posLineStart = pdoc->LineStart(line); LayoutLine(line, surface, vs, ll, wrapWidth); int posInLine = pos.Position() - posLineStart; // In case of very long line put x at arbitrary large position if (posInLine > ll->maxLineLength) { pt.x = ll->positions[ll->maxLineLength] - ll->positions[ll->LineStart(ll->lines)]; } for (int subLine = 0; subLine < ll->lines; subLine++) { if ((posInLine >= ll->LineStart(subLine)) && (posInLine <= ll->LineStart(subLine + 1))) { pt.x = ll->positions[posInLine] - ll->positions[ll->LineStart(subLine)]; if (ll->wrapIndent != 0) { int lineStart = ll->LineStart(subLine); if (lineStart != 0) // Wrapped pt.x += ll->wrapIndent; } } if (posInLine >= ll->LineStart(subLine)) { pt.y += vs.lineHeight; } } pt.x += vs.textStart - xOffset; } pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth; return pt; } Point Editor::LocationFromPosition(int pos) { return LocationFromPosition(SelectionPosition(pos)); } int Editor::XFromPosition(int pos) { Point pt = LocationFromPosition(pos); return pt.x - vs.textStart + xOffset; } int Editor::XFromPosition(SelectionPosition sp) { Point pt = LocationFromPosition(sp); return pt.x - vs.textStart + xOffset; } int Editor::LineFromLocation(Point pt) const { return cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); } void Editor::SetTopLine(int topLineNew) { if ((topLine != topLineNew) && (topLineNew >= 0)) { topLine = topLineNew; ContainerNeedsUpdate(SC_UPDATE_V_SCROLL); } posTopLine = pdoc->LineStart(cs.DocFromDisplay(topLine)); } SelectionPosition Editor::SPositionFromLocation(Point pt, bool canReturnInvalid, bool charPosition, bool virtualSpace) { RefreshStyleData(); if (canReturnInvalid) { PRectangle rcClient = GetTextRectangle(); // May be in scroll view coordinates so translate back to main view Point ptOrigin = GetVisibleOriginInMain(); rcClient.Move(-ptOrigin.x, -ptOrigin.y); if (!rcClient.Contains(pt)) return SelectionPosition(INVALID_POSITION); if (pt.x < vs.textStart) return SelectionPosition(INVALID_POSITION); if (pt.y < 0) return SelectionPosition(INVALID_POSITION); } pt = DocumentPointFromView(pt); pt.x = pt.x - vs.textStart; int visibleLine = floor(pt.y / vs.lineHeight); if (!canReturnInvalid && (visibleLine < 0)) visibleLine = 0; int lineDoc = cs.DocFromDisplay(visibleLine); if (canReturnInvalid && (lineDoc < 0)) return SelectionPosition(INVALID_POSITION); if (lineDoc >= pdoc->LinesTotal()) return SelectionPosition(canReturnInvalid ? INVALID_POSITION : pdoc->Length()); unsigned int posLineStart = pdoc->LineStart(lineDoc); SelectionPosition retVal(canReturnInvalid ? INVALID_POSITION : static_cast(posLineStart)); AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); if (surface && ll) { LayoutLine(lineDoc, surface, vs, ll, wrapWidth); int lineStartSet = cs.DisplayFromDoc(lineDoc); int subLine = visibleLine - lineStartSet; if (subLine < ll->lines) { int lineStart = ll->LineStart(subLine); int lineEnd = ll->LineLastVisible(subLine); XYPOSITION subLineStart = ll->positions[lineStart]; if (ll->wrapIndent != 0) { if (lineStart != 0) // Wrapped pt.x -= ll->wrapIndent; } int i = ll->FindBefore(pt.x + subLineStart, lineStart, lineEnd); while (i < lineEnd) { if (charPosition) { if ((pt.x + subLineStart) < (ll->positions[i + 1])) { return SelectionPosition(pdoc->MovePositionOutsideChar(i + posLineStart, 1)); } } else { if ((pt.x + subLineStart) < ((ll->positions[i] + ll->positions[i + 1]) / 2)) { return SelectionPosition(pdoc->MovePositionOutsideChar(i + posLineStart, 1)); } } i++; } if (virtualSpace) { const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; int spaceOffset = (pt.x + subLineStart - ll->positions[lineEnd] + spaceWidth / 2) / spaceWidth; return SelectionPosition(lineEnd + posLineStart, spaceOffset); } else if (canReturnInvalid) { if (pt.x < (ll->positions[lineEnd] - subLineStart)) { return SelectionPosition(pdoc->MovePositionOutsideChar(lineEnd + posLineStart, 1)); } } else { return SelectionPosition(lineEnd + posLineStart); } } if (!canReturnInvalid) return SelectionPosition(ll->numCharsInLine + posLineStart); } return retVal; } int Editor::PositionFromLocation(Point pt, bool canReturnInvalid, bool charPosition) { return SPositionFromLocation(pt, canReturnInvalid, charPosition, false).Position(); } /** * Find the document position corresponding to an x coordinate on a particular document line. * Ensure is between whole characters when document is in multi-byte or UTF-8 mode. */ SelectionPosition Editor::SPositionFromLineX(int lineDoc, int x) { RefreshStyleData(); if (lineDoc >= pdoc->LinesTotal()) return SelectionPosition(pdoc->Length()); //Platform::DebugPrintf("Position of (%d,%d) line = %d top=%d\n", pt.x, pt.y, line, topLine); AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); int retVal = 0; if (surface && ll) { unsigned int posLineStart = pdoc->LineStart(lineDoc); LayoutLine(lineDoc, surface, vs, ll, wrapWidth); int subLine = 0; int lineStart = ll->LineStart(subLine); int lineEnd = ll->LineLastVisible(subLine); XYPOSITION subLineStart = ll->positions[lineStart]; XYPOSITION newX = x; if (ll->wrapIndent != 0) { if (lineStart != 0) // Wrapped newX -= ll->wrapIndent; } int i = ll->FindBefore(newX + subLineStart, lineStart, lineEnd); while (i < lineEnd) { if ((newX + subLineStart) < ((ll->positions[i] + ll->positions[i + 1]) / 2)) { retVal = pdoc->MovePositionOutsideChar(i + posLineStart, 1); return SelectionPosition(retVal); } i++; } const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; int spaceOffset = (newX + subLineStart - ll->positions[lineEnd] + spaceWidth / 2) / spaceWidth; return SelectionPosition(lineEnd + posLineStart, spaceOffset); } return SelectionPosition(retVal); } int Editor::PositionFromLineX(int lineDoc, int x) { return SPositionFromLineX(lineDoc, x).Position(); } /** * If painting then abandon the painting because a wider redraw is needed. * @return true if calling code should stop drawing. */ bool Editor::AbandonPaint() { if ((paintState == painting) && !paintingAllText) { paintState = paintAbandoned; } return paintState == paintAbandoned; } void Editor::RedrawRect(PRectangle rc) { //Platform::DebugPrintf("Redraw %0d,%0d - %0d,%0d\n", rc.left, rc.top, rc.right, rc.bottom); // Clip the redraw rectangle into the client area PRectangle rcClient = GetClientRectangle(); if (rc.top < rcClient.top) rc.top = rcClient.top; if (rc.bottom > rcClient.bottom) rc.bottom = rcClient.bottom; if (rc.left < rcClient.left) rc.left = rcClient.left; if (rc.right > rcClient.right) rc.right = rcClient.right; if ((rc.bottom > rc.top) && (rc.right > rc.left)) { wMain.InvalidateRectangle(rc); } } void Editor::Redraw() { //Platform::DebugPrintf("Redraw all\n"); PRectangle rcClient = GetClientRectangle(); wMain.InvalidateRectangle(rcClient); if (wMargin.GetID()) wMargin.InvalidateAll(); //wMain.InvalidateAll(); } void Editor::RedrawSelMargin(int line, bool allAfter) { if (!AbandonPaint()) { if (vs.maskInLine) { Redraw(); } else { PRectangle rcSelMargin = GetClientRectangle(); rcSelMargin.right = rcSelMargin.left + vs.fixedColumnWidth; if (line != -1) { int position = pdoc->LineStart(line); PRectangle rcLine = RectangleFromRange(position, position); // Inflate line rectangle if there are image markers with height larger than line height if (vs.largestMarkerHeight > vs.lineHeight) { int delta = (vs.largestMarkerHeight - vs.lineHeight + 1) / 2; rcLine.top -= delta; rcLine.bottom += delta; if (rcLine.top < rcSelMargin.top) rcLine.top = rcSelMargin.top; if (rcLine.bottom > rcSelMargin.bottom) rcLine.bottom = rcSelMargin.bottom; } rcSelMargin.top = rcLine.top; if (!allAfter) rcSelMargin.bottom = rcLine.bottom; if (rcSelMargin.Empty()) return; } if (wMargin.GetID()) { Point ptOrigin = GetVisibleOriginInMain(); rcSelMargin.Move(-ptOrigin.x, -ptOrigin.y); wMargin.InvalidateRectangle(rcSelMargin); } else { wMain.InvalidateRectangle(rcSelMargin); } } } } PRectangle Editor::RectangleFromRange(int start, int end) { int minPos = start; if (minPos > end) minPos = end; int maxPos = start; if (maxPos < end) maxPos = end; int minLine = cs.DisplayFromDoc(pdoc->LineFromPosition(minPos)); int lineDocMax = pdoc->LineFromPosition(maxPos); int maxLine = cs.DisplayFromDoc(lineDocMax) + cs.GetHeight(lineDocMax) - 1; PRectangle rcClient = GetTextRectangle(); PRectangle rc; const int leftTextOverlap = ((xOffset == 0) && (vs.leftMarginWidth > 0)) ? 1 : 0; rc.left = vs.textStart - leftTextOverlap; rc.top = (minLine - TopLineOfMain()) * vs.lineHeight; if (rc.top < rcClient.top) rc.top = rcClient.top; rc.right = rcClient.right; rc.bottom = (maxLine - TopLineOfMain() + 1) * vs.lineHeight; return rc; } void Editor::InvalidateRange(int start, int end) { RedrawRect(RectangleFromRange(start, end)); } int Editor::CurrentPosition() const { return sel.MainCaret(); } bool Editor::SelectionEmpty() const { return sel.Empty(); } SelectionPosition Editor::SelectionStart() { return sel.RangeMain().Start(); } SelectionPosition Editor::SelectionEnd() { return sel.RangeMain().End(); } void Editor::SetRectangularRange() { if (sel.IsRectangular()) { int xAnchor = XFromPosition(sel.Rectangular().anchor); int xCaret = XFromPosition(sel.Rectangular().caret); if (sel.selType == Selection::selThin) { xCaret = xAnchor; } int lineAnchorRect = pdoc->LineFromPosition(sel.Rectangular().anchor.Position()); int lineCaret = pdoc->LineFromPosition(sel.Rectangular().caret.Position()); int increment = (lineCaret > lineAnchorRect) ? 1 : -1; for (int line=lineAnchorRect; line != lineCaret+increment; line += increment) { SelectionRange range(SPositionFromLineX(line, xCaret), SPositionFromLineX(line, xAnchor)); if ((virtualSpaceOptions & SCVS_RECTANGULARSELECTION) == 0) range.ClearVirtualSpace(); if (line == lineAnchorRect) sel.SetSelection(range); else sel.AddSelectionWithoutTrim(range); } } } void Editor::ThinRectangularRange() { if (sel.IsRectangular()) { sel.selType = Selection::selThin; if (sel.Rectangular().caret < sel.Rectangular().anchor) { sel.Rectangular() = SelectionRange(sel.Range(sel.Count()-1).caret, sel.Range(0).anchor); } else { sel.Rectangular() = SelectionRange(sel.Range(sel.Count()-1).anchor, sel.Range(0).caret); } SetRectangularRange(); } } void Editor::InvalidateSelection(SelectionRange newMain, bool invalidateWholeSelection) { if (sel.Count() > 1 || !(sel.RangeMain().anchor == newMain.anchor) || sel.IsRectangular()) { invalidateWholeSelection = true; } int firstAffected = Platform::Minimum(sel.RangeMain().Start().Position(), newMain.Start().Position()); // +1 for lastAffected ensures caret repainted int lastAffected = Platform::Maximum(newMain.caret.Position()+1, newMain.anchor.Position()); lastAffected = Platform::Maximum(lastAffected, sel.RangeMain().End().Position()); if (invalidateWholeSelection) { for (size_t r=0; rLineFromPosition(currentPos_.Position()); /* For Line selection - ensure the anchor and caret are always at the beginning and end of the region lines. */ if (sel.selType == Selection::selLines) { if (currentPos_ > anchor_) { anchor_ = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(anchor_.Position()))); currentPos_ = SelectionPosition(pdoc->LineEnd(pdoc->LineFromPosition(currentPos_.Position()))); } else { currentPos_ = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(currentPos_.Position()))); anchor_ = SelectionPosition(pdoc->LineEnd(pdoc->LineFromPosition(anchor_.Position()))); } } SelectionRange rangeNew(currentPos_, anchor_); if (sel.Count() > 1 || !(sel.RangeMain() == rangeNew)) { InvalidateSelection(rangeNew); } sel.RangeMain() = rangeNew; SetRectangularRange(); ClaimSelection(); if (highlightDelimiter.NeedsDrawing(currentLine)) { RedrawSelMargin(); } QueueIdleWork(WorkNeeded::workUpdateUI); } void Editor::SetSelection(int currentPos_, int anchor_) { SetSelection(SelectionPosition(currentPos_), SelectionPosition(anchor_)); } // Just move the caret on the main selection void Editor::SetSelection(SelectionPosition currentPos_) { currentPos_ = ClampPositionIntoDocument(currentPos_); int currentLine = pdoc->LineFromPosition(currentPos_.Position()); if (sel.Count() > 1 || !(sel.RangeMain().caret == currentPos_)) { InvalidateSelection(SelectionRange(currentPos_)); } if (sel.IsRectangular()) { sel.Rectangular() = SelectionRange(SelectionPosition(currentPos_), sel.Rectangular().anchor); SetRectangularRange(); } else { sel.RangeMain() = SelectionRange(SelectionPosition(currentPos_), sel.RangeMain().anchor); } ClaimSelection(); if (highlightDelimiter.NeedsDrawing(currentLine)) { RedrawSelMargin(); } QueueIdleWork(WorkNeeded::workUpdateUI); } void Editor::SetSelection(int currentPos_) { SetSelection(SelectionPosition(currentPos_)); } void Editor::SetEmptySelection(SelectionPosition currentPos_) { int currentLine = pdoc->LineFromPosition(currentPos_.Position()); SelectionRange rangeNew(ClampPositionIntoDocument(currentPos_)); if (sel.Count() > 1 || !(sel.RangeMain() == rangeNew)) { InvalidateSelection(rangeNew); } sel.Clear(); sel.RangeMain() = rangeNew; SetRectangularRange(); ClaimSelection(); if (highlightDelimiter.NeedsDrawing(currentLine)) { RedrawSelMargin(); } QueueIdleWork(WorkNeeded::workUpdateUI); } void Editor::SetEmptySelection(int currentPos_) { SetEmptySelection(SelectionPosition(currentPos_)); } bool Editor::RangeContainsProtected(int start, int end) const { if (vs.ProtectionActive()) { if (start > end) { int t = start; start = end; end = t; } int mask = pdoc->stylingBitsMask; for (int pos = start; pos < end; pos++) { if (vs.styles[pdoc->StyleAt(pos) & mask].IsProtected()) return true; } } return false; } bool Editor::SelectionContainsProtected() { for (size_t r=0; rMovePositionOutsideChar(pos.Position(), moveDir, checkLineEnd); if (posMoved != pos.Position()) pos.SetPosition(posMoved); if (vs.ProtectionActive()) { int mask = pdoc->stylingBitsMask; if (moveDir > 0) { if ((pos.Position() > 0) && vs.styles[pdoc->StyleAt(pos.Position() - 1) & mask].IsProtected()) { while ((pos.Position() < pdoc->Length()) && (vs.styles[pdoc->StyleAt(pos.Position()) & mask].IsProtected())) pos.Add(1); } } else if (moveDir < 0) { if (vs.styles[pdoc->StyleAt(pos.Position()) & mask].IsProtected()) { while ((pos.Position() > 0) && (vs.styles[pdoc->StyleAt(pos.Position() - 1) & mask].IsProtected())) pos.Add(-1); } } } return pos; } int Editor::MovePositionTo(SelectionPosition newPos, Selection::selTypes selt, bool ensureVisible) { bool simpleCaret = (sel.Count() == 1) && sel.Empty(); SelectionPosition spCaret = sel.Last(); int delta = newPos.Position() - sel.MainCaret(); newPos = ClampPositionIntoDocument(newPos); newPos = MovePositionOutsideChar(newPos, delta); if (!multipleSelection && sel.IsRectangular() && (selt == Selection::selStream)) { // Can't turn into multiple selection so clear additional selections InvalidateSelection(SelectionRange(newPos), true); SelectionRange rangeMain = sel.RangeMain(); sel.SetSelection(rangeMain); } if (!sel.IsRectangular() && (selt == Selection::selRectangle)) { // Switching to rectangular InvalidateSelection(sel.RangeMain(), false); SelectionRange rangeMain = sel.RangeMain(); sel.Clear(); sel.Rectangular() = rangeMain; } if (selt != Selection::noSel) { sel.selType = selt; } if (selt != Selection::noSel || sel.MoveExtends()) { SetSelection(newPos); } else { SetEmptySelection(newPos); } ShowCaretAtCurrentPosition(); int currentLine = pdoc->LineFromPosition(newPos.Position()); if (ensureVisible) { // In case in need of wrapping to ensure DisplayFromDoc works. if (currentLine >= wrapPending.start) WrapLines(wsAll); XYScrollPosition newXY = XYScrollToMakeVisible( SelectionRange(posDrag.IsValid() ? posDrag : sel.RangeMain().caret), xysDefault); if (simpleCaret && (newXY.xOffset == xOffset)) { // simple vertical scroll then invalidate ScrollTo(newXY.topLine); InvalidateSelection(SelectionRange(spCaret), true); } else { SetXYScroll(newXY); } } if (highlightDelimiter.NeedsDrawing(currentLine)) { RedrawSelMargin(); } return 0; } int Editor::MovePositionTo(int newPos, Selection::selTypes selt, bool ensureVisible) { return MovePositionTo(SelectionPosition(newPos), selt, ensureVisible); } SelectionPosition Editor::MovePositionSoVisible(SelectionPosition pos, int moveDir) { pos = ClampPositionIntoDocument(pos); pos = MovePositionOutsideChar(pos, moveDir); int lineDoc = pdoc->LineFromPosition(pos.Position()); if (cs.GetVisible(lineDoc)) { return pos; } else { int lineDisplay = cs.DisplayFromDoc(lineDoc); if (moveDir > 0) { // lineDisplay is already line before fold as lines in fold use display line of line after fold lineDisplay = Platform::Clamp(lineDisplay, 0, cs.LinesDisplayed()); return SelectionPosition(pdoc->LineStart(cs.DocFromDisplay(lineDisplay))); } else { lineDisplay = Platform::Clamp(lineDisplay - 1, 0, cs.LinesDisplayed()); return SelectionPosition(pdoc->LineEnd(cs.DocFromDisplay(lineDisplay))); } } } SelectionPosition Editor::MovePositionSoVisible(int pos, int moveDir) { return MovePositionSoVisible(SelectionPosition(pos), moveDir); } Point Editor::PointMainCaret() { return LocationFromPosition(sel.Range(sel.Main()).caret); } /** * Choose the x position that the caret will try to stick to * as it moves up and down. */ void Editor::SetLastXChosen() { Point pt = PointMainCaret(); lastXChosen = pt.x + xOffset; } void Editor::ScrollTo(int line, bool moveThumb) { int topLineNew = Platform::Clamp(line, 0, MaxScrollPos()); if (topLineNew != topLine) { // Try to optimise small scrolls #ifndef UNDER_CE int linesToMove = topLine - topLineNew; bool performBlit = (abs(linesToMove) <= 10) && (paintState == notPainting); willRedrawAll = !performBlit; #endif SetTopLine(topLineNew); // Optimize by styling the view as this will invalidate any needed area // which could abort the initial paint if discovered later. StyleToPositionInView(PositionAfterArea(GetClientRectangle())); #ifndef UNDER_CE // Perform redraw rather than scroll if many lines would be redrawn anyway. if (performBlit) { ScrollText(linesToMove); } else { Redraw(); } willRedrawAll = false; #else Redraw(); #endif if (moveThumb) { SetVerticalScrollPos(); } } } void Editor::ScrollText(int /* linesToMove */) { //Platform::DebugPrintf("Editor::ScrollText %d\n", linesToMove); Redraw(); } void Editor::HorizontalScrollTo(int xPos) { //Platform::DebugPrintf("HorizontalScroll %d\n", xPos); if (xPos < 0) xPos = 0; if (!Wrapping() && (xOffset != xPos)) { xOffset = xPos; ContainerNeedsUpdate(SC_UPDATE_H_SCROLL); SetHorizontalScrollPos(); RedrawRect(GetClientRectangle()); } } void Editor::VerticalCentreCaret() { int lineDoc = pdoc->LineFromPosition(sel.IsRectangular() ? sel.Rectangular().caret.Position() : sel.MainCaret()); int lineDisplay = cs.DisplayFromDoc(lineDoc); int newTop = lineDisplay - (LinesOnScreen() / 2); if (topLine != newTop) { SetTopLine(newTop > 0 ? newTop : 0); RedrawRect(GetClientRectangle()); } } // Avoid 64 bit compiler warnings. // Scintilla does not support text buffers larger than 2**31 static int istrlen(const char *s) { return static_cast(strlen(s)); } void Editor::MoveSelectedLines(int lineDelta) { // if selection doesn't start at the beginning of the line, set the new start int selectionStart = SelectionStart().Position(); int startLine = pdoc->LineFromPosition(selectionStart); int beginningOfStartLine = pdoc->LineStart(startLine); selectionStart = beginningOfStartLine; // if selection doesn't end at the beginning of a line greater than that of the start, // then set it at the beginning of the next one int selectionEnd = SelectionEnd().Position(); int endLine = pdoc->LineFromPosition(selectionEnd); int beginningOfEndLine = pdoc->LineStart(endLine); bool appendEol = false; if (selectionEnd > beginningOfEndLine || selectionStart == selectionEnd) { selectionEnd = pdoc->LineStart(endLine + 1); appendEol = (selectionEnd == pdoc->Length() && pdoc->LineFromPosition(selectionEnd) == endLine); } // if there's nowhere for the selection to move // (i.e. at the beginning going up or at the end going down), // stop it right there! if ((selectionStart == 0 && lineDelta < 0) || (selectionEnd == pdoc->Length() && lineDelta > 0) || selectionStart == selectionEnd) { return; } UndoGroup ug(pdoc); if (lineDelta > 0 && selectionEnd == pdoc->LineStart(pdoc->LinesTotal() - 1)) { SetSelection(pdoc->MovePositionOutsideChar(selectionEnd - 1, -1), selectionEnd); ClearSelection(); selectionEnd = CurrentPosition(); } SetSelection(selectionStart, selectionEnd); SelectionText selectedText; CopySelectionRange(&selectedText); int selectionLength = SelectionRange(selectionStart, selectionEnd).Length(); Point currentLocation = LocationFromPosition(CurrentPosition()); int currentLine = LineFromLocation(currentLocation); if (appendEol) SetSelection(pdoc->MovePositionOutsideChar(selectionStart - 1, -1), selectionEnd); ClearSelection(); const char *eol = StringFromEOLMode(pdoc->eolMode); if (currentLine + lineDelta >= pdoc->LinesTotal()) pdoc->InsertCString(pdoc->Length(), eol); GoToLine(currentLine + lineDelta); pdoc->InsertCString(CurrentPosition(), selectedText.Data()); if (appendEol) { pdoc->InsertCString(CurrentPosition() + selectionLength, eol); selectionLength += istrlen(eol); } SetSelection(CurrentPosition(), CurrentPosition() + selectionLength); } void Editor::MoveSelectedLinesUp() { MoveSelectedLines(-1); } void Editor::MoveSelectedLinesDown() { MoveSelectedLines(1); } void Editor::MoveCaretInsideView(bool ensureVisible) { PRectangle rcClient = GetTextRectangle(); Point pt = PointMainCaret(); if (pt.y < rcClient.top) { MovePositionTo(SPositionFromLocation( Point(lastXChosen - xOffset, rcClient.top), false, false, UserVirtualSpace()), Selection::noSel, ensureVisible); } else if ((pt.y + vs.lineHeight - 1) > rcClient.bottom) { int yOfLastLineFullyDisplayed = rcClient.top + (LinesOnScreen() - 1) * vs.lineHeight; MovePositionTo(SPositionFromLocation( Point(lastXChosen - xOffset, rcClient.top + yOfLastLineFullyDisplayed), false, false, UserVirtualSpace()), Selection::noSel, ensureVisible); } } int Editor::DisplayFromPosition(int pos) { int lineDoc = pdoc->LineFromPosition(pos); int lineDisplay = cs.DisplayFromDoc(lineDoc); AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); if (surface && ll) { LayoutLine(lineDoc, surface, vs, ll, wrapWidth); unsigned int posLineStart = pdoc->LineStart(lineDoc); int posInLine = pos - posLineStart; lineDisplay--; // To make up for first increment ahead. for (int subLine = 0; subLine < ll->lines; subLine++) { if (posInLine >= ll->LineStart(subLine)) { lineDisplay++; } } } return lineDisplay; } /** * Ensure the caret is reasonably visible in context. * Caret policy in SciTE If slop is set, we can define a slop value. This value defines an unwanted zone (UZ) where the caret is... unwanted. This zone is defined as a number of pixels near the vertical margins, and as a number of lines near the horizontal margins. By keeping the caret away from the edges, it is seen within its context, so it is likely that the identifier that the caret is on can be completely seen, and that the current line is seen with some of the lines following it which are often dependent on that line. If strict is set, the policy is enforced... strictly. The caret is centred on the display if slop is not set, and cannot go in the UZ if slop is set. If jumps is set, the display is moved more energetically so the caret can move in the same direction longer before the policy is applied again. '3UZ' notation is used to indicate three time the size of the UZ as a distance to the margin. If even is not set, instead of having symmetrical UZs, the left and bottom UZs are extended up to right and top UZs respectively. This way, we favour the displaying of useful information: the begining of lines, where most code reside, and the lines after the caret, eg. the body of a function. | | | | | slop | strict | jumps | even | Caret can go to the margin | When reaching limit (caret going out of | | | | | visibility or going into the UZ) display is... -----+--------+-------+------+--------------------------------------------+-------------------------------------------------------------- 0 | 0 | 0 | 0 | Yes | moved to put caret on top/on right 0 | 0 | 0 | 1 | Yes | moved by one position 0 | 0 | 1 | 0 | Yes | moved to put caret on top/on right 0 | 0 | 1 | 1 | Yes | centred on the caret 0 | 1 | - | 0 | Caret is always on top/on right of display | - 0 | 1 | - | 1 | No, caret is always centred | - 1 | 0 | 0 | 0 | Yes | moved to put caret out of the asymmetrical UZ 1 | 0 | 0 | 1 | Yes | moved to put caret out of the UZ 1 | 0 | 1 | 0 | Yes | moved to put caret at 3UZ of the top or right margin 1 | 0 | 1 | 1 | Yes | moved to put caret at 3UZ of the margin 1 | 1 | - | 0 | Caret is always at UZ of top/right margin | - 1 | 1 | 0 | 1 | No, kept out of UZ | moved by one position 1 | 1 | 1 | 1 | No, kept out of UZ | moved to put caret at 3UZ of the margin */ Editor::XYScrollPosition Editor::XYScrollToMakeVisible(const SelectionRange range, const XYScrollOptions options) { PRectangle rcClient = GetTextRectangle(); Point pt = LocationFromPosition(range.caret); Point ptAnchor = LocationFromPosition(range.anchor); const Point ptOrigin = GetVisibleOriginInMain(); pt.x += ptOrigin.x; pt.y += ptOrigin.y; ptAnchor.x += ptOrigin.x; ptAnchor.y += ptOrigin.y; const Point ptBottomCaret(pt.x, pt.y + vs.lineHeight - 1); XYScrollPosition newXY(xOffset, topLine); if (rcClient.Empty()) { return newXY; } // Vertical positioning if ((options & xysVertical) && (pt.y < rcClient.top || ptBottomCaret.y >= rcClient.bottom || (caretYPolicy & CARET_STRICT) != 0)) { const int lineCaret = DisplayFromPosition(range.caret.Position()); const int linesOnScreen = LinesOnScreen(); const int halfScreen = Platform::Maximum(linesOnScreen - 1, 2) / 2; const bool bSlop = (caretYPolicy & CARET_SLOP) != 0; const bool bStrict = (caretYPolicy & CARET_STRICT) != 0; const bool bJump = (caretYPolicy & CARET_JUMPS) != 0; const bool bEven = (caretYPolicy & CARET_EVEN) != 0; // It should be possible to scroll the window to show the caret, // but this fails to remove the caret on GTK+ if (bSlop) { // A margin is defined int yMoveT, yMoveB; if (bStrict) { int yMarginT, yMarginB; if (!(options & xysUseMargin)) { // In drag mode, avoid moves // otherwise, a double click will select several lines. yMarginT = yMarginB = 0; } else { // yMarginT must equal to caretYSlop, with a minimum of 1 and // a maximum of slightly less than half the heigth of the text area. yMarginT = Platform::Clamp(caretYSlop, 1, halfScreen); if (bEven) { yMarginB = yMarginT; } else { yMarginB = linesOnScreen - yMarginT - 1; } } yMoveT = yMarginT; if (bEven) { if (bJump) { yMoveT = Platform::Clamp(caretYSlop * 3, 1, halfScreen); } yMoveB = yMoveT; } else { yMoveB = linesOnScreen - yMoveT - 1; } if (lineCaret < topLine + yMarginT) { // Caret goes too high newXY.topLine = lineCaret - yMoveT; } else if (lineCaret > topLine + linesOnScreen - 1 - yMarginB) { // Caret goes too low newXY.topLine = lineCaret - linesOnScreen + 1 + yMoveB; } } else { // Not strict yMoveT = bJump ? caretYSlop * 3 : caretYSlop; yMoveT = Platform::Clamp(yMoveT, 1, halfScreen); if (bEven) { yMoveB = yMoveT; } else { yMoveB = linesOnScreen - yMoveT - 1; } if (lineCaret < topLine) { // Caret goes too high newXY.topLine = lineCaret - yMoveT; } else if (lineCaret > topLine + linesOnScreen - 1) { // Caret goes too low newXY.topLine = lineCaret - linesOnScreen + 1 + yMoveB; } } } else { // No slop if (!bStrict && !bJump) { // Minimal move if (lineCaret < topLine) { // Caret goes too high newXY.topLine = lineCaret; } else if (lineCaret > topLine + linesOnScreen - 1) { // Caret goes too low if (bEven) { newXY.topLine = lineCaret - linesOnScreen + 1; } else { newXY.topLine = lineCaret; } } } else { // Strict or going out of display if (bEven) { // Always center caret newXY.topLine = lineCaret - halfScreen; } else { // Always put caret on top of display newXY.topLine = lineCaret; } } } if (!(range.caret == range.anchor)) { const int lineAnchor = DisplayFromPosition(range.anchor.Position()); if (lineAnchor < lineCaret) { // Shift up to show anchor or as much of range as possible newXY.topLine = std::min(newXY.topLine, lineAnchor); newXY.topLine = std::max(newXY.topLine, lineCaret - LinesOnScreen()); } else { // Shift down to show anchor or as much of range as possible newXY.topLine = std::max(newXY.topLine, lineAnchor - LinesOnScreen()); newXY.topLine = std::min(newXY.topLine, lineCaret); } } newXY.topLine = Platform::Clamp(newXY.topLine, 0, MaxScrollPos()); } // Horizontal positioning if ((options & xysHorizontal) && !Wrapping()) { const int halfScreen = Platform::Maximum(rcClient.Width() - 4, 4) / 2; const bool bSlop = (caretXPolicy & CARET_SLOP) != 0; const bool bStrict = (caretXPolicy & CARET_STRICT) != 0; const bool bJump = (caretXPolicy & CARET_JUMPS) != 0; const bool bEven = (caretXPolicy & CARET_EVEN) != 0; if (bSlop) { // A margin is defined int xMoveL, xMoveR; if (bStrict) { int xMarginL, xMarginR; if (!(options & xysUseMargin)) { // In drag mode, avoid moves unless very near of the margin // otherwise, a simple click will select text. xMarginL = xMarginR = 2; } else { // xMargin must equal to caretXSlop, with a minimum of 2 and // a maximum of slightly less than half the width of the text area. xMarginR = Platform::Clamp(caretXSlop, 2, halfScreen); if (bEven) { xMarginL = xMarginR; } else { xMarginL = rcClient.Width() - xMarginR - 4; } } if (bJump && bEven) { // Jump is used only in even mode xMoveL = xMoveR = Platform::Clamp(caretXSlop * 3, 1, halfScreen); } else { xMoveL = xMoveR = 0; // Not used, avoid a warning } if (pt.x < rcClient.left + xMarginL) { // Caret is on the left of the display if (bJump && bEven) { newXY.xOffset -= xMoveL; } else { // Move just enough to allow to display the caret newXY.xOffset -= (rcClient.left + xMarginL) - pt.x; } } else if (pt.x >= rcClient.right - xMarginR) { // Caret is on the right of the display if (bJump && bEven) { newXY.xOffset += xMoveR; } else { // Move just enough to allow to display the caret newXY.xOffset += pt.x - (rcClient.right - xMarginR) + 1; } } } else { // Not strict xMoveR = bJump ? caretXSlop * 3 : caretXSlop; xMoveR = Platform::Clamp(xMoveR, 1, halfScreen); if (bEven) { xMoveL = xMoveR; } else { xMoveL = rcClient.Width() - xMoveR - 4; } if (pt.x < rcClient.left) { // Caret is on the left of the display newXY.xOffset -= xMoveL; } else if (pt.x >= rcClient.right) { // Caret is on the right of the display newXY.xOffset += xMoveR; } } } else { // No slop if (bStrict || (bJump && (pt.x < rcClient.left || pt.x >= rcClient.right))) { // Strict or going out of display if (bEven) { // Center caret newXY.xOffset += pt.x - rcClient.left - halfScreen; } else { // Put caret on right newXY.xOffset += pt.x - rcClient.right + 1; } } else { // Move just enough to allow to display the caret if (pt.x < rcClient.left) { // Caret is on the left of the display if (bEven) { newXY.xOffset -= rcClient.left - pt.x; } else { newXY.xOffset += pt.x - rcClient.right + 1; } } else if (pt.x >= rcClient.right) { // Caret is on the right of the display newXY.xOffset += pt.x - rcClient.right + 1; } } } // In case of a jump (find result) largely out of display, adjust the offset to display the caret if (pt.x + xOffset < rcClient.left + newXY.xOffset) { newXY.xOffset = pt.x + xOffset - rcClient.left; } else if (pt.x + xOffset >= rcClient.right + newXY.xOffset) { newXY.xOffset = pt.x + xOffset - rcClient.right + 1; if (vs.caretStyle == CARETSTYLE_BLOCK) { // Ensure we can see a good portion of the block caret newXY.xOffset += static_cast(vs.aveCharWidth); } } if (!(range.caret == range.anchor)) { if (ptAnchor.x < pt.x) { // Shift to left to show anchor or as much of range as possible int maxOffset = ptAnchor.x + xOffset - rcClient.left - 1; int minOffset = pt.x + xOffset - rcClient.right + 1; newXY.xOffset = std::min(newXY.xOffset, maxOffset); newXY.xOffset = std::max(newXY.xOffset, minOffset); } else { // Shift to right to show anchor or as much of range as possible int minOffset = ptAnchor.x + xOffset - rcClient.right + 1; int maxOffset = pt.x + xOffset - rcClient.left - 1; newXY.xOffset = std::max(newXY.xOffset, minOffset); newXY.xOffset = std::min(newXY.xOffset, maxOffset); } } if (newXY.xOffset < 0) { newXY.xOffset = 0; } } return newXY; } void Editor::SetXYScroll(XYScrollPosition newXY) { if ((newXY.topLine != topLine) || (newXY.xOffset != xOffset)) { if (newXY.topLine != topLine) { SetTopLine(newXY.topLine); SetVerticalScrollPos(); } if (newXY.xOffset != xOffset) { xOffset = newXY.xOffset; ContainerNeedsUpdate(SC_UPDATE_H_SCROLL); if (newXY.xOffset > 0) { PRectangle rcText = GetTextRectangle(); if (horizontalScrollBarVisible && rcText.Width() + xOffset > scrollWidth) { scrollWidth = xOffset + rcText.Width(); SetScrollBars(); } } SetHorizontalScrollPos(); } Redraw(); UpdateSystemCaret(); } } void Editor::ScrollRange(SelectionRange range) { SetXYScroll(XYScrollToMakeVisible(range, xysDefault)); } void Editor::EnsureCaretVisible(bool useMargin, bool vert, bool horiz) { SetXYScroll(XYScrollToMakeVisible(SelectionRange(posDrag.IsValid() ? posDrag : sel.RangeMain().caret), static_cast((useMargin?xysUseMargin:0)|(vert?xysVertical:0)|(horiz?xysHorizontal:0)))); } void Editor::ShowCaretAtCurrentPosition() { if (hasFocus) { caret.active = true; caret.on = true; SetTicking(true); } else { caret.active = false; caret.on = false; } InvalidateCaret(); } void Editor::DropCaret() { caret.active = false; InvalidateCaret(); } void Editor::InvalidateCaret() { if (posDrag.IsValid()) { InvalidateRange(posDrag.Position(), posDrag.Position() + 1); } else { for (size_t r=0; rlines; } return cs.SetHeight(lineToWrap, linesWrapped + (vs.annotationVisible ? pdoc->AnnotationLines(lineToWrap) : 0)); } // Perform wrapping for a subset of the lines needing wrapping. // wsAll: wrap all lines which need wrapping in this single call // wsVisible: wrap currently visible lines // wsIdle: wrap one page + 100 lines // Return true if wrapping occurred. bool Editor::WrapLines(enum wrapScope ws) { int goodTopLine = topLine; bool wrapOccurred = false; if (!Wrapping()) { if (wrapWidth != LineLayout::wrapWidthInfinite) { wrapWidth = LineLayout::wrapWidthInfinite; for (int lineDoc = 0; lineDoc < pdoc->LinesTotal(); lineDoc++) { cs.SetHeight(lineDoc, 1 + (vs.annotationVisible ? pdoc->AnnotationLines(lineDoc) : 0)); } wrapOccurred = true; } wrapPending.Reset(); } else if (wrapPending.NeedsWrap()) { wrapPending.start = std::min(wrapPending.start, pdoc->LinesTotal()); if (!SetIdle(true)) { // Idle processing not supported so full wrap required. ws = wsAll; } // Decide where to start wrapping int lineToWrap = wrapPending.start; int lineToWrapEnd = std::min(wrapPending.end, pdoc->LinesTotal()); const int lineDocTop = cs.DocFromDisplay(topLine); const int subLineTop = topLine - cs.DisplayFromDoc(lineDocTop); if (ws == wsVisible) { lineToWrap = Platform::Clamp(lineDocTop-5, wrapPending.start, pdoc->LinesTotal()); // Priority wrap to just after visible area. // Since wrapping could reduce display lines, treat each // as taking only one display line. lineToWrapEnd = lineDocTop; int lines = LinesOnScreen() + 1; while ((lineToWrapEnd < cs.LinesInDoc()) && (lines>0)) { if (cs.GetVisible(lineToWrapEnd)) lines--; lineToWrapEnd++; } // .. and if the paint window is outside pending wraps if ((lineToWrap > wrapPending.end) || (lineToWrapEnd < wrapPending.start)) { // Currently visible text does not need wrapping return false; } } else if (ws == wsIdle) { lineToWrapEnd = lineToWrap + LinesOnScreen() + 100; } const int lineEndNeedWrap = std::min(wrapPending.end, pdoc->LinesTotal()); lineToWrapEnd = std::min(lineToWrapEnd, lineEndNeedWrap); // Ensure all lines being wrapped are styled. pdoc->EnsureStyledTo(pdoc->LineStart(lineToWrapEnd)); if (lineToWrap < lineToWrapEnd) { PRectangle rcTextArea = GetClientRectangle(); rcTextArea.left = vs.textStart; rcTextArea.right -= vs.rightMarginWidth; wrapWidth = rcTextArea.Width(); RefreshStyleData(); AutoSurface surface(this); if (surface) { //Platform::DebugPrintf("Wraplines: scope=%0d need=%0d..%0d perform=%0d..%0d\n", ws, wrapPending.start, wrapPending.end, lineToWrap, lineToWrapEnd); while (lineToWrap < lineToWrapEnd) { if (WrapOneLine(surface, lineToWrap)) { wrapOccurred = true; } wrapPending.Wrapped(lineToWrap); lineToWrap++; } goodTopLine = cs.DisplayFromDoc(lineDocTop) + std::min(subLineTop, cs.GetHeight(lineDocTop)-1); } } // If wrapping is done, bring it to resting position if (wrapPending.start >= lineEndNeedWrap) { wrapPending.Reset(); } } if (wrapOccurred) { SetScrollBars(); SetTopLine(Platform::Clamp(goodTopLine, 0, MaxScrollPos())); SetVerticalScrollPos(); } return wrapOccurred; } void Editor::LinesJoin() { if (!RangeContainsProtected(targetStart, targetEnd)) { UndoGroup ug(pdoc); bool prevNonWS = true; for (int pos = targetStart; pos < targetEnd; pos++) { if (pdoc->IsPositionInLineEnd(pos)) { targetEnd -= pdoc->LenChar(pos); pdoc->DelChar(pos); if (prevNonWS) { // Ensure at least one space separating previous lines pdoc->InsertChar(pos, ' '); targetEnd++; } } else { prevNonWS = pdoc->CharAt(pos) != ' '; } } } } const char *Editor::StringFromEOLMode(int eolMode) { if (eolMode == SC_EOL_CRLF) { return "\r\n"; } else if (eolMode == SC_EOL_CR) { return "\r"; } else { return "\n"; } } void Editor::LinesSplit(int pixelWidth) { if (!RangeContainsProtected(targetStart, targetEnd)) { if (pixelWidth == 0) { PRectangle rcText = GetTextRectangle(); pixelWidth = rcText.Width(); } int lineStart = pdoc->LineFromPosition(targetStart); int lineEnd = pdoc->LineFromPosition(targetEnd); const char *eol = StringFromEOLMode(pdoc->eolMode); UndoGroup ug(pdoc); for (int line = lineStart; line <= lineEnd; line++) { AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(line)); if (surface && ll) { unsigned int posLineStart = pdoc->LineStart(line); LayoutLine(line, surface, vs, ll, pixelWidth); for (int subLine = 1; subLine < ll->lines; subLine++) { pdoc->InsertCString( static_cast(posLineStart + (subLine - 1) * strlen(eol) + ll->LineStart(subLine)), eol); targetEnd += static_cast(strlen(eol)); } } lineEnd = pdoc->LineFromPosition(targetEnd); } } } int Editor::SubstituteMarkerIfEmpty(int markerCheck, int markerDefault) const { if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) return markerDefault; return markerCheck; } bool ValidStyledText(ViewStyle &vs, size_t styleOffset, const StyledText &st) { if (st.multipleStyles) { for (size_t iStyle=0; iStyle(styles[endSegment+1]) == style)) endSegment++; width += surface->WidthText(vs.styles[style+styleOffset].font, text + start, static_cast(endSegment - start + 1)); start = endSegment + 1; } return width; } static int WidestLineWidth(Surface *surface, ViewStyle &vs, int styleOffset, const StyledText &st) { int widthMax = 0; size_t start = 0; while (start < st.length) { size_t lenLine = st.LineLength(start); int widthSubLine; if (st.multipleStyles) { widthSubLine = WidthStyledText(surface, vs, styleOffset, st.text + start, st.styles + start, lenLine); } else { widthSubLine = surface->WidthText(vs.styles[styleOffset + st.style].font, st.text + start, static_cast(lenLine)); } if (widthSubLine > widthMax) widthMax = widthSubLine; start += lenLine + 1; } return widthMax; } void DrawStyledText(Surface *surface, ViewStyle &vs, int styleOffset, PRectangle rcText, int ascent, const StyledText &st, size_t start, size_t length) { if (st.multipleStyles) { int x = rcText.left; size_t i = 0; while (i < length) { size_t end = i; int style = st.styles[i + start]; while (end < length-1 && st.styles[start+end+1] == style) end++; style += styleOffset; int width = surface->WidthText(vs.styles[style].font, st.text + start + i, static_cast(end - i + 1)); PRectangle rcSegment = rcText; rcSegment.left = x; rcSegment.right = x + width + 1; surface->DrawTextNoClip(rcSegment, vs.styles[style].font, ascent, st.text + start + i, static_cast(end - i + 1), vs.styles[style].fore, vs.styles[style].back); x += width; i = end + 1; } } else { size_t style = st.style + styleOffset; surface->DrawTextNoClip(rcText, vs.styles[style].font, rcText.top + vs.maxAscent, st.text + start, static_cast(length), vs.styles[style].fore, vs.styles[style].back); } } void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { if (vs.fixedColumnWidth == 0) return; AllocateGraphics(); RefreshStyleData(); RefreshPixMaps(surfWindow); PRectangle rcMargin = GetClientRectangle(); Point ptOrigin = GetVisibleOriginInMain(); rcMargin.Move(0, -ptOrigin.y); rcMargin.left = 0; rcMargin.right = vs.fixedColumnWidth; if (!rc.Intersects(rcMargin)) return; Surface *surface; if (bufferedDraw) { surface = pixmapSelMargin; } else { surface = surfWindow; } // Clip vertically to paint area to avoid drawing line numbers if (rcMargin.bottom > rc.bottom) rcMargin.bottom = rc.bottom; if (rcMargin.top < rc.top) rcMargin.top = rc.top; PRectangle rcSelMargin = rcMargin; rcSelMargin.right = rcMargin.left; if (rcSelMargin.bottom < rc.bottom) rcSelMargin.bottom = rc.bottom; for (int margin = 0; margin <= SC_MAX_MARGIN; margin++) { if (vs.ms[margin].width > 0) { rcSelMargin.left = rcSelMargin.right; rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; if (vs.ms[margin].style != SC_MARGIN_NUMBER) { if (vs.ms[margin].mask & SC_MASK_FOLDERS) { // Required because of special way brush is created for selection margin // Ensure patterns line up when scrolling with separate margin view // by choosing correctly aligned variant. bool invertPhase = static_cast(ptOrigin.y) & 1; surface->FillRectangle(rcSelMargin, invertPhase ? *pixmapSelPattern : *pixmapSelPatternOffset1); } else { ColourDesired colour; switch (vs.ms[margin].style) { case SC_MARGIN_BACK: colour = vs.styles[STYLE_DEFAULT].back; break; case SC_MARGIN_FORE: colour = vs.styles[STYLE_DEFAULT].fore; break; default: colour = vs.styles[STYLE_LINENUMBER].back; break; } surface->FillRectangle(rcSelMargin, colour); } } else { surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back); } const int lineStartPaint = (rcMargin.top + ptOrigin.y) / vs.lineHeight; int visibleLine = TopLineOfMain() + lineStartPaint; int yposScreen = lineStartPaint * vs.lineHeight - ptOrigin.y; // Work out whether the top line is whitespace located after a // lessening of fold level which implies a 'fold tail' but which should not // be displayed until the last of a sequence of whitespace. bool needWhiteClosure = false; if (vs.ms[margin].mask & SC_MASK_FOLDERS) { int level = pdoc->GetLevel(cs.DocFromDisplay(visibleLine)); if (level & SC_FOLDLEVELWHITEFLAG) { int lineBack = cs.DocFromDisplay(visibleLine); int levelPrev = level; while ((lineBack > 0) && (levelPrev & SC_FOLDLEVELWHITEFLAG)) { lineBack--; levelPrev = pdoc->GetLevel(lineBack); } if (!(levelPrev & SC_FOLDLEVELHEADERFLAG)) { if ((level & SC_FOLDLEVELNUMBERMASK) < (levelPrev & SC_FOLDLEVELNUMBERMASK)) needWhiteClosure = true; } } if (highlightDelimiter.isEnabled) { int lastLine = cs.DocFromDisplay(topLine + LinesOnScreen()) + 1; pdoc->GetHighlightDelimiters(highlightDelimiter, pdoc->LineFromPosition(CurrentPosition()), lastLine); } } // Old code does not know about new markers needed to distinguish all cases int folderOpenMid = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEROPENMID, SC_MARKNUM_FOLDEROPEN); int folderEnd = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEREND, SC_MARKNUM_FOLDER); while ((visibleLine < cs.LinesDisplayed()) && yposScreen < rc.bottom) { PLATFORM_ASSERT(visibleLine < cs.LinesDisplayed()); int lineDoc = cs.DocFromDisplay(visibleLine); PLATFORM_ASSERT(cs.GetVisible(lineDoc)); bool firstSubLine = visibleLine == cs.DisplayFromDoc(lineDoc); bool lastSubLine = visibleLine == (cs.DisplayFromDoc(lineDoc + 1) - 1); int marks = pdoc->GetMark(lineDoc); if (!firstSubLine) marks = 0; bool headWithTail = false; if (vs.ms[margin].mask & SC_MASK_FOLDERS) { // Decide which fold indicator should be displayed int level = pdoc->GetLevel(lineDoc); int levelNext = pdoc->GetLevel(lineDoc + 1); int levelNum = level & SC_FOLDLEVELNUMBERMASK; int levelNextNum = levelNext & SC_FOLDLEVELNUMBERMASK; if (level & SC_FOLDLEVELHEADERFLAG) { if (firstSubLine) { if (levelNum < levelNextNum) { if (cs.GetExpanded(lineDoc)) { if (levelNum == SC_FOLDLEVELBASE) marks |= 1 << SC_MARKNUM_FOLDEROPEN; else marks |= 1 << folderOpenMid; } else { if (levelNum == SC_FOLDLEVELBASE) marks |= 1 << SC_MARKNUM_FOLDER; else marks |= 1 << folderEnd; } } else if (levelNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } else { if (levelNum < levelNextNum) { if (cs.GetExpanded(lineDoc)) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } else if (levelNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } else if (levelNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } needWhiteClosure = false; int firstFollowupLine = cs.DocFromDisplay(cs.DisplayFromDoc(lineDoc + 1)); int firstFollowupLineLevel = pdoc->GetLevel(firstFollowupLine); int secondFollowupLineLevelNum = pdoc->GetLevel(firstFollowupLine + 1) & SC_FOLDLEVELNUMBERMASK; if (!cs.GetExpanded(lineDoc)) { if ((firstFollowupLineLevel & SC_FOLDLEVELWHITEFLAG) && (levelNum > secondFollowupLineLevelNum)) needWhiteClosure = true; if (highlightDelimiter.IsFoldBlockHighlighted(firstFollowupLine)) headWithTail = true; } } else if (level & SC_FOLDLEVELWHITEFLAG) { if (needWhiteClosure) { if (levelNext & SC_FOLDLEVELWHITEFLAG) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } else if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; needWhiteClosure = false; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; needWhiteClosure = false; } } else if (levelNum > SC_FOLDLEVELBASE) { if (levelNextNum < levelNum) { if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } } else if (levelNum > SC_FOLDLEVELBASE) { if (levelNextNum < levelNum) { needWhiteClosure = false; if (levelNext & SC_FOLDLEVELWHITEFLAG) { marks |= 1 << SC_MARKNUM_FOLDERSUB; needWhiteClosure = true; } else if (lastSubLine) { if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } } marks &= vs.ms[margin].mask; PRectangle rcMarker = rcSelMargin; rcMarker.top = yposScreen; rcMarker.bottom = yposScreen + vs.lineHeight; if (vs.ms[margin].style == SC_MARGIN_NUMBER) { if (firstSubLine) { char number[100] = ""; if (lineDoc >= 0) sprintf(number, "%d", lineDoc + 1); if (foldFlags & SC_FOLDFLAG_LEVELNUMBERS) { int lev = pdoc->GetLevel(lineDoc); sprintf(number, "%c%c %03X %03X", (lev & SC_FOLDLEVELHEADERFLAG) ? 'H' : '_', (lev & SC_FOLDLEVELWHITEFLAG) ? 'W' : '_', lev & SC_FOLDLEVELNUMBERMASK, lev >> 16 ); } PRectangle rcNumber = rcMarker; // Right justify XYPOSITION width = surface->WidthText(vs.styles[STYLE_LINENUMBER].font, number, istrlen(number)); XYPOSITION xpos = rcNumber.right - width - vs.marginNumberPadding; rcNumber.left = xpos; surface->DrawTextNoClip(rcNumber, vs.styles[STYLE_LINENUMBER].font, rcNumber.top + vs.maxAscent, number, istrlen(number), vs.styles[STYLE_LINENUMBER].fore, vs.styles[STYLE_LINENUMBER].back); } else if (vs.wrapVisualFlags & SC_WRAPVISUALFLAG_MARGIN) { PRectangle rcWrapMarker = rcMarker; rcWrapMarker.right -= 3; rcWrapMarker.left = rcWrapMarker.right - vs.styles[STYLE_LINENUMBER].aveCharWidth; DrawWrapMarker(surface, rcWrapMarker, false, vs.styles[STYLE_LINENUMBER].fore); } } else if (vs.ms[margin].style == SC_MARGIN_TEXT || vs.ms[margin].style == SC_MARGIN_RTEXT) { if (firstSubLine) { const StyledText stMargin = pdoc->MarginStyledText(lineDoc); if (stMargin.text && ValidStyledText(vs, vs.marginStyleOffset, stMargin)) { surface->FillRectangle(rcMarker, vs.styles[stMargin.StyleAt(0)+vs.marginStyleOffset].back); if (vs.ms[margin].style == SC_MARGIN_RTEXT) { int width = WidestLineWidth(surface, vs, vs.marginStyleOffset, stMargin); rcMarker.left = rcMarker.right - width - 3; } DrawStyledText(surface, vs, vs.marginStyleOffset, rcMarker, rcMarker.top + vs.maxAscent, stMargin, 0, stMargin.length); } } } if (marks) { for (int markBit = 0; (markBit < 32) && marks; markBit++) { if (marks & 1) { LineMarker::typeOfFold tFold = LineMarker::undefined; if ((vs.ms[margin].mask & SC_MASK_FOLDERS) && highlightDelimiter.IsFoldBlockHighlighted(lineDoc)) { if (highlightDelimiter.IsBodyOfFoldBlock(lineDoc)) { tFold = LineMarker::body; } else if (highlightDelimiter.IsHeadOfFoldBlock(lineDoc)) { if (firstSubLine) { tFold = headWithTail ? LineMarker::headWithTail : LineMarker::head; } else { if (cs.GetExpanded(lineDoc) || headWithTail) { tFold = LineMarker::body; } else { tFold = LineMarker::undefined; } } } else if (highlightDelimiter.IsTailOfFoldBlock(lineDoc)) { tFold = LineMarker::tail; } } vs.markers[markBit].Draw(surface, rcMarker, vs.styles[STYLE_LINENUMBER].font, tFold, vs.ms[margin].style); } marks >>= 1; } } visibleLine++; yposScreen += vs.lineHeight; } } } PRectangle rcBlankMargin = rcMargin; rcBlankMargin.left = rcSelMargin.right; surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back); if (bufferedDraw) { surfWindow->Copy(rcMargin, Point(rcMargin.left, rcMargin.top), *pixmapSelMargin); } } void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) { int ydiff = (rcTab.bottom - rcTab.top) / 2; int xhead = rcTab.right - 1 - ydiff; if (xhead <= rcTab.left) { ydiff -= rcTab.left - xhead - 1; xhead = rcTab.left - 1; } if ((rcTab.left + 2) < (rcTab.right - 1)) surface->MoveTo(rcTab.left + 2, ymid); else surface->MoveTo(rcTab.right - 1, ymid); surface->LineTo(rcTab.right - 1, ymid); surface->LineTo(xhead, ymid - ydiff); surface->MoveTo(rcTab.right - 1, ymid); surface->LineTo(xhead, ymid + ydiff); } LineLayout *Editor::RetrieveLineLayout(int lineNumber) { int posLineStart = pdoc->LineStart(lineNumber); int posLineEnd = pdoc->LineStart(lineNumber + 1); PLATFORM_ASSERT(posLineEnd >= posLineStart); int lineCaret = pdoc->LineFromPosition(sel.MainCaret()); return llc.Retrieve(lineNumber, lineCaret, posLineEnd - posLineStart, pdoc->GetStyleClock(), LinesOnScreen() + 1, pdoc->LinesTotal()); } /** * Fill in the LineLayout data for the given line. * Copy the given @a line and its styles from the document into local arrays. * Also determine the x position at which each character starts. */ void Editor::LayoutLine(int line, Surface *surface, ViewStyle &vstyle, LineLayout *ll, int width) { //void LayoutLine(int line, Surface *surface, ViewStyle &vstyle, LineLayout *ll, int width, Document *pdoc, PositionCache &posCache, SpecialRepresentations &reprs) { if (!ll) return; PLATFORM_ASSERT(line < pdoc->LinesTotal()); PLATFORM_ASSERT(ll->chars != NULL); int posLineStart = pdoc->LineStart(line); int posLineEnd = pdoc->LineStart(line + 1); // If the line is very long, limit the treatment to a length that should fit in the viewport if (posLineEnd > (posLineStart + ll->maxLineLength)) { posLineEnd = posLineStart + ll->maxLineLength; } if (ll->validity == LineLayout::llCheckTextAndStyle) { int lineLength = posLineEnd - posLineStart; if (!vstyle.viewEOL) { lineLength = pdoc->LineEnd(line) - posLineStart; } if (lineLength == ll->numCharsInLine) { // See if chars, styles, indicators, are all the same bool allSame = true; const int styleMask = pdoc->stylingBitsMask; // Check base line layout char styleByte = 0; int numCharsInLine = 0; while (numCharsInLine < lineLength) { int charInDoc = numCharsInLine + posLineStart; char chDoc = pdoc->CharAt(charInDoc); styleByte = pdoc->StyleAt(charInDoc); allSame = allSame && (ll->styles[numCharsInLine] == static_cast(styleByte & styleMask)); allSame = allSame && (ll->indicators[numCharsInLine] == static_cast(styleByte & ~styleMask)); if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseMixed) allSame = allSame && (ll->chars[numCharsInLine] == chDoc); else if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseLower) allSame = allSame && (ll->chars[numCharsInLine] == static_cast(tolower(chDoc))); else // Style::caseUpper allSame = allSame && (ll->chars[numCharsInLine] == static_cast(toupper(chDoc))); numCharsInLine++; } allSame = allSame && (ll->styles[numCharsInLine] == styleByte); // For eolFilled if (allSame) { ll->validity = LineLayout::llPositions; } else { ll->validity = LineLayout::llInvalid; } } else { ll->validity = LineLayout::llInvalid; } } if (ll->validity == LineLayout::llInvalid) { ll->widthLine = LineLayout::wrapWidthInfinite; ll->lines = 1; if (vstyle.edgeState == EDGE_BACKGROUND) { ll->edgeColumn = pdoc->FindColumn(line, vstyle.theEdge); if (ll->edgeColumn >= posLineStart) { ll->edgeColumn -= posLineStart; } } else { ll->edgeColumn = -1; } char styleByte; const int styleMask = pdoc->stylingBitsMask; ll->styleBitsSet = 0; // Fill base line layout const int lineLength = posLineEnd - posLineStart; pdoc->GetCharRange(ll->chars, posLineStart, lineLength); pdoc->GetStyleRange(ll->styles, posLineStart, lineLength); int numCharsBeforeEOL = pdoc->LineEnd(line) - posLineStart; const int numCharsInLine = (vstyle.viewEOL) ? lineLength : numCharsBeforeEOL; for (int styleInLine = 0; styleInLine < numCharsInLine; styleInLine++) { styleByte = ll->styles[styleInLine]; ll->styleBitsSet |= styleByte; ll->styles[styleInLine] = static_cast(styleByte & styleMask); ll->indicators[styleInLine] = static_cast(styleByte & ~styleMask); } styleByte = static_cast(((lineLength > 0) ? ll->styles[lineLength-1] : 0) & styleMask); if (vstyle.someStylesForceCase) { for (int charInLine = 0; charInLinechars[charInLine]; if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseUpper) ll->chars[charInLine] = static_cast(toupper(chDoc)); else if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseLower) ll->chars[charInLine] = static_cast(tolower(chDoc)); } } ll->xHighlightGuide = 0; // Extra element at the end of the line to hold end x position and act as ll->chars[numCharsInLine] = 0; // Also triggers processing in the loops as this is a control character ll->styles[numCharsInLine] = styleByte; // For eolFilled ll->indicators[numCharsInLine] = 0; // Layout the line, determining the position of each character, // with an extra element at the end for the end of the line. ll->positions[0] = 0; bool lastSegItalics = false; BreakFinder bfLayout(ll, 0, numCharsInLine, posLineStart, 0, false, pdoc, &reprs); while (bfLayout.More()) { const TextSegment ts = bfLayout.Next(); std::fill(&ll->positions[ts.start+1], &ll->positions[ts.end()+1], 0.0f); if (vstyle.styles[ll->styles[ts.start]].visible) { if (ts.representation) { XYPOSITION representationWidth = vstyle.controlCharWidth; if (ll->chars[ts.start] == '\t') { // Tab is a special case of representation, taking a variable amount of space representationWidth = ((static_cast((ll->positions[ts.start] + 2) / vstyle.tabWidth) + 1) * vstyle.tabWidth) - ll->positions[ts.start]; } else { if (representationWidth <= 0.0) { XYPOSITION positionsRepr[256]; // Should expand when needed posCache.MeasureWidths(surface, vstyle, STYLE_CONTROLCHAR, ts.representation->stringRep.c_str(), static_cast(ts.representation->stringRep.length()), positionsRepr, pdoc); representationWidth = positionsRepr[ts.representation->stringRep.length()-1] + vstyle.ctrlCharPadding; } } for (int ii=0; ii < ts.length; ii++) ll->positions[ts.start + 1 + ii] = representationWidth; } else { if ((ts.length == 1) && (' ' == ll->chars[ts.start])) { // Over half the segments are single characters and of these about half are space characters. ll->positions[ts.start + 1] = vstyle.styles[ll->styles[ts.start]].spaceWidth; } else { posCache.MeasureWidths(surface, vstyle, ll->styles[ts.start], ll->chars + ts.start, ts.length, ll->positions + ts.start + 1, pdoc); } } lastSegItalics = (!ts.representation) && ((ll->chars[ts.end()-1] != ' ') && vstyle.styles[ll->styles[ts.start]].italic); } for (int posToIncrease = ts.start+1; posToIncrease <= ts.end(); posToIncrease++) { ll->positions[posToIncrease] += ll->positions[ts.start]; } } // Small hack to make lines that end with italics not cut off the edge of the last character if (lastSegItalics) { ll->positions[numCharsInLine] += vstyle.lastSegItalicsOffset; } ll->numCharsInLine = numCharsInLine; ll->numCharsBeforeEOL = numCharsBeforeEOL; ll->validity = LineLayout::llPositions; } // Hard to cope when too narrow, so just assume there is space if (width < 20) { width = 20; } if ((ll->validity == LineLayout::llPositions) || (ll->widthLine != width)) { ll->widthLine = width; if (width == LineLayout::wrapWidthInfinite) { ll->lines = 1; } else if (width > ll->positions[ll->numCharsInLine]) { // Simple common case where line does not need wrapping. ll->lines = 1; } else { if (vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { width -= static_cast(vstyle.aveCharWidth); // take into account the space for end wrap mark } XYPOSITION wrapAddIndent = 0; // This will be added to initial indent of line if (vstyle.wrapIndentMode == SC_WRAPINDENT_INDENT) { wrapAddIndent = pdoc->IndentSize() * vstyle.spaceWidth; } else if (vstyle.wrapIndentMode == SC_WRAPINDENT_FIXED) { wrapAddIndent = vstyle.wrapVisualStartIndent * vstyle.aveCharWidth; } ll->wrapIndent = wrapAddIndent; if (vstyle.wrapIndentMode != SC_WRAPINDENT_FIXED) for (int i = 0; i < ll->numCharsInLine; i++) { if (!IsSpaceOrTab(ll->chars[i])) { ll->wrapIndent += ll->positions[i]; // Add line indent break; } } // Check for text width minimum if (ll->wrapIndent > width - static_cast(vstyle.aveCharWidth) * 15) ll->wrapIndent = wrapAddIndent; // Check for wrapIndent minimum if ((vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_START) && (ll->wrapIndent < vstyle.aveCharWidth)) ll->wrapIndent = vstyle.aveCharWidth; // Indent to show start visual ll->lines = 0; // Calculate line start positions based upon width. int lastGoodBreak = 0; int lastLineStart = 0; XYACCUMULATOR startOffset = 0; int p = 0; while (p < ll->numCharsInLine) { if ((ll->positions[p + 1] - startOffset) >= width) { if (lastGoodBreak == lastLineStart) { // Try moving to start of last character if (p > 0) { lastGoodBreak = pdoc->MovePositionOutsideChar(p + posLineStart, -1) - posLineStart; } if (lastGoodBreak == lastLineStart) { // Ensure at least one character on line. lastGoodBreak = pdoc->MovePositionOutsideChar(lastGoodBreak + posLineStart + 1, 1) - posLineStart; } } lastLineStart = lastGoodBreak; ll->lines++; ll->SetLineStart(ll->lines, lastGoodBreak); startOffset = ll->positions[lastGoodBreak]; // take into account the space for start wrap mark and indent startOffset -= ll->wrapIndent; p = lastGoodBreak + 1; continue; } if (p > 0) { if (vstyle.wrapState == eWrapChar) { lastGoodBreak = pdoc->MovePositionOutsideChar(p + posLineStart, -1) - posLineStart; p = pdoc->MovePositionOutsideChar(p + 1 + posLineStart, 1) - posLineStart; continue; } else if (ll->styles[p] != ll->styles[p - 1]) { lastGoodBreak = p; } else if (IsSpaceOrTab(ll->chars[p - 1]) && !IsSpaceOrTab(ll->chars[p])) { lastGoodBreak = p; } } p++; } ll->lines++; } ll->validity = LineLayout::llLines; } } ColourDesired Editor::SelectionBackground(ViewStyle &vsDraw, bool main) const { return main ? (primarySelection ? vsDraw.selColours.back : vsDraw.selBackground2) : vsDraw.selAdditionalBackground; } ColourDesired Editor::TextBackground(ViewStyle &vsDraw, bool overrideBackground, ColourDesired background, int inSelection, bool inHotspot, int styleMain, int i, LineLayout *ll) const { if (inSelection == 1) { if (vsDraw.selColours.back.isSet && (vsDraw.selAlpha == SC_ALPHA_NOALPHA)) { return SelectionBackground(vsDraw, true); } } else if (inSelection == 2) { if (vsDraw.selColours.back.isSet && (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA)) { return SelectionBackground(vsDraw, false); } } else { if ((vsDraw.edgeState == EDGE_BACKGROUND) && (i >= ll->edgeColumn) && (i < ll->numCharsBeforeEOL)) return vsDraw.edgecolour; if (inHotspot && vsDraw.hotspotColours.back.isSet) return vsDraw.hotspotColours.back; } if (overrideBackground && (styleMain != STYLE_BRACELIGHT) && (styleMain != STYLE_BRACEBAD)) { return background; } else { return vsDraw.styles[styleMain].back; } } void Editor::DrawIndentGuide(Surface *surface, int lineVisible, int lineHeight, int start, PRectangle rcSegment, bool highlight) { Point from(0, ((lineVisible & 1) && (lineHeight & 1)) ? 1 : 0); PRectangle rcCopyArea(start + 1, rcSegment.top, start + 2, rcSegment.bottom); surface->Copy(rcCopyArea, from, highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide); } void Editor::DrawWrapMarker(Surface *surface, PRectangle rcPlace, bool isEndMarker, ColourDesired wrapColour) { surface->PenColour(wrapColour); enum { xa = 1 }; // gap before start int w = rcPlace.right - rcPlace.left - xa - 1; bool xStraight = isEndMarker; // x-mirrored symbol for start marker int x0 = xStraight ? rcPlace.left : rcPlace.right - 1; int y0 = rcPlace.top; int dy = (rcPlace.bottom - rcPlace.top) / 5; int y = (rcPlace.bottom - rcPlace.top) / 2 + dy; struct Relative { Surface *surface; int xBase; int xDir; int yBase; int yDir; void MoveTo(int xRelative, int yRelative) { surface->MoveTo(xBase + xDir * xRelative, yBase + yDir * yRelative); } void LineTo(int xRelative, int yRelative) { surface->LineTo(xBase + xDir * xRelative, yBase + yDir * yRelative); } }; Relative rel = {surface, x0, xStraight ? 1 : -1, y0, 1}; // arrow head rel.MoveTo(xa, y); rel.LineTo(xa + 2*w / 3, y - dy); rel.MoveTo(xa, y); rel.LineTo(xa + 2*w / 3, y + dy); // arrow body rel.MoveTo(xa, y); rel.LineTo(xa + w, y); rel.LineTo(xa + w, y - 2 * dy); rel.LineTo(xa - 1, // on windows lineto is exclusive endpoint, perhaps GTK not... y - 2 * dy); } static void SimpleAlphaRectangle(Surface *surface, PRectangle rc, ColourDesired fill, int alpha) { if (alpha != SC_ALPHA_NOALPHA) { surface->AlphaRectangle(rc, 0, fill, alpha, fill, alpha, 0); } } void DrawTextBlob(Surface *surface, ViewStyle &vsDraw, PRectangle rcSegment, const char *s, ColourDesired textBack, ColourDesired textFore, bool twoPhaseDraw) { if (!twoPhaseDraw) { surface->FillRectangle(rcSegment, textBack); } Font &ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; int normalCharHeight = surface->Ascent(ctrlCharsFont) - surface->InternalLeading(ctrlCharsFont); PRectangle rcCChar = rcSegment; rcCChar.left = rcCChar.left + 1; rcCChar.top = rcSegment.top + vsDraw.maxAscent - normalCharHeight; rcCChar.bottom = rcSegment.top + vsDraw.maxAscent + 1; PRectangle rcCentral = rcCChar; rcCentral.top++; rcCentral.bottom--; surface->FillRectangle(rcCentral, textFore); PRectangle rcChar = rcCChar; rcChar.left++; rcChar.right--; surface->DrawTextClipped(rcChar, ctrlCharsFont, rcSegment.top + vsDraw.maxAscent, s, istrlen(s), textBack, textFore); } void Editor::DrawEOL(Surface *surface, ViewStyle &vsDraw, PRectangle rcLine, LineLayout *ll, int line, int lineEnd, int xStart, int subLine, XYACCUMULATOR subLineStart, bool overrideBackground, ColourDesired background, bool drawWrapMarkEnd, ColourDesired wrapColour) { const int posLineStart = pdoc->LineStart(line); const int styleMask = pdoc->stylingBitsMask; PRectangle rcSegment = rcLine; const bool lastSubLine = subLine == (ll->lines - 1); XYPOSITION virtualSpace = 0; if (lastSubLine) { const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; virtualSpace = sel.VirtualSpaceFor(pdoc->LineEnd(line)) * spaceWidth; } XYPOSITION xEol = ll->positions[lineEnd] - subLineStart; // Fill the virtual space and show selections within it if (virtualSpace) { rcSegment.left = xEol + xStart; rcSegment.right = xEol + xStart + virtualSpace; surface->FillRectangle(rcSegment, overrideBackground ? background : vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back); if (!hideSelection && ((vsDraw.selAlpha == SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA))) { SelectionSegment virtualSpaceRange(SelectionPosition(pdoc->LineEnd(line)), SelectionPosition(pdoc->LineEnd(line), sel.VirtualSpaceFor(pdoc->LineEnd(line)))); for (size_t r=0; rEndLineStyle()].spaceWidth; rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - subLineStart + portion.start.VirtualSpace() * spaceWidth; rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - subLineStart + portion.end.VirtualSpace() * spaceWidth; rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, r == sel.Main())); } } } } } int eolInSelection = 0; int alpha = SC_ALPHA_NOALPHA; if (!hideSelection) { int posAfterLineEnd = pdoc->LineStart(line + 1); eolInSelection = (subLine == (ll->lines - 1)) ? sel.InSelectionForEOL(posAfterLineEnd) : 0; alpha = (eolInSelection == 1) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; } // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on XYPOSITION blobsWidth = 0; if (lastSubLine) { for (int eolPos=ll->numCharsBeforeEOL; eolPosnumCharsInLine; eolPos++) { rcSegment.left = xStart + ll->positions[eolPos] - subLineStart + virtualSpace; rcSegment.right = xStart + ll->positions[eolPos+1] - subLineStart + virtualSpace; blobsWidth += rcSegment.Width(); char hexits[4]; const char *ctrlChar; unsigned char chEOL = ll->chars[eolPos]; if (UTF8IsAscii(chEOL)) { ctrlChar = ControlCharacterString(chEOL); } else { sprintf(hexits, "x%2X", chEOL); ctrlChar = hexits; } int styleMain = ll->styles[eolPos]; ColourDesired textBack = TextBackground(vsDraw, overrideBackground, background, eolInSelection, false, styleMain, eolPos, ll); ColourDesired textFore = vsDraw.styles[styleMain].fore; if (eolInSelection && vsDraw.selColours.fore.isSet) { textFore = (eolInSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; } if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1)) { if (alpha == SC_ALPHA_NOALPHA) { surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1)); } else { surface->FillRectangle(rcSegment, textBack); } } else { surface->FillRectangle(rcSegment, textBack); } DrawTextBlob(surface, vsDraw, rcSegment, ctrlChar, textBack, textFore, twoPhaseDraw); if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1), alpha); } } } // Draw the eol-is-selected rectangle rcSegment.left = xEol + xStart + virtualSpace + blobsWidth; rcSegment.right = rcSegment.left + vsDraw.aveCharWidth; if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1)); } else { if (overrideBackground) { surface->FillRectangle(rcSegment, background); } else if (line < pdoc->LinesTotal() - 1) { surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back); } else if (vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].eolFilled) { surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back); } else { surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back); } if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1), alpha); } } // Fill the remainder of the line rcSegment.left = rcSegment.right; if (rcSegment.left < rcLine.left) rcSegment.left = rcLine.left; rcSegment.right = rcLine.right; if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1)); } else { if (overrideBackground) { surface->FillRectangle(rcSegment, background); } else if (vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].eolFilled) { surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back); } else { surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back); } if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1), alpha); } } if (drawWrapMarkEnd) { PRectangle rcPlace = rcSegment; if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_END_BY_TEXT) { rcPlace.left = xEol + xStart + virtualSpace; rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; } else { // rcLine is clipped to text area rcPlace.right = rcLine.right; rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; } DrawWrapMarker(surface, rcPlace, true, wrapColour); } } void Editor::DrawIndicator(int indicNum, int startPos, int endPos, Surface *surface, ViewStyle &vsDraw, int xStart, PRectangle rcLine, LineLayout *ll, int subLine) { const XYPOSITION subLineStart = ll->positions[ll->LineStart(subLine)]; PRectangle rcIndic( ll->positions[startPos] + xStart - subLineStart, rcLine.top + vsDraw.maxAscent, ll->positions[endPos] + xStart - subLineStart, rcLine.top + vsDraw.maxAscent + 3); vsDraw.indicators[indicNum].Draw(surface, rcIndic, rcLine); } void Editor::DrawIndicators(Surface *surface, ViewStyle &vsDraw, int line, int xStart, PRectangle rcLine, LineLayout *ll, int subLine, int lineEnd, bool under) { // Draw decorators const int posLineStart = pdoc->LineStart(line); const int lineStart = ll->LineStart(subLine); const int posLineEnd = posLineStart + lineEnd; if (!under) { // Draw indicators // foreach indicator... for (int indicnum = 0, mask = 1 << pdoc->stylingBits; mask < 0x100; indicnum++) { if (!(mask & ll->styleBitsSet)) { mask <<= 1; continue; } int startPos = -1; // foreach style pos in line... for (int indicPos = lineStart; indicPos <= lineEnd; indicPos++) { // look for starts... if (startPos < 0) { // NOT in indicator run, looking for START if (indicPos < lineEnd && (ll->indicators[indicPos] & mask)) startPos = indicPos; } // ... or ends if (startPos >= 0) { // IN indicator run, looking for END if (indicPos >= lineEnd || !(ll->indicators[indicPos] & mask)) { // AT end of indicator run, DRAW it! DrawIndicator(indicnum, startPos, indicPos, surface, vsDraw, xStart, rcLine, ll, subLine); // RESET control var startPos = -1; } } } mask <<= 1; } } for (Decoration *deco = pdoc->decorations.root; deco; deco = deco->next) { if (under == vsDraw.indicators[deco->indicator].under) { int startPos = posLineStart + lineStart; if (!deco->rs.ValueAt(startPos)) { startPos = deco->rs.EndRun(startPos); } while ((startPos < posLineEnd) && (deco->rs.ValueAt(startPos))) { int endPos = deco->rs.EndRun(startPos); if (endPos > posLineEnd) endPos = posLineEnd; DrawIndicator(deco->indicator, startPos - posLineStart, endPos - posLineStart, surface, vsDraw, xStart, rcLine, ll, subLine); startPos = deco->rs.EndRun(endPos); } } } // Use indicators to highlight matching braces if ((vs.braceHighlightIndicatorSet && (bracesMatchStyle == STYLE_BRACELIGHT)) || (vs.braceBadLightIndicatorSet && (bracesMatchStyle == STYLE_BRACEBAD))) { int braceIndicator = (bracesMatchStyle == STYLE_BRACELIGHT) ? vs.braceHighlightIndicator : vs.braceBadLightIndicator; if (under == vsDraw.indicators[braceIndicator].under) { Range rangeLine(posLineStart + lineStart, posLineEnd); if (rangeLine.ContainsCharacter(braces[0])) { int braceOffset = braces[0] - posLineStart; if (braceOffset < ll->numCharsInLine) { DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, xStart, rcLine, ll, subLine); } } if (rangeLine.ContainsCharacter(braces[1])) { int braceOffset = braces[1] - posLineStart; if (braceOffset < ll->numCharsInLine) { DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, xStart, rcLine, ll, subLine); } } } } } void Editor::DrawAnnotation(Surface *surface, ViewStyle &vsDraw, int line, int xStart, PRectangle rcLine, LineLayout *ll, int subLine) { int indent = pdoc->GetLineIndentation(line) * vsDraw.spaceWidth; PRectangle rcSegment = rcLine; int annotationLine = subLine - ll->lines; const StyledText stAnnotation = pdoc->AnnotationStyledText(line); if (stAnnotation.text && ValidStyledText(vsDraw, vsDraw.annotationStyleOffset, stAnnotation)) { surface->FillRectangle(rcSegment, vsDraw.styles[0].back); if (vs.annotationVisible == ANNOTATION_BOXED) { // Only care about calculating width if need to draw box int widthAnnotation = WidestLineWidth(surface, vsDraw, vsDraw.annotationStyleOffset, stAnnotation); widthAnnotation += vsDraw.spaceWidth * 2; // Margins rcSegment.left = xStart + indent; rcSegment.right = rcSegment.left + widthAnnotation; } else { rcSegment.left = xStart; } const int annotationLines = pdoc->AnnotationLines(line); size_t start = 0; size_t lengthAnnotation = stAnnotation.LineLength(start); int lineInAnnotation = 0; while ((lineInAnnotation < annotationLine) && (start < stAnnotation.length)) { start += lengthAnnotation + 1; lengthAnnotation = stAnnotation.LineLength(start); lineInAnnotation++; } PRectangle rcText = rcSegment; if (vs.annotationVisible == ANNOTATION_BOXED) { surface->FillRectangle(rcText, vsDraw.styles[stAnnotation.StyleAt(start) + vsDraw.annotationStyleOffset].back); rcText.left += vsDraw.spaceWidth; } DrawStyledText(surface, vsDraw, vsDraw.annotationStyleOffset, rcText, rcText.top + vsDraw.maxAscent, stAnnotation, start, lengthAnnotation); if (vs.annotationVisible == ANNOTATION_BOXED) { surface->PenColour(vsDraw.styles[vsDraw.annotationStyleOffset].fore); surface->MoveTo(rcSegment.left, rcSegment.top); surface->LineTo(rcSegment.left, rcSegment.bottom); surface->MoveTo(rcSegment.right, rcSegment.top); surface->LineTo(rcSegment.right, rcSegment.bottom); if (subLine == ll->lines) { surface->MoveTo(rcSegment.left, rcSegment.top); surface->LineTo(rcSegment.right, rcSegment.top); } if (subLine == ll->lines+annotationLines-1) { surface->MoveTo(rcSegment.left, rcSegment.bottom - 1); surface->LineTo(rcSegment.right, rcSegment.bottom - 1); } } } } void Editor::DrawLine(Surface *surface, ViewStyle &vsDraw, int line, int lineVisible, int xStart, PRectangle rcLine, LineLayout *ll, int subLine) { if (subLine >= ll->lines) { DrawAnnotation(surface, vsDraw, line, xStart, rcLine, ll, subLine); return; // No further drawing } PRectangle rcSegment = rcLine; // Using one font for all control characters so it can be controlled independently to ensure // the box goes around the characters tightly. Seems to be no way to work out what height // is taken by an individual character - internal leading gives varying results. Font &ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; // See if something overrides the line background color: Either if caret is on the line // and background color is set for that, or if a marker is defined that forces its background // color onto the line, or if a marker is defined but has no selection margin in which to // display itself (as long as it's not an SC_MARK_EMPTY marker). These are checked in order // with the earlier taking precedence. When multiple markers cause background override, // the color for the highest numbered one is used. bool overrideBackground = false; ColourDesired background; if ((caret.active || vsDraw.alwaysShowCaretLineBackground) && vsDraw.showCaretLineBackground && (vsDraw.caretLineAlpha == SC_ALPHA_NOALPHA) && ll->containsCaret) { overrideBackground = true; background = vsDraw.caretLineBackground; } if (!overrideBackground) { int marks = pdoc->GetMark(line); for (int markBit = 0; (markBit < 32) && marks; markBit++) { if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_BACKGROUND) && (vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { background = vsDraw.markers[markBit].back; overrideBackground = true; } marks >>= 1; } } if (!overrideBackground) { if (vsDraw.maskInLine) { int marksMasked = pdoc->GetMark(line) & vsDraw.maskInLine; if (marksMasked) { for (int markBit = 0; (markBit < 32) && marksMasked; markBit++) { if ((marksMasked & 1) && (vsDraw.markers[markBit].markType != SC_MARK_EMPTY) && (vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { overrideBackground = true; background = vsDraw.markers[markBit].back; } marksMasked >>= 1; } } } } const bool drawWhitespaceBackground = (vsDraw.viewWhitespace != wsInvisible) && (!overrideBackground) && (vsDraw.whitespaceColours.back.isSet); bool inIndentation = subLine == 0; // Do not handle indentation except on first subline. const XYPOSITION indentWidth = pdoc->IndentSize() * vsDraw.spaceWidth; const XYPOSITION epsilon = 0.0001f; // A small nudge to avoid floating point precision issues const int posLineStart = pdoc->LineStart(line); const int startseg = ll->LineStart(subLine); const XYACCUMULATOR subLineStart = ll->positions[startseg]; int lineStart = 0; int lineEnd = 0; if (subLine < ll->lines) { lineStart = ll->LineStart(subLine); lineEnd = ll->LineStart(subLine + 1); if (subLine == ll->lines - 1) { lineEnd = ll->numCharsBeforeEOL; } } const ColourDesired wrapColour = vsDraw.WrapColour(); bool drawWrapMarkEnd = false; if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { if (subLine + 1 < ll->lines) { drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0; } } if (ll->wrapIndent != 0) { bool continuedWrapLine = false; if (subLine < ll->lines) { continuedWrapLine = ll->LineStart(subLine) != 0; } if (continuedWrapLine) { // draw continuation rect PRectangle rcPlace = rcSegment; rcPlace.left = ll->positions[startseg] + xStart - subLineStart; rcPlace.right = rcPlace.left + ll->wrapIndent; // default bgnd here.. surface->FillRectangle(rcSegment, overrideBackground ? background : vsDraw.styles[STYLE_DEFAULT].back); // main line style would be below but this would be inconsistent with end markers // also would possibly not be the style at wrap point //int styleMain = ll->styles[lineStart]; //surface->FillRectangle(rcPlace, vsDraw.styles[styleMain].back); if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_START) { if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_START_BY_TEXT) rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; else rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; DrawWrapMarker(surface, rcPlace, false, wrapColour); } xStart += static_cast(ll->wrapIndent); } } const bool selBackDrawn = vsDraw.selColours.back.isSet && ((vsDraw.selAlpha == SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA)); // Does not take margin into account but not significant const int xStartVisible = static_cast(subLineStart) - xStart; ll->psel = &sel; if (twoPhaseDraw) { BreakFinder bfBack(ll, lineStart, lineEnd, posLineStart, xStartVisible, selBackDrawn, pdoc, &reprs); // Background drawing loop while (bfBack.More()) { const TextSegment ts = bfBack.Next(); const int i = ts.end() - 1; const int iDoc = i + posLineStart; rcSegment.left = ll->positions[ts.start] + xStart - subLineStart; rcSegment.right = ll->positions[ts.end()] + xStart - subLineStart; // Only try to draw if really visible - enhances performance by not calling environment to // draw strings that are completely past the right side of the window. if (rcSegment.Intersects(rcLine)) { // Clip to line rectangle, since may have a huge position which will not work with some platforms if (rcSegment.left < rcLine.left) rcSegment.left = rcLine.left; if (rcSegment.right > rcLine.right) rcSegment.right = rcLine.right; const int inSelection = hideSelection ? 0 : sel.CharacterInSelection(iDoc); const bool inHotspot = (ll->hsStart != -1) && (iDoc >= ll->hsStart) && (iDoc < ll->hsEnd); ColourDesired textBack = TextBackground(vsDraw, overrideBackground, background, inSelection, inHotspot, ll->styles[i], i, ll); if (ts.representation) { if (ll->chars[i] == '\t') { // Tab display if (drawWhitespaceBackground && (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) textBack = vsDraw.whitespaceColours.back; } else { // Blob display inIndentation = false; } surface->FillRectangle(rcSegment, textBack); } else { // Normal text display surface->FillRectangle(rcSegment, textBack); if (vsDraw.viewWhitespace != wsInvisible || (inIndentation && vsDraw.viewIndentationGuides == ivReal)) { for (int cpos = 0; cpos <= i - ts.start; cpos++) { if (ll->chars[cpos + ts.start] == ' ') { if (drawWhitespaceBackground && (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { PRectangle rcSpace(ll->positions[cpos + ts.start] + xStart - subLineStart, rcSegment.top, ll->positions[cpos + ts.start + 1] + xStart - subLineStart, rcSegment.bottom); surface->FillRectangle(rcSpace, vsDraw.whitespaceColours.back); } } else { inIndentation = false; } } } } } else if (rcSegment.left > rcLine.right) { break; } } DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, xStart, subLine, subLineStart, overrideBackground, background, drawWrapMarkEnd, wrapColour); } DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineEnd, true); if (vsDraw.edgeState == EDGE_LINE) { int edgeX = vsDraw.theEdge * vsDraw.spaceWidth; rcSegment.left = edgeX + xStart; if ((ll->wrapIndent != 0) && (lineStart != 0)) rcSegment.left -= ll->wrapIndent; rcSegment.right = rcSegment.left + 1; surface->FillRectangle(rcSegment, vsDraw.edgecolour); } // Draw underline mark as part of background if not transparent int marks = pdoc->GetMark(line); int markBit; for (markBit = 0; (markBit < 32) && marks; markBit++) { if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_UNDERLINE) && (vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { PRectangle rcUnderline = rcLine; rcUnderline.top = rcUnderline.bottom - 2; surface->FillRectangle(rcUnderline, vsDraw.markers[markBit].back); } marks >>= 1; } inIndentation = subLine == 0; // Do not handle indentation except on first subline. // Foreground drawing loop BreakFinder bfFore(ll, lineStart, lineEnd, posLineStart, xStartVisible, ((!twoPhaseDraw && selBackDrawn) || vsDraw.selColours.fore.isSet), pdoc, &reprs); while (bfFore.More()) { const TextSegment ts = bfFore.Next(); const int i = ts.end() - 1; const int iDoc = i + posLineStart; rcSegment.left = ll->positions[ts.start] + xStart - subLineStart; rcSegment.right = ll->positions[ts.end()] + xStart - subLineStart; // Only try to draw if really visible - enhances performance by not calling environment to // draw strings that are completely past the right side of the window. if (rcSegment.Intersects(rcLine)) { int styleMain = ll->styles[i]; ColourDesired textFore = vsDraw.styles[styleMain].fore; Font &textFont = vsDraw.styles[styleMain].font; //hotspot foreground if (ll->hsStart != -1 && iDoc >= ll->hsStart && iDoc < hsEnd) { if (vsDraw.hotspotColours.fore.isSet) textFore = vsDraw.hotspotColours.fore; } const int inSelection = hideSelection ? 0 : sel.CharacterInSelection(iDoc); if (inSelection && (vsDraw.selColours.fore.isSet)) { textFore = (inSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; } const bool inHotspot = (ll->hsStart != -1) && (iDoc >= ll->hsStart) && (iDoc < ll->hsEnd); ColourDesired textBack = TextBackground(vsDraw, overrideBackground, background, inSelection, inHotspot, styleMain, i, ll); if (ts.representation) { if (ll->chars[i] == '\t') { // Tab display if (!twoPhaseDraw) { if (drawWhitespaceBackground && (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) textBack = vsDraw.whitespaceColours.back; surface->FillRectangle(rcSegment, textBack); } if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { for (int indentCount = (ll->positions[i] + epsilon) / indentWidth; indentCount <= (ll->positions[i + 1] - epsilon) / indentWidth; indentCount++) { if (indentCount > 0) { int xIndent = indentCount * indentWidth; DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, (ll->xHighlightGuide == xIndent)); } } } if (vsDraw.viewWhitespace != wsInvisible) { if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { if (vsDraw.whitespaceColours.fore.isSet) textFore = vsDraw.whitespaceColours.fore; surface->PenColour(textFore); PRectangle rcTab(rcSegment.left + 1, rcSegment.top + 4, rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent); DrawTabArrow(surface, rcTab, rcSegment.top + vsDraw.lineHeight / 2); } } } else { inIndentation = false; if (vsDraw.controlCharSymbol >= 32) { char cc[2] = { static_cast(vsDraw.controlCharSymbol), '\0' }; surface->DrawTextNoClip(rcSegment, ctrlCharsFont, rcSegment.top + vsDraw.maxAscent, cc, 1, textBack, textFore); } else { DrawTextBlob(surface, vsDraw, rcSegment, ts.representation->stringRep.c_str(), textBack, textFore, twoPhaseDraw); } } } else { // Normal text display if (vsDraw.styles[styleMain].visible) { if (twoPhaseDraw) { surface->DrawTextTransparent(rcSegment, textFont, rcSegment.top + vsDraw.maxAscent, ll->chars + ts.start, i - ts.start + 1, textFore); } else { surface->DrawTextNoClip(rcSegment, textFont, rcSegment.top + vsDraw.maxAscent, ll->chars + ts.start, i - ts.start + 1, textFore, textBack); } } if (vsDraw.viewWhitespace != wsInvisible || (inIndentation && vsDraw.viewIndentationGuides != ivNone)) { for (int cpos = 0; cpos <= i - ts.start; cpos++) { if (ll->chars[cpos + ts.start] == ' ') { if (vsDraw.viewWhitespace != wsInvisible) { if (vsDraw.whitespaceColours.fore.isSet) textFore = vsDraw.whitespaceColours.fore; if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { XYPOSITION xmid = (ll->positions[cpos + ts.start] + ll->positions[cpos + ts.start + 1]) / 2; if (!twoPhaseDraw && drawWhitespaceBackground && (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { textBack = vsDraw.whitespaceColours.back; PRectangle rcSpace(ll->positions[cpos + ts.start] + xStart - subLineStart, rcSegment.top, ll->positions[cpos + ts.start + 1] + xStart - subLineStart, rcSegment.bottom); surface->FillRectangle(rcSpace, textBack); } PRectangle rcDot(xmid + xStart - subLineStart, rcSegment.top + vsDraw.lineHeight / 2, 0, 0); rcDot.right = rcDot.left + vs.whitespaceSize; rcDot.bottom = rcDot.top + vs.whitespaceSize; surface->FillRectangle(rcDot, textFore); } } if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { for (int indentCount = (ll->positions[cpos + ts.start] + epsilon) / indentWidth; indentCount <= (ll->positions[cpos + ts.start + 1] - epsilon) / indentWidth; indentCount++) { if (indentCount > 0) { int xIndent = indentCount * indentWidth; DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, (ll->xHighlightGuide == xIndent)); } } } } else { inIndentation = false; } } } } if (ll->hsStart != -1 && vsDraw.hotspotUnderline && iDoc >= ll->hsStart && iDoc < ll->hsEnd) { PRectangle rcUL = rcSegment; rcUL.top = rcUL.top + vsDraw.maxAscent + 1; rcUL.bottom = rcUL.top + 1; if (vsDraw.hotspotColours.fore.isSet) surface->FillRectangle(rcUL, vsDraw.hotspotColours.fore); else surface->FillRectangle(rcUL, textFore); } else if (vsDraw.styles[styleMain].underline) { PRectangle rcUL = rcSegment; rcUL.top = rcUL.top + vsDraw.maxAscent + 1; rcUL.bottom = rcUL.top + 1; surface->FillRectangle(rcUL, textFore); } } else if (rcSegment.left > rcLine.right) { break; } } if ((vsDraw.viewIndentationGuides == ivLookForward || vsDraw.viewIndentationGuides == ivLookBoth) && (subLine == 0)) { int indentSpace = pdoc->GetLineIndentation(line); int xStartText = ll->positions[pdoc->GetLineIndentPosition(line) - posLineStart]; // Find the most recent line with some text int lineLastWithText = line; while (lineLastWithText > Platform::Maximum(line-20, 0) && pdoc->IsWhiteLine(lineLastWithText)) { lineLastWithText--; } if (lineLastWithText < line) { xStartText = 100000; // Don't limit to visible indentation on empty line // This line is empty, so use indentation of last line with text int indentLastWithText = pdoc->GetLineIndentation(lineLastWithText); int isFoldHeader = pdoc->GetLevel(lineLastWithText) & SC_FOLDLEVELHEADERFLAG; if (isFoldHeader) { // Level is one more level than parent indentLastWithText += pdoc->IndentSize(); } if (vsDraw.viewIndentationGuides == ivLookForward) { // In viLookForward mode, previous line only used if it is a fold header if (isFoldHeader) { indentSpace = Platform::Maximum(indentSpace, indentLastWithText); } } else { // viLookBoth indentSpace = Platform::Maximum(indentSpace, indentLastWithText); } } int lineNextWithText = line; while (lineNextWithText < Platform::Minimum(line+20, pdoc->LinesTotal()) && pdoc->IsWhiteLine(lineNextWithText)) { lineNextWithText++; } if (lineNextWithText > line) { xStartText = 100000; // Don't limit to visible indentation on empty line // This line is empty, so use indentation of first next line with text indentSpace = Platform::Maximum(indentSpace, pdoc->GetLineIndentation(lineNextWithText)); } for (int indentPos = pdoc->IndentSize(); indentPos < indentSpace; indentPos += pdoc->IndentSize()) { int xIndent = indentPos * vsDraw.spaceWidth; if (xIndent < xStartText) { DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, (ll->xHighlightGuide == xIndent)); } } } DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineEnd, false); // End of the drawing of the current line if (!twoPhaseDraw) { DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, xStart, subLine, subLineStart, overrideBackground, background, drawWrapMarkEnd, wrapColour); } if (!hideSelection && ((vsDraw.selAlpha != SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha != SC_ALPHA_NOALPHA))) { // For each selection draw int virtualSpaces = 0; if (subLine == (ll->lines - 1)) { virtualSpaces = sel.VirtualSpaceFor(pdoc->LineEnd(line)); } SelectionPosition posStart(posLineStart + lineStart); SelectionPosition posEnd(posLineStart + lineEnd, virtualSpaces); SelectionSegment virtualSpaceRange(posStart, posEnd); for (size_t r=0; rEndLineStyle()].spaceWidth; rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - subLineStart + portion.start.VirtualSpace() * spaceWidth; rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - subLineStart + portion.end.VirtualSpace() * spaceWidth; if ((ll->wrapIndent != 0) && (lineStart != 0)) { if ((portion.start.Position() - posLineStart) == lineStart && sel.Range(r).ContainsCharacter(portion.start.Position() - 1)) rcSegment.left -= static_cast(ll->wrapIndent); // indentation added to xStart was truncated to int, so we do the same here } rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; if (rcSegment.right > rcLine.left) SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, r == sel.Main()), alpha); } } } } // Draw any translucent whole line states rcSegment = rcLine; HTTP/1.1 200 OK Connection: keep-alive Connection: keep-alive Content-Disposition: inline; filename="Editor.cxx" Content-Disposition: inline; filename="Editor.cxx" Content-Length: 300609 Content-Length: 300609 Content-Security-Policy: default-src 'none' Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Type: text/plain; charset=UTF-8 Date: Thu, 23 Oct 2025 03:27:24 UTC ETag: "8fda6b9ba4ab089275b902a0bed196048ee63406" ETag: "8fda6b9ba4ab089275b902a0bed196048ee63406" Expires: Sun, 21 Oct 2035 03:27:24 GMT Expires: Sun, 21 Oct 2035 03:27:24 GMT Last-Modified: Thu, 23 Oct 2025 03:27:24 GMT Last-Modified: Thu, 23 Oct 2025 03:27:24 GMT Server: OpenBSD httpd Server: OpenBSD httpd X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff // Scintilla source code edit control /** @file Editor.cxx ** Main code for the edit control. **/ // Copyright 1998-2011 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include #include #include #include #include #include #include "Platform.h" #include "ILexer.h" #include "Scintilla.h" #include "SplitVector.h" #include "Partitioning.h" #include "RunStyles.h" #include "ContractionState.h" #include "CellBuffer.h" #include "KeyMap.h" #include "Indicator.h" #include "XPM.h" #include "LineMarker.h" #include "Style.h" #include "ViewStyle.h" #include "CharClassify.h" #include "Decoration.h" #include "CaseFolder.h" #include "Document.h" #include "UniConversion.h" #include "Selection.h" #include "PositionCache.h" #include "Editor.h" #ifdef SCI_NAMESPACE using namespace Scintilla; #endif /* return whether this modification represents an operation that may reasonably be deferred (not done now OR [possibly] at all) */ static bool CanDeferToLastStep(const DocModification &mh) { if (mh.modificationType & (SC_MOD_BEFOREINSERT | SC_MOD_BEFOREDELETE)) return true; // CAN skip if (!(mh.modificationType & (SC_PERFORMED_UNDO | SC_PERFORMED_REDO))) return false; // MUST do if (mh.modificationType & SC_MULTISTEPUNDOREDO) return true; // CAN skip return false; // PRESUMABLY must do } static bool CanEliminate(const DocModification &mh) { return (mh.modificationType & (SC_MOD_BEFOREINSERT | SC_MOD_BEFOREDELETE)) != 0; } /* return whether this modification represents the FINAL step in a [possibly lengthy] multi-step Undo/Redo sequence */ static bool IsLastStep(const DocModification &mh) { return (mh.modificationType & (SC_PERFORMED_UNDO | SC_PERFORMED_REDO)) != 0 && (mh.modificationType & SC_MULTISTEPUNDOREDO) != 0 && (mh.modificationType & SC_LASTSTEPINUNDOREDO) != 0 && (mh.modificationType & SC_MULTILINEUNDOREDO) != 0; } Caret::Caret() : active(false), on(false), period(500) {} Timer::Timer() : ticking(false), ticksToWait(0), tickerID(0) {} Idler::Idler() : state(false), idlerID(0) {} static inline bool IsControlCharacter(int ch) { // iscntrl returns true for lots of chars > 127 which are displayable return ch >= 0 && ch < ' '; } static inline bool IsAllSpacesOrTabs(char *s, unsigned int len) { for (unsigned int i = 0; i < len; i++) { // This is safe because IsSpaceOrTab() will return false for null terminators if (!IsSpaceOrTab(s[i])) return false; } return true; } PrintParameters::PrintParameters() { magnification = 0; colourMode = SC_PRINT_NORMAL; wrapState = eWrapWord; } Editor::Editor() { ctrlID = 0; stylesValid = false; technology = SC_TECHNOLOGY_DEFAULT; scaleRGBAImage = 100; cursorMode = SC_CURSORNORMAL; hasFocus = false; hideSelection = false; inOverstrike = false; drawOverstrikeCaret = true; errorStatus = 0; mouseDownCaptures = true; bufferedDraw = true; twoPhaseDraw = true; lastClickTime = 0; dwellDelay = SC_TIME_FOREVER; ticksToDwell = SC_TIME_FOREVER; dwelling = false; ptMouseLast.x = 0; ptMouseLast.y = 0; inDragDrop = ddNone; dropWentOutside = false; posDrag = SelectionPosition(invalidPosition); posDrop = SelectionPosition(invalidPosition); hotSpotClickPos = INVALID_POSITION; selectionType = selChar; lastXChosen = 0; lineAnchorPos = 0; originalAnchorPos = 0; wordSelectAnchorStartPos = 0; wordSelectAnchorEndPos = 0; wordSelectInitialCaretPos = -1; primarySelection = true; caretXPolicy = CARET_SLOP | CARET_EVEN; caretXSlop = 50; caretYPolicy = CARET_EVEN; caretYSlop = 0; visiblePolicy = 0; visibleSlop = 0; searchAnchor = 0; xOffset = 0; xCaretMargin = 50; horizontalScrollBarVisible = true; scrollWidth = 2000; trackLineWidth = false; lineWidthMaxSeen = 0; verticalScrollBarVisible = true; endAtLastLine = true; caretSticky = SC_CARETSTICKY_OFF; marginOptions = SC_MARGINOPTION_NONE; mouseSelectionRectangularSwitch = false; multipleSelection = false; additionalSelectionTyping = false; multiPasteMode = SC_MULTIPASTE_ONCE; additionalCaretsBlink = true; additionalCaretsVisible = true; virtualSpaceOptions = SCVS_NONE; pixmapLine = 0; pixmapSelMargin = 0; pixmapSelPattern = 0; pixmapSelPatternOffset1 = 0; pixmapIndentGuide = 0; pixmapIndentGuideHighlight = 0; targetStart = 0; targetEnd = 0; searchFlags = 0; topLine = 0; posTopLine = 0; lengthForEncode = -1; needUpdateUI = 0; ContainerNeedsUpdate(SC_UPDATE_CONTENT); braces[0] = invalidPosition; braces[1] = invalidPosition; bracesMatchStyle = STYLE_BRACEBAD; highlightGuideColumn = 0; paintState = notPainting; paintAbandonedByStyling = false; paintingAllText = false; willRedrawAll = false; modEventMask = SC_MODEVENTMASKALL; pdoc = new Document(); pdoc->AddRef(); pdoc->AddWatcher(this, 0); recordingMacro = false; foldFlags = 0; foldAutomatic = 0; wrapWidth = LineLayout::wrapWidthInfinite; convertPastes = true; hsStart = -1; hsEnd = -1; llc.SetLevel(LineLayoutCache::llcCaret); posCache.SetSize(0x400); SetRepresentations(); } Editor::~Editor() { pdoc->RemoveWatcher(this, 0); pdoc->Release(); pdoc = 0; DropGraphics(true); } void Editor::Finalise() { SetIdle(false); CancelModes(); } void Editor::SetRepresentations() { reprs.Clear(); // C0 control set const char *reps[] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; for (size_t j=0; j < (sizeof(reps) / sizeof(reps[0])); j++) { char c[2] = { static_cast(j), 0 }; reprs.SetRepresentation(c, reps[j]); } // C1 control set // As well as Unicode mode, ISO-8859-1 should use these if (IsUnicodeMode()) { const char *repsC1[] = { "PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA", "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3", "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA", "SOS", "SGCI", "SCI", "CSI", "ST", "OSC", "PM", "APC" }; for (size_t j=0; j < (sizeof(repsC1) / sizeof(repsC1[0])); j++) { char c1[3] = { '\xc2', static_cast(0x80+j), 0 }; reprs.SetRepresentation(c1, repsC1[j]); } } // UTF-8 invalid bytes if (IsUnicodeMode()) { for (int k=0x80; k < 0x100; k++) { char hiByte[2] = { static_cast(k), 0 }; char hexits[4]; sprintf(hexits, "x%2X", k); reprs.SetRepresentation(hiByte, hexits); } } } void Editor::DropGraphics(bool freeObjects) { if (freeObjects) { delete pixmapLine; pixmapLine = 0; delete pixmapSelMargin; pixmapSelMargin = 0; delete pixmapSelPattern; pixmapSelPattern = 0; delete pixmapSelPatternOffset1; pixmapSelPatternOffset1 = 0; delete pixmapIndentGuide; pixmapIndentGuide = 0; delete pixmapIndentGuideHighlight; pixmapIndentGuideHighlight = 0; } else { if (pixmapLine) pixmapLine->Release(); if (pixmapSelMargin) pixmapSelMargin->Release(); if (pixmapSelPattern) pixmapSelPattern->Release(); if (pixmapSelPatternOffset1) pixmapSelPatternOffset1->Release(); if (pixmapIndentGuide) pixmapIndentGuide->Release(); if (pixmapIndentGuideHighlight) pixmapIndentGuideHighlight->Release(); } } void Editor::AllocateGraphics() { if (!pixmapLine) pixmapLine = Surface::Allocate(technology); if (!pixmapSelMargin) pixmapSelMargin = Surface::Allocate(technology); if (!pixmapSelPattern) pixmapSelPattern = Surface::Allocate(technology); if (!pixmapSelPatternOffset1) pixmapSelPatternOffset1 = Surface::Allocate(technology); if (!pixmapIndentGuide) pixmapIndentGuide = Surface::Allocate(technology); if (!pixmapIndentGuideHighlight) pixmapIndentGuideHighlight = Surface::Allocate(technology); } void Editor::InvalidateStyleData() { stylesValid = false; vs.technology = technology; DropGraphics(false); AllocateGraphics(); llc.Invalidate(LineLayout::llInvalid); posCache.Clear(); } void Editor::InvalidateStyleRedraw() { NeedWrapping(); InvalidateStyleData(); Redraw(); } void Editor::RefreshStyleData() { if (!stylesValid) { stylesValid = true; AutoSurface surface(this); if (surface) { vs.Refresh(*surface, pdoc->tabInChars); } SetScrollBars(); SetRectangularRange(); } } Point Editor::GetVisibleOriginInMain() { return Point(0,0); } Point Editor::DocumentPointFromView(Point ptView) { Point ptDocument = ptView; if (wMargin.GetID()) { Point ptOrigin = GetVisibleOriginInMain(); ptDocument.x += ptOrigin.x; ptDocument.y += ptOrigin.y; } else { ptDocument.x += xOffset; ptDocument.y += topLine * vs.lineHeight; } return ptDocument; } int Editor::TopLineOfMain() const { if (wMargin.GetID()) return 0; else return topLine; } PRectangle Editor::GetClientRectangle() { return wMain.GetClientPosition(); } PRectangle Editor::GetTextRectangle() { PRectangle rc = GetClientRectangle(); rc.left += vs.textStart; rc.right -= vs.rightMarginWidth; return rc; } int Editor::LinesOnScreen() { PRectangle rcClient = GetClientRectangle(); int htClient = rcClient.bottom - rcClient.top; //Platform::DebugPrintf("lines on screen = %d\n", htClient / lineHeight + 1); return htClient / vs.lineHeight; } int Editor::LinesToScroll() { int retVal = LinesOnScreen() - 1; if (retVal < 1) return 1; else return retVal; } int Editor::MaxScrollPos() { //Platform::DebugPrintf("Lines %d screen = %d maxScroll = %d\n", //LinesTotal(), LinesOnScreen(), LinesTotal() - LinesOnScreen() + 1); int retVal = cs.LinesDisplayed(); if (endAtLastLine) { retVal -= LinesOnScreen(); } else { retVal--; } if (retVal < 0) { return 0; } else { return retVal; } } const char *ControlCharacterString(unsigned char ch) { const char *reps[] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; if (ch < (sizeof(reps) / sizeof(reps[0]))) { return reps[ch]; } else { return "BAD"; } } /** * Convenience class to ensure LineLayout objects are always disposed. */ class AutoLineLayout { LineLayoutCache &llc; LineLayout *ll; AutoLineLayout &operator=(const AutoLineLayout &); public: AutoLineLayout(LineLayoutCache &llc_, LineLayout *ll_) : llc(llc_), ll(ll_) {} ~AutoLineLayout() { llc.Dispose(ll); ll = 0; } LineLayout *operator->() const { return ll; } operator LineLayout *() const { return ll; } void Set(LineLayout *ll_) { llc.Dispose(ll); ll = ll_; } }; SelectionPosition Editor::ClampPositionIntoDocument(SelectionPosition sp) const { if (sp.Position() < 0) { return SelectionPosition(0); } else if (sp.Position() > pdoc->Length()) { return SelectionPosition(pdoc->Length()); } else { // If not at end of line then set offset to 0 if (!pdoc->IsLineEndPosition(sp.Position())) sp.SetVirtualSpace(0); return sp; } } Point Editor::LocationFromPosition(SelectionPosition pos) { Point pt; RefreshStyleData(); if (pos.Position() == INVALID_POSITION) return pt; int line = pdoc->LineFromPosition(pos.Position()); int lineVisible = cs.DisplayFromDoc(line); //Platform::DebugPrintf("line=%d\n", line); AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(line)); if (surface && ll) { // -1 because of adding in for visible lines in following loop. pt.y = (lineVisible - topLine - 1) * vs.lineHeight; pt.x = 0; unsigned int posLineStart = pdoc->LineStart(line); LayoutLine(line, surface, vs, ll, wrapWidth); int posInLine = pos.Position() - posLineStart; // In case of very long line put x at arbitrary large position if (posInLine > ll->maxLineLength) { pt.x = ll->positions[ll->maxLineLength] - ll->positions[ll->LineStart(ll->lines)]; } for (int subLine = 0; subLine < ll->lines; subLine++) { if ((posInLine >= ll->LineStart(subLine)) && (posInLine <= ll->LineStart(subLine + 1))) { pt.x = ll->positions[posInLine] - ll->positions[ll->LineStart(subLine)]; if (ll->wrapIndent != 0) { int lineStart = ll->LineStart(subLine); if (lineStart != 0) // Wrapped pt.x += ll->wrapIndent; } } if (posInLine >= ll->LineStart(subLine)) { pt.y += vs.lineHeight; } } pt.x += vs.textStart - xOffset; } pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth; return pt; } Point Editor::LocationFromPosition(int pos) { return LocationFromPosition(SelectionPosition(pos)); } int Editor::XFromPosition(int pos) { Point pt = LocationFromPosition(pos); return pt.x - vs.textStart + xOffset; } int Editor::XFromPosition(SelectionPosition sp) { Point pt = LocationFromPosition(sp); return pt.x - vs.textStart + xOffset; } int Editor::LineFromLocation(Point pt) const { return cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); } void Editor::SetTopLine(int topLineNew) { if ((topLine != topLineNew) && (topLineNew >= 0)) { topLine = topLineNew; ContainerNeedsUpdate(SC_UPDATE_V_SCROLL); } posTopLine = pdoc->LineStart(cs.DocFromDisplay(topLine)); } SelectionPosition Editor::SPositionFromLocation(Point pt, bool canReturnInvalid, bool charPosition, bool virtualSpace) { RefreshStyleData(); if (canReturnInvalid) { PRectangle rcClient = GetTextRectangle(); // May be in scroll view coordinates so translate back to main view Point ptOrigin = GetVisibleOriginInMain(); rcClient.Move(-ptOrigin.x, -ptOrigin.y); if (!rcClient.Contains(pt)) return SelectionPosition(INVALID_POSITION); if (pt.x < vs.textStart) return SelectionPosition(INVALID_POSITION); if (pt.y < 0) return SelectionPosition(INVALID_POSITION); } pt = DocumentPointFromView(pt); pt.x = pt.x - vs.textStart; int visibleLine = floor(pt.y / vs.lineHeight); if (!canReturnInvalid && (visibleLine < 0)) visibleLine = 0; int lineDoc = cs.DocFromDisplay(visibleLine); if (canReturnInvalid && (lineDoc < 0)) return SelectionPosition(INVALID_POSITION); if (lineDoc >= pdoc->LinesTotal()) return SelectionPosition(canReturnInvalid ? INVALID_POSITION : pdoc->Length()); unsigned int posLineStart = pdoc->LineStart(lineDoc); SelectionPosition retVal(canReturnInvalid ? INVALID_POSITION : static_cast(posLineStart)); AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); if (surface && ll) { LayoutLine(lineDoc, surface, vs, ll, wrapWidth); int lineStartSet = cs.DisplayFromDoc(lineDoc); int subLine = visibleLine - lineStartSet; if (subLine < ll->lines) { int lineStart = ll->LineStart(subLine); int lineEnd = ll->LineLastVisible(subLine); XYPOSITION subLineStart = ll->positions[lineStart]; if (ll->wrapIndent != 0) { if (lineStart != 0) // Wrapped pt.x -= ll->wrapIndent; } int i = ll->FindBefore(pt.x + subLineStart, lineStart, lineEnd); while (i < lineEnd) { if (charPosition) { if ((pt.x + subLineStart) < (ll->positions[i + 1])) { return SelectionPosition(pdoc->MovePositionOutsideChar(i + posLineStart, 1)); } } else { if ((pt.x + subLineStart) < ((ll->positions[i] + ll->positions[i + 1]) / 2)) { return SelectionPosition(pdoc->MovePositionOutsideChar(i + posLineStart, 1)); } } i++; } if (virtualSpace) { const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; int spaceOffset = (pt.x + subLineStart - ll->positions[lineEnd] + spaceWidth / 2) / spaceWidth; return SelectionPosition(lineEnd + posLineStart, spaceOffset); } else if (canReturnInvalid) { if (pt.x < (ll->positions[lineEnd] - subLineStart)) { return SelectionPosition(pdoc->MovePositionOutsideChar(lineEnd + posLineStart, 1)); } } else { return SelectionPosition(lineEnd + posLineStart); } } if (!canReturnInvalid) return SelectionPosition(ll->numCharsInLine + posLineStart); } return retVal; } int Editor::PositionFromLocation(Point pt, bool canReturnInvalid, bool charPosition) { return SPositionFromLocation(pt, canReturnInvalid, charPosition, false).Position(); } /** * Find the document position corresponding to an x coordinate on a particular document line. * Ensure is between whole characters when document is in multi-byte or UTF-8 mode. */ SelectionPosition Editor::SPositionFromLineX(int lineDoc, int x) { RefreshStyleData(); if (lineDoc >= pdoc->LinesTotal()) return SelectionPosition(pdoc->Length()); //Platform::DebugPrintf("Position of (%d,%d) line = %d top=%d\n", pt.x, pt.y, line, topLine); AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); int retVal = 0; if (surface && ll) { unsigned int posLineStart = pdoc->LineStart(lineDoc); LayoutLine(lineDoc, surface, vs, ll, wrapWidth); int subLine = 0; int lineStart = ll->LineStart(subLine); int lineEnd = ll->LineLastVisible(subLine); XYPOSITION subLineStart = ll->positions[lineStart]; XYPOSITION newX = x; if (ll->wrapIndent != 0) { if (lineStart != 0) // Wrapped newX -= ll->wrapIndent; } int i = ll->FindBefore(newX + subLineStart, lineStart, lineEnd); while (i < lineEnd) { if ((newX + subLineStart) < ((ll->positions[i] + ll->positions[i + 1]) / 2)) { retVal = pdoc->MovePositionOutsideChar(i + posLineStart, 1); return SelectionPosition(retVal); } i++; } const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; int spaceOffset = (newX + subLineStart - ll->positions[lineEnd] + spaceWidth / 2) / spaceWidth; return SelectionPosition(lineEnd + posLineStart, spaceOffset); } return SelectionPosition(retVal); } int Editor::PositionFromLineX(int lineDoc, int x) { return SPositionFromLineX(lineDoc, x).Position(); } /** * If painting then abandon the painting because a wider redraw is needed. * @return true if calling code should stop drawing. */ bool Editor::AbandonPaint() { if ((paintState == painting) && !paintingAllText) { paintState = paintAbandoned; } return paintState == paintAbandoned; } void Editor::RedrawRect(PRectangle rc) { //Platform::DebugPrintf("Redraw %0d,%0d - %0d,%0d\n", rc.left, rc.top, rc.right, rc.bottom); // Clip the redraw rectangle into the client area PRectangle rcClient = GetClientRectangle(); if (rc.top < rcClient.top) rc.top = rcClient.top; if (rc.bottom > rcClient.bottom) rc.bottom = rcClient.bottom; if (rc.left < rcClient.left) rc.left = rcClient.left; if (rc.right > rcClient.right) rc.right = rcClient.right; if ((rc.bottom > rc.top) && (rc.right > rc.left)) { wMain.InvalidateRectangle(rc); } } void Editor::Redraw() { //Platform::DebugPrintf("Redraw all\n"); PRectangle rcClient = GetClientRectangle(); wMain.InvalidateRectangle(rcClient); if (wMargin.GetID()) wMargin.InvalidateAll(); //wMain.InvalidateAll(); } void Editor::RedrawSelMargin(int line, bool allAfter) { if (!AbandonPaint()) { if (vs.maskInLine) { Redraw(); } else { PRectangle rcSelMargin = GetClientRectangle(); rcSelMargin.right = rcSelMargin.left + vs.fixedColumnWidth; if (line != -1) { int position = pdoc->LineStart(line); PRectangle rcLine = RectangleFromRange(position, position); // Inflate line rectangle if there are image markers with height larger than line height if (vs.largestMarkerHeight > vs.lineHeight) { int delta = (vs.largestMarkerHeight - vs.lineHeight + 1) / 2; rcLine.top -= delta; rcLine.bottom += delta; if (rcLine.top < rcSelMargin.top) rcLine.top = rcSelMargin.top; if (rcLine.bottom > rcSelMargin.bottom) rcLine.bottom = rcSelMargin.bottom; } rcSelMargin.top = rcLine.top; if (!allAfter) rcSelMargin.bottom = rcLine.bottom; if (rcSelMargin.Empty()) return; } if (wMargin.GetID()) { Point ptOrigin = GetVisibleOriginInMain(); rcSelMargin.Move(-ptOrigin.x, -ptOrigin.y); wMargin.InvalidateRectangle(rcSelMargin); } else { wMain.InvalidateRectangle(rcSelMargin); } } } } PRectangle Editor::RectangleFromRange(int start, int end) { int minPos = start; if (minPos > end) minPos = end; int maxPos = start; if (maxPos < end) maxPos = end; int minLine = cs.DisplayFromDoc(pdoc->LineFromPosition(minPos)); int lineDocMax = pdoc->LineFromPosition(maxPos); int maxLine = cs.DisplayFromDoc(lineDocMax) + cs.GetHeight(lineDocMax) - 1; PRectangle rcClient = GetTextRectangle(); PRectangle rc; const int leftTextOverlap = ((xOffset == 0) && (vs.leftMarginWidth > 0)) ? 1 : 0; rc.left = vs.textStart - leftTextOverlap; rc.top = (minLine - TopLineOfMain()) * vs.lineHeight; if (rc.top < rcClient.top) rc.top = rcClient.top; rc.right = rcClient.right; rc.bottom = (maxLine - TopLineOfMain() + 1) * vs.lineHeight; return rc; } void Editor::InvalidateRange(int start, int end) { RedrawRect(RectangleFromRange(start, end)); } int Editor::CurrentPosition() const { return sel.MainCaret(); } bool Editor::SelectionEmpty() const { return sel.Empty(); } SelectionPosition Editor::SelectionStart() { return sel.RangeMain().Start(); } SelectionPosition Editor::SelectionEnd() { return sel.RangeMain().End(); } void Editor::SetRectangularRange() { if (sel.IsRectangular()) { int xAnchor = XFromPosition(sel.Rectangular().anchor); int xCaret = XFromPosition(sel.Rectangular().caret); if (sel.selType == Selection::selThin) { xCaret = xAnchor; } int lineAnchorRect = pdoc->LineFromPosition(sel.Rectangular().anchor.Position()); int lineCaret = pdoc->LineFromPosition(sel.Rectangular().caret.Position()); int increment = (lineCaret > lineAnchorRect) ? 1 : -1; for (int line=lineAnchorRect; line != lineCaret+increment; line += increment) { SelectionRange range(SPositionFromLineX(line, xCaret), SPositionFromLineX(line, xAnchor)); if ((virtualSpaceOptions & SCVS_RECTANGULARSELECTION) == 0) range.ClearVirtualSpace(); if (line == lineAnchorRect) sel.SetSelection(range); else sel.AddSelectionWithoutTrim(range); } } } void Editor::ThinRectangularRange() { if (sel.IsRectangular()) { sel.selType = Selection::selThin; if (sel.Rectangular().caret < sel.Rectangular().anchor) { sel.Rectangular() = SelectionRange(sel.Range(sel.Count()-1).caret, sel.Range(0).anchor); } else { sel.Rectangular() = SelectionRange(sel.Range(sel.Count()-1).anchor, sel.Range(0).caret); } SetRectangularRange(); } } void Editor::InvalidateSelection(SelectionRange newMain, bool invalidateWholeSelection) { if (sel.Count() > 1 || !(sel.RangeMain().anchor == newMain.anchor) || sel.IsRectangular()) { invalidateWholeSelection = true; } int firstAffected = Platform::Minimum(sel.RangeMain().Start().Position(), newMain.Start().Position()); // +1 for lastAffected ensures caret repainted int lastAffected = Platform::Maximum(newMain.caret.Position()+1, newMain.anchor.Position()); lastAffected = Platform::Maximum(lastAffected, sel.RangeMain().End().Position()); if (invalidateWholeSelection) { for (size_t r=0; rLineFromPosition(currentPos_.Position()); /* For Line selection - ensure the anchor and caret are always at the beginning and end of the region lines. */ if (sel.selType == Selection::selLines) { if (currentPos_ > anchor_) { anchor_ = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(anchor_.Position()))); currentPos_ = SelectionPosition(pdoc->LineEnd(pdoc->LineFromPosition(currentPos_.Position()))); } else { currentPos_ = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(currentPos_.Position()))); anchor_ = SelectionPosition(pdoc->LineEnd(pdoc->LineFromPosition(anchor_.Position()))); } } SelectionRange rangeNew(currentPos_, anchor_); if (sel.Count() > 1 || !(sel.RangeMain() == rangeNew)) { InvalidateSelection(rangeNew); } sel.RangeMain() = rangeNew; SetRectangularRange(); ClaimSelection(); if (highlightDelimiter.NeedsDrawing(currentLine)) { RedrawSelMargin(); } QueueIdleWork(WorkNeeded::workUpdateUI); } void Editor::SetSelection(int currentPos_, int anchor_) { SetSelection(SelectionPosition(currentPos_), SelectionPosition(anchor_)); } // Just move the caret on the main selection void Editor::SetSelection(SelectionPosition currentPos_) { currentPos_ = ClampPositionIntoDocument(currentPos_); int currentLine = pdoc->LineFromPosition(currentPos_.Position()); if (sel.Count() > 1 || !(sel.RangeMain().caret == currentPos_)) { InvalidateSelection(SelectionRange(currentPos_)); } if (sel.IsRectangular()) { sel.Rectangular() = SelectionRange(SelectionPosition(currentPos_), sel.Rectangular().anchor); SetRectangularRange(); } else { sel.RangeMain() = SelectionRange(SelectionPosition(currentPos_), sel.RangeMain().anchor); } ClaimSelection(); if (highlightDelimiter.NeedsDrawing(currentLine)) { RedrawSelMargin(); } QueueIdleWork(WorkNeeded::workUpdateUI); } void Editor::SetSelection(int currentPos_) { SetSelection(SelectionPosition(currentPos_)); } void Editor::SetEmptySelection(SelectionPosition currentPos_) { int currentLine = pdoc->LineFromPosition(currentPos_.Position()); SelectionRange rangeNew(ClampPositionIntoDocument(currentPos_)); if (sel.Count() > 1 || !(sel.RangeMain() == rangeNew)) { InvalidateSelection(rangeNew); } sel.Clear(); sel.RangeMain() = rangeNew; SetRectangularRange(); ClaimSelection(); if (highlightDelimiter.NeedsDrawing(currentLine)) { RedrawSelMargin(); } QueueIdleWork(WorkNeeded::workUpdateUI); } void Editor::SetEmptySelection(int currentPos_) { SetEmptySelection(SelectionPosition(currentPos_)); } bool Editor::RangeContainsProtected(int start, int end) const { if (vs.ProtectionActive()) { if (start > end) { int t = start; start = end; end = t; } int mask = pdoc->stylingBitsMask; for (int pos = start; pos < end; pos++) { if (vs.styles[pdoc->StyleAt(pos) & mask].IsProtected()) return true; } } return false; } bool Editor::SelectionContainsProtected() { for (size_t r=0; rMovePositionOutsideChar(pos.Position(), moveDir, checkLineEnd); if (posMoved != pos.Position()) pos.SetPosition(posMoved); if (vs.ProtectionActive()) { int mask = pdoc->stylingBitsMask; if (moveDir > 0) { if ((pos.Position() > 0) && vs.styles[pdoc->StyleAt(pos.Position() - 1) & mask].IsProtected()) { while ((pos.Position() < pdoc->Length()) && (vs.styles[pdoc->StyleAt(pos.Position()) & mask].IsProtected())) pos.Add(1); } } else if (moveDir < 0) { if (vs.styles[pdoc->StyleAt(pos.Position()) & mask].IsProtected()) { while ((pos.Position() > 0) && (vs.styles[pdoc->StyleAt(pos.Position() - 1) & mask].IsProtected())) pos.Add(-1); } } } return pos; } int Editor::MovePositionTo(SelectionPosition newPos, Selection::selTypes selt, bool ensureVisible) { bool simpleCaret = (sel.Count() == 1) && sel.Empty(); SelectionPosition spCaret = sel.Last(); int delta = newPos.Position() - sel.MainCaret(); newPos = ClampPositionIntoDocument(newPos); newPos = MovePositionOutsideChar(newPos, delta); if (!multipleSelection && sel.IsRectangular() && (selt == Selection::selStream)) { // Can't turn into multiple selection so clear additional selections InvalidateSelection(SelectionRange(newPos), true); SelectionRange rangeMain = sel.RangeMain(); sel.SetSelection(rangeMain); } if (!sel.IsRectangular() && (selt == Selection::selRectangle)) { // Switching to rectangular InvalidateSelection(sel.RangeMain(), false); SelectionRange rangeMain = sel.RangeMain(); sel.Clear(); sel.Rectangular() = rangeMain; } if (selt != Selection::noSel) { sel.selType = selt; } if (selt != Selection::noSel || sel.MoveExtends()) { SetSelection(newPos); } else { SetEmptySelection(newPos); } ShowCaretAtCurrentPosition(); int currentLine = pdoc->LineFromPosition(newPos.Position()); if (ensureVisible) { // In case in need of wrapping to ensure DisplayFromDoc works. if (currentLine >= wrapPending.start) WrapLines(wsAll); XYScrollPosition newXY = XYScrollToMakeVisible( SelectionRange(posDrag.IsValid() ? posDrag : sel.RangeMain().caret), xysDefault); if (simpleCaret && (newXY.xOffset == xOffset)) { // simple vertical scroll then invalidate ScrollTo(newXY.topLine); InvalidateSelection(SelectionRange(spCaret), true); } else { SetXYScroll(newXY); } } if (highlightDelimiter.NeedsDrawing(currentLine)) { RedrawSelMargin(); } return 0; } int Editor::MovePositionTo(int newPos, Selection::selTypes selt, bool ensureVisible) { return MovePositionTo(SelectionPosition(newPos), selt, ensureVisible); } SelectionPosition Editor::MovePositionSoVisible(SelectionPosition pos, int moveDir) { pos = ClampPositionIntoDocument(pos); pos = MovePositionOutsideChar(pos, moveDir); int lineDoc = pdoc->LineFromPosition(pos.Position()); if (cs.GetVisible(lineDoc)) { return pos; } else { int lineDisplay = cs.DisplayFromDoc(lineDoc); if (moveDir > 0) { // lineDisplay is already line before fold as lines in fold use display line of line after fold lineDisplay = Platform::Clamp(lineDisplay, 0, cs.LinesDisplayed()); return SelectionPosition(pdoc->LineStart(cs.DocFromDisplay(lineDisplay))); } else { lineDisplay = Platform::Clamp(lineDisplay - 1, 0, cs.LinesDisplayed()); return SelectionPosition(pdoc->LineEnd(cs.DocFromDisplay(lineDisplay))); } } } SelectionPosition Editor::MovePositionSoVisible(int pos, int moveDir) { return MovePositionSoVisible(SelectionPosition(pos), moveDir); } Point Editor::PointMainCaret() { return LocationFromPosition(sel.Range(sel.Main()).caret); } /** * Choose the x position that the caret will try to stick to * as it moves up and down. */ void Editor::SetLastXChosen() { Point pt = PointMainCaret(); lastXChosen = pt.x + xOffset; } void Editor::ScrollTo(int line, bool moveThumb) { int topLineNew = Platform::Clamp(line, 0, MaxScrollPos()); if (topLineNew != topLine) { // Try to optimise small scrolls #ifndef UNDER_CE int linesToMove = topLine - topLineNew; bool performBlit = (abs(linesToMove) <= 10) && (paintState == notPainting); willRedrawAll = !performBlit; #endif SetTopLine(topLineNew); // Optimize by styling the view as this will invalidate any needed area // which could abort the initial paint if discovered later. StyleToPositionInView(PositionAfterArea(GetClientRectangle())); #ifndef UNDER_CE // Perform redraw rather than scroll if many lines would be redrawn anyway. if (performBlit) { ScrollText(linesToMove); } else { Redraw(); } willRedrawAll = false; #else Redraw(); #endif if (moveThumb) { SetVerticalScrollPos(); } } } void Editor::ScrollText(int /* linesToMove */) { //Platform::DebugPrintf("Editor::ScrollText %d\n", linesToMove); Redraw(); } void Editor::HorizontalScrollTo(int xPos) { //Platform::DebugPrintf("HorizontalScroll %d\n", xPos); if (xPos < 0) xPos = 0; if (!Wrapping() && (xOffset != xPos)) { xOffset = xPos; ContainerNeedsUpdate(SC_UPDATE_H_SCROLL); SetHorizontalScrollPos(); RedrawRect(GetClientRectangle()); } } void Editor::VerticalCentreCaret() { int lineDoc = pdoc->LineFromPosition(sel.IsRectangular() ? sel.Rectangular().caret.Position() : sel.MainCaret()); int lineDisplay = cs.DisplayFromDoc(lineDoc); int newTop = lineDisplay - (LinesOnScreen() / 2); if (topLine != newTop) { SetTopLine(newTop > 0 ? newTop : 0); RedrawRect(GetClientRectangle()); } } // Avoid 64 bit compiler warnings. // Scintilla does not support text buffers larger than 2**31 static int istrlen(const char *s) { return static_cast(strlen(s)); } void Editor::MoveSelectedLines(int lineDelta) { // if selection doesn't start at the beginning of the line, set the new start int selectionStart = SelectionStart().Position(); int startLine = pdoc->LineFromPosition(selectionStart); int beginningOfStartLine = pdoc->LineStart(startLine); selectionStart = beginningOfStartLine; // if selection doesn't end at the beginning of a line greater than that of the start, // then set it at the beginning of the next one int selectionEnd = SelectionEnd().Position(); int endLine = pdoc->LineFromPosition(selectionEnd); int beginningOfEndLine = pdoc->LineStart(endLine); bool appendEol = false; if (selectionEnd > beginningOfEndLine || selectionStart == selectionEnd) { selectionEnd = pdoc->LineStart(endLine + 1); appendEol = (selectionEnd == pdoc->Length() && pdoc->LineFromPosition(selectionEnd) == endLine); } // if there's nowhere for the selection to move // (i.e. at the beginning going up or at the end going down), // stop it right there! if ((selectionStart == 0 && lineDelta < 0) || (selectionEnd == pdoc->Length() && lineDelta > 0) || selectionStart == selectionEnd) { return; } UndoGroup ug(pdoc); if (lineDelta > 0 && selectionEnd == pdoc->LineStart(pdoc->LinesTotal() - 1)) { SetSelection(pdoc->MovePositionOutsideChar(selectionEnd - 1, -1), selectionEnd); ClearSelection(); selectionEnd = CurrentPosition(); } SetSelection(selectionStart, selectionEnd); SelectionText selectedText; CopySelectionRange(&selectedText); int selectionLength = SelectionRange(selectionStart, selectionEnd).Length(); Point currentLocation = LocationFromPosition(CurrentPosition()); int currentLine = LineFromLocation(currentLocation); if (appendEol) SetSelection(pdoc->MovePositionOutsideChar(selectionStart - 1, -1), selectionEnd); ClearSelection(); const char *eol = StringFromEOLMode(pdoc->eolMode); if (currentLine + lineDelta >= pdoc->LinesTotal()) pdoc->InsertCString(pdoc->Length(), eol); GoToLine(currentLine + lineDelta); pdoc->InsertCString(CurrentPosition(), selectedText.Data()); if (appendEol) { pdoc->InsertCString(CurrentPosition() + selectionLength, eol); selectionLength += istrlen(eol); } SetSelection(CurrentPosition(), CurrentPosition() + selectionLength); } void Editor::MoveSelectedLinesUp() { MoveSelectedLines(-1); } void Editor::MoveSelectedLinesDown() { MoveSelectedLines(1); } void Editor::MoveCaretInsideView(bool ensureVisible) { PRectangle rcClient = GetTextRectangle(); Point pt = PointMainCaret(); if (pt.y < rcClient.top) { MovePositionTo(SPositionFromLocation( Point(lastXChosen - xOffset, rcClient.top), false, false, UserVirtualSpace()), Selection::noSel, ensureVisible); } else if ((pt.y + vs.lineHeight - 1) > rcClient.bottom) { int yOfLastLineFullyDisplayed = rcClient.top + (LinesOnScreen() - 1) * vs.lineHeight; MovePositionTo(SPositionFromLocation( Point(lastXChosen - xOffset, rcClient.top + yOfLastLineFullyDisplayed), false, false, UserVirtualSpace()), Selection::noSel, ensureVisible); } } int Editor::DisplayFromPosition(int pos) { int lineDoc = pdoc->LineFromPosition(pos); int lineDisplay = cs.DisplayFromDoc(lineDoc); AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); if (surface && ll) { LayoutLine(lineDoc, surface, vs, ll, wrapWidth); unsigned int posLineStart = pdoc->LineStart(lineDoc); int posInLine = pos - posLineStart; lineDisplay--; // To make up for first increment ahead. for (int subLine = 0; subLine < ll->lines; subLine++) { if (posInLine >= ll->LineStart(subLine)) { lineDisplay++; } } } return lineDisplay; } /** * Ensure the caret is reasonably visible in context. * Caret policy in SciTE If slop is set, we can define a slop value. This value defines an unwanted zone (UZ) where the caret is... unwanted. This zone is defined as a number of pixels near the vertical margins, and as a number of lines near the horizontal margins. By keeping the caret away from the edges, it is seen within its context, so it is likely that the identifier that the caret is on can be completely seen, and that the current line is seen with some of the lines following it which are often dependent on that line. If strict is set, the policy is enforced... strictly. The caret is centred on the display if slop is not set, and cannot go in the UZ if slop is set. If jumps is set, the display is moved more energetically so the caret can move in the same direction longer before the policy is applied again. '3UZ' notation is used to indicate three time the size of the UZ as a distance to the margin. If even is not set, instead of having symmetrical UZs, the left and bottom UZs are extended up to right and top UZs respectively. This way, we favour the displaying of useful information: the begining of lines, where most code reside, and the lines after the caret, eg. the body of a function. | | | | | slop | strict | jumps | even | Caret can go to the margin | When reaching limit (caret going out of | | | | | visibility or going into the UZ) display is... -----+--------+-------+------+--------------------------------------------+-------------------------------------------------------------- 0 | 0 | 0 | 0 | Yes | moved to put caret on top/on right 0 | 0 | 0 | 1 | Yes | moved by one position 0 | 0 | 1 | 0 | Yes | moved to put caret on top/on right 0 | 0 | 1 | 1 | Yes | centred on the caret 0 | 1 | - | 0 | Caret is always on top/on right of display | - 0 | 1 | - | 1 | No, caret is always centred | - 1 | 0 | 0 | 0 | Yes | moved to put caret out of the asymmetrical UZ 1 | 0 | 0 | 1 | Yes | moved to put caret out of the UZ 1 | 0 | 1 | 0 | Yes | moved to put caret at 3UZ of the top or right margin 1 | 0 | 1 | 1 | Yes | moved to put caret at 3UZ of the margin 1 | 1 | - | 0 | Caret is always at UZ of top/right margin | - 1 | 1 | 0 | 1 | No, kept out of UZ | moved by one position 1 | 1 | 1 | 1 | No, kept out of UZ | moved to put caret at 3UZ of the margin */ Editor::XYScrollPosition Editor::XYScrollToMakeVisible(const SelectionRange range, const XYScrollOptions options) { PRectangle rcClient = GetTextRectangle(); Point pt = LocationFromPosition(range.caret); Point ptAnchor = LocationFromPosition(range.anchor); const Point ptOrigin = GetVisibleOriginInMain(); pt.x += ptOrigin.x; pt.y += ptOrigin.y; ptAnchor.x += ptOrigin.x; ptAnchor.y += ptOrigin.y; const Point ptBottomCaret(pt.x, pt.y + vs.lineHeight - 1); XYScrollPosition newXY(xOffset, topLine); if (rcClient.Empty()) { return newXY; } // Vertical positioning if ((options & xysVertical) && (pt.y < rcClient.top || ptBottomCaret.y >= rcClient.bottom || (caretYPolicy & CARET_STRICT) != 0)) { const int lineCaret = DisplayFromPosition(range.caret.Position()); const int linesOnScreen = LinesOnScreen(); const int halfScreen = Platform::Maximum(linesOnScreen - 1, 2) / 2; const bool bSlop = (caretYPolicy & CARET_SLOP) != 0; const bool bStrict = (caretYPolicy & CARET_STRICT) != 0; const bool bJump = (caretYPolicy & CARET_JUMPS) != 0; const bool bEven = (caretYPolicy & CARET_EVEN) != 0; // It should be possible to scroll the window to show the caret, // but this fails to remove the caret on GTK+ if (bSlop) { // A margin is defined int yMoveT, yMoveB; if (bStrict) { int yMarginT, yMarginB; if (!(options & xysUseMargin)) { // In drag mode, avoid moves // otherwise, a double click will select several lines. yMarginT = yMarginB = 0; } else { // yMarginT must equal to caretYSlop, with a minimum of 1 and // a maximum of slightly less than half the heigth of the text area. yMarginT = Platform::Clamp(caretYSlop, 1, halfScreen); if (bEven) { yMarginB = yMarginT; } else { yMarginB = linesOnScreen - yMarginT - 1; } } yMoveT = yMarginT; if (bEven) { if (bJump) { yMoveT = Platform::Clamp(caretYSlop * 3, 1, halfScreen); } yMoveB = yMoveT; } else { yMoveB = linesOnScreen - yMoveT - 1; } if (lineCaret < topLine + yMarginT) { // Caret goes too high newXY.topLine = lineCaret - yMoveT; } else if (lineCaret > topLine + linesOnScreen - 1 - yMarginB) { // Caret goes too low newXY.topLine = lineCaret - linesOnScreen + 1 + yMoveB; } } else { // Not strict yMoveT = bJump ? caretYSlop * 3 : caretYSlop; yMoveT = Platform::Clamp(yMoveT, 1, halfScreen); if (bEven) { yMoveB = yMoveT; } else { yMoveB = linesOnScreen - yMoveT - 1; } if (lineCaret < topLine) { // Caret goes too high newXY.topLine = lineCaret - yMoveT; } else if (lineCaret > topLine + linesOnScreen - 1) { // Caret goes too low newXY.topLine = lineCaret - linesOnScreen + 1 + yMoveB; } } } else { // No slop if (!bStrict && !bJump) { // Minimal move if (lineCaret < topLine) { // Caret goes too high newXY.topLine = lineCaret; } else if (lineCaret > topLine + linesOnScreen - 1) { // Caret goes too low if (bEven) { newXY.topLine = lineCaret - linesOnScreen + 1; } else { newXY.topLine = lineCaret; } } } else { // Strict or going out of display if (bEven) { // Always center caret newXY.topLine = lineCaret - halfScreen; } else { // Always put caret on top of display newXY.topLine = lineCaret; } } } if (!(range.caret == range.anchor)) { const int lineAnchor = DisplayFromPosition(range.anchor.Position()); if (lineAnchor < lineCaret) { // Shift up to show anchor or as much of range as possible newXY.topLine = std::min(newXY.topLine, lineAnchor); newXY.topLine = std::max(newXY.topLine, lineCaret - LinesOnScreen()); } else { // Shift down to show anchor or as much of range as possible newXY.topLine = std::max(newXY.topLine, lineAnchor - LinesOnScreen()); newXY.topLine = std::min(newXY.topLine, lineCaret); } } newXY.topLine = Platform::Clamp(newXY.topLine, 0, MaxScrollPos()); } // Horizontal positioning if ((options & xysHorizontal) && !Wrapping()) { const int halfScreen = Platform::Maximum(rcClient.Width() - 4, 4) / 2; const bool bSlop = (caretXPolicy & CARET_SLOP) != 0; const bool bStrict = (caretXPolicy & CARET_STRICT) != 0; const bool bJump = (caretXPolicy & CARET_JUMPS) != 0; const bool bEven = (caretXPolicy & CARET_EVEN) != 0; if (bSlop) { // A margin is defined int xMoveL, xMoveR; if (bStrict) { int xMarginL, xMarginR; if (!(options & xysUseMargin)) { // In drag mode, avoid moves unless very near of the margin // otherwise, a simple click will select text. xMarginL = xMarginR = 2; } else { // xMargin must equal to caretXSlop, with a minimum of 2 and // a maximum of slightly less than half the width of the text area. xMarginR = Platform::Clamp(caretXSlop, 2, halfScreen); if (bEven) { xMarginL = xMarginR; } else { xMarginL = rcClient.Width() - xMarginR - 4; } } if (bJump && bEven) { // Jump is used only in even mode xMoveL = xMoveR = Platform::Clamp(caretXSlop * 3, 1, halfScreen); } else { xMoveL = xMoveR = 0; // Not used, avoid a warning } if (pt.x < rcClient.left + xMarginL) { // Caret is on the left of the display if (bJump && bEven) { newXY.xOffset -= xMoveL; } else { // Move just enough to allow to display the caret newXY.xOffset -= (rcClient.left + xMarginL) - pt.x; } } else if (pt.x >= rcClient.right - xMarginR) { // Caret is on the right of the display if (bJump && bEven) { newXY.xOffset += xMoveR; } else { // Move just enough to allow to display the caret newXY.xOffset += pt.x - (rcClient.right - xMarginR) + 1; } } } else { // Not strict xMoveR = bJump ? caretXSlop * 3 : caretXSlop; xMoveR = Platform::Clamp(xMoveR, 1, halfScreen); if (bEven) { xMoveL = xMoveR; } else { xMoveL = rcClient.Width() - xMoveR - 4; } if (pt.x < rcClient.left) { // Caret is on the left of the display newXY.xOffset -= xMoveL; } else if (pt.x >= rcClient.right) { // Caret is on the right of the display newXY.xOffset += xMoveR; } } } else { // No slop if (bStrict || (bJump && (pt.x < rcClient.left || pt.x >= rcClient.right))) { // Strict or going out of display if (bEven) { // Center caret newXY.xOffset += pt.x - rcClient.left - halfScreen; } else { // Put caret on right newXY.xOffset += pt.x - rcClient.right + 1; } } else { // Move just enough to allow to display the caret if (pt.x < rcClient.left) { // Caret is on the left of the display if (bEven) { newXY.xOffset -= rcClient.left - pt.x; } else { newXY.xOffset += pt.x - rcClient.right + 1; } } else if (pt.x >= rcClient.right) { // Caret is on the right of the display newXY.xOffset += pt.x - rcClient.right + 1; } } } // In case of a jump (find result) largely out of display, adjust the offset to display the caret if (pt.x + xOffset < rcClient.left + newXY.xOffset) { newXY.xOffset = pt.x + xOffset - rcClient.left; } else if (pt.x + xOffset >= rcClient.right + newXY.xOffset) { newXY.xOffset = pt.x + xOffset - rcClient.right + 1; if (vs.caretStyle == CARETSTYLE_BLOCK) { // Ensure we can see a good portion of the block caret newXY.xOffset += static_cast(vs.aveCharWidth); } } if (!(range.caret == range.anchor)) { if (ptAnchor.x < pt.x) { // Shift to left to show anchor or as much of range as possible int maxOffset = ptAnchor.x + xOffset - rcClient.left - 1; int minOffset = pt.x + xOffset - rcClient.right + 1; newXY.xOffset = std::min(newXY.xOffset, maxOffset); newXY.xOffset = std::max(newXY.xOffset, minOffset); } else { // Shift to right to show anchor or as much of range as possible int minOffset = ptAnchor.x + xOffset - rcClient.right + 1; int maxOffset = pt.x + xOffset - rcClient.left - 1; newXY.xOffset = std::max(newXY.xOffset, minOffset); newXY.xOffset = std::min(newXY.xOffset, maxOffset); } } if (newXY.xOffset < 0) { newXY.xOffset = 0; } } return newXY; } void Editor::SetXYScroll(XYScrollPosition newXY) { if ((newXY.topLine != topLine) || (newXY.xOffset != xOffset)) { if (newXY.topLine != topLine) { SetTopLine(newXY.topLine); SetVerticalScrollPos(); } if (newXY.xOffset != xOffset) { xOffset = newXY.xOffset; ContainerNeedsUpdate(SC_UPDATE_H_SCROLL); if (newXY.xOffset > 0) { PRectangle rcText = GetTextRectangle(); if (horizontalScrollBarVisible && rcText.Width() + xOffset > scrollWidth) { scrollWidth = xOffset + rcText.Width(); SetScrollBars(); } } SetHorizontalScrollPos(); } Redraw(); UpdateSystemCaret(); } } void Editor::ScrollRange(SelectionRange range) { SetXYScroll(XYScrollToMakeVisible(range, xysDefault)); } void Editor::EnsureCaretVisible(bool useMargin, bool vert, bool horiz) { SetXYScroll(XYScrollToMakeVisible(SelectionRange(posDrag.IsValid() ? posDrag : sel.RangeMain().caret), static_cast((useMargin?xysUseMargin:0)|(vert?xysVertical:0)|(horiz?xysHorizontal:0)))); } void Editor::ShowCaretAtCurrentPosition() { if (hasFocus) { caret.active = true; caret.on = true; SetTicking(true); } else { caret.active = false; caret.on = false; } InvalidateCaret(); } void Editor::DropCaret() { caret.active = false; InvalidateCaret(); } void Editor::InvalidateCaret() { if (posDrag.IsValid()) { InvalidateRange(posDrag.Position(), posDrag.Position() + 1); } else { for (size_t r=0; rlines; } return cs.SetHeight(lineToWrap, linesWrapped + (vs.annotationVisible ? pdoc->AnnotationLines(lineToWrap) : 0)); } // Perform wrapping for a subset of the lines needing wrapping. // wsAll: wrap all lines which need wrapping in this single call // wsVisible: wrap currently visible lines // wsIdle: wrap one page + 100 lines // Return true if wrapping occurred. bool Editor::WrapLines(enum wrapScope ws) { int goodTopLine = topLine; bool wrapOccurred = false; if (!Wrapping()) { if (wrapWidth != LineLayout::wrapWidthInfinite) { wrapWidth = LineLayout::wrapWidthInfinite; for (int lineDoc = 0; lineDoc < pdoc->LinesTotal(); lineDoc++) { cs.SetHeight(lineDoc, 1 + (vs.annotationVisible ? pdoc->AnnotationLines(lineDoc) : 0)); } wrapOccurred = true; } wrapPending.Reset(); } else if (wrapPending.NeedsWrap()) { wrapPending.start = std::min(wrapPending.start, pdoc->LinesTotal()); if (!SetIdle(true)) { // Idle processing not supported so full wrap required. ws = wsAll; } // Decide where to start wrapping int lineToWrap = wrapPending.start; int lineToWrapEnd = std::min(wrapPending.end, pdoc->LinesTotal()); const int lineDocTop = cs.DocFromDisplay(topLine); const int subLineTop = topLine - cs.DisplayFromDoc(lineDocTop); if (ws == wsVisible) { lineToWrap = Platform::Clamp(lineDocTop-5, wrapPending.start, pdoc->LinesTotal()); // Priority wrap to just after visible area. // Since wrapping could reduce display lines, treat each // as taking only one display line. lineToWrapEnd = lineDocTop; int lines = LinesOnScreen() + 1; while ((lineToWrapEnd < cs.LinesInDoc()) && (lines>0)) { if (cs.GetVisible(lineToWrapEnd)) lines--; lineToWrapEnd++; } // .. and if the paint window is outside pending wraps if ((lineToWrap > wrapPending.end) || (lineToWrapEnd < wrapPending.start)) { // Currently visible text does not need wrapping return false; } } else if (ws == wsIdle) { lineToWrapEnd = lineToWrap + LinesOnScreen() + 100; } const int lineEndNeedWrap = std::min(wrapPending.end, pdoc->LinesTotal()); lineToWrapEnd = std::min(lineToWrapEnd, lineEndNeedWrap); // Ensure all lines being wrapped are styled. pdoc->EnsureStyledTo(pdoc->LineStart(lineToWrapEnd)); if (lineToWrap < lineToWrapEnd) { PRectangle rcTextArea = GetClientRectangle(); rcTextArea.left = vs.textStart; rcTextArea.right -= vs.rightMarginWidth; wrapWidth = rcTextArea.Width(); RefreshStyleData(); AutoSurface surface(this); if (surface) { //Platform::DebugPrintf("Wraplines: scope=%0d need=%0d..%0d perform=%0d..%0d\n", ws, wrapPending.start, wrapPending.end, lineToWrap, lineToWrapEnd); while (lineToWrap < lineToWrapEnd) { if (WrapOneLine(surface, lineToWrap)) { wrapOccurred = true; } wrapPending.Wrapped(lineToWrap); lineToWrap++; } goodTopLine = cs.DisplayFromDoc(lineDocTop) + std::min(subLineTop, cs.GetHeight(lineDocTop)-1); } } // If wrapping is done, bring it to resting position if (wrapPending.start >= lineEndNeedWrap) { wrapPending.Reset(); } } if (wrapOccurred) { SetScrollBars(); SetTopLine(Platform::Clamp(goodTopLine, 0, MaxScrollPos())); SetVerticalScrollPos(); } return wrapOccurred; } void Editor::LinesJoin() { if (!RangeContainsProtected(targetStart, targetEnd)) { UndoGroup ug(pdoc); bool prevNonWS = true; for (int pos = targetStart; pos < targetEnd; pos++) { if (pdoc->IsPositionInLineEnd(pos)) { targetEnd -= pdoc->LenChar(pos); pdoc->DelChar(pos); if (prevNonWS) { // Ensure at least one space separating previous lines pdoc->InsertChar(pos, ' '); targetEnd++; } } else { prevNonWS = pdoc->CharAt(pos) != ' '; } } } } const char *Editor::StringFromEOLMode(int eolMode) { if (eolMode == SC_EOL_CRLF) { return "\r\n"; } else if (eolMode == SC_EOL_CR) { return "\r"; } else { return "\n"; } } void Editor::LinesSplit(int pixelWidth) { if (!RangeContainsProtected(targetStart, targetEnd)) { if (pixelWidth == 0) { PRectangle rcText = GetTextRectangle(); pixelWidth = rcText.Width(); } int lineStart = pdoc->LineFromPosition(targetStart); int lineEnd = pdoc->LineFromPosition(targetEnd); const char *eol = StringFromEOLMode(pdoc->eolMode); UndoGroup ug(pdoc); for (int line = lineStart; line <= lineEnd; line++) { AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(line)); if (surface && ll) { unsigned int posLineStart = pdoc->LineStart(line); LayoutLine(line, surface, vs, ll, pixelWidth); for (int subLine = 1; subLine < ll->lines; subLine++) { pdoc->InsertCString( static_cast(posLineStart + (subLine - 1) * strlen(eol) + ll->LineStart(subLine)), eol); targetEnd += static_cast(strlen(eol)); } } lineEnd = pdoc->LineFromPosition(targetEnd); } } } int Editor::SubstituteMarkerIfEmpty(int markerCheck, int markerDefault) const { if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) return markerDefault; return markerCheck; } bool ValidStyledText(ViewStyle &vs, size_t styleOffset, const StyledText &st) { if (st.multipleStyles) { for (size_t iStyle=0; iStyle(styles[endSegment+1]) == style)) endSegment++; width += surface->WidthText(vs.styles[style+styleOffset].font, text + start, static_cast(endSegment - start + 1)); start = endSegment + 1; } return width; } static int WidestLineWidth(Surface *surface, ViewStyle &vs, int styleOffset, const StyledText &st) { int widthMax = 0; size_t start = 0; while (start < st.length) { size_t lenLine = st.LineLength(start); int widthSubLine; if (st.multipleStyles) { widthSubLine = WidthStyledText(surface, vs, styleOffset, st.text + start, st.styles + start, lenLine); } else { widthSubLine = surface->WidthText(vs.styles[styleOffset + st.style].font, st.text + start, static_cast(lenLine)); } if (widthSubLine > widthMax) widthMax = widthSubLine; start += lenLine + 1; } return widthMax; } void DrawStyledText(Surface *surface, ViewStyle &vs, int styleOffset, PRectangle rcText, int ascent, const StyledText &st, size_t start, size_t length) { if (st.multipleStyles) { int x = rcText.left; size_t i = 0; while (i < length) { size_t end = i; int style = st.styles[i + start]; while (end < length-1 && st.styles[start+end+1] == style) end++; style += styleOffset; int width = surface->WidthText(vs.styles[style].font, st.text + start + i, static_cast(end - i + 1)); PRectangle rcSegment = rcText; rcSegment.left = x; rcSegment.right = x + width + 1; surface->DrawTextNoClip(rcSegment, vs.styles[style].font, ascent, st.text + start + i, static_cast(end - i + 1), vs.styles[style].fore, vs.styles[style].back); x += width; i = end + 1; } } else { size_t style = st.style + styleOffset; surface->DrawTextNoClip(rcText, vs.styles[style].font, rcText.top + vs.maxAscent, st.text + start, static_cast(length), vs.styles[style].fore, vs.styles[style].back); } } void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { if (vs.fixedColumnWidth == 0) return; AllocateGraphics(); RefreshStyleData(); RefreshPixMaps(surfWindow); PRectangle rcMargin = GetClientRectangle(); Point ptOrigin = GetVisibleOriginInMain(); rcMargin.Move(0, -ptOrigin.y); rcMargin.left = 0; rcMargin.right = vs.fixedColumnWidth; if (!rc.Intersects(rcMargin)) return; Surface *surface; if (bufferedDraw) { surface = pixmapSelMargin; } else { surface = surfWindow; } // Clip vertically to paint area to avoid drawing line numbers if (rcMargin.bottom > rc.bottom) rcMargin.bottom = rc.bottom; if (rcMargin.top < rc.top) rcMargin.top = rc.top; PRectangle rcSelMargin = rcMargin; rcSelMargin.right = rcMargin.left; if (rcSelMargin.bottom < rc.bottom) rcSelMargin.bottom = rc.bottom; for (int margin = 0; margin <= SC_MAX_MARGIN; margin++) { if (vs.ms[margin].width > 0) { rcSelMargin.left = rcSelMargin.right; rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; if (vs.ms[margin].style != SC_MARGIN_NUMBER) { if (vs.ms[margin].mask & SC_MASK_FOLDERS) { // Required because of special way brush is created for selection margin // Ensure patterns line up when scrolling with separate margin view // by choosing correctly aligned variant. bool invertPhase = static_cast(ptOrigin.y) & 1; surface->FillRectangle(rcSelMargin, invertPhase ? *pixmapSelPattern : *pixmapSelPatternOffset1); } else { ColourDesired colour; switch (vs.ms[margin].style) { case SC_MARGIN_BACK: colour = vs.styles[STYLE_DEFAULT].back; break; case SC_MARGIN_FORE: colour = vs.styles[STYLE_DEFAULT].fore; break; default: colour = vs.styles[STYLE_LINENUMBER].back; break; } surface->FillRectangle(rcSelMargin, colour); } } else { surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back); } const int lineStartPaint = (rcMargin.top + ptOrigin.y) / vs.lineHeight; int visibleLine = TopLineOfMain() + lineStartPaint; int yposScreen = lineStartPaint * vs.lineHeight - ptOrigin.y; // Work out whether the top line is whitespace located after a // lessening of fold level which implies a 'fold tail' but which should not // be displayed until the last of a sequence of whitespace. bool needWhiteClosure = false; if (vs.ms[margin].mask & SC_MASK_FOLDERS) { int level = pdoc->GetLevel(cs.DocFromDisplay(visibleLine)); if (level & SC_FOLDLEVELWHITEFLAG) { int lineBack = cs.DocFromDisplay(visibleLine); int levelPrev = level; while ((lineBack > 0) && (levelPrev & SC_FOLDLEVELWHITEFLAG)) { lineBack--; levelPrev = pdoc->GetLevel(lineBack); } if (!(levelPrev & SC_FOLDLEVELHEADERFLAG)) { if ((level & SC_FOLDLEVELNUMBERMASK) < (levelPrev & SC_FOLDLEVELNUMBERMASK)) needWhiteClosure = true; } } if (highlightDelimiter.isEnabled) { int lastLine = cs.DocFromDisplay(topLine + LinesOnScreen()) + 1; pdoc->GetHighlightDelimiters(highlightDelimiter, pdoc->LineFromPosition(CurrentPosition()), lastLine); } } // Old code does not know about new markers needed to distinguish all cases int folderOpenMid = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEROPENMID, SC_MARKNUM_FOLDEROPEN); int folderEnd = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEREND, SC_MARKNUM_FOLDER); while ((visibleLine < cs.LinesDisplayed()) && yposScreen < rc.bottom) { PLATFORM_ASSERT(visibleLine < cs.LinesDisplayed()); int lineDoc = cs.DocFromDisplay(visibleLine); PLATFORM_ASSERT(cs.GetVisible(lineDoc)); bool firstSubLine = visibleLine == cs.DisplayFromDoc(lineDoc); bool lastSubLine = visibleLine == (cs.DisplayFromDoc(lineDoc + 1) - 1); int marks = pdoc->GetMark(lineDoc); if (!firstSubLine) marks = 0; bool headWithTail = false; if (vs.ms[margin].mask & SC_MASK_FOLDERS) { // Decide which fold indicator should be displayed int level = pdoc->GetLevel(lineDoc); int levelNext = pdoc->GetLevel(lineDoc + 1); int levelNum = level & SC_FOLDLEVELNUMBERMASK; int levelNextNum = levelNext & SC_FOLDLEVELNUMBERMASK; if (level & SC_FOLDLEVELHEADERFLAG) { if (firstSubLine) { if (levelNum < levelNextNum) { if (cs.GetExpanded(lineDoc)) { if (levelNum == SC_FOLDLEVELBASE) marks |= 1 << SC_MARKNUM_FOLDEROPEN; else marks |= 1 << folderOpenMid; } else { if (levelNum == SC_FOLDLEVELBASE) marks |= 1 << SC_MARKNUM_FOLDER; else marks |= 1 << folderEnd; } } else if (levelNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } else { if (levelNum < levelNextNum) { if (cs.GetExpanded(lineDoc)) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } else if (levelNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } else if (levelNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } needWhiteClosure = false; int firstFollowupLine = cs.DocFromDisplay(cs.DisplayFromDoc(lineDoc + 1)); int firstFollowupLineLevel = pdoc->GetLevel(firstFollowupLine); int secondFollowupLineLevelNum = pdoc->GetLevel(firstFollowupLine + 1) & SC_FOLDLEVELNUMBERMASK; if (!cs.GetExpanded(lineDoc)) { if ((firstFollowupLineLevel & SC_FOLDLEVELWHITEFLAG) && (levelNum > secondFollowupLineLevelNum)) needWhiteClosure = true; if (highlightDelimiter.IsFoldBlockHighlighted(firstFollowupLine)) headWithTail = true; } } else if (level & SC_FOLDLEVELWHITEFLAG) { if (needWhiteClosure) { if (levelNext & SC_FOLDLEVELWHITEFLAG) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } else if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; needWhiteClosure = false; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; needWhiteClosure = false; } } else if (levelNum > SC_FOLDLEVELBASE) { if (levelNextNum < levelNum) { if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } } else if (levelNum > SC_FOLDLEVELBASE) { if (levelNextNum < levelNum) { needWhiteClosure = false; if (levelNext & SC_FOLDLEVELWHITEFLAG) { marks |= 1 << SC_MARKNUM_FOLDERSUB; needWhiteClosure = true; } else if (lastSubLine) { if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } } marks &= vs.ms[margin].mask; PRectangle rcMarker = rcSelMargin; rcMarker.top = yposScreen; rcMarker.bottom = yposScreen + vs.lineHeight; if (vs.ms[margin].style == SC_MARGIN_NUMBER) { if (firstSubLine) { char number[100] = ""; if (lineDoc >= 0) sprintf(number, "%d", lineDoc + 1); if (foldFlags & SC_FOLDFLAG_LEVELNUMBERS) { int lev = pdoc->GetLevel(lineDoc); sprintf(number, "%c%c %03X %03X", (lev & SC_FOLDLEVELHEADERFLAG) ? 'H' : '_', (lev & SC_FOLDLEVELWHITEFLAG) ? 'W' : '_', lev & SC_FOLDLEVELNUMBERMASK, lev >> 16 ); } PRectangle rcNumber = rcMarker; // Right justify XYPOSITION width = surface->WidthText(vs.styles[STYLE_LINENUMBER].font, number, istrlen(number)); XYPOSITION xpos = rcNumber.right - width - vs.marginNumberPadding; rcNumber.left = xpos; surface->DrawTextNoClip(rcNumber, vs.styles[STYLE_LINENUMBER].font, rcNumber.top + vs.maxAscent, number, istrlen(number), vs.styles[STYLE_LINENUMBER].fore, vs.styles[STYLE_LINENUMBER].back); } else if (vs.wrapVisualFlags & SC_WRAPVISUALFLAG_MARGIN) { PRectangle rcWrapMarker = rcMarker; rcWrapMarker.right -= 3; rcWrapMarker.left = rcWrapMarker.right - vs.styles[STYLE_LINENUMBER].aveCharWidth; DrawWrapMarker(surface, rcWrapMarker, false, vs.styles[STYLE_LINENUMBER].fore); } } else if (vs.ms[margin].style == SC_MARGIN_TEXT || vs.ms[margin].style == SC_MARGIN_RTEXT) { if (firstSubLine) { const StyledText stMargin = pdoc->MarginStyledText(lineDoc); if (stMargin.text && ValidStyledText(vs, vs.marginStyleOffset, stMargin)) { surface->FillRectangle(rcMarker, vs.styles[stMargin.StyleAt(0)+vs.marginStyleOffset].back); if (vs.ms[margin].style == SC_MARGIN_RTEXT) { int width = WidestLineWidth(surface, vs, vs.marginStyleOffset, stMargin); rcMarker.left = rcMarker.right - width - 3; } DrawStyledText(surface, vs, vs.marginStyleOffset, rcMarker, rcMarker.top + vs.maxAscent, stMargin, 0, stMargin.length); } } } if (marks) { for (int markBit = 0; (markBit < 32) && marks; markBit++) { if (marks & 1) { LineMarker::typeOfFold tFold = LineMarker::undefined; if ((vs.ms[margin].mask & SC_MASK_FOLDERS) && highlightDelimiter.IsFoldBlockHighlighted(lineDoc)) { if (highlightDelimiter.IsBodyOfFoldBlock(lineDoc)) { tFold = LineMarker::body; } else if (highlightDelimiter.IsHeadOfFoldBlock(lineDoc)) { if (firstSubLine) { tFold = headWithTail ? LineMarker::headWithTail : LineMarker::head; } else { if (cs.GetExpanded(lineDoc) || headWithTail) { tFold = LineMarker::body; } else { tFold = LineMarker::undefined; } } } else if (highlightDelimiter.IsTailOfFoldBlock(lineDoc)) { tFold = LineMarker::tail; } } vs.markers[markBit].Draw(surface, rcMarker, vs.styles[STYLE_LINENUMBER].font, tFold, vs.ms[margin].style); } marks >>= 1; } } visibleLine++; yposScreen += vs.lineHeight; } } } PRectangle rcBlankMargin = rcMargin; rcBlankMargin.left = rcSelMargin.right; surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back); if (bufferedDraw) { surfWindow->Copy(rcMargin, Point(rcMargin.left, rcMargin.top), *pixmapSelMargin); } } void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) { int ydiff = (rcTab.bottom - rcTab.top) / 2; int xhead = rcTab.right - 1 - ydiff; if (xhead <= rcTab.left) { ydiff -= rcTab.left - xhead - 1; xhead = rcTab.left - 1; } if ((rcTab.left + 2) < (rcTab.right - 1)) surface->MoveTo(rcTab.left + 2, ymid); else surface->MoveTo(rcTab.right - 1, ymid); surface->LineTo(rcTab.right - 1, ymid); surface->LineTo(xhead, ymid - ydiff); surface->MoveTo(rcTab.right - 1, ymid); surface->LineTo(xhead, ymid + ydiff); } LineLayout *Editor::RetrieveLineLayout(int lineNumber) { int posLineStart = pdoc->LineStart(lineNumber); int posLineEnd = pdoc->LineStart(lineNumber + 1); PLATFORM_ASSERT(posLineEnd >= posLineStart); int lineCaret = pdoc->LineFromPosition(sel.MainCaret()); return llc.Retrieve(lineNumber, lineCaret, posLineEnd - posLineStart, pdoc->GetStyleClock(), LinesOnScreen() + 1, pdoc->LinesTotal()); } /** * Fill in the LineLayout data for the given line. * Copy the given @a line and its styles from the document into local arrays. * Also determine the x position at which each character starts. */ void Editor::LayoutLine(int line, Surface *surface, ViewStyle &vstyle, LineLayout *ll, int width) { //void LayoutLine(int line, Surface *surface, ViewStyle &vstyle, LineLayout *ll, int width, Document *pdoc, PositionCache &posCache, SpecialRepresentations &reprs) { if (!ll) return; PLATFORM_ASSERT(line < pdoc->LinesTotal()); PLATFORM_ASSERT(ll->chars != NULL); int posLineStart = pdoc->LineStart(line); int posLineEnd = pdoc->LineStart(line + 1); // If the line is very long, limit the treatment to a length that should fit in the viewport if (posLineEnd > (posLineStart + ll->maxLineLength)) { posLineEnd = posLineStart + ll->maxLineLength; } if (ll->validity == LineLayout::llCheckTextAndStyle) { int lineLength = posLineEnd - posLineStart; if (!vstyle.viewEOL) { lineLength = pdoc->LineEnd(line) - posLineStart; } if (lineLength == ll->numCharsInLine) { // See if chars, styles, indicators, are all the same bool allSame = true; const int styleMask = pdoc->stylingBitsMask; // Check base line layout char styleByte = 0; int numCharsInLine = 0; while (numCharsInLine < lineLength) { int charInDoc = numCharsInLine + posLineStart; char chDoc = pdoc->CharAt(charInDoc); styleByte = pdoc->StyleAt(charInDoc); allSame = allSame && (ll->styles[numCharsInLine] == static_cast(styleByte & styleMask)); allSame = allSame && (ll->indicators[numCharsInLine] == static_cast(styleByte & ~styleMask)); if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseMixed) allSame = allSame && (ll->chars[numCharsInLine] == chDoc); else if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseLower) allSame = allSame && (ll->chars[numCharsInLine] == static_cast(tolower(chDoc))); else // Style::caseUpper allSame = allSame && (ll->chars[numCharsInLine] == static_cast(toupper(chDoc))); numCharsInLine++; } allSame = allSame && (ll->styles[numCharsInLine] == styleByte); // For eolFilled if (allSame) { ll->validity = LineLayout::llPositions; } else { ll->validity = LineLayout::llInvalid; } } else { ll->validity = LineLayout::llInvalid; } } if (ll->validity == LineLayout::llInvalid) { ll->widthLine = LineLayout::wrapWidthInfinite; ll->lines = 1; if (vstyle.edgeState == EDGE_BACKGROUND) { ll->edgeColumn = pdoc->FindColumn(line, vstyle.theEdge); if (ll->edgeColumn >= posLineStart) { ll->edgeColumn -= posLineStart; } } else { ll->edgeColumn = -1; } char styleByte; const int styleMask = pdoc->stylingBitsMask; ll->styleBitsSet = 0; // Fill base line layout const int lineLength = posLineEnd - posLineStart; pdoc->GetCharRange(ll->chars, posLineStart, lineLength); pdoc->GetStyleRange(ll->styles, posLineStart, lineLength); int numCharsBeforeEOL = pdoc->LineEnd(line) - posLineStart; const int numCharsInLine = (vstyle.viewEOL) ? lineLength : numCharsBeforeEOL; for (int styleInLine = 0; styleInLine < numCharsInLine; styleInLine++) { styleByte = ll->styles[styleInLine]; ll->styleBitsSet |= styleByte; ll->styles[styleInLine] = static_cast(styleByte & styleMask); ll->indicators[styleInLine] = static_cast(styleByte & ~styleMask); } styleByte = static_cast(((lineLength > 0) ? ll->styles[lineLength-1] : 0) & styleMask); if (vstyle.someStylesForceCase) { for (int charInLine = 0; charInLinechars[charInLine]; if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseUpper) ll->chars[charInLine] = static_cast(toupper(chDoc)); else if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseLower) ll->chars[charInLine] = static_cast(tolower(chDoc)); } } ll->xHighlightGuide = 0; // Extra element at the end of the line to hold end x position and act as ll->chars[numCharsInLine] = 0; // Also triggers processing in the loops as this is a control character ll->styles[numCharsInLine] = styleByte; // For eolFilled ll->indicators[numCharsInLine] = 0; // Layout the line, determining the position of each character, // with an extra element at the end for the end of the line. ll->positions[0] = 0; bool lastSegItalics = false; BreakFinder bfLayout(ll, 0, numCharsInLine, posLineStart, 0, false, pdoc, &reprs); while (bfLayout.More()) { const TextSegment ts = bfLayout.Next(); std::fill(&ll->positions[ts.start+1], &ll->positions[ts.end()+1], 0.0f); if (vstyle.styles[ll->styles[ts.start]].visible) { if (ts.representation) { XYPOSITION representationWidth = vstyle.controlCharWidth; if (ll->chars[ts.start] == '\t') { // Tab is a special case of representation, taking a variable amount of space representationWidth = ((static_cast((ll->positions[ts.start] + 2) / vstyle.tabWidth) + 1) * vstyle.tabWidth) - ll->positions[ts.start]; } else { if (representationWidth <= 0.0) { XYPOSITION positionsRepr[256]; // Should expand when needed posCache.MeasureWidths(surface, vstyle, STYLE_CONTROLCHAR, ts.representation->stringRep.c_str(), static_cast(ts.representation->stringRep.length()), positionsRepr, pdoc); representationWidth = positionsRepr[ts.representation->stringRep.length()-1] + vstyle.ctrlCharPadding; } } for (int ii=0; ii < ts.length; ii++) ll->positions[ts.start + 1 + ii] = representationWidth; } else { if ((ts.length == 1) && (' ' == ll->chars[ts.start])) { // Over half the segments are single characters and of these about half are space characters. ll->positions[ts.start + 1] = vstyle.styles[ll->styles[ts.start]].spaceWidth; } else { posCache.MeasureWidths(surface, vstyle, ll->styles[ts.start], ll->chars + ts.start, ts.length, ll->positions + ts.start + 1, pdoc); } } lastSegItalics = (!ts.representation) && ((ll->chars[ts.end()-1] != ' ') && vstyle.styles[ll->styles[ts.start]].italic); } for (int posToIncrease = ts.start+1; posToIncrease <= ts.end(); posToIncrease++) { ll->positions[posToIncrease] += ll->positions[ts.start]; } } // Small hack to make lines that end with italics not cut off the edge of the last character if (lastSegItalics) { ll->positions[numCharsInLine] += vstyle.lastSegItalicsOffset; } ll->numCharsInLine = numCharsInLine; ll->numCharsBeforeEOL = numCharsBeforeEOL; ll->validity = LineLayout::llPositions; } // Hard to cope when too narrow, so just assume there is space if (width < 20) { width = 20; } if ((ll->validity == LineLayout::llPositions) || (ll->widthLine != width)) { ll->widthLine = width; if (width == LineLayout::wrapWidthInfinite) { ll->lines = 1; } else if (width > ll->positions[ll->numCharsInLine]) { // Simple common case where line does not need wrapping. ll->lines = 1; } else { if (vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { width -= static_cast(vstyle.aveCharWidth); // take into account the space for end wrap mark } XYPOSITION wrapAddIndent = 0; // This will be added to initial indent of line if (vstyle.wrapIndentMode == SC_WRAPINDENT_INDENT) { wrapAddIndent = pdoc->IndentSize() * vstyle.spaceWidth; } else if (vstyle.wrapIndentMode == SC_WRAPINDENT_FIXED) { wrapAddIndent = vstyle.wrapVisualStartIndent * vstyle.aveCharWidth; } ll->wrapIndent = wrapAddIndent; if (vstyle.wrapIndentMode != SC_WRAPINDENT_FIXED) for (int i = 0; i < ll->numCharsInLine; i++) { if (!IsSpaceOrTab(ll->chars[i])) { ll->wrapIndent += ll->positions[i]; // Add line indent break; } } // Check for text width minimum if (ll->wrapIndent > width - static_cast(vstyle.aveCharWidth) * 15) ll->wrapIndent = wrapAddIndent; // Check for wrapIndent minimum if ((vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_START) && (ll->wrapIndent < vstyle.aveCharWidth)) ll->wrapIndent = vstyle.aveCharWidth; // Indent to show start visual ll->lines = 0; // Calculate line start positions based upon width. int lastGoodBreak = 0; int lastLineStart = 0; XYACCUMULATOR startOffset = 0; int p = 0; while (p < ll->numCharsInLine) { if ((ll->positions[p + 1] - startOffset) >= width) { if (lastGoodBreak == lastLineStart) { // Try moving to start of last character if (p > 0) { lastGoodBreak = pdoc->MovePositionOutsideChar(p + posLineStart, -1) - posLineStart; } if (lastGoodBreak == lastLineStart) { // Ensure at least one character on line. lastGoodBreak = pdoc->MovePositionOutsideChar(lastGoodBreak + posLineStart + 1, 1) - posLineStart; } } lastLineStart = lastGoodBreak; ll->lines++; ll->SetLineStart(ll->lines, lastGoodBreak); startOffset = ll->positions[lastGoodBreak]; // take into account the space for start wrap mark and indent startOffset -= ll->wrapIndent; p = lastGoodBreak + 1; continue; } if (p > 0) { if (vstyle.wrapState == eWrapChar) { lastGoodBreak = pdoc->MovePositionOutsideChar(p + posLineStart, -1) - posLineStart; p = pdoc->MovePositionOutsideChar(p + 1 + posLineStart, 1) - posLineStart; continue; } else if (ll->styles[p] != ll->styles[p - 1]) { lastGoodBreak = p; } else if (IsSpaceOrTab(ll->chars[p - 1]) && !IsSpaceOrTab(ll->chars[p])) { lastGoodBreak = p; } } p++; } ll->lines++; } ll->validity = LineLayout::llLines; } } ColourDesired Editor::SelectionBackground(ViewStyle &vsDraw, bool main) const { return main ? (primarySelection ? vsDraw.selColours.back : vsDraw.selBackground2) : vsDraw.selAdditionalBackground; } ColourDesired Editor::TextBackground(ViewStyle &vsDraw, bool overrideBackground, ColourDesired background, int inSelection, bool inHotspot, int styleMain, int i, LineLayout *ll) const { if (inSelection == 1) { if (vsDraw.selColours.back.isSet && (vsDraw.selAlpha == SC_ALPHA_NOALPHA)) { return SelectionBackground(vsDraw, true); } } else if (inSelection == 2) { if (vsDraw.selColours.back.isSet && (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA)) { return SelectionBackground(vsDraw, false); } } else { if ((vsDraw.edgeState == EDGE_BACKGROUND) && (i >= ll->edgeColumn) && (i < ll->numCharsBeforeEOL)) return vsDraw.edgecolour; if (inHotspot && vsDraw.hotspotColours.back.isSet) return vsDraw.hotspotColours.back; } if (overrideBackground && (styleMain != STYLE_BRACELIGHT) && (styleMain != STYLE_BRACEBAD)) { return background; } else { return vsDraw.styles[styleMain].back; } } void Editor::DrawIndentGuide(Surface *surface, int lineVisible, int lineHeight, int start, PRectangle rcSegment, bool highlight) { Point from(0, ((lineVisible & 1) && (lineHeight & 1)) ? 1 : 0); PRectangle rcCopyArea(start + 1, rcSegment.top, start + 2, rcSegment.bottom); surface->Copy(rcCopyArea, from, highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide); } void Editor::DrawWrapMarker(Surface *surface, PRectangle rcPlace, bool isEndMarker, ColourDesired wrapColour) { surface->PenColour(wrapColour); enum { xa = 1 }; // gap before start int w = rcPlace.right - rcPlace.left - xa - 1; bool xStraight = isEndMarker; // x-mirrored symbol for start marker int x0 = xStraight ? rcPlace.left : rcPlace.right - 1; int y0 = rcPlace.top; int dy = (rcPlace.bottom - rcPlace.top) / 5; int y = (rcPlace.bottom - rcPlace.top) / 2 + dy; struct Relative { Surface *surface; int xBase; int xDir; int yBase; int yDir; void MoveTo(int xRelative, int yRelative) { surface->MoveTo(xBase + xDir * xRelative, yBase + yDir * yRelative); } void LineTo(int xRelative, int yRelative) { surface->LineTo(xBase + xDir * xRelative, yBase + yDir * yRelative); } }; Relative rel = {surface, x0, xStraight ? 1 : -1, y0, 1}; // arrow head rel.MoveTo(xa, y); rel.LineTo(xa + 2*w / 3, y - dy); rel.MoveTo(xa, y); rel.LineTo(xa + 2*w / 3, y + dy); // arrow body rel.MoveTo(xa, y); rel.LineTo(xa + w, y); rel.LineTo(xa + w, y - 2 * dy); rel.LineTo(xa - 1, // on windows lineto is exclusive endpoint, perhaps GTK not... y - 2 * dy); } static void SimpleAlphaRectangle(Surface *surface, PRectangle rc, ColourDesired fill, int alpha) { if (alpha != SC_ALPHA_NOALPHA) { surface->AlphaRectangle(rc, 0, fill, alpha, fill, alpha, 0); } } void DrawTextBlob(Surface *surface, ViewStyle &vsDraw, PRectangle rcSegment, const char *s, ColourDesired textBack, ColourDesired textFore, bool twoPhaseDraw) { if (!twoPhaseDraw) { surface->FillRectangle(rcSegment, textBack); } Font &ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; int normalCharHeight = surface->Ascent(ctrlCharsFont) - surface->InternalLeading(ctrlCharsFont); PRectangle rcCChar = rcSegment; rcCChar.left = rcCChar.left + 1; rcCChar.top = rcSegment.top + vsDraw.maxAscent - normalCharHeight; rcCChar.bottom = rcSegment.top + vsDraw.maxAscent + 1; PRectangle rcCentral = rcCChar; rcCentral.top++; rcCentral.bottom--; surface->FillRectangle(rcCentral, textFore); PRectangle rcChar = rcCChar; rcChar.left++; rcChar.right--; surface->DrawTextClipped(rcChar, ctrlCharsFont, rcSegment.top + vsDraw.maxAscent, s, istrlen(s), textBack, textFore); } void Editor::DrawEOL(Surface *surface, ViewStyle &vsDraw, PRectangle rcLine, LineLayout *ll, int line, int lineEnd, int xStart, int subLine, XYACCUMULATOR subLineStart, bool overrideBackground, ColourDesired background, bool drawWrapMarkEnd, ColourDesired wrapColour) { const int posLineStart = pdoc->LineStart(line); const int styleMask = pdoc->stylingBitsMask; PRectangle rcSegment = rcLine; const bool lastSubLine = subLine == (ll->lines - 1); XYPOSITION virtualSpace = 0; if (lastSubLine) { const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; virtualSpace = sel.VirtualSpaceFor(pdoc->LineEnd(line)) * spaceWidth; } XYPOSITION xEol = ll->positions[lineEnd] - subLineStart; // Fill the virtual space and show selections within it if (virtualSpace) { rcSegment.left = xEol + xStart; rcSegment.right = xEol + xStart + virtualSpace; surface->FillRectangle(rcSegment, overrideBackground ? background : vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back); if (!hideSelection && ((vsDraw.selAlpha == SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA))) { SelectionSegment virtualSpaceRange(SelectionPosition(pdoc->LineEnd(line)), SelectionPosition(pdoc->LineEnd(line), sel.VirtualSpaceFor(pdoc->LineEnd(line)))); for (size_t r=0; rEndLineStyle()].spaceWidth; rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - subLineStart + portion.start.VirtualSpace() * spaceWidth; rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - subLineStart + portion.end.VirtualSpace() * spaceWidth; rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, r == sel.Main())); } } } } } int eolInSelection = 0; int alpha = SC_ALPHA_NOALPHA; if (!hideSelection) { int posAfterLineEnd = pdoc->LineStart(line + 1); eolInSelection = (subLine == (ll->lines - 1)) ? sel.InSelectionForEOL(posAfterLineEnd) : 0; alpha = (eolInSelection == 1) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; } // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on XYPOSITION blobsWidth = 0; if (lastSubLine) { for (int eolPos=ll->numCharsBeforeEOL; eolPosnumCharsInLine; eolPos++) { rcSegment.left = xStart + ll->positions[eolPos] - subLineStart + virtualSpace; rcSegment.right = xStart + ll->positions[eolPos+1] - subLineStart + virtualSpace; blobsWidth += rcSegment.Width(); char hexits[4]; const char *ctrlChar; unsigned char chEOL = ll->chars[eolPos]; if (UTF8IsAscii(chEOL)) { ctrlChar = ControlCharacterString(chEOL); } else { sprintf(hexits, "x%2X", chEOL); ctrlChar = hexits; } int styleMain = ll->styles[eolPos]; ColourDesired textBack = TextBackground(vsDraw, overrideBackground, background, eolInSelection, false, styleMain, eolPos, ll); ColourDesired textFore = vsDraw.styles[styleMain].fore; if (eolInSelection && vsDraw.selColours.fore.isSet) { textFore = (eolInSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; } if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1)) { if (alpha == SC_ALPHA_NOALPHA) { surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1)); } else { surface->FillRectangle(rcSegment, textBack); } } else { surface->FillRectangle(rcSegment, textBack); } DrawTextBlob(surface, vsDraw, rcSegment, ctrlChar, textBack, textFore, twoPhaseDraw); if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1), alpha); } } } // Draw the eol-is-selected rectangle rcSegment.left = xEol + xStart + virtualSpace + blobsWidth; rcSegment.right = rcSegment.left + vsDraw.aveCharWidth; if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1)); } else { if (overrideBackground) { surface->FillRectangle(rcSegment, background); } else if (line < pdoc->LinesTotal() - 1) { surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back); } else if (vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].eolFilled) { surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back); } else { surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back); } if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1), alpha); } } // Fill the remainder of the line rcSegment.left = rcSegment.right; if (rcSegment.left < rcLine.left) rcSegment.left = rcLine.left; rcSegment.right = rcLine.right; if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1)); } else { if (overrideBackground) { surface->FillRectangle(rcSegment, background); } else if (vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].eolFilled) { surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back); } else { surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back); } if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1), alpha); } } if (drawWrapMarkEnd) { PRectangle rcPlace = rcSegment; if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_END_BY_TEXT) { rcPlace.left = xEol + xStart + virtualSpace; rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; } else { // rcLine is clipped to text area rcPlace.right = rcLine.right; rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; } DrawWrapMarker(surface, rcPlace, true, wrapColour); } } void Editor::DrawIndicator(int indicNum, int startPos, int endPos, Surface *surface, ViewStyle &vsDraw, int xStart, PRectangle rcLine, LineLayout *ll, int subLine) { const XYPOSITION subLineStart = ll->positions[ll->LineStart(subLine)]; PRectangle rcIndic( ll->positions[startPos] + xStart - subLineStart, rcLine.top + vsDraw.maxAscent, ll->positions[endPos] + xStart - subLineStart, rcLine.top + vsDraw.maxAscent + 3); vsDraw.indicators[indicNum].Draw(surface, rcIndic, rcLine); } void Editor::DrawIndicators(Surface *surface, ViewStyle &vsDraw, int line, int xStart, PRectangle rcLine, LineLayout *ll, int subLine, int lineEnd, bool under) { // Draw decorators const int posLineStart = pdoc->LineStart(line); const int lineStart = ll->LineStart(subLine); const int posLineEnd = posLineStart + lineEnd; if (!under) { // Draw indicators // foreach indicator... for (int indicnum = 0, mask = 1 << pdoc->stylingBits; mask < 0x100; indicnum++) { if (!(mask & ll->styleBitsSet)) { mask <<= 1; continue; } int startPos = -1; // foreach style pos in line... for (int indicPos = lineStart; indicPos <= lineEnd; indicPos++) { // look for starts... if (startPos < 0) { // NOT in indicator run, looking for START if (indicPos < lineEnd && (ll->indicators[indicPos] & mask)) startPos = indicPos; } // ... or ends if (startPos >= 0) { // IN indicator run, looking for END if (indicPos >= lineEnd || !(ll->indicators[indicPos] & mask)) { // AT end of indicator run, DRAW it! DrawIndicator(indicnum, startPos, indicPos, surface, vsDraw, xStart, rcLine, ll, subLine); // RESET control var startPos = -1; } } } mask <<= 1; } } for (Decoration *deco = pdoc->decorations.root; deco; deco = deco->next) { if (under == vsDraw.indicators[deco->indicator].under) { int startPos = posLineStart + lineStart; if (!deco->rs.ValueAt(startPos)) { startPos = deco->rs.EndRun(startPos); } while ((startPos < posLineEnd) && (deco->rs.ValueAt(startPos))) { int endPos = deco->rs.EndRun(startPos); if (endPos > posLineEnd) endPos = posLineEnd; DrawIndicator(deco->indicator, startPos - posLineStart, endPos - posLineStart, surface, vsDraw, xStart, rcLine, ll, subLine); startPos = deco->rs.EndRun(endPos); } } } // Use indicators to highlight matching braces if ((vs.braceHighlightIndicatorSet && (bracesMatchStyle == STYLE_BRACELIGHT)) || (vs.braceBadLightIndicatorSet && (bracesMatchStyle == STYLE_BRACEBAD))) { int braceIndicator = (bracesMatchStyle == STYLE_BRACELIGHT) ? vs.braceHighlightIndicator : vs.braceBadLightIndicator; if (under == vsDraw.indicators[braceIndicator].under) { Range rangeLine(posLineStart + lineStart, posLineEnd); if (rangeLine.ContainsCharacter(braces[0])) { int braceOffset = braces[0] - posLineStart; if (braceOffset < ll->numCharsInLine) { DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, xStart, rcLine, ll, subLine); } } if (rangeLine.ContainsCharacter(braces[1])) { int braceOffset = braces[1] - posLineStart; if (braceOffset < ll->numCharsInLine) { DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, xStart, rcLine, ll, subLine); } } } } } void Editor::DrawAnnotation(Surface *surface, ViewStyle &vsDraw, int line, int xStart, PRectangle rcLine, LineLayout *ll, int subLine) { int indent = pdoc->GetLineIndentation(line) * vsDraw.spaceWidth; PRectangle rcSegment = rcLine; int annotationLine = subLine - ll->lines; const StyledText stAnnotation = pdoc->AnnotationStyledText(line); if (stAnnotation.text && ValidStyledText(vsDraw, vsDraw.annotationStyleOffset, stAnnotation)) { surface->FillRectangle(rcSegment, vsDraw.styles[0].back); if (vs.annotationVisible == ANNOTATION_BOXED) { // Only care about calculating width if need to draw box int widthAnnotation = WidestLineWidth(surface, vsDraw, vsDraw.annotationStyleOffset, stAnnotation); widthAnnotation += vsDraw.spaceWidth * 2; // Margins rcSegment.left = xStart + indent; rcSegment.right = rcSegment.left + widthAnnotation; } else { rcSegment.left = xStart; } const int annotationLines = pdoc->AnnotationLines(line); size_t start = 0; size_t lengthAnnotation = stAnnotation.LineLength(start); int lineInAnnotation = 0; while ((lineInAnnotation < annotationLine) && (start < stAnnotation.length)) { start += lengthAnnotation + 1; lengthAnnotation = stAnnotation.LineLength(start); lineInAnnotation++; } PRectangle rcText = rcSegment; if (vs.annotationVisible == ANNOTATION_BOXED) { surface->FillRectangle(rcText, vsDraw.styles[stAnnotation.StyleAt(start) + vsDraw.annotationStyleOffset].back); rcText.left += vsDraw.spaceWidth; } DrawStyledText(surface, vsDraw, vsDraw.annotationStyleOffset, rcText, rcText.top + vsDraw.maxAscent, stAnnotation, start, lengthAnnotation); if (vs.annotationVisible == ANNOTATION_BOXED) { surface->PenColour(vsDraw.styles[vsDraw.annotationStyleOffset].fore); surface->MoveTo(rcSegment.left, rcSegment.top); surface->LineTo(rcSegment.left, rcSegment.bottom); surface->MoveTo(rcSegment.right, rcSegment.top); surface->LineTo(rcSegment.right, rcSegment.bottom); if (subLine == ll->lines) { surface->MoveTo(rcSegment.left, rcSegment.top); surface->LineTo(rcSegment.right, rcSegment.top); } if (subLine == ll->lines+annotationLines-1) { surface->MoveTo(rcSegment.left, rcSegment.bottom - 1); surface->LineTo(rcSegment.right, rcSegment.bottom - 1); } } } } void Editor::DrawLine(Surface *surface, ViewStyle &vsDraw, int line, int lineVisible, int xStart, PRectangle rcLine, LineLayout *ll, int subLine) { if (subLine >= ll->lines) { DrawAnnotation(surface, vsDraw, line, xStart, rcLine, ll, subLine); return; // No further drawing } PRectangle rcSegment = rcLine; // Using one font for all control characters so it can be controlled independently to ensure // the box goes around the characters tightly. Seems to be no way to work out what height // is taken by an individual character - internal leading gives varying results. Font &ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; // See if something overrides the line background color: Either if caret is on the line // and background color is set for that, or if a marker is defined that forces its background // color onto the line, or if a marker is defined but has no selection margin in which to // display itself (as long as it's not an SC_MARK_EMPTY marker). These are checked in order // with the earlier taking precedence. When multiple markers cause background override, // the color for the highest numbered one is used. bool overrideBackground = false; ColourDesired background; if ((caret.active || vsDraw.alwaysShowCaretLineBackground) && vsDraw.showCaretLineBackground && (vsDraw.caretLineAlpha == SC_ALPHA_NOALPHA) && ll->containsCaret) { overrideBackground = true; background = vsDraw.caretLineBackground; } if (!overrideBackground) { int marks = pdoc->GetMark(line); for (int markBit = 0; (markBit < 32) && marks; markBit++) { if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_BACKGROUND) && (vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { background = vsDraw.markers[markBit].back; overrideBackground = true; } marks >>= 1; } } if (!overrideBackground) { if (vsDraw.maskInLine) { int marksMasked = pdoc->GetMark(line) & vsDraw.maskInLine; if (marksMasked) { for (int markBit = 0; (markBit < 32) && marksMasked; markBit++) { if ((marksMasked & 1) && (vsDraw.markers[markBit].markType != SC_MARK_EMPTY) && (vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { overrideBackground = true; background = vsDraw.markers[markBit].back; } marksMasked >>= 1; } } } } const bool drawWhitespaceBackground = (vsDraw.viewWhitespace != wsInvisible) && (!overrideBackground) && (vsDraw.whitespaceColours.back.isSet); bool inIndentation = subLine == 0; // Do not handle indentation except on first subline. const XYPOSITION indentWidth = pdoc->IndentSize() * vsDraw.spaceWidth; const XYPOSITION epsilon = 0.0001f; // A small nudge to avoid floating point precision issues const int posLineStart = pdoc->LineStart(line); const int startseg = ll->LineStart(subLine); const XYACCUMULATOR subLineStart = ll->positions[startseg]; int lineStart = 0; int lineEnd = 0; if (subLine < ll->lines) { lineStart = ll->LineStart(subLine); lineEnd = ll->LineStart(subLine + 1); if (subLine == ll->lines - 1) { lineEnd = ll->numCharsBeforeEOL; } } const ColourDesired wrapColour = vsDraw.WrapColour(); bool drawWrapMarkEnd = false; if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { if (subLine + 1 < ll->lines) { drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0; } } if (ll->wrapIndent != 0) { bool continuedWrapLine = false; if (subLine < ll->lines) { continuedWrapLine = ll->LineStart(subLine) != 0; } if (continuedWrapLine) { // draw continuation rect PRectangle rcPlace = rcSegment; rcPlace.left = ll->positions[startseg] + xStart - subLineStart; rcPlace.right = rcPlace.left + ll->wrapIndent; // default bgnd here.. surface->FillRectangle(rcSegment, overrideBackground ? background : vsDraw.styles[STYLE_DEFAULT].back); // main line style would be below but this would be inconsistent with end markers // also would possibly not be the style at wrap point //int styleMain = ll->styles[lineStart]; //surface->FillRectangle(rcPlace, vsDraw.styles[styleMain].back); if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_START) { if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_START_BY_TEXT) rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; else rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; DrawWrapMarker(surface, rcPlace, false, wrapColour); } xStart += static_cast(ll->wrapIndent); } } const bool selBackDrawn = vsDraw.selColours.back.isSet && ((vsDraw.selAlpha == SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA)); // Does not take margin into account but not significant const int xStartVisible = static_cast(subLineStart) - xStart; ll->psel = &sel; if (twoPhaseDraw) { BreakFinder bfBack(ll, lineStart, lineEnd, posLineStart, xStartVisible, selBackDrawn, pdoc, &reprs); // Background drawing loop while (bfBack.More()) { const TextSegment ts = bfBack.Next(); const int i = ts.end() - 1; const int iDoc = i + posLineStart; rcSegment.left = ll->positions[ts.start] + xStart - subLineStart; rcSegment.right = ll->positions[ts.end()] + xStart - subLineStart; // Only try to draw if really visible - enhances performance by not calling environment to // draw strings that are completely past the right side of the window. if (rcSegment.Intersects(rcLine)) { // Clip to line rectangle, since may have a huge position which will not work with some platforms if (rcSegment.left < rcLine.left) rcSegment.left = rcLine.left; if (rcSegment.right > rcLine.right) rcSegment.right = rcLine.right; const int inSelection = hideSelection ? 0 : sel.CharacterInSelection(iDoc); const bool inHotspot = (ll->hsStart != -1) && (iDoc >= ll->hsStart) && (iDoc < ll->hsEnd); ColourDesired textBack = TextBackground(vsDraw, overrideBackground, background, inSelection, inHotspot, ll->styles[i], i, ll); if (ts.representation) { if (ll->chars[i] == '\t') { // Tab display if (drawWhitespaceBackground && (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) textBack = vsDraw.whitespaceColours.back; } else { // Blob display inIndentation = false; } surface->FillRectangle(rcSegment, textBack); } else { // Normal text display surface->FillRectangle(rcSegment, textBack); if (vsDraw.viewWhitespace != wsInvisible || (inIndentation && vsDraw.viewIndentationGuides == ivReal)) { for (int cpos = 0; cpos <= i - ts.start; cpos++) { if (ll->chars[cpos + ts.start] == ' ') { if (drawWhitespaceBackground && (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { PRectangle rcSpace(ll->positions[cpos + ts.start] + xStart - subLineStart, rcSegment.top, ll->positions[cpos + ts.start + 1] + xStart - subLineStart, rcSegment.bottom); surface->FillRectangle(rcSpace, vsDraw.whitespaceColours.back); } } else { inIndentation = false; } } } } } else if (rcSegment.left > rcLine.right) { break; } } DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, xStart, subLine, subLineStart, overrideBackground, background, drawWrapMarkEnd, wrapColour); } DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineEnd, true); if (vsDraw.edgeState == EDGE_LINE) { int edgeX = vsDraw.theEdge * vsDraw.spaceWidth; rcSegment.left = edgeX + xStart; if ((ll->wrapIndent != 0) && (lineStart != 0)) rcSegment.left -= ll->wrapIndent; rcSegment.right = rcSegment.left + 1; surface->FillRectangle(rcSegment, vsDraw.edgecolour); } // Draw underline mark as part of background if not transparent int marks = pdoc->GetMark(line); int markBit; for (markBit = 0; (markBit < 32) && marks; markBit++) { if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_UNDERLINE) && (vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { PRectangle rcUnderline = rcLine; rcUnderline.top = rcUnderline.bottom - 2; surface->FillRectangle(rcUnderline, vsDraw.markers[markBit].back); } marks >>= 1; } inIndentation = subLine == 0; // Do not handle indentation except on first subline. // Foreground drawing loop BreakFinder bfFore(ll, lineStart, lineEnd, posLineStart, xStartVisible, ((!twoPhaseDraw && selBackDrawn) || vsDraw.selColours.fore.isSet), pdoc, &reprs); while (bfFore.More()) { const TextSegment ts = bfFore.Next(); const int i = ts.end() - 1; const int iDoc = i + posLineStart; rcSegment.left = ll->positions[ts.start] + xStart - subLineStart; rcSegment.right = ll->positions[ts.end()] + xStart - subLineStart; // Only try to draw if really visible - enhances performance by not calling environment to // draw strings that are completely past the right side of the window. if (rcSegment.Intersects(rcLine)) { int styleMain = ll->styles[i]; ColourDesired textFore = vsDraw.styles[styleMain].fore; Font &textFont = vsDraw.styles[styleMain].font; //hotspot foreground if (ll->hsStart != -1 && iDoc >= ll->hsStart && iDoc < hsEnd) { if (vsDraw.hotspotColours.fore.isSet) textFore = vsDraw.hotspotColours.fore; } const int inSelection = hideSelection ? 0 : sel.CharacterInSelection(iDoc); if (inSelection && (vsDraw.selColours.fore.isSet)) { textFore = (inSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; } const bool inHotspot = (ll->hsStart != -1) && (iDoc >= ll->hsStart) && (iDoc < ll->hsEnd); ColourDesired textBack = TextBackground(vsDraw, overrideBackground, background, inSelection, inHotspot, styleMain, i, ll); if (ts.representation) { if (ll->chars[i] == '\t') { // Tab display if (!twoPhaseDraw) { if (drawWhitespaceBackground && (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) textBack = vsDraw.whitespaceColours.back; surface->FillRectangle(rcSegment, textBack); } if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { for (int indentCount = (ll->positions[i] + epsilon) / indentWidth; indentCount <= (ll->positions[i + 1] - epsilon) / indentWidth; indentCount++) { if (indentCount > 0) { int xIndent = indentCount * indentWidth; DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, (ll->xHighlightGuide == xIndent)); } } } if (vsDraw.viewWhitespace != wsInvisible) { if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { if (vsDraw.whitespaceColours.fore.isSet) textFore = vsDraw.whitespaceColours.fore; surface->PenColour(textFore); PRectangle rcTab(rcSegment.left + 1, rcSegment.top + 4, rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent); DrawTabArrow(surface, rcTab, rcSegment.top + vsDraw.lineHeight / 2); } } } else { inIndentation = false; if (vsDraw.controlCharSymbol >= 32) { char cc[2] = { static_cast(vsDraw.controlCharSymbol), '\0' }; surface->DrawTextNoClip(rcSegment, ctrlCharsFont, rcSegment.top + vsDraw.maxAscent, cc, 1, textBack, textFore); } else { DrawTextBlob(surface, vsDraw, rcSegment, ts.representation->stringRep.c_str(), textBack, textFore, twoPhaseDraw); } } } else { // Normal text display if (vsDraw.styles[styleMain].visible) { if (twoPhaseDraw) { surface->DrawTextTransparent(rcSegment, textFont, rcSegment.top + vsDraw.maxAscent, ll->chars + ts.start, i - ts.start + 1, textFore); } else { surface->DrawTextNoClip(rcSegment, textFont, rcSegment.top + vsDraw.maxAscent, ll->chars + ts.start, i - ts.start + 1, textFore, textBack); } } if (vsDraw.viewWhitespace != wsInvisible || (inIndentation && vsDraw.viewIndentationGuides != ivNone)) { for (int cpos = 0; cpos <= i - ts.start; cpos++) { if (ll->chars[cpos + ts.start] == ' ') { if (vsDraw.viewWhitespace != wsInvisible) { if (vsDraw.whitespaceColours.fore.isSet) textFore = vsDraw.whitespaceColours.fore; if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { XYPOSITION xmid = (ll->positions[cpos + ts.start] + ll->positions[cpos + ts.start + 1]) / 2; if (!twoPhaseDraw && drawWhitespaceBackground && (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { textBack = vsDraw.whitespaceColours.back; PRectangle rcSpace(ll->positions[cpos + ts.start] + xStart - subLineStart, rcSegment.top, ll->positions[cpos + ts.start + 1] + xStart - subLineStart, rcSegment.bottom); surface->FillRectangle(rcSpace, textBack); } PRectangle rcDot(xmid + xStart - subLineStart, rcSegment.top + vsDraw.lineHeight / 2, 0, 0); rcDot.right = rcDot.left + vs.whitespaceSize; rcDot.bottom = rcDot.top + vs.whitespaceSize; surface->FillRectangle(rcDot, textFore); } } if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { for (int indentCount = (ll->positions[cpos + ts.start] + epsilon) / indentWidth; indentCount <= (ll->positions[cpos + ts.start + 1] - epsilon) / indentWidth; indentCount++) { if (indentCount > 0) { int xIndent = indentCount * indentWidth; DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, (ll->xHighlightGuide == xIndent)); } } } } else { inIndentation = false; } } } } if (ll->hsStart != -1 && vsDraw.hotspotUnderline && iDoc >= ll->hsStart && iDoc < ll->hsEnd) { PRectangle rcUL = rcSegment; rcUL.top = rcUL.top + vsDraw.maxAscent + 1; rcUL.bottom = rcUL.top + 1; if (vsDraw.hotspotColours.fore.isSet) surface->FillRectangle(rcUL, vsDraw.hotspotColours.fore); else surface->FillRectangle(rcUL, textFore); } else if (vsDraw.styles[styleMain].underline) { PRectangle rcUL = rcSegment; rcUL.top = rcUL.top + vsDraw.maxAscent + 1; rcUL.bottom = rcUL.top + 1; surface->FillRectangle(rcUL, textFore); } } else if (rcSegment.left > rcLine.right) { break; } } if ((vsDraw.viewIndentationGuides == ivLookForward || vsDraw.viewIndentationGuides == ivLookBoth) && (subLine == 0)) { int indentSpace = pdoc->GetLineIndentation(line); int xStartText = ll->positions[pdoc->GetLineIndentPosition(line) - posLineStart]; // Find the most recent line with some text int lineLastWithText = line; while (lineLastWithText > Platform::Maximum(line-20, 0) && pdoc->IsWhiteLine(lineLastWithText)) { lineLastWithText--; } if (lineLastWithText < line) { xStartText = 100000; // Don't limit to visible indentation on empty line // This line is empty, so use indentation of last line with text int indentLastWithText = pdoc->GetLineIndentation(lineLastWithText); int isFoldHeader = pdoc->GetLevel(lineLastWithText) & SC_FOLDLEVELHEADERFLAG; if (isFoldHeader) { // Level is one more level than parent indentLastWithText += pdoc->IndentSize(); } if (vsDraw.viewIndentationGuides == ivLookForward) { // In viLookForward mode, previous line only used if it is a fold header if (isFoldHeader) { indentSpace = Platform::Maximum(indentSpace, indentLastWithText); } } else { // viLookBoth indentSpace = Platform::Maximum(indentSpace, indentLastWithText); } } int lineNextWithText = line; while (lineNextWithText < Platform::Minimum(line+20, pdoc->LinesTotal()) && pdoc->IsWhiteLine(lineNextWithText)) { lineNextWithText++; } if (lineNextWithText > line) { xStartText = 100000; // Don't limit to visible indentation on empty line // This line is empty, so use indentation of first next line with text indentSpace = Platform::Maximum(indentSpace, pdoc->GetLineIndentation(lineNextWithText)); } for (int indentPos = pdoc->IndentSize(); indentPos < indentSpace; indentPos += pdoc->IndentSize()) { int xIndent = indentPos * vsDraw.spaceWidth; if (xIndent < xStartText) { DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, (ll->xHighlightGuide == xIndent)); } } } DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineEnd, false); // End of the drawing of the current line if (!twoPhaseDraw) { DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, xStart, subLine, subLineStart, overrideBackground, background, drawWrapMarkEnd, wrapColour); } if (!hideSelection && ((vsDraw.selAlpha != SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha != SC_ALPHA_NOALPHA))) { // For each selection draw int virtualSpaces = 0; if (subLine == (ll->lines - 1)) { virtualSpaces = sel.VirtualSpaceFor(pdoc->LineEnd(line)); } SelectionPosition posStart(posLineStart + lineStart); SelectionPosition posEnd(posLineStart + lineEnd, virtualSpaces); SelectionSegment virtualSpaceRange(posStart, posEnd); for (size_t r=0; rEndLineStyle()].spaceWidth; rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - subLineStart + portion.start.VirtualSpace() * spaceWidth; rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - subLineStart + portion.end.VirtualSpace() * spaceWidth; if ((ll->wrapIndent != 0) && (lineStart != 0)) { if ((portion.start.Position() - posLineStart) == lineStart && sel.Range(r).ContainsCharacter(portion.start.Position() - 1)) rcSegment.left -= static_cast(ll->wrapIndent); // indentation added to xStart was truncated to int, so we do the same here } rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; if (rcSegment.right > rcLine.left) SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, r == sel.Main()), alpha); } } } } // Draw any translucent whole line states rcSegment = rcLine; if ((caret.active || vsDraw.alwaysShowCaretLineBackground) && vsDraw.showCaretLineBackground && ll->containsCaret) { SimpleAlphaRectangle(surface, rcSegment, vsDraw.caretLineBackground, vsDraw.caretLineAlpha); } marks = pdoc->GetMark(line); for (markBit = 0; (markBit < 32) && marks; markBit++) { if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_BACKGROUND)) { SimpleAlphaRectangle(surface, rcSegment, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); } else if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_UNDERLINE)) { PRectangle rcUnderline = rcSegment; rcUnderline.top = rcUnderline.bottom - 2; SimpleAlphaRectangle(surface, rcUnderline, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); } marks >>= 1; } if (vsDraw.maskInLine) { int marksMasked = pdoc->GetMark(line) & vsDraw.maskInLine; if (marksMasked) { for (markBit = 0; (markBit < 32) && marksMasked; markBit++) { if ((marksMasked & 1) && (vsDraw.markers[markBit].markType != SC_MARK_EMPTY)) { SimpleAlphaRectangle(surface, rcSegment, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); } marksMasked >>= 1; } } } } void Editor::DrawBlockCaret(Surface *surface, ViewStyle &vsDraw, LineLayout *ll, int subLine, int xStart, int offset, int posCaret, PRectangle rcCaret, ColourDesired caretColour) { int lineStart = ll->LineStart(subLine); int posBefore = posCaret; int posAfter = MovePositionOutsideChar(posCaret + 1, 1); int numCharsToDraw = posAfter - posCaret; // Work out where the starting and ending offsets are. We need to // see if the previous character shares horizontal space, such as a // glyph / combining character. If so we'll need to draw that too. int offsetFirstChar = offset; int offsetLastChar = offset + (posAfter - posCaret); while ((posBefore > 0) && ((offsetLastChar - numCharsToDraw) >= lineStart)) { if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - numCharsToDraw]) > 0) { // The char does not share horizontal space break; } // Char shares horizontal space, update the numChars to draw // Update posBefore to point to the prev char posBefore = MovePositionOutsideChar(posBefore - 1, -1); numCharsToDraw = posAfter - posBefore; offsetFirstChar = offset - (posCaret - posBefore); } // See if the next character shares horizontal space, if so we'll // need to draw that too. if (offsetFirstChar < 0) offsetFirstChar = 0; numCharsToDraw = offsetLastChar - offsetFirstChar; while ((offsetLastChar < ll->LineStart(subLine + 1)) && (offsetLastChar <= ll->numCharsInLine)) { // Update posAfter to point to the 2nd next char, this is where // the next character ends, and 2nd next begins. We'll need // to compare these two posBefore = posAfter; posAfter = MovePositionOutsideChar(posAfter + 1, 1); offsetLastChar = offset + (posAfter - posCaret); if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - (posAfter - posBefore)]) > 0) { // The char does not share horizontal space break; } // Char shares horizontal space, update the numChars to draw numCharsToDraw = offsetLastChar - offsetFirstChar; } // We now know what to draw, update the caret drawing rectangle rcCaret.left = ll->positions[offsetFirstChar] - ll->positions[lineStart] + xStart; rcCaret.right = ll->positions[offsetFirstChar+numCharsToDraw] - ll->positions[lineStart] + xStart; // Adjust caret position to take into account any word wrapping symbols. if ((ll->wrapIndent != 0) && (lineStart != 0)) { XYPOSITION wordWrapCharWidth = ll->wrapIndent; rcCaret.left += wordWrapCharWidth; rcCaret.right += wordWrapCharWidth; } // This character is where the caret block is, we override the colours // (inversed) for drawing the caret here. int styleMain = ll->styles[offsetFirstChar]; surface->DrawTextClipped(rcCaret, vsDraw.styles[styleMain].font, rcCaret.top + vsDraw.maxAscent, ll->chars + offsetFirstChar, numCharsToDraw, vsDraw.styles[styleMain].back, caretColour); } void Editor::RefreshPixMaps(Surface *surfaceWindow) { if (!pixmapSelPattern->Initialised()) { const int patternSize = 8; pixmapSelPattern->InitPixMap(patternSize, patternSize, surfaceWindow, wMain.GetID()); pixmapSelPatternOffset1->InitPixMap(patternSize, patternSize, surfaceWindow, wMain.GetID()); // This complex procedure is to reproduce the checkerboard dithered pattern used by windows // for scroll bars and Visual Studio for its selection margin. The colour of this pattern is half // way between the chrome colour and the chrome highlight colour making a nice transition // between the window chrome and the content area. And it works in low colour depths. PRectangle rcPattern(0, 0, patternSize, patternSize); // Initialize default colours based on the chrome colour scheme. Typically the highlight is white. ColourDesired colourFMFill = vs.selbar; ColourDesired colourFMStripes = vs.selbarlight; if (!(vs.selbarlight == ColourDesired(0xff, 0xff, 0xff))) { // User has chosen an unusual chrome colour scheme so just use the highlight edge colour. // (Typically, the highlight colour is white.) colourFMFill = vs.selbarlight; } if (vs.foldmarginColour.isSet) { // override default fold margin colour colourFMFill = vs.foldmarginColour; } if (vs.foldmarginHighlightColour.isSet) { // override default fold margin highlight colour colourFMStripes = vs.foldmarginHighlightColour; } pixmapSelPattern->FillRectangle(rcPattern, colourFMFill); pixmapSelPatternOffset1->FillRectangle(rcPattern, colourFMStripes); for (int y = 0; y < patternSize; y++) { for (int x = y % 2; x < patternSize; x+=2) { PRectangle rcPixel(x, y, x+1, y+1); pixmapSelPattern->FillRectangle(rcPixel, colourFMStripes); pixmapSelPatternOffset1->FillRectangle(rcPixel, colourFMFill); } } } if (!pixmapIndentGuide->Initialised()) { // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line pixmapIndentGuide->InitPixMap(1, vs.lineHeight + 1, surfaceWindow, wMain.GetID()); pixmapIndentGuideHighlight->InitPixMap(1, vs.lineHeight + 1, surfaceWindow, wMain.GetID()); PRectangle rcIG(0, 0, 1, vs.lineHeight); pixmapIndentGuide->FillRectangle(rcIG, vs.styles[STYLE_INDENTGUIDE].back); pixmapIndentGuide->PenColour(vs.styles[STYLE_INDENTGUIDE].fore); pixmapIndentGuideHighlight->FillRectangle(rcIG, vs.styles[STYLE_BRACELIGHT].back); pixmapIndentGuideHighlight->PenColour(vs.styles[STYLE_BRACELIGHT].fore); for (int stripe = 1; stripe < vs.lineHeight + 1; stripe += 2) { PRectangle rcPixel(0, stripe, 1, stripe+1); pixmapIndentGuide->FillRectangle(rcPixel, vs.styles[STYLE_INDENTGUIDE].fore); pixmapIndentGuideHighlight->FillRectangle(rcPixel, vs.styles[STYLE_BRACELIGHT].fore); } } if (bufferedDraw) { if (!pixmapLine->Initialised()) { PRectangle rcClient = GetClientRectangle(); pixmapLine->InitPixMap(rcClient.Width(), vs.lineHeight, surfaceWindow, wMain.GetID()); pixmapSelMargin->InitPixMap(vs.fixedColumnWidth, rcClient.Height(), surfaceWindow, wMain.GetID()); } } } void Editor::DrawCarets(Surface *surface, ViewStyle &vsDraw, int lineDoc, int xStart, PRectangle rcLine, LineLayout *ll, int subLine) { // When drag is active it is the only caret drawn bool drawDrag = posDrag.IsValid(); if (hideSelection && !drawDrag) return; const int posLineStart = pdoc->LineStart(lineDoc); // For each selection draw for (size_t r=0; (rEndLineStyle()].spaceWidth; const XYPOSITION virtualOffset = posCaret.VirtualSpace() * spaceWidth; if (ll->InLine(offset, subLine) && offset <= ll->numCharsBeforeEOL) { XYPOSITION xposCaret = ll->positions[offset] + virtualOffset - ll->positions[ll->LineStart(subLine)]; if (ll->wrapIndent != 0) { int lineStart = ll->LineStart(subLine); if (lineStart != 0) // Wrapped xposCaret += ll->wrapIndent; } bool caretBlinkState = (caret.active && caret.on) || (!additionalCaretsBlink && !mainCaret); bool caretVisibleState = additionalCaretsVisible || mainCaret; if ((xposCaret >= 0) && (vsDraw.caretWidth > 0) && (vsDraw.caretStyle != CARETSTYLE_INVISIBLE) && ((posDrag.IsValid()) || (caretBlinkState && caretVisibleState))) { bool caretAtEOF = false; bool caretAtEOL = false; bool drawBlockCaret = false; XYPOSITION widthOverstrikeCaret; int caretWidthOffset = 0; PRectangle rcCaret = rcLine; if (posCaret.Position() == pdoc->Length()) { // At end of document caretAtEOF = true; widthOverstrikeCaret = vsDraw.aveCharWidth; } else if ((posCaret.Position() - posLineStart) >= ll->numCharsInLine) { // At end of line caretAtEOL = true; widthOverstrikeCaret = vsDraw.aveCharWidth; } else { widthOverstrikeCaret = ll->positions[offset + 1] - ll->positions[offset]; } if (widthOverstrikeCaret < 3) // Make sure its visible widthOverstrikeCaret = 3; if (xposCaret > 0) caretWidthOffset = 1; // Move back so overlaps both character cells. xposCaret += xStart; if (posDrag.IsValid()) { /* Dragging text, use a line caret */ rcCaret.left = xposCaret - caretWidthOffset; rcCaret.right = rcCaret.left + vsDraw.caretWidth; } else if (inOverstrike && drawOverstrikeCaret) { /* Overstrike (insert mode), use a modified bar caret */ rcCaret.top = rcCaret.bottom - 2; rcCaret.left = xposCaret + 1; rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1; } else if (vsDraw.caretStyle == CARETSTYLE_BLOCK) { /* Block caret */ rcCaret.left = xposCaret; if (!caretAtEOL && !caretAtEOF && (ll->chars[offset] != '\t') && !(IsControlCharacter(ll->chars[offset]))) { drawBlockCaret = true; rcCaret.right = xposCaret + widthOverstrikeCaret; } else { rcCaret.right = xposCaret + vsDraw.aveCharWidth; } } else { /* Line caret */ rcCaret.left = xposCaret - caretWidthOffset; rcCaret.right = rcCaret.left + vsDraw.caretWidth; } ColourDesired caretColour = mainCaret ? vsDraw.caretcolour : vsDraw.additionalCaretColour; if (drawBlockCaret) { DrawBlockCaret(surface, vsDraw, ll, subLine, xStart, offset, posCaret.Position(), rcCaret, caretColour); } else { surface->FillRectangle(rcCaret, caretColour); } } } if (drawDrag) break; } } void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { //Platform::DebugPrintf("Paint:%1d (%3d,%3d) ... (%3d,%3d)\n", // paintingAllText, rcArea.left, rcArea.top, rcArea.right, rcArea.bottom); AllocateGraphics(); RefreshStyleData(); if (paintState == paintAbandoned) return; // Scroll bars may have changed so need redraw RefreshPixMaps(surfaceWindow); paintAbandonedByStyling = false; StyleToPositionInView(PositionAfterArea(rcArea)); PRectangle rcClient = GetClientRectangle(); Point ptOrigin = GetVisibleOriginInMain(); //Platform::DebugPrintf("Client: (%3d,%3d) ... (%3d,%3d) %d\n", // rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); int screenLinePaintFirst = rcArea.top / vs.lineHeight; int xStart = vs.textStart - xOffset + ptOrigin.x; int ypos = 0; if (!bufferedDraw) ypos += screenLinePaintFirst * vs.lineHeight; int yposScreen = screenLinePaintFirst * vs.lineHeight; if (NotifyUpdateUI()) { RefreshStyleData(); RefreshPixMaps(surfaceWindow); } // Wrap the visible lines if needed. if (WrapLines(wsVisible)) { // The wrapping process has changed the height of some lines so // abandon this paint for a complete repaint. if (AbandonPaint()) { return; } RefreshPixMaps(surfaceWindow); // In case pixmaps invalidated by scrollbar change } PLATFORM_ASSERT(pixmapSelPattern->Initialised()); if (!bufferedDraw) surfaceWindow->SetClip(rcArea); if (paintState != paintAbandoned) { if (vs.marginInside) { PaintSelMargin(surfaceWindow, rcArea); PRectangle rcRightMargin = rcClient; rcRightMargin.left = rcRightMargin.right - vs.rightMarginWidth; if (rcArea.Intersects(rcRightMargin)) { surfaceWindow->FillRectangle(rcRightMargin, vs.styles[STYLE_DEFAULT].back); } } else { // Else separate view so separate paint event but leftMargin included to allow overlap PRectangle rcLeftMargin = rcArea; rcLeftMargin.left = 0; rcLeftMargin.right = rcLeftMargin.left + vs.leftMarginWidth; if (rcArea.Intersects(rcLeftMargin)) { surfaceWindow->FillRectangle(rcLeftMargin, vs.styles[STYLE_DEFAULT].back); } } } if (paintState == paintAbandoned) { // Either styling or NotifyUpdateUI noticed that painting is needed // outside the current painting rectangle //Platform::DebugPrintf("Abandoning paint\n"); if (Wrapping()) { if (paintAbandonedByStyling) { // Styling has spilled over a line end, such as occurs by starting a multiline // comment. The width of subsequent text may have changed, so rewrap. NeedWrapping(cs.DocFromDisplay(topLine)); } } return; } //Platform::DebugPrintf("start display %d, offset = %d\n", pdoc->Length(), xOffset); // Allow text at start of line to overlap 1 pixel into the margin as this displays // serifs and italic stems for aliased text. const int leftTextOverlap = ((xOffset == 0) && (vs.leftMarginWidth > 0)) ? 1 : 0; // Do the painting if (rcArea.right > vs.textStart - leftTextOverlap) { Surface *surface = surfaceWindow; if (bufferedDraw) { surface = pixmapLine; PLATFORM_ASSERT(pixmapLine->Initialised()); } surface->SetUnicodeMode(IsUnicodeMode()); surface->SetDBCSMode(CodePage()); int visibleLine = TopLineOfMain() + screenLinePaintFirst; SelectionPosition posCaret = sel.RangeMain().caret; if (posDrag.IsValid()) posCaret = posDrag; int lineCaret = pdoc->LineFromPosition(posCaret.Position()); PRectangle rcTextArea = rcClient; if (vs.marginInside) { rcTextArea.left += vs.textStart; rcTextArea.right -= vs.rightMarginWidth; } else { rcTextArea = rcArea; } // Remove selection margin from drawing area so text will not be drawn // on it in unbuffered mode. if (!bufferedDraw && vs.marginInside) { PRectangle rcClipText = rcTextArea; rcClipText.left -= leftTextOverlap; surfaceWindow->SetClip(rcClipText); } // Loop on visible lines //double durLayout = 0.0; //double durPaint = 0.0; //double durCopy = 0.0; //ElapsedTime etWhole; int lineDocPrevious = -1; // Used to avoid laying out one document line multiple times AutoLineLayout ll(llc, 0); while (visibleLine < cs.LinesDisplayed() && yposScreen < rcArea.bottom) { int lineDoc = cs.DocFromDisplay(visibleLine); // Only visible lines should be handled by the code within the loop PLATFORM_ASSERT(cs.GetVisible(lineDoc)); int lineStartSet = cs.DisplayFromDoc(lineDoc); int subLine = visibleLine - lineStartSet; // Copy this line and its styles from the document into local arrays // and determine the x position at which each character starts. //ElapsedTime et; if (lineDoc != lineDocPrevious) { ll.Set(0); ll.Set(RetrieveLineLayout(lineDoc)); LayoutLine(lineDoc, surface, vs, ll, wrapWidth); lineDocPrevious = lineDoc; } //durLayout += et.Duration(true); if (ll) { ll->containsCaret = lineDoc == lineCaret; if (hideSelection) { ll->containsCaret = false; } GetHotSpotRange(ll->hsStart, ll->hsEnd); PRectangle rcLine = rcTextArea; rcLine.top = ypos; rcLine.bottom = ypos + vs.lineHeight; bool bracesIgnoreStyle = false; if ((vs.braceHighlightIndicatorSet && (bracesMatchStyle == STYLE_BRACELIGHT)) || (vs.braceBadLightIndicatorSet && (bracesMatchStyle == STYLE_BRACEBAD))) { bracesIgnoreStyle = true; } Range rangeLine(pdoc->LineStart(lineDoc), pdoc->LineStart(lineDoc + 1)); // Highlight the current braces if any ll->SetBracesHighlight(rangeLine, braces, static_cast(bracesMatchStyle), highlightGuideColumn * vs.spaceWidth, bracesIgnoreStyle); if (leftTextOverlap && bufferedDraw) { PRectangle rcSpacer = rcLine; rcSpacer.right = rcSpacer.left; rcSpacer.left -= 1; surface->FillRectangle(rcSpacer, vs.styles[STYLE_DEFAULT].back); } // Draw the line DrawLine(surface, vs, lineDoc, visibleLine, xStart, rcLine, ll, subLine); //durPaint += et.Duration(true); // Restore the previous styles for the brace highlights in case layout is in cache. ll->RestoreBracesHighlight(rangeLine, braces, bracesIgnoreStyle); bool expanded = cs.GetExpanded(lineDoc); const int level = pdoc->GetLevel(lineDoc); const int levelNext = pdoc->GetLevel(lineDoc + 1); if ((level & SC_FOLDLEVELHEADERFLAG) && ((level & SC_FOLDLEVELNUMBERMASK) < (levelNext & SC_FOLDLEVELNUMBERMASK))) { // Paint the line above the fold if ((expanded && (foldFlags & SC_FOLDFLAG_LINEBEFORE_EXPANDED)) || (!expanded && (foldFlags & SC_FOLDFLAG_LINEBEFORE_CONTRACTED))) { PRectangle rcFoldLine = rcLine; rcFoldLine.bottom = rcFoldLine.top + 1; surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore); } // Paint the line below the fold if ((expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_EXPANDED)) || (!expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_CONTRACTED))) { PRectangle rcFoldLine = rcLine; rcFoldLine.top = rcFoldLine.bottom - 1; surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore); } } DrawCarets(surface, vs, lineDoc, xStart, rcLine, ll, subLine); if (bufferedDraw) { Point from(vs.textStart-leftTextOverlap, 0); PRectangle rcCopyArea(vs.textStart-leftTextOverlap, yposScreen, rcClient.right - vs.rightMarginWidth, yposScreen + vs.lineHeight); surfaceWindow->Copy(rcCopyArea, from, *pixmapLine); } lineWidthMaxSeen = Platform::Maximum( lineWidthMaxSeen, ll->positions[ll->numCharsInLine]); //durCopy += et.Duration(true); } if (!bufferedDraw) { ypos += vs.lineHeight; } yposScreen += vs.lineHeight; visibleLine++; //gdk_flush(); } ll.Set(0); //if (durPaint < 0.00000001) // durPaint = 0.00000001; // Right column limit indicator PRectangle rcBeyondEOF = (vs.marginInside) ? rcClient : rcArea; rcBeyondEOF.left = vs.textStart; rcBeyondEOF.right = rcBeyondEOF.right - ((vs.marginInside) ? vs.rightMarginWidth : 0); rcBeyondEOF.top = (cs.LinesDisplayed() - TopLineOfMain()) * vs.lineHeight; if (rcBeyondEOF.top < rcBeyondEOF.bottom) { surfaceWindow->FillRectangle(rcBeyondEOF, vs.styles[STYLE_DEFAULT].back); if (vs.edgeState == EDGE_LINE) { int edgeX = vs.theEdge * vs.spaceWidth; rcBeyondEOF.left = edgeX + xStart; rcBeyondEOF.right = rcBeyondEOF.left + 1; surfaceWindow->FillRectangle(rcBeyondEOF, vs.edgecolour); } } //Platform::DebugPrintf( //"Layout:%9.6g Paint:%9.6g Ratio:%9.6g Copy:%9.6g Total:%9.6g\n", //durLayout, durPaint, durLayout / durPaint, durCopy, etWhole.Duration()); NotifyPainted(); } } // Space (3 space characters) between line numbers and text when printing. #define lineNumberPrintSpace " " ColourDesired InvertedLight(ColourDesired orig) { unsigned int r = orig.GetRed(); unsigned int g = orig.GetGreen(); unsigned int b = orig.GetBlue(); unsigned int l = (r + g + b) / 3; // There is a better calculation for this that matches human eye unsigned int il = 0xff - l; if (l == 0) return ColourDesired(0xff, 0xff, 0xff); r = r * il / l; g = g * il / l; b = b * il / l; return ColourDesired(Platform::Minimum(r, 0xff), Platform::Minimum(g, 0xff), Platform::Minimum(b, 0xff)); } // This is mostly copied from the Paint method but with some things omitted // such as the margin markers, line numbers, selection and caret // Should be merged back into a combined Draw method. long Editor::FormatRange(bool draw, Sci_RangeToFormat *pfr) { if (!pfr) return 0; AutoSurface surface(pfr->hdc, this, SC_TECHNOLOGY_DEFAULT); if (!surface) return 0; AutoSurface surfaceMeasure(pfr->hdcTarget, this, SC_TECHNOLOGY_DEFAULT); if (!surfaceMeasure) { return 0; } // Can't use measurements cached for screen posCache.Clear(); ViewStyle vsPrint(vs); vsPrint.technology = SC_TECHNOLOGY_DEFAULT; // Modify the view style for printing as do not normally want any of the transient features to be printed // Printing supports only the line number margin. int lineNumberIndex = -1; for (int margin = 0; margin <= SC_MAX_MARGIN; margin++) { if ((vsPrint.ms[margin].style == SC_MARGIN_NUMBER) && (vsPrint.ms[margin].width > 0)) { lineNumberIndex = margin; } else { vsPrint.ms[margin].width = 0; } } vsPrint.fixedColumnWidth = 0; vsPrint.zoomLevel = printParameters.magnification; // Don't show indentation guides // If this ever gets changed, cached pixmap would need to be recreated if technology != SC_TECHNOLOGY_DEFAULT vsPrint.viewIndentationGuides = ivNone; // Don't show the selection when printing vsPrint.selColours.back.isSet = false; vsPrint.selColours.fore.isSet = false; vsPrint.selAlpha = SC_ALPHA_NOALPHA; vsPrint.selAdditionalAlpha = SC_ALPHA_NOALPHA; vsPrint.whitespaceColours.back.isSet = false; vsPrint.whitespaceColours.fore.isSet = false; vsPrint.showCaretLineBackground = false; vsPrint.alwaysShowCaretLineBackground = false; // Don't highlight matching braces using indicators vsPrint.braceHighlightIndicatorSet = false; vsPrint.braceBadLightIndicatorSet = false; // Set colours for printing according to users settings for (size_t sty = 0; sty < vsPrint.styles.size(); sty++) { if (printParameters.colourMode == SC_PRINT_INVERTLIGHT) { vsPrint.styles[sty].fore = InvertedLight(vsPrint.styles[sty].fore); vsPrint.styles[sty].back = InvertedLight(vsPrint.styles[sty].back); } else if (printParameters.colourMode == SC_PRINT_BLACKONWHITE) { vsPrint.styles[sty].fore = ColourDesired(0, 0, 0); vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); } else if (printParameters.colourMode == SC_PRINT_COLOURONWHITE) { vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); } else if (printParameters.colourMode == SC_PRINT_COLOURONWHITEDEFAULTBG) { if (sty <= STYLE_DEFAULT) { vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); } } } // White background for the line numbers vsPrint.styles[STYLE_LINENUMBER].back = ColourDesired(0xff, 0xff, 0xff); // Printing uses different margins, so reset screen margins vsPrint.leftMarginWidth = 0; vsPrint.rightMarginWidth = 0; vsPrint.Refresh(*surfaceMeasure, pdoc->tabInChars); // Determining width must hapen after fonts have been realised in Refresh int lineNumberWidth = 0; if (lineNumberIndex >= 0) { lineNumberWidth = surfaceMeasure->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, "99999" lineNumberPrintSpace, 5 + istrlen(lineNumberPrintSpace)); vsPrint.ms[lineNumberIndex].width = lineNumberWidth; vsPrint.Refresh(*surfaceMeasure, pdoc->tabInChars); // Recalculate fixedColumnWidth } int linePrintStart = pdoc->LineFromPosition(pfr->chrg.cpMin); int linePrintLast = linePrintStart + (pfr->rc.bottom - pfr->rc.top) / vsPrint.lineHeight - 1; if (linePrintLast < linePrintStart) linePrintLast = linePrintStart; int linePrintMax = pdoc->LineFromPosition(pfr->chrg.cpMax); if (linePrintLast > linePrintMax) linePrintLast = linePrintMax; //Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n", // linePrintStart, linePrintLast, linePrintMax, pfr->rc.top, pfr->rc.bottom, vsPrint.lineHeight, // surfaceMeasure->Height(vsPrint.styles[STYLE_LINENUMBER].font)); int endPosPrint = pdoc->Length(); if (linePrintLast < pdoc->LinesTotal()) endPosPrint = pdoc->LineStart(linePrintLast + 1); // Ensure we are styled to where we are formatting. pdoc->EnsureStyledTo(endPosPrint); int xStart = vsPrint.fixedColumnWidth + pfr->rc.left; int ypos = pfr->rc.top; int lineDoc = linePrintStart; int nPrintPos = pfr->chrg.cpMin; int visibleLine = 0; int widthPrint = pfr->rc.right - pfr->rc.left - vsPrint.fixedColumnWidth; if (printParameters.wrapState == eWrapNone) widthPrint = LineLayout::wrapWidthInfinite; while (lineDoc <= linePrintLast && ypos < pfr->rc.bottom) { // When printing, the hdc and hdcTarget may be the same, so // changing the state of surfaceMeasure may change the underlying // state of surface. Therefore, any cached state is discarded before // using each surface. surfaceMeasure->FlushCachedState(); // Copy this line and its styles from the document into local arrays // and determine the x position at which each character starts. LineLayout ll(pdoc->LineStart(lineDoc+1)-pdoc->LineStart(lineDoc)+1); LayoutLine(lineDoc, surfaceMeasure, vsPrint, &ll, widthPrint); ll.containsCaret = false; PRectangle rcLine; rcLine.left = pfr->rc.left; rcLine.top = ypos; rcLine.right = pfr->rc.right - 1; rcLine.bottom = ypos + vsPrint.lineHeight; // When document line is wrapped over multiple display lines, find where // to start printing from to ensure a particular position is on the first // line of the page. if (visibleLine == 0) { int startWithinLine = nPrintPos - pdoc->LineStart(lineDoc); for (int iwl = 0; iwl < ll.lines - 1; iwl++) { if (ll.LineStart(iwl) <= startWithinLine && ll.LineStart(iwl + 1) >= startWithinLine) { visibleLine = -iwl; } } if (ll.lines > 1 && startWithinLine >= ll.LineStart(ll.lines - 1)) { visibleLine = -(ll.lines - 1); } } if (draw && lineNumberWidth && (ypos + vsPrint.lineHeight <= pfr->rc.bottom) && (visibleLine >= 0)) { char number[100]; sprintf(number, "%d" lineNumberPrintSpace, lineDoc + 1); PRectangle rcNumber = rcLine; rcNumber.right = rcNumber.left + lineNumberWidth; // Right justify rcNumber.left = rcNumber.right - surfaceMeasure->WidthText( vsPrint.styles[STYLE_LINENUMBER].font, number, istrlen(number)); surface->FlushCachedState(); surface->DrawTextNoClip(rcNumber, vsPrint.styles[STYLE_LINENUMBER].font, ypos + vsPrint.maxAscent, number, istrlen(number), vsPrint.styles[STYLE_LINENUMBER].fore, vsPrint.styles[STYLE_LINENUMBER].back); } // Draw the line surface->FlushCachedState(); for (int iwl = 0; iwl < ll.lines; iwl++) { if (ypos + vsPrint.lineHeight <= pfr->rc.bottom) { if (visibleLine >= 0) { if (draw) { rcLine.top = ypos; rcLine.bottom = ypos + vsPrint.lineHeight; DrawLine(surface, vsPrint, lineDoc, visibleLine, xStart, rcLine, &ll, iwl); } ypos += vsPrint.lineHeight; } visibleLine++; if (iwl == ll.lines - 1) nPrintPos = pdoc->LineStart(lineDoc + 1); else nPrintPos += ll.LineStart(iwl + 1) - ll.LineStart(iwl); } } ++lineDoc; } // Clear cache so measurements are not used for screen posCache.Clear(); return nPrintPos; } int Editor::TextWidth(int style, const char *text) { RefreshStyleData(); AutoSurface surface(this); if (surface) { return surface->WidthText(vs.styles[style].font, text, istrlen(text)); } else { return 1; } } // Empty method is overridden on GTK+ to show / hide scrollbars void Editor::ReconfigureScrollBars() {} void Editor::SetScrollBars() { RefreshStyleData(); int nMax = MaxScrollPos(); int nPage = LinesOnScreen(); bool modified = ModifyScrollBars(nMax + nPage - 1, nPage); if (modified) { DwellEnd(true); } // TODO: ensure always showing as many lines as possible // May not be, if, for example, window made larger if (topLine > MaxScrollPos()) { SetTopLine(Platform::Clamp(topLine, 0, MaxScrollPos())); SetVerticalScrollPos(); Redraw(); } if (modified) { if (!AbandonPaint()) Redraw(); } //Platform::DebugPrintf("end max = %d page = %d\n", nMax, nPage); } void Editor::ChangeSize() { DropGraphics(false); SetScrollBars(); if (Wrapping()) { PRectangle rcTextArea = GetClientRectangle(); rcTextArea.left = vs.textStart; rcTextArea.right -= vs.rightMarginWidth; if (wrapWidth != rcTextArea.Width()) { NeedWrapping(); Redraw(); } } } int Editor::InsertSpace(int position, unsigned int spaces) { if (spaces > 0) { std::string spaceText(spaces, ' '); pdoc->InsertString(position, spaceText.c_str(), spaces); position += spaces; } return position; } void Editor::AddChar(char ch) { char s[2]; s[0] = ch; s[1] = '\0'; AddCharUTF(s, 1); } void Editor::FilterSelections() { if (!additionalSelectionTyping && (sel.Count() > 1)) { SelectionRange rangeOnly = sel.RangeMain(); InvalidateSelection(rangeOnly, true); sel.SetSelection(rangeOnly); } } static bool cmpSelPtrs(const SelectionRange *a, const SelectionRange *b) { return *a < *b; } // AddCharUTF inserts an array of bytes which may or may not be in UTF-8. void Editor::AddCharUTF(char *s, unsigned int len, bool treatAsDBCS) { FilterSelections(); { UndoGroup ug(pdoc, (sel.Count() > 1) || !sel.Empty() || inOverstrike); std::vector selPtrs; for (size_t r = 0; r < sel.Count(); r++) { selPtrs.push_back(&sel.Range(r)); } std::sort(selPtrs.begin(), selPtrs.end(), cmpSelPtrs); for (std::vector::reverse_iterator rit = selPtrs.rbegin(); rit != selPtrs.rend(); ++rit) { SelectionRange *currentSel = *rit; if (!RangeContainsProtected(currentSel->Start().Position(), currentSel->End().Position())) { int positionInsert = currentSel->Start().Position(); if (!currentSel->Empty()) { if (currentSel->Length()) { pdoc->DeleteChars(positionInsert, currentSel->Length()); currentSel->ClearVirtualSpace(); } else { // Range is all virtual so collapse to start of virtual space currentSel->MinimizeVirtualSpace(); } } else if (inOverstrike) { if (positionInsert < pdoc->Length()) { if (!pdoc->IsPositionInLineEnd(positionInsert)) { pdoc->DelChar(positionInsert); currentSel->ClearVirtualSpace(); } } } positionInsert = InsertSpace(positionInsert, currentSel->caret.VirtualSpace()); if (pdoc->InsertString(positionInsert, s, len)) { currentSel->caret.SetPosition(positionInsert + len); currentSel->anchor.SetPosition(positionInsert + len); } currentSel->ClearVirtualSpace(); // If in wrap mode rewrap current line so EnsureCaretVisible has accurate information if (Wrapping()) { AutoSurface surface(this); if (surface) { if (WrapOneLine(surface, pdoc->LineFromPosition(positionInsert))) { SetScrollBars(); SetVerticalScrollPos(); Redraw(); } } } } } } if (Wrapping()) { SetScrollBars(); } ThinRectangularRange(); // If in wrap mode rewrap current line so EnsureCaretVisible has accurate information EnsureCaretVisible(); // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); if ((caretSticky == SC_CARETSTICKY_OFF) || ((caretSticky == SC_CARETSTICKY_WHITESPACE) && !IsAllSpacesOrTabs(s, len))) { SetLastXChosen(); } if (treatAsDBCS) { NotifyChar((static_cast(s[0]) << 8) | static_cast(s[1])); } else if (len > 0) { int byte = static_cast(s[0]); if ((byte < 0xC0) || (1 == len)) { // Handles UTF-8 characters between 0x01 and 0x7F and single byte // characters when not in UTF-8 mode. // Also treats \0 and naked trail bytes 0x80 to 0xBF as valid // characters representing themselves. } else { // Unroll 1 to 3 byte UTF-8 sequences. See reference data at: // http://www.cl.cam.ac.uk/~mgk25/unicode.html // http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt if (byte < 0xE0) { int byte2 = static_cast(s[1]); if ((byte2 & 0xC0) == 0x80) { // Two-byte-character lead-byte followed by a trail-byte. byte = (((byte & 0x1F) << 6) | (byte2 & 0x3F)); } // A two-byte-character lead-byte not followed by trail-byte // represents itself. } else if (byte < 0xF0) { int byte2 = static_cast(s[1]); int byte3 = static_cast(s[2]); if (((byte2 & 0xC0) == 0x80) && ((byte3 & 0xC0) == 0x80)) { // Three-byte-character lead byte followed by two trail bytes. byte = (((byte & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F)); } // A three-byte-character lead-byte not followed by two trail-bytes // represents itself. } } NotifyChar(byte); } if (recordingMacro) { NotifyMacroRecord(SCI_REPLACESEL, 0, reinterpret_cast(s)); } } void Editor::InsertPaste(SelectionPosition selStart, const char *text, int len) { if (multiPasteMode == SC_MULTIPASTE_ONCE) { selStart = SelectionPosition(InsertSpace(selStart.Position(), selStart.VirtualSpace())); if (pdoc->InsertString(selStart.Position(), text, len)) { SetEmptySelection(selStart.Position() + len); } } else { // SC_MULTIPASTE_EACH for (size_t r=0; rDeleteChars(positionInsert, sel.Range(r).Length()); sel.Range(r).ClearVirtualSpace(); } else { // Range is all virtual so collapse to start of virtual space sel.Range(r).MinimizeVirtualSpace(); } } positionInsert = InsertSpace(positionInsert, sel.Range(r).caret.VirtualSpace()); if (pdoc->InsertString(positionInsert, text, len)) { sel.Range(r).caret.SetPosition(positionInsert + len); sel.Range(r).anchor.SetPosition(positionInsert + len); } sel.Range(r).ClearVirtualSpace(); } } } } void Editor::ClearSelection(bool retainMultipleSelections) { if (!sel.IsRectangular() && !retainMultipleSelections) FilterSelections(); UndoGroup ug(pdoc); for (size_t r=0; rDeleteChars(sel.Range(r).Start().Position(), sel.Range(r).Length()); sel.Range(r) = sel.Range(r).Start(); } } } ThinRectangularRange(); sel.RemoveDuplicates(); ClaimSelection(); } void Editor::ClearAll() { { UndoGroup ug(pdoc); if (0 != pdoc->Length()) { pdoc->DeleteChars(0, pdoc->Length()); } if (!pdoc->IsReadOnly()) { cs.Clear(); pdoc->AnnotationClearAll(); pdoc->MarginClearAll(); } } sel.Clear(); SetTopLine(0); SetVerticalScrollPos(); InvalidateStyleRedraw(); } void Editor::ClearDocumentStyle() { Decoration *deco = pdoc->decorations.root; while (deco) { // Save next in case deco deleted Decoration *decoNext = deco->next; if (deco->indicator < INDIC_CONTAINER) { pdoc->decorations.SetCurrentIndicator(deco->indicator); pdoc->DecorationFillRange(0, 0, pdoc->Length()); } deco = decoNext; } pdoc->StartStyling(0, '\377'); pdoc->SetStyleFor(pdoc->Length(), 0); cs.ShowAll(); pdoc->ClearLevels(); } void Editor::CopyAllowLine() { SelectionText selectedText; CopySelectionRange(&selectedText, true); CopyToClipboard(selectedText); } void Editor::Cut() { pdoc->CheckReadOnly(); if (!pdoc->IsReadOnly() && !SelectionContainsProtected()) { Copy(); ClearSelection(); } } void Editor::PasteRectangular(SelectionPosition pos, const char *ptr, int len) { if (pdoc->IsReadOnly() || SelectionContainsProtected()) { return; } sel.Clear(); sel.RangeMain() = SelectionRange(pos); int line = pdoc->LineFromPosition(sel.MainCaret()); UndoGroup ug(pdoc); sel.RangeMain().caret = SelectionPosition( InsertSpace(sel.RangeMain().caret.Position(), sel.RangeMain().caret.VirtualSpace())); int xInsert = XFromPosition(sel.RangeMain().caret); bool prevCr = false; while ((len > 0) && IsEOLChar(ptr[len-1])) len--; for (int i = 0; i < len; i++) { if (IsEOLChar(ptr[i])) { if ((ptr[i] == '\r') || (!prevCr)) line++; if (line >= pdoc->LinesTotal()) { if (pdoc->eolMode != SC_EOL_LF) pdoc->InsertChar(pdoc->Length(), '\r'); if (pdoc->eolMode != SC_EOL_CR) pdoc->InsertChar(pdoc->Length(), '\n'); } // Pad the end of lines with spaces if required sel.RangeMain().caret.SetPosition(PositionFromLineX(line, xInsert)); if ((XFromPosition(sel.MainCaret()) < xInsert) && (i + 1 < len)) { while (XFromPosition(sel.MainCaret()) < xInsert) { pdoc->InsertChar(sel.MainCaret(), ' '); sel.RangeMain().caret.Add(1); } } prevCr = ptr[i] == '\r'; } else { pdoc->InsertString(sel.MainCaret(), ptr + i, 1); sel.RangeMain().caret.Add(1); prevCr = false; } } SetEmptySelection(pos); } bool Editor::CanPaste() { return !pdoc->IsReadOnly() && !SelectionContainsProtected(); } void Editor::Clear() { // If multiple selections, don't delete EOLS if (sel.Empty()) { bool singleVirtual = false; if ((sel.Count() == 1) && !RangeContainsProtected(sel.MainCaret(), sel.MainCaret() + 1) && sel.RangeMain().Start().VirtualSpace()) { singleVirtual = true; } UndoGroup ug(pdoc, (sel.Count() > 1) || singleVirtual); for (size_t r=0; rIsPositionInLineEnd(sel.Range(r).caret.Position())) { pdoc->DelChar(sel.Range(r).caret.Position()); sel.Range(r).ClearVirtualSpace(); } // else multiple selection so don't eat line ends } else { sel.Range(r).ClearVirtualSpace(); } } } else { ClearSelection(); } sel.RemoveDuplicates(); } void Editor::SelectAll() { sel.Clear(); SetSelection(0, pdoc->Length()); Redraw(); } void Editor::Undo() { if (pdoc->CanUndo()) { InvalidateCaret(); int newPos = pdoc->Undo(); if (newPos >= 0) SetEmptySelection(newPos); EnsureCaretVisible(); } } void Editor::Redo() { if (pdoc->CanRedo()) { int newPos = pdoc->Redo(); if (newPos >= 0) SetEmptySelection(newPos); EnsureCaretVisible(); } } void Editor::DelChar() { if (!RangeContainsProtected(sel.MainCaret(), sel.MainCaret() + 1)) { pdoc->DelChar(sel.MainCaret()); } // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); } void Editor::DelCharBack(bool allowLineStartDeletion) { if (!sel.IsRectangular()) FilterSelections(); if (sel.IsRectangular()) allowLineStartDeletion = false; UndoGroup ug(pdoc, (sel.Count() > 1) || !sel.Empty()); if (sel.Empty()) { for (size_t r=0; rLineFromPosition(sel.Range(r).caret.Position()); if (allowLineStartDeletion || (pdoc->LineStart(lineCurrentPos) != sel.Range(r).caret.Position())) { if (pdoc->GetColumn(sel.Range(r).caret.Position()) <= pdoc->GetLineIndentation(lineCurrentPos) && pdoc->GetColumn(sel.Range(r).caret.Position()) > 0 && pdoc->backspaceUnindents) { UndoGroup ugInner(pdoc, !ug.Needed()); int indentation = pdoc->GetLineIndentation(lineCurrentPos); int indentationStep = pdoc->IndentSize(); if (indentation % indentationStep == 0) { pdoc->SetLineIndentation(lineCurrentPos, indentation - indentationStep); } else { pdoc->SetLineIndentation(lineCurrentPos, indentation - (indentation % indentationStep)); } // SetEmptySelection sel.Range(r) = SelectionRange(pdoc->GetLineIndentPosition(lineCurrentPos), pdoc->GetLineIndentPosition(lineCurrentPos)); } else { pdoc->DelCharBack(sel.Range(r).caret.Position()); } } } } else { sel.Range(r).ClearVirtualSpace(); } } ThinRectangularRange(); } else { ClearSelection(); } sel.RemoveDuplicates(); ContainerNeedsUpdate(SC_UPDATE_SELECTION); // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); } void Editor::NotifyFocus(bool focus) { SCNotification scn = {}; scn.nmhdr.code = focus ? SCN_FOCUSIN : SCN_FOCUSOUT; NotifyParent(scn); } void Editor::SetCtrlID(int identifier) { ctrlID = identifier; } void Editor::NotifyStyleToNeeded(int endStyleNeeded) { SCNotification scn = {}; scn.nmhdr.code = SCN_STYLENEEDED; scn.position = endStyleNeeded; NotifyParent(scn); } void Editor::NotifyStyleNeeded(Document *, void *, int endStyleNeeded) { NotifyStyleToNeeded(endStyleNeeded); } void Editor::NotifyLexerChanged(Document *, void *) { } void Editor::NotifyErrorOccurred(Document *, void *, int status) { errorStatus = status; } void Editor::NotifyChar(int ch) { SCNotification scn = {}; scn.nmhdr.code = SCN_CHARADDED; scn.ch = ch; NotifyParent(scn); } void Editor::NotifySavePoint(bool isSavePoint) { SCNotification scn = {}; if (isSavePoint) { scn.nmhdr.code = SCN_SAVEPOINTREACHED; } else { scn.nmhdr.code = SCN_SAVEPOINTLEFT; } NotifyParent(scn); } void Editor::NotifyModifyAttempt() { SCNotification scn = {}; scn.nmhdr.code = SCN_MODIFYATTEMPTRO; NotifyParent(scn); } void Editor::NotifyDoubleClick(Point pt, bool shift, bool ctrl, bool alt) { SCNotification scn = {}; scn.nmhdr.code = SCN_DOUBLECLICK; scn.line = LineFromLocation(pt); scn.position = PositionFromLocation(pt, true); scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | (alt ? SCI_ALT : 0); NotifyParent(scn); } void Editor::NotifyHotSpotDoubleClicked(int position, bool shift, bool ctrl, bool alt) { SCNotification scn = {}; scn.nmhdr.code = SCN_HOTSPOTDOUBLECLICK; scn.position = position; scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | (alt ? SCI_ALT : 0); NotifyParent(scn); } void Editor::NotifyHotSpotClicked(int position, bool shift, bool ctrl, bool alt) { SCNotification scn = {}; scn.nmhdr.code = SCN_HOTSPOTCLICK; scn.position = position; scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | (alt ? SCI_ALT : 0); NotifyParent(scn); } void Editor::NotifyHotSpotReleaseClick(int position, bool shift, bool ctrl, bool alt) { SCNotification scn = {}; scn.nmhdr.code = SCN_HOTSPOTRELEASECLICK; scn.position = position; scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | (alt ? SCI_ALT : 0); NotifyParent(scn); } bool Editor::NotifyUpdateUI() { if (needUpdateUI) { SCNotification scn = {}; scn.nmhdr.code = SCN_UPDATEUI; scn.updated = needUpdateUI; NotifyParent(scn); needUpdateUI = 0; return true; } return false; } void Editor::NotifyPainted() { SCNotification scn = {}; scn.nmhdr.code = SCN_PAINTED; NotifyParent(scn); } void Editor::NotifyIndicatorClick(bool click, int position, bool shift, bool ctrl, bool alt) { int mask = pdoc->decorations.AllOnFor(position); if ((click && mask) || pdoc->decorations.clickNotified) { SCNotification scn = {}; pdoc->decorations.clickNotified = click; scn.nmhdr.code = click ? SCN_INDICATORCLICK : SCN_INDICATORRELEASE; scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | (alt ? SCI_ALT : 0); scn.position = position; NotifyParent(scn); } } bool Editor::NotifyMarginClick(Point pt, bool shift, bool ctrl, bool alt) { int marginClicked = -1; int x = vs.textStart - vs.fixedColumnWidth; for (int margin = 0; margin <= SC_MAX_MARGIN; margin++) { if ((pt.x >= x) && (pt.x < x + vs.ms[margin].width)) marginClicked = margin; x += vs.ms[margin].width; } if ((marginClicked >= 0) && vs.ms[marginClicked].sensitive) { int position = pdoc->LineStart(LineFromLocation(pt)); if ((vs.ms[marginClicked].mask & SC_MASK_FOLDERS) && (foldAutomatic & SC_AUTOMATICFOLD_CLICK)) { int lineClick = pdoc->LineFromPosition(position); if (shift && ctrl) { FoldAll(SC_FOLDACTION_TOGGLE); } else { int levelClick = pdoc->GetLevel(lineClick); if (levelClick & SC_FOLDLEVELHEADERFLAG) { if (shift) { // Ensure all children visible FoldExpand(lineClick, SC_FOLDACTION_EXPAND, levelClick); } else if (ctrl) { FoldExpand(lineClick, SC_FOLDACTION_TOGGLE, levelClick); } else { // Toggle this line FoldLine(lineClick, SC_FOLDACTION_TOGGLE); } } } return true; } SCNotification scn = {}; scn.nmhdr.code = SCN_MARGINCLICK; scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | (alt ? SCI_ALT : 0); scn.position = position; scn.margin = marginClicked; NotifyParent(scn); return true; } else { return false; } } void Editor::NotifyNeedShown(int pos, int len) { SCNotification scn = {}; scn.nmhdr.code = SCN_NEEDSHOWN; scn.position = pos; scn.length = len; NotifyParent(scn); } void Editor::NotifyDwelling(Point pt, bool state) { SCNotification scn = {}; scn.nmhdr.code = state ? SCN_DWELLSTART : SCN_DWELLEND; scn.position = PositionFromLocation(pt, true); scn.x = pt.x + vs.ExternalMarginWidth(); scn.y = pt.y; NotifyParent(scn); } void Editor::NotifyZoom() { SCNotification scn = {}; scn.nmhdr.code = SCN_ZOOM; NotifyParent(scn); } // Notifications from document void Editor::NotifyModifyAttempt(Document *, void *) { //Platform::DebugPrintf("** Modify Attempt\n"); NotifyModifyAttempt(); } void Editor::NotifySavePoint(Document *, void *, bool atSavePoint) { //Platform::DebugPrintf("** Save Point %s\n", atSavePoint ? "On" : "Off"); NotifySavePoint(atSavePoint); } void Editor::CheckModificationForWrap(DocModification mh) { if (mh.modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT)) { llc.Invalidate(LineLayout::llCheckTextAndStyle); int lineDoc = pdoc->LineFromPosition(mh.position); int lines = Platform::Maximum(0, mh.linesAdded); if (Wrapping()) { NeedWrapping(lineDoc, lineDoc + lines + 1); } RefreshStyleData(); // Fix up annotation heights SetAnnotationHeights(lineDoc, lineDoc + lines + 2); } } // Move a position so it is still after the same character as before the insertion. static inline int MovePositionForInsertion(int position, int startInsertion, int length) { if (position > startInsertion) { return position + length; } return position; } // Move a position so it is still after the same character as before the deletion if that // character is still present else after the previous surviving character. static inline int MovePositionForDeletion(int position, int startDeletion, int length) { if (position > startDeletion) { int endDeletion = startDeletion + length; if (position > endDeletion) { return position - length; } else { return startDeletion; } } else { return position; } } void Editor::NotifyModified(Document *, DocModification mh, void *) { ContainerNeedsUpdate(SC_UPDATE_CONTENT); if (paintState == painting) { CheckForChangeOutsidePaint(Range(mh.position, mh.position + mh.length)); } if (mh.modificationType & SC_MOD_CHANGELINESTATE) { if (paintState == painting) { CheckForChangeOutsidePaint( Range(pdoc->LineStart(mh.line), pdoc->LineStart(mh.line + 1))); } else { // Could check that change is before last visible line. Redraw(); } } if (mh.modificationType & SC_MOD_LEXERSTATE) { if (paintState == painting) { CheckForChangeOutsidePaint( Range(mh.position, mh.position + mh.length)); } else { Redraw(); } } if (mh.modificationType & (SC_MOD_CHANGESTYLE | SC_MOD_CHANGEINDICATOR)) { if (mh.modificationType & SC_MOD_CHANGESTYLE) { pdoc->IncrementStyleClock(); } if (paintState == notPainting) { if (mh.position < pdoc->LineStart(topLine)) { // Styling performed before this view Redraw(); } else { InvalidateRange(mh.position, mh.position + mh.length); } } if (mh.modificationType & SC_MOD_CHANGESTYLE) { llc.Invalidate(LineLayout::llCheckTextAndStyle); } } else { // Move selection and brace highlights if (mh.modificationType & SC_MOD_INSERTTEXT) { sel.MovePositions(true, mh.position, mh.length); braces[0] = MovePositionForInsertion(braces[0], mh.position, mh.length); braces[1] = MovePositionForInsertion(braces[1], mh.position, mh.length); } else if (mh.modificationType & SC_MOD_DELETETEXT) { sel.MovePositions(false, mh.position, mh.length); braces[0] = MovePositionForDeletion(braces[0], mh.position, mh.length); braces[1] = MovePositionForDeletion(braces[1], mh.position, mh.length); } if ((mh.modificationType & (SC_MOD_BEFOREINSERT | SC_MOD_BEFOREDELETE)) && cs.HiddenLines()) { // Some lines are hidden so may need shown. // TODO: check if the modified area is hidden. if (mh.modificationType & SC_MOD_BEFOREINSERT) { int lineOfPos = pdoc->LineFromPosition(mh.position); bool insertingNewLine = false; for (int i=0; i < mh.length; i++) { if ((mh.text[i] == '\n') || (mh.text[i] == '\r')) insertingNewLine = true; } if (insertingNewLine && (mh.position != pdoc->LineStart(lineOfPos))) NeedShown(mh.position, pdoc->LineStart(lineOfPos+1) - mh.position); else NeedShown(mh.position, 0); } else if (mh.modificationType & SC_MOD_BEFOREDELETE) { NeedShown(mh.position, mh.length); } } if (mh.linesAdded != 0) { // Update contraction state for inserted and removed lines // lineOfPos should be calculated in context of state before modification, shouldn't it int lineOfPos = pdoc->LineFromPosition(mh.position); if (mh.position > pdoc->LineStart(lineOfPos)) lineOfPos++; // Affecting subsequent lines if (mh.linesAdded > 0) { cs.InsertLines(lineOfPos, mh.linesAdded); } else { cs.DeleteLines(lineOfPos, -mh.linesAdded); } } if (mh.modificationType & SC_MOD_CHANGEANNOTATION) { int lineDoc = pdoc->LineFromPosition(mh.position); if (vs.annotationVisible) { cs.SetHeight(lineDoc, cs.GetHeight(lineDoc) + mh.annotationLinesAdded); Redraw(); } } CheckModificationForWrap(mh); if (mh.linesAdded != 0) { // Avoid scrolling of display if change before current display if (mh.position < posTopLine && !CanDeferToLastStep(mh)) { int newTop = Platform::Clamp(topLine + mh.linesAdded, 0, MaxScrollPos()); if (newTop != topLine) { SetTopLine(newTop); SetVerticalScrollPos(); } } if (paintState == notPainting && !CanDeferToLastStep(mh)) { QueueIdleWork(WorkNeeded::workStyle, pdoc->Length()); Redraw(); } } else { if (paintState == notPainting && mh.length && !CanEliminate(mh)) { QueueIdleWork(WorkNeeded::workStyle, mh.position + mh.length); InvalidateRange(mh.position, mh.position + mh.length); } } } if (mh.linesAdded != 0 && !CanDeferToLastStep(mh)) { SetScrollBars(); } if ((mh.modificationType & SC_MOD_CHANGEMARKER) || (mh.modificationType & SC_MOD_CHANGEMARGIN)) { if ((!willRedrawAll) && ((paintState == notPainting) || !PaintContainsMargin())) { if (mh.modificationType & SC_MOD_CHANGEFOLD) { // Fold changes can affect the drawing of following lines so redraw whole margin RedrawSelMargin(highlightDelimiter.isEnabled ? -1 : mh.line-1, true); } else { RedrawSelMargin(mh.line); } } } if ((mh.modificationType & SC_MOD_CHANGEFOLD) && (foldAutomatic & SC_AUTOMATICFOLD_CHANGE)) { FoldChanged(mh.line, mh.foldLevelNow, mh.foldLevelPrev); } // NOW pay the piper WRT "deferred" visual updates if (IsLastStep(mh)) { SetScrollBars(); Redraw(); } // If client wants to see this modification if (mh.modificationType & modEventMask) { if ((mh.modificationType & (SC_MOD_CHANGESTYLE | SC_MOD_CHANGEINDICATOR)) == 0) { // Real modification made to text of document. NotifyChange(); // Send EN_CHANGE } SCNotification scn = {}; scn.nmhdr.code = SCN_MODIFIED; scn.position = mh.position; scn.modificationType = mh.modificationType; scn.text = mh.text; scn.length = mh.length; scn.linesAdded = mh.linesAdded; scn.line = mh.line; scn.foldLevelNow = mh.foldLevelNow; scn.foldLevelPrev = mh.foldLevelPrev; scn.token = mh.token; scn.annotationLinesAdded = mh.annotationLinesAdded; NotifyParent(scn); } } void Editor::NotifyDeleted(Document *, void *) { /* Do nothing */ } void Editor::NotifyMacroRecord(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { // Enumerates all macroable messages switch (iMessage) { case SCI_CUT: case SCI_COPY: case SCI_PASTE: case SCI_CLEAR: case SCI_REPLACESEL: case SCI_ADDTEXT: case SCI_INSERTTEXT: case SCI_APPENDTEXT: case SCI_CLEARALL: case SCI_SELECTALL: case SCI_GOTOLINE: case SCI_GOTOPOS: case SCI_SEARCHANCHOR: case SCI_SEARCHNEXT: case SCI_SEARCHPREV: case SCI_LINEDOWN: case SCI_LINEDOWNEXTEND: case SCI_PARADOWN: case SCI_PARADOWNEXTEND: case SCI_LINEUP: case SCI_LINEUPEXTEND: case SCI_PARAUP: case SCI_PARAUPEXTEND: case SCI_CHARLEFT: case SCI_CHARLEFTEXTEND: case SCI_CHARRIGHT: case SCI_CHARRIGHTEXTEND: case SCI_WORDLEFT: case SCI_WORDLEFTEXTEND: case SCI_WORDRIGHT: case SCI_WORDRIGHTEXTEND: case SCI_WORDPARTLEFT: case SCI_WORDPARTLEFTEXTEND: case SCI_WORDPARTRIGHT: case SCI_WORDPARTRIGHTEXTEND: case SCI_WORDLEFTEND: case SCI_WORDLEFTENDEXTEND: case SCI_WORDRIGHTEND: case SCI_WORDRIGHTENDEXTEND: case SCI_HOME: case SCI_HOMEEXTEND: case SCI_LINEEND: case SCI_LINEENDEXTEND: case SCI_HOMEWRAP: case SCI_HOMEWRAPEXTEND: case SCI_LINEENDWRAP: case SCI_LINEENDWRAPEXTEND: case SCI_DOCUMENTSTART: case SCI_DOCUMENTSTARTEXTEND: case SCI_DOCUMENTEND: case SCI_DOCUMENTENDEXTEND: case SCI_STUTTEREDPAGEUP: case SCI_STUTTEREDPAGEUPEXTEND: case SCI_STUTTEREDPAGEDOWN: case SCI_STUTTEREDPAGEDOWNEXTEND: case SCI_PAGEUP: case SCI_PAGEUPEXTEND: case SCI_PAGEDOWN: case SCI_PAGEDOWNEXTEND: case SCI_EDITTOGGLEOVERTYPE: case SCI_CANCEL: case SCI_DELETEBACK: case SCI_TAB: case SCI_BACKTAB: case SCI_FORMFEED: case SCI_VCHOME: case SCI_VCHOMEEXTEND: case SCI_VCHOMEWRAP: case SCI_VCHOMEWRAPEXTEND: case SCI_VCHOMEDISPLAY: case SCI_VCHOMEDISPLAYEXTEND: case SCI_DELWORDLEFT: case SCI_DELWORDRIGHT: case SCI_DELWORDRIGHTEND: case SCI_DELLINELEFT: case SCI_DELLINERIGHT: case SCI_LINECOPY: case SCI_LINECUT: case SCI_LINEDELETE: case SCI_LINETRANSPOSE: case SCI_LINEDUPLICATE: case SCI_LOWERCASE: case SCI_UPPERCASE: case SCI_LINESCROLLDOWN: case SCI_LINESCROLLUP: case SCI_DELETEBACKNOTLINE: case SCI_HOMEDISPLAY: case SCI_HOMEDISPLAYEXTEND: case SCI_LINEENDDISPLAY: case SCI_LINEENDDISPLAYEXTEND: case SCI_SETSELECTIONMODE: case SCI_LINEDOWNRECTEXTEND: case SCI_LINEUPRECTEXTEND: case SCI_CHARLEFTRECTEXTEND: case SCI_CHARRIGHTRECTEXTEND: case SCI_HOMERECTEXTEND: case SCI_VCHOMERECTEXTEND: case SCI_LINEENDRECTEXTEND: case SCI_PAGEUPRECTEXTEND: case SCI_PAGEDOWNRECTEXTEND: case SCI_SELECTIONDUPLICATE: case SCI_COPYALLOWLINE: case SCI_VERTICALCENTRECARET: case SCI_MOVESELECTEDLINESUP: case SCI_MOVESELECTEDLINESDOWN: case SCI_SCROLLTOSTART: case SCI_SCROLLTOEND: break; // Filter out all others like display changes. Also, newlines are redundant // with char insert messages. case SCI_NEWLINE: default: // printf("Filtered out %ld of macro recording\n", iMessage); return ; } // Send notification SCNotification scn = {}; scn.nmhdr.code = SCN_MACRORECORD; scn.message = iMessage; scn.wParam = wParam; scn.lParam = lParam; NotifyParent(scn); } // Something has changed that the container should know about void Editor::ContainerNeedsUpdate(int flags) { needUpdateUI |= flags; } /** * Force scroll and keep position relative to top of window. * * If stuttered = true and not already at first/last row, move to first/last row of window. * If stuttered = true and already at first/last row, scroll as normal. */ void Editor::PageMove(int direction, Selection::selTypes selt, bool stuttered) { int topLineNew; SelectionPosition newPos; int currentLine = pdoc->LineFromPosition(sel.MainCaret()); int topStutterLine = topLine + caretYSlop; int bottomStutterLine = pdoc->LineFromPosition(PositionFromLocation( Point(lastXChosen - xOffset, direction * vs.lineHeight * LinesToScroll()))) - caretYSlop - 1; if (stuttered && (direction < 0 && currentLine > topStutterLine)) { topLineNew = topLine; newPos = SPositionFromLocation(Point(lastXChosen - xOffset, vs.lineHeight * caretYSlop), false, false, UserVirtualSpace()); } else if (stuttered && (direction > 0 && currentLine < bottomStutterLine)) { topLineNew = topLine; newPos = SPositionFromLocation(Point(lastXChosen - xOffset, vs.lineHeight * (LinesToScroll() - caretYSlop)), false, false, UserVirtualSpace()); } else { Point pt = LocationFromPosition(sel.MainCaret()); topLineNew = Platform::Clamp( topLine + direction * LinesToScroll(), 0, MaxScrollPos()); newPos = SPositionFromLocation( Point(lastXChosen - xOffset, pt.y + direction * (vs.lineHeight * LinesToScroll())), false, false, UserVirtualSpace()); } if (topLineNew != topLine) { SetTopLine(topLineNew); MovePositionTo(newPos, selt); Redraw(); SetVerticalScrollPos(); } else { MovePositionTo(newPos, selt); } } void Editor::ChangeCaseOfSelection(int caseMapping) { UndoGroup ug(pdoc); for (size_t r=0; r 0) { std::string sText = RangeText(currentNoVS.Start().Position(), currentNoVS.End().Position()); std::string sMapped = CaseMapString(sText, caseMapping); if (sMapped != sText) { size_t firstDifference = 0; while (sMapped[firstDifference] == sText[firstDifference]) firstDifference++; size_t lastDifferenceText = sText.size() - 1; size_t lastDifferenceMapped = sMapped.size() - 1; while (sMapped[lastDifferenceMapped] == sText[lastDifferenceText]) { lastDifferenceText--; lastDifferenceMapped--; } size_t endDifferenceText = sText.size() - 1 - lastDifferenceText; pdoc->DeleteChars( static_cast(currentNoVS.Start().Position() + firstDifference), static_cast(rangeBytes - firstDifference - endDifferenceText)); pdoc->InsertString( static_cast(currentNoVS.Start().Position() + firstDifference), sMapped.c_str() + firstDifference, static_cast(lastDifferenceMapped - firstDifference + 1)); // Automatic movement changes selection so reset to exactly the same as it was. int diffSizes = static_cast(sMapped.size() - sText.size()); if (diffSizes != 0) { if (current.anchor > current.caret) current.anchor.Add(diffSizes); else current.caret.Add(diffSizes); } sel.Range(r) = current; } } } } void Editor::LineTranspose() { int line = pdoc->LineFromPosition(sel.MainCaret()); if (line > 0) { UndoGroup ug(pdoc); int startPrev = pdoc->LineStart(line - 1); int endPrev = pdoc->LineEnd(line - 1); int start = pdoc->LineStart(line); int end = pdoc->LineEnd(line); std::string line1 = RangeText(startPrev, endPrev); int len1 = endPrev - startPrev; std::string line2 = RangeText(start, end); int len2 = end - start; pdoc->DeleteChars(start, len2); pdoc->DeleteChars(startPrev, len1); pdoc->InsertString(startPrev, line2.c_str(), len2); pdoc->InsertString(start - len1 + len2, line1.c_str(), len1); MovePositionTo(SelectionPosition(start - len1 + len2)); } } void Editor::Duplicate(bool forLine) { if (sel.Empty()) { forLine = true; } UndoGroup ug(pdoc); const char *eol = ""; int eolLen = 0; if (forLine) { eol = StringFromEOLMode(pdoc->eolMode); eolLen = istrlen(eol); } for (size_t r=0; rLineFromPosition(sel.Range(r).caret.Position()); start = SelectionPosition(pdoc->LineStart(line)); end = SelectionPosition(pdoc->LineEnd(line)); } std::string text = RangeText(start.Position(), end.Position()); if (forLine) pdoc->InsertString(end.Position(), eol, eolLen); pdoc->InsertString(end.Position() + eolLen, text.c_str(), SelectionRange(end, start).Length()); } if (sel.Count() && sel.IsRectangular()) { SelectionPosition last = sel.Last(); if (forLine) { int line = pdoc->LineFromPosition(last.Position()); last = SelectionPosition(last.Position() + pdoc->LineStart(line+1) - pdoc->LineStart(line)); } if (sel.Rectangular().anchor > sel.Rectangular().caret) sel.Rectangular().anchor = last; else sel.Rectangular().caret = last; SetRectangularRange(); } } void Editor::CancelModes() { sel.SetMoveExtends(false); } void Editor::NewLine() { // Remove non-main ranges InvalidateSelection(sel.RangeMain(), true); sel.SetSelection(sel.RangeMain()); // Clear main range and insert line end bool needGroupUndo = !sel.Empty(); if (needGroupUndo) pdoc->BeginUndoAction(); if (!sel.Empty()) ClearSelection(); const char *eol = "\n"; if (pdoc->eolMode == SC_EOL_CRLF) { eol = "\r\n"; } else if (pdoc->eolMode == SC_EOL_CR) { eol = "\r"; } // else SC_EOL_LF -> "\n" already set bool inserted = pdoc->InsertCString(sel.MainCaret(), eol); // Want to end undo group before NotifyChar as applications often modify text here if (needGroupUndo) pdoc->EndUndoAction(); if (inserted) { SetEmptySelection(sel.MainCaret() + istrlen(eol)); while (*eol) { NotifyChar(*eol); if (recordingMacro) { char txt[2]; txt[0] = *eol; txt[1] = '\0'; NotifyMacroRecord(SCI_REPLACESEL, 0, reinterpret_cast(txt)); } eol++; } } SetLastXChosen(); SetScrollBars(); EnsureCaretVisible(); // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); } void Editor::CursorUpOrDown(int direction, Selection::selTypes selt) { SelectionPosition caretToUse = sel.Range(sel.Main()).caret; if (sel.IsRectangular()) { if (selt == Selection::noSel) { caretToUse = (direction > 0) ? sel.Limits().end : sel.Limits().start; } else { caretToUse = sel.Rectangular().caret; } } Point pt = LocationFromPosition(caretToUse); int skipLines = 0; if (vs.annotationVisible) { int lineDoc = pdoc->LineFromPosition(caretToUse.Position()); Point ptStartLine = LocationFromPosition(pdoc->LineStart(lineDoc)); int subLine = (pt.y - ptStartLine.y) / vs.lineHeight; if (direction < 0 && subLine == 0) { int lineDisplay = cs.DisplayFromDoc(lineDoc); if (lineDisplay > 0) { skipLines = pdoc->AnnotationLines(cs.DocFromDisplay(lineDisplay - 1)); } } else if (direction > 0 && subLine >= (cs.GetHeight(lineDoc) - 1 - pdoc->AnnotationLines(lineDoc))) { skipLines = pdoc->AnnotationLines(lineDoc); } } int newY = pt.y + (1 + skipLines) * direction * vs.lineHeight; SelectionPosition posNew = SPositionFromLocation( Point(lastXChosen - xOffset, newY), false, false, UserVirtualSpace()); if (direction < 0) { // Line wrapping may lead to a location on the same line, so // seek back if that is the case. Point ptNew = LocationFromPosition(posNew.Position()); while ((posNew.Position() > 0) && (pt.y == ptNew.y)) { posNew.Add(-1); posNew.SetVirtualSpace(0); ptNew = LocationFromPosition(posNew.Position()); } } else if (direction > 0 && posNew.Position() != pdoc->Length()) { // There is an equivalent case when moving down which skips // over a line. Point ptNew = LocationFromPosition(posNew.Position()); while ((posNew.Position() > caretToUse.Position()) && (ptNew.y > newY)) { posNew.Add(-1); posNew.SetVirtualSpace(0); ptNew = LocationFromPosition(posNew.Position()); } } MovePositionTo(MovePositionSoVisible(posNew, direction), selt); } void Editor::ParaUpOrDown(int direction, Selection::selTypes selt) { int lineDoc, savedPos = sel.MainCaret(); do { MovePositionTo(SelectionPosition(direction > 0 ? pdoc->ParaDown(sel.MainCaret()) : pdoc->ParaUp(sel.MainCaret())), selt); lineDoc = pdoc->LineFromPosition(sel.MainCaret()); if (direction > 0) { if (sel.MainCaret() >= pdoc->Length() && !cs.GetVisible(lineDoc)) { if (selt == Selection::noSel) { MovePositionTo(SelectionPosition(pdoc->LineEndPosition(savedPos))); } break; } } } while (!cs.GetVisible(lineDoc)); } int Editor::StartEndDisplayLine(int pos, bool start) { RefreshStyleData(); int line = pdoc->LineFromPosition(pos); AutoSurface surface(this); AutoLineLayout ll(llc, RetrieveLineLayout(line)); int posRet = INVALID_POSITION; if (surface && ll) { unsigned int posLineStart = pdoc->LineStart(line); LayoutLine(line, surface, vs, ll, wrapWidth); int posInLine = pos - posLineStart; if (posInLine <= ll->maxLineLength) { for (int subLine = 0; subLine < ll->lines; subLine++) { if ((posInLine >= ll->LineStart(subLine)) && (posInLine <= ll->LineStart(subLine + 1))) { if (start) { posRet = ll->LineStart(subLine) + posLineStart; } else { if (subLine == ll->lines - 1) posRet = ll->LineStart(subLine + 1) + posLineStart; else posRet = ll->LineStart(subLine + 1) + posLineStart - 1; } } } } } if (posRet == INVALID_POSITION) { return pos; } else { return posRet; } } int Editor::KeyCommand(unsigned int iMessage) { switch (iMessage) { case SCI_LINEDOWN: CursorUpOrDown(1); break; case SCI_LINEDOWNEXTEND: CursorUpOrDown(1, Selection::selStream); break; case SCI_LINEDOWNRECTEXTEND: CursorUpOrDown(1, Selection::selRectangle); break; case SCI_PARADOWN: ParaUpOrDown(1); break; case SCI_PARADOWNEXTEND: ParaUpOrDown(1, Selection::selStream); break; case SCI_LINESCROLLDOWN: ScrollTo(topLine + 1); MoveCaretInsideView(false); break; case SCI_LINEUP: CursorUpOrDown(-1); break; case SCI_LINEUPEXTEND: CursorUpOrDown(-1, Selection::selStream); break; case SCI_LINEUPRECTEXTEND: CursorUpOrDown(-1, Selection::selRectangle); break; case SCI_PARAUP: ParaUpOrDown(-1); break; case SCI_PARAUPEXTEND: ParaUpOrDown(-1, Selection::selStream); break; case SCI_LINESCROLLUP: ScrollTo(topLine - 1); MoveCaretInsideView(false); break; case SCI_CHARLEFT: if (SelectionEmpty() || sel.MoveExtends()) { if ((sel.Count() == 1) && pdoc->IsLineEndPosition(sel.MainCaret()) && sel.RangeMain().caret.VirtualSpace()) { SelectionPosition spCaret = sel.RangeMain().caret; spCaret.SetVirtualSpace(spCaret.VirtualSpace() - 1); MovePositionTo(spCaret); } else if (sel.MoveExtends() && sel.selType == Selection::selStream) { MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() - 1), -1)); } else { MovePositionTo(MovePositionSoVisible( SelectionPosition((sel.LimitsForRectangularElseMain().start).Position() - 1), -1)); } } else { MovePositionTo(sel.LimitsForRectangularElseMain().start); } SetLastXChosen(); break; case SCI_CHARLEFTEXTEND: if (pdoc->IsLineEndPosition(sel.MainCaret()) && sel.RangeMain().caret.VirtualSpace()) { SelectionPosition spCaret = sel.RangeMain().caret; spCaret.SetVirtualSpace(spCaret.VirtualSpace() - 1); MovePositionTo(spCaret, Selection::selStream); } else { MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() - 1), -1), Selection::selStream); } SetLastXChosen(); break; case SCI_CHARLEFTRECTEXTEND: if (pdoc->IsLineEndPosition(sel.MainCaret()) && sel.RangeMain().caret.VirtualSpace()) { SelectionPosition spCaret = sel.RangeMain().caret; spCaret.SetVirtualSpace(spCaret.VirtualSpace() - 1); MovePositionTo(spCaret, Selection::selRectangle); } else { MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() - 1), -1), Selection::selRectangle); } SetLastXChosen(); break; case SCI_CHARRIGHT: if (SelectionEmpty() || sel.MoveExtends()) { if ((virtualSpaceOptions & SCVS_USERACCESSIBLE) && pdoc->IsLineEndPosition(sel.MainCaret())) { SelectionPosition spCaret = sel.RangeMain().caret; spCaret.SetVirtualSpace(spCaret.VirtualSpace() + 1); MovePositionTo(spCaret); } else if (sel.MoveExtends() && sel.selType == Selection::selStream) { MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() + 1), 1)); } else { MovePositionTo(MovePositionSoVisible( SelectionPosition((sel.LimitsForRectangularElseMain().end).Position() + 1), 1)); } } else { MovePositionTo(sel.LimitsForRectangularElseMain().end); } SetLastXChosen(); break; case SCI_CHARRIGHTEXTEND: if ((virtualSpaceOptions & SCVS_USERACCESSIBLE) && pdoc->IsLineEndPosition(sel.MainCaret())) { SelectionPosition spCaret = sel.RangeMain().caret; spCaret.SetVirtualSpace(spCaret.VirtualSpace() + 1); MovePositionTo(spCaret, Selection::selStream); } else { MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() + 1), 1), Selection::selStream); } SetLastXChosen(); break; case SCI_CHARRIGHTRECTEXTEND: if ((virtualSpaceOptions & SCVS_RECTANGULARSELECTION) && pdoc->IsLineEndPosition(sel.MainCaret())) { SelectionPosition spCaret = sel.RangeMain().caret; spCaret.SetVirtualSpace(spCaret.VirtualSpace() + 1); MovePositionTo(spCaret, Selection::selRectangle); } else { MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() + 1), 1), Selection::selRectangle); } SetLastXChosen(); break; case SCI_WORDLEFT: MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(sel.MainC